2003-03-27 Ville Palo <vi64pa@kolumbus.fi>
[mono.git] / mcs / class / System.Data / System.Data / UniqueConstraint.cs
1 //
2 // System.Data.UniqueConstraint.cs
3 //
4 // Author:
5 //   Franklin Wise <gracenote@earthlink.net>
6 //   Daniel Morgan <danmorg@sc.rr.com>
7 //   Tim Coleman (tim@timcoleman.com)
8 //   
9 // (C) 2002 Franklin Wise
10 // (C) 2002 Daniel Morgan
11 // Copyright (C) Tim Coleman, 2002
12
13 using System;
14 using System.Collections;
15 using System.ComponentModel;
16 using System.Runtime.InteropServices;
17
18 namespace System.Data {
19         [Editor]
20         [DefaultProperty ("ConstraintName")]
21         [Serializable]
22         public class UniqueConstraint : Constraint 
23         {
24                 private bool _isPrimaryKey = false;
25                 private bool __isPrimaryKey = false;
26                 private DataTable _dataTable; //set by ctor except when unique case
27                 
28                 private DataColumn [] _dataColumns;
29
30                 //TODO:provide helpers for this case
31                 private string [] _dataColumnNames; //unique case
32                 
33
34                 #region Constructors
35
36                 public UniqueConstraint (DataColumn column) 
37                 {
38                         _uniqueConstraint ("", column, false);
39                 }
40
41                 public UniqueConstraint (DataColumn[] columns) 
42                 {
43                         _uniqueConstraint ("", columns, false);
44                 }
45
46                 public UniqueConstraint (DataColumn column, bool isPrimaryKey) 
47                 {
48                         _uniqueConstraint ("", column, isPrimaryKey);
49                 }
50
51                 public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey) 
52                 {
53                         _uniqueConstraint ("", columns, isPrimaryKey);
54                 }
55
56                 public UniqueConstraint (string name, DataColumn column) 
57                 {
58                         _uniqueConstraint (name, column, false);
59                 }
60
61                 public UniqueConstraint (string name, DataColumn[] columns) 
62                 {
63                         _uniqueConstraint (name, columns, false);
64                 }
65
66                 public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey) 
67                 {
68                         _uniqueConstraint (name, column, isPrimaryKey);
69                 }
70
71                 public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey) 
72                 {
73                         _uniqueConstraint (name, columns, isPrimaryKey);
74                 }
75
76                 //Special case.  Can only be added to the Collection with AddRange
77                 [MonoTODO]
78                 [Browsable (false)]
79                 public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey) 
80                 {
81                         throw new NotImplementedException(); //need to finish related logic
82                         /*
83                         base.ConstraintName = name;
84                         
85                         //set unique
86                         //must set unique when added to the collection
87
88                         //keep list of names to resolve later
89                         _dataColumnNames = columnNames;
90
91                         _isPrimaryKey = isPrimaryKey;
92                         */
93                 }
94
95                 //helper ctor
96                 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey) 
97                 {
98                         //validate
99                         _validateColumn (column);
100
101                         //Set Constraint Name
102                         base.ConstraintName = name;
103
104                         __isPrimaryKey = isPrimaryKey;
105
106                         //keep reference 
107                         _dataColumns = new DataColumn [] {column};
108                         
109                         //Get table reference
110                         _dataTable = column.Table;
111                 }
112
113                 //helpter ctor  
114                 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey) 
115                 {
116                         //validate
117                         _validateColumns (columns, out _dataTable);
118
119                         //Set Constraint Name
120                         base.ConstraintName = name;
121
122                         //keep reference
123                         _dataColumns = columns;
124
125                         //PK?
126                         __isPrimaryKey = isPrimaryKey;
127                 }
128                 
129                 #endregion // Constructors
130
131                 #region Helpers
132                 
133                 private void _validateColumns(DataColumn [] columns)
134                 {
135                         DataTable table;
136                         _validateColumns(columns, out table);
137                 }
138                 
139                 //Validates a collection of columns with the ctor rules
140                 private void _validateColumns(DataColumn [] columns, out DataTable table) {
141                         table = null;
142
143                         //not null
144                         if (null == columns) throw new ArgumentNullException();
145                         
146                         //check that there is at least one column
147                         //LAMESPEC: not in spec
148                         if (columns.Length < 1)
149                                 throw new InvalidConstraintException("Must be at least one column.");
150                         
151                         DataTable compareTable = columns[0].Table;
152                         //foreach
153                         foreach (DataColumn col in columns){
154                                 
155                                 //check individual column rules
156                                 _validateColumn (col);
157                                 
158                                 
159                                 //check that columns are all from the same table??
160                                 //LAMESPEC: not in spec
161                                 if (compareTable != col.Table)
162                                         throw new InvalidConstraintException("Columns must be from the same table.");
163                                 
164                         }
165
166                         table = compareTable;
167                 }
168                 
169                 //validates a column with the ctor rules
170                 private void _validateColumn(DataColumn column) {
171         
172                         //not null
173                         if (null == column)  // FIXME: This is little weird, but here it goes...
174                                 throw new NullReferenceException("Object reference not set to an instance of an object.");
175
176                         
177                         //column must belong to a table
178                         //LAMESPEC: not in spec
179                         if (null == column.Table) {
180                                 string message = "Column ";
181                                 if (column.ColumnName != string.Empty)
182                                         message += column.ColumnName + " ";
183                                 
184                                 message += "must belong to a table.";
185                                 
186                                 throw new ArgumentException(message);
187                         }
188                         
189                 }
190
191                 /// <summary>
192                 ///  If IsPrimaryKey is set to be true, this sets it true
193                 /// </summary>
194                 internal void UpdatePrimaryKey ()
195                 {
196                         _isPrimaryKey = __isPrimaryKey;
197                         if (_isPrimaryKey) {
198                                 foreach (DataColumn Col in _dataColumns)
199                                         Col.SetUnique();
200                         }
201                 }
202
203                 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
204                 {
205                         //not null
206                         if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
207                         
208                         //make sure newPrimaryKey belongs to the collection parm unless it is null
209                         if (  collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) ) 
210                                 throw new ArgumentException("newPrimaryKey must belong to collection.");
211                         
212                         //Get existing pk
213                         UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
214                         
215                         //clear existing
216                         if (null != uc) uc._isPrimaryKey = false;
217
218                         //set new key
219                         if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
220                         
221                         
222                 }
223
224                 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
225                 {
226                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
227
228                         UniqueConstraint uc;
229                         IEnumerator enumer = collection.GetEnumerator();
230                         while (enumer.MoveNext())
231                         {
232                                 uc = enumer.Current as UniqueConstraint;
233                                 if (null == uc) continue;
234                                 
235                                 if (uc.IsPrimaryKey) return uc; 
236                         }
237
238                         //if we got here there was no pk
239                         return null;
240                         
241                 }
242
243                 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
244                                 DataColumn[] columns)
245                 {
246                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
247                         if (null == columns ) return null;
248                         
249                         UniqueConstraint uniqueConstraint;
250                         IEnumerator enumer = collection.GetEnumerator();
251                         while (enumer.MoveNext())
252                         {
253                                 uniqueConstraint = enumer.Current as UniqueConstraint;
254                                 if (uniqueConstraint != null)
255                                 {
256                                         if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
257                                         {
258                                                 return uniqueConstraint;
259                                         }
260                                 }
261                         }
262                         return null;
263                 }
264                         
265                 #endregion //Helpers
266
267                 #region Properties
268
269                 [DataCategory ("Data")]
270                 [DataSysDescription ("Indicates the columns of this constraint.")]
271                 [ReadOnly (true)]
272                 public virtual DataColumn[] Columns {
273                         get { return _dataColumns; }
274                 }
275
276                 [DataCategory ("Data")]
277                 [DataSysDescription ("Indicates if this constraint is a primary key.")]
278                 [ReadOnly (true)]
279                 public bool IsPrimaryKey {
280                         get { return _isPrimaryKey; }
281                 }
282
283                 [DataCategory ("Data")]
284                 [DataSysDescription ("Indicates the table of this constraint.")]
285                 [ReadOnly (true)]
286                 public override DataTable Table {
287                         get { return _dataTable; }
288                 }
289
290                 #endregion // Properties
291
292                 #region Methods
293
294                 public override bool Equals(object key2) {
295
296                         UniqueConstraint cst = key2 as UniqueConstraint;
297                         if (null == cst) return false;
298
299                         //according to spec if the cols are equal
300                         //then two UniqueConstraints are equal
301                         return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);      
302
303                 }
304
305                 public override int GetHashCode() 
306                 {
307                         //initialize hash with default value 
308                         int hash = 42;
309                         int i;
310
311                         //derive the hash code from the columns that way
312                         //Equals and GetHashCode return Equal objects to be the
313                         //same
314
315                         //Get the first column hash
316                         if (this.Columns.Length > 0)
317                                 hash ^= this.Columns[0].GetHashCode();
318                         
319                         //get the rest of the column hashes if there any
320                         for (i = 1; i < this.Columns.Length; i++)
321                         {
322                                 hash ^= this.Columns[1].GetHashCode();
323                                 
324                         }
325                         
326                         return hash ;
327                 }
328                 
329                 [MonoTODO]
330                 internal override void AddToConstraintCollectionSetup(
331                                 ConstraintCollection collection)
332                 {
333                         //run Ctor rules again
334                         _validateColumns(_dataColumns);
335                         
336                         //make sure a unique constraint doesn't already exists for these columns
337                         UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);       
338                         if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
339                                         " columns. Existing ConstraintName is " + uc.ConstraintName);
340
341                         //Allow only one primary key
342                         if (this.IsPrimaryKey)
343                         {
344                                 uc = GetPrimaryKeyConstraint(collection);
345                                 if (null != uc) uc._isPrimaryKey = false;
346
347                         }
348                                         
349                         //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
350                         //this method, so that it is executed twice. Need to investigate which
351                         // call to remove as that migth affect other parts of the classes.
352                         AssertConstraint();
353                 }
354                                         
355                 
356                 internal override void RemoveFromConstraintCollectionCleanup( 
357                                 ConstraintCollection collection)
358                 {
359                 }
360
361                 [MonoTODO]
362                 internal override void AssertConstraint()
363                 {
364                         
365                         if (_dataTable == null) return; //???
366                         if (_dataColumns == null) return; //???
367
368                         
369                         //Unique?       
370                         DataTable tbl = _dataTable;
371
372                         //TODO: Investigate other ways of speeding up the validation work below.
373                         //FIXME: This only works when only one DataColumn has been specified.
374
375                         //validate no duplicates exists.
376                         //Only validate when there are at least 2 rows
377                         //so that a duplicate migth exist.
378                         if(tbl.Rows.Count > 1) {
379                                 //get copy of rows collection first so that we do not modify the
380                                 //original.
381                                 DataRow[] rows = new DataRow [tbl.Rows.Count];
382                                 tbl.Rows.CopyTo (rows, 0);
383                                 ArrayList clonedDataList = new ArrayList (rows);
384
385                                 ArrayList newDataList = new ArrayList();
386
387                                 //copy to array list only the column we are interested in.
388                                 foreach (DataRow row in clonedDataList)
389                                         newDataList.Add (row[this._dataColumns[0]]);
390                                 
391                                 //sort ArrayList and check adjacent values for duplicates.
392                                 newDataList.Sort ();
393
394                                 for (int i = 0 ; i < newDataList.Count - 1 ; i++) 
395                                         if (newDataList[i].Equals (newDataList[i+1])) 
396                                                 throw new InvalidConstraintException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
397                         }
398
399
400                 }
401
402                 [MonoTODO]
403                 internal override void AssertConstraint(DataRow row)
404                 {
405
406                         if (_dataTable == null) return; //???
407                         if (_dataColumns == null) return; //???
408
409
410                         //Unique?
411                         DataTable tbl = _dataTable;
412
413                         foreach(DataRow compareRow in tbl.Rows)
414                         {
415                                 //skip if it is the same row to be validated
416                                 if(!row.Equals(compareRow))
417                                 {
418                                         if(compareRow.HasVersion (DataRowVersion.Original))
419                                         {
420                                                 //FIXME: should we compare to compareRow[DataRowVersion.Current]?
421                                                 //FIXME: We need to compare to all columns the constraint is set to.
422                                                 if(row[_dataColumns[0], DataRowVersion.Proposed].Equals( compareRow[_dataColumns[0], DataRowVersion.Current]))
423                                                 {
424                                                         string ExceptionMessage;
425                                                         ExceptionMessage = "Column '" + _dataColumns[0].ColumnName + "' is constrained to be unique.";
426                                                         ExceptionMessage += " Value '" + row[_dataColumns[0], DataRowVersion.Proposed].ToString();
427                                                         ExceptionMessage += "' is already present.";
428
429                                                         throw new ConstraintException (ExceptionMessage);
430                                                 }
431
432                                         }
433
434                 }
435
436                         }
437
438                 }
439
440                 #endregion // Methods
441         }
442 }