merging the Mainsoft branch to the trunk
[mono.git] / mcs / class / System.Data / System.Data / DataRowCollection.cs
index 135065ee768429a9fbd07080339cb020de3e5d39..a3cbb45804e42ff47a371aa07769f1472cb1c2ae 100644 (file)
 // (C) Copyright 2002 Daniel Morgan
 //
 
+//
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
 using System;
 using System.Collections;
 using System.ComponentModel;
+using System.Data.Common;
 
 namespace System.Data
 {
@@ -38,10 +62,10 @@ namespace System.Data
                public DataRow this[int index] 
                {
                        get { 
-                               if (index >= Count)
+                               if (index < 0 || index >= Count)
                                        throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
 
-                               return (DataRow) list[index]; 
+                               return (DataRow) List[index]; 
                        }
                }
 
@@ -50,7 +74,7 @@ namespace System.Data
                /// </summary>
                protected override ArrayList List 
                {
-                       get { return list; }
+                       get { return base.List; }
                }               
 
                /// <summary>
@@ -64,17 +88,26 @@ namespace System.Data
 
                        if (row.Table != this.table)
                                throw new ArgumentException ("This row already belongs to another table.");
-
-                       if (list.IndexOf(row) != -1)
+                       
+                       // If row id is not -1, we know that it is in the collection.
+                       if (row.RowID != -1)
                                throw new ArgumentException ("This row already belongs to this table.");
                        
+                       row.BeginEdit();
 
                        if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
                                // we have to check that the new row doesn't colide with existing row
                                ValidateDataRowInternal(row);
-                       
+
+                       AddInternal(row);
+               }
+
+               internal void AddInternal(DataRow row) {
+                       row.Table.ChangingDataRow (row, DataRowAction.Add);
                        row.HasParentCollection = true;
-                       list.Add (row);
+                       List.Add (row);
+                       // Set the row id.
+                       row.RowID = List.Count - 1;
                        row.AttachRow ();
                        row.Table.ChangedDataRow (row, DataRowAction.Add);
                }
@@ -82,11 +115,19 @@ namespace System.Data
                /// <summary>
                /// Creates a row using specified values and adds it to the DataRowCollection.
                /// </summary>
+#if NET_2_0
+               public virtual DataRow Add (params object[] values) 
+#else
                public virtual DataRow Add (object[] values) 
+#endif
                {
-                       DataRow row = table.NewRow ();
-                       row.ItemArray = values;
-                       Add (row);
+                       DataRow row = table.NewNotInitializedRow();
+                       int newRecord = table.CreateRecord(values);
+                       row.ImportRecord(newRecord);
+                       if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
+                               // we have to check that the new row doesn't colide with existing row
+                               ValidateDataRowInternal(row);
+                       AddInternal (row);
                        return row;
                }
 
