DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
TransactionConstraintExtensions.cs
Go to the documentation of this file.
1 //
2 // Copyright 2010-2015 Deveel
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 using System;
18 using System.Collections.Generic;
19 using System.IO;
20 using System.Linq;
21 using System.Text;
22 
23 using Deveel.Data;
24 using Deveel.Data.Sql;
26 using Deveel.Data.Sql.Objects;
27 using Deveel.Data.Sql.Tables;
28 using Deveel.Data.Types;
29 
30 namespace Deveel.Data.Transactions {
31  public static class TransactionConstraintExtensions {
32  public static void AddPrimaryKey(this ITransaction transaction, ObjectName tableName, string[] columns,
33  string constraintName) {
34  AddPrimaryKey(transaction, tableName, columns, ConstraintDeferrability.InitiallyImmediate, constraintName);
35  }
36 
37  public static void AddPrimaryKey(this ITransaction transaction, ObjectName tableName, string[] columns,
38  ConstraintDeferrability deferred, string constraintName) {
39  var t = transaction.GetMutableTable(SystemSchema.PrimaryKeyInfoTableName);
40  var tcols = transaction.GetMutableTable(SystemSchema.PrimaryKeyColumnsTableName);
41 
42  try {
43  // Insert a value into PrimaryInfoTable
44  var row = t.NewRow();
45  var uniqueId = transaction.NextTableId(SystemSchema.PrimaryKeyInfoTableName);
46  constraintName = MakeUniqueConstraintName(constraintName, uniqueId);
47  row.SetValue(0, uniqueId);
48  row.SetValue(1, constraintName);
49  row.SetValue(2, tableName.Parent.Name);
50  row.SetValue(3, tableName.Name);
51  row.SetValue(4, (short) deferred);
52  t.AddRow(row);
53 
54  // Insert the columns
55  for (int i = 0; i < columns.Length; ++i) {
56  row = tcols.NewRow();
57  row.SetValue(0, uniqueId); // unique id
58  row.SetValue(1, columns[i]); // column name
59  row.SetValue(2, i); // Sequence number
60  tcols.AddRow(row);
61  }
62 
63  } catch (ConstraintViolationException e) {
64  // Constraint violation when inserting the data. Check the type and
65  // wrap around an appropriate error message.
67  // This means we gave a constraint name that's already being used
68  // for a primary key.
69  throw new Exception(String.Format("Primary key constraint name '{0}' is already being used.", constraintName));
70  }
71 
72  throw;
73  }
74  }
75 
76  public static void AddForeignKey(this ITransaction transaction, ObjectName table, string[] columns,
77  ObjectName refTable, string[] refColumns,
78  ForeignKeyAction deleteRule, ForeignKeyAction updateRule, String constraintName) {
79  AddForeignKey(transaction, table, columns, refTable, refColumns, deleteRule, updateRule,
80  ConstraintDeferrability.InitiallyImmediate, constraintName);
81  }
82 
83  public static void AddForeignKey(this ITransaction transaction, ObjectName table, string[] columns,
84  ObjectName refTable, string[] refColumns, String constraintName) {
85  AddForeignKey(transaction, table, columns, refTable, refColumns, ConstraintDeferrability.InitiallyImmediate,
86  constraintName);
87  }
88 
89  public static void AddForeignKey(this ITransaction transaction, ObjectName table, string[] columns,
90  ObjectName refTable, string[] refColumns, ConstraintDeferrability deferred, String constraintName) {
91  AddForeignKey(transaction, table, columns, refTable, refColumns, ForeignKeyAction.NoAction, ForeignKeyAction.NoAction,
92  deferred, constraintName);
93  }
94 
95  public static void AddForeignKey(this ITransaction transaction, ObjectName table, string[] columns,
96  ObjectName refTable, string[] refColumns,
97  ForeignKeyAction deleteRule, ForeignKeyAction updateRule, ConstraintDeferrability deferred, String constraintName) {
98  var t = transaction.GetMutableTable(SystemSchema.ForeignKeyInfoTableName);
99  var tcols = transaction.GetMutableTable(SystemSchema.ForeignKeyColumnsTableName);
100 
101  try {
102  // If 'ref_columns' empty then set to primary key for referenced table,
103  // ISSUE: What if primary key changes after the fact?
104  if (refColumns.Length == 0) {
105  var set = transaction.QueryTablePrimaryKey(refTable);
106  if (set == null)
107  throw new Exception(String.Format("No primary key defined for referenced table '{0}'", refTable));
108 
109  refColumns = set.ColumnNames;
110  }
111 
112  if (columns.Length != refColumns.Length) {
113  throw new Exception(String.Format("Foreign key reference '{0}' -> '{1}' does not have an equal number of " +
114  "column terms.", table, refTable));
115  }
116 
117  // If delete or update rule is 'SET NULL' then check the foreign key
118  // columns are not constrained as 'NOT NULL'
119  if (deleteRule == ForeignKeyAction.SetNull ||
120  updateRule == ForeignKeyAction.SetNull) {
121  var tableInfo = transaction.GetTableInfo(table);
122  for (int i = 0; i < columns.Length; ++i) {
123  var columnInfo = tableInfo[tableInfo.IndexOfColumn(columns[i])];
124  if (columnInfo.IsNotNull) {
125  throw new Exception(String.Format("Foreign key reference '{0}' -> '{1}' update or delete triggered " +
126  "action is SET NULL for columns that are constrained as " +
127  "NOT NULL.", table, refTable));
128  }
129  }
130  }
131 
132  // Insert a value into ForeignInfoTable
133  var row = t.NewRow();
134  var uniqueId = transaction.NextTableId(SystemSchema.ForeignKeyInfoTableName);
135  constraintName = MakeUniqueConstraintName(constraintName, uniqueId);
136  row.SetValue(0, uniqueId);
137  row.SetValue(1, constraintName);
138  row.SetValue(2, table.Parent.Name);
139  row.SetValue(3, table.Name);
140  row.SetValue(4, refTable.Parent.Name);
141  row.SetValue(5, refTable.Name);
142  row.SetValue(6, ((int) updateRule));
143  row.SetValue(7, ((int) deleteRule));
144  row.SetValue(8, ((short) deferred));
145  t.AddRow(row);
146 
147  // Insert the columns
148  for (int i = 0; i < columns.Length; ++i) {
149  row = tcols.NewRow();
150  row.SetValue(0, uniqueId); // unique id
151  row.SetValue(1, columns[i]); // column name
152  row.SetValue(2, refColumns[i]); // ref column name
153  row.SetValue(3, i); // sequence number
154  tcols.AddRow(row);
155  }
156 
157  } catch (ConstraintViolationException e) {
158  // Constraint violation when inserting the data. Check the type and
159  // wrap around an appropriate error message.
161 
162  // This means we gave a constraint name that's already being used
163  // for a primary key.
164  throw new Exception(String.Format("Foreign key constraint name '{0}' is already being used.", constraintName));
165 
166  throw;
167  }
168  }
169 
170  public static void AddUniqueKey(this ITransaction transaction, ObjectName tableName, string[] columns,
171  string constraintName) {
172  AddUniqueKey(transaction, tableName, columns, ConstraintDeferrability.InitiallyImmediate, constraintName);
173  }
174 
175  public static void AddUniqueKey(this ITransaction transaction, ObjectName tableName, string[] columns,
176  ConstraintDeferrability deferred, string constraintName) {
177  var t = transaction.GetMutableTable(SystemSchema.UniqueKeyInfoTableName);
178  var tcols = transaction.GetMutableTable(SystemSchema.UniqueKeyColumnsTableName);
179 
180  try {
181  // Insert a value into UniqueInfoTable
182  var row = t.NewRow();
183  var uniqueId = transaction.NextTableId(SystemSchema.UniqueKeyInfoTableName);
184  constraintName = MakeUniqueConstraintName(constraintName, uniqueId);
185  row.SetValue(0, uniqueId);
186  row.SetValue(1, constraintName);
187  row.SetValue(2, tableName.Parent.Name);
188  row.SetValue(3, tableName.Name);
189  row.SetValue(4, (short) deferred);
190  t.AddRow(row);
191 
192  // Insert the columns
193  for (int i = 0; i < columns.Length; ++i) {
194  row = tcols.NewRow();
195  row.SetValue(0, uniqueId); // unique id
196  row.SetValue(1, columns[i]); // column name
197  row.SetValue(2, i); // sequence number
198  tcols.AddRow(row);
199  }
200 
201  } catch (ConstraintViolationException e) {
202  // Constraint violation when inserting the data. Check the type and
203  // wrap around an appropriate error message.
205 
206  // This means we gave a constraint name that's already being used
207  // for a primary key.
208  throw new Exception(String.Format("Unique constraint name '{0}' is already being used.", constraintName));
209 
210  throw;
211  }
212  }
213 
214  public static void AddCheck(this ITransaction transaction, ObjectName tableName, SqlExpression expression,
215  ConstraintDeferrability deferrability, string constraintName) {
217  var t = transaction.GetMutableTable(tn);
218  int colCount = t.TableInfo.ColumnCount;
219 
220  try {
221  byte[] binExp;
222  using (var stream = new MemoryStream()) {
223  using (var writer = new BinaryWriter(stream, Encoding.Unicode)) {
224  SqlExpression.Serialize(expression, writer);
225  writer.Flush();
226 
227  binExp = stream.ToArray();
228  }
229  }
230 
231  // Insert check constraint data.
232  var uniqueId = transaction.NextTableId(tn);
233  constraintName = MakeUniqueConstraintName(constraintName, uniqueId);
234  var rd = t.NewRow();
235  rd.SetValue(0, uniqueId);
236  rd.SetValue(1, constraintName);
237  rd.SetValue(2, tableName.ParentName);
238  rd.SetValue(3, tableName.Name);
239  rd.SetValue(4, expression.ToString());
240  rd.SetValue(5, (short) deferrability);
241  if (colCount > 6) {
242  rd.SetValue(6, DataObject.Binary(new SqlBinary(binExp)));
243  }
244 
245  t.AddRow(rd);
246 
247  } catch (ConstraintViolationException e) {
248  // Constraint violation when inserting the data. Check the type and
249  // wrap around an appropriate error message.
251  // This means we gave a constraint name that's already being used.
252  throw new InvalidOperationException("Check constraint name '" + constraintName + "' is already being used.");
253  }
254  throw;
255  }
256  }
257 
258  private static string MakeUniqueConstraintName(string constraintName, SqlNumber uniqueId) {
259  return String.IsNullOrEmpty(constraintName) ? ("_ANONYMOUS_CONSTRAINT_" + uniqueId) : constraintName;
260  }
261 
262  private static String[] ToColumns(ITable table, IEnumerable<int> cols) {
263  var colList = cols.ToList();
264  int size = colList.Count;
265  var list = new String[size];
266 
267  // for each n of the output list
268  for (int n = 0; n < size; ++n) {
269  // for each i of the input list
270  for (int i = 0; i < size; ++i) {
271  int rowIndex = colList[i];
272  int seqNo = ((SqlNumber) table.GetValue(rowIndex, 2).Value).ToInt32();
273  if (seqNo == n) {
274  list[n] = table.GetValue(rowIndex, 1).Value.ToString();
275  break;
276  }
277  }
278  }
279 
280  return list;
281  }
282 
283  private static bool IsUniqueColumns(ITable table, int rindex, string[] cols, bool nullsAllowed) {
284  var tableInfo = table.TableInfo;
285 
286  // 'identical_rows' keeps a tally of the rows that match our added cell.
287  IList<int> identicalRows = null;
288 
289  // Resolve the list of column names to column indexes
290  var colIndexes = tableInfo.IndexOfColumns(cols).ToList();
291 
292  // If the value being tested for uniqueness contains NULL, we return true
293  // if nulls are allowed.
294  if (colIndexes.Select(x => table.GetValue(rindex, x)).Any(x => x.IsNull))
295  return nullsAllowed;
296 
297  foreach (var colIndex in colIndexes) {
298  var value = table.GetValue(rindex, colIndex);
299 
300  // We are assured of uniqueness if 'identicalRows != null &&
301  // identicalRows.Count == 0' This is because 'identicalRows' keeps
302  // a running tally of the rows in the table that contain unique columns
303  // whose cells match the record being added.
304 
305  if (identicalRows == null || identicalRows.Count > 0) {
306  // Ask SelectableScheme to return pointers to row(s) if there is
307  // already a cell identical to this in the table.
308 
309  var index = table.GetIndex(colIndex);
310  var list = index.SelectEqual(value).ToList();
311 
312  // If 'identical_rows' hasn't been set up yet then set it to 'ivec'
313  // (the list of rows where there is a cell which is equal to the one
314  // being added)
315  // If 'identical_rows' has been set up, then perform an
316  // 'intersection' operation on the two lists (only keep the numbers
317  // that are repeated in both lists). Therefore we keep the rows
318  // that match the row being added.
319 
320  if (identicalRows == null) {
321  identicalRows = list;
322  } else {
323  list.Sort();
324  int rowIndex = identicalRows.Count - 1;
325  while (rowIndex >= 0) {
326  int val = identicalRows[rowIndex];
327  int foundIndex = list.BinarySearch(val);
328 
329  // If we _didn't_ find the index in the array
330  if (foundIndex < 0 ||
331  list[foundIndex] != val) {
332  identicalRows.RemoveAt(rowIndex);
333  }
334  --rowIndex;
335  }
336  }
337  }
338  }
339 
340  // If there is 1 (the row we added) then we are unique, otherwise we are
341  // not.
342  if (identicalRows != null) {
343  int sz = identicalRows.Count;
344  if (sz == 1)
345  return true;
346  if (sz > 1)
347  return false;
348  if (sz == 0)
349  throw new InvalidOperationException("Assertion failed: We must be able to find the " +
350  "row we are testing uniqueness against!");
351  }
352 
353  return true;
354  }
355 
356  private static int RowCountOfReferenceTable(this ITransaction transaction, int rowIndex, ObjectName table1,
357  string[] cols1, ObjectName table2, String[] cols2,
358  bool checkSourceTableKey) {
359 
360  // Get the tables
361  var t1 = transaction.GetTable(table1);
362  var t2 = transaction.GetTable(table2);
363 
364  // The table defs
365  var dti1 = t1.TableInfo;
366  var dti2 = t2.TableInfo;
367 
368  // Resolve the list of column names to column indexes
369  var col1Indexes = dti1.IndexOfColumns(cols1).ToArray();
370  var col2Indexes = dti2.IndexOfColumns(cols2).ToArray();
371 
372  int keySize = col1Indexes.Length;
373 
374  // Get the data from table1
375  var keyValue = new DataObject[keySize];
376  int nullCount = 0;
377  for (int n = 0; n < keySize; ++n) {
378  keyValue[n] = t1.GetValue(rowIndex, col1Indexes[n]);
379  if (keyValue[n].IsNull) {
380  ++nullCount;
381  }
382  }
383 
384  // If we are searching for null then return -1;
385  if (nullCount > 0)
386  return -1;
387 
388  // HACK: This is a hack. The purpose is if the key exists in the source
389  // table we return 0 indicating to the delete check that there are no
390  // references and it's valid. To the semantics of the method this is
391  // incorrect.
392  if (checkSourceTableKey) {
393  var keys = t1.FindKeys(col1Indexes, keyValue);
394  if (keys.Any())
395  return 0;
396  }
397 
398  return t2.FindKeys(col2Indexes, keyValue).Count();
399  }
400 
401  public static void CheckFieldConstraintViolations(this ITransaction transaction, ITable table, int[] rowIndices) {
402  if (rowIndices == null || rowIndices.Length == 0)
403  return;
404 
405  // Check for any bad cells - which are either cells that are 'null' in a
406  // column declared as 'not null', or duplicated in a column declared as
407  // unique.
408 
409  var tableInfo = table.TableInfo;
410 
411  // Check not-null columns are not null. If they are null, throw an
412  // error. Additionally check that OBJECT columns are correctly
413  // typed.
414 
415  // Check each field of the added rows
416  int len = tableInfo.ColumnCount;
417  for (int i = 0; i < len; ++i) {
418  // Get the column definition and the cell being inserted,
419  var columnInfo = tableInfo[i];
420 
421  // For each row added to this column
422  for (int rn = 0; rn < rowIndices.Length; ++rn) {
423  var value = table.GetValue(rowIndices[rn], i);
424 
425  // Check: Column defined as not null and cell being inserted is
426  // not null.
427  if (columnInfo.IsNotNull && value.IsNull) {
430  "Attempt to set NULL value to column '" +
431  tableInfo[i].ColumnName +
432  "' which is declared as NOT NULL");
433  }
434 
435  // Check: If column is an object, then deserialize and check the
436  // object is an instance of the class constraint,
437  if (!value.IsNull &&
438  columnInfo.ColumnType.TypeCode == SqlTypeCode.Object) {
439  throw new NotImplementedException(); // TODO:
440  }
441  }
442  }
443  }
444 
445  public static void CheckAddConstraintViolations(this ITransaction transaction, ITable table,
446  ConstraintDeferrability deferred) {
447  // Get all the rows in the table
448  var rows = table.Select(x => x.RowId.RowNumber).ToArray();
449 
450  // Check the constraints of all the rows in the table.
451  CheckAddConstraintViolations(transaction, table, rows, deferred);
452  }
453 
454  public static void CheckAddConstraintViolations(this ITransaction transaction, ITable table, int[] rowIndices,
455  ConstraintDeferrability deferred) {
456  string curSchema = table.TableInfo.TableName.Parent.Name;
457  using (var session = new SystemSession(transaction, curSchema)) {
458  using (var queryContext = session.CreateQuery()) {
459 
460  // Quick exit case
461  if (rowIndices == null || rowIndices.Length == 0)
462  return;
463 
464  var tableInfo = table.TableInfo;
465  var tableName = tableInfo.TableName;
466 
467  // ---- Constraint checking ----
468 
469  // Check any primary key constraint.
470  var primaryKey = transaction.QueryTablePrimaryKey(tableName);
471  if (primaryKey != null &&
472  (deferred == ConstraintDeferrability.InitiallyDeferred ||
473  primaryKey.Deferred == ConstraintDeferrability.InitiallyImmediate)) {
474 
475  // For each row added to this column
476  foreach (int rowIndex in rowIndices) {
477  if (!IsUniqueColumns(table, rowIndex, primaryKey.ColumnNames, false)) {
480  deferred.AsDebugString() + " primary Key constraint violation (" +
481  primaryKey.ConstraintName + ") Columns = ( " +
482  String.Join(", ", primaryKey.ColumnNames) +
483  " ) Table = ( " + tableName + " )");
484  }
485  } // For each row being added
486  }
487 
488  // Check any unique constraints.
489  var uniqueConstraints = transaction.QueryTableUniqueKeys(tableName);
490  foreach (var unique in uniqueConstraints) {
491  if (deferred == ConstraintDeferrability.InitiallyDeferred ||
492  unique.Deferred == ConstraintDeferrability.InitiallyImmediate) {
493 
494  // For each row added to this column
495  foreach (int rowIndex in rowIndices) {
496  if (!IsUniqueColumns(table, rowIndex, unique.ColumnNames, true)) {
499  deferred.AsDebugString() + " unique constraint violation (" +
500  unique.ConstraintName + ") Columns = ( " +
501  String.Join(", ", unique.ColumnNames) + " ) Table = ( " +
502  tableName + " )");
503  }
504  } // For each row being added
505  }
506  }
507 
508  // Check any foreign key constraints.
509  // This ensures all foreign references in the table are referenced
510  // to valid records.
511  var foreignConstraints = transaction.QueryTableForeignKeys(tableName);
512 
513  foreach (var reference in foreignConstraints) {
514  if (deferred == ConstraintDeferrability.InitiallyDeferred ||
515  reference.Deferred == ConstraintDeferrability.InitiallyImmediate) {
516  // For each row added to this column
517  foreach (int rowIndex in rowIndices) {
518  // Make sure the referenced record exists
519 
520  // Return the count of records where the given row of
521  // table_name(columns, ...) IN
522  // ref_table_name(ref_columns, ...)
523  int rowCount = RowCountOfReferenceTable(transaction,
524  rowIndex,
525  reference.TableName, reference.ColumnNames,
526  reference.ForeignTable, reference.ForeignColumnNames,
527  false);
528  if (rowCount == -1) {
529  // foreign key is NULL
530  }
531 
532  if (rowCount == 0) {
535  deferred.AsDebugString() + " foreign key constraint violation (" +
536  reference.ConstraintName + ") Columns = " +
537  reference.TableName + "( " +
538  String.Join(", ", reference.ColumnNames) + " ) -> " +
539  reference.ForeignTable + "( " +
540  String.Join(", ", reference.ForeignColumnNames) + " )");
541  }
542  } // For each row being added.
543  }
544  }
545 
546  // Any general checks of the inserted data
547  var checkConstraints = transaction.QueryTableCheckExpressions(tableName);
548 
549  // For each check constraint, check that it evaluates to true.
550  for (int i = 0; i < checkConstraints.Length; ++i) {
551  var check = checkConstraints[i];
552  if (deferred == ConstraintDeferrability.InitiallyDeferred ||
553  check.Deferred == ConstraintDeferrability.InitiallyImmediate) {
554 
555  // TODO: var exp = tableInfo.ResolveColumns(transaction.IgnoreIdentifierCase(), check.CheckExpression);
556  var exp = tableInfo.ResolveColumns(true, check.CheckExpression);
557 
558  // For each row being added to this column
559  for (int rn = 0; rn < rowIndices.Length; ++rn) {
560  var resolver = new TableRowVariableResolver(table, rowIndices[rn]);
561  var evalExp = exp.Evaluate(queryContext, resolver, null);
562  var ob = ((SqlConstantExpression) evalExp).Value;
563 
564  var b = ob.AsBoolean();
565 
566  if (!b.IsNull) {
567  if (b) {
568  // Evaluated to false so don't allow this row to be added.
571  deferred.AsDebugString() + " check constraint violation (" +
572  check.ConstraintName + ") - '" + exp +
573  "' evaluated to false for inserted/updated row.");
574  }
575  } else {
576  // NOTE: This error will pass the row by default
577  // TODO: emit a warning
578  }
579  }
580  }
581  }
582  }
583  }
584  }
585 
586  public static
587  void CheckRemoveConstraintViolations(this ITransaction transaction, ITable table, int[] rowIndices,
588  ConstraintDeferrability deferred) {
589  // Quick exit case
590  if (rowIndices == null || rowIndices.Length == 0)
591  return;
592 
593  var tableInfo = table.TableInfo;
594  var tableName = tableInfo.TableName;
595 
596  // Check any imported foreign key constraints.
597  // This ensures that a referential reference can not be removed making
598  // it invalid.
599  var foreignConstraints = transaction.QueryTableImportedForeignKeys(tableName);
600  foreach (var reference in foreignConstraints) {
601  if (deferred == ConstraintDeferrability.InitiallyDeferred ||
602  reference.Deferred == ConstraintDeferrability.InitiallyImmediate) {
603  // For each row removed from this column
604  foreach (int rowIndex in rowIndices) {
605  // Make sure the referenced record exists
606 
607  // Return the count of records where the given row of
608  // ref_table_name(columns, ...) IN
609  // table_name(ref_columns, ...)
610  int rowCount = RowCountOfReferenceTable(transaction,
611  rowIndex,
612  reference.ForeignTable, reference.ForeignColumnNames,
613  reference.TableName, reference.ColumnNames,
614  true);
615 
616  // There must be 0 references otherwise the delete isn't allowed to
617  // happen.
618  if (rowCount > 0) {
620  deferred.AsDebugString() + " foreign key constraint violation " +
621  "on delete (" +
622  reference.ConstraintName + ") Columns = " +
623  reference.TableName + "( " +
624  String.Join(", ", reference.ColumnNames) + " ) -> " +
625  reference.ForeignTable + "( " +
626  String.Join(", ", reference.ForeignColumnNames) + " )");
627  }
628  }
629  }
630  }
631  }
632 
633  public static ConstraintInfo[] QueryTableForeignKeys(this ITransaction transaction, ObjectName tableName) {
634  var t = transaction.GetTable(SystemSchema.ForeignKeyInfoTableName);
635  var t2 = transaction.GetTable(SystemSchema.ForeignKeyColumnsTableName);
636 
637  // Returns the list indexes where column 3 = table name
638  // and column 2 = schema name
639  var objTableName = DataObject.String(tableName.Name);
640  var objSchema = DataObject.String(tableName.Parent.Name);
641  var data = t.SelectRowsEqual(3, objTableName, 2, objSchema).ToList();
642 
643  var groups = new ConstraintInfo[data.Count];
644 
645  for (int i = 0; i < data.Count; ++i) {
646  int rowIndex = data[i];
647 
648  // The foreign key id
649  var id = t.GetValue(rowIndex, 0);
650 
651  // The referenced table
652  var refTableName = new ObjectName(
653  new ObjectName(t.GetValue(rowIndex, 4).Value.ToString()),
654  t.GetValue(rowIndex, 5).Value.ToString());
655 
656  // Select all records with equal id
657  var cols = t2.SelectRowsEqual(0, id).ToList();
658 
659  var name = t.GetValue(rowIndex, 1).Value.ToString();
660  var updateRule = (ForeignKeyAction) ((SqlNumber) t.GetValue(rowIndex, 6).Value).ToInt32();
661  var deleteRule = (ForeignKeyAction) ((SqlNumber) t.GetValue(rowIndex, 7).Value).ToInt32();
662  var deferred = (ConstraintDeferrability) ((SqlNumber) t.GetValue(rowIndex, 8).Value).ToInt16();
663  ;
664 
665  int colsSize = cols.Count;
666  string[] keyCols = new string[colsSize];
667  string[] refCols = new string[colsSize];
668  for (int n = 0; n < colsSize; ++n) {
669  for (int p = 0; p < colsSize; ++p) {
670  int colsIndex = cols[p];
671  if (t2.GetValue(colsIndex, 3) == n) {
672  keyCols[n] = t2.GetValue(colsIndex, 1).Value.ToString();
673  refCols[n] = t2.GetValue(colsIndex, 2).Value.ToString();
674  break;
675  }
676  }
677  }
678 
679  var constraint = ConstraintInfo.ForeignKey(name, tableName, keyCols, refTableName, refCols);
680  constraint.OnDelete = deleteRule;
681  constraint.OnUpdate = updateRule;
682  constraint.Deferred = deferred;
683 
684  groups[i] = constraint;
685  }
686 
687  return groups;
688  }
689 
690  public static ConstraintInfo[] QueryTableImportedForeignKeys(this ITransaction transaction, ObjectName refTableName) {
691  var t = transaction.GetTable(SystemSchema.ForeignKeyInfoTableName);
692  var t2 = transaction.GetTable(SystemSchema.ForeignKeyColumnsTableName);
693 
694  // Returns the list indexes where column 5 = ref table name
695  // and column 4 = ref schema name
696  var objRefTableName = DataObject.String(refTableName.Name);
697  var objRefSchema = DataObject.String(refTableName.Parent.Name);
698  var data = t.SelectRowsEqual(5, objRefTableName, 4, objRefSchema).ToArray();
699 
700  var groups = new ConstraintInfo[data.Length];
701 
702  for (int i = 0; i < data.Length; ++i) {
703  int rowIndex = data[i];
704 
705  // The foreign key id
706  var id = t.GetValue(rowIndex, 0);
707 
708  // The referencee table
709  var schemaNamePart = t.GetValue(rowIndex, 2).AsVarChar().Value.ToString();
710  var tableNamePart = t.GetValue(rowIndex, 3).AsVarChar().Value.ToString();
711  var tableName = new ObjectName(new ObjectName(schemaNamePart), tableNamePart);
712 
713  // Select all records with equal id
714  var cols = t2.SelectRowsEqual(0, id).ToArray();
715 
716  var name = t.GetValue(rowIndex, 1).AsVarChar().Value.ToString();
717 
718  var updateRule = (ForeignKeyAction) ((SqlNumber) t.GetValue(rowIndex, 6).AsBigInt().Value).ToInt32();
719  var deleteRule = (ForeignKeyAction) ((SqlNumber) t.GetValue(rowIndex, 7).AsBigInt().Value).ToInt32();
720  var deferred = (ConstraintDeferrability) ((SqlNumber) t.GetValue(rowIndex, 8).AsBigInt().Value).ToInt16();
721 
722  int colsSize = cols.Length;
723  string[] keyCols = new string[colsSize];
724  string[] refCols = new string[colsSize];
725  for (int n = 0; n < colsSize; ++n) {
726  for (int p = 0; p < colsSize; ++p) {
727  int colsIndex = cols[p];
728  if (t2.GetValue(colsIndex, 3) == n) {
729  keyCols[n] = t2.GetValue(colsIndex, 1);
730  refCols[n] = t2.GetValue(colsIndex, 2);
731  break;
732  }
733  }
734  }
735 
736  var constraint = ConstraintInfo.ForeignKey(name, tableName, keyCols, refTableName, refCols);
737  constraint.OnDelete = deleteRule;
738  constraint.OnUpdate = updateRule;
739  constraint.Deferred = deferred;
740 
741  groups[i] = constraint;
742  }
743 
744  return groups;
745  }
746 
747  public static ConstraintInfo[] QueryTableUniqueKeys(this ITransaction transaction, ObjectName tableName) {
748  var t = transaction.GetTable(SystemSchema.UniqueKeyInfoTableName);
749  var t2 = transaction.GetTable(SystemSchema.UniqueKeyColumnsTableName);
750 
751  // Returns the list indexes where column 3 = table name
752  // and column 2 = schema name
753  var objTableName = DataObject.String(tableName.Name);
754  var objSchemaName = DataObject.String(tableName.Parent.Name);
755  var data = t.SelectRowsEqual(3, objTableName, 2, objSchemaName).ToList();
756 
757  var constraints = new ConstraintInfo[data.Count];
758 
759  for (int i = 0; i < data.Count; ++i) {
760  var id = t.GetValue(data[i], 0);
761 
762  // Select all records with equal id
763  var cols = t2.SelectRowsEqual(0, id);
764 
765  var name = t.GetValue(data[i], 1).Value.ToString();
766  var columns = ToColumns(t2, cols); // the list of columns
767  var deferred = (ConstraintDeferrability) ((SqlNumber) t.GetValue(data[i], 4).Value).ToInt16();
768 
769  var constraint = ConstraintInfo.Unique(name, tableName, columns);
770  constraint.Deferred = deferred;
771  constraints[i] = constraint;
772  }
773 
774  return constraints;
775  }
776 
777  public static ConstraintInfo QueryTablePrimaryKey(this ITransaction transaction, ObjectName tableName) {
778  var t = transaction.GetTable(SystemSchema.PrimaryKeyInfoTableName);
779  var t2 = transaction.GetTable(SystemSchema.PrimaryKeyColumnsTableName);
780 
781  // Returns the list indexes where column 3 = table name
782  // and column 2 = schema name
783  var objTableName = DataObject.String(tableName.Name);
784  var objSchemaName = DataObject.String(tableName.Parent.Name);
785  var data = t.SelectRowsEqual(3, objTableName, 2, objSchemaName).ToList();
786 
787  if (data.Count > 1)
788  throw new InvalidOperationException("Assertion failed: multiple primary key for: " + tableName);
789 
790  if (data.Count == 0)
791  return null;
792 
793  int rowIndex = data[0];
794 
795  var id = t.GetValue(rowIndex, 0);
796 
797  // All columns with this id
798  var list = t2.SelectRowsEqual(0, id);
799 
800  // Make it in to a columns object
801  var name = t.GetValue(rowIndex, 1).AsVarChar().Value.ToString();
802  string[] columns = ToColumns(t2, list);
803  var deferred = (ConstraintDeferrability) ((SqlNumber) t.GetValue(rowIndex, 4).Value).ToInt16();
804 
805  var constraint = ConstraintInfo.PrimaryKey(name, tableName, columns);
806  constraint.Deferred = deferred;
807  return constraint;
808  }
809 
810  public static ObjectName[] QueryTablesRelationallyLinkedTo(this ITransaction transaction, ObjectName tableName) {
811  var list = new List<ObjectName>();
812  var refs = QueryTableForeignKeys(transaction, tableName);
813  foreach (var fkeyRef in refs) {
814  var tname = fkeyRef.ForeignTable;
815  if (!list.Contains(tname))
816  list.Add(tname);
817  }
818 
819  refs = QueryTableImportedForeignKeys(transaction, tableName);
820  foreach (var fkeyRef in refs) {
821  var tname = fkeyRef.TableName;
822  if (!list.Contains(tname))
823  list.Add(tname);
824  }
825 
826  return list.ToArray();
827  }
828 
829  public static ConstraintInfo[] QueryTableCheckExpressions(this ITransaction transaction, ObjectName tableName) {
830  var t = transaction.GetTable(SystemSchema.CheckInfoTableName);
831 
832  // Returns the list indexes where column 3 = table name
833  // and column 2 = schema name
834  var objTableName = DataObject.String(tableName.Name);
835  var objSchemaName = DataObject.String(tableName.Parent.Name);
836  var data = t.SelectRowsEqual(3, objTableName, 2, objSchemaName).ToList();
837  var checks = new ConstraintInfo[data.Count];
838 
839  for (int i = 0; i < checks.Length; ++i) {
840  int rowIndex = data[i];
841 
842  string name = t.GetValue(rowIndex, 1).Value.ToString();
843  var deferred = (ConstraintDeferrability) ((SqlNumber) t.GetValue(rowIndex, 5).Value).ToInt16();
844  SqlExpression expression = null;
845 
846  // Is the deserialized version available?
847  if (t.TableInfo.ColumnCount > 6) {
848  var sexp = (SqlBinary) t.GetValue(rowIndex, 6).Value;
849  if (!sexp.IsNull) {
850  try {
851  // Deserialize the expression
852  // TODO: expression = (SqlExpression)ObjectTranslator.Deserialize(sexp);
853  throw new NotImplementedException();
854  } catch (Exception) {
855  // We weren't able to deserialize the expression so report the
856  // error to the log
857  // TODO:
858  }
859  }
860  }
861 
862  // Otherwise we need to parse it from the string
863  if (expression == null) {
864  expression = SqlExpression.Parse(t.GetValue(rowIndex, 4).Value.ToString());
865  }
866 
867  var check = ConstraintInfo.Check(name, tableName, expression);
868  check.Deferred = deferred;
869  checks[i] = check;
870  }
871 
872  return checks;
873  }
874 
875  public static void DropAllTableConstraints(this ITransaction transaction, ObjectName tableName) {
876  var primary = transaction.QueryTablePrimaryKey(tableName);
877  var uniques = transaction.QueryTableUniqueKeys(tableName);
878  var expressions = transaction.QueryTableCheckExpressions(tableName);
879  var refs = transaction.QueryTableForeignKeys(tableName);
880 
881  if (primary != null)
882  transaction.DropTablePrimaryKey(tableName, primary.ConstraintName);
883  foreach (var unique in uniques) {
884  transaction.DropTableUniqueKey(tableName, unique.ConstraintName);
885  }
886  foreach (var expression in expressions) {
887  transaction.DropTableCheck(tableName, expression.ConstraintName);
888  }
889  foreach (var reference in refs) {
890  transaction.DropTableForeignKey(tableName, reference.ConstraintName);
891  }
892  }
893 
894  public static int DropTableConstraint(this ITransaction transaction, ObjectName tableName, string constraintName) {
895  int dropCount = 0;
896  if (transaction.DropTablePrimaryKey(tableName, constraintName)) {
897  ++dropCount;
898  }
899  if (transaction.DropTableUniqueKey(tableName, constraintName)) {
900  ++dropCount;
901  }
902  if (transaction.DropTableCheck(tableName, constraintName)) {
903  ++dropCount;
904  }
905  if (transaction.DropTableForeignKey(tableName, constraintName)) {
906  ++dropCount;
907  }
908  return dropCount;
909  }
910 
911  public static bool DropTablePrimaryKey(this ITransaction transaction, ObjectName tableName, string constraintName) {
912  var t = transaction.GetMutableTable(SystemSchema.PrimaryKeyInfoTableName);
913  var t2 = transaction.GetMutableTable(SystemSchema.PrimaryKeyColumnsTableName);
914  IEnumerable<int> data;
915  if (constraintName != null) {
916  // Returns the list of indexes where column 1 = constraint name
917  // and column 2 = schema name
918  data = t.SelectRowsEqual(1, DataObject.String(constraintName), 2, DataObject.String(tableName.ParentName));
919  } else {
920  // Returns the list of indexes where column 3 = table name
921  // and column 2 = schema name
922  data = t.SelectRowsEqual(3, DataObject.String(tableName.Name), 2, DataObject.String(tableName.ParentName));
923  }
924 
925  var resultList = data.ToList();
926 
927  if (resultList.Count > 1)
928  throw new InvalidOperationException("Assertion failed: multiple primary key for: " + tableName);
929 
930  if (resultList.Count == 1) {
931  int rowIndex = resultList[0];
932 
933  // The id
934  var id = t.GetValue(rowIndex, 0);
935 
936  // All columns with this id
937  var columns = t2.SelectRowsEqual(0, id);
938 
939  // Delete from the table
940  t2.DeleteRows(columns);
941  t.DeleteRows(resultList);
942  return true;
943  }
944 
945  return false;
946  }
947 
948  public static bool DropTableUniqueKey(this ITransaction transaction, ObjectName table, string constraintName) {
949  var t = transaction.GetMutableTable(SystemSchema.UniqueKeyInfoTableName);
950  var t2 = transaction.GetMutableTable(SystemSchema.UniqueKeyColumnsTableName);
951 
952  // Returns the list of indexes where column 1 = constraint name
953  // and column 2 = schema name
954  var data = t.SelectRowsEqual(1, DataObject.String(constraintName), 2, DataObject.String(table.ParentName));
955 
956  var resultList = data.ToList();
957  if (resultList.Count > 1)
958  throw new InvalidOperationException("Assertion failed: multiple unique constraint name: " + constraintName);
959 
960  if (resultList.Count == 1) {
961  var rowIndex = resultList[0];
962 
963  // The id
964  var id = t.GetValue(rowIndex, 0);
965 
966  // All columns with this id
967  var columns = t2.SelectRowsEqual(0, id);
968 
969  // Delete from the table
970  t2.DeleteRows(columns);
971  t.DeleteRows(resultList);
972  return true;
973  }
974 
975  return false;
976  }
977 
978  public static bool DropTableCheck(this ITransaction transaction, ObjectName table, string constraintName) {
979  var t = transaction.GetMutableTable(SystemSchema.CheckInfoTableName);
980 
981  // Returns the list of indexes where column 1 = constraint name
982  // and column 2 = schema name
983  var data = t.SelectRowsEqual(1, DataObject.String(constraintName), 2, DataObject.String(table.ParentName));
984  var resultList = data.ToList();
985  if (resultList.Count > 1)
986  throw new InvalidOperationException("Assertion failed: multiple check constraint name: " + constraintName);
987 
988  if (resultList.Count == 1) {
989  // Delete the check constraint
990  t.DeleteRows(resultList);
991  return true;
992  }
993 
994  // data.size() == 0 so the constraint wasn't found
995  return false;
996  }
997 
998  public static bool DropTableForeignKey(this ITransaction transaction, ObjectName table, string constraintName) {
999  var t = transaction.GetMutableTable(SystemSchema.ForeignKeyInfoTableName);
1000  var t2 = transaction.GetMutableTable(SystemSchema.ForeignKeyColumnsTableName);
1001 
1002  // Returns the list of indexes where column 1 = constraint name
1003  // and column 2 = schema name
1004  var data = t.SelectRowsEqual(1, DataObject.String(constraintName), 2, DataObject.String(table.ParentName));
1005  var resultList = data.ToList();
1006 
1007  if (resultList.Count > 1)
1008  throw new InvalidOperationException("Assertion failed: multiple foreign key constraint " + "name: " + constraintName);
1009 
1010  if (resultList.Count == 1) {
1011  int rowIndex = resultList[0];
1012 
1013  // The id
1014  var id = t.GetValue(rowIndex, 0);
1015 
1016  // All columns with this id
1017  var columns = t2.SelectRowsEqual(0, id);
1018 
1019  // Delete from the table
1020  t2.DeleteRows(columns);
1021  t.DeleteRows(resultList);
1022  return true;
1023  }
1024 
1025  // data.size() == 0 so the constraint wasn't found
1026  return false;
1027  }
1028 
1030 
1031  private readonly ITable table;
1032  private readonly int rowIndex;
1033 
1034  public TableRowVariableResolver(ITable table, int rowIndex) {
1035  this.table = table;
1036  this.rowIndex = rowIndex;
1037  }
1038 
1039  private int FindColumnName(ObjectName variable) {
1040  int colIndex = table.TableInfo.IndexOfColumn(variable.Name);
1041  if (colIndex == -1)
1042  throw new InvalidOperationException("Can't find column: " + variable);
1043 
1044  return colIndex;
1045  }
1046 
1047  // --- Implemented ---
1048 
1049  public int SetId {
1050  get { return rowIndex; }
1051  }
1052 
1053  public DataObject Resolve(ObjectName variable) {
1054  int colIndex = FindColumnName(variable);
1055  return table.GetValue(rowIndex, colIndex);
1056  }
1057 
1058  public SqlType ReturnType(ObjectName variable) {
1059  int colIndex = FindColumnName(variable);
1060  return table.TableInfo[colIndex].ColumnType;
1061  }
1062  }
1063  }
1064 }
static ConstraintInfo PrimaryKey(ObjectName tableName, params string[] columnNames)
static bool DropTablePrimaryKey(this ITransaction transaction, ObjectName tableName, string constraintName)
Defines the contract to access the data contained into a table of a database.
Definition: ITable.cs:40
static DataObject Binary(SqlBinary binary)
Definition: DataObject.cs:638
Enumerates a known set of codes in a SQL Model
static ConstraintInfo[] QueryTableForeignKeys(this ITransaction transaction, ObjectName tableName)
A database exception that represents a constraint violation.
static ConstraintInfo[] QueryTableCheckExpressions(this ITransaction transaction, ObjectName tableName)
override string ToString()
Definition: ObjectName.cs:225
static SqlExpression Parse(string s)
Parses the given SQL string to an expression that can be evaluated.
A long string in the system.
int ErrorCode
Gets a numeric value representing the code of the error catched by this exception.
const int UniqueViolation
A Unique constraint violation error code.
static void CheckFieldConstraintViolations(this ITransaction transaction, ITable table, int[] rowIndices)
SqlType ReturnType(ObjectName variable)
Returns the SqlType of object the given variable is.
static void CheckAddConstraintViolations(this ITransaction transaction, ITable table, ConstraintDeferrability deferred)
Implements a BINARY object that handles a limited number of bytes, not exceding MaxLength.
Definition: SqlBinary.cs:27
static void CheckRemoveConstraintViolations(this ITransaction transaction, ITable table, int[] rowIndices, ConstraintDeferrability deferred)
static String[] ToColumns(ITable table, IEnumerable< int > cols)
static ConstraintInfo Check(ObjectName tableName, SqlExpression expression, params string[] columnNames)
ConstraintDeferrability Deferred
static readonly ObjectName CheckInfoTableName
Describes the name of an object within a database.
Definition: ObjectName.cs:44
static readonly ObjectName PrimaryKeyInfoTableName
static void AddForeignKey(this ITransaction transaction, ObjectName table, string[] columns, ObjectName refTable, string[] refColumns, ForeignKeyAction deleteRule, ForeignKeyAction updateRule, String constraintName)
static readonly ObjectName UniqueKeyInfoTableName
static int DropTableConstraint(this ITransaction transaction, ObjectName tableName, string constraintName)
ISqlObject Value
Gets the underlined value that is handled.
Definition: DataObject.cs:84
ConstraintDeferrability
The type of deferrance of a constraint.
static DataObject String(string s)
Definition: DataObject.cs:592
static readonly ObjectName UniqueKeyColumnsTableName
DataObject GetValue(long rowNumber, int columnOffset)
Gets a single cell within the table that is located at the given column offset and row...
static void Serialize(SqlExpression expression, BinaryWriter writer)
DataObject Resolve(ObjectName variable)
Returns the value of a given variable.
static bool DropTableUniqueKey(this ITransaction transaction, ObjectName table, string constraintName)
static bool DropTableCheck(this ITransaction transaction, ObjectName table, string constraintName)
ObjectName TableName
Gets the fully qualified name of the table that is ensured to be unique within the system...
Definition: TableInfo.cs:97
static ConstraintInfo[] QueryTableUniqueKeys(this ITransaction transaction, ObjectName tableName)
static readonly ObjectName ForeignKeyInfoTableName
ForeignKeyAction
Enumerates the foreign key referential trigger actions.
static bool DropTableForeignKey(this ITransaction transaction, ObjectName table, string constraintName)
static readonly ObjectName ForeignKeyColumnsTableName
static string MakeUniqueConstraintName(string constraintName, SqlNumber uniqueId)
const int ForeignKeyViolation
A Foreign Key constraint violation error code.
static ConstraintInfo QueryTablePrimaryKey(this ITransaction transaction, ObjectName tableName)
Represents a dynamic object that encapsulates a defined SqlType and a compatible constant ISqlObject ...
Definition: DataObject.cs:35
static void AddForeignKey(this ITransaction transaction, ObjectName table, string[] columns, ObjectName refTable, string[] refColumns, ConstraintDeferrability deferred, String constraintName)
static void DropAllTableConstraints(this ITransaction transaction, ObjectName tableName)
static void AddCheck(this ITransaction transaction, ObjectName tableName, SqlExpression expression, ConstraintDeferrability deferrability, string constraintName)
Provides utilities and properties for handling the SYSTEN schema of a database.
Definition: SystemSchema.cs:37
Defines the properties of a specific SQL Type and handles the values compatible.
Definition: SqlType.cs:33
static void AddPrimaryKey(this ITransaction transaction, ObjectName tableName, string[] columns, string constraintName)
TableInfo TableInfo
Gets the metadata information of the table, used to resolve the column sources.
Definition: ITable.cs:47
static void AddUniqueKey(this ITransaction transaction, ObjectName tableName, string[] columns, string constraintName)
static ConstraintInfo[] QueryTableImportedForeignKeys(this ITransaction transaction, ObjectName refTableName)
const int PrimaryKeyViolation
A Primary Key constraint violation error code.
const int NullableViolation
A Nullable constraint violation error code (data added to not null columns that was null)...
SqlTypeCode
Enumerates the codes of all SQL types handled by the system.
Definition: SqlTypeCode.cs:23
An interface to resolve a variable name to a constant object.
static void AddForeignKey(this ITransaction transaction, ObjectName table, string[] columns, ObjectName refTable, string[] refColumns, ForeignKeyAction deleteRule, ForeignKeyAction updateRule, ConstraintDeferrability deferred, String constraintName)
ObjectName Parent
Gets the parent reference of the current one, if any or null if none.
Definition: ObjectName.cs:99
An expression that holds a constant value.
static int RowCountOfReferenceTable(this ITransaction transaction, int rowIndex, ObjectName table1, string[] cols1, ObjectName table2, String[] cols2, bool checkSourceTableKey)
static void AddUniqueKey(this ITransaction transaction, ObjectName tableName, string[] columns, ConstraintDeferrability deferred, string constraintName)
string Name
Gets the name of the object being referenced.
Definition: ObjectName.cs:108
static readonly ObjectName PrimaryKeyColumnsTableName
int ColumnCount
Gets a count of the columns defined by this object.
Definition: TableInfo.cs:159
static bool IsUniqueColumns(ITable table, int rindex, string[] cols, bool nullsAllowed)
static ConstraintInfo ForeignKey(ObjectName tableName, string columnName, ObjectName refTable, string refColumn)
Defines the base class for instances that represent SQL expression tree nodes.
static ObjectName[] QueryTablesRelationallyLinkedTo(this ITransaction transaction, ObjectName tableName)
The simplest implementation of a transaction.
Definition: ITransaction.cs:30
static void AddForeignKey(this ITransaction transaction, ObjectName table, string[] columns, ObjectName refTable, string[] refColumns, String constraintName)
static void AddPrimaryKey(this ITransaction transaction, ObjectName tableName, string[] columns, ConstraintDeferrability deferred, string constraintName)
const int CheckViolation
A Check constraint violation error code.
static ConstraintInfo Unique(ObjectName tableName, params string[] columnNames)
IEnumerable< int > IndexOfColumns(IEnumerable< string > columnNames)
Definition: TableInfo.cs:380
static void CheckAddConstraintViolations(this ITransaction transaction, ITable table, int[] rowIndices, ConstraintDeferrability deferred)