2004-03-31 Juraj Skripsky <juraj@hotfeet.ch>
[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                 #region Constructors
34
35                 public UniqueConstraint (DataColumn column) 
36                 {
37                         _uniqueConstraint ("", column, false);
38                 }
39
40                 public UniqueConstraint (DataColumn[] columns) 
41                 {
42                         _uniqueConstraint ("", columns, false);
43                 }
44
45                 public UniqueConstraint (DataColumn column, bool isPrimaryKey) 
46                 {
47                         _uniqueConstraint ("", column, isPrimaryKey);
48                 }
49
50                 public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey) 
51                 {
52                         _uniqueConstraint ("", columns, isPrimaryKey);
53                 }
54
55                 public UniqueConstraint (string name, DataColumn column) 
56                 {
57                         _uniqueConstraint (name, column, false);
58                 }
59
60                 public UniqueConstraint (string name, DataColumn[] columns) 
61                 {
62                         _uniqueConstraint (name, columns, false);
63                 }
64
65                 public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey) 
66                 {
67                         _uniqueConstraint (name, column, isPrimaryKey);
68                 }
69
70                 public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey) 
71                 {
72                         _uniqueConstraint (name, columns, isPrimaryKey);
73                 }
74
75                 //Special case.  Can only be added to the Collection with AddRange
76                 [MonoTODO]
77                 [Browsable (false)]
78                 public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey) 
79                 {
80                         throw new NotImplementedException(); //need to finish related logic
81                         /*
82                         base.ConstraintName = name;
83                         
84                         //set unique
85                         //must set unique when added to the collection
86
87                         //keep list of names to resolve later
88                         _dataColumnNames = columnNames;
89
90                         _isPrimaryKey = isPrimaryKey;
91                         */
92                 }
93
94                 //helper ctor
95                 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey) 
96                 {
97                         //validate
98                         _validateColumn (column);
99
100                         //Set Constraint Name
101                         base.ConstraintName = name;
102
103                         __isPrimaryKey = isPrimaryKey;
104
105                         //keep reference 
106                         _dataColumns = new DataColumn [] {column};
107                         
108                         //Get table reference
109                         _dataTable = column.Table;
110
111                         
112                 }
113
114                 //helpter ctor  
115                 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey) 
116                 {
117                         //validate
118                         _validateColumns (columns, out _dataTable);
119
120                         //Set Constraint Name
121                         base.ConstraintName = name;
122
123                         //keep reference
124                         _dataColumns = columns;
125
126                         //PK?
127                         __isPrimaryKey = isPrimaryKey;
128
129                         
130                 }
131                 
132                 #endregion // Constructors
133
134                 #region Helpers
135                 
136                 private void _validateColumns(DataColumn [] columns)
137                 {
138                         DataTable table;
139                         _validateColumns(columns, out table);
140                 }
141                 
142                 //Validates a collection of columns with the ctor rules
143                 private void _validateColumns(DataColumn [] columns, out DataTable table) {
144                         table = null;
145
146                         //not null
147                         if (null == columns) throw new ArgumentNullException();
148                         
149                         //check that there is at least one column
150                         //LAMESPEC: not in spec
151                         if (columns.Length < 1)
152                                 throw new InvalidConstraintException("Must be at least one column.");
153                         
154                         DataTable compareTable = columns[0].Table;
155                         //foreach
156                         foreach (DataColumn col in columns){
157                                 
158                                 //check individual column rules
159                                 _validateColumn (col);
160                                 
161                                 
162                                 //check that columns are all from the same table??
163                                 //LAMESPEC: not in spec
164                                 if (compareTable != col.Table)
165                                         throw new InvalidConstraintException("Columns must be from the same table.");
166                                 
167                         }
168
169                         table = compareTable;
170                 }
171                 
172                 //validates a column with the ctor rules
173                 private void _validateColumn(DataColumn column) {
174         
175                         //not null
176                         if (null == column)  // FIXME: This is little weird, but here it goes...
177                                 throw new NullReferenceException("Object reference not set to an instance of an object.");
178
179                         
180                         //column must belong to a table
181                         //LAMESPEC: not in spec
182                         if (null == column.Table)
183                                 throw new ArgumentException ("Column must belong to a table.");                 
184                 }
185
186                 /// <summary>
187                 ///  If IsPrimaryKey is set to be true, this sets it true
188                 /// </summary>
189                 internal void UpdatePrimaryKey ()
190                 {
191                         _isPrimaryKey = __isPrimaryKey;
192                         foreach (DataColumn Col in _dataColumns)
193                                 Col.SetUnique();
194                         
195                 }
196
197                 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
198                 {
199                         //not null
200                         if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
201                         
202                         //make sure newPrimaryKey belongs to the collection parm unless it is null
203                         if (  collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) ) 
204                                 throw new ArgumentException("newPrimaryKey must belong to collection.");
205                         
206                         //Get existing pk
207                         UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
208                         
209                         //clear existing
210                         if (null != uc) uc._isPrimaryKey = false;
211
212                         //set new key
213                         if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
214                         
215                         
216                 }
217
218                 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
219                 {
220                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
221
222                         UniqueConstraint uc;
223                         IEnumerator enumer = collection.GetEnumerator();
224                         while (enumer.MoveNext())
225                         {
226                                 uc = enumer.Current as UniqueConstraint;
227                                 if (null == uc) continue;
228                                 
229                                 if (uc.IsPrimaryKey) return uc; 
230                         }
231
232                         //if we got here there was no pk
233                         return null;
234                         
235                 }
236
237                 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
238                                 DataColumn[] columns)
239                 {
240                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
241                         if (null == columns ) return null;
242                         
243                         UniqueConstraint uniqueConstraint;
244                         IEnumerator enumer = collection.GetEnumerator();
245                         while (enumer.MoveNext())
246                         {
247                                 uniqueConstraint = enumer.Current as UniqueConstraint;
248                                 if (uniqueConstraint != null)
249                                 {
250                                         if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
251                                         {
252                                                 return uniqueConstraint;
253                                         }
254                                 }
255                         }
256                         return null;
257                 }
258                         
259                 #endregion //Helpers
260
261                 #region Properties
262
263                 [DataCategory ("Data")]
264                 [DataSysDescription ("Indicates the columns of this constraint.")]
265                 [ReadOnly (true)]
266                 public virtual DataColumn[] Columns {
267                         get { return _dataColumns; }
268                 }
269
270                 [DataCategory ("Data")]
271                 [DataSysDescription ("Indicates if this constraint is a primary key.")]
272                 [ReadOnly (true)]
273                 public bool IsPrimaryKey {
274                         get { return _isPrimaryKey; }
275                 }
276
277                 [DataCategory ("Data")]
278                 [DataSysDescription ("Indicates the table of this constraint.")]
279                 [ReadOnly (true)]
280                 public override DataTable Table {
281                         get { return _dataTable; }
282                 }
283
284                 #endregion // Properties
285
286                 #region Methods
287
288                 public override bool Equals(object key2) {
289
290                         UniqueConstraint cst = key2 as UniqueConstraint;
291                         if (null == cst) return false;
292
293                         //according to spec if the cols are equal
294                         //then two UniqueConstraints are equal
295                         return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);      
296
297                 }
298
299                 public override int GetHashCode() 
300                 {
301                         //initialize hash with default value 
302                         int hash = 42;
303                         int i;
304
305                         //derive the hash code from the columns that way
306                         //Equals and GetHashCode return Equal objects to be the
307                         //same
308
309                         //Get the first column hash
310                         if (this.Columns.Length > 0)
311                                 hash ^= this.Columns[0].GetHashCode();
312                         
313                         //get the rest of the column hashes if there any
314                         for (i = 1; i < this.Columns.Length; i++)
315                         {
316                                 hash ^= this.Columns[1].GetHashCode();
317                                 
318                         }
319                         
320                         return hash ;
321                 }
322                 
323                 [MonoTODO]
324                 internal override void AddToConstraintCollectionSetup(
325                                 ConstraintCollection collection)
326                 {
327                         //run Ctor rules again
328                         _validateColumns(_dataColumns);
329                         
330                         //make sure a unique constraint doesn't already exists for these columns
331                         UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);       
332                         if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
333                                         " columns. Existing ConstraintName is " + uc.ConstraintName);
334
335                         //Allow only one primary key
336                         if (this.IsPrimaryKey)
337                         {
338                                 uc = GetPrimaryKeyConstraint(collection);
339                                 if (null != uc) uc._isPrimaryKey = false;
340
341                         }
342                                         
343                         //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
344                         //this method, so that it is executed twice. Need to investigate which
345                         // call to remove as that migth affect other parts of the classes.
346                         AssertConstraint();
347                 }
348                                         
349                 
350                 internal override void RemoveFromConstraintCollectionCleanup( 
351                                 ConstraintCollection collection)
352                 {
353                         Index index = this.Index;
354                         this.Index = null;
355                         // if a foreign key constraint references the same index - 
356                         // change the index be to not unique.
357                         // In this case we can not just drop the index
358                         ICollection fkCollection = collection.ForeignKeyConstraints;
359                         foreach (ForeignKeyConstraint fkc in fkCollection) {
360                                 if (index == fkc.Index) {
361                                         fkc.Index.SetUnique (false);
362                                         // this is not referencing the index anymore
363                                         return;
364                                 }
365                         }
366                         
367                         // if we are here no one is using this index so we can remove it.
368                         // There is no need calling drop index here
369                         // since two unique constraints never references the same index
370                         // and we already check that there is no foreign key constraint referencing it.
371                         Table.RemoveIndex (index);
372                 }
373
374                 [MonoTODO]
375                 internal override void AssertConstraint()
376                 {                       
377                         if (_dataTable == null) return; //???
378                         if (_dataColumns == null) return; //???         
379                         
380                         Index fromTableIndex = null;
381                         if (Index == null) {
382                                 fromTableIndex = Table.GetIndexByColumns (Columns);
383                                 if (fromTableIndex == null) {
384                                         Index = new Index (ConstraintName, _dataTable, _dataColumns, true);     
385                                 }
386                                 else {
387                                         fromTableIndex.SetUnique (true);
388                                         Index = fromTableIndex;
389                                 }
390                         }
391
392                         try {
393                                 Table.InitializeIndex (Index);
394                         }
395                         catch (ConstraintException) {
396                                 Index = null;
397                                 throw new ArgumentException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
398                         }
399                         
400                         // if there is no index with same columns - add the new index to the table.
401                         if (fromTableIndex == null)
402                                 Table.AddIndex (Index);
403                 }
404
405                 [MonoTODO]
406                 internal override void AssertConstraint(DataRow row)
407                 {
408                         if (_dataTable == null) return; //???
409                         if (_dataColumns == null) return; //???
410                         
411                         if (Index == null) {
412                                 Index = Table.GetIndexByColumns (Columns, true);
413                                 if (Index == null) {
414                                         Index = new Index (ConstraintName, _dataTable, _dataColumns, true);
415                                         Table.AddIndex (Index);
416                                 }
417                         }
418
419                         object val;
420                         for (int i = 0; i < _dataColumns.Length; i++) {
421
422                                 val = row[_dataColumns[i]];
423                                 if (val == null || val == DBNull.Value)
424                                         throw new NoNullAllowedException("Column '" + _dataColumns[i].ColumnName + "' does not allow nulls.");
425                         }
426                         
427                         try {
428                                 UpdateIndex (row);
429                         }
430                         catch (ConstraintException) {
431                                 throw new ConstraintException(GetErrorMessage(row));
432                         }
433                 }
434
435                 private string GetErrorMessage(DataRow row)
436                 {
437                         int i;
438                          
439                         System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
440                         for (i = 1; i < _dataColumns.Length; i++)
441                                 sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
442                         string valStr = sb.ToString();
443                         sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
444                         for (i = 1; i < _dataColumns.Length; i++)
445                                 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
446                         string colStr = sb.ToString();
447                         return "Column '" + colStr + "' is constrained to be unique.  Value '" + valStr + "' is already present.";
448                 }
449                 
450                 
451                 #endregion // Methods
452
453         }
454
455         
456 }