* support-test-*.cs: Rename from test-*-p2.cs.
[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 using System.Data.Common;
41
42 namespace System.Data {
43         [Editor ("Microsoft.VSDesigner.Data.Design.UniqueConstraintEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
44                  "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
45         [DefaultProperty ("ConstraintName")]
46         [Serializable]
47         public class UniqueConstraint : Constraint 
48         {
49                 private bool _isPrimaryKey = false;
50                 private bool _belongsToCollection = false;
51                 private DataTable _dataTable; //set by ctor except when unique case
52                 
53                 //FIXME: create a class which will wrap this collection
54                 private DataColumn [] _dataColumns;
55
56                 //TODO:provide helpers for this case
57                 private string [] _dataColumnNames; //unique case
58                 private bool _dataColsNotValidated;
59
60
61                 #region Constructors
62
63                 public UniqueConstraint (DataColumn column) 
64                 {
65                         _uniqueConstraint ("", column, false);
66                 }
67
68                 public UniqueConstraint (DataColumn[] columns) 
69                 {
70                         _uniqueConstraint ("", columns, false);
71                 }
72
73                 public UniqueConstraint (DataColumn column, bool isPrimaryKey) 
74                 {
75                         _uniqueConstraint ("", column, isPrimaryKey);
76                 }
77
78                 public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey) 
79                 {
80                         _uniqueConstraint ("", columns, isPrimaryKey);
81                 }
82
83                 public UniqueConstraint (string name, DataColumn column) 
84                 {
85                         _uniqueConstraint (name, column, false);
86                 }
87
88                 public UniqueConstraint (string name, DataColumn[] columns) 
89                 {
90                         _uniqueConstraint (name, columns, false);
91                 }
92
93                 public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey) 
94                 {
95                         _uniqueConstraint (name, column, isPrimaryKey);
96                 }
97
98                 public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey) 
99                 {
100                         _uniqueConstraint (name, columns, isPrimaryKey);
101                 }
102
103                 //Special case.  Can only be added to the Collection with AddRange
104                 [Browsable (false)]
105                 public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey) 
106                 {
107                          _dataColsNotValidated = true;
108                                                                                                     
109             //keep list of names to resolve later
110             _dataColumnNames = columnNames;
111                                                                                         
112             base.ConstraintName = name;
113                                                                                         
114             _isPrimaryKey = isPrimaryKey;
115
116                 }
117
118                 //helper ctor
119                 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey) 
120                 {
121                         _dataColsNotValidated = false;
122                         //validate
123                         _validateColumn (column);
124
125                         //Set Constraint Name
126                         base.ConstraintName = name;
127
128                         _isPrimaryKey = isPrimaryKey;
129
130                         //keep reference 
131                         _dataColumns = new DataColumn [] {column};
132                         
133                         //Get table reference
134                         _dataTable = column.Table;                      
135                 }
136
137                 //helpter ctor  
138                 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey) 
139                 {
140                         _dataColsNotValidated = false;
141                         
142                         //validate
143                         _validateColumns (columns, out _dataTable);
144
145                         //Set Constraint Name
146                         base.ConstraintName = name;
147
148                         //keep reference
149                         _dataColumns = columns;
150
151                         //PK?
152                         _isPrimaryKey = isPrimaryKey;                   
153                 }
154                 
155                 #endregion // Constructors
156
157                 #region Helpers
158                 
159                 private void _validateColumns(DataColumn [] columns)
160                 {
161                         DataTable table;
162                         _validateColumns(columns, out table);
163                 }
164                 
165                 //Validates a collection of columns with the ctor rules
166                 private void _validateColumns(DataColumn [] columns, out DataTable table) {
167                         table = null;
168
169                         //not null
170                         if (null == columns) throw new ArgumentNullException();
171                         
172                         //check that there is at least one column
173                         //LAMESPEC: not in spec
174                         if (columns.Length < 1)
175                                 throw new InvalidConstraintException("Must be at least one column.");
176                         
177                         DataTable compareTable = columns[0].Table;
178                         //foreach
179                         foreach (DataColumn col in columns){
180                                 
181                                 //check individual column rules
182                                 _validateColumn (col);
183                                 
184                                 
185                                 //check that columns are all from the same table??
186                                 //LAMESPEC: not in spec
187                                 if (compareTable != col.Table)
188                                         throw new InvalidConstraintException("Columns must be from the same table.");
189                                 
190                         }
191
192                         table = compareTable;
193                 }
194                 
195                 //validates a column with the ctor rules
196                 private void _validateColumn(DataColumn column) {
197         
198                         //not null
199                         if (null == column)  // FIXME: This is little weird, but here it goes...
200                                 throw new NullReferenceException("Object reference not set to an instance of an object.");
201
202                         
203                         //column must belong to a table
204                         //LAMESPEC: not in spec
205                         if (null == column.Table)
206                                 throw new ArgumentException ("Column must belong to a table.");                 
207                 }
208
209                 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
210                 {
211                         //not null
212                         if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
213                         
214                         //make sure newPrimaryKey belongs to the collection parm unless it is null
215                         if (  collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) ) 
216                                 throw new ArgumentException("newPrimaryKey must belong to collection.");
217                         
218                         //Get existing pk
219                         UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
220                         
221                         //clear existing
222                         if (null != uc) uc._isPrimaryKey = false;
223
224                         //set new key
225                         if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
226                         
227                         
228                 }
229
230                 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
231                 {
232                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
233
234                         UniqueConstraint uc;
235                         IEnumerator enumer = collection.GetEnumerator();
236                         while (enumer.MoveNext())
237                         {
238                                 uc = enumer.Current as UniqueConstraint;
239                                 if (null == uc) continue;
240                                 
241                                 if (uc.IsPrimaryKey) return uc; 
242                         }
243
244                         //if we got here there was no pk
245                         return null;
246                         
247                 }
248
249                 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
250                                 DataColumn[] columns)
251                 {
252                         if (null == collection) throw new ArgumentNullException("Collection can't be null.");
253                         if (null == columns ) return null;
254                         
255                         foreach(Constraint constraint in collection) {
256                                 if (constraint is UniqueConstraint) {
257                                         UniqueConstraint uc = constraint as UniqueConstraint;
258                                         if ( DataColumn.AreColumnSetsTheSame(uc.Columns, columns) ) {
259                                                 return uc;
260                                         }
261                                 }
262                         }
263                         return null;
264                 }
265
266                 internal bool DataColsNotValidated 
267                 {               
268                         get { 
269                                 return (_dataColsNotValidated); 
270                         }
271                  }
272
273                 // Helper Special Ctor
274         // Set the _dataTable property to the table to which this instance is bound when AddRange()
275         // is called with the special constructor.
276         // Validate whether the named columns exist in the _dataTable
277         internal void PostAddRange( DataTable _setTable ) 
278                 {                
279                         _dataTable = _setTable;
280             DataColumn []cols = new DataColumn [_dataColumnNames.Length];
281             int i = 0;
282             foreach ( string _columnName in _dataColumnNames ) {
283                 if ( _setTable.Columns.Contains (_columnName) ) {
284                                         cols [i] = _setTable.Columns [_columnName];
285                                         i++;
286                                         continue;
287                                 }
288                                 throw( new InvalidConstraintException ( "The named columns must exist in the table" ));
289             }
290             _dataColumns = cols;
291         }
292
293                         
294                 #endregion //Helpers
295
296                 #region Properties
297
298                 [DataCategory ("Data")]
299                 [DataSysDescription ("Indicates the columns of this constraint.")]
300                 [ReadOnly (true)]
301                 public virtual DataColumn[] Columns {
302                         get { return _dataColumns; }
303                 }
304
305                 [DataCategory ("Data")]
306                 [DataSysDescription ("Indicates if this constraint is a primary key.")]
307                 public bool IsPrimaryKey {
308                         get { 
309                                 if (Table == null || (!_belongsToCollection)) {
310                                         return false;
311                                 }
312                                 return _isPrimaryKey; 
313                         }
314                 }
315
316                 [DataCategory ("Data")]
317                 [DataSysDescription ("Indicates the table of this constraint.")]
318                 [ReadOnly (true)]
319                 public override DataTable Table {
320                         get { return _dataTable; }
321                 }
322
323                 #endregion // Properties
324
325                 #region Methods
326
327                 public override bool Equals(object key2) {
328
329                         UniqueConstraint cst = key2 as UniqueConstraint;
330                         if (null == cst) return false;
331
332                         //according to spec if the cols are equal
333                         //then two UniqueConstraints are equal
334                         return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);      
335
336                 }
337
338                 public override int GetHashCode() 
339                 {
340                         //initialize hash with default value 
341                         int hash = 42;
342                         int i;
343
344                         //derive the hash code from the columns that way
345                         //Equals and GetHashCode return Equal objects to be the
346                         //same
347
348                         //Get the first column hash
349                         if (this.Columns.Length > 0)
350                                 hash ^= this.Columns[0].GetHashCode();
351                         
352                         //get the rest of the column hashes if there any
353                         for (i = 1; i < this.Columns.Length; i++)
354                         {
355                                 hash ^= this.Columns[1].GetHashCode();
356                                 
357                         }
358                         
359                         return hash ;
360                 }
361                 
362                 internal override void AddToConstraintCollectionSetup(
363                                 ConstraintCollection collection)
364                 {
365                         for (int i = 0; i < Columns.Length; i++)
366                                 if (Columns[i].Table != collection.Table)
367                                         throw new ArgumentException("These columns don't point to this table.");
368                         //run Ctor rules again
369                         _validateColumns(_dataColumns);
370                         
371                         //make sure a unique constraint doesn't already exists for these columns
372                         UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);       
373                         if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
374                                         " columns. Existing ConstraintName is " + uc.ConstraintName);
375
376                         //Allow only one primary key
377                         if (this.IsPrimaryKey) {
378                                 uc = GetPrimaryKeyConstraint(collection);
379                                 if (null != uc) uc._isPrimaryKey = false;
380                         }
381
382                         // if constraint is based on one column only
383                         // this column becomes unique
384                         if (_dataColumns.Length == 1) {
385                                 _dataColumns[0].SetUnique();
386                         }
387                                         
388                         //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
389                         //this method, so that it is executed twice. Need to investigate which
390                         // call to remove as that migth affect other parts of the classes.
391                         //AssertConstraint();
392                         if (IsConstraintViolated())
393                                 throw new ArgumentException("These columns don't currently have unique values.");
394
395                         _belongsToCollection = true;
396                 }
397                                         
398                 
399                 internal override void RemoveFromConstraintCollectionCleanup( 
400                                 ConstraintCollection collection)
401                 {
402                         _belongsToCollection = false;
403                         Index index = Index;
404                         Index = null;
405                 }
406
407                 internal override bool IsConstraintViolated()
408                 {       
409                         if (Index == null) {
410                                 Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
411                         }
412
413                         if (Index.HasDuplicates) {
414                                 int[] dups = Index.Duplicates;
415                                 for (int i = 0; i < dups.Length; i++){
416                                         DataRow row = Table.RecordCache[dups[i]];
417                                         ArrayList columns = new ArrayList();
418                                         ArrayList values = new ArrayList();
419                                         foreach (DataColumn col in Columns){
420                                                 columns.Add(col.ColumnName);
421                                                 values.Add(row[col].ToString());
422                                         }
423
424                                         string columnNames = String.Join(",", (string[])columns.ToArray(typeof(string)));
425                                         string columnValues = String.Join(",", (string[])values.ToArray(typeof(string)));
426
427                                         row.RowError = String.Format("Column(s) '{0}' are constrained to be unique.  Value(s) '{1}' are already present", columnNames, columnValues);
428                                 }
429                                 // FIXME : check the exception to be thrown here
430                                 // throw new ConstraintException("These columns don't currently have unique values");
431                                 //throw new ConstraintException ("Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.");
432                                 return true;
433                         }
434
435                         return false;
436                 }
437
438                 internal override void AssertConstraint(DataRow row)
439                 {       
440                         if (IsPrimaryKey && row.HasVersion(DataRowVersion.Default)) {
441                                 for (int i = 0; i < Columns.Length; i++) {
442                                         if (row.IsNull(Columns[i])) {
443                                                 throw new NoNullAllowedException("Column '" + Columns[i].ColumnName + "' does not allow nulls.");
444                                         }
445                                 }
446                         }
447                         
448                         if (Index == null) {
449                                 Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
450                         }
451
452                         if (Index.HasDuplicates) {
453                                 throw new ConstraintException(GetErrorMessage(row));
454                         }
455                 }
456
457                 internal override bool IsColumnContained(DataColumn column)
458                 {
459                         for (int i = 0; i < _dataColumns.Length; i++)
460                                 if (column == _dataColumns[i])
461                                         return true;
462
463                         return false;
464                 }
465
466                 internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){
467                         if (Equals(col.Table.PrimaryKey)){
468                                 if (shouldThrow)
469                                         throw new ArgumentException("Cannot remove unique constraint since it's the primary key of a table.");
470
471                                 return false;
472                         }
473
474                         if (Table.DataSet != null){
475                                 foreach (DataTable table in Table.DataSet.Tables){
476                                         foreach (Constraint constraint in table.Constraints){
477                                                 if (constraint is ForeignKeyConstraint)
478                                                         if (((ForeignKeyConstraint)constraint).RelatedTable == Table){
479                                                                 if (shouldThrow)
480                                                                         throw new ArgumentException(
481                                                                                 String.Format("Cannot remove unique constraint '{0}'. Remove foreign key constraint '{1}' first.",
482                                                                                 ConstraintName, constraint.ConstraintName)
483                                                                                 );
484                                                                 return false;
485                                                         }
486                                         }
487                                 }
488                         }
489
490                         return true;
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                         }
501                         string valStr = sb.ToString();
502                         sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
503                         for (i = 1; i < _dataColumns.Length; i++) {
504                                 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
505                         }
506                         string colStr = sb.ToString();
507                         return "Column '" + colStr + "' is constrained to be unique.  Value '" + valStr + "' is already present.";
508                 }
509                                        
510                 
511                 #endregion // Methods
512
513         }
514
515         
516 }