new error tests
[mono.git] / mcs / class / System.Data / System.Data / DataRowCollection.cs
1 //
2 // System.Data.DataRowCollection.cs
3 //
4 // Author:
5 //   Daniel Morgan <danmorg@sc.rr.com>
6 //   Tim Coleman <tim@timcoleman.com>
7 //
8 // (C) Ximian, Inc 2002
9 // (C) Copyright 2002 Tim Coleman
10 // (C) Copyright 2002 Daniel Morgan
11 //
12
13 //
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System;
37 using System.Collections;
38 using System.ComponentModel;
39
40 namespace System.Data
41 {
42         /// <summary>
43         /// Collection of DataRows in a DataTable
44         /// </summary>
45         [Serializable]
46         public class DataRowCollection : InternalDataCollectionBase 
47         {
48                 private DataTable table;
49
50                 /// <summary>
51                 /// Internal constructor used to build a DataRowCollection.
52                 /// </summary>
53                 internal DataRowCollection (DataTable table) : base ()
54                 {
55                         this.table = table;
56                 }
57
58                 /// <summary>
59                 /// Gets the row at the specified index.
60                 /// </summary>
61                 public DataRow this[int index] 
62                 {
63                         get { 
64                                 if (index >= Count)
65                                         throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
66
67                                 return (DataRow) List[index]; 
68                         }
69                 }
70
71                 /// <summary>
72                 /// This member overrides InternalDataCollectionBase.List
73                 /// </summary>
74                 protected override ArrayList List 
75                 {
76                         get { return base.List; }
77                 }               
78
79                 /// <summary>
80                 /// Adds the specified DataRow to the DataRowCollection object.
81                 /// </summary>
82                 public void Add (DataRow row) 
83                 {
84                         //TODO: validation
85                         if (row == null)
86                                 throw new ArgumentNullException("row", "'row' argument cannot be null.");
87
88                         if (row.Table != this.table)
89                                 throw new ArgumentException ("This row already belongs to another table.");
90                         
91                         // If row id is not -1, we know that it is in the collection.
92                         if (row.RowID != -1)
93                                 throw new ArgumentException ("This row already belongs to this table.");
94                         
95
96                         if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
97                                 // we have to check that the new row doesn't colide with existing row
98                                 ValidateDataRowInternal(row);
99                         
100                         row.Table.ChangingDataRow (row, DataRowAction.Add);
101                         row.HasParentCollection = true;
102                         List.Add (row);
103                         // Set the row id.
104                         row.RowID = List.Count - 1;
105                         row.AttachRow ();
106                         row.Table.ChangedDataRow (row, DataRowAction.Add);
107                 }
108
109                 /// <summary>
110                 /// Creates a row using specified values and adds it to the DataRowCollection.
111                 /// </summary>
112 #if NET_2_0
113                 public virtual DataRow Add (params object[] values) 
114 #else
115                 public virtual DataRow Add (object[] values) 
116 #endif
117                 {
118                         if (values.Length > table.Columns.Count)
119                                 throw new ArgumentException ("The array is larger than the number of columns in the table.");
120                         DataRow row = table.NewRow ();
121                         row.ItemArray = values;
122                         Add (row);
123                         return row;
124                 }
125
126                 /// <summary>
127                 /// Clears the collection of all rows.
128                 /// </summary>
129                 public void Clear () 
130                 {
131                         if (this.table.DataSet != null && this.table.DataSet.EnforceConstraints) {
132                                 foreach (DataTable table in this.table.DataSet.Tables) {
133                                         foreach (Constraint c in table.Constraints) {
134                                                 if (c is ForeignKeyConstraint) {
135                                                                                                                 ForeignKeyConstraint fk = (ForeignKeyConstraint) c;
136                                                         if (fk.RelatedTable.Equals(this.table) 
137                                                             && fk.Table.Rows.Count > 0) // check does not make sense if we don't have rows
138 #if NET_1_1
139                                                                 throw new InvalidConstraintException (String.Format ("Cannot clear table Parent" + 
140                                                                                                                      " because ForeignKeyConstraint "+
141                                                                                                                      "{0} enforces Child.", 
142                                                                                                                      c.ConstraintName));
143 #else
144                                                                 throw new ArgumentException (String.Format ("Cannot clear table Parent because " +
145                                                                                                             "ForeignKeyConstraint {0} enforces Child.", 
146                                                                                                             c.ConstraintName));
147 #endif
148                                                 }
149                                         }
150                                 }
151                         }
152                         List.Clear ();
153                 }
154
155                 /// <summary>
156                 /// Gets a value indicating whether the primary key of any row in the collection contains
157                 /// the specified value.
158                 /// </summary>
159                 public bool Contains (object key) 
160                 {
161                         return Find (key) != null;
162                 }
163
164                 /// <summary>
165                 /// Gets a value indicating whether the primary key column(s) of any row in the 
166                 /// collection contains the values specified in the object array.
167                 /// </summary>
168                 public bool Contains (object[] keys) 
169                 {
170                         if (table.PrimaryKey.Length != keys.Length)
171                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length + " value(s) for the key " + 
172                                                              "being indexed, but received " + keys.Length + " value(s).");
173
174                         return Find (keys) != null;
175                 }
176
177                 /// <summary>
178                 /// Gets the row specified by the primary key value.
179                 /// </summary>
180                 public DataRow Find (object key) 
181                 {
182                         if (table.PrimaryKey.Length == 0)
183                                 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
184                         if (table.PrimaryKey.Length > 1)
185                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 1 value(s).");
186
187                         if (key == null)
188 #if NET_1_1
189                                 return null;
190 #else
191                                 throw new ArgumentException("Expecting 1 value(s) for the key being indexed, but received 0 value(s).");
192 #endif
193
194                         DataColumn primaryKey = table.PrimaryKey[0];
195                         Index primaryKeyIndex = table.GetIndexByColumns(table.PrimaryKey);
196                         int tmpRecord = table.RecordCache.NewRecord();
197                         try {
198                                 primaryKey.DataContainer[tmpRecord] = key;
199
200                                 // if we can search through index
201                                 if (primaryKeyIndex != null) {
202                                         // get the child rows from the index
203                                         Node node = primaryKeyIndex.FindSimple(tmpRecord,1,true);
204                                         if (node != null) {
205                                                 return node.Row;
206                                         }
207                                 }
208                         
209                                 //loop through all collection rows                      
210                                 foreach (DataRow row in this) {
211                                         if (row.RowState != DataRowState.Deleted) {
212                                                 int index = row.IndexFromVersion(DataRowVersion.Default); 
213                                                 if (primaryKey.DataContainer.CompareValues(index, tmpRecord) == 0) {
214                                                         return row;
215                                                 }
216                                         }
217                                 }
218                                 return null;
219                         }
220                         finally {
221                                 table.RecordCache.DisposeRecord(tmpRecord);
222                         }
223                 }
224
225                 /// <summary>
226                 /// Gets the row containing the specified primary key values.
227                 /// </summary>
228                 public DataRow Find (object[] keys) 
229                 {
230                         if (table.PrimaryKey.Length == 0)
231                                 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
232
233                         if (keys == null)
234                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 0 value(s).");
235                         if (table.PrimaryKey.Length != keys.Length)
236                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received " + keys.Length +  " value(s).");
237                                                                                                     
238                         DataColumn[] primaryKey = table.PrimaryKey;
239                         int tmpRecord = table.RecordCache.NewRecord();
240                         try {
241                                 int numColumn = keys.Length;
242                                 for (int i = 0; i < numColumn; i++) {
243                                         // according to MSDN: the DataType value for both columns must be identical.
244                                         primaryKey[i].DataContainer[tmpRecord] = keys[i];
245                                 }
246                                 return Find(tmpRecord,numColumn);
247                         }
248                         finally {
249                                 table.RecordCache.DisposeRecord(tmpRecord);
250                         }
251                 }
252
253                 internal DataRow Find(int index, int length)
254                 {
255                         DataColumn[] primaryKey = table.PrimaryKey;
256                         Index primaryKeyIndex = table.GetIndexByColumns(primaryKey);
257                         // if we can search through index
258                         if (primaryKeyIndex != null) {
259                                 // get the child rows from the index
260                                 Node node = primaryKeyIndex.FindSimple(index,length,true);
261                                 if ( node != null ) {
262                                         return node.Row;
263                                 }
264                         }
265                 
266                         //loop through all collection rows                      
267                         foreach (DataRow row in this) {
268                                 if (row.RowState != DataRowState.Deleted) {
269                                         int rowIndex = row.IndexFromVersion(DataRowVersion.Default);
270                                         bool match = true;
271                                         for (int columnCnt = 0; columnCnt < length; ++columnCnt) { 
272                                                 if (primaryKey[columnCnt].DataContainer.CompareValues(rowIndex, index) != 0) {
273                                                         match = false;
274                                                 }
275                                         }
276                                         if ( match ) {
277                                                 return row;
278                                         }
279                                 }
280                         }
281                         return null;
282                 }
283
284                 /// <summary>
285                 /// Inserts a new row into the collection at the specified location.
286                 /// </summary>
287                 public void InsertAt (DataRow row, int pos) 
288                 {
289                         if (pos < 0)
290                                 throw new IndexOutOfRangeException ("The row insert position " + pos + " is invalid.");
291                         
292                         if (row == null)
293                                 throw new ArgumentNullException("row", "'row' argument cannot be null.");
294         
295                         if (row.Table != this.table)
296                                 throw new ArgumentException ("This row already belongs to another table.");
297
298                         // If row id is not -1, we know that it is in the collection.
299                         if (row.RowID != -1)
300                                 throw new ArgumentException ("This row already belongs to this table.");
301                         
302                         if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
303                                 // we have to check that the new row doesn't colide with existing row
304                                 ValidateDataRowInternal(row);
305                                 
306                         row.Table.ChangingDataRow (row, DataRowAction.Add);
307
308                         if (pos >= List.Count) {
309                                 row.RowID = List.Count;
310                                 List.Add (row);
311                         }
312                         else {
313                                 List.Insert (pos, row);
314                                 row.RowID = pos;
315                                 for (int i = pos+1; i < List.Count; i++) {
316                                         ((DataRow)List [i]).RowID = i;
317                                 }
318                         }
319                                 
320                         row.HasParentCollection = true;
321                         row.AttachRow ();
322                         row.Table.ChangedDataRow (row, DataRowAction.Add);
323                 }
324
325                 /// <summary>
326                 /// Removes the specified DataRow from the internal list. Used by DataRow to commit the removing.
327                 /// </summary>
328                 internal void RemoveInternal (DataRow row) {
329                         if (row == null) {
330                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
331                         }
332                         int index = List.IndexOf(row);
333                         if (index < 0) {
334                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
335                         }
336                         List.RemoveAt(index);
337                 }
338
339                 /// <summary>
340                 /// Removes the specified DataRow from the collection.
341                 /// </summary>
342                 public void Remove (DataRow row) 
343                 {
344                         if (row == null)
345                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
346                         int index = List.IndexOf(row);
347                         if (index < 0)
348                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
349                         row.Delete();
350                         // if the row was in added state it will be in Detached state after the
351                         // delete operation, so we have to check it.
352                         if (row.RowState != DataRowState.Detached)
353                                 row.AcceptChanges();
354                 }
355
356                 /// <summary>
357                 /// Removes the row at the specified index from the collection.
358                 /// </summary>
359                 public void RemoveAt (int index) 
360                 {                       
361                         if (index < 0 || index >= List.Count)
362                                 throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
363                         DataRow row = (DataRow)List [index];
364                         row.Delete();
365                         // if the row was in added state it will be in Detached state after the
366                         // delete operation, so we have to check it.
367                         if (row.RowState != DataRowState.Detached)
368                                 row.AcceptChanges();
369                 }
370
371                 ///<summary>
372                 ///Internal method used to validate a given DataRow with respect
373                 ///to the DataRowCollection
374                 ///</summary>
375                 [MonoTODO]
376                 internal void ValidateDataRowInternal(DataRow row)
377                 {
378                         //first check for null violations.
379                         row._nullConstraintViolation = true;
380                         row.CheckNullConstraints();
381                         // This validates constraints in the specific order : 
382                         // first unique/primary keys first, then Foreignkeys, etc
383                         ArrayList uniqueConstraintsDone = new ArrayList();
384                         ArrayList foreignKeyConstraintsDone = new ArrayList();
385                         try {
386                                 foreach(Constraint constraint in table.Constraints.UniqueConstraints) {
387                                         constraint.AssertConstraint(row);
388                                         uniqueConstraintsDone.Add(constraint);
389                                 }
390                         
391                                 foreach(Constraint constraint in table.Constraints.ForeignKeyConstraints) {
392                                         constraint.AssertConstraint(row);
393                                         foreignKeyConstraintsDone.Add(constraint);
394                                 }
395                         }
396                         // if one of the AssertConstraint failed - we need to "rollback" all the changes
397                         // caused by AssertCoinstraint calls already succeeded
398                         catch(ConstraintException e) {
399                                 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
400                                 throw e;
401                         }
402                         catch(InvalidConstraintException e) {   
403                                 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
404                                 throw e;
405                         }
406                 }
407
408                 private void RollbackAsserts(DataRow row,ICollection foreignKeyConstraintsDone,
409                         ICollection uniqueConstraintsDone)
410                 {
411                         // if any of constraints assert failed - 
412                         // we have to rollback all the asserts scceeded
413                         // on order reverse to thier original execution
414                         foreach(Constraint constraint in foreignKeyConstraintsDone) {
415                                 constraint.RollbackAssert(row);
416                         }
417
418                         foreach(Constraint constraint in uniqueConstraintsDone) {
419                                 constraint.RollbackAssert(row);
420                         }
421                 }
422         }
423 }