| /* |
| * For work developed by the HSQL Development Group: |
| * |
| * Copyright (c) 2001-2010, 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. |
| * |
| * |
| * |
| * For work originally developed by the Hypersonic SQL Group: |
| * |
| * 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. |
| */ |
| |
| |
| package org.hsqldb; |
| |
| import org.hsqldb.HsqlNameManager.HsqlName; |
| import org.hsqldb.RangeVariable.RangeIteratorBase; |
| import org.hsqldb.error.Error; |
| import org.hsqldb.error.ErrorCode; |
| import org.hsqldb.index.Index; |
| import org.hsqldb.lib.ArrayUtil; |
| import org.hsqldb.lib.OrderedHashSet; |
| import org.hsqldb.navigator.RowIterator; |
| import org.hsqldb.persist.PersistentStore; |
| import org.hsqldb.result.Result; |
| import org.hsqldb.rights.Grantee; |
| import org.hsqldb.types.Type; |
| |
| // 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.<p> |
| * |
| * Partly based on Hypersonic code. |
| * |
| * @author Thomas Mueller (Hypersonic SQL Group) |
| * @author Fred Toussi (fredt@users dot sourceforge.net) |
| * @version 1.9.0 |
| * @since Hypersonic SQL |
| */ |
| public final class Constraint implements SchemaObject { |
| |
| ConstraintCore core; |
| private HsqlName name; |
| int constType; |
| boolean isForward; |
| |
| // |
| Expression check; |
| private boolean isNotNull; |
| int notNullColumnIndex; |
| RangeVariable rangeVariable; |
| |
| // for temp constraints only |
| OrderedHashSet mainColSet; |
| OrderedHashSet refColSet; |
| |
| // |
| final public static Constraint[] emptyArray = new Constraint[]{}; |
| |
| private Constraint() {} |
| |
| /** |
| * Constructor declaration for PK and UNIQUE |
| */ |
| public Constraint(HsqlName name, Table t, Index index, int type) { |
| |
| this.name = name; |
| constType = type; |
| core = new ConstraintCore(); |
| core.mainTable = t; |
| core.mainIndex = index; |
| core.mainCols = index.getColumns(); |
| |
| for (int i = 0; i < core.mainCols.length; i++) { |
| Type dataType = t.getColumn(core.mainCols[i]).getDataType(); |
| |
| if (dataType.isLobType()) { |
| throw Error.error(ErrorCode.X_42534); |
| } |
| } |
| } |
| |
| public Constraint(HsqlName name, Table table, int[] cols, int type) { |
| |
| this.name = name; |
| constType = type; |
| core = new ConstraintCore(); |
| core.mainTable = table; |
| core.mainCols = cols; |
| } |
| |
| /** |
| * Constructor for main constraints (foreign key references in PK table) |
| */ |
| public Constraint(HsqlName name, Constraint fkconstraint) { |
| |
| this.name = name; |
| constType = SchemaObject.ConstraintTypes.MAIN; |
| core = fkconstraint.core; |
| } |
| |
| /** |
| * General constructor for foreign key constraints. |
| * |
| * @param name name of constraint |
| * @param refCols list of referencing columns |
| * @param mainTableName referenced table |
| * @param mainCols list of referenced columns |
| * @param type constraint type |
| * @param deleteAction triggered action on delete |
| * @param updateAction triggered action on update |
| * |
| */ |
| public Constraint(HsqlName name, HsqlName refTableName, |
| OrderedHashSet refCols, HsqlName mainTableName, |
| OrderedHashSet mainCols, int type, int deleteAction, |
| int updateAction, int matchType) { |
| |
| this.name = name; |
| constType = type; |
| mainColSet = mainCols; |
| refColSet = refCols; |
| core = new ConstraintCore(); |
| core.refTableName = refTableName; |
| core.mainTableName = mainTableName; |
| core.deleteAction = deleteAction; |
| core.updateAction = updateAction; |
| core.matchType = matchType; |
| |
| switch (core.deleteAction) { |
| |
| case SchemaObject.ReferentialAction.CASCADE : |
| case SchemaObject.ReferentialAction.SET_DEFAULT : |
| case SchemaObject.ReferentialAction.SET_NULL : |
| core.hasDeleteAction = true; |
| } |
| |
| switch (core.updateAction) { |
| |
| case SchemaObject.ReferentialAction.CASCADE : |
| case SchemaObject.ReferentialAction.SET_DEFAULT : |
| case SchemaObject.ReferentialAction.SET_NULL : |
| core.hasUpdateAction = true; |
| } |
| } |
| |
| public Constraint(HsqlName name, OrderedHashSet mainCols, int type) { |
| |
| this.name = name; |
| constType = type; |
| mainColSet = mainCols; |
| core = new ConstraintCore(); |
| } |
| |
| public Constraint(HsqlName uniqueName, HsqlName mainName, |
| HsqlName refName, Table mainTable, Table refTable, |
| int[] mainCols, int[] refCols, Index mainIndex, |
| Index refIndex, int deleteAction, |
| int updateAction) throws HsqlException { |
| |
| this.name = refName; |
| constType = SchemaObject.ConstraintTypes.FOREIGN_KEY; |
| core = new ConstraintCore(); |
| core.uniqueName = uniqueName; |
| core.mainName = mainName; |
| core.refName = refName; |
| core.mainTable = mainTable; |
| core.refTable = refTable; |
| core.mainCols = mainCols; |
| core.refCols = refCols; |
| core.mainIndex = mainIndex; |
| core.refIndex = refIndex; |
| core.deleteAction = deleteAction; |
| core.updateAction = updateAction; |
| } |
| |
| Constraint duplicate() { |
| |
| Constraint copy = new Constraint(); |
| |
| copy.core = core.duplicate(); |
| copy.name = name; |
| copy.constType = constType; |
| copy.isForward = isForward; |
| |
| // |
| copy.check = check; |
| copy.isNotNull = isNotNull; |
| copy.notNullColumnIndex = notNullColumnIndex; |
| copy.rangeVariable = rangeVariable; |
| |
| return copy; |
| } |
| |
| void setColumnsIndexes(Table table) { |
| |
| if (constType == SchemaObject.ConstraintTypes.FOREIGN_KEY) { |
| if (mainColSet == null) { |
| core.mainCols = core.mainTable.getPrimaryKey(); |
| |
| if (core.mainCols == null) { |
| throw Error.error(ErrorCode.X_42581); |
| } |
| } else if (core.mainCols == null) { |
| core.mainCols = core.mainTable.getColumnIndexes(mainColSet); |
| } |
| |
| if (core.refCols == null) { |
| core.refCols = table.getColumnIndexes(refColSet); |
| } |
| |
| for (int i = 0; i < core.refCols.length; i++) { |
| Type dataType = table.getColumn(core.refCols[i]).getDataType(); |
| |
| if (dataType.isLobType()) { |
| throw Error.error(ErrorCode.X_42534); |
| } |
| } |
| } else if (mainColSet != null) { |
| core.mainCols = table.getColumnIndexes(mainColSet); |
| |
| for (int i = 0; i < core.mainCols.length; i++) { |
| Type dataType = |
| table.getColumn(core.mainCols[i]).getDataType(); |
| |
| if (dataType.isLobType()) { |
| throw Error.error(ErrorCode.X_42534); |
| } |
| } |
| } |
| } |
| |
| public int getType() { |
| return SchemaObject.CONSTRAINT; |
| } |
| |
| /** |
| * Returns the HsqlName. |
| */ |
| public HsqlName getName() { |
| return name; |
| } |
| |
| public HsqlName getCatalogName() { |
| return name.schema.schema; |
| } |
| |
| public HsqlName getSchemaName() { |
| return name.schema; |
| } |
| |
| public Grantee getOwner() { |
| return name.schema.owner; |
| } |
| |
| public OrderedHashSet getReferences() { |
| |
| switch (constType) { |
| |
| case SchemaObject.ConstraintTypes.CHECK : |
| OrderedHashSet refs = new OrderedHashSet(); |
| |
| check.collectObjectNames(refs); |
| |
| for (int j = refs.size() - 1; j >= 0; j--) { |
| HsqlName name = (HsqlName) refs.get(j); |
| |
| if (name.type == SchemaObject.COLUMN |
| || name.type == SchemaObject.TABLE) { |
| refs.remove(j); |
| } |
| } |
| |
| return refs; |
| |
| case SchemaObject.ConstraintTypes.FOREIGN_KEY : |
| OrderedHashSet set = new OrderedHashSet(); |
| |
| set.add(core.uniqueName); |
| |
| return set; |
| } |
| |
| return new OrderedHashSet(); |
| } |
| |
| public OrderedHashSet getComponents() { |
| return null; |
| } |
| |
| public void compile(Session session, SchemaObject parentObject) {} |
| |
| public String getSQL() { |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| switch (getConstraintType()) { |
| |
| case SchemaObject.ConstraintTypes.PRIMARY_KEY : |
| if (getMainColumns().length > 1 |
| || (getMainColumns().length == 1 |
| && !getName().isReservedName())) { |
| if (!getName().isReservedName()) { |
| sb.append(Tokens.T_CONSTRAINT).append(' '); |
| sb.append(getName().statementName).append(' '); |
| } |
| |
| sb.append(Tokens.T_PRIMARY).append(' ').append( |
| Tokens.T_KEY); |
| sb.append( |
| getMain().getColumnListSQL( |
| getMainColumns(), getMainColumns().length)); |
| } |
| break; |
| |
| case SchemaObject.ConstraintTypes.UNIQUE : |
| if (!getName().isReservedName()) { |
| sb.append(Tokens.T_CONSTRAINT).append(' '); |
| sb.append(getName().statementName); |
| sb.append(' '); |
| } |
| |
| sb.append(Tokens.T_UNIQUE); |
| |
| int[] col = getMainColumns(); |
| |
| sb.append(getMain().getColumnListSQL(col, col.length)); |
| break; |
| |
| case SchemaObject.ConstraintTypes.FOREIGN_KEY : |
| if (isForward) { |
| sb.append(Tokens.T_ALTER).append(' ').append( |
| Tokens.T_TABLE).append(' '); |
| sb.append( |
| getRef().getName().getSchemaQualifiedStatementName()); |
| sb.append(' ').append(Tokens.T_ADD).append(' '); |
| getFKStatement(sb); |
| } else { |
| getFKStatement(sb); |
| } |
| break; |
| |
| case SchemaObject.ConstraintTypes.CHECK : |
| if (isNotNull()) { |
| break; |
| } |
| |
| if (!getName().isReservedName()) { |
| sb.append(Tokens.T_CONSTRAINT).append(' '); |
| sb.append(getName().statementName).append(' '); |
| } |
| |
| sb.append(Tokens.T_CHECK).append('('); |
| sb.append(check.getSQL()); |
| sb.append(')'); |
| |
| // should not throw as it is already tested OK |
| break; |
| } |
| |
| return sb.toString(); |
| } |
| |
| public long getChangeTimestamp() { |
| return 0; |
| } |
| |
| /** |
| * Generates the foreign key declaration for a given Constraint object. |
| */ |
| private void getFKStatement(StringBuffer sb) { |
| |
| if (!getName().isReservedName()) { |
| sb.append(Tokens.T_CONSTRAINT).append(' '); |
| sb.append(getName().statementName); |
| sb.append(' '); |
| } |
| |
| sb.append(Tokens.T_FOREIGN).append(' ').append(Tokens.T_KEY); |
| |
| int[] col = getRefColumns(); |
| |
| sb.append(getRef().getColumnListSQL(col, col.length)); |
| sb.append(' ').append(Tokens.T_REFERENCES).append(' '); |
| sb.append(getMain().getName().getSchemaQualifiedStatementName()); |
| |
| col = getMainColumns(); |
| |
| sb.append(getMain().getColumnListSQL(col, col.length)); |
| |
| if (getDeleteAction() != SchemaObject.ReferentialAction.NO_ACTION) { |
| sb.append(' ').append(Tokens.T_ON).append(' ').append( |
| Tokens.T_DELETE).append(' '); |
| sb.append(getDeleteActionString()); |
| } |
| |
| if (getUpdateAction() != SchemaObject.ReferentialAction.NO_ACTION) { |
| sb.append(' ').append(Tokens.T_ON).append(' ').append( |
| Tokens.T_UPDATE).append(' '); |
| sb.append(getUpdateActionString()); |
| } |
| } |
| |
| public HsqlName getMainTableName() { |
| return core.mainTableName; |
| } |
| |
| public HsqlName getMainName() { |
| return core.mainName; |
| } |
| |
| public HsqlName getRefName() { |
| return core.refName; |
| } |
| |
| public HsqlName getUniqueName() { |
| return core.uniqueName; |
| } |
| |
| /** |
| * Returns the type of constraint |
| */ |
| public int getConstraintType() { |
| return constType; |
| } |
| |
| /** |
| * Returns the main table |
| */ |
| public Table getMain() { |
| return core.mainTable; |
| } |
| |
| /** |
| * Returns the main index |
| */ |
| Index getMainIndex() { |
| return core.mainIndex; |
| } |
| |
| /** |
| * Returns the reference table |
| */ |
| public Table getRef() { |
| return core.refTable; |
| } |
| |
| /** |
| * Returns the reference index |
| */ |
| Index getRefIndex() { |
| return core.refIndex; |
| } |
| |
| /** |
| * Returns the foreign key action rule. |
| */ |
| private static String getActionString(int action) { |
| |
| switch (action) { |
| |
| case SchemaObject.ReferentialAction.RESTRICT : |
| return Tokens.T_RESTRICT; |
| |
| case SchemaObject.ReferentialAction.CASCADE : |
| return Tokens.T_CASCADE; |
| |
| case SchemaObject.ReferentialAction.SET_DEFAULT : |
| return Tokens.T_SET + ' ' + Tokens.T_DEFAULT; |
| |
| case SchemaObject.ReferentialAction.SET_NULL : |
| return Tokens.T_SET + ' ' + Tokens.T_NULL; |
| |
| default : |
| return Tokens.T_NO + ' ' + Tokens.T_ACTION; |
| } |
| } |
| |
| /** |
| * The ON DELETE triggered action of (foreign key) constraint |
| */ |
| public int getDeleteAction() { |
| return core.deleteAction; |
| } |
| |
| public String getDeleteActionString() { |
| return getActionString(core.deleteAction); |
| } |
| |
| /** |
| * The ON UPDATE triggered action of (foreign key) constraint |
| */ |
| public int getUpdateAction() { |
| return core.updateAction; |
| } |
| |
| public String getUpdateActionString() { |
| return getActionString(core.updateAction); |
| } |
| |
| public boolean hasTriggeredAction() { |
| |
| if (constType == SchemaObject.ConstraintTypes.FOREIGN_KEY) { |
| switch (core.deleteAction) { |
| |
| case SchemaObject.ReferentialAction.CASCADE : |
| case SchemaObject.ReferentialAction.SET_DEFAULT : |
| case SchemaObject.ReferentialAction.SET_NULL : |
| return true; |
| } |
| |
| switch (core.updateAction) { |
| |
| case SchemaObject.ReferentialAction.CASCADE : |
| case SchemaObject.ReferentialAction.SET_DEFAULT : |
| case SchemaObject.ReferentialAction.SET_NULL : |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| public int getDeferability() { |
| return SchemaObject.Deferable.NOT_DEFERRABLE; |
| } |
| |
| /** |
| * Returns the main table column index array |
| */ |
| public int[] getMainColumns() { |
| return core.mainCols; |
| } |
| |
| /** |
| * Returns the reference table column index array |
| */ |
| public int[] getRefColumns() { |
| return core.refCols; |
| } |
| |
| /** |
| * Returns the SQL for the expression in CHECK clause |
| */ |
| public String getCheckSQL() { |
| return check.getSQL(); |
| } |
| |
| /** |
| * Returns true if the expression in CHECK is a simple IS NOT NULL |
| */ |
| public boolean isNotNull() { |
| return isNotNull; |
| } |
| |
| boolean hasColumnOnly(int colIndex) { |
| |
| switch (constType) { |
| |
| case SchemaObject.ConstraintTypes.CHECK : |
| return rangeVariable.usedColumns[colIndex] && ArrayUtil |
| .countTrueElements(rangeVariable.usedColumns) == 1; |
| |
| case SchemaObject.ConstraintTypes.PRIMARY_KEY : |
| case SchemaObject.ConstraintTypes.UNIQUE : |
| return core.mainCols.length == 1 |
| && core.mainCols[0] == colIndex; |
| |
| case SchemaObject.ConstraintTypes.MAIN : |
| return core.mainCols.length == 1 |
| && core.mainCols[0] == colIndex |
| && core.mainTable == core.refTable; |
| |
| case SchemaObject.ConstraintTypes.FOREIGN_KEY : |
| return core.refCols.length == 1 && core.refCols[0] == colIndex |
| && core.mainTable == core.refTable; |
| |
| default : |
| throw Error.runtimeError(ErrorCode.U_S0500, "Constraint"); |
| } |
| } |
| |
| boolean hasColumnPlus(int colIndex) { |
| |
| switch (constType) { |
| |
| case SchemaObject.ConstraintTypes.CHECK : |
| return rangeVariable.usedColumns[colIndex] && ArrayUtil |
| .countTrueElements(rangeVariable.usedColumns) > 1; |
| |
| case SchemaObject.ConstraintTypes.PRIMARY_KEY : |
| case SchemaObject.ConstraintTypes.UNIQUE : |
| return core.mainCols.length != 1 |
| && ArrayUtil.find(core.mainCols, colIndex) != -1; |
| |
| case SchemaObject.ConstraintTypes.MAIN : |
| return ArrayUtil.find(core.mainCols, colIndex) != -1 |
| && (core.mainCols.length != 1 |
| || core.mainTable != core.refTable); |
| |
| case SchemaObject.ConstraintTypes.FOREIGN_KEY : |
| return ArrayUtil.find(core.refCols, colIndex) != -1 |
| && (core.mainCols.length != 1 |
| || core.mainTable == core.refTable); |
| |
| default : |
| throw Error.runtimeError(ErrorCode.U_S0500, "Constraint"); |
| } |
| } |
| |
| boolean hasColumn(int colIndex) { |
| |
| switch (constType) { |
| |
| case SchemaObject.ConstraintTypes.CHECK : |
| return rangeVariable.usedColumns[colIndex]; |
| |
| case SchemaObject.ConstraintTypes.PRIMARY_KEY : |
| case SchemaObject.ConstraintTypes.UNIQUE : |
| case SchemaObject.ConstraintTypes.MAIN : |
| return ArrayUtil.find(core.mainCols, colIndex) != -1; |
| |
| case SchemaObject.ConstraintTypes.FOREIGN_KEY : |
| return ArrayUtil.find(core.refCols, colIndex) != -1; |
| |
| default : |
| throw Error.runtimeError(ErrorCode.U_S0500, "Constraint"); |
| } |
| } |
| |
| // fredt@users 20020225 - patch 1.7.0 by fredt - duplicate constraints |
| |
| /** |
| * Compares this with another constraint column set. This is used only for |
| * UNIQUE constraints. |
| */ |
| boolean isUniqueWithColumns(int[] cols) { |
| |
| if (constType != SchemaObject.ConstraintTypes.UNIQUE |
| || core.mainCols.length != cols.length) { |
| return false; |
| } |
| |
| return ArrayUtil.haveEqualSets(core.mainCols, cols, cols.length); |
| } |
| |
| /** |
| * Compares this with another constraint column set. This implementation |
| * only checks FOREIGN KEY constraints. |
| */ |
| boolean isEquivalent(Table mainTable, int[] mainCols, Table refTable, |
| int[] refCols) { |
| |
| if (constType != SchemaObject.ConstraintTypes.MAIN |
| && constType != SchemaObject.ConstraintTypes.FOREIGN_KEY) { |
| return false; |
| } |
| |
| if (mainTable != core.mainTable || refTable != core.refTable) { |
| return false; |
| } |
| |
| return ArrayUtil.areEqualSets(core.mainCols, mainCols) |
| && ArrayUtil.areEqualSets(core.refCols, refCols); |
| } |
| |
| /** |
| * Used to update constrains to reflect structural changes in a table. Prior |
| * checks must ensure that this method does not throw. |
| * |
| * @param session Session |
| * @param oldTable reference to the old version of the table |
| * @param newTable 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 |
| * @ |
| */ |
| void updateTable(Session session, Table oldTable, Table newTable, |
| int colIndex, int adjust) { |
| |
| if (oldTable == core.mainTable) { |
| core.mainTable = newTable; |
| |
| if (core.mainIndex != null) { |
| core.mainIndex = |
| core.mainTable.getIndex(core.mainIndex.getName().name); |
| core.mainCols = ArrayUtil.toAdjustedColumnArray(core.mainCols, |
| colIndex, adjust); |
| |
| core.mainIndex.setTable(newTable); |
| } |
| } |
| |
| if (oldTable == core.refTable) { |
| core.refTable = newTable; |
| |
| if (core.refIndex != null) { |
| core.refIndex = |
| core.refTable.getIndex(core.refIndex.getName().name); |
| core.refCols = ArrayUtil.toAdjustedColumnArray(core.refCols, |
| colIndex, adjust); |
| |
| core.refIndex.setTable(newTable); |
| } |
| } |
| |
| // CHECK |
| if (constType == SchemaObject.ConstraintTypes.CHECK) { |
| recompile(session, newTable); |
| } |
| } |
| |
| /** |
| * Checks for foreign key or check constraint violation when |
| * inserting a row into the child table. |
| */ |
| void checkInsert(Session session, Table table, Object[] data, |
| boolean isNew) { |
| |
| switch (constType) { |
| |
| case SchemaObject.ConstraintTypes.CHECK : |
| if (!isNotNull) { |
| checkCheckConstraint(session, table, data); |
| } |
| |
| return; |
| |
| case SchemaObject.ConstraintTypes.FOREIGN_KEY : |
| PersistentStore store = |
| session.sessionData.getRowStore(core.mainTable); |
| |
| if (ArrayUtil.hasNull(data, core.refCols)) { |
| if (core.matchType == OpTypes.MATCH_SIMPLE) { |
| return; |
| } |
| |
| if (core.refCols.length == 1) { |
| return; |
| } |
| |
| if (ArrayUtil.hasAllNull(data, core.refCols)) { |
| return; |
| } |
| |
| // core.matchType == OpTypes.MATCH_FULL |
| } else if (core.mainIndex.existsParent(session, store, data, |
| core.refCols)) { |
| return; |
| } |
| |
| throw getException(data); |
| } |
| } |
| |
| /* |
| * Tests a row against this CHECK constraint. |
| */ |
| void checkCheckConstraint(Session session, Table table, Object[] data) { |
| |
| /* |
| if (session.compiledStatementExecutor.rangeIterators[1] == null) { |
| session.compiledStatementExecutor.rangeIterators[1] = |
| rangeVariable.getIterator(session); |
| } |
| */ |
| RangeIteratorBase it = |
| session.sessionContext.getCheckIterator(rangeVariable); |
| |
| it.currentData = data; |
| |
| boolean nomatch = Boolean.FALSE.equals(check.getValue(session)); |
| |
| it.currentData = null; |
| |
| if (nomatch) { |
| String[] info = new String[] { |
| name.name, table.getName().name |
| }; |
| |
| throw Error.error(null, ErrorCode.X_23513, ErrorCode.CONSTRAINT, |
| info); |
| } |
| } |
| |
| void checkCheckConstraint(Session session, Table table, Object data) { |
| |
| session.sessionData.currentValue = data; |
| |
| boolean nomatch = Boolean.FALSE.equals(check.getValue(session)); |
| |
| session.sessionData.currentValue = null; |
| |
| if (nomatch) { |
| if (table == null) { |
| throw Error.error(ErrorCode.X_23513, name.name); |
| } else { |
| String[] info = new String[] { |
| name.name, table.getName().name |
| }; |
| |
| throw Error.error(null, ErrorCode.X_23513, |
| ErrorCode.CONSTRAINT, info); |
| } |
| } |
| } |
| |
| public HsqlException getException(Object[] data) { |
| |
| switch (this.constType) { |
| |
| case SchemaObject.ConstraintTypes.CHECK : { |
| String[] info = new String[]{ name.name }; |
| |
| return Error.error(null, ErrorCode.X_23513, |
| ErrorCode.CONSTRAINT, info); |
| } |
| case SchemaObject.ConstraintTypes.FOREIGN_KEY : { |
| StringBuffer sb = new StringBuffer(); |
| |
| for (int i = 0; i < core.refCols.length; i++) { |
| Object o = data[core.refCols[i]]; |
| |
| sb.append(core.refTable.getColumnTypes()[core.refCols[i]] |
| .convertToString(o)); |
| sb.append(','); |
| } |
| |
| String[] info = new String[] { |
| getName().statementName, |
| core.refTable.getName().statementName, sb.toString() |
| }; |
| |
| return Error.error(null, ErrorCode.X_23503, |
| ErrorCode.CONSTRAINT, info); |
| } |
| case SchemaObject.ConstraintTypes.PRIMARY_KEY : |
| case SchemaObject.ConstraintTypes.UNIQUE : { |
| StringBuffer sb = new StringBuffer(); |
| |
| for (int i = 0; i < core.mainCols.length; i++) { |
| Object o = data[core.mainCols[i]]; |
| |
| sb.append(core.mainTable.colTypes[core.mainCols[i]] |
| .convertToString(o)); |
| sb.append(','); |
| } |
| |
| return Error.error(null, ErrorCode.X_23505, |
| ErrorCode.CONSTRAINT, new String[] { |
| getName().statementName, |
| core.mainTable.getName().statementName, sb.toString() |
| }); |
| } |
| default : |
| throw Error.runtimeError(ErrorCode.U_S0500, "Constraint"); |
| } |
| } |
| |
| // 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 specified for 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. If no |
| * row is found, null is returned. (fredt@users) |
| * |
| * @param session Session |
| * @param row array of objects for a database row |
| * @param delete should we allow 'ON DELETE CASCADE' or 'ON UPDATE CASCADE' |
| * @return iterator |
| * @ |
| */ |
| RowIterator findFkRef(Session session, Object[] row) { |
| |
| if (row == null || ArrayUtil.hasNull(row, core.mainCols)) { |
| return core.refIndex.emptyIterator(); |
| } |
| |
| PersistentStore store = session.sessionData.getRowStore(core.refTable); |
| |
| return core.refIndex.findFirstRow(session, store, row, core.mainCols); |
| } |
| |
| /** |
| * 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. |
| */ |
| void checkReferencedRows(Session session, Table table) { |
| |
| RowIterator it = table.rowIterator(session); |
| |
| while (true) { |
| Row row = it.getNextRow(); |
| |
| if (row == null) { |
| break; |
| } |
| |
| Object[] rowData = row.getData(); |
| |
| checkInsert(session, table, rowData, false); |
| } |
| } |
| |
| public Expression getCheckExpression() { |
| return check; |
| } |
| |
| public OrderedHashSet getCheckColumnExpressions() { |
| |
| OrderedHashSet set = new OrderedHashSet(); |
| |
| check.collectAllExpressions(set, Expression.columnExpressionSet, |
| Expression.emptyExpressionSet); |
| |
| return set; |
| } |
| |
| void recompile(Session session, Table newTable) { |
| |
| String ddl = check.getSQL(); |
| Scanner scanner = new Scanner(ddl); |
| ParserDQL parser = new ParserDQL(session, scanner); |
| |
| parser.read(); |
| |
| parser.isCheckOrTriggerCondition = true; |
| |
| Expression condition = parser.XreadBooleanValueExpression(); |
| |
| check = condition; |
| |
| // this workaround is here to stop LIKE optimisation (for proper scripting) |
| QuerySpecification s = Expression.getCheckSelect(session, newTable, |
| check); |
| |
| rangeVariable = s.rangeVariables[0]; |
| |
| rangeVariable.setForCheckConstraint(); |
| } |
| |
| void prepareCheckConstraint(Session session, Table table, |
| boolean checkValues) { |
| |
| // to ensure no subselects etc. are in condition |
| check.checkValidCheckConstraint(); |
| |
| if (table == null) { |
| check.resolveTypes(session, null); |
| } else { |
| QuerySpecification s = Expression.getCheckSelect(session, table, |
| check); |
| Result r = s.getResult(session, 1); |
| |
| if (r.getNavigator().getSize() != 0) { |
| String[] info = new String[] { |
| table.getName().name, "" |
| }; |
| |
| throw Error.error(null, ErrorCode.X_23513, |
| ErrorCode.CONSTRAINT, info); |
| } |
| |
| rangeVariable = s.rangeVariables[0]; |
| |
| // removes reference to the Index object in range variable |
| rangeVariable.setForCheckConstraint(); |
| } |
| |
| if (check.getType() == OpTypes.NOT |
| && check.getLeftNode().getType() == OpTypes.IS_NULL |
| && check.getLeftNode().getLeftNode().getType() |
| == OpTypes.COLUMN) { |
| notNullColumnIndex = |
| check.getLeftNode().getLeftNode().getColumnIndex(); |
| isNotNull = true; |
| } |
| } |
| } |