@@ -95,21 +136,32 @@ namespace System.Data
                /// </summary>
                public void Clear () 
                {
-                       if (this.table.DataSet != null)
-                       {
-                               foreach (DataTable table in this.table.DataSet.Tables)
-                               {
-                                       foreach (Constraint c in table.Constraints)
-                                       {
-                                               if (c is ForeignKeyConstraint)
-                                               {
-                                                       if (((ForeignKeyConstraint) c).RelatedTable.Equals(this.table))
-                                                               throw new InvalidConstraintException("Cannot clear table Parent because ForeignKeyConstraint " + c.ConstraintName + " enforces Child.");
+                       if (this.table.DataSet != null && this.table.DataSet.EnforceConstraints) {
+                               foreach (DataTable table in this.table.DataSet.Tables) {
+                                       foreach (Constraint c in table.Constraints) {
+                                               if (c is ForeignKeyConstraint) {
+                                                                                                                ForeignKeyConstraint fk = (ForeignKeyConstraint) c;
+                                                       if (fk.RelatedTable.Equals(this.table) 
+                                                            && fk.Table.Rows.Count > 0) // check does not make sense if we don't have rows
+#if NET_1_1
+                                                               throw new InvalidConstraintException (String.Format ("Cannot clear table Parent" + 
+                                                                                                                     " because ForeignKeyConstraint "+
+                                                                                                                     "{0} enforces Child.", 
+                                                                                                                     c.ConstraintName));
+#else
+                                                               throw new ArgumentException (String.Format ("Cannot clear table Parent because " +
+                                                                                                            "ForeignKeyConstraint {0} enforces Child.", 
+                                                                                                            c.ConstraintName));
+#endif
                                                }
                                        }
                                }
                        }
-                       list.Clear ();
+                        // Remove from indexes
+                        for (int i = 0; i < this.Count; i++)
+                                this.table.DeleteRowFromIndexes (this [i]);
+
+                       List.Clear ();
                }
 
                /// <summary>
@@ -127,103 +179,63 @@ namespace System.Data
                /// </summary>
                public bool Contains (object[] keys) 
                {
-                       if (table.PrimaryKey.Length != keys.Length)
-                               throw new ArgumentException ("Expecting " + table.PrimaryKey.Length + " value(s) for the key " + 
-                                                            "being indexed, but received " + keys.Length + " value(s).");
-
                        return Find (keys) != null;
                }
 
                /// <summary>
                /// Gets the row specified by the primary key value.
                /// </summary>
-               [MonoTODO]
                public DataRow Find (object key) 
                {
-                       if (table.PrimaryKey.Length == 0)
-                               throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
-                       if (table.PrimaryKey.Length > 1)
-                               throw new ArgumentException ("Expecting " + table.PrimaryKey.Length + 
-                                                            " value(s) for the key being indexed, but received 1 value(s).");
-
-                       string primColumnName = table.PrimaryKey [0].ColumnName;
-                       Type coltype = null;
-                       object newKey = null;
-                       
-                       foreach (DataRow row in this) {
-                               
-                               if (row.RowState != DataRowState.Deleted)
-                               {
-                                       object primValue = row [primColumnName];
-                                       if (key == null) \r
-                                       {
-                                               if (primValue == null)
-                                                       return row;
-                                               else 
-                                                       continue;
-                                       }
-                                      
-                                       newKey = Convert.ChangeType (key, Type.GetTypeCode(primValue.GetType ()));
-
-                                       if (primValue.Equals (newKey))
-                                               return row;
-                               }
-                       }
-                                               
-                       // FIXME: is the correct value null?
-                       return null;
-               }
+                       return Find(new object[]{key});
+                }
 
                /// <summary>
                /// Gets the row containing the specified primary key values.
                /// </summary>
-               [MonoTODO]
-               public DataRow Find (object[] keys) 
+               public DataRow Find (object[] keys)
                {
                        if (table.PrimaryKey.Length == 0)
                                throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
 
-                       string  [] primColumnNames = new string [table.PrimaryKey.Length];
-                       
-                       for (int i = 0; i < primColumnNames.Length; i++)
-                               primColumnNames [i] = table.PrimaryKey [i].ColumnName;
+                       if (keys == null)
+                               throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 0 value(s).");
+                                                                                                    
+                       Index index = table.GetIndex(table.PrimaryKey,null,DataViewRowState.None,null,false);
 
-                       Type coltype = null;
-                       object newKey = null;
-                       
-                       foreach (DataRow row in this) {
-                               
-                               if (row.RowState != DataRowState.Deleted)
-                               {
-                                       bool eq = true;
-                                       for (int i = 0; i < keys.Length; i++) \r
-                                       {
-                                       
-                                               object primValue = row [primColumnNames [i]];
-                                               object keyValue = keys [i];
-                                               if (keyValue == null) \r
-                                               {
-                                                       if (primValue == null)
-                                                               return row;
-                                                       else 
-                                                               continue;
+                       int record = index.Find(keys);
+                       return (record != -1) ? table.RecordCache[record] : null;
+               }
+
+               internal DataRow Find(int index)
+               {
+                       DataColumn[] primaryKey = table.PrimaryKey;
+                       Index primaryKeyIndex = table.FindIndex(primaryKey);
+                       // if we can search through index
+                       if (primaryKeyIndex != null) {
+                               // get the child rows from the index
+                               int record = primaryKeyIndex.Find(index);
+                               if ( record != -1 ) {
+                                       return table.RecordCache[record];
+                               }
+                       }
+                       else {
+                               //loop through all collection rows                      
+                               foreach (DataRow row in this) {
+                                       if (row.RowState != DataRowState.Deleted) {
+                                               int rowIndex = row.IndexFromVersion(DataRowVersion.Default);
+                                               bool match = true;
+                                               for (int columnCnt = 0; columnCnt < primaryKey.Length; ++columnCnt) { 
+                                                       if (primaryKey[columnCnt].DataContainer.CompareValues(rowIndex, index) != 0) {
+                                                               match = false;
+                                                       }
+                                               }
+                                               if ( match ) {
+                                                       return row;
                                                }
-                                                                      
-                                               newKey = Convert.ChangeType (keyValue, Type.GetTypeCode(primValue.GetType ()));
-
-                                               if (!primValue.Equals (newKey)) \r
-                                               {
-                                                       eq = false;
-                                                       break;
-                                               }                                               
                                        }
-
-                                       if (eq)
-                                               return row;
                                }
                        }
-                                               
-                       // FIXME: is the correct value null?
                        return null;
                }
 
@@ -234,11 +246,52 @@ namespace System.Data
                {
                        if (pos < 0)
                                throw new IndexOutOfRangeException ("The row insert position " + pos + " is invalid.");
+                       
+                       if (row == null)
+                               throw new ArgumentNullException("row", "'row' argument cannot be null.");
+       
+                       if (row.Table != this.table)
+                               throw new ArgumentException ("This row already belongs to another table.");
+
+                       // If row id is not -1, we know that it is in the collection.
+                       if (row.RowID != -1)
+                               throw new ArgumentException ("This row already belongs to this table.");
+                       
+                       if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
+                               // we have to check that the new row doesn't colide with existing row
+                               ValidateDataRowInternal(row);
+                               
+                       row.Table.ChangingDataRow (row, DataRowAction.Add);
+
+                       if (pos >= List.Count) {
+                               row.RowID = List.Count;
+                               List.Add (row);
+                       }
+                       else {
+                               List.Insert (pos, row);
+                               row.RowID = pos;
+                               for (int i = pos+1; i < List.Count; i++) {
+                                       ((DataRow)List [i]).RowID = i;
+                               }
+                       }
                                
-                       if (pos >= list.Count)
-                               list.Add (row);
-                       else
-                               list.Insert (pos, row);
+                       row.HasParentCollection = true;
+                       row.AttachRow ();
+                       row.Table.ChangedDataRow (row, DataRowAction.Add);
+               }
+
+               /// <summary>
+               /// Removes the specified DataRow from the internal list. Used by DataRow to commit the removing.
+               /// </summary>
+               internal void RemoveInternal (DataRow row) {
+                       if (row == null) {
+                               throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
+                       }
+                       int index = List.IndexOf(row);
+                       if (index < 0) {
+                               throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
+                       }
+                       List.RemoveAt(index);
                }
 
                /// <summary>
@@ -246,12 +299,18 @@ namespace System.Data
                /// </summary>
                public void Remove (DataRow row) 
                {
-                       if (row == null)
-                               throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
-                       int index = list.IndexOf(row);
-                       if (index < 0)
+                       if (!List.Contains(row))
                                throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
-                       row.Delete();
+
+                       DataRowState state = row.RowState;
+                       if (state != DataRowState.Deleted &&
+                               state != DataRowState.Detached) {
+                               row.Delete();
+                               // if the row was in added state it will be in Detached state after the
+                               // delete operation, so we have to check it.
+                               if (row.RowState != DataRowState.Detached)
+                                       row.AcceptChanges();
+                       }
                }
 
                /// <summary>
@@ -259,11 +318,7 @@ namespace System.Data
                /// </summary>
                public void RemoveAt (int index) 
                {                       
-                       if (index < 0 || index >= list.Count)
-                               throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
-
-                       DataRow row = (DataRow)list [index];
-                       row.Delete();
+                       Remove(this[index]);
                }
 
                ///<summary>
@@ -273,17 +328,30 @@ namespace System.Data
                [MonoTODO]
                internal void ValidateDataRowInternal(DataRow row)
                {
-                       //FIXME: this validates constraints in the order they appear
-                       //in the collection. Most probably we need to do it in a 
-                       //specific order like unique/primary keys first, then Foreignkeys, etc
-                       foreach(Constraint constraint in table.Constraints)
-                       {
-                               constraint.AssertConstraint(row);
+                       //first check for null violations.
+                       row._nullConstraintViolation = true;
+                       row.CheckNullConstraints();
+
+                       int newRecord = (row.Proposed >= 0) ? row.Proposed : row.Current;
+                       if (newRecord < 0)
+                               return;
+
+                       foreach(Index index in table.Indexes) {
+                               index.Update(row,newRecord);
                        }
 
+                       foreach(Constraint constraint in table.Constraints) {
+                               try {
+                                       constraint.AssertConstraint(row);
+                               }
+                               catch(Exception e) {
+                                       // remove row from indexes
+                                       foreach(Index index in table.Indexes) {
+                                               index.Delete(newRecord);
+                                       }
+                                       throw e;
+                               }
+                       }
                }
-               
        }
-
-
 }