(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / System.Data / System.Data / ForeignKeyConstraint.cs
blob3e03e3ff0655e887c80295f1cd05fc471b1bd3d7
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
13 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 //
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 //
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System;
36 using System.Collections;
37 using System.ComponentModel;
38 using System.Runtime.InteropServices;
40 namespace System.Data {
41 [Editor]
42 [DefaultProperty ("ConstraintName")]
43 [Serializable]
44 public class ForeignKeyConstraint : Constraint
46 private UniqueConstraint _parentUniqueConstraint;
47 private DataColumn [] _parentColumns;
48 private DataColumn [] _childColumns;
49 private DataColumn [] _childColumnsExtended;
50 private Rule _deleteRule = Rule.Cascade;
51 private Rule _updateRule = Rule.Cascade;
52 private AcceptRejectRule _acceptRejectRule = AcceptRejectRule.None;
53 private string _parentTableName;
54 private string _childTableName;
55 private string [] _parentColumnNames;
56 private string [] _childColumnNames;
57 private bool _dataColsNotValidated = false;
59 #region Constructors
61 public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn)
63 if (null == parentColumn || null == childColumn) {
64 throw new NullReferenceException("Neither parentColumn or" +
65 " childColumn can be null.");
67 _foreignKeyConstraint(null, new DataColumn[] {parentColumn},
68 new DataColumn[] {childColumn});
71 public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns)
73 _foreignKeyConstraint(null, parentColumns, childColumns);
76 public ForeignKeyConstraint(string constraintName, DataColumn parentColumn, DataColumn childColumn)
78 if (null == parentColumn || null == childColumn) {
79 throw new NullReferenceException("Neither parentColumn or" +
80 " childColumn can be null.");
83 _foreignKeyConstraint(constraintName, new DataColumn[] {parentColumn},
84 new DataColumn[] {childColumn});
87 public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns)
89 _foreignKeyConstraint(constraintName, parentColumns, childColumns);
92 //special case
93 [Browsable (false)]
94 public ForeignKeyConstraint(string constraintName, string parentTableName, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
96 _dataColsNotValidated = true;
97 base.ConstraintName = constraintName;
99 // "parentTableName" is searched in the "DataSet" to which the "DataTable"
100 // from which AddRange() is called
101 // childTable is the "DataTable" which calls AddRange()
103 // Keep reference to parentTableName to resolve later
104 _parentTableName = parentTableName;
106 // Keep reference to parentColumnNames to resolve later
107 _parentColumnNames = parentColumnNames;
109 // Keep reference to childColumnNames to resolve later
110 _childColumnNames = childColumnNames;
112 _acceptRejectRule = acceptRejectRule;
113 _deleteRule = deleteRule;
114 _updateRule = updateRule;
118 internal void postAddRange (DataTable childTable)
120 // LAMESPEC - Does not say that this is mandatory
121 // Check whether childTable belongs to a DataSet
122 if (childTable.DataSet == null)
123 throw new InvalidConstraintException ("ChildTable : " + childTable.TableName + " does not belong to any DataSet");
124 DataSet dataSet = childTable.DataSet;
125 _childTableName = childTable.TableName;
126 // Search for the parentTable in the childTable's DataSet
127 if (!dataSet.Tables.Contains (_parentTableName))
128 throw new InvalidConstraintException ("Table : " + _parentTableName + "does not exist in DataSet : " + dataSet);
130 // Keep reference to parentTable
131 DataTable parentTable = dataSet.Tables [_parentTableName];
133 int i = 0, j = 0;
135 // LAMESPEC - Does not say which Exception is thrown
136 if (_parentColumnNames.Length < 0 || _childColumnNames.Length < 0)
137 throw new InvalidConstraintException ("Neither parent nor child columns can be zero length");
138 // LAMESPEC - Does not say which Exception is thrown
139 if (_parentColumnNames.Length != _childColumnNames.Length)
140 throw new InvalidConstraintException ("Both parent and child columns must be of same length");
141 DataColumn []parentColumns = new DataColumn [_parentColumnNames.Length];
142 DataColumn []childColumns = new DataColumn [_childColumnNames.Length];
144 // Search for the parentColumns in parentTable
145 foreach (string parentCol in _parentColumnNames){
146 if (!parentTable.Columns.Contains (parentCol))
147 throw new InvalidConstraintException ("Table : " + _parentTableName + "does not contain the column :" + parentCol);
148 parentColumns [i++] = parentTable. Columns [parentCol];
150 // Search for the childColumns in childTable
151 foreach (string childCol in _childColumnNames){
152 if (!childTable.Columns.Contains (childCol))
153 throw new InvalidConstraintException ("Table : " + _childTableName + "does not contain the column : " + childCol);
154 childColumns [j++] = childTable.Columns [childCol];
156 _validateColumns (parentColumns, childColumns);
157 _parentColumns = parentColumns;
158 _childColumns = childColumns;
161 #if NET_2_0
162 [MonoTODO]
163 public ForeignKeyConstraint (string constraintName, string parentTableName, string parentTableNamespace, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
165 throw new NotImplementedException ();
167 #endif
169 private void _foreignKeyConstraint(string constraintName, DataColumn[] parentColumns,
170 DataColumn[] childColumns)
173 //Validate
174 _validateColumns(parentColumns, childColumns);
176 //Set Constraint Name
177 base.ConstraintName = constraintName;
179 //Keep reference to columns
180 _parentColumns = parentColumns;
181 _childColumns = childColumns;
184 #endregion // Constructors
186 #region Helpers
188 private void _validateColumns(DataColumn[] parentColumns, DataColumn[] childColumns)
190 //not null
191 if (null == parentColumns || null == childColumns)
192 throw new ArgumentNullException();
194 //at least one element in each array
195 if (parentColumns.Length < 1 || childColumns.Length < 1)
196 throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
197 " zero length.");
199 //same size arrays
200 if (parentColumns.Length != childColumns.Length)
201 throw new ArgumentException("Parent columns and child columns must be the same length.");
204 DataTable ptable = parentColumns[0].Table;
205 DataTable ctable = childColumns[0].Table;
207 for (int i = 0; i < parentColumns.Length; i++) {
208 DataColumn pc = parentColumns[i];
209 DataColumn cc = childColumns[i];
211 //not null check
212 if (null == pc.Table)
213 throw new ArgumentException("All columns must belong to a table." +
214 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
216 //All columns must belong to the same table
217 if (ptable != pc.Table)
218 throw new InvalidConstraintException("Parent columns must all belong to the same table.");
220 //not null check
221 if (null == cc.Table)
222 throw new ArgumentException("All columns must belong to a table." +
223 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
225 //All columns must belong to the same table.
226 if (ctable != cc.Table)
227 throw new InvalidConstraintException("Child columns must all belong to the same table.");
231 for (int i = 0; i < parentColumns.Length; i++)
233 DataColumn pc = parentColumns[i];
234 DataColumn cc = childColumns[i];
236 //Can't be the same column
237 if (pc == cc)
238 throw new InvalidOperationException("Parent and child columns can't be the same column.");
240 if (! pc.DataType.Equals(cc.DataType))
242 //LAMESPEC: spec says throw InvalidConstraintException
243 // implementation throws InvalidOperationException
244 throw new InvalidConstraintException("Parent column is not type compatible with it's child"
245 + " column.");
250 //Same dataset. If both are null it's ok
251 if (ptable.DataSet != ctable.DataSet)
253 //LAMESPEC: spec says InvalidConstraintExceptoin
254 // impl does InvalidOperationException
255 throw new InvalidOperationException("Parent column and child column must belong to" +
256 " tables that belong to the same DataSet.");
263 private void _validateRemoveParentConstraint(ConstraintCollection sender,
264 Constraint constraint, ref bool cancel, ref string failReason)
266 #if !NET_1_1
267 //if we hold a reference to the parent then cancel it
268 if (constraint == _parentUniqueConstraint)
270 cancel = true;
271 failReason = "Cannot remove UniqueConstraint because the"
272 + " ForeignKeyConstraint " + this.ConstraintName + " exists.";
274 #endif
277 //Checks to see if a related unique constraint exists
278 //if it doesn't then a unique constraint is created.
279 //if a unique constraint can't be created an exception will be thrown
280 private void _ensureUniqueConstraintExists(ConstraintCollection collection,
281 DataColumn [] parentColumns)
283 //not null
284 if (null == parentColumns) throw new ArgumentNullException(
285 "ParentColumns can't be null");
287 UniqueConstraint uc = null;
289 //see if unique constraint already exists
290 //if not create unique constraint
291 if(parentColumns[0] != null)
292 uc = UniqueConstraint.GetUniqueConstraintForColumnSet(parentColumns[0].Table.Constraints, parentColumns);
294 if (null == uc) {
295 uc = new UniqueConstraint(parentColumns, false); //could throw
296 parentColumns [0].Table.Constraints.Add (uc);
299 //keep reference
300 _parentUniqueConstraint = uc;
301 //parentColumns [0].Table.Constraints.Add (uc);
302 //if this unique constraint is attempted to be removed before us
303 //we can fail the validation
304 //collection.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
305 // _validateRemoveParentConstraint);
307 parentColumns [0].Table.Constraints.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
308 _validateRemoveParentConstraint);
313 #endregion //Helpers
315 #region Properties
317 [DataCategory ("Data")]
318 [DataSysDescription ("For accept and reject changes, indicates what kind of cascading should take place across this relation.")]
319 [DefaultValue (AcceptRejectRule.None)]
320 public virtual AcceptRejectRule AcceptRejectRule {
321 get { return _acceptRejectRule; }
322 set { _acceptRejectRule = value; }
325 [DataCategory ("Data")]
326 [DataSysDescription ("Indicates the child columns of this constraint.")]
327 [ReadOnly (true)]
328 public virtual DataColumn[] Columns {
329 get { return _childColumns; }
332 [DataCategory ("Data")]
333 [DataSysDescription ("For deletions, indicates what kind of cascading should take place across this relation.")]
334 [DefaultValue (Rule.Cascade)]
335 public virtual Rule DeleteRule {
336 get { return _deleteRule; }
337 set { _deleteRule = value; }
340 [DataCategory ("Data")]
341 [DataSysDescription ("For updates, indicates what kind of cascading should take place across this relation.")]
342 [DefaultValue (Rule.Cascade)]
343 public virtual Rule UpdateRule {
344 get { return _updateRule; }
345 set { _updateRule = value; }
348 [DataCategory ("Data")]
349 [DataSysDescription ("Indicates the parent columns of this constraint.")]
350 [ReadOnly (true)]
351 public virtual DataColumn[] RelatedColumns {
352 get { return _parentColumns; }
355 [DataCategory ("Data")]
356 [DataSysDescription ("Indicates the child table of this constraint.")]
357 [ReadOnly (true)]
358 public virtual DataTable RelatedTable {
359 get {
360 if (_parentColumns != null)
361 if (_parentColumns.Length > 0)
362 return _parentColumns[0].Table;
364 throw new InvalidOperationException ("Property not accessible because 'Object reference not set to an instance of an object'");
368 [DataCategory ("Data")]
369 [DataSysDescription ("Indicates the table of this constraint.")]
370 [ReadOnly (true)]
371 public override DataTable Table {
372 get {
373 if (_childColumns != null)
374 if (_childColumns.Length > 0)
375 return _childColumns[0].Table;
377 throw new InvalidOperationException ("Property not accessible because 'Object reference not set to an instance of an object'");
381 internal bool DataColsNotValidated{
382 get{ return (_dataColsNotValidated); }
386 #endregion // Properties
388 #region Methods
390 public override bool Equals(object key)
392 ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
393 if (null == fkc) return false;
395 //if the fk constrains the same columns then they are equal
396 if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
397 return false;
398 if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
399 return false;
401 return true;
404 public override int GetHashCode()
406 //initialize hash1 and hash2 with default hashes
407 //any two DIFFERENT numbers will do here
408 int hash1 = 32, hash2 = 88;
409 int i;
411 //derive the hash code from the columns that way
412 //Equals and GetHashCode return Equal objects to be the
413 //same
415 //Get the first parent column hash
416 if (this.Columns.Length > 0)
417 hash1 ^= this.Columns[0].GetHashCode();
419 //get the rest of the parent column hashes if there any
420 for (i = 1; i < this.Columns.Length; i++)
422 hash1 ^= this.Columns[1].GetHashCode();
426 //Get the child column hash
427 if (this.RelatedColumns.Length > 0)
428 hash2 ^= this.Columns[0].GetHashCode();
430 for (i = 1; i < this.RelatedColumns.Length; i++)
432 hash2 ^= this.RelatedColumns[1].GetHashCode();
435 //combine the two hashes
436 return hash1 ^ hash2;
439 internal override void AddToConstraintCollectionSetup(
440 ConstraintCollection collection)
443 if (collection.Table != Table)
444 throw new InvalidConstraintException("This constraint cannot be added since ForeignKey doesn't belong to table " + RelatedTable.TableName + ".");
446 //run Ctor rules again
447 _validateColumns(_parentColumns, _childColumns);
449 //we must have a unique constraint on the parent
450 _ensureUniqueConstraintExists(collection, _parentColumns);
452 //Make sure we can create this thing
453 AssertConstraint();
454 //TODO:if this fails and we created a unique constraint
455 //we should probably roll it back
456 // and remove index form Table
460 [MonoTODO]
461 internal override void RemoveFromConstraintCollectionCleanup(
462 ConstraintCollection collection)
464 // this is not referncing the index anymore
465 Index index = this.Index;
466 this.Index = null;
467 // drop the extended index on child table
468 this.Table.DropIndex(index);
471 [MonoTODO]
472 internal override void AssertConstraint()
474 //Constraint only works if both tables are part of the same dataset
475 //if DataSet ...
476 if (Table == null || RelatedTable == null) return; //TODO: Do we want this
478 if (Table.DataSet == null || RelatedTable.DataSet == null) return; //
480 try {
481 foreach (DataRow row in Table.Rows) {
482 // first we check if all values in _childColumns place are nulls.
483 // if yes we return.
484 if (row.IsNullColumns(_childColumns))
485 continue;
487 // check whenever there is (at least one) parent row in RelatedTable
488 if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) {
489 // if no parent row exists - constraint is violated
490 string values = "";
491 for (int i = 0; i < _childColumns.Length; i++) {
492 values += row[_childColumns[0]].ToString();
493 if (i != _childColumns.Length - 1)
494 values += ",";
496 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + values + ") to exist in the parent table.");
500 catch (InvalidConstraintException){
501 throw new ArgumentException("This constraint cannot be enabled as not all values have corresponding parent values.");
504 // Create or rebuild index on Table
505 // We create index for FK only if PK on the Table exists
506 // and extended index is created based on appending PK columns to the
507 // FK columns
508 if((this.Table.PrimaryKey != null) && (this.Table.PrimaryKey.Length > 0)) {
509 // rebuild extended columns
510 RebuildExtendedColumns();
512 if(this.Index == null) {
513 this.Index = this.Table.CreateIndex(this.ConstraintName + "_index",_childColumnsExtended,false);
516 if(UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
517 this.Table.InitializeIndex(this.Index);
522 [MonoTODO]
523 internal override void AssertConstraint(DataRow row)
525 // first we check if all values in _childColumns place are nulls.
526 // if yes we return.
527 if (row.IsNullColumns(_childColumns))
528 return;
530 // check whenever there is (at least one) parent row in RelatedTable
531 if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) {
532 // if no parent row exists - constraint is violated
533 string values = "";
534 for (int i = 0; i < _childColumns.Length; i++) {
535 values += row[_childColumns[0]].ToString();
536 if (i != _childColumns.Length - 1)
537 values += ",";
539 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + values + ") to exist in the parent table.");
542 // if row can be inserted - add it to constraint index
543 // if there is no UniqueConstraint on the same columns
544 if(this.Index != null && UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
545 UpdateIndex(row);
549 internal override void RollbackAssert (DataRow row)
551 // first we check if all values in _childColumns place are DBNull.
552 // if yes we return.
553 if (row.IsNullColumns(_childColumns))
554 return;
556 // if there is no UniqueConstraint on the same columns
557 // we should rollback row from index
558 if(this.Index != null && UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
559 RollbackIndex(row);
563 internal void RebuildExtendedColumns()
565 DataColumn[] pkColumns = this.Table.PrimaryKey;
566 if((pkColumns != null) && (pkColumns.Length > 0)) {
567 _childColumnsExtended = new DataColumn[_childColumns.Length + pkColumns.Length];
568 Array.Copy(_childColumns,0,_childColumnsExtended,0,_childColumns.Length);
569 Array.Copy(pkColumns,0,_childColumnsExtended,_childColumns.Length,pkColumns.Length);
571 else {
572 throw new InvalidOperationException("Can not extend columns for foreign key");
576 #endregion // Methods