/* Copyright (c) 1995-2000, The Hypersonic SQL Group. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the Hypersonic SQL Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * on behalf of the Hypersonic SQL Group. * * * For work added by the HSQL Development Group: * * Copyright (c) 2001-2008, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb; import org.hsqldb.HsqlNameManager.HsqlName; import org.hsqldb.index.RowIterator; import org.hsqldb.lib.ArrayUtil; import org.hsqldb.lib.Iterator; // fredt@users 20020225 - patch 1.7.0 by boucherb@users - named constraints // fredt@users 20020320 - doc 1.7.0 - update // tony_lai@users 20020820 - patch 595156 - violation of Integrity constraint name /** * Implementation of a table constraint with references to the indexes used * by the constraint.
*
* @author Thomas Mueller (Hypersonic SQL Group)
* @version 1.8.0
* @since Hypersonic SQL
*/
public class Constraint {
/*
SQL CLI codes
Referential Constraint 0 CASCADE
Referential Constraint 1 RESTRICT
Referential Constraint 2 SET NULL
Referential Constraint 3 NO ACTION
Referential Constraint 4 SET DEFAULT
*/
static final int CASCADE = 0,
SET_NULL = 2,
NO_ACTION = 3,
SET_DEFAULT = 4,
INIT_DEFERRED = 5,
INIT_IMMEDIATE = 6,
NOT_DEFERRABLE = 7;
static final int FOREIGN_KEY = 0,
MAIN = 1,
UNIQUE = 2,
CHECK = 3,
PRIMARY_KEY = 4;
ConstraintCore core;
HsqlName constName;
int constType;
/**
* Constructor declaration for PK and UNIQUE
*/
Constraint(HsqlName name, Table t, Index index, int type) {
core = new ConstraintCore();
constName = name;
constType = type;
core.mainTable = t;
core.mainIndex = index;
/* fredt - in unique constraints column list for iColMain is the
visible columns of iMain
*/
core.mainColArray = ArrayUtil.arraySlice(index.getColumns(), 0,
index.getVisibleColumns());
core.colLen = core.mainColArray.length;
}
/**
* Constructor for main constraints (foreign key references in PK table)
*/
Constraint(HsqlName name, Constraint fkconstraint) {
constName = name;
constType = MAIN;
core = fkconstraint.core;
}
/**
* Constructor for foreign key constraints.
*
* @param pkname name in the main (referenced) table, used internally
* @param name name in the referencing table, public name of the constraint
* @param mainTable referenced table
* @param refTable referencing talbe
* @param mainCols array of column indexes in main table
* @param refCols array of column indexes in referencing table
* @param mainIndex index on the main table
* @param refIndex index on the referencing table
* @param deleteAction triggered action on delete
* @param updateAction triggered action on update
* @exception HsqlException
*/
Constraint(HsqlName pkname, HsqlName name, Table mainTable,
Table refTable, int[] mainCols, int[] refCols,
Index mainIndex, Index refIndex, int deleteAction,
int updateAction) throws HsqlException {
core = new ConstraintCore();
core.pkName = pkname;
core.fkName = name;
constName = name;
constType = FOREIGN_KEY;
core.mainTable = mainTable;
core.refTable = refTable;
/* fredt - in FK constraints column lists for iColMain and iColRef have
identical sets to visible columns of iMain and iRef respectively
but the order of columns can be different and must be preserved
*/
core.mainColArray = mainCols;
core.colLen = core.mainColArray.length;
core.refColArray = refCols;
core.mainIndex = mainIndex;
core.refIndex = refIndex;
core.deleteAction = deleteAction;
core.updateAction = updateAction;
}
/**
* temp constraint constructor
*/
Constraint(HsqlName name, int[] mainCols, Table refTable, int[] refCols,
int type, int deleteAction, int updateAction) {
core = new ConstraintCore();
constName = name;
constType = type;
core.mainColArray = mainCols;
core.refTable = refTable;
core.refColArray = refCols;
core.deleteAction = deleteAction;
core.updateAction = updateAction;
}
private Constraint() {}
/**
* Returns the HsqlName.
*/
HsqlName getName() {
return constName;
}
/**
* Changes constraint name.
*/
private void setName(String name, boolean isquoted) throws HsqlException {
constName.rename(name, isquoted);
}
/**
* probably a misnomer, but DatabaseMetaData.getCrossReference specifies
* it this way (I suppose because most FKs are declared against the PK of
* another table)
*
* @return name of the index refereneced by a foreign key
*/
String getPkName() {
return core.pkName == null ? null
: core.pkName.name;
}
/**
* probably a misnomer, but DatabaseMetaData.getCrossReference specifies
* it this way (I suppose because most FKs are declared against the PK of
* another table)
*
* @return name of the index for the referencing foreign key
*/
String getFkName() {
return core.fkName == null ? null
: core.fkName.name;
}
/**
* Returns the type of constraint
*/
int getType() {
return constType;
}
/**
* Returns the main table
*/
Table getMain() {
return core.mainTable;
}
/**
* Returns the main index
*/
Index getMainIndex() {
return core.mainIndex;
}
/**
* Returns the reference table
*/
Table getRef() {
return core.refTable;
}
/**
* Returns the reference index
*/
Index getRefIndex() {
return core.refIndex;
}
/**
* The ON DELETE triggered action of (foreign key) constraint
*/
int getDeleteAction() {
return core.deleteAction;
}
/**
* The ON UPDATE triggered action of (foreign key) constraint
*/
int getUpdateAction() {
return core.updateAction;
}
/**
* Returns the main table column index array
*/
int[] getMainColumns() {
return core.mainColArray;
}
/**
* Returns the reference table column index array
*/
int[] getRefColumns() {
return core.refColArray;
}
/**
* Returns true if an index is part this constraint and the constraint is set for
* a foreign key. Used for tests before dropping an index.
*/
boolean isIndexFK(Index index) {
if (constType == FOREIGN_KEY || constType == MAIN) {
if (core.mainIndex == index || core.refIndex == index) {
return true;
}
}
return false;
}
/**
* Returns true if an index is part this constraint and the constraint is set for
* a unique constraint. Used for tests before dropping an index.
*/
boolean isIndexUnique(Index index) {
return (constType == UNIQUE && core.mainIndex == index);
}
/**
* Only for check constraints
*/
boolean hasColumn(Table table, String colname) {
if (constType != CHECK) {
return false;
}
Expression.Collector coll = new Expression.Collector();
coll.addAll(core.check, Expression.COLUMN);
Iterator it = coll.iterator();
for (; it.hasNext(); ) {
Expression e = (Expression) it.next();
if (e.getColumnName().equals(colname)
&& table.tableName.name.equals(e.getTableName())) {
return true;
}
}
return false;
}
boolean hasColumn(int colIndex) {
if (constType == MAIN) {
return ArrayUtil.find(core.mainColArray, colIndex) != -1;
} else if (constType == FOREIGN_KEY) {
return ArrayUtil.find(core.refColArray, colIndex) != -1;
}
return false;
}
// fredt@users 20020225 - patch 1.7.0 by fredt - duplicate constraints
/**
* Compares this with another constraint column set. This implementation
* only checks UNIQUE constraints.
*/
boolean isEquivalent(int[] col, int type) {
if (type != constType || constType != UNIQUE
|| core.colLen != col.length) {
return false;
}
return ArrayUtil.haveEqualSets(core.mainColArray, col, core.colLen);
}
/**
* Compares this with another constraint column set. This implementation
* only checks FOREIGN KEY constraints.
*/
boolean isEquivalent(Table tablemain, int[] colmain, Table tableref,
int[] colref) {
if (constType != Constraint.MAIN
&& constType != Constraint.FOREIGN_KEY) {
return false;
}
if (tablemain != core.mainTable || tableref != core.refTable) {
return false;
}
return ArrayUtil.areEqualSets(core.mainColArray, colmain)
&& ArrayUtil.areEqualSets(core.refColArray, colref);
}
/**
* Used to update constrains to reflect structural changes in a table.
* Prior checks must ensure that this method does not throw.
*
* @param oldt reference to the old version of the table
* @param newt referenct to the new version of the table
* @param colindex index at which table column is added or removed
* @param adjust -1, 0, +1 to indicate if column is added or removed
* @throws HsqlException
*/
void replaceTable(Table oldt, Table newt, int colindex,
int adjust) throws HsqlException {
if (oldt == core.mainTable) {
core.mainTable = newt;
// exclude CHECK
if (core.mainIndex != null) {
core.mainIndex =
core.mainTable.getIndex(core.mainIndex.getName().name);
core.mainColArray =
ArrayUtil.toAdjustedColumnArray(core.mainColArray,
colindex, adjust);
}
}
if (oldt == core.refTable) {
core.refTable = newt;
if (core.refIndex != null) {
core.refIndex =
core.refTable.getIndex(core.refIndex.getName().name);
if (core.refIndex != core.mainIndex) {
core.refColArray =
ArrayUtil.toAdjustedColumnArray(core.refColArray,
colindex, adjust);
}
}
}
}
/**
* Checks for foreign key or check constraint violation when
* inserting a row into the child table.
*/
void checkInsert(Session session, Object[] row) throws HsqlException {
if (constType == Constraint.MAIN || constType == Constraint.UNIQUE
|| constType == Constraint.PRIMARY_KEY) {
// inserts in the main table are never a problem
// unique constraints are checked by the unique index
return;
}
if (constType == Constraint.CHECK) {
checkCheckConstraint(session, row);
return;
}
if (Index.isNull(row, core.refColArray)) {
return;
}
// a record must exist in the main table
boolean exists = core.mainIndex.exists(session, row,
core.refColArray);
if (!exists) {
// special case: self referencing table and self referencing row
if (core.mainTable == core.refTable) {
boolean match = true;
for (int i = 0; i < core.colLen; i++) {
if (!row[core.refColArray[i]].equals(
row[core.mainColArray[i]])) {
match = false;
break;
}
}
if (match) {
return;
}
}
throw Trace.error(Trace.INTEGRITY_CONSTRAINT_VIOLATION_NOPARENT,
Trace.Constraint_violation, new Object[] {
core.fkName.name, core.mainTable.getName().name
});
}
}
/*
* Tests a row against this CHECK constraint.
*/
void checkCheckConstraint(Session session,
Object[] row) throws HsqlException {
core.checkFilter.currentData = row;
boolean nomatch = Boolean.FALSE.equals(core.check.test(session));
core.checkFilter.currentData = null;
if (nomatch) {
throw Trace.error(Trace.CHECK_CONSTRAINT_VIOLATION,
Trace.Constraint_violation, new Object[] {
constName.name, core.mainTable.tableName.name
});
}
}
// fredt@users 20020225 - patch 1.7.0 - cascading deletes
/**
* New method to find any referencing row for a
* foreign key (finds row in child table). If ON DELETE CASCADE is
* supported by this constraint, then the method finds the first row
* among the rows of the table ordered by the index and doesn't throw.
* Without ON DELETE CASCADE, the method attempts to finds any row that
* exists, in which case it throws an exception. If no row is found,
* null is returned.
* (fredt@users)
*
* @param row array of objects for a database row
* @param forDelete should we allow 'ON DELETE CASCADE' or 'ON UPDATE CASCADE'
* @return Node object or null
* @throws HsqlException
*/
RowIterator findFkRef(Session session, Object[] row,
boolean delete) throws HsqlException {
if (row == null || Index.isNull(row, core.mainColArray)) {
return core.refIndex.emptyIterator();
}
return delete
? core.refIndex.findFirstRowForDelete(session, row,
core.mainColArray)
: core.refIndex.findFirstRow(session, row, core.mainColArray);
}
/**
* For the candidate table row, finds any referring node in the main table.
* This is used to check referential integrity when updating a node. We
* have to make sure that the main table still holds a valid main record.
* If a valid row is found the corresponding Node
is returned.
* Otherwise a 'INTEGRITY VIOLATION' Exception gets thrown.
*/
boolean hasMainRef(Session session, Object[] row) throws HsqlException {
if (Index.isNull(row, core.refColArray)) {
return false;
}
boolean exists = core.mainIndex.exists(session, row,
core.refColArray);
// -- there has to be a valid node in the main table
// --
if (!exists) {
throw Trace.error(Trace.INTEGRITY_CONSTRAINT_VIOLATION_NOPARENT,
Trace.Constraint_violation, new Object[] {
core.fkName.name, core.refTable.getName().name
});
}
return exists;
}
/**
* Test used before adding a new foreign key constraint. This method
* returns true if the given row has a corresponding row in the main
* table. Also returns true if any column covered by the foreign key
* constraint has a null value.
*/
private static boolean hasReferencedRow(Session session,
Object[] rowdata, int[] rowColArray,
Index mainIndex) throws HsqlException {
if (Index.isNull(rowdata, rowColArray)) {
return true;
}
// else a record must exist in the main index
return mainIndex.exists(session, rowdata, rowColArray);
}
/**
* Check used before creating a new foreign key cosntraint, this method
* checks all rows of a table to ensure they all have a corresponding
* row in the main table.
*/
static void checkReferencedRows(Session session, Table table,
int[] rowColArray,
Index mainIndex) throws HsqlException {
RowIterator it = table.getPrimaryIndex().firstRow(session);
while (true) {
Row row = it.next();
if (row == null) {
break;
}
Object[] rowdata = row.getData();
if (!Constraint.hasReferencedRow(session, rowdata, rowColArray,
mainIndex)) {
String colvalues = "";
for (int i = 0; i < rowColArray.length; i++) {
Object o = rowdata[rowColArray[i]];
colvalues += o;
colvalues += ",";
}
throw Trace.error(
Trace.INTEGRITY_CONSTRAINT_VIOLATION_NOPARENT,
Trace.Constraint_violation, new Object[] {
colvalues, table.getName().name
});
}
}
}
}