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