New test.
[mono.git] / mcs / class / System.Data / System.Data / DataColumnCollection.cs
1 //
2 // System.Data.DataColumnCollection.cs
3 //
4 // Author:
5 //   Christopher Podurgiel (cpodurgiel@msn.com)
6 //   Stuart Caborn      <stuart.caborn@virgin.net>
7 //   Tim Coleman (tim@timcoleman.com)
8 //
9 // (C) Chris Podurgiel
10 // Copyright (C) Tim Coleman, 2002
11 // Copyright (C) Daniel Morgan, 2003
12 //
13
14 //
15 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
16 //
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
24 // 
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
27 // 
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 //
36
37 using System;
38 using System.Text;
39 using System.Collections;
40 using System.ComponentModel;
41
42 namespace System.Data {
43         [Editor ("Microsoft.VSDesigner.Data.Design.ColumnsCollectionEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
44                  "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
45 #if !NET_2_0
46         [Serializable]
47 #endif
48         [DefaultEvent ("CollectionChanged")]
49         public
50 #if NET_2_0
51         sealed
52 #endif
53         class DataColumnCollection : InternalDataCollectionBase
54         {
55                 //This hashtable maps between column name to DataColumn object.
56                 private Hashtable columnFromName = new Hashtable();
57                 //This ArrayList contains the auto-increment columns names
58                 private ArrayList autoIncrement = new ArrayList();
59                 //This holds the next index to use for default column name.
60                 private int defaultColumnIndex = 1;
61                 //table should be the DataTable this DataColumnCollection belongs to.
62                 private DataTable parentTable = null;
63                 // Keep reference to most recent columns passed to AddRange()
64                 // so that they can be added when EndInit() is called.
65                 DataColumn[] _mostRecentColumns = null;
66
67                 // Internal Constructor.  This Class can only be created from other classes in this assembly.
68                 internal DataColumnCollection(DataTable table):base()
69                 {
70                         parentTable = table;
71                 }
72
73                 /// <summary>
74                 /// Gets the DataColumn from the collection at the specified index.
75                 /// </summary>
76                 public
77 #if !NET_2_0
78                 virtual
79 #endif
80                 DataColumn this[int index]
81                 {
82                         get
83                         {
84                                 if (index < 0 || index > base.List.Count) {
85                                         throw new IndexOutOfRangeException("Cannot find column " + index + ".");
86                                 }
87                                 return (DataColumn) base.List[index];
88                         }
89                 }
90
91                 /// <summary>
92                 /// Gets the DataColumn from the collection with the specified name.
93                 /// </summary>
94                 public
95 #if !NET_2_0
96                 virtual
97 #endif
98                 DataColumn this[string name]
99                 {
100                         get
101                         {
102                                 DataColumn dc = columnFromName[name] as DataColumn;
103                                 
104                                 if (dc != null)
105                                         return dc;
106
107                                 int tmp = IndexOf(name, true);
108                                 if (tmp == -1)
109                                         return null;
110                                 return this[tmp]; 
111                         }
112                 }
113
114                 /// <summary>
115                 /// Gets a list of the DataColumnCollection items.
116                 /// </summary>
117                 protected override ArrayList List 
118                 {
119                         get
120                         {
121                                 return base.List;
122                         }
123                 }
124
125                 internal ArrayList AutoIncrmentColumns 
126                 {
127                         get
128                         {
129                                 return autoIncrement;
130                         }
131                 }
132
133                 //Add Logic
134                 //
135                 //Changing Event
136                 //DefaultValue set and AutoInc set check
137                 //?Validate Expression??
138                 //Name check and creation
139                 //Set Table
140                 //Check Unique if true then add a unique constraint
141                 //?Notify Rows of new column ?
142                 //Add to collection
143                 //Changed Event
144
145                 /// <summary>
146                 /// Creates and adds a DataColumn object to the DataColumnCollection.
147                 /// </summary>
148                 /// <returns></returns>
149                 public
150 #if !NET_2_0
151                 virtual
152 #endif
153                 DataColumn Add()
154                 {
155                         string defaultName = GetNextDefaultColumnName ();
156                         DataColumn column = new DataColumn (defaultName);
157                         Add (column);
158                         return column;
159                 }
160
161                 public void CopyTo (DataColumn [] array, int index)
162                 {
163                         CopyTo ((Array) array, index);
164                 }
165
166                 internal void RegisterName(string name, DataColumn column)
167                 {
168                         if (columnFromName.Contains(name))
169                                 throw new DuplicateNameException("A DataColumn named '" + name + "' already belongs to this DataTable.");
170
171                         columnFromName[name] = column;
172
173                         if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex + 1))
174                         {
175                                 do
176                                 {
177                                         defaultColumnIndex++;
178                                 }
179                                 while (Contains(MakeName(defaultColumnIndex + 1)));
180                         }
181                 }
182
183                 internal void UnregisterName(string name)
184                 {
185                         if (columnFromName.Contains(name))
186                                 columnFromName.Remove(name);
187
188                         if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex - 1))
189                         {
190                                 do
191                                 {
192                                         defaultColumnIndex--;
193                                 }
194                                 while (!Contains(MakeName(defaultColumnIndex - 1)) && defaultColumnIndex > 1);
195                         }
196                 }
197
198                 private string GetNextDefaultColumnName ()
199                 {
200                         string defColumnName = MakeName(defaultColumnIndex);
201                         for (int index = defaultColumnIndex + 1; Contains(defColumnName); ++index) {
202                                 defColumnName = MakeName(index);
203                                 defaultColumnIndex++;
204                         }
205                         defaultColumnIndex++;
206                         return defColumnName;
207                 }
208
209                 static readonly string[] TenColumns = { "Column0", "Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9" };
210
211                 private string MakeName(int index)
212                 {
213                         if (index < 10)
214                                 return TenColumns[index];
215
216                         return String.Concat("Column", index.ToString());
217                 }
218
219                 /// <summary>
220                 /// Creates and adds the specified DataColumn object to the DataColumnCollection.
221                 /// </summary>
222                 /// <param name="column">The DataColumn to add.</param>
223                 public void Add(DataColumn column)
224                 {
225
226                         if (column == null)
227                                 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
228
229                         if (column.ColumnName.Equals(String.Empty))
230                         {
231                                 column.ColumnName = GetNextDefaultColumnName ();
232                         }
233
234 //                      if (Contains(column.ColumnName))
235 //                              throw new DuplicateNameException("A DataColumn named '" + column.ColumnName + "' already belongs to this DataTable.");
236
237                         if (column.Table != null)
238                                 throw new ArgumentException ("Column '" + column.ColumnName + "' already belongs to this or another DataTable.");
239
240                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
241
242                         column.SetTable (parentTable);
243                         RegisterName(column.ColumnName, column);
244                         int ordinal = base.List.Add(column);
245 #if NET_2_0
246                         column.Ordinal = ordinal;
247 #else
248                         column.SetOrdinal (ordinal);
249 #endif
250                 
251                         // Check if the Column Expression is ok 
252                         if (column.CompiledExpression != null)
253                                 if (parentTable.Rows.Count == 0)
254                                         column.CompiledExpression.Eval (parentTable.NewRow());
255                                 else
256                                         column.CompiledExpression.Eval (parentTable.Rows[0]);
257
258                         // if table already has rows we need to allocate space 
259                         // in the column data container 
260                         if ( parentTable.Rows.Count > 0 ) {
261                                 column.DataContainer.Capacity = parentTable.RecordCache.CurrentCapacity;
262                         }
263
264                         if (column.AutoIncrement) {
265                                 DataRowCollection rows = column.Table.Rows;
266                                 for (int i = 0; i < rows.Count; i++)
267                                         rows [i] [ordinal] = column.AutoIncrementValue ();
268                         }
269
270                         if (column.AutoIncrement)
271                                 autoIncrement.Add(column);
272
273                         OnCollectionChanged (e);
274                 }
275
276                 /// <summary>
277                 /// Creates and adds a DataColumn object with the specified name to the DataColumnCollection.
278                 /// </summary>
279                 /// <param name="columnName">The name of the column.</param>
280                 /// <returns>The newly created DataColumn.</returns>
281                 public
282 #if !NET_2_0
283                 virtual
284 #endif
285                 DataColumn Add(string columnName)
286                 {
287                         if (columnName == null || columnName == String.Empty)
288                         {
289                                 columnName = GetNextDefaultColumnName();
290                         }
291                         
292                         DataColumn column = new DataColumn(columnName);
293                         Add (column);
294                         return column;
295                 }
296
297                 /// <summary>
298                 /// Creates and adds a DataColumn object with the specified name and type to the DataColumnCollection.
299                 /// </summary>
300                 /// <param name="columnName">The ColumnName to use when cretaing the column.</param>
301                 /// <param name="type">The DataType of the new column.</param>
302                 /// <returns>The newly created DataColumn.</returns>
303                 public
304 #if !NET_2_0
305                 virtual
306 #endif
307                 DataColumn Add(string columnName, Type type)
308                 {
309                         if (columnName == null || columnName == "")
310                         {
311                                 columnName = GetNextDefaultColumnName ();
312                         }
313                         
314                         DataColumn column = new DataColumn(columnName, type);
315                         Add (column);
316                         return column;
317                 }
318
319                 /// <summary>
320                 /// Creates and adds a DataColumn object with the specified name, type, and expression to the DataColumnCollection.
321                 /// </summary>
322                 /// <param name="columnName">The name to use when creating the column.</param>
323                 /// <param name="type">The DataType of the new column.</param>
324                 /// <param name="expression">The expression to assign to the Expression property.</param>
325                 /// <returns>The newly created DataColumn.</returns>
326                 public
327 #if !NET_2_0
328                 virtual
329 #endif
330                 DataColumn Add(string columnName, Type type, string expression)
331                 {
332                         if (columnName == null || columnName == "")
333                         {
334                                 columnName = GetNextDefaultColumnName ();
335                         }
336                         
337                         DataColumn column = new DataColumn(columnName, type, expression);
338                         Add (column);
339                         return column;
340                 }
341
342                 /// <summary>
343                 /// Copies the elements of the specified DataColumn array to the end of the collection.
344                 /// </summary>
345                 /// <param name="columns">The array of DataColumn objects to add to the collection.</param>
346                 public void AddRange(DataColumn[] columns)
347                 {
348                         if (parentTable.InitInProgress){
349                                 _mostRecentColumns = columns;
350                                 return;
351                         }
352
353                         if (columns == null)
354                                 return;
355
356                         foreach (DataColumn column in columns){
357                                 if (column == null)
358                                         continue;
359                                 Add(column);
360                         }
361                 }
362
363                 private string GetColumnDependency (DataColumn column)
364                 {
365
366                         foreach (DataRelation rel in parentTable.ParentRelations)
367                                 if (Array.IndexOf (rel.ChildColumns, column) != -1)
368                                         return String.Format (" child key for relationship {0}.", rel.RelationName);
369                         foreach (DataRelation rel in parentTable.ChildRelations)
370                                 if (Array.IndexOf (rel.ParentColumns, column) != -1)
371                                         return String.Format (" parent key for relationship {0}.", rel.RelationName);
372
373                         foreach (Constraint c in parentTable.Constraints) 
374                                 if (c.IsColumnContained (column))
375                                         return String.Format (" constraint {0} on the table {1}.", 
376                                                         c.ConstraintName, parentTable);
377                         
378                         
379                         // check if the foreign-key constraint on any table in the dataset refers to this column.
380                         // though a forignkeyconstraint automatically creates a uniquecontrainton the parent 
381                         // table and would fail above, but we still need to check, as it is legal to manually remove
382                         // the constraint on the parent table.
383                         if (parentTable.DataSet != null)
384                                 foreach (DataTable table in parentTable.DataSet.Tables)
385                                         foreach (Constraint c in table.Constraints)
386                                                 if (c is ForeignKeyConstraint && c.IsColumnContained(column))
387                                                         return String.Format (" constraint {0} on the table {1}.", 
388                                                                         c.ConstraintName, table.TableName);
389                         
390                         foreach (DataColumn col in this) 
391                                 if (col.CompiledExpression != null && col.CompiledExpression.DependsOn (column))
392                                         return  col.Expression;
393                         return String.Empty;
394                 }
395
396                 /// <summary>
397                 /// Checks whether a given column can be removed from the collection.
398                 /// </summary>
399                 /// <param name="column">A DataColumn in the collection.</param>
400                 /// <returns>true if the column can be removed; otherwise, false.</returns>
401                 public bool CanRemove(DataColumn column)
402                 {
403                         if (column == null || column.Table != parentTable || GetColumnDependency(column) != String.Empty) 
404                                 return false;
405                         return true;
406                 }
407
408                 /// <summary>
409                 /// Clears the collection of any columns.
410                 /// </summary>
411                 public void Clear()
412                 {
413                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
414
415                         // its not necessary to check if each column in the collection can removed.
416                         // Can simply check, if there are any constraints/relations related to the table,
417                         // in which case, throw an exception.
418                         // Also, shudnt check for expression columns since all the columns in the table
419                         // are being removed.
420                         if (parentTable.Constraints.Count != 0 || 
421                             parentTable.ParentRelations.Count != 0 ||
422                             parentTable.ChildRelations.Count != 0)
423                                 foreach (DataColumn col in this) {
424                                         string s = GetColumnDependency (col);
425                                         if (s != String.Empty)
426                                                 throw new ArgumentException (
427                                                                 "Cannot remove this column, because it is part of the"+ s);
428                                 }
429
430                         if (parentTable.DataSet != null)
431                                 foreach (DataTable table in parentTable.DataSet.Tables)
432                                         foreach (Constraint c in table.Constraints) {
433                                                 if (!(c is ForeignKeyConstraint) ||
434                                                     ((ForeignKeyConstraint)c).RelatedTable != parentTable)
435                                                         continue;
436                                                 throw new ArgumentException (String.Format ("Cannot remove this column, " +
437                                                                         "because it is part of the constraint {0} on " +
438                                                                         "the table {1}", c.ConstraintName, table.TableName));
439                                         }
440                         
441                         foreach (DataColumn col in this)
442                                 col.ResetColumnInfo ();
443
444                         columnFromName.Clear();
445                         autoIncrement.Clear();
446                         base.List.Clear();
447                         OnCollectionChanged(e);
448                 }
449
450                 /// <summary>
451                 /// Checks whether the collection contains a column with the specified name.
452                 /// </summary>
453                 /// <param name="name">The ColumnName of the column to check for.</param>
454                 /// <returns>true if a column exists with this name; otherwise, false.</returns>
455                 public bool Contains(string name)
456                 {
457                         if (columnFromName.Contains(name))
458                                 return true;
459                         
460                         return (IndexOf(name, false) != -1);
461                 }
462
463                 /// <summary>
464                 /// Gets the index of a column specified by name.
465                 /// </summary>
466                 /// <param name="column">The name of the column to return.</param>
467                 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
468                 public
469 #if !NET_2_0
470                 virtual
471 #endif
472                 int IndexOf(DataColumn column)
473                 {
474                         if (column == null)
475                                 return -1;
476                         return base.List.IndexOf(column);
477                 }
478
479                 /// <summary>
480                 /// Gets the index of the column with the given name (the name is not case sensitive).
481                 /// </summary>
482                 /// <param name="columnName">The name of the column to find.</param>
483                 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
484                 public int IndexOf(string columnName)
485                 {
486                         if (columnName == null)
487                                 return -1;
488                         DataColumn dc = columnFromName[columnName] as DataColumn;
489                                 
490                         if (dc != null)
491                                 return IndexOf(dc);
492
493                         return IndexOf(columnName, false);
494                 }
495
496                 /// <summary>
497                 /// Raises the OnCollectionChanged event.
498                 /// </summary>
499                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
500 #if !NET_2_0
501                 protected virtual
502 #else
503                 internal
504 #endif
505                 void OnCollectionChanged(CollectionChangeEventArgs ccevent)
506                 {
507                         parentTable.ResetPropertyDescriptorsCache();
508                         if (CollectionChanged != null) 
509                                 CollectionChanged(this, ccevent);
510                 }
511
512                 /// <summary>
513                 /// Raises the OnCollectionChanging event.
514                 /// </summary>
515                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
516 #if !NET_2_0
517                 protected internal virtual
518 #else
519                 internal
520 #endif
521                 void OnCollectionChanging(CollectionChangeEventArgs ccevent)
522                 {
523                         if (CollectionChanged != null) {
524                                 //FIXME: this is not right
525                                 //CollectionChanged(this, ccevent);
526                                 throw new NotImplementedException();
527                         }
528                 }
529
530                 /// <summary>
531                 /// Removes the specified DataColumn object from the collection.
532                 /// </summary>
533                 /// <param name="column">The DataColumn to remove.</param>
534                 public void Remove(DataColumn column)
535                 {
536                         if (column == null)
537                                 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
538
539                         if (!Contains(column.ColumnName))
540                                 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
541
542                         string dependency = GetColumnDependency (column);
543                         if (dependency != String.Empty)
544                                 throw new ArgumentException ("Cannot remove this column, because it is part of " + dependency);
545
546                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
547                         
548                         int ordinal = column.Ordinal;
549                         UnregisterName(column.ColumnName);
550                         base.List.Remove(column);
551                         
552                         // Reset column info
553                         column.ResetColumnInfo ();
554         
555                         //Update the ordinals
556                         for( int i = ordinal ; i < this.Count ; i ++ )
557 #if NET_2_0
558                                 this[i].Ordinal = i;
559 #else
560                                 this[i].SetOrdinal(i);
561 #endif
562
563                         if (parentTable != null)
564                                 parentTable.OnRemoveColumn(column);
565
566                         if (column.AutoIncrement)
567                                 autoIncrement.Remove(column);
568
569                         OnCollectionChanged(e);
570                 }
571
572                 /// <summary>
573                 /// Removes the DataColumn object with the specified name from the collection.
574                 /// </summary>
575                 /// <param name="name">The name of the column to remove.</param>
576                 public void Remove(string name)
577                 {
578                         DataColumn column = this[name];
579                         
580                         if (column == null)
581                                 throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
582                         Remove(column);
583                 }
584
585                 /// <summary>
586                 /// Removes the column at the specified index from the collection.
587                 /// </summary>
588                 /// <param name="index">The index of the column to remove.</param>
589                 public void RemoveAt(int index)
590                 {
591                         if (Count <= index)
592                                 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
593
594                         DataColumn column = this[index];
595                         Remove(column);
596                 }
597
598                 // Helper AddRange() - Call this function when EndInit is called
599                 internal void PostAddRange ()
600                 {
601                         if (_mostRecentColumns == null)
602                                 return;
603
604                         foreach (DataColumn column in _mostRecentColumns){
605                                 if (column == null)
606                                         continue;
607                                 Add (column);
608                         }
609                         _mostRecentColumns = null;
610                 }
611
612
613                 /// <summary>
614                 ///  Do the same as Constains -method but case sensitive
615                 /// </summary>
616                 private bool CaseSensitiveContains(string columnName)
617                 {
618                         DataColumn column = this[columnName];
619                         
620                         if (column != null)
621                                 return string.Compare(column.ColumnName, columnName, false) == 0; 
622
623                         return false;
624                 }
625
626                 internal void UpdateAutoIncrement(DataColumn col,bool isAutoIncrement)
627                 {
628                         if (isAutoIncrement)
629                         {
630                                 if (!autoIncrement.Contains(col))
631                                         autoIncrement.Add(col);
632                         }
633                         else
634                         {
635                                 if (autoIncrement.Contains(col))
636                                         autoIncrement.Remove(col);
637                         }
638                 }
639
640                 private int IndexOf (string name, bool error)
641                 {
642                         int count = 0, match = -1;
643                         for (int i = 0; i < List.Count; i++)
644                         {
645                                 String name2 = ((DataColumn) List[i]).ColumnName;
646                                 if (String.Compare (name, name2, true) == 0)
647                                 {
648                                         if (String.Compare (name, name2, false) == 0)
649                                                 return i;
650                                         match = i;
651                                         count++;
652                                 }
653                         }
654                         if (count == 1)
655                                 return match;
656                         if (count > 1 && error)
657                                 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
658                         return -1;
659                 }
660                 
661                 #region Events
662
663                 /// <summary>
664                 /// Occurs when the columns collection changes, either by adding or removing a column.
665                 /// </summary>
666                 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")] 
667                 public event CollectionChangeEventHandler CollectionChanged;
668
669                 #endregion 
670                 
671 #if NET_2_0
672                 internal void MoveColumn (int oldOrdinal, int newOrdinal)
673                 {
674                         if (newOrdinal == -1 || newOrdinal > this.Count)
675                                 throw new ArgumentOutOfRangeException ("ordinal", "Ordinal '" + newOrdinal + "' exceeds the maximum number.");
676                         if (oldOrdinal == newOrdinal)
677                                 return;
678                         
679                         int start = newOrdinal > oldOrdinal ? oldOrdinal : newOrdinal;
680                         int end = newOrdinal > oldOrdinal ?  newOrdinal : oldOrdinal;
681                         int direction = newOrdinal > oldOrdinal ? 1 : (-1);
682
683                         DataColumn currColumn = this [start];
684                         for (int i=start; i < end; i+=direction) {
685                                 List [i] = List [i+direction];
686                                 ((DataColumn)List [i]).Ordinal = i;
687                         }
688                         List [end] = currColumn;
689                         currColumn.Ordinal = end;
690                 }
691 #endif
692         }
693 }