001: /* Copyright (c) 1995-2000, The Hypersonic SQL Group.
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the Hypersonic SQL Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: *
030: * This software consists of voluntary contributions made by many individuals
031: * on behalf of the Hypersonic SQL Group.
032: *
033: *
034: * For work added by the HSQL Development Group:
035: *
036: * Copyright (c) 2001-2005, The HSQL Development Group
037: * All rights reserved.
038: *
039: * Redistribution and use in source and binary forms, with or without
040: * modification, are permitted provided that the following conditions are met:
041: *
042: * Redistributions of source code must retain the above copyright notice, this
043: * list of conditions and the following disclaimer.
044: *
045: * Redistributions in binary form must reproduce the above copyright notice,
046: * this list of conditions and the following disclaimer in the documentation
047: * and/or other materials provided with the distribution.
048: *
049: * Neither the name of the HSQL Development Group nor the names of its
050: * contributors may be used to endorse or promote products derived from this
051: * software without specific prior written permission.
052: *
053: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
054: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
055: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
056: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
057: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
058: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
059: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
060: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
061: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
062: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
063: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
064: */
065:
066: package org.hsqldb;
067:
068: import org.hsqldb.HsqlNameManager.HsqlName;
069: import org.hsqldb.index.RowIterator;
070: import org.hsqldb.lib.ArrayUtil;
071: import org.hsqldb.lib.Iterator;
072:
073: // fredt@users 20020225 - patch 1.7.0 by boucherb@users - named constraints
074: // fredt@users 20020320 - doc 1.7.0 - update
075: // tony_lai@users 20020820 - patch 595156 - violation of Integrity constraint name
076:
077: /**
078: * Implementation of a table constraint with references to the indexes used
079: * by the constraint.<p>
080: *
081: * @author Thomas Mueller (Hypersonic SQL Group)
082: * @version 1.8.0
083: * @since Hypersonic SQL
084: */
085: class Constraint {
086:
087: /*
088: SQL CLI codes
089:
090: Referential Constraint 0 CASCADE
091: Referential Constraint 1 RESTRICT
092: Referential Constraint 2 SET NULL
093: Referential Constraint 3 NO ACTION
094: Referential Constraint 4 SET DEFAULT
095: */
096: static final int CASCADE = 0, SET_NULL = 2, NO_ACTION = 3,
097: SET_DEFAULT = 4, INIT_DEFERRED = 5, INIT_IMMEDIATE = 6,
098: NOT_DEFERRABLE = 7;
099: static final int FOREIGN_KEY = 0, MAIN = 1, UNIQUE = 2, CHECK = 3,
100: PRIMARY_KEY = 4;
101: ConstraintCore core;
102: HsqlName constName;
103: int constType;
104:
105: /**
106: * Constructor declaration for PK and UNIQUE
107: */
108: Constraint(HsqlName name, Table t, Index index, int type) {
109:
110: core = new ConstraintCore();
111: constName = name;
112: constType = type;
113: core.mainTable = t;
114: core.mainIndex = index;
115: /* fredt - in unique constraints column list for iColMain is the
116: visible columns of iMain
117: */
118: core.mainColArray = ArrayUtil.arraySlice(index.getColumns(), 0,
119: index.getVisibleColumns());
120: core.colLen = core.mainColArray.length;
121: }
122:
123: /**
124: * Constructor for main constraints (foreign key references in PK table)
125: */
126: Constraint(HsqlName name, Constraint fkconstraint) {
127:
128: constName = name;
129: constType = MAIN;
130: core = fkconstraint.core;
131: }
132:
133: /**
134: * Constructor for foreign key constraints.
135: *
136: * @param pkname name in the main (referenced) table, used internally
137: * @param name name in the referencing table, public name of the constraint
138: * @param mainTable referenced table
139: * @param refTable referencing talbe
140: * @param mainCols array of column indexes in main table
141: * @param refCols array of column indexes in referencing table
142: * @param mainIndex index on the main table
143: * @param refIndex index on the referencing table
144: * @param deleteAction triggered action on delete
145: * @param updateAction triggered action on update
146: * @exception HsqlException
147: */
148: Constraint(HsqlName pkname, HsqlName name, Table mainTable,
149: Table refTable, int[] mainCols, int[] refCols,
150: Index mainIndex, Index refIndex, int deleteAction,
151: int updateAction) throws HsqlException {
152:
153: core = new ConstraintCore();
154: core.pkName = pkname;
155: core.fkName = name;
156: constName = name;
157: constType = FOREIGN_KEY;
158: core.mainTable = mainTable;
159: core.refTable = refTable;
160: /* fredt - in FK constraints column lists for iColMain and iColRef have
161: identical sets to visible columns of iMain and iRef respectively
162: but the order of columns can be different and must be preserved
163: */
164: core.mainColArray = mainCols;
165: core.colLen = core.mainColArray.length;
166: core.refColArray = refCols;
167: core.mainIndex = mainIndex;
168: core.refIndex = refIndex;
169: core.deleteAction = deleteAction;
170: core.updateAction = updateAction;
171: }
172:
173: /**
174: * temp constraint constructor
175: */
176: Constraint(HsqlName name, int[] mainCols, Table refTable,
177: int[] refCols, int type, int deleteAction, int updateAction) {
178:
179: core = new ConstraintCore();
180: constName = name;
181: constType = type;
182: core.mainColArray = mainCols;
183: core.refTable = refTable;
184: core.refColArray = refCols;
185: core.deleteAction = deleteAction;
186: core.updateAction = updateAction;
187: }
188:
189: private Constraint() {
190: }
191:
192: /**
193: * Returns the HsqlName.
194: */
195: HsqlName getName() {
196: return constName;
197: }
198:
199: /**
200: * Changes constraint name.
201: */
202: private void setName(String name, boolean isquoted)
203: throws HsqlException {
204: constName.rename(name, isquoted);
205: }
206:
207: /**
208: * probably a misnomer, but DatabaseMetaData.getCrossReference specifies
209: * it this way (I suppose because most FKs are declared against the PK of
210: * another table)
211: *
212: * @return name of the index refereneced by a foreign key
213: */
214: String getPkName() {
215: return core.pkName == null ? null : core.pkName.name;
216: }
217:
218: /**
219: * probably a misnomer, but DatabaseMetaData.getCrossReference specifies
220: * it this way (I suppose because most FKs are declared against the PK of
221: * another table)
222: *
223: * @return name of the index for the referencing foreign key
224: */
225: String getFkName() {
226: return core.fkName == null ? null : core.fkName.name;
227: }
228:
229: /**
230: * Returns the type of constraint
231: */
232: int getType() {
233: return constType;
234: }
235:
236: /**
237: * Returns the main table
238: */
239: Table getMain() {
240: return core.mainTable;
241: }
242:
243: /**
244: * Returns the main index
245: */
246: Index getMainIndex() {
247: return core.mainIndex;
248: }
249:
250: /**
251: * Returns the reference table
252: */
253: Table getRef() {
254: return core.refTable;
255: }
256:
257: /**
258: * Returns the reference index
259: */
260: Index getRefIndex() {
261: return core.refIndex;
262: }
263:
264: /**
265: * The ON DELETE triggered action of (foreign key) constraint
266: */
267: int getDeleteAction() {
268: return core.deleteAction;
269: }
270:
271: /**
272: * The ON UPDATE triggered action of (foreign key) constraint
273: */
274: int getUpdateAction() {
275: return core.updateAction;
276: }
277:
278: /**
279: * Returns the main table column index array
280: */
281: int[] getMainColumns() {
282: return core.mainColArray;
283: }
284:
285: /**
286: * Returns the reference table column index array
287: */
288: int[] getRefColumns() {
289: return core.refColArray;
290: }
291:
292: /**
293: * Returns true if an index is part this constraint and the constraint is set for
294: * a foreign key. Used for tests before dropping an index.
295: */
296: boolean isIndexFK(Index index) {
297:
298: if (constType == FOREIGN_KEY || constType == MAIN) {
299: if (core.mainIndex == index || core.refIndex == index) {
300: return true;
301: }
302: }
303:
304: return false;
305: }
306:
307: /**
308: * Returns true if an index is part this constraint and the constraint is set for
309: * a unique constraint. Used for tests before dropping an index.
310: */
311: boolean isIndexUnique(Index index) {
312: return (constType == UNIQUE && core.mainIndex == index);
313: }
314:
315: /**
316: * Only for check constraints
317: */
318: boolean hasColumn(Table table, String colname) {
319:
320: if (constType != CHECK) {
321: return false;
322: }
323:
324: Expression.Collector coll = new Expression.Collector();
325:
326: coll.addAll(core.check, Expression.COLUMN);
327:
328: Iterator it = coll.iterator();
329:
330: for (; it.hasNext();) {
331: Expression e = (Expression) it.next();
332:
333: if (e.getColumnName().equals(colname)
334: && table.tableName.name.equals(e.getTableName())) {
335: return true;
336: }
337: }
338:
339: return false;
340: }
341:
342: boolean hasColumn(int colIndex) {
343:
344: if (constType == MAIN) {
345: return ArrayUtil.find(core.mainColArray, colIndex) != -1;
346: } else if (constType == FOREIGN_KEY) {
347: return ArrayUtil.find(core.refColArray, colIndex) != -1;
348: }
349:
350: return false;
351: }
352:
353: // fredt@users 20020225 - patch 1.7.0 by fredt - duplicate constraints
354:
355: /**
356: * Compares this with another constraint column set. This implementation
357: * only checks UNIQUE constraints.
358: */
359: boolean isEquivalent(int[] col, int type) {
360:
361: if (type != constType || constType != UNIQUE
362: || core.colLen != col.length) {
363: return false;
364: }
365:
366: return ArrayUtil.haveEqualSets(core.mainColArray, col,
367: core.colLen);
368: }
369:
370: /**
371: * Compares this with another constraint column set. This implementation
372: * only checks FOREIGN KEY constraints.
373: */
374: boolean isEquivalent(Table tablemain, int[] colmain,
375: Table tableref, int[] colref) {
376:
377: if (constType != Constraint.MAIN
378: && constType != Constraint.FOREIGN_KEY) {
379: return false;
380: }
381:
382: if (tablemain != core.mainTable || tableref != core.refTable) {
383: return false;
384: }
385:
386: return ArrayUtil.areEqualSets(core.mainColArray, colmain)
387: && ArrayUtil.areEqualSets(core.refColArray, colref);
388: }
389:
390: /**
391: * Used to update constrains to reflect structural changes in a table.
392: * Prior checks must ensure that this method does not throw.
393: *
394: * @param oldt reference to the old version of the table
395: * @param newt referenct to the new version of the table
396: * @param colindex index at which table column is added or removed
397: * @param adjust -1, 0, +1 to indicate if column is added or removed
398: * @throws HsqlException
399: */
400: void replaceTable(Table oldt, Table newt, int colindex, int adjust)
401: throws HsqlException {
402:
403: if (oldt == core.mainTable) {
404: core.mainTable = newt;
405:
406: // exclude CHECK
407: if (core.mainIndex != null) {
408: core.mainIndex = core.mainTable.getIndex(core.mainIndex
409: .getName().name);
410: core.mainColArray = ArrayUtil.toAdjustedColumnArray(
411: core.mainColArray, colindex, adjust);
412: }
413: }
414:
415: if (oldt == core.refTable) {
416: core.refTable = newt;
417:
418: if (core.refIndex != null) {
419: core.refIndex = core.refTable.getIndex(core.refIndex
420: .getName().name);
421:
422: if (core.refIndex != core.mainIndex) {
423: core.refColArray = ArrayUtil.toAdjustedColumnArray(
424: core.refColArray, colindex, adjust);
425: }
426: }
427: }
428: }
429:
430: /**
431: * Checks for foreign key or check constraint violation when
432: * inserting a row into the child table.
433: */
434: void checkInsert(Session session, Object[] row)
435: throws HsqlException {
436:
437: if (constType == Constraint.MAIN
438: || constType == Constraint.UNIQUE
439: || constType == Constraint.PRIMARY_KEY) {
440:
441: // inserts in the main table are never a problem
442: // unique constraints are checked by the unique index
443: return;
444: }
445:
446: if (constType == Constraint.CHECK) {
447: checkCheckConstraint(session, row);
448:
449: return;
450: }
451:
452: if (Index.isNull(row, core.refColArray)) {
453: return;
454: }
455:
456: // a record must exist in the main table
457: boolean exists = core.mainIndex.exists(session, row,
458: core.refColArray);
459:
460: if (!exists) {
461:
462: // special case: self referencing table and self referencing row
463: if (core.mainTable == core.refTable) {
464: boolean match = true;
465:
466: for (int i = 0; i < core.colLen; i++) {
467: if (!row[core.refColArray[i]]
468: .equals(row[core.mainColArray[i]])) {
469: match = false;
470:
471: break;
472: }
473: }
474:
475: if (match) {
476: return;
477: }
478: }
479:
480: throw Trace.error(
481: Trace.INTEGRITY_CONSTRAINT_VIOLATION_NOPARENT,
482: Trace.Constraint_violation, new Object[] {
483: core.fkName.name,
484: core.mainTable.getName().name });
485: }
486: }
487:
488: /*
489: * Tests a row against this CHECK constraint.
490: */
491: void checkCheckConstraint(Session session, Object[] row)
492: throws HsqlException {
493:
494: core.checkFilter.currentData = row;
495:
496: boolean nomatch = Boolean.FALSE
497: .equals(core.check.test(session));
498:
499: core.checkFilter.currentData = null;
500:
501: if (nomatch) {
502: throw Trace.error(Trace.CHECK_CONSTRAINT_VIOLATION,
503: Trace.Constraint_violation, new Object[] {
504: constName.name,
505: core.mainTable.tableName.name });
506: }
507: }
508:
509: // fredt@users 20020225 - patch 1.7.0 - cascading deletes
510:
511: /**
512: * New method to find any referencing row for a
513: * foreign key (finds row in child table). If ON DELETE CASCADE is
514: * supported by this constraint, then the method finds the first row
515: * among the rows of the table ordered by the index and doesn't throw.
516: * Without ON DELETE CASCADE, the method attempts to finds any row that
517: * exists, in which case it throws an exception. If no row is found,
518: * null is returned.
519: * (fredt@users)
520: *
521: * @param row array of objects for a database row
522: * @param forDelete should we allow 'ON DELETE CASCADE' or 'ON UPDATE CASCADE'
523: * @return Node object or null
524: * @throws HsqlException
525: */
526: RowIterator findFkRef(Session session, Object[] row, boolean delete)
527: throws HsqlException {
528:
529: if (row == null || Index.isNull(row, core.mainColArray)) {
530: return core.refIndex.emptyIterator();
531: }
532:
533: return delete ? core.refIndex.findFirstRowForDelete(session,
534: row, core.mainColArray) : core.refIndex.findFirstRow(
535: session, row, core.mainColArray);
536: }
537:
538: /**
539: * For the candidate table row, finds any referring node in the main table.
540: * This is used to check referential integrity when updating a node. We
541: * have to make sure that the main table still holds a valid main record.
542: * If a valid row is found the corresponding <code>Node</code> is returned.
543: * Otherwise a 'INTEGRITY VIOLATION' Exception gets thrown.
544: */
545: boolean hasMainRef(Session session, Object[] row)
546: throws HsqlException {
547:
548: if (Index.isNull(row, core.refColArray)) {
549: return false;
550: }
551:
552: boolean exists = core.mainIndex.exists(session, row,
553: core.refColArray);
554:
555: // -- there has to be a valid node in the main table
556: // --
557: if (!exists) {
558: throw Trace.error(
559: Trace.INTEGRITY_CONSTRAINT_VIOLATION_NOPARENT,
560: Trace.Constraint_violation, new Object[] {
561: core.fkName.name,
562: core.refTable.getName().name });
563: }
564:
565: return exists;
566: }
567:
568: /**
569: * Test used before adding a new foreign key constraint. This method
570: * returns true if the given row has a corresponding row in the main
571: * table. Also returns true if any column covered by the foreign key
572: * constraint has a null value.
573: */
574: private static boolean hasReferencedRow(Session session,
575: Object[] rowdata, int[] rowColArray, Index mainIndex)
576: throws HsqlException {
577:
578: if (Index.isNull(rowdata, rowColArray)) {
579: return true;
580: }
581:
582: // else a record must exist in the main index
583: return mainIndex.exists(session, rowdata, rowColArray);
584: }
585:
586: /**
587: * Check used before creating a new foreign key cosntraint, this method
588: * checks all rows of a table to ensure they all have a corresponding
589: * row in the main table.
590: */
591: static void checkReferencedRows(Session session, Table table,
592: int[] rowColArray, Index mainIndex) throws HsqlException {
593:
594: RowIterator it = table.getPrimaryIndex().firstRow(session);
595:
596: while (true) {
597: Row row = it.next();
598:
599: if (row == null) {
600: break;
601: }
602:
603: Object[] rowdata = row.getData();
604:
605: if (!Constraint.hasReferencedRow(session, rowdata,
606: rowColArray, mainIndex)) {
607: String colvalues = "";
608:
609: for (int i = 0; i < rowColArray.length; i++) {
610: Object o = rowdata[rowColArray[i]];
611:
612: colvalues += o;
613: colvalues += ",";
614: }
615:
616: throw Trace.error(
617: Trace.INTEGRITY_CONSTRAINT_VIOLATION_NOPARENT,
618: Trace.Constraint_violation, new Object[] {
619: colvalues, table.getName().name });
620: }
621: }
622: }
623: }
|