Locale.cs removed
[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                                 throw new ArgumentException ("Column must belong to a table.");                 
181                 }
182
183                 /// <summary>
184                 ///  If IsPrimaryKey is set to be true, this sets it true
185                 /// </summary>
186                 internal void UpdatePrimaryKey ()
187                 {
188                         _isPrimaryKey = __isPrimaryKey;
189                         if (_isPrimaryKey) {
190                                 foreach (DataColumn Col in _dataColumns)
191                                         Col.SetUnique();
192                         }
193                 }
194
195                 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
196                 {
197                         //not null
198                         if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
199                         
200                         //make sure newPrimaryKey belongs to the collection parm unless it is null
201                         if (  collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) ) 
202                                 throw new ArgumentException("newPrimaryKey must belong to collection.");
203                         
204                         //Get existing pk
205                         UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
206                         
207                         //clear existing
208                         if (null != uc) uc._isPrimaryKey = false;
209
210                         //set new key
211                         if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
212                         
213                         
214                 }
215
216                 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
217                 {
218                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
219
220                         UniqueConstraint uc;
221                         IEnumerator enumer = collection.GetEnumerator();
222                         while (enumer.MoveNext())
223                         {
224                                 uc = enumer.Current as UniqueConstraint;
225                                 if (null == uc) continue;
226                                 
227                                 if (uc.IsPrimaryKey) return uc; 
228                         }
229
230                         //if we got here there was no pk
231                         return null;
232                         
233                 }
234
235                 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
236                                 DataColumn[] columns)
237                 {
238                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
239                         if (null == columns ) return null;
240                         
241                         UniqueConstraint uniqueConstraint;
242                         IEnumerator enumer = collection.GetEnumerator();
243                         while (enumer.MoveNext())
244                         {
245                                 uniqueConstraint = enumer.Current as UniqueConstraint;
246                                 if (uniqueConstraint != null)
247                                 {
248                                         if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
249                                         {
250                                                 return uniqueConstraint;
251                                         }
252                                 }
253                         }
254                         return null;
255                 }
256                         
257                 #endregion //Helpers
258
259                 #region Properties
260
261                 [DataCategory ("Data")]
262                 [DataSysDescription ("Indicates the columns of this constraint.")]
263                 [ReadOnly (true)]
264                 public virtual DataColumn[] Columns {
265                         get { return _dataColumns; }
266                 }
267
268                 [DataCategory ("Data")]
269                 [DataSysDescription ("Indicates if this constraint is a primary key.")]
270                 [ReadOnly (true)]
271                 public bool IsPrimaryKey {
272                         get { return _isPrimaryKey; }
273                 }
274
275                 [DataCategory ("Data")]
276                 [DataSysDescription ("Indicates the table of this constraint.")]
277                 [ReadOnly (true)]
278                 public override DataTable Table {
279                         get { return _dataTable; }
280                 }
281
282                 #endregion // Properties
283
284                 #region Methods
285
286                 public override bool Equals(object key2) {
287
288                         UniqueConstraint cst = key2 as UniqueConstraint;
289                         if (null == cst) return false;
290
291                         //according to spec if the cols are equal
292                         //then two UniqueConstraints are equal
293                         return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);      
294
295                 }
296
297                 public override int GetHashCode() 
298                 {
299                         //initialize hash with default value 
300                         int hash = 42;
301                         int i;
302
303                         //derive the hash code from the columns that way
304                         //Equals and GetHashCode return Equal objects to be the
305                         //same
306
307                         //Get the first column hash
308                         if (this.Columns.Length > 0)
309                                 hash ^= this.Columns[0].GetHashCode();
310                         
311                         //get the rest of the column hashes if there any
312                         for (i = 1; i < this.Columns.Length; i++)
313                         {
314                                 hash ^= this.Columns[1].GetHashCode();
315                                 
316                         }
317                         
318                         return hash ;
319                 }
320                 
321                 [MonoTODO]
322                 internal override void AddToConstraintCollectionSetup(
323                                 ConstraintCollection collection)
324                 {
325                         //run Ctor rules again
326                         _validateColumns(_dataColumns);
327                         
328                         //make sure a unique constraint doesn't already exists for these columns
329                         UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);       
330                         if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
331                                         " columns. Existing ConstraintName is " + uc.ConstraintName);
332
333                         //Allow only one primary key
334                         if (this.IsPrimaryKey)
335                         {
336                                 uc = GetPrimaryKeyConstraint(collection);
337                                 if (null != uc) uc._isPrimaryKey = false;
338
339                         }
340                                         
341                         //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
342                         //this method, so that it is executed twice. Need to investigate which
343                         // call to remove as that migth affect other parts of the classes.
344                         AssertConstraint();
345                 }
346                                         
347                 
348                 internal override void RemoveFromConstraintCollectionCleanup( 
349                                 ConstraintCollection collection)
350                 {
351                 }
352
353                 [MonoTODO]
354                 internal override void AssertConstraint()
355                 {
356                         
357                         if (_dataTable == null) return; //???
358                         if (_dataColumns == null) return; //???
359
360                         
361                         //Unique?       
362                         DataTable tbl = _dataTable;
363
364                         //TODO: Investigate other ways of speeding up the validation work below.
365                         //FIXME: This only works when only one DataColumn has been specified.
366
367                         //validate no duplicates exists.
368                         //Only validate when there are at least 2 rows
369                         //so that a duplicate migth exist.
370                         if(tbl.Rows.Count > 1) {
371                                 //get copy of rows collection first so that we do not modify the
372                                 //original.
373                                 DataRow[] rows = new DataRow [tbl.Rows.Count];
374                                 tbl.Rows.CopyTo (rows, 0);
375                                 ArrayList clonedDataList = new ArrayList (rows);
376
377                                 ArrayList newDataList = new ArrayList();
378
379                                 //copy to array list only the column we are interested in.
380                                 foreach (DataRow row in clonedDataList) {
381                                         
382                                         object colvalue = row[this._dataColumns[0]];
383                                         if (colvalue == DBNull.Value)
384                                                 newDataList.Add (null);
385                                         else
386                                                 newDataList.Add (colvalue);
387                                 }
388                                 
389                                 //sort ArrayList and check adjacent values for duplicates.
390                                 newDataList.Sort ();
391
392                                 for (int i = 0 ; i < newDataList.Count - 1 ; i++) 
393                                         
394                                         if (newDataList [i] == null) {
395                                                 if (newDataList [i+1] == null)
396                                                         throw new InvalidConstraintException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
397                                         }
398                                         else if (newDataList[i].Equals (newDataList[i+1])) 
399                                                 throw new InvalidConstraintException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
400                         }
401
402
403                 }
404
405                 [MonoTODO]
406                 internal override void AssertConstraint(DataRow row)
407                 {
408
409                         if (_dataTable == null) return; //???
410                         if (_dataColumns == null) return; //???
411
412
413                         //Unique?
414                         DataTable tbl = _dataTable;
415
416                         foreach(DataRow compareRow in tbl.Rows)
417                         {
418                                 //skip if it is the same row to be validated
419                                 if(!row.Equals(compareRow))
420                                 {
421                                         if(compareRow.HasVersion (DataRowVersion.Original))
422                                         {
423                                                 //FIXME: should we compare to compareRow[DataRowVersion.Current]?
424                                                 //FIXME: We need to compare to all columns the constraint is set to.
425                                                 if(row[_dataColumns[0], DataRowVersion.Proposed].Equals( compareRow[_dataColumns[0], DataRowVersion.Current]))
426                                                 {
427                                                         string ExceptionMessage;
428                                                         ExceptionMessage = "Column '" + _dataColumns[0].ColumnName + "' is constrained to be unique.";
429                                                         ExceptionMessage += " Value '" + row[_dataColumns[0], DataRowVersion.Proposed].ToString();
430                                                         ExceptionMessage += "' is already present.";
431
432                                                         throw new ConstraintException (ExceptionMessage);
433                                                 }
434
435                                         }
436
437                 }
438
439                         }
440
441                 }
442
443                 #endregion // Methods
444         }
445 }