Apply patch from David Sandor (David.Sandor@scigames.com): LIKE expression was not...
[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 Rule _deleteRule;
27                 private Rule _updateRule;
28                 private AcceptRejectRule _acceptRejectRule;
29                 
30                 #region Constructors
31
32                 public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn) 
33                 {
34                         if (null == parentColumn || null == childColumn) {
35                                 throw new ArgumentNullException("Neither parentColumn or" +
36                                         " childColumn can be null.");
37                         }
38
39                         _foreignKeyConstraint(null, new DataColumn[] {parentColumn},
40                                         new DataColumn[] {childColumn});
41                 }
42
43                 public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns) 
44                 {
45                         _foreignKeyConstraint(null, parentColumns, childColumns);
46                 }
47
48                 public ForeignKeyConstraint(string constraintName, DataColumn parentColumn, DataColumn childColumn) 
49                 {
50                         if (null == parentColumn || null == childColumn) {
51                                 throw new ArgumentNullException("Neither parentColumn or" +
52                                         " childColumn can be null.");
53                         }
54
55                         _foreignKeyConstraint(constraintName, new DataColumn[] {parentColumn},
56                                         new DataColumn[] {childColumn});
57                 }
58
59                 public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns) 
60                 {
61                         _foreignKeyConstraint(constraintName, parentColumns, childColumns);
62                 }
63                 
64                 //special case
65                 [MonoTODO]
66                 [Browsable (false)]
67                 public ForeignKeyConstraint(string constraintName, string parentTableName, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule) 
68                 {
69                 }
70
71                 private void _foreignKeyConstraint(string constraintName, DataColumn[] parentColumns,
72                                 DataColumn[] childColumns)
73                 {
74
75                         //Validate 
76                         _validateColumns(parentColumns, childColumns);
77
78                         //Set Constraint Name
79                         base.ConstraintName = constraintName;   
80
81                         //Keep reference to columns
82                         _parentColumns = parentColumns;
83                         _childColumns = childColumns;
84                 }
85
86                 #endregion // Constructors
87
88                 #region Helpers
89
90                 private void _validateColumns(DataColumn[] parentColumns, DataColumn[] childColumns)
91                 {
92                         //not null
93                         if (null == parentColumns || null == childColumns) 
94                                 throw new ArgumentNullException();
95                         
96                         //at least one element in each array
97                         if (parentColumns.Length < 1 || childColumns.Length < 1)
98                                 throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
99                                                 " zero length.");
100                                 
101                         //same size arrays
102                         if (parentColumns.Length != childColumns.Length)
103                                 throw new ArgumentException("Parent columns and child columns must be the same length.");
104                         
105
106                         DataTable ptable = parentColumns[0].Table;
107                         DataTable ctable = childColumns[0].Table;
108         
109                         
110                         foreach (DataColumn pc in parentColumns)
111                         {
112                                 //not null check
113                                 if (null == pc.Table) 
114                                 {
115                                         throw new ArgumentException("All columns must belong to a table." + 
116                                                         " ColumnName: " + pc.ColumnName + " does not belong to a table.");
117                                 }
118                                 
119                                 //All columns must belong to the same table
120                                 if (ptable != pc.Table)
121                                         throw new InvalidConstraintException("Parent columns must all belong to the same table.");
122                                 
123                                 foreach (DataColumn cc in childColumns)
124                                 {
125                                         //not null
126                                         if (null == pc.Table) 
127                                         {
128                                                 throw new ArgumentException("All columns must belong to a table." + 
129                                                         " ColumnName: " + pc.ColumnName + " does not belong to a table.");
130                                         }
131                         
132                                         //All columns must belong to the same table.
133                                         if (ctable != cc.Table)
134                                                 throw new InvalidConstraintException("Child columns must all belong to the same table.");
135                                                 
136                                                 
137                                         //Can't be the same column
138                                         if (pc == cc)
139                                                 throw new InvalidOperationException("Parent and child columns can't be the same column.");
140
141                                         foreach (DataColumn c2 in childColumns) {
142                                                 if (!Object.ReferenceEquals (c2.Table, cc.Table))
143                                                         throw new InvalidConstraintException ("Cannot create a Key from Columns thath belong to different tables.");
144                                         }
145
146                                         if (! pc.DataType.Equals(cc.DataType))
147                                         {
148                                                 //LAMESPEC: spec says throw InvalidConstraintException
149                                                 //              implementation throws InvalidOperationException
150                                                 throw new InvalidOperationException("Parent column is not type compatible with it's child"
151                                                                 + " column.");
152                                         }
153                                 }       
154                         }
155                         
156                         
157                         //Same dataset.  If both are null it's ok
158                         if (ptable.DataSet != ctable.DataSet)
159                         {
160                                 //LAMESPEC: spec says InvalidConstraintExceptoin
161                                 //      impl does InvalidOperationException
162                                 throw new InvalidOperationException("Parent column and child column must belong to" + 
163                                                 " tables that belong to the same DataSet.");
164                                                 
165                         }
166
167                         
168                 }
169                 
170
171
172                 private void _validateRemoveParentConstraint(ConstraintCollection sender, 
173                                 Constraint constraint, ref bool cancel, ref string failReason)
174                 {
175                         //if we hold a reference to the parent then cancel it
176                         if (constraint == _parentUniqueConstraint) 
177                         {
178                                 cancel = true;
179                                 failReason = "Cannot remove UniqueConstraint because the"
180                                         + " ForeignKeyConstraint " + this.ConstraintName + " exists.";
181                         }
182                 }
183                 
184                 //Checks to see if a related unique constraint exists
185                 //if it doesn't then a unique constraint is created.
186                 //if a unique constraint can't be created an exception will be thrown
187                 private void _ensureUniqueConstraintExists(ConstraintCollection collection,
188                                 DataColumn [] parentColumns)
189                 {
190                         //not null
191                         if (null == parentColumns) throw new ArgumentNullException(
192                                         "ParentColumns can't be null");
193
194                         UniqueConstraint uc = null;
195                         
196                         //see if unique constraint already exists
197                         //if not create unique constraint
198                         uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, parentColumns);
199
200                         if (null == uc) uc = new UniqueConstraint(parentColumns, false); //could throw
201
202                         //keep reference
203                         _parentUniqueConstraint = uc;
204                         parentColumns [0].Table.Constraints.Add (uc);
205                         //if this unique constraint is attempted to be removed before us
206                         //we can fail the validation
207                         collection.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
208                                         _validateRemoveParentConstraint);
209                 }
210                 
211                 
212                 #endregion //Helpers
213                 
214                 #region Properties
215
216                 [DataCategory ("Data")]
217                 [DataSysDescription ("For accept and reject changes, indicates what kind of cascading should take place across this relation.")]
218                 [DefaultValue (AcceptRejectRule.None)]
219                 public virtual AcceptRejectRule AcceptRejectRule {
220                         get { return _acceptRejectRule; }
221                         set { _acceptRejectRule = value; }
222                 }
223
224                 [DataCategory ("Data")]
225                 [DataSysDescription ("Indicates the child columns of this constraint.")]
226                 [ReadOnly (true)]
227                 public virtual DataColumn[] Columns {
228                         get { return _childColumns; }
229                 }
230
231                 [DataCategory ("Data")]
232                 [DataSysDescription ("For deletions, indicates what kind of cascading should take place across this relation.")]
233                 [DefaultValue (Rule.Cascade)]
234                 public virtual Rule DeleteRule {
235                         get { return _deleteRule; }
236                         set { _deleteRule = value; }
237                 }
238
239                 [DataCategory ("Data")]
240                 [DataSysDescription ("For updates, indicates what kind of cascading should take place across this relation.")]
241                 [DefaultValue (Rule.Cascade)]
242                 public virtual Rule UpdateRule {
243                         get { return _updateRule; }
244                         set { _updateRule = value; }
245                 }
246
247                 [DataCategory ("Data")] 
248                 [DataSysDescription ("Indicates the parent columns of this constraint.")]
249                 [ReadOnly (true)]
250                 public virtual DataColumn[] RelatedColumns {
251                         get { return _parentColumns; }
252                 }
253
254                 [DataCategory ("Data")] 
255                 [DataSysDescription ("Indicates the child table of this constraint.")]
256                 [ReadOnly (true)]
257                 public virtual DataTable RelatedTable {
258                         get {
259                                 if (_parentColumns != null)
260                                         if (_parentColumns.Length > 0)
261                                                 return _parentColumns[0].Table;
262
263                                 return null;
264                         }
265                 }
266
267                 [DataCategory ("Data")]
268                 [DataSysDescription ("Indicates the table of this constraint.")]
269                 [ReadOnly (true)]
270                 public override DataTable Table {
271                         get {
272                                 if (_childColumns != null)
273                                         if (_childColumns.Length > 0)
274                                                 return _childColumns[0].Table;
275
276                                 return null;
277                         }
278                 }
279
280                 #endregion // Properties
281
282                 #region Methods
283
284                 public override bool Equals(object key) 
285                 {
286                         ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
287                         if (null == fkc) return false;
288
289                         //if the fk constrains the same columns then they are equal
290                         if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
291                                 return false;
292                         if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
293                                 return false;
294
295                         return true;
296                 }
297
298                 public override int GetHashCode()
299                 {
300                         //initialize hash1 and hash2 with default hashes
301                         //any two DIFFERENT numbers will do here
302                         int hash1 = 32, hash2 = 88;
303                         int i;
304
305                         //derive the hash code from the columns that way
306                         //Equals and GetHashCode return Equal objects to be the
307                         //same
308
309                         //Get the first parent column hash
310                         if (this.Columns.Length > 0)
311                                 hash1 ^= this.Columns[0].GetHashCode();
312                         
313                         //get the rest of the parent column hashes if there any
314                         for (i = 1; i < this.Columns.Length; i++)
315                         {
316                                 hash1 ^= this.Columns[1].GetHashCode();
317                                 
318                         }
319                         
320                         //Get the child column hash
321                         if (this.RelatedColumns.Length > 0)
322                                 hash2 ^= this.Columns[0].GetHashCode();
323                         
324                         for (i = 1; i < this.RelatedColumns.Length; i++)
325                         {
326                                 hash2 ^= this.RelatedColumns[1].GetHashCode();
327                         }
328
329                         //combine the two hashes
330                         return hash1 ^ hash2;
331                 }
332
333                 internal override void AddToConstraintCollectionSetup(
334                                 ConstraintCollection collection)
335                 {
336
337                         //run Ctor rules again
338                         _validateColumns(_parentColumns, _childColumns);
339                         
340                         //we must have a unique constraint on the parent
341                         _ensureUniqueConstraintExists(collection, _parentColumns);
342                         
343                         //Make sure we can create this thing
344                         AssertConstraint(); //TODO:if this fails and we created a unique constraint
345                                                 //we should probably roll it back
346                         
347                 }
348                                         
349         
350                 [MonoTODO]
351                 internal override void RemoveFromConstraintCollectionCleanup( 
352                                 ConstraintCollection collection)
353                 {
354                         return; //no rules yet          
355                 }
356                 
357                 [MonoTODO]
358                 internal override void AssertConstraint()
359                 {
360                         //Constraint only works if both tables are part of the same dataset
361                         
362                         //if DataSet ...
363                         if (Table == null || RelatedTable == null) return; //TODO: Do we want this
364
365                         if (Table.DataSet == null || RelatedTable.DataSet == null) return; //   
366                                 
367                         //TODO:
368                         //check for orphaned children
369                         //check for...
370                         
371                 }
372                 
373                 [MonoTODO]
374                 internal override void AssertConstraint(DataRow row)
375                 {
376                         //Implement: this should be used to validate ForeignKeys constraints 
377                         //when modifiying the DataRow values of a DataTable.
378                 }
379                 
380                 #endregion // Methods
381         }
382
383 }