2004-03-10 Umadevi S (sumadevi@novell.com)
[mono.git] / mcs / class / System.Data / System.Data / ForeignKeyConstraint.cs
1 //
2 // System.Data.ForeignKeyConstraint.cs
3 //
4 // Author:
5 //   Franklin Wise <gracenote@earthlink.net>
6 //   Daniel Morgan <danmorg@sc.rr.com>
7 //
8 // (C) 2002 Franklin Wise
9 // (C) 2002 Daniel Morgan
10 //
11
12 using System;
13 using System.Collections;
14 using System.ComponentModel;
15 using System.Runtime.InteropServices;
16
17 namespace System.Data {
18         [Editor]
19         [DefaultProperty ("ConstraintName")]
20         [Serializable]
21         public class ForeignKeyConstraint : Constraint 
22         {
23                 private UniqueConstraint _parentUniqueConstraint;
24                 private DataColumn [] _parentColumns;
25                 private DataColumn [] _childColumns;
26                 private DataColumn [] _childColumnsExtended;
27                 private Rule _deleteRule = Rule.Cascade;
28                 private Rule _updateRule = Rule.Cascade;
29                 private AcceptRejectRule _acceptRejectRule = AcceptRejectRule.None;
30                 
31                 #region Constructors
32
33                 public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn) 
34                 {
35                         if (null == parentColumn || null == childColumn) {
36                                 throw new ArgumentNullException("Neither parentColumn or" +
37                                         " childColumn can be null.");
38                         }
39
40                         _foreignKeyConstraint(null, new DataColumn[] {parentColumn},
41                                         new DataColumn[] {childColumn});
42                 }
43
44                 public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns) 
45                 {
46                         _foreignKeyConstraint(null, parentColumns, childColumns);
47                 }
48
49                 public ForeignKeyConstraint(string constraintName, DataColumn parentColumn, DataColumn childColumn) 
50                 {
51                         if (null == parentColumn || null == childColumn) {
52                                 throw new ArgumentNullException("Neither parentColumn or" +
53                                         " childColumn can be null.");
54                         }
55
56                         _foreignKeyConstraint(constraintName, new DataColumn[] {parentColumn},
57                                         new DataColumn[] {childColumn});
58                 }
59
60                 public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns) 
61                 {
62                         _foreignKeyConstraint(constraintName, parentColumns, childColumns);
63                 }
64                 
65                 //special case
66                 [MonoTODO]
67                 [Browsable (false)]
68                 public ForeignKeyConstraint(string constraintName, string parentTableName, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule) 
69                 {
70                 }
71
72 #if NET_1_2
73                 [MonoTODO]
74                 public ForeignKeyConstraint (string constraintName, string parentTableName, string parentTableNamespace, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
75                 {
76                         throw new NotImplementedException ();
77                 }
78 #endif
79
80                 private void _foreignKeyConstraint(string constraintName, DataColumn[] parentColumns,
81                                 DataColumn[] childColumns)
82                 {
83
84                         //Validate 
85                         _validateColumns(parentColumns, childColumns);
86
87                         //Set Constraint Name
88                         base.ConstraintName = constraintName;   
89
90                         //Keep reference to columns
91                         _parentColumns = parentColumns;
92                         _childColumns = childColumns;
93                 }
94
95                 #endregion // Constructors
96
97                 #region Helpers
98
99                 private void _validateColumns(DataColumn[] parentColumns, DataColumn[] childColumns)
100                 {
101                         //not null
102                         if (null == parentColumns || null == childColumns) 
103                                 throw new ArgumentNullException();
104                         
105                         //at least one element in each array
106                         if (parentColumns.Length < 1 || childColumns.Length < 1)
107                                 throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
108                                                 " zero length.");
109                                 
110                         //same size arrays
111                         if (parentColumns.Length != childColumns.Length)
112                                 throw new ArgumentException("Parent columns and child columns must be the same length.");
113                         
114
115                         DataTable ptable = parentColumns[0].Table;
116                         DataTable ctable = childColumns[0].Table;
117         
118                         for (int i = 0; i < parentColumns.Length; i++)
119                         {
120                                 DataColumn pc = parentColumns[i];
121                                 DataColumn cc = childColumns[i];
122                                 //not null check
123                                 if (null == pc.Table) 
124                                         throw new ArgumentException("All columns must belong to a table." + 
125                                                 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
126                                 
127                                 //All columns must belong to the same table
128                                 if (ptable != pc.Table)
129                                         throw new InvalidConstraintException("Parent columns must all belong to the same table.");
130                                 
131                                 //not null check
132                                 if (null == cc.Table) 
133                                         throw new ArgumentException("All columns must belong to a table." + 
134                                                 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
135
136                                 //All columns must belong to the same table.
137                                 if (ctable != cc.Table)
138                                         throw new InvalidConstraintException("Child columns must all belong to the same table.");
139                                 
140                                 //Can't be the same column
141                                 if (pc == cc)
142                                         throw new InvalidOperationException("Parent and child columns can't be the same column.");
143
144                                 if (! pc.DataType.Equals(cc.DataType))
145                                 {
146                                         //LAMESPEC: spec says throw InvalidConstraintException
147                                         //              implementation throws InvalidOperationException
148                                         throw new ArgumentException("Parent column is not type compatible with it's child"
149                                                 + " column.");
150                                 }
151                                         
152                         }
153                         
154                         //Same dataset.  If both are null it's ok
155                         if (ptable.DataSet != ctable.DataSet)
156                         {
157                                 //LAMESPEC: spec says InvalidConstraintExceptoin
158                                 //      impl does InvalidOperationException
159                                 throw new InvalidOperationException("Parent column and child column must belong to" + 
160                                                 " tables that belong to the same DataSet.");
161                                                 
162                         }       
163                 }
164                 
165
166
167                 private void _validateRemoveParentConstraint(ConstraintCollection sender, 
168                                 Constraint constraint, ref bool cancel, ref string failReason)
169                 {
170                         //if we hold a reference to the parent then cancel it
171                         if (constraint == _parentUniqueConstraint) 
172                         {
173                                 cancel = true;
174                                 failReason = "Cannot remove UniqueConstraint because the"
175                                         + " ForeignKeyConstraint " + this.ConstraintName + " exists.";
176                         }
177                 }
178                 
179                 //Checks to see if a related unique constraint exists
180                 //if it doesn't then a unique constraint is created.
181                 //if a unique constraint can't be created an exception will be thrown
182                 private void _ensureUniqueConstraintExists(ConstraintCollection collection,
183                                 DataColumn [] parentColumns)
184                 {
185                         //not null
186                         if (null == parentColumns) throw new ArgumentNullException(
187                                         "ParentColumns can't be null");
188
189                         UniqueConstraint uc = null;
190                         
191                         //see if unique constraint already exists
192                         //if not create unique constraint
193                         if(parentColumns[0] != null)
194                                 uc = UniqueConstraint.GetUniqueConstraintForColumnSet(parentColumns[0].Table.Constraints, parentColumns);
195
196                         if (null == uc) {
197                                 uc = new UniqueConstraint(parentColumns, false); //could throw
198                                 parentColumns [0].Table.Constraints.Add (uc);
199                         }
200
201                         //keep reference
202                         _parentUniqueConstraint = uc;
203                         //parentColumns [0].Table.Constraints.Add (uc);
204                         //if this unique constraint is attempted to be removed before us
205                         //we can fail the validation
206                         //collection.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
207                         //              _validateRemoveParentConstraint);
208
209                         parentColumns [0].Table.Constraints.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
210                                 _validateRemoveParentConstraint);
211
212                 }
213                 
214                 
215                 #endregion //Helpers
216                 
217                 #region Properties
218
219                 [DataCategory ("Data")]
220                 [DataSysDescription ("For accept and reject changes, indicates what kind of cascading should take place across this relation.")]
221                 [DefaultValue (AcceptRejectRule.None)]
222                 public virtual AcceptRejectRule AcceptRejectRule {
223                         get { return _acceptRejectRule; }
224                         set { _acceptRejectRule = value; }
225                 }
226
227                 [DataCategory ("Data")]
228                 [DataSysDescription ("Indicates the child columns of this constraint.")]
229                 [ReadOnly (true)]
230                 public virtual DataColumn[] Columns {
231                         get { return _childColumns; }
232                 }
233
234                 [DataCategory ("Data")]
235                 [DataSysDescription ("For deletions, indicates what kind of cascading should take place across this relation.")]
236                 [DefaultValue (Rule.Cascade)]
237                 public virtual Rule DeleteRule {
238                         get { return _deleteRule; }
239                         set { _deleteRule = value; }
240                 }
241
242                 [DataCategory ("Data")]
243                 [DataSysDescription ("For updates, indicates what kind of cascading should take place across this relation.")]
244                 [DefaultValue (Rule.Cascade)]
245                 public virtual Rule UpdateRule {
246                         get { return _updateRule; }
247                         set { _updateRule = value; }
248                 }
249
250                 [DataCategory ("Data")] 
251                 [DataSysDescription ("Indicates the parent columns of this constraint.")]
252                 [ReadOnly (true)]
253                 public virtual DataColumn[] RelatedColumns {
254                         get { return _parentColumns; }
255                 }
256
257                 [DataCategory ("Data")] 
258                 [DataSysDescription ("Indicates the child table of this constraint.")]
259                 [ReadOnly (true)]
260                 public virtual DataTable RelatedTable {
261                         get {
262                                 if (_parentColumns != null)
263                                         if (_parentColumns.Length > 0)
264                                                 return _parentColumns[0].Table;
265
266                                 return null;
267                         }
268                 }
269
270                 [DataCategory ("Data")]
271                 [DataSysDescription ("Indicates the table of this constraint.")]
272                 [ReadOnly (true)]
273                 public override DataTable Table {
274                         get {
275                                 if (_childColumns != null)
276                                         if (_childColumns.Length > 0)
277                                                 return _childColumns[0].Table;
278
279                                 return null;
280                         }
281                 }
282
283                 #endregion // Properties
284
285                 #region Methods
286
287                 public override bool Equals(object key) 
288                 {
289                         ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
290                         if (null == fkc) return false;
291
292                         //if the fk constrains the same columns then they are equal
293                         if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
294                                 return false;
295                         if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
296                                 return false;
297
298                         return true;
299                 }
300
301                 public override int GetHashCode()
302                 {
303                         //initialize hash1 and hash2 with default hashes
304                         //any two DIFFERENT numbers will do here
305                         int hash1 = 32, hash2 = 88;
306                         int i;
307
308                         //derive the hash code from the columns that way
309                         //Equals and GetHashCode return Equal objects to be the
310                         //same
311
312                         //Get the first parent column hash
313                         if (this.Columns.Length > 0)
314                                 hash1 ^= this.Columns[0].GetHashCode();
315                         
316                         //get the rest of the parent column hashes if there any
317                         for (i = 1; i < this.Columns.Length; i++)
318                         {
319                                 hash1 ^= this.Columns[1].GetHashCode();
320                                 
321                         }
322                         
323                         //Get the child column hash
324                         if (this.RelatedColumns.Length > 0)
325                                 hash2 ^= this.Columns[0].GetHashCode();
326                         
327                         for (i = 1; i < this.RelatedColumns.Length; i++)
328                         {
329                                 hash2 ^= this.RelatedColumns[1].GetHashCode();
330                         }
331
332                         //combine the two hashes
333                         return hash1 ^ hash2;
334                 }
335
336                 internal override void AddToConstraintCollectionSetup(
337                                 ConstraintCollection collection)
338                 {
339                         
340                         if (collection.Table != Table)
341                                 throw new InvalidConstraintException("This constraint cannot be added since ForeignKey doesn't belong to table " + RelatedTable.TableName + ".");
342
343                         //run Ctor rules again
344                         _validateColumns(_parentColumns, _childColumns);
345                         
346                         //we must have a unique constraint on the parent
347                         _ensureUniqueConstraintExists(collection, _parentColumns);      
348                         
349                         //Make sure we can create this thing
350                         AssertConstraint(); 
351                         //TODO:if this fails and we created a unique constraint
352                         //we should probably roll it back
353                         // and remove index form Table                  
354                 }
355                                         
356         
357                 [MonoTODO]
358                 internal override void RemoveFromConstraintCollectionCleanup( 
359                                 ConstraintCollection collection)
360                 {
361                         // this is not referncing the index anymore
362                         Index index = this.Index;
363                         this.Index = null;
364                         // drop the extended index on child table
365                         this.Table.DropIndex(index);
366                 }
367                 
368                 [MonoTODO]
369                 internal override void AssertConstraint()
370                 {
371                         //Constraint only works if both tables are part of the same dataset                     
372                         //if DataSet ...
373                         if (Table == null || RelatedTable == null) return; //TODO: Do we want this
374
375                         if (Table.DataSet == null || RelatedTable.DataSet == null) return; //   
376                         
377                         try {
378                                 foreach (DataRow row in Table.Rows) {
379                                         // first we check if all values in _childColumns place are nulls.
380                                         // if yes we return.
381                                         if (row.IsNullColumns(_childColumns))
382                                                 continue;
383
384                                         // check whenever there is (at least one) parent row  in RelatedTable
385                                         if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) { 
386                                                 // if no parent row exists - constraint is violated
387                                                 string values = "";
388                                                 for (int i = 0; i < _childColumns.Length; i++) {
389                                                         values += row[_childColumns[0]].ToString();
390                                                         if (i != _childColumns.Length - 1)
391                                                                 values += ",";
392                                                 }
393                                                 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + values + ") to exist in the parent table.");
394                                         }
395                                 }
396                         }
397                         catch (InvalidConstraintException){
398                                 throw new ArgumentException("This constraint cannot be enabled as not all values have corresponding parent values.");
399                         }
400
401                         // Create or rebuild index on Table
402                         // We create index for FK only if PK on the Table exists
403                         // and extended index is created based on appending PK columns to the 
404                         // FK columns                   
405                         if((this.Table.PrimaryKey != null) && (this.Table.PrimaryKey.Length > 0)) {
406                                 // rebuild extended columns
407                                 RebuildExtendedColumns();
408
409                                 if(this.Index == null) {
410                                         this.Index = this.Table.CreateIndex(this.ConstraintName + "_index",_childColumnsExtended,false);
411                                 }
412
413                                 if(UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
414                                         this.Table.InitializeIndex(this.Index);
415                                 }       
416                         }
417                 }
418                 
419                 [MonoTODO]
420                 internal override void AssertConstraint(DataRow row)
421                 {
422                         // first we check if all values in _childColumns place are nulls.
423                         // if yes we return.
424                         if (row.IsNullColumns(_childColumns))
425                                 return;
426
427                         // check whenever there is (at least one) parent row  in RelatedTable
428                         if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) { 
429                                 // if no parent row exists - constraint is violated
430                                 string values = "";
431                                 for (int i = 0; i < _childColumns.Length; i++) {
432                                         values += row[_childColumns[0]].ToString();
433                                         if (i != _childColumns.Length - 1)
434                                                 values += ",";
435                                 }
436                                 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + values + ") to exist in the parent table.");
437                         }
438
439                         // if row can be inserted - add it to constraint index
440                         // if there is no UniqueConstraint on the same columns
441                         if(this.Index != null && UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
442                                 UpdateIndex(row);
443                         }
444                 }
445
446                 internal override void RollbackAssert (DataRow row)
447                 {
448                         // first we check if all values in _childColumns place are DBNull.
449                         // if yes we return.
450                         if (row.IsNullColumns(_childColumns))
451                                 return;
452
453                         // if there is no UniqueConstraint on the same columns
454                         // we should rollback row from index
455                         if(this.Index != null && UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
456                                 RollbackIndex(row);
457                         }
458                 }
459
460                 internal void RebuildExtendedColumns()
461                 {
462                         DataColumn[] pkColumns = this.Table.PrimaryKey;
463                         if((pkColumns != null) && (pkColumns.Length > 0)) {
464                                 _childColumnsExtended = new DataColumn[_childColumns.Length + pkColumns.Length];                                        
465                                 Array.Copy(_childColumns,0,_childColumnsExtended,0,_childColumns.Length);
466                                 Array.Copy(pkColumns,0,_childColumnsExtended,_childColumns.Length,pkColumns.Length);
467                         }
468                         else {
469                                 throw new InvalidOperationException("Can not extend columns for foreign key");
470                         }
471                 }
472                 
473                 #endregion // Methods
474         }
475
476 }