[runtime] Overwrite stacktrace for exception on re-throw. Fixes #1856.
[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 //
13 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
14 //
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.
33 //
34
35 using System;
36 using System.Collections;
37 using System.ComponentModel;
38 using System.Runtime.InteropServices;
39 using System.Data.Common;
40
41 namespace System.Data {
42         [Editor ("Microsoft.VSDesigner.Data.Design.ForeignKeyConstraintEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
43                  "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
44         [DefaultProperty ("ConstraintName")]
45         public class ForeignKeyConstraint : Constraint 
46         {
47                 private UniqueConstraint _parentUniqueConstraint;
48                 //FIXME: create a class which will wrap this collection
49                 private DataColumn [] _parentColumns;
50                 //FIXME: create a class which will wrap this collection
51                 private DataColumn [] _childColumns;
52                 private Rule _deleteRule = Rule.Cascade;
53                 private Rule _updateRule = Rule.Cascade;
54                 private AcceptRejectRule _acceptRejectRule = AcceptRejectRule.None;
55                 private string _parentTableName;
56                 private string _parentTableNamespace;
57                 private string _childTableName;
58
59                 //FIXME: remove those; and use only DataColumns[]
60                 private string [] _parentColumnNames;
61                 private string [] _childColumnNames;
62                         
63                 #region Constructors
64
65                 public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn) 
66                 {
67                         if (null == parentColumn || null == childColumn) {
68                                 throw new NullReferenceException("Neither parentColumn or" +
69                                         " childColumn can be null.");
70                         }
71                         _foreignKeyConstraint(null, new DataColumn[] {parentColumn},
72                                         new DataColumn[] {childColumn});
73                 }
74
75                 public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns) 
76                 {
77                         _foreignKeyConstraint(null, parentColumns, childColumns);
78                 }
79
80                 public ForeignKeyConstraint(string constraintName, DataColumn parentColumn, DataColumn childColumn) 
81                 {
82                         if (null == parentColumn || null == childColumn) {
83                                 throw new NullReferenceException("Neither parentColumn or" +
84                                         " childColumn can be null.");
85                         }
86
87                         _foreignKeyConstraint(constraintName, new DataColumn[] {parentColumn},
88                                         new DataColumn[] {childColumn});
89                 }
90
91                 public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns) 
92                 {
93                         _foreignKeyConstraint(constraintName, parentColumns, childColumns);
94                 }
95
96                 //special case
97                 [Browsable (false)]
98                 public ForeignKeyConstraint(string constraintName, string parentTableName, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule) 
99                 {
100                         int i;
101                         InitInProgress = true;
102                         base.ConstraintName = constraintName;
103
104                         // "parentTableName" is searched in the "DataSet" to which the "DataTable"
105                         // from which AddRange() is called
106                         // childTable is the "DataTable" which calls AddRange()
107
108                         // Keep reference to parentTableName to resolve later
109                         _parentTableName = parentTableName;
110
111                         // Keep a copy of parentColumnNames to resolve later
112                         _parentColumnNames = new string [parentColumnNames.Length];
113                         for (i = 0; i < parentColumnNames.Length; i++)
114                                _parentColumnNames[i] = parentColumnNames[i];
115                         
116                         // Keep a copy of childColumnNames to resolve later
117                         _childColumnNames = new string [childColumnNames.Length];
118                         for (i = 0; i < childColumnNames.Length; i++)
119                                _childColumnNames[i] = childColumnNames[i];
120                        
121
122                         _acceptRejectRule = acceptRejectRule;
123                         _deleteRule = deleteRule;
124                         _updateRule = updateRule;
125                 }
126
127                 internal override void FinishInit (DataTable childTable)
128                 {
129                         if (childTable.DataSet == null)
130                                 throw new InvalidConstraintException ("ChildTable : " + childTable.TableName + " does not belong to any DataSet");
131
132                         DataSet dataSet = childTable.DataSet;
133                         _childTableName = childTable.TableName;
134
135                         if (!dataSet.Tables.Contains (_parentTableName))
136                                 throw new InvalidConstraintException ("Table : " + _parentTableName + "does not exist in DataSet : " + dataSet);
137
138                         DataTable parentTable = dataSet.Tables [_parentTableName];
139
140                         int i = 0, j = 0;
141
142                         if (_parentColumnNames.Length < 0 || _childColumnNames.Length < 0)
143                                 throw new InvalidConstraintException ("Neither parent nor child columns can be zero length");
144
145                         if (_parentColumnNames.Length != _childColumnNames.Length)
146                                 throw new InvalidConstraintException ("Both parent and child columns must be of same length");                                                                                                    
147                         DataColumn[] parentColumns = new DataColumn [_parentColumnNames.Length];
148                         DataColumn[] childColumns = new DataColumn [_childColumnNames.Length];
149
150                         foreach (string parentCol in _parentColumnNames){
151                                 if (!parentTable.Columns.Contains (parentCol))
152                                         throw new InvalidConstraintException ("Table : " + _parentTableName + "does not contain the column :" + parentCol);
153                                 parentColumns [i++] = parentTable. Columns [parentCol];
154                         }
155
156                         foreach (string childCol in _childColumnNames){
157                                 if (!childTable.Columns.Contains (childCol))
158                                         throw new InvalidConstraintException ("Table : " + _childTableName + "does not contain the column : " + childCol);
159                                 childColumns [j++] = childTable.Columns [childCol];
160                         }
161                         _validateColumns (parentColumns, childColumns);
162                         _parentColumns = parentColumns;
163                         _childColumns = childColumns;
164                         parentTable.Namespace = _parentTableNamespace;
165                         InitInProgress = false;
166                 }
167
168                 [Browsable (false)]
169                 public ForeignKeyConstraint (string constraintName, string parentTableName, string parentTableNamespace, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
170                 {
171                         InitInProgress = true;
172                         base.ConstraintName = constraintName;
173
174                         // "parentTableName" is searched in the "DataSet" to which the "DataTable"
175                         // from which AddRange() is called
176                         // childTable is the "DataTable" which calls AddRange()
177
178                         // Keep reference to parentTableName to resolve later
179                         _parentTableName = parentTableName;
180                         _parentTableNamespace = parentTableNamespace;
181
182                         // Keep reference to parentColumnNames to resolve later
183                         _parentColumnNames = parentColumnNames;
184
185                         // Keep reference to childColumnNames to resolve later
186                         _childColumnNames = childColumnNames;
187
188                         _acceptRejectRule = acceptRejectRule;
189                         _deleteRule = deleteRule;
190                         _updateRule = updateRule;
191                 }
192
193                 private void _foreignKeyConstraint(string constraintName, DataColumn[] parentColumns,
194                                 DataColumn[] childColumns)
195                 {
196                         int i;
197                         //Validate 
198                         _validateColumns(parentColumns, childColumns);
199
200                         //Set Constraint Name
201                         base.ConstraintName = constraintName;   
202
203                         //copy the columns - Do not keep reference #672113
204                         _parentColumns = new DataColumn [parentColumns.Length];
205                         for (i = 0; i < parentColumns.Length; i++)
206                                _parentColumns[i] = parentColumns[i];
207                         
208                         _childColumns = new DataColumn [childColumns.Length];                   
209                         for (i = 0; i < childColumns.Length; i++)
210                                _childColumns[i] = childColumns[i];
211                 }
212
213 #endregion // Constructors
214
215 #region Helpers
216
217                 private void _validateColumns(DataColumn[] parentColumns, DataColumn[] childColumns)
218                 {
219                         //not null
220                         if (null == parentColumns || null == childColumns) 
221                                 throw new ArgumentNullException();
222
223                         //at least one element in each array
224                         if (parentColumns.Length < 1 || childColumns.Length < 1)
225                                 throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
226                                                 " zero length.");
227                                 
228                         //same size arrays
229                         if (parentColumns.Length != childColumns.Length)
230                                 throw new ArgumentException("Parent columns and child columns must be the same length.");
231                         
232
233                         DataTable ptable = parentColumns[0].Table;
234                         DataTable ctable = childColumns[0].Table;
235
236                         for (int i = 0; i < parentColumns.Length; i++) {
237                                 DataColumn pc = parentColumns[i];
238                                 DataColumn cc = childColumns[i];
239
240                                 //not null check
241                                 if (null == pc.Table) 
242                                         throw new ArgumentException("All columns must belong to a table." + 
243                                                 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
244                                 
245                                 //All columns must belong to the same table
246                                 if (ptable != pc.Table)
247                                         throw new InvalidConstraintException("Parent columns must all belong to the same table.");
248
249                                 //not null check
250                                 if (null == cc.Table) 
251                                         throw new ArgumentException("All columns must belong to a table." + 
252                                                 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
253
254                                 //All columns must belong to the same table.
255                                 if (ctable != cc.Table)
256                                         throw new InvalidConstraintException("Child columns must all belong to the same table.");
257
258                                 if (pc.CompiledExpression != null)
259                                         throw new ArgumentException(String.Format("Cannot create a constraint based on Expression column {0}.", pc.ColumnName));
260
261                                 if (cc.CompiledExpression != null)
262                                         throw new ArgumentException(String.Format("Cannot create a constraint based on Expression column {0}.", cc.ColumnName));
263                                 
264                         }
265
266                         //Same dataset.  If both are null it's ok
267                         if (ptable.DataSet != ctable.DataSet)
268                         {
269                                 //LAMESPEC: spec says InvalidConstraintExceptoin
270                                 //      impl does InvalidOperationException
271                                 throw new InvalidOperationException("Parent column and child column must belong to" + 
272                                                 " tables that belong to the same DataSet.");
273                                                 
274                         }       
275
276
277                         int identicalCols = 0;
278                         for (int i = 0; i < parentColumns.Length; i++)
279                         {
280                                 DataColumn pc = parentColumns[i];
281                                 DataColumn cc = childColumns[i];
282                                 
283                                 if (pc == cc) {
284                                         identicalCols++;
285                                         continue;
286                                 }
287
288                                 if (!pc.DataTypeMatches (cc)) {
289                                         //LAMESPEC: spec says throw InvalidConstraintException
290                                         //              implementation throws InvalidOperationException
291                                         throw new InvalidOperationException ("Parent column is not type compatible with it's child"
292                                                 + " column.");
293                                 }
294                         }
295                         if (identicalCols == parentColumns.Length)
296                                 throw new InvalidOperationException ("Property not accessible because 'ParentKey and ChildKey are identical.'.");
297                         
298                 }
299                 
300                 //Checks to see if a related unique constraint exists
301                 //if it doesn't then a unique constraint is created.
302                 //if a unique constraint can't be created an exception will be thrown
303                 private void _ensureUniqueConstraintExists(ConstraintCollection collection,
304                                 DataColumn [] parentColumns)
305                 {
306                         //not null
307                         if (null == parentColumns) throw new ArgumentNullException(
308                                         "ParentColumns can't be null");
309
310                         UniqueConstraint uc = null;
311                         
312                         //see if unique constraint already exists
313                         //if not create unique constraint
314                         if(parentColumns[0] != null)
315                                 uc = UniqueConstraint.GetUniqueConstraintForColumnSet(parentColumns[0].Table.Constraints, parentColumns);
316
317                         if (null == uc) {
318                                 uc = new UniqueConstraint(parentColumns, false); //could throw
319                                 parentColumns [0].Table.Constraints.Add (uc);
320                         }
321
322                         //keep reference
323                         _parentUniqueConstraint = uc;
324                         _parentUniqueConstraint.ChildConstraint = this;
325                 }
326                 
327                 
328                 #endregion //Helpers
329                 
330                 #region Properties
331
332                 [DataCategory ("Data")]
333                 [DefaultValue (AcceptRejectRule.None)]
334                 public virtual AcceptRejectRule AcceptRejectRule {
335                         get { return _acceptRejectRule; }
336                         set { _acceptRejectRule = value; }
337                 }
338
339                 [DataCategory ("Data")]
340                 [ReadOnly (true)]
341                 public virtual DataColumn[] Columns {
342                         get { return _childColumns; }
343                 }
344
345                 [DataCategory ("Data")]
346                 [DefaultValue (Rule.Cascade)]
347                 public virtual Rule DeleteRule {
348                         get { return _deleteRule; }
349                         set { _deleteRule = value; }
350                 }
351
352                 [DataCategory ("Data")]
353                 [DefaultValue (Rule.Cascade)]
354                 public virtual Rule UpdateRule {
355                         get { return _updateRule; }
356                         set { _updateRule = value; }
357                 }
358
359                 [DataCategory ("Data")] 
360                 [ReadOnly (true)]
361                 public virtual DataColumn[] RelatedColumns {
362                         get { return _parentColumns; }
363                 }
364
365                 [DataCategory ("Data")] 
366                 [ReadOnly (true)]
367                 public virtual DataTable RelatedTable {
368                         get {
369                                 if (_parentColumns != null)
370                                         if (_parentColumns.Length > 0)
371                                                 return _parentColumns[0].Table;
372
373                                 throw new InvalidOperationException ("Property not accessible because 'Object reference not set to an instance of an object'");
374                         }
375                 }
376
377                 [DataCategory ("Data")]
378                 [ReadOnly (true)]
379                 public override DataTable Table {
380                         get {
381                                 if (_childColumns != null)
382                                         if (_childColumns.Length > 0)
383                                                 return _childColumns[0].Table;
384
385                                 throw new InvalidOperationException ("Property not accessible because 'Object reference not set to an instance of an object'");
386                         }
387                 }
388
389                 internal UniqueConstraint ParentConstraint {
390                         get { return _parentUniqueConstraint; }
391                 }
392
393                 #endregion // Properties
394
395                 #region Methods
396
397                 public override bool Equals(object key) 
398                 {
399                         ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
400                         if (null == fkc) return false;
401
402                         //if the fk constrains the same columns then they are equal
403                         if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
404                                 return false;
405                         if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
406                                 return false;
407
408                         return true;
409                 }
410
411                 public override int GetHashCode()
412                 {
413                         //initialize hash1 and hash2 with default hashes
414                         //any two DIFFERENT numbers will do here
415                         int hash1 = 32, hash2 = 88;
416                         int i;
417
418                         //derive the hash code from the columns that way
419                         //Equals and GetHashCode return Equal objects to be the
420                         //same
421
422                         //Get the first parent column hash
423                         if (this.Columns.Length > 0)
424                                 hash1 ^= this.Columns[0].GetHashCode();
425                         
426                         //get the rest of the parent column hashes if there any
427                         for (i = 1; i < this.Columns.Length; i++)
428                         {
429                                 hash1 ^= this.Columns[1].GetHashCode();
430                                 
431                         }
432                         
433                         //Get the child column hash
434                         if (this.RelatedColumns.Length > 0)
435                                 hash2 ^= this.Columns[0].GetHashCode();
436                         
437                         for (i = 1; i < this.RelatedColumns.Length; i++)
438                         {
439                                 hash2 ^= this.RelatedColumns[1].GetHashCode();
440                         }
441
442                         //combine the two hashes
443                         return hash1 ^ hash2;
444                 }
445
446                 internal override void AddToConstraintCollectionSetup(
447                                 ConstraintCollection collection)
448                 {                       
449                         if (collection.Table != Table)
450                                 throw new InvalidConstraintException("This constraint cannot be added since ForeignKey doesn't belong to table " + RelatedTable.TableName + ".");
451
452                         //run Ctor rules again
453                         _validateColumns(_parentColumns, _childColumns);
454                         
455                         //we must have a unique constraint on the parent
456                         _ensureUniqueConstraintExists(collection, _parentColumns);
457                         
458                         if ( (Table.DataSet != null && Table.DataSet.EnforceConstraints)
459                              || (Table.DataSet == null && Table.EnforceConstraints)) {
460                                 if (IsConstraintViolated())
461                                         throw new ArgumentException("This constraint cannot be enabled as not all values have corresponding parent values.");
462                         }
463                         //FIXME : if this fails and we created a unique constraint
464                         //we should probably roll it back
465                         // and remove index form Table                  
466                 }
467                                         
468                 internal override void RemoveFromConstraintCollectionCleanup( 
469                                 ConstraintCollection collection)
470                 {
471                         _parentUniqueConstraint.ChildConstraint = null;
472                         Index = null;
473                 }
474                 
475                 internal override bool IsConstraintViolated()
476                 {
477                         if (Table.DataSet == null || RelatedTable.DataSet == null) 
478                                 return false;
479                         
480                         bool hasErrors = false;
481                         foreach (DataRow row in Table.Rows) {
482                                 if (row.RowState == DataRowState.Deleted)
483                                         continue;
484                                 
485                                 // we check if all values in _childColumns place are nulls.
486                                 // if yes we return.
487                                 if (row.IsNullColumns(_childColumns))
488                                         continue;
489
490                                 // check whenever there is (at least one) parent row  in RelatedTable
491                                 if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) { 
492                                         // if no parent row exists - constraint is violated
493                                         hasErrors = true;
494                                         string[] values = new string[_childColumns.Length];
495                                         for (int i = 0; i < _childColumns.Length; i++){
496                                                 DataColumn col = _childColumns[i];
497                                                 values[i] = row[col].ToString();
498                                         }
499
500                                         row.RowError = String.Format("ForeignKeyConstraint {0} requires the child key values ({1}) to exist in the parent table.",
501                                                 ConstraintName, String.Join(",", values));
502                                 }
503                         }
504
505                         if (hasErrors)
506                                 //throw new ConstraintException("Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.");
507                                 return true;
508
509                         return false;
510                 }
511                 
512                 internal override void AssertConstraint(DataRow row)
513                 {
514                         // first we check if all values in _childColumns place are nulls.
515                         // if yes we return.
516                         if (row.IsNullColumns(_childColumns))
517                                 return;
518
519                         // check whenever there is (at least one) parent row  in RelatedTable
520                         if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) { 
521                                 // if no parent row exists - constraint is violated
522                                 throw new InvalidConstraintException(GetErrorMessage(row));
523                         }
524                 }
525
526                 internal override bool IsColumnContained(DataColumn column)
527                 {
528                         for (int i = 0; i < _parentColumns.Length; i++)
529                                 if (column == _parentColumns[i])
530                                         return true;
531
532                         for (int i = 0; i < _childColumns.Length; i++)
533                                 if (column == _childColumns[i])
534                                         return true;
535
536                         return false;
537                 }
538
539                 internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){
540                         return true;
541                 }
542
543                 private string GetErrorMessage(DataRow row)
544                 {
545                         System.Text.StringBuilder sb = new System.Text.StringBuilder();
546                         for (int i = 0; i < _childColumns.Length; i++) {
547                                 sb.Append(row[_childColumns[0]].ToString());
548                                 if (i != _childColumns.Length - 1) {
549                                         sb.Append(',');
550                                 }
551                         }
552                         string valStr = sb.ToString();
553                         return "ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + valStr + ") to exist in the parent table.";
554                 }               
555                 
556                 #endregion // Methods
557         }
558
559 }