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