updating to the latest module.
[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 //
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System;
37 using System.Collections;
38 using System.ComponentModel;
39 using System.Runtime.InteropServices;
40
41 namespace System.Data {
42         [Editor]
43         [DefaultProperty ("ConstraintName")]
44         [Serializable]
45         public class UniqueConstraint : Constraint 
46         {
47                 private bool _isPrimaryKey = false;
48                 private bool __isPrimaryKey = false;
49                 private DataTable _dataTable; //set by ctor except when unique case
50                 
51                 private DataColumn [] _dataColumns;
52
53                 //TODO:provide helpers for this case
54                 private string [] _dataColumnNames; //unique case
55                 private bool _dataColsNotValidated;
56
57
58                 #region Constructors
59
60                 public UniqueConstraint (DataColumn column) 
61                 {
62                         _uniqueConstraint ("", column, false);
63                 }
64
65                 public UniqueConstraint (DataColumn[] columns) 
66                 {
67                         _uniqueConstraint ("", columns, false);
68                 }
69
70                 public UniqueConstraint (DataColumn column, bool isPrimaryKey) 
71                 {
72                         _uniqueConstraint ("", column, isPrimaryKey);
73                 }
74
75                 public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey) 
76                 {
77                         _uniqueConstraint ("", columns, isPrimaryKey);
78                 }
79
80                 public UniqueConstraint (string name, DataColumn column) 
81                 {
82                         _uniqueConstraint (name, column, false);
83                 }
84
85                 public UniqueConstraint (string name, DataColumn[] columns) 
86                 {
87                         _uniqueConstraint (name, columns, false);
88                 }
89
90                 public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey) 
91                 {
92                         _uniqueConstraint (name, column, isPrimaryKey);
93                 }
94
95                 public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey) 
96                 {
97                         _uniqueConstraint (name, columns, isPrimaryKey);
98                 }
99
100                 //Special case.  Can only be added to the Collection with AddRange
101                 [Browsable (false)]
102                 public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey) 
103                 {
104                          _dataColsNotValidated = true;
105                                                                                                     
106                         //keep list of names to resolve later
107                         _dataColumnNames = columnNames;
108                                                                                                     
109                         base.ConstraintName = name;
110                                                                                                     
111                         _isPrimaryKey = isPrimaryKey;
112
113                 }
114
115                 //helper ctor
116                 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey) 
117                 {
118                         _dataColsNotValidated = false;
119                         //validate
120                         _validateColumn (column);
121
122                         //Set Constraint Name
123                         base.ConstraintName = name;
124
125                         __isPrimaryKey = isPrimaryKey;
126
127                         //keep reference 
128                         _dataColumns = new DataColumn [] {column};
129                         
130                         //Get table reference
131                         _dataTable = column.Table;
132
133                         
134                 }
135
136                 //helpter ctor  
137                 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey) 
138                 {
139                         _dataColsNotValidated = false;
140                         
141                         //validate
142                         _validateColumns (columns, out _dataTable);
143
144                         //Set Constraint Name
145                         base.ConstraintName = name;
146
147                         //keep reference
148                         _dataColumns = columns;
149
150                         //PK?
151                         __isPrimaryKey = isPrimaryKey;
152
153                         
154                 }
155                 
156                 #endregion // Constructors
157
158                 #region Helpers
159                 
160                 private void _validateColumns(DataColumn [] columns)
161                 {
162                         DataTable table;
163                         _validateColumns(columns, out table);
164                 }
165                 
166                 //Validates a collection of columns with the ctor rules
167                 private void _validateColumns(DataColumn [] columns, out DataTable table) {
168                         table = null;
169
170                         //not null
171                         if (null == columns) throw new ArgumentNullException();
172                         
173                         //check that there is at least one column
174                         //LAMESPEC: not in spec
175                         if (columns.Length < 1)
176                                 throw new InvalidConstraintException("Must be at least one column.");
177                         
178                         DataTable compareTable = columns[0].Table;
179                         //foreach
180                         foreach (DataColumn col in columns){
181                                 
182                                 //check individual column rules
183                                 _validateColumn (col);
184                                 
185                                 
186                                 //check that columns are all from the same table??
187                                 //LAMESPEC: not in spec
188                                 if (compareTable != col.Table)
189                                         throw new InvalidConstraintException("Columns must be from the same table.");
190                                 
191                         }
192
193                         table = compareTable;
194                 }
195                 
196                 //validates a column with the ctor rules
197                 private void _validateColumn(DataColumn column) {
198         
199                         //not null
200                         if (null == column)  // FIXME: This is little weird, but here it goes...
201                                 throw new NullReferenceException("Object reference not set to an instance of an object.");
202
203                         
204                         //column must belong to a table
205                         //LAMESPEC: not in spec
206                         if (null == column.Table)
207                                 throw new ArgumentException ("Column must belong to a table.");                 
208                 }
209
210                 /// <summary>
211                 ///  If IsPrimaryKey is set to be true, this sets it true
212                 /// </summary>
213                 internal void UpdatePrimaryKey ()
214                 {
215                         _isPrimaryKey = __isPrimaryKey;
216                         // if unique constraint defined on single column
217                         // the column becomes unique
218                         if (_dataColumns.Length == 1){
219                                 // use SetUnique - because updating Unique property causes loop
220                                 _dataColumns[0].SetUnique();
221                         }
222                 }
223
224                 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
225                 {
226                         //not null
227                         if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
228                         
229                         //make sure newPrimaryKey belongs to the collection parm unless it is null
230                         if (  collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) ) 
231                                 throw new ArgumentException("newPrimaryKey must belong to collection.");
232                         
233                         //Get existing pk
234                         UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
235                         
236                         //clear existing
237                         if (null != uc) uc._isPrimaryKey = false;
238
239                         //set new key
240                         if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
241                         
242                         
243                 }
244
245                 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
246                 {
247                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
248
249                         UniqueConstraint uc;
250                         IEnumerator enumer = collection.GetEnumerator();
251                         while (enumer.MoveNext())
252                         {
253                                 uc = enumer.Current as UniqueConstraint;
254                                 if (null == uc) continue;
255                                 
256                                 if (uc.IsPrimaryKey) return uc; 
257                         }
258
259                         //if we got here there was no pk
260                         return null;
261                         
262                 }
263
264                 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
265                                 DataColumn[] columns)
266                 {
267                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
268                         if (null == columns ) return null;
269                         
270                         UniqueConstraint uniqueConstraint;
271                         IEnumerator enumer = collection.GetEnumerator();
272                         while (enumer.MoveNext())
273                         {
274                                 uniqueConstraint = enumer.Current as UniqueConstraint;
275                                 if (uniqueConstraint != null)
276                                 {
277                                         if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
278                                         {
279                                                 return uniqueConstraint;
280                                         }
281                                 }
282                         }
283                         return null;
284                 }
285
286                  internal bool DataColsNotValidated {
287                         
288                         get { return (_dataColsNotValidated); 
289                         }
290                 }
291
292                 // Helper Special Ctor
293                 // Set the _dataTable property to the table to which this instance is bound when AddRange()
294                 // is called with the special constructor.
295                 // Validate whether the named columns exist in the _dataTable
296                 internal void PostAddRange( DataTable _setTable ) {
297                 
298                         _dataTable = _setTable;
299                         DataColumn []cols = new DataColumn [_dataColumnNames.Length];
300                         int i = 0;
301                         foreach ( string _columnName in _dataColumnNames ){
302                                  if ( _setTable.Columns.Contains (_columnName) ){
303                                         cols [i] = _setTable.Columns [_columnName];
304                                         i++;
305                                         continue;
306                                 }
307                                 throw( new InvalidConstraintException ( "The named columns must exist in the table" ));
308                         }
309                         _dataColumns = cols;
310                 }
311
312                         
313                 #endregion //Helpers
314
315                 #region Properties
316
317                 [DataCategory ("Data")]
318                 [DataSysDescription ("Indicates the columns of this constraint.")]
319                 [ReadOnly (true)]
320                 public virtual DataColumn[] Columns {
321                         get { return _dataColumns; }
322                 }
323
324                 [DataCategory ("Data")]
325                 [DataSysDescription ("Indicates if this constraint is a primary key.")]
326                 public bool IsPrimaryKey {
327                         get { return _isPrimaryKey; }
328                 }
329
330                 [DataCategory ("Data")]
331                 [DataSysDescription ("Indicates the table of this constraint.")]
332                 [ReadOnly (true)]
333                 public override DataTable Table {
334                         get { return _dataTable; }
335                 }
336
337                 #endregion // Properties
338
339                 #region Methods
340
341                 public override bool Equals(object key2) {
342
343                         UniqueConstraint cst = key2 as UniqueConstraint;
344                         if (null == cst) return false;
345
346                         //according to spec if the cols are equal
347                         //then two UniqueConstraints are equal
348                         return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);      
349
350                 }
351
352                 public override int GetHashCode() 
353                 {
354                         //initialize hash with default value 
355                         int hash = 42;
356                         int i;
357
358                         //derive the hash code from the columns that way
359                         //Equals and GetHashCode return Equal objects to be the
360                         //same
361
362                         //Get the first column hash
363                         if (this.Columns.Length > 0)
364                                 hash ^= this.Columns[0].GetHashCode();
365                         
366                         //get the rest of the column hashes if there any
367                         for (i = 1; i < this.Columns.Length; i++)
368                         {
369                                 hash ^= this.Columns[1].GetHashCode();
370                                 
371                         }
372                         
373                         return hash ;
374                 }
375                 
376                 [MonoTODO]
377                 internal override void AddToConstraintCollectionSetup(
378                                 ConstraintCollection collection)
379                 {
380                         //run Ctor rules again
381                         _validateColumns(_dataColumns);
382                         
383                         //make sure a unique constraint doesn't already exists for these columns
384                         UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);       
385                         if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
386                                         " columns. Existing ConstraintName is " + uc.ConstraintName);
387
388                         //Allow only one primary key
389                         if (this.IsPrimaryKey)
390                         {
391                                 uc = GetPrimaryKeyConstraint(collection);
392                                 if (null != uc) uc._isPrimaryKey = false;
393
394                         }
395                                         
396                         //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
397                         //this method, so that it is executed twice. Need to investigate which
398                         // call to remove as that migth affect other parts of the classes.
399                         AssertConstraint();
400                 }
401                                         
402                 
403                 internal override void RemoveFromConstraintCollectionCleanup( 
404                                 ConstraintCollection collection)
405                 {
406                         Index index = this.Index;
407                         this.Index = null;
408                         // if a foreign key constraint references the same index - 
409                         // change the index be to not unique.
410                         // In this case we can not just drop the index
411                         ICollection fkCollection = collection.ForeignKeyConstraints;
412                         foreach (ForeignKeyConstraint fkc in fkCollection) {
413                                 if (index == fkc.Index) {
414                                         fkc.Index.SetUnique (false);
415                                         // this is not referencing the index anymore
416                                         return;
417                                 }
418                         }
419                         
420                         // if we are here no one is using this index so we can remove it.
421                         // There is no need calling drop index here
422                         // since two unique constraints never references the same index
423                         // and we already check that there is no foreign key constraint referencing it.
424                         Table.RemoveIndex (index);
425                 }
426
427                 [MonoTODO]
428                 internal override void AssertConstraint()
429                 {                       
430                         if (_dataTable == null) return; //???
431                         if (_dataColumns == null) return; //???         
432                         
433                         Index fromTableIndex = null;
434                         if (Index == null) {
435                                 fromTableIndex = Table.GetIndexByColumns (Columns);
436                                 if (fromTableIndex == null) {
437                                         Index = new Index (ConstraintName, _dataTable, _dataColumns, true);     
438                                 }
439                                 else {
440                                         fromTableIndex.SetUnique (true);
441                                         Index = fromTableIndex;
442                                 }
443                         }
444
445                         try {
446                                 Table.InitializeIndex (Index);
447                         }
448                         catch (ConstraintException) {
449 #if NET_1_1
450                                 throw;
451 #else
452                                 Index = null;
453                                 throw new ArgumentException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
454 #endif
455                         }
456                         
457                         // if there is no index with same columns - add the new index to the table.
458                         if (fromTableIndex == null)
459                                 Table.AddIndex (Index);
460                 }
461
462                 [MonoTODO]
463                 internal override void AssertConstraint(DataRow row)
464                 {
465                         if (_dataTable == null) return; //???
466                         if (_dataColumns == null) return; //???
467                         
468                         if (Index == null) {
469                                 Index = Table.GetIndexByColumns (Columns, true);
470                                 if (Index == null) {
471                                         Index = new Index (ConstraintName, _dataTable, _dataColumns, true);
472                                         Table.AddIndex (Index);
473                                 }
474                         }
475
476                         if (IsPrimaryKey) {
477                                 object val;
478                                 for (int i = 0; i < _dataColumns.Length; i++) {
479                                         val = row[_dataColumns[i]];
480                                         if (val == null || val == DBNull.Value)
481                                                 throw new NoNullAllowedException("Column '" + _dataColumns[i].ColumnName + "' does not allow nulls.");
482                                 }
483                         }
484
485                         try {
486                                 UpdateIndex (row);
487                         }
488                         catch (ConstraintException) {
489                                 throw new ConstraintException(GetErrorMessage(row));
490                         }
491                 }
492
493                 private string GetErrorMessage(DataRow row)
494                 {
495                         int i;
496                          
497                         System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
498                         for (i = 1; i < _dataColumns.Length; i++)
499                                 sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
500                         string valStr = sb.ToString();
501                         sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
502                         for (i = 1; i < _dataColumns.Length; i++)
503                                 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
504                         string colStr = sb.ToString();
505                         return "Column '" + colStr + "' is constrained to be unique.  Value '" + valStr + "' is already present.";
506                 }
507                 
508                 internal bool Contains (DataColumn c)
509                 {
510                         foreach (DataColumn col in Columns)
511                                 if (c == col)
512                                         return true;
513                         return false;
514                 }
515                 
516                 
517                 #endregion // Methods
518
519         }
520
521         
522 }