0001: /* Copyright (c) 1995-2000, The Hypersonic SQL Group.
0002: * All rights reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * Redistributions of source code must retain the above copyright notice, this
0008: * list of conditions and the following disclaimer.
0009: *
0010: * Redistributions in binary form must reproduce the above copyright notice,
0011: * this list of conditions and the following disclaimer in the documentation
0012: * and/or other materials provided with the distribution.
0013: *
0014: * Neither the name of the Hypersonic SQL Group nor the names of its
0015: * contributors may be used to endorse or promote products derived from this
0016: * software without specific prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0021: * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
0022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0029: *
0030: * This software consists of voluntary contributions made by many individuals
0031: * on behalf of the Hypersonic SQL Group.
0032: *
0033: *
0034: * For work added by the HSQL Development Group:
0035: *
0036: * Copyright (c) 2001-2005, The HSQL Development Group
0037: * All rights reserved.
0038: *
0039: * Redistribution and use in source and binary forms, with or without
0040: * modification, are permitted provided that the following conditions are met:
0041: *
0042: * Redistributions of source code must retain the above copyright notice, this
0043: * list of conditions and the following disclaimer.
0044: *
0045: * Redistributions in binary form must reproduce the above copyright notice,
0046: * this list of conditions and the following disclaimer in the documentation
0047: * and/or other materials provided with the distribution.
0048: *
0049: * Neither the name of the HSQL Development Group nor the names of its
0050: * contributors may be used to endorse or promote products derived from this
0051: * software without specific prior written permission.
0052: *
0053: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0054: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0055: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0056: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
0057: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0058: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0059: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0060: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0061: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0062: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0063: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0064: */
0065:
0066: package org.hsqldb;
0067:
0068: import java.io.IOException;
0069:
0070: import org.hsqldb.HsqlNameManager.HsqlName;
0071: import org.hsqldb.index.RowIterator;
0072: import org.hsqldb.lib.ArrayUtil;
0073: import org.hsqldb.lib.HashMappedList;
0074: import org.hsqldb.lib.HashSet;
0075: import org.hsqldb.lib.HsqlArrayList;
0076: import org.hsqldb.lib.Iterator;
0077: import org.hsqldb.lib.StringUtil;
0078: import org.hsqldb.persist.CachedObject;
0079: import org.hsqldb.persist.DataFileCache;
0080: import org.hsqldb.persist.PersistentStore;
0081: import org.hsqldb.rowio.RowInputInterface;
0082: import org.hsqldb.store.ValuePool;
0083:
0084: // fredt@users 20020130 - patch 491987 by jimbag@users - made optional
0085: // fredt@users 20020405 - patch 1.7.0 by fredt - quoted identifiers
0086: // for sql standard quoted identifiers for column and table names and aliases
0087: // applied to different places
0088: // fredt@users 20020225 - patch 1.7.0 - restructuring
0089: // some methods moved from Database.java, some rewritten
0090: // changes to several methods
0091: // fredt@users 20020225 - patch 1.7.0 - ON DELETE CASCADE
0092: // fredt@users 20020225 - patch 1.7.0 - named constraints
0093: // boucherb@users 20020225 - patch 1.7.0 - multi-column primary keys
0094: // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
0095: // tony_lai@users 20020820 - patch 595099 - user defined PK name
0096: // tony_lai@users 20020820 - patch 595172 - drop constraint fix
0097: // kloska@users 20021030 - patch 1.7.2 - ON UPDATE CASCADE | SET NULL | SET DEFAULT
0098: // kloska@users 20021112 - patch 1.7.2 - ON DELETE SET NULL | SET DEFAULT
0099: // fredt@users 20021210 - patch 1.7.2 - better ADD / DROP INDEX for non-CACHED tables
0100: // fredt@users 20030901 - patch 1.7.2 - allow multiple nulls for UNIQUE columns
0101: // fredt@users 20030901 - patch 1.7.2 - reworked IDENTITY support
0102: // achnettest@users 20040130 - patch 878288 - bug fix for new indexes in memory tables by Arne Christensen
0103: // boucherb@users 20040327 - doc 1.7.2 - javadoc updates
0104: // boucherb@users 200404xx - patch 1.7.2 - proper uri for getCatalogName
0105: // fredt@users 20050000 - 1.8.0 updates in several areas
0106: // fredt@users 20050220 - patch 1.8.0 enforcement of DECIMAL precision/scale
0107:
0108: /**
0109: * Holds the data structures and methods for creation of a database table.
0110: *
0111: *
0112: * Extensively rewritten and extended in successive versions of HSQLDB.
0113: *
0114: * @author Thomas Mueller (Hypersonic SQL Group)
0115: * @version 1.8.0
0116: * @since Hypersonic SQL
0117: */
0118: public class Table extends BaseTable {
0119:
0120: // types of table
0121: public static final int SYSTEM_TABLE = 0;
0122: public static final int SYSTEM_SUBQUERY = 1;
0123: public static final int TEMP_TABLE = 2;
0124: public static final int MEMORY_TABLE = 3;
0125: public static final int CACHED_TABLE = 4;
0126: public static final int TEMP_TEXT_TABLE = 5;
0127: public static final int TEXT_TABLE = 6;
0128: public static final int VIEW = 7;
0129:
0130: // boucherb@users - for future implementation of SQL standard INFORMATION_SCHEMA
0131: static final int SYSTEM_VIEW = 8;
0132:
0133: // main properties
0134: // boucherb@users - access changed in support of metadata 1.7.2
0135: public HashMappedList columnList; // columns in table
0136: private int[] primaryKeyCols; // column numbers for primary key
0137: private int[] primaryKeyTypes; // types for primary key
0138: private int[] primaryKeyColsSequence; // {0,1,2,...}
0139: int[] bestRowIdentifierCols; // column set for best index
0140: boolean bestRowIdentifierStrict; // true if it has no nullable column
0141: int[] bestIndexForColumn; // index of the 'best' index for each column
0142: Index bestIndex; // the best index overall - null if there is no user-defined index
0143: int identityColumn; // -1 means no such row
0144: NumberSequence identitySequence; // next value of identity column
0145: NumberSequence rowIdSequence; // next value of optional rowid
0146:
0147: // -----------------------------------------------------------------------
0148: Constraint[] constraintList; // constrainst for the table
0149: HsqlArrayList[] triggerLists; // array of trigger lists
0150: private int[] colTypes; // fredt - types of columns
0151: private int[] colSizes; // fredt - copy of SIZE values for columns
0152: private int[] colScales; // fredt - copy of SCALE values for columns
0153: private boolean[] colNullable; // fredt - modified copy of isNullable() values
0154: private Expression[] colDefaults; // fredt - expressions of DEFAULT values
0155: private int[] defaultColumnMap; // fred - holding 0,1,2,3,...
0156: private boolean hasDefaultValues; //fredt - shortcut for above
0157: boolean sqlEnforceSize; // inherited from the database -
0158:
0159: // properties for subclasses
0160: protected int columnCount; // inclusive the hidden primary key
0161: public Database database;
0162: protected DataFileCache cache;
0163: protected HsqlName tableName; // SQL name
0164: private int tableType;
0165: protected boolean isReadOnly;
0166: protected boolean isTemp;
0167: protected boolean isCached;
0168: protected boolean isText;
0169: protected boolean isMemory;
0170: private boolean isView;
0171: protected boolean isLogged;
0172: protected int indexType; // fredt - type of index used
0173: protected boolean onCommitPreserve; // for temp tables
0174:
0175: //
0176: PersistentStore rowStore;
0177: Index[] indexList; // vIndex(0) is the primary key index
0178:
0179: /**
0180: * Constructor
0181: *
0182: * @param db
0183: * @param name
0184: * @param type
0185: * @param sessionid
0186: * @exception HsqlException
0187: */
0188: Table(Database db, HsqlName name, int type) throws HsqlException {
0189:
0190: database = db;
0191: sqlEnforceSize = db.sqlEnforceStrictSize;
0192: identitySequence = new NumberSequence(null, 0, 1, Types.BIGINT);
0193: rowIdSequence = new NumberSequence(null, 0, 1, Types.BIGINT);
0194:
0195: switch (type) {
0196:
0197: case SYSTEM_SUBQUERY:
0198: isTemp = true;
0199: isMemory = true;
0200: break;
0201:
0202: case SYSTEM_TABLE:
0203: isMemory = true;
0204: break;
0205:
0206: case CACHED_TABLE:
0207: if (DatabaseURL.isFileBasedDatabaseType(db.getType())) {
0208: cache = db.logger.getCache();
0209: isCached = true;
0210: isLogged = !database.isFilesReadOnly();
0211: indexType = Index.DISK_INDEX;
0212: rowStore = new RowStore();
0213:
0214: break;
0215: }
0216:
0217: type = MEMORY_TABLE;
0218: case MEMORY_TABLE:
0219: isMemory = true;
0220: isLogged = !database.isFilesReadOnly();
0221: break;
0222:
0223: case TEMP_TABLE:
0224: isMemory = true;
0225: isTemp = true;
0226: break;
0227:
0228: case TEMP_TEXT_TABLE:
0229: if (!DatabaseURL.isFileBasedDatabaseType(db.getType())) {
0230: throw Trace.error(Trace.DATABASE_IS_MEMORY_ONLY);
0231: }
0232:
0233: isTemp = true;
0234: isText = true;
0235: isReadOnly = true;
0236: indexType = Index.POINTER_INDEX;
0237: rowStore = new RowStore();
0238: break;
0239:
0240: case TEXT_TABLE:
0241: if (!DatabaseURL.isFileBasedDatabaseType(db.getType())) {
0242: throw Trace.error(Trace.DATABASE_IS_MEMORY_ONLY);
0243: }
0244:
0245: isText = true;
0246: indexType = Index.POINTER_INDEX;
0247: rowStore = new RowStore();
0248: break;
0249:
0250: case VIEW:
0251: case SYSTEM_VIEW:
0252: isView = true;
0253: break;
0254: }
0255:
0256: // type may have changed above for CACHED tables
0257: tableType = type;
0258: tableName = name;
0259: primaryKeyCols = null;
0260: primaryKeyTypes = null;
0261: identityColumn = -1;
0262: columnList = new HashMappedList();
0263: indexList = new Index[0];
0264: constraintList = new Constraint[0];
0265: triggerLists = new HsqlArrayList[TriggerDef.NUM_TRIGS];
0266:
0267: // ----------------------------------------------------------------------------
0268: // akede@users - 1.7.2 patch Files readonly
0269: // Changing the mode of the table if necessary
0270: if (db.isFilesReadOnly() && isFileBased()) {
0271: setIsReadOnly(true);
0272: }
0273:
0274: // ----------------------------------------------------------------------------
0275: }
0276:
0277: boolean equals(Session session, String name) {
0278:
0279: /*
0280: if (isTemp && (session != null
0281: && session.getId() != ownerSessionId)) {
0282: return false;
0283: }
0284: */
0285: return (tableName.name.equals(name));
0286: }
0287:
0288: boolean equals(String name) {
0289: return (tableName.name.equals(name));
0290: }
0291:
0292: boolean equals(HsqlName name) {
0293: return (tableName.equals(name));
0294: }
0295:
0296: public final boolean isText() {
0297: return isText;
0298: }
0299:
0300: public final boolean isTemp() {
0301: return isTemp;
0302: }
0303:
0304: public final boolean isReadOnly() {
0305: return isDataReadOnly();
0306: }
0307:
0308: final boolean isView() {
0309: return isView;
0310: }
0311:
0312: final int getIndexType() {
0313: return indexType;
0314: }
0315:
0316: public final int getTableType() {
0317: return tableType;
0318: }
0319:
0320: public boolean isDataReadOnly() {
0321: return isReadOnly;
0322: }
0323:
0324: /**
0325: * sets the isReadOnly flag, and invalidates the database's system tables as needed
0326: */
0327: protected void setIsReadOnly(boolean newReadOnly) {
0328:
0329: isReadOnly = newReadOnly;
0330:
0331: database.setMetaDirty(true);
0332: }
0333:
0334: /**
0335: * Used by INSERT, DELETE, UPDATE operations
0336: */
0337: void checkDataReadOnly() throws HsqlException {
0338:
0339: if (isDataReadOnly()) {
0340: throw Trace.error(Trace.DATA_IS_READONLY);
0341: }
0342: }
0343:
0344: // ----------------------------------------------------------------------------
0345: // akede@users - 1.7.2 patch Files readonly
0346: void setDataReadOnly(boolean value) throws HsqlException {
0347:
0348: // Changing the Read-Only mode for the table is only allowed if
0349: // the database can realize it.
0350: if (!value && database.isFilesReadOnly() && isFileBased()) {
0351: throw Trace.error(Trace.DATA_IS_READONLY);
0352: }
0353:
0354: isReadOnly = value;
0355: }
0356:
0357: /**
0358: * Text or Cached Tables are normally file based
0359: */
0360: boolean isFileBased() {
0361: return isCached || isText;
0362: }
0363:
0364: /**
0365: * For text tables
0366: */
0367: protected void setDataSource(Session s, String source,
0368: boolean isDesc, boolean newFile) throws HsqlException {
0369: throw (Trace.error(Trace.TABLE_NOT_FOUND));
0370: }
0371:
0372: /**
0373: * For text tables
0374: */
0375: protected String getDataSource() {
0376: return null;
0377: }
0378:
0379: /**
0380: * For text tables.
0381: */
0382: protected boolean isDescDataSource() {
0383: return false;
0384: }
0385:
0386: /**
0387: * For text tables.
0388: */
0389: public void setHeader(String header) throws HsqlException {
0390: throw Trace.error(Trace.TEXT_TABLE_HEADER);
0391: }
0392:
0393: /**
0394: * For text tables.
0395: */
0396: public String getHeader() {
0397: return null;
0398: }
0399:
0400: /**
0401: * determines whether the table is actually connected to the underlying data source.
0402: *
0403: * <p>This method is available for text tables only.</p>
0404: *
0405: * @see setDataSource
0406: * @see disconnect
0407: * @see isConnected
0408: */
0409: public boolean isConnected() {
0410: return true;
0411: }
0412:
0413: /**
0414: * connects the table to the underlying data source.
0415: *
0416: * <p>This method is available for text tables only.</p>
0417: *
0418: * @param session
0419: * denotes the current session. Might be <code>null</code>.
0420: *
0421: * @see setDataSource
0422: * @see disconnect
0423: * @see isConnected
0424: */
0425: public void connect(Session session) throws HsqlException {
0426: throw Trace.error(Trace.CANNOT_CONNECT_TABLE);
0427: }
0428:
0429: /**
0430: * disconnects the table from the underlying data source.
0431: *
0432: * <p>This method is available for text tables only.</p>
0433: *
0434: * @param session
0435: * denotes the current session. Might be <code>null</code>.
0436: *
0437: * @see setDataSource
0438: * @see connect
0439: * @see isConnected
0440: */
0441: public void disconnect(Session session) throws HsqlException {
0442: throw Trace.error(Trace.CANNOT_CONNECT_TABLE);
0443: }
0444:
0445: /**
0446: * Adds a constraint.
0447: */
0448: void addConstraint(Constraint c) {
0449:
0450: constraintList = (Constraint[]) ArrayUtil.toAdjustedArray(
0451: constraintList, c, constraintList.length, 1);
0452: }
0453:
0454: /**
0455: * Returns the list of constraints.
0456: */
0457: Constraint[] getConstraints() {
0458: return constraintList;
0459: }
0460:
0461: /**
0462: * Returns the primary constraint.
0463: */
0464: Constraint getPrimaryConstraint() {
0465: return primaryKeyCols.length == 0 ? null : constraintList[0];
0466: }
0467:
0468: /** @todo fredt - this can be improved to ignore order of columns in
0469: * multi-column indexes */
0470:
0471: /**
0472: * Returns the index supporting a constraint with the given column signature.
0473: * Only Unique constraints are considered.
0474: */
0475: Index getUniqueConstraintIndexForColumns(int[] col) {
0476:
0477: if (ArrayUtil.areEqual(getPrimaryIndex().getColumns(), col,
0478: col.length, true)) {
0479: return getPrimaryIndex();
0480: }
0481:
0482: for (int i = 0, size = constraintList.length; i < size; i++) {
0483: Constraint c = constraintList[i];
0484:
0485: if (c.getType() != Constraint.UNIQUE) {
0486: continue;
0487: }
0488:
0489: if (ArrayUtil.areEqual(c.getMainColumns(), col, col.length,
0490: true)) {
0491: return c.getMainIndex();
0492: }
0493: }
0494:
0495: return null;
0496: }
0497:
0498: /**
0499: * Returns any foreign key constraint equivalent to the column sets
0500: */
0501: Constraint getConstraintForColumns(Table tablemain, int[] colmain,
0502: int[] colref) {
0503:
0504: for (int i = 0, size = constraintList.length; i < size; i++) {
0505: Constraint c = constraintList[i];
0506:
0507: if (c.isEquivalent(tablemain, colmain, this , colref)) {
0508: return c;
0509: }
0510: }
0511:
0512: return null;
0513: }
0514:
0515: /**
0516: * Returns any unique constraint equivalent to the column set
0517: */
0518: Constraint getUniqueConstraintForColumns(int[] cols) {
0519:
0520: for (int i = 0, size = constraintList.length; i < size; i++) {
0521: Constraint c = constraintList[i];
0522:
0523: if (c.isEquivalent(cols, Constraint.UNIQUE)) {
0524: return c;
0525: }
0526: }
0527:
0528: return null;
0529: }
0530:
0531: /**
0532: * Returns any unique Constraint using this index
0533: *
0534: * @param index
0535: * @return
0536: */
0537: Constraint getUniqueOrPKConstraintForIndex(Index index) {
0538:
0539: for (int i = 0, size = constraintList.length; i < size; i++) {
0540: Constraint c = constraintList[i];
0541:
0542: if (c.getMainIndex() == index
0543: && (c.getType() == Constraint.UNIQUE || c.getType() == Constraint.PRIMARY_KEY)) {
0544: return c;
0545: }
0546: }
0547:
0548: return null;
0549: }
0550:
0551: /**
0552: * Returns the next constraint of a given type
0553: *
0554: * @param from
0555: * @param type
0556: * @return
0557: */
0558: int getNextConstraintIndex(int from, int type) {
0559:
0560: for (int i = from, size = constraintList.length; i < size; i++) {
0561: Constraint c = constraintList[i];
0562:
0563: if (c.getType() == type) {
0564: return i;
0565: }
0566: }
0567:
0568: return -1;
0569: }
0570:
0571: // fredt@users 20020220 - patch 475199 - duplicate column
0572:
0573: /**
0574: * Performs the table level checks and adds a column to the table at the
0575: * DDL level. Only used at table creation, not at alter column.
0576: */
0577: void addColumn(Column column) throws HsqlException {
0578:
0579: if (findColumn(column.columnName.name) >= 0) {
0580: throw Trace.error(Trace.COLUMN_ALREADY_EXISTS,
0581: column.columnName.name);
0582: }
0583:
0584: if (column.isIdentity()) {
0585: Trace.check(column.getType() == Types.INTEGER
0586: || column.getType() == Types.BIGINT,
0587: Trace.WRONG_DATA_TYPE, column.columnName.name);
0588: Trace.check(identityColumn == -1, Trace.SECOND_PRIMARY_KEY,
0589: column.columnName.name);
0590:
0591: identityColumn = columnCount;
0592: }
0593:
0594: if (primaryKeyCols != null) {
0595: Trace.doAssert(false, "Table.addColumn");
0596: }
0597:
0598: columnList.add(column.columnName.name, column);
0599:
0600: columnCount++;
0601: }
0602:
0603: /**
0604: * Add a set of columns based on a ResultMetaData
0605: */
0606: void addColumns(Result.ResultMetaData metadata, int count)
0607: throws HsqlException {
0608:
0609: for (int i = 0; i < count; i++) {
0610: Column column = new Column(database.nameManager
0611: .newHsqlName(metadata.colLabels[i],
0612: metadata.isLabelQuoted[i]), true,
0613: metadata.colTypes[i], metadata.colSizes[i],
0614: metadata.colScales[i], false, null);
0615:
0616: addColumn(column);
0617: }
0618: }
0619:
0620: /**
0621: * Adds a set of columns based on a compiled Select
0622: */
0623: void addColumns(Select select) throws HsqlException {
0624:
0625: int colCount = select.iResultLen;
0626:
0627: for (int i = 0; i < colCount; i++) {
0628: Expression e = select.exprColumns[i];
0629: Column column = new Column(database.nameManager
0630: .newHsqlName(e.getAlias(), e.isAliasQuoted()),
0631: true, e.getDataType(), e.getColumnSize(), e
0632: .getColumnScale(), false, null);
0633:
0634: addColumn(column);
0635: }
0636: }
0637:
0638: /**
0639: * Returns the HsqlName object fo the table
0640: */
0641: public HsqlName getName() {
0642: return tableName;
0643: }
0644:
0645: public int getId() {
0646: return tableName.hashCode();
0647: }
0648:
0649: /**
0650: * Changes table name. Used by 'alter table rename to'.
0651: * Essential to use the existing HsqlName as this is is referenced by
0652: * intances of Constraint etc.
0653: */
0654: void rename(Session session, String newname, boolean isquoted)
0655: throws HsqlException {
0656:
0657: String oldname = tableName.name;
0658:
0659: tableName.rename(newname, isquoted);
0660:
0661: renameTableInCheckConstraints(session, oldname, newname);
0662: }
0663:
0664: /**
0665: * Returns total column counts, including hidden ones.
0666: */
0667: int getInternalColumnCount() {
0668: return columnCount;
0669: }
0670:
0671: /**
0672: * returns a basic duplicate of the table without the data structures.
0673: */
0674: protected Table duplicate() throws HsqlException {
0675:
0676: Table t = new Table(database, tableName, tableType);
0677:
0678: t.onCommitPreserve = onCommitPreserve;
0679:
0680: return t;
0681: }
0682:
0683: /**
0684: * Match two columns arrays for length and type of columns
0685: *
0686: * @param col column array from this Table
0687: * @param other the other Table object
0688: * @param othercol column array from the other Table
0689: * @throws HsqlException if there is a mismatch
0690: */
0691: void checkColumnsMatch(int[] col, Table other, int[] othercol)
0692: throws HsqlException {
0693:
0694: if (col.length != othercol.length) {
0695: throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
0696: }
0697:
0698: for (int i = 0; i < col.length; i++) {
0699:
0700: // integrity check - should not throw in normal operation
0701: if (col[i] >= columnCount
0702: || othercol[i] >= other.columnCount) {
0703: throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
0704: }
0705:
0706: if (getColumn(col[i]).getType() != other.getColumn(
0707: othercol[i]).getType()) {
0708: throw Trace.error(Trace.COLUMN_TYPE_MISMATCH);
0709: }
0710: }
0711: }
0712:
0713: // fredt@users 20020405 - patch 1.7.0 by fredt - DROP and CREATE INDEX bug
0714:
0715: /**
0716: * Constraints that need removing are removed outside this method.<br>
0717: * removeIndex is the index of an index to be removed, in which case
0718: * no change is made to columns <br>
0719: * When withoutindex is null, adjust {-1 | 0 | +1} indicates if a
0720: * column is {removed | replaced | added}
0721: *
0722: */
0723: Table moveDefinition(int[] removeIndex, Column newColumn,
0724: int colIndex, int adjust) throws HsqlException {
0725:
0726: Table tn = duplicate();
0727:
0728: // loop beyond the end in order to be able to add a column to the end
0729: // of the list
0730: for (int i = 0; i < columnCount + 1; i++) {
0731: if (i == colIndex) {
0732: if (adjust == 0) {
0733: if (newColumn != null) {
0734: tn.addColumn(newColumn);
0735:
0736: continue;
0737: }
0738: } else if (adjust > 0) {
0739: tn.addColumn(newColumn);
0740: } else if (adjust < 0) {
0741: continue;
0742: }
0743: }
0744:
0745: if (i == columnCount) {
0746: break;
0747: }
0748:
0749: tn.addColumn(getColumn(i));
0750: }
0751:
0752: // treat it the same as new table creation and
0753: int[] primarykey = primaryKeyCols.length == 0 ? null
0754: : primaryKeyCols;
0755:
0756: if (primarykey != null) {
0757: int[] newpk = ArrayUtil.toAdjustedColumnArray(primarykey,
0758: colIndex, adjust);
0759:
0760: if (primarykey.length != newpk.length) {
0761: throw Trace.error(Trace.DROP_PRIMARY_KEY);
0762: } else {
0763: primarykey = newpk;
0764: }
0765: }
0766:
0767: tn.createPrimaryKey(getIndex(0).getName(), primarykey, false);
0768:
0769: tn.constraintList = constraintList;
0770:
0771: Index idx = null;
0772:
0773: if (removeIndex != null) {
0774: idx = getIndex(removeIndex, colIndex);
0775: }
0776:
0777: if (idx != null) {
0778: if (idx.isConstraint()) {
0779: throw Trace.error(Trace.COLUMN_IS_IN_CONSTRAINT);
0780: } else {
0781: throw Trace.error(Trace.COLUMN_IS_IN_INDEX);
0782: }
0783: }
0784:
0785: for (int i = 1; i < indexList.length; i++) {
0786: if (removeIndex != null
0787: && ArrayUtil.find(removeIndex, i) != -1) {
0788: continue;
0789: }
0790:
0791: tn.createAdjustedIndex(indexList[i], colIndex, adjust);
0792: }
0793:
0794: tn.triggerLists = triggerLists;
0795:
0796: return tn;
0797: }
0798:
0799: Index getIndex(int[] exclude, int colIndex) {
0800:
0801: for (int i = 1; i < indexList.length; i++) {
0802: if (exclude != null && ArrayUtil.find(exclude, i) != -1) {
0803: continue;
0804: }
0805:
0806: Index idx = indexList[i];
0807: int[] cols = idx.getColumns();
0808:
0809: if (ArrayUtil.find(cols, colIndex) != -1) {
0810: return idx;
0811: }
0812: }
0813:
0814: return null;
0815: }
0816:
0817: private void copyIndexes(Table tn, int removeIndex, int colIndex,
0818: int adjust) throws HsqlException {
0819:
0820: for (int i = 1; i < getIndexCount(); i++) {
0821: Index idx = indexList[i];
0822:
0823: if (removeIndex == i) {
0824: continue;
0825: }
0826:
0827: Index newidx = tn
0828: .createAdjustedIndex(idx, colIndex, adjust);
0829:
0830: if (newidx == null) {
0831:
0832: // column to remove is part of an index
0833: throw Trace.error(Trace.COLUMN_IS_IN_INDEX);
0834: }
0835: }
0836: }
0837:
0838: /**
0839: * cols == null means drop
0840: */
0841: Table moveDefinitionPK(int[] pkCols, boolean withIdentity)
0842: throws HsqlException {
0843:
0844: // some checks
0845: if ((hasPrimaryKey() && pkCols != null)
0846: || (!hasPrimaryKey() && pkCols == null)) {
0847: throw Trace.error(Trace.DROP_PRIMARY_KEY);
0848: }
0849:
0850: Table tn = duplicate();
0851:
0852: for (int i = 0; i < columnCount; i++) {
0853: tn.addColumn(getColumn(i).duplicate(withIdentity));
0854: }
0855:
0856: tn.createPrimaryKey(getIndex(0).getName(), pkCols, true);
0857:
0858: tn.constraintList = constraintList;
0859:
0860: for (int i = 1; i < getIndexCount(); i++) {
0861: Index idx = getIndex(i);
0862:
0863: tn.createAdjustedIndex(idx, -1, 0);
0864: }
0865:
0866: tn.triggerLists = triggerLists;
0867:
0868: return tn;
0869: }
0870:
0871: /**
0872: * Updates the constraint and replaces references to the old table with
0873: * the new one, adjusting column index arrays by the given amount.
0874: */
0875: void updateConstraintsTables(Session session, Table old,
0876: int colindex, int adjust) throws HsqlException {
0877:
0878: for (int i = 0, size = constraintList.length; i < size; i++) {
0879: Constraint c = constraintList[i];
0880:
0881: c.replaceTable(old, this , colindex, adjust);
0882:
0883: if (c.constType == Constraint.CHECK) {
0884: recompileCheckConstraint(session, c);
0885: }
0886: }
0887: }
0888:
0889: private void recompileCheckConstraints(Session session)
0890: throws HsqlException {
0891:
0892: for (int i = 0, size = constraintList.length; i < size; i++) {
0893: Constraint c = constraintList[i];
0894:
0895: if (c.constType == Constraint.CHECK) {
0896: recompileCheckConstraint(session, c);
0897: }
0898: }
0899: }
0900:
0901: /**
0902: * Used after adding columns or indexes to the table.
0903: */
0904: private void recompileCheckConstraint(Session session, Constraint c)
0905: throws HsqlException {
0906:
0907: String ddl = c.core.check.getDDL();
0908: Tokenizer tokenizer = new Tokenizer(ddl);
0909: Parser parser = new Parser(session, database, tokenizer);
0910: Expression condition = parser.parseExpression();
0911:
0912: c.core.check = condition;
0913:
0914: // this workaround is here to stop LIKE optimisation (for proper scripting)
0915: condition.setLikeOptimised();
0916:
0917: Select s = Expression.getCheckSelect(session, this , condition);
0918:
0919: c.core.checkFilter = s.tFilter[0];
0920:
0921: c.core.checkFilter.setAsCheckFilter();
0922:
0923: c.core.mainTable = this ;
0924: }
0925:
0926: /**
0927: * Used for drop column.
0928: */
0929: void checkColumnInCheckConstraint(String colname)
0930: throws HsqlException {
0931:
0932: for (int i = 0, size = constraintList.length; i < size; i++) {
0933: Constraint c = constraintList[i];
0934:
0935: if (c.constType == Constraint.CHECK) {
0936: if (c.hasColumn(this , colname)) {
0937: throw Trace.error(Trace.COLUMN_IS_REFERENCED, c
0938: .getName());
0939: }
0940: }
0941: }
0942: }
0943:
0944: /**
0945: * Used for retype column. Checks whether column is in an FK or is
0946: * referenced by a FK
0947: * @param colIndex index
0948: */
0949: void checkColumnInFKConstraint(int colIndex) throws HsqlException {
0950:
0951: for (int i = 0, size = constraintList.length; i < size; i++) {
0952: Constraint c = constraintList[i];
0953:
0954: if (c.hasColumn(colIndex)
0955: && (c.getType() == Constraint.MAIN || c.getType() == Constraint.FOREIGN_KEY)) {
0956: throw Trace.error(Trace.COLUMN_IS_REFERENCED, c
0957: .getName().name);
0958: }
0959: }
0960: }
0961:
0962: /**
0963: * Used for column defaults and nullability. Checks whether column is in an FK.
0964: * @param colIndex index of column
0965: * @param refOnly only check FK columns, not referenced columns
0966: */
0967: void checkColumnInFKConstraint(int colIndex, int actionType)
0968: throws HsqlException {
0969:
0970: for (int i = 0, size = constraintList.length; i < size; i++) {
0971: Constraint c = constraintList[i];
0972:
0973: if (c.hasColumn(colIndex)) {
0974: if (c.getType() == Constraint.FOREIGN_KEY
0975: && (actionType == c.getUpdateAction() || actionType == c
0976: .getDeleteAction())) {
0977: throw Trace.error(Trace.COLUMN_IS_REFERENCED, c
0978: .getName().name);
0979: }
0980: }
0981: }
0982: }
0983:
0984: /**
0985: * Used for rename column.
0986: */
0987: private void renameColumnInCheckConstraints(String oldname,
0988: String newname, boolean isquoted) throws HsqlException {
0989:
0990: for (int i = 0, size = constraintList.length; i < size; i++) {
0991: Constraint c = constraintList[i];
0992:
0993: if (c.constType == Constraint.CHECK) {
0994: Expression.Collector coll = new Expression.Collector();
0995:
0996: coll.addAll(c.core.check, Expression.COLUMN);
0997:
0998: Iterator it = coll.iterator();
0999:
1000: for (; it.hasNext();) {
1001: Expression e = (Expression) it.next();
1002:
1003: if (e.getColumnName() == oldname) {
1004: e.setColumnName(newname, isquoted);
1005: }
1006: }
1007: }
1008: }
1009: }
1010:
1011: /**
1012: * Used for drop column.
1013: */
1014: private void renameTableInCheckConstraints(Session session,
1015: String oldname, String newname) throws HsqlException {
1016:
1017: for (int i = 0, size = constraintList.length; i < size; i++) {
1018: Constraint c = constraintList[i];
1019:
1020: if (c.constType == Constraint.CHECK) {
1021: Expression.Collector coll = new Expression.Collector();
1022:
1023: coll.addAll(c.core.check, Expression.COLUMN);
1024:
1025: Iterator it = coll.iterator();
1026:
1027: for (; it.hasNext();) {
1028: Expression e = (Expression) it.next();
1029:
1030: if (e.getTableName() == oldname) {
1031: e.setTableName(newname);
1032: }
1033: }
1034: }
1035: }
1036:
1037: recompileCheckConstraints(session);
1038: }
1039:
1040: /**
1041: * Returns the count of user defined columns.
1042: */
1043: public int getColumnCount() {
1044: return columnCount;
1045: }
1046:
1047: /**
1048: * Returns the count of indexes on this table.
1049: */
1050: public int getIndexCount() {
1051: return indexList.length;
1052: }
1053:
1054: /**
1055: * Returns the identity column or null.
1056: */
1057: int getIdentityColumn() {
1058: return identityColumn;
1059: }
1060:
1061: /**
1062: * Returns the index of given column name or throws if not found
1063: */
1064: int getColumnNr(String c) throws HsqlException {
1065:
1066: int i = findColumn(c);
1067:
1068: if (i == -1) {
1069: throw Trace.error(Trace.COLUMN_NOT_FOUND, c);
1070: }
1071:
1072: return i;
1073: }
1074:
1075: /**
1076: * Returns the index of given column name or -1 if not found.
1077: */
1078: int findColumn(String c) {
1079:
1080: int index = columnList.getIndex(c);
1081:
1082: return index;
1083: }
1084:
1085: /**
1086: * Returns the primary index (user defined or system defined)
1087: */
1088: public Index getPrimaryIndex() {
1089: return getIndex(0);
1090: }
1091:
1092: /**
1093: * Return the user defined primary key column indexes, or empty array for system PK's.
1094: */
1095: public int[] getPrimaryKey() {
1096: return primaryKeyCols;
1097: }
1098:
1099: public int[] getPrimaryKeyTypes() {
1100: return primaryKeyTypes;
1101: }
1102:
1103: public boolean hasPrimaryKey() {
1104: return !(primaryKeyCols.length == 0);
1105: }
1106:
1107: int[] getBestRowIdentifiers() {
1108: return bestRowIdentifierCols;
1109: }
1110:
1111: boolean isBestRowIdentifiersStrict() {
1112: return bestRowIdentifierStrict;
1113: }
1114:
1115: /**
1116: * This method is called whenever there is a change to table structure and
1117: * serves two porposes: (a) to reset the best set of columns that identify
1118: * the rows of the table (b) to reset the best index that can be used
1119: * to find rows of the table given a column value.
1120: *
1121: * (a) gives most weight to a primary key index, followed by a unique
1122: * address with the lowest count of nullable columns. Otherwise there is
1123: * no best row identifier.
1124: *
1125: * (b) finds for each column an index with a corresponding first column.
1126: * It uses any type of visible index and accepts the first one (it doesn't
1127: * make any difference to performance).
1128: *
1129: * bestIndex is the user defined, primary key, the first unique index, or
1130: * the first non-unique index. NULL if there is no user-defined index.
1131: *
1132: */
1133: void setBestRowIdentifiers() {
1134:
1135: int[] briCols = null;
1136: int briColsCount = 0;
1137: boolean isStrict = false;
1138: int nNullCount = 0;
1139:
1140: // ignore if called prior to completion of primary key construction
1141: if (colNullable == null) {
1142: return;
1143: }
1144:
1145: bestIndex = null;
1146: bestIndexForColumn = new int[columnList.size()];
1147:
1148: ArrayUtil.fillArray(bestIndexForColumn, -1);
1149:
1150: for (int i = 0; i < indexList.length; i++) {
1151: Index index = indexList[i];
1152: int[] cols = index.getColumns();
1153: int colsCount = index.getVisibleColumns();
1154:
1155: if (i == 0) {
1156:
1157: // ignore system primary keys
1158: if (hasPrimaryKey()) {
1159: isStrict = true;
1160: } else {
1161: continue;
1162: }
1163: }
1164:
1165: if (bestIndexForColumn[cols[0]] == -1) {
1166: bestIndexForColumn[cols[0]] = i;
1167: }
1168:
1169: if (!index.isUnique()) {
1170: if (bestIndex == null) {
1171: bestIndex = index;
1172: }
1173:
1174: continue;
1175: }
1176:
1177: int nnullc = 0;
1178:
1179: for (int j = 0; j < colsCount; j++) {
1180: if (!colNullable[cols[j]]) {
1181: nnullc++;
1182: }
1183: }
1184:
1185: if (bestIndex != null) {
1186: bestIndex = index;
1187: }
1188:
1189: if (nnullc == colsCount) {
1190: if (briCols == null || briColsCount != nNullCount
1191: || colsCount < briColsCount) {
1192:
1193: // nothing found before ||
1194: // found but has null columns ||
1195: // found but has more columns than this index
1196: briCols = cols;
1197: briColsCount = colsCount;
1198: nNullCount = colsCount;
1199: isStrict = true;
1200: }
1201:
1202: continue;
1203: } else if (isStrict) {
1204: continue;
1205: } else if (briCols == null || colsCount < briColsCount
1206: || nnullc > nNullCount) {
1207:
1208: // nothing found before ||
1209: // found but has more columns than this index||
1210: // found but has fewer not null columns than this index
1211: briCols = cols;
1212: briColsCount = colsCount;
1213: nNullCount = nnullc;
1214: }
1215: }
1216:
1217: // remove rowID column from bestRowIdentiferCols
1218: bestRowIdentifierCols = briCols == null
1219: || briColsCount == briCols.length ? briCols : ArrayUtil
1220: .arraySlice(briCols, 0, briColsCount);
1221: bestRowIdentifierStrict = isStrict;
1222:
1223: if (hasPrimaryKey()) {
1224: bestIndex = getPrimaryIndex();
1225: }
1226: }
1227:
1228: /**
1229: * Sets the SQL default value for a columm.
1230: */
1231: void setDefaultExpression(int columnIndex, Expression def) {
1232:
1233: Column column = getColumn(columnIndex);
1234:
1235: column.setDefaultExpression(def);
1236:
1237: colDefaults[columnIndex] = column.getDefaultExpression();
1238:
1239: resetDefaultsFlag();
1240: }
1241:
1242: /**
1243: * sets the flag for the presence of any default expression
1244: */
1245: void resetDefaultsFlag() {
1246:
1247: hasDefaultValues = false;
1248:
1249: for (int i = 0; i < columnCount; i++) {
1250: hasDefaultValues = hasDefaultValues
1251: || colDefaults[i] != null;
1252: }
1253: }
1254:
1255: DataFileCache getCache() {
1256: return cache;
1257: }
1258:
1259: /**
1260: * Used in TableFilter to get an index for the column.
1261: * An index is created automatically for system tables or subqueries.
1262: */
1263: Index getIndexForColumn(Session session, int column) {
1264:
1265: int i = bestIndexForColumn[column];
1266:
1267: if (i == -1
1268: && (tableType == Table.SYSTEM_SUBQUERY || tableType == Table.SYSTEM_TABLE)) {
1269: try {
1270: HsqlName indexName = database.nameManager
1271: .newAutoName("IDX");
1272:
1273: createIndex(session, new int[] { column }, indexName,
1274: false, false, false);
1275:
1276: i = bestIndexForColumn[column];
1277: } catch (Exception e) {
1278: }
1279: }
1280:
1281: return i == -1 ? null : getIndex(i);
1282: }
1283:
1284: /**
1285: * Used for TableFilter to get an index for the columns
1286: */
1287: Index getIndexForColumns(boolean[] columnCheck) {
1288:
1289: Index indexChoice = null;
1290: int colCount = 0;
1291:
1292: for (int i = 0; i < indexList.length; i++) {
1293: Index index = indexList[i];
1294: boolean result = ArrayUtil.containsAllTrueElements(
1295: columnCheck, index.colCheck);
1296:
1297: if (result && index.getVisibleColumns() > colCount) {
1298: colCount = index.getVisibleColumns();
1299: indexChoice = index;
1300: }
1301: }
1302:
1303: return indexChoice;
1304: }
1305:
1306: /**
1307: * Finds an existing index for a foreign key column group
1308: */
1309: Index getIndexForColumns(int[] col, boolean unique)
1310: throws HsqlException {
1311:
1312: for (int i = 0, count = getIndexCount(); i < count; i++) {
1313: Index currentindex = getIndex(i);
1314: int[] indexcol = currentindex.getColumns();
1315:
1316: if (ArrayUtil.haveEqualArrays(indexcol, col, col.length)) {
1317: if (!unique || currentindex.isUnique()) {
1318: return currentindex;
1319: }
1320: }
1321: }
1322:
1323: return null;
1324: }
1325:
1326: /**
1327: * Return the list of file pointers to root nodes for this table's
1328: * indexes.
1329: */
1330: public int[] getIndexRootsArray() {
1331:
1332: int[] roots = new int[getIndexCount()];
1333:
1334: for (int i = 0; i < getIndexCount(); i++) {
1335: roots[i] = indexList[i].getRoot();
1336: }
1337:
1338: return roots;
1339: }
1340:
1341: /**
1342: * Returns the string consisting of file pointers to roots of indexes
1343: * plus the next identity value (hidden or user defined). This is used
1344: * with CACHED tables.
1345: */
1346: String getIndexRoots() {
1347:
1348: String roots = StringUtil
1349: .getList(getIndexRootsArray(), " ", "");
1350: StringBuffer s = new StringBuffer(roots);
1351:
1352: s.append(' ');
1353: s.append(identitySequence.peek());
1354:
1355: return s.toString();
1356: }
1357:
1358: /**
1359: * Sets the index roots of a cached/text table to specified file
1360: * pointers. If a
1361: * file pointer is -1 then the particular index root is null. A null index
1362: * root signifies an empty table. Accordingly, all index roots should be
1363: * null or all should be a valid file pointer/reference.
1364: */
1365: public void setIndexRoots(int[] roots) throws HsqlException {
1366:
1367: Trace.check(isCached, Trace.TABLE_NOT_FOUND);
1368:
1369: for (int i = 0; i < getIndexCount(); i++) {
1370: int p = roots[i];
1371: Row r = null;
1372:
1373: if (p != -1) {
1374: r = (CachedRow) rowStore.get(p);
1375: }
1376:
1377: Node f = null;
1378:
1379: if (r != null) {
1380: f = r.getNode(i);
1381: }
1382:
1383: indexList[i].setRoot(null, f);
1384: }
1385: }
1386:
1387: /**
1388: * Sets the index roots and next identity.
1389: */
1390: void setIndexRoots(String s) throws HsqlException {
1391:
1392: // the user may try to set this; this is not only internal problem
1393: Trace.check(isCached, Trace.TABLE_NOT_FOUND);
1394:
1395: Tokenizer t = new Tokenizer(s);
1396: int[] roots = new int[getIndexCount()];
1397:
1398: for (int i = 0; i < getIndexCount(); i++) {
1399: int v = t.getInt();
1400:
1401: roots[i] = v;
1402: }
1403:
1404: setIndexRoots(roots);
1405:
1406: long v = t.getBigint();
1407:
1408: identitySequence.reset(v);
1409: }
1410:
1411: /**
1412: * Shortcut for creating system table PK's.
1413: */
1414: void createPrimaryKey(int[] cols) throws HsqlException {
1415: createPrimaryKey(null, cols, false);
1416: }
1417:
1418: /**
1419: * Shortcut for creating default PK's.
1420: */
1421: void createPrimaryKey() throws HsqlException {
1422: createPrimaryKey(null, null, false);
1423: }
1424:
1425: /**
1426: * Creates a single or multi-column primary key and index. sets the
1427: * colTypes array. Finalises the creation of the table. (fredt@users)
1428: */
1429:
1430: // tony_lai@users 20020820 - patch 595099
1431: void createPrimaryKey(HsqlName indexName, int[] columns,
1432: boolean columnsNotNull) throws HsqlException {
1433:
1434: if (primaryKeyCols != null) {
1435: Trace.doAssert(false, "Table.createPrimaryKey(column)");
1436: }
1437:
1438: if (columns == null) {
1439: columns = new int[0];
1440: } else {
1441: for (int i = 0; i < columns.length; i++) {
1442: if (columnsNotNull) {
1443: getColumn(columns[i]).setNullable(false);
1444: }
1445:
1446: getColumn(columns[i]).setPrimaryKey(true);
1447: }
1448: }
1449:
1450: primaryKeyCols = columns;
1451: colTypes = new int[columnCount];
1452: colDefaults = new Expression[columnCount];
1453: colSizes = new int[columnCount];
1454: colScales = new int[columnCount];
1455: colNullable = new boolean[columnCount];
1456: defaultColumnMap = new int[columnCount];
1457:
1458: for (int i = 0; i < columnCount; i++) {
1459: setColumnTypeVars(i);
1460: }
1461:
1462: primaryKeyTypes = new int[primaryKeyCols.length];
1463:
1464: ArrayUtil.copyColumnValues(colTypes, primaryKeyCols,
1465: primaryKeyTypes);
1466:
1467: primaryKeyColsSequence = new int[primaryKeyCols.length];
1468:
1469: ArrayUtil.fillSequence(primaryKeyColsSequence);
1470: resetDefaultsFlag();
1471:
1472: // tony_lai@users 20020820 - patch 595099
1473: HsqlName name = indexName != null ? indexName
1474: : database.nameManager.newAutoName("IDX");
1475:
1476: createPrimaryIndex(columns, name);
1477: setBestRowIdentifiers();
1478: }
1479:
1480: void setColumnTypeVars(int i) {
1481:
1482: Column column = getColumn(i);
1483:
1484: colTypes[i] = column.getType();
1485: colSizes[i] = column.getSize();
1486: colScales[i] = column.getScale();
1487: colNullable[i] = column.isNullable();
1488: defaultColumnMap[i] = i;
1489:
1490: if (column.isIdentity()) {
1491: identitySequence.reset(column.identityStart,
1492: column.identityIncrement);
1493: }
1494:
1495: colDefaults[i] = column.getDefaultExpression();
1496: }
1497:
1498: HsqlName makeSysPKName() throws HsqlException {
1499: return database.nameManager.newAutoName("PK");
1500: }
1501:
1502: void createPrimaryIndex(int[] pkcols, HsqlName name)
1503: throws HsqlException {
1504:
1505: int[] pkcoltypes = new int[pkcols.length];
1506:
1507: for (int j = 0; j < pkcols.length; j++) {
1508: pkcoltypes[j] = colTypes[pkcols[j]];
1509: }
1510:
1511: Index newindex = new Index(database, name, this , pkcols,
1512: pkcoltypes, true, true, true, false, pkcols,
1513: pkcoltypes, isTemp);
1514:
1515: addIndex(newindex);
1516: }
1517:
1518: /**
1519: * Create new index taking into account removal or addition of a column
1520: * to the table.
1521: */
1522: private Index createAdjustedIndex(Index index, int colindex,
1523: int adjust) throws HsqlException {
1524:
1525: int[] indexcolumns = (int[]) ArrayUtil.resizeArray(index
1526: .getColumns(), index.getVisibleColumns());
1527: int[] colarr = ArrayUtil.toAdjustedColumnArray(indexcolumns,
1528: colindex, adjust);
1529:
1530: // if a column to remove is one of the Index columns
1531: if (colarr.length != index.getVisibleColumns()) {
1532: return null;
1533: }
1534:
1535: return createIndexStructure(colarr, index.getName(), index
1536: .isUnique(), index.isConstraint, index.isForward);
1537: }
1538:
1539: /**
1540: * Create new memory-resident index. For MEMORY and TEXT tables.
1541: */
1542: Index createIndex(Session session, int[] column, HsqlName name,
1543: boolean unique, boolean constraint, boolean forward)
1544: throws HsqlException {
1545:
1546: int newindexNo = createIndexStructureGetNo(column, name,
1547: unique, constraint, forward);
1548: Index newindex = indexList[newindexNo];
1549: Index primaryindex = getPrimaryIndex();
1550: RowIterator it = primaryindex.firstRow(session);
1551: int rowCount = 0;
1552: HsqlException error = null;
1553:
1554: try {
1555: while (it.hasNext()) {
1556: Row row = it.next();
1557: Node backnode = row.getNode(newindexNo - 1);
1558: Node newnode = Node.newNode(row, newindexNo, this );
1559:
1560: newnode.nNext = backnode.nNext;
1561: backnode.nNext = newnode;
1562:
1563: // count before inserting
1564: rowCount++;
1565:
1566: newindex.insert(session, row, newindexNo);
1567: }
1568:
1569: return newindex;
1570: } catch (java.lang.OutOfMemoryError e) {
1571: error = Trace.error(Trace.OUT_OF_MEMORY);
1572: } catch (HsqlException e) {
1573: error = e;
1574: }
1575:
1576: // backtrack on error
1577: // rowCount rows have been modified
1578: it = primaryindex.firstRow(session);
1579:
1580: for (int i = 0; i < rowCount; i++) {
1581: Row row = it.next();
1582: Node backnode = row.getNode(0);
1583: int j = newindexNo;
1584:
1585: while (--j > 0) {
1586: backnode = backnode.nNext;
1587: }
1588:
1589: backnode.nNext = backnode.nNext.nNext;
1590: }
1591:
1592: indexList = (Index[]) ArrayUtil.toAdjustedArray(indexList,
1593: null, newindexNo, -1);
1594:
1595: setBestRowIdentifiers();
1596:
1597: throw error;
1598: }
1599:
1600: /**
1601: * Creates the internal structures for an index.
1602: */
1603: Index createIndexStructure(int[] columns, HsqlName name,
1604: boolean unique, boolean constraint, boolean forward)
1605: throws HsqlException {
1606:
1607: int i = createIndexStructureGetNo(columns, name, unique,
1608: constraint, forward);
1609:
1610: return indexList[i];
1611: }
1612:
1613: int createIndexStructureGetNo(int[] column, HsqlName name,
1614: boolean unique, boolean constraint, boolean forward)
1615: throws HsqlException {
1616:
1617: if (primaryKeyCols == null) {
1618: Trace.doAssert(false, "createIndex");
1619: }
1620:
1621: int s = column.length;
1622: int[] col = new int[s];
1623: int[] type = new int[s];
1624:
1625: for (int j = 0; j < s; j++) {
1626: col[j] = column[j];
1627: type[j] = colTypes[col[j]];
1628: }
1629:
1630: int[] pkcols = getPrimaryKey();
1631: int[] pktypes = getPrimaryKeyTypes();
1632: Index newindex = new Index(database, name, this , col, type,
1633: false, unique, constraint, forward, pkcols, pktypes,
1634: isTemp);
1635: int indexNo = addIndex(newindex);
1636:
1637: setBestRowIdentifiers();
1638:
1639: return indexNo;
1640: }
1641:
1642: private int addIndex(Index index) {
1643:
1644: int i = 0;
1645:
1646: for (; i < indexList.length; i++) {
1647: Index current = indexList[i];
1648: int order = index.getIndexOrderValue()
1649: - current.getIndexOrderValue();
1650:
1651: if (order < 0) {
1652: break;
1653: }
1654: }
1655:
1656: indexList = (Index[]) ArrayUtil.toAdjustedArray(indexList,
1657: index, i, 1);
1658:
1659: return i;
1660: }
1661:
1662: /**
1663: * returns false if the table has to be recreated in order to add / drop
1664: * indexes. Only CACHED tables return false.
1665: */
1666: boolean isIndexingMutable() {
1667: return !isIndexCached();
1668: }
1669:
1670: /**
1671: * Checks for use of a named index in table constraints,
1672: * while ignorring a given set of constraints.
1673: * @throws HsqlException if index is used in a constraint
1674: */
1675: void checkDropIndex(String indexname, HashSet ignore, boolean dropPK)
1676: throws HsqlException {
1677:
1678: Index index = this .getIndex(indexname);
1679:
1680: if (index == null) {
1681: throw Trace.error(Trace.INDEX_NOT_FOUND, indexname);
1682: }
1683:
1684: if (!dropPK && index.equals(getIndex(0))) {
1685: throw Trace.error(Trace.DROP_PRIMARY_KEY, indexname);
1686: }
1687:
1688: for (int i = 0, size = constraintList.length; i < size; i++) {
1689: Constraint c = constraintList[i];
1690:
1691: if (ignore != null && ignore.contains(c)) {
1692: continue;
1693: }
1694:
1695: if (c.isIndexFK(index)) {
1696: throw Trace.error(Trace.DROP_FK_INDEX, indexname);
1697: }
1698:
1699: if (c.isIndexUnique(index)) {
1700: throw Trace.error(Trace.SYSTEM_INDEX, indexname);
1701: }
1702: }
1703:
1704: return;
1705: }
1706:
1707: /**
1708: * Returns true if the table has any rows at all.
1709: */
1710: public boolean isEmpty(Session session) {
1711:
1712: if (getIndexCount() == 0) {
1713: return true;
1714: }
1715:
1716: return getIndex(0).isEmpty(session);
1717: }
1718:
1719: /**
1720: * Returns direct mapping array.
1721: */
1722: int[] getColumnMap() {
1723: return defaultColumnMap;
1724: }
1725:
1726: /**
1727: * Returns empty mapping array.
1728: */
1729: int[] getNewColumnMap() {
1730: return new int[columnCount];
1731: }
1732:
1733: /**
1734: * Returns empty boolean array.
1735: */
1736: boolean[] getNewColumnCheckList() {
1737: return new boolean[columnCount];
1738: }
1739:
1740: /**
1741: * Returns empty Object array for a new row.
1742: */
1743: public Object[] getEmptyRowData() {
1744: return new Object[columnCount];
1745: }
1746:
1747: /**
1748: * Returns array for a new row with SQL DEFAULT value for each column n
1749: * where exists[n] is false. This provides default values only where
1750: * required and avoids evaluating these values where they will be
1751: * overwritten.
1752: */
1753: Object[] getNewRowData(Session session, boolean[] exists)
1754: throws HsqlException {
1755:
1756: Object[] data = new Object[columnCount];
1757: int i;
1758:
1759: if (exists != null && hasDefaultValues) {
1760: for (i = 0; i < columnCount; i++) {
1761: Expression def = colDefaults[i];
1762:
1763: if (exists[i] == false && def != null) {
1764: data[i] = def.getValue(session, colTypes[i]);
1765: }
1766: }
1767: }
1768:
1769: return data;
1770: }
1771:
1772: /**
1773: * Performs Table structure modification and changes to the index nodes
1774: * to remove a given index from a MEMORY or TEXT table. Not for PK index.
1775: *
1776: */
1777: void dropIndex(Session session, String indexname)
1778: throws HsqlException {
1779:
1780: // find the array index for indexname and remove
1781: int todrop = getIndexIndex(indexname);
1782:
1783: indexList = (Index[]) ArrayUtil.toAdjustedArray(indexList,
1784: null, todrop, -1);
1785:
1786: setBestRowIdentifiers();
1787: dropIndexFromRows(session, todrop);
1788: }
1789:
1790: void dropIndexFromRows(Session session, int index)
1791: throws HsqlException {
1792:
1793: RowIterator it = getPrimaryIndex().firstRow(session);
1794:
1795: while (it.hasNext()) {
1796: Row row = it.next();
1797: int i = index - 1;
1798: Node backnode = row.getNode(0);
1799:
1800: while (i-- > 0) {
1801: backnode = backnode.nNext;
1802: }
1803:
1804: backnode.nNext = backnode.nNext.nNext;
1805: }
1806: }
1807:
1808: /**
1809: * Moves the data from table to table.
1810: * The colindex argument is the index of the column that was
1811: * added or removed. The adjust argument is {-1 | 0 | +1}
1812: */
1813: void moveData(Session session, Table from, int colindex, int adjust)
1814: throws HsqlException {
1815:
1816: Object colvalue = null;
1817: Column column = null;
1818:
1819: if (adjust >= 0 && colindex != -1) {
1820: column = getColumn(colindex);
1821: colvalue = column.getDefaultValue(session);
1822: }
1823:
1824: RowIterator it = from.getPrimaryIndex().firstRow(session);
1825:
1826: while (it.hasNext()) {
1827: Row row = it.next();
1828: Object[] o = row.getData();
1829: Object[] data = getEmptyRowData();
1830:
1831: if (adjust == 0 && colindex != -1) {
1832: colvalue = Column.convertObject(session, o[colindex],
1833: column.getType(), column.getSize(), column
1834: .getScale());
1835: }
1836:
1837: ArrayUtil.copyAdjustArray(o, data, colvalue, colindex,
1838: adjust);
1839: setIdentityColumn(session, data);
1840: enforceNullConstraints(data);
1841:
1842: Row newrow = newRow(data);
1843:
1844: indexRow(session, newrow);
1845: }
1846:
1847: from.drop();
1848: }
1849:
1850: /**
1851: * Highest level multiple row insert method. Corresponds to an SQL
1852: * INSERT INTO ... SELECT ... statement.
1853: */
1854: int insert(Session session, Result ins) throws HsqlException {
1855:
1856: Record ni = ins.rRoot;
1857: int count = 0;
1858:
1859: fireAll(session, Trigger.INSERT_BEFORE);
1860:
1861: while (ni != null) {
1862: insertRow(session, ni.data);
1863:
1864: ni = ni.next;
1865:
1866: count++;
1867: }
1868:
1869: fireAll(session, Trigger.INSERT_AFTER);
1870:
1871: return count;
1872: }
1873:
1874: /**
1875: * Highest level method for inserting a single row. Corresponds to an
1876: * SQL INSERT INTO .... VALUES(,,) statement.
1877: * fires triggers.
1878: */
1879: void insert(Session session, Object[] data) throws HsqlException {
1880:
1881: fireAll(session, Trigger.INSERT_BEFORE);
1882: insertRow(session, data);
1883: fireAll(session, Trigger.INSERT_AFTER);
1884: }
1885:
1886: /**
1887: * Mid level method for inserting rows. Performs constraint checks and
1888: * fires row level triggers.
1889: */
1890: private void insertRow(Session session, Object[] data)
1891: throws HsqlException {
1892:
1893: if (triggerLists[Trigger.INSERT_BEFORE_ROW] != null) {
1894: fireAll(session, Trigger.INSERT_BEFORE_ROW, null, data);
1895: }
1896:
1897: setIdentityColumn(session, data);
1898: checkRowDataInsert(session, data);
1899: insertNoCheck(session, data);
1900:
1901: if (triggerLists[Trigger.INSERT_AFTER_ROW] != null) {
1902: fireAll(session, Trigger.INSERT_AFTER_ROW, null, data);
1903: checkRowDataInsert(session, data);
1904: }
1905: }
1906:
1907: /**
1908: * Multi-row insert method. Used for SELECT ... INTO tablename queries.
1909: * These tables are new, empty tables, with no constraints, triggers
1910: * column default values, column size enforcement whatsoever.
1911: *
1912: * Not used for INSERT INTO .... SELECT ... FROM queries
1913: */
1914: void insertIntoTable(Session session, Result result)
1915: throws HsqlException {
1916:
1917: insertResult(session, result);
1918:
1919: if (!isLogged) {
1920: return;
1921: }
1922:
1923: Record r = result.rRoot;
1924:
1925: while (r != null) {
1926: database.logger.writeInsertStatement(session, this , r.data);
1927:
1928: r = r.next;
1929: }
1930: }
1931:
1932: /**
1933: * Low level method for row insert.
1934: * UNIQUE or PRIMARY constraints are enforced by attempting to
1935: * add the row to the indexes.
1936: */
1937: private void insertNoCheck(Session session, Object[] data)
1938: throws HsqlException {
1939:
1940: Row row = newRow(data);
1941:
1942: // this handles the UNIQUE constraints
1943: indexRow(session, row);
1944:
1945: if (session != null) {
1946: session.addInsertAction(this , row);
1947: }
1948:
1949: if (isLogged) {
1950: database.logger.writeInsertStatement(session, this , data);
1951: }
1952: }
1953:
1954: /**
1955: *
1956: */
1957: public void insertNoCheckFromLog(Session session, Object[] data)
1958: throws HsqlException {
1959:
1960: Row r = newRow(data);
1961:
1962: updateIdentityValue(data);
1963: indexRow(session, r);
1964:
1965: if (session != null) {
1966: session.addInsertAction(this , r);
1967: }
1968: }
1969:
1970: /**
1971: * Low level method for restoring deleted rows
1972: */
1973: void insertNoCheckRollback(Session session, Row row, boolean log)
1974: throws HsqlException {
1975:
1976: Row newrow = restoreRow(row);
1977:
1978: // instead of new row, use new routine so that the row does not use
1979: // rowstore.add(), which will allocate new space and different pos
1980: indexRow(session, newrow);
1981:
1982: if (log && isLogged) {
1983: database.logger.writeInsertStatement(session, this , row
1984: .getData());
1985: }
1986: }
1987:
1988: /**
1989: * Used for system table inserts. No checks. No identity
1990: * columns.
1991: */
1992: int insertSys(Result ins) throws HsqlException {
1993:
1994: Record ni = ins.rRoot;
1995: int count = 0;
1996:
1997: while (ni != null) {
1998: insertData(null, ni.data);
1999:
2000: ni = ni.next;
2001:
2002: count++;
2003: }
2004:
2005: return count;
2006: }
2007:
2008: /**
2009: * Used for subquery inserts. No checks. No identity
2010: * columns.
2011: */
2012: int insertResult(Session session, Result ins) throws HsqlException {
2013:
2014: Record ni = ins.rRoot;
2015: int count = 0;
2016:
2017: while (ni != null) {
2018: Object[] newData = (Object[]) ArrayUtil
2019: .resizeArrayIfDifferent(ni.data, columnCount);
2020:
2021: insertData(session, newData);
2022:
2023: ni = ni.next;
2024:
2025: count++;
2026: }
2027:
2028: return count;
2029: }
2030:
2031: /**
2032: * Not for general use.
2033: * Used by ScriptReader to unconditionally insert a row into
2034: * the table when the .script file is read.
2035: */
2036: public void insertFromScript(Object[] data) throws HsqlException {
2037: updateIdentityValue(data);
2038: insertData(null, data);
2039: }
2040:
2041: /**
2042: * Used by the methods above.
2043: */
2044: public void insertData(Session session, Object[] data)
2045: throws HsqlException {
2046:
2047: Row row = newRow(data);
2048:
2049: indexRow(session, row);
2050: commitRowToStore(row);
2051: }
2052:
2053: /**
2054: * Used by the system tables
2055: */
2056: public void insertSys(Object[] data) throws HsqlException {
2057:
2058: Row row = newRow(data);
2059:
2060: indexRow(null, row);
2061: }
2062:
2063: /**
2064: * Used by TextCache to insert a row into the indexes when the source
2065: * file is first read.
2066: */
2067: protected void insertFromTextSource(CachedRow row)
2068: throws HsqlException {
2069:
2070: Object[] data = row.getData();
2071:
2072: updateIdentityValue(data);
2073: enforceFieldValueLimits(data, defaultColumnMap);
2074: enforceNullConstraints(data);
2075: indexRow(null, row);
2076: }
2077:
2078: /**
2079: * Checks a row against NOT NULL constraints on columns.
2080: */
2081: protected void enforceNullConstraints(Object[] data)
2082: throws HsqlException {
2083:
2084: for (int i = 0; i < columnCount; i++) {
2085: if (data[i] == null && !colNullable[i]) {
2086: Trace.throwerror(Trace.TRY_TO_INSERT_NULL, "column: "
2087: + getColumn(i).columnName.name + " table: "
2088: + tableName.name);
2089: }
2090: }
2091: }
2092:
2093: /**
2094: * If there is an identity column (visible or hidden) on the table, sets
2095: * the value and/or adjusts the iIdentiy value for the table.
2096: */
2097: protected void setIdentityColumn(Session session, Object[] data)
2098: throws HsqlException {
2099:
2100: if (identityColumn != -1) {
2101: Number id = (Number) data[identityColumn];
2102:
2103: if (id == null) {
2104: if (colTypes[identityColumn] == Types.INTEGER) {
2105: id = ValuePool.getInt((int) identitySequence
2106: .getValue());
2107: } else {
2108: id = ValuePool.getLong(identitySequence.getValue());
2109: }
2110:
2111: data[identityColumn] = id;
2112: } else {
2113: identitySequence.getValue(id.longValue());
2114: }
2115:
2116: if (session != null) {
2117: session.setLastIdentity(id);
2118: }
2119: }
2120: }
2121:
2122: /**
2123: * If there is an identity column (visible or hidden) on the table, sets
2124: * the max identity value.
2125: */
2126: protected void updateIdentityValue(Object[] data)
2127: throws HsqlException {
2128:
2129: if (identityColumn != -1) {
2130: Number id = (Number) data[identityColumn];
2131:
2132: if (id != null) {
2133: identitySequence.getValue(id.longValue());
2134: }
2135: }
2136: }
2137:
2138: /**
2139: * Enforce max field sizes according to SQL column definition.
2140: * SQL92 13.8
2141: */
2142: void enforceFieldValueLimits(Object[] data, int[] cols)
2143: throws HsqlException {
2144:
2145: int i;
2146: int colindex;
2147:
2148: if (sqlEnforceSize) {
2149: if (cols == null) {
2150: cols = defaultColumnMap;
2151: }
2152:
2153: for (i = 0; i < cols.length; i++) {
2154: colindex = cols[i];
2155:
2156: if ((colTypes[colindex] == Types.TIMESTAMP || colSizes[colindex] != 0)
2157: && data[colindex] != null) {
2158: data[colindex] = Column.enforceSize(data[colindex],
2159: colTypes[colindex], colSizes[colindex],
2160: colScales[colindex], true);
2161: }
2162: }
2163: }
2164: }
2165:
2166: // fredt@users 20020130 - patch 491987 by jimbag@users - modified
2167:
2168: /**
2169: * Fires all row-level triggers of the given set (trigger type)
2170: *
2171: */
2172: void fireAll(Session session, int trigVecIndx, Object[] oldrow,
2173: Object[] newrow) {
2174:
2175: if (!database.isReferentialIntegrity()) {
2176:
2177: // isReferentialIntegrity is false when reloading db
2178: return;
2179: }
2180:
2181: HsqlArrayList trigVec = triggerLists[trigVecIndx];
2182:
2183: if (trigVec == null) {
2184: return;
2185: }
2186:
2187: for (int i = 0, size = trigVec.size(); i < size; i++) {
2188: TriggerDef td = (TriggerDef) trigVec.get(i);
2189:
2190: td.pushPair(session, oldrow, newrow); // tell the trigger thread to fire with this row
2191: }
2192: }
2193:
2194: /**
2195: * Statement level triggers.
2196: */
2197: void fireAll(Session session, int trigVecIndex) {
2198:
2199: if (triggerLists[trigVecIndex] != null) {
2200: fireAll(session, trigVecIndex, null, null);
2201: }
2202: }
2203:
2204: /**
2205: * Adds a trigger.
2206: */
2207: void addTrigger(TriggerDef trigDef) {
2208:
2209: if (triggerLists[trigDef.vectorIndex] == null) {
2210: triggerLists[trigDef.vectorIndex] = new HsqlArrayList();
2211: }
2212:
2213: triggerLists[trigDef.vectorIndex].add(trigDef);
2214: }
2215:
2216: /**
2217: * Drops a trigger.
2218: */
2219: void dropTrigger(String name) {
2220:
2221: // look in each trigger list of each type of trigger
2222: int numTrigs = TriggerDef.NUM_TRIGS;
2223:
2224: for (int tv = 0; tv < numTrigs; tv++) {
2225: HsqlArrayList v = triggerLists[tv];
2226:
2227: if (v == null) {
2228: continue;
2229: }
2230:
2231: for (int tr = v.size() - 1; tr >= 0; tr--) {
2232: TriggerDef td = (TriggerDef) v.get(tr);
2233:
2234: if (td.name.name.equals(name)) {
2235: v.remove(tr);
2236: td.terminate();
2237: }
2238: }
2239:
2240: if (v.isEmpty()) {
2241: triggerLists[tv] = null;
2242: }
2243: }
2244: }
2245:
2246: /**
2247: * Drops all triggers.
2248: */
2249: void dropTriggers() {
2250:
2251: // look in each trigger list of each type of trigger
2252: int numTrigs = TriggerDef.NUM_TRIGS;
2253:
2254: for (int tv = 0; tv < numTrigs; tv++) {
2255: HsqlArrayList v = triggerLists[tv];
2256:
2257: if (v == null) {
2258: continue;
2259: }
2260:
2261: for (int tr = v.size() - 1; tr >= 0; tr--) {
2262: TriggerDef td = (TriggerDef) v.get(tr);
2263:
2264: td.terminate();
2265: }
2266:
2267: triggerLists[tv] = null;
2268: }
2269: }
2270:
2271: /** @todo fredt - reused structures to be reviewed for multi-threading */
2272:
2273: /**
2274: * Reusable set of all FK constraints that have so far been enforced while
2275: * a cascading insert or delete is in progress. This is emptied and passed
2276: * with the first call to checkCascadeDelete or checkCascadeUpdate. During
2277: * recursion, if an FK constraint is encountered and is already present
2278: * in the set, the recursion stops.
2279: */
2280: HashSet constraintPath;
2281:
2282: /**
2283: * Current list of updates on this table. This is emptied once a cascading
2284: * operation is over.
2285: */
2286: HashMappedList tableUpdateList;
2287:
2288: // fredt@users 20020225 - patch 1.7.0 - CASCADING DELETES
2289:
2290: /**
2291: * Method is called recursively on a tree of tables from the current one
2292: * until no referring foreign-key table is left. In the process, if a
2293: * non-cascading foreign-key referring table contains data, an exception
2294: * is thrown. Parameter delete indicates whether to delete refering rows.
2295: * The method is called first to check if the row can be deleted, then to
2296: * delete the row and all the refering rows.<p>
2297: *
2298: * Support added for SET NULL and SET DEFAULT by kloska@users involves
2299: * switching to checkCascadeUpdate(,,,,) when these rules are encountered
2300: * in the constraint.(fredt@users)
2301: *
2302: * @table table table to update
2303: * @param tableUpdateLists list of update lists
2304: * @param row row to delete
2305: * @param session
2306: * @param delete
2307: * @param path
2308: * @throws HsqlException
2309: */
2310: static void checkCascadeDelete(Session session, Table table,
2311: HashMappedList tableUpdateLists, Row row, boolean delete,
2312: HashSet path) throws HsqlException {
2313:
2314: for (int i = 0, size = table.constraintList.length; i < size; i++) {
2315: Constraint c = table.constraintList[i];
2316:
2317: if (c.getType() != Constraint.MAIN || c.getRef() == null) {
2318: continue;
2319: }
2320:
2321: RowIterator refiterator = c.findFkRef(session, row
2322: .getData(), delete);
2323:
2324: if (!refiterator.hasNext()) {
2325: continue;
2326: }
2327:
2328: try {
2329: if (c.core.deleteAction == Constraint.NO_ACTION) {
2330: if (c.core.mainTable == c.core.refTable) {
2331: Row refrow = refiterator.next();
2332:
2333: // fredt - it's the same row
2334: // this supports deleting a single row
2335: // in future we can iterate over and check against
2336: // the full delete row list to enable multi-row
2337: // with self-referencing FK's deletes
2338: if (row.equals(refrow)) {
2339: continue;
2340: }
2341: }
2342:
2343: throw Trace.error(
2344: Trace.INTEGRITY_CONSTRAINT_VIOLATION,
2345: Trace.Constraint_violation, new Object[] {
2346: c.core.fkName.name,
2347: c.core.refTable.getName().name });
2348: }
2349:
2350: Table reftable = c.getRef();
2351:
2352: // shortcut when deltable has no imported constraint
2353: boolean hasref = reftable.getNextConstraintIndex(0,
2354: Constraint.MAIN) != -1;
2355:
2356: // if (reftable == this) we don't need to go further and can return ??
2357: if (delete == false && hasref == false) {
2358: continue;
2359: }
2360:
2361: Index refindex = c.getRefIndex();
2362: int[] m_columns = c.getMainColumns();
2363: int[] r_columns = c.getRefColumns();
2364: Object[] mdata = row.getData();
2365: boolean isUpdate = c.getDeleteAction() == Constraint.SET_NULL
2366: || c.getDeleteAction() == Constraint.SET_DEFAULT;
2367:
2368: // -- list for records to be inserted if this is
2369: // -- a 'ON DELETE SET [NULL|DEFAULT]' constraint
2370: HashMappedList rowSet = null;
2371:
2372: if (isUpdate) {
2373: rowSet = (HashMappedList) tableUpdateLists
2374: .get(reftable);
2375:
2376: if (rowSet == null) {
2377: rowSet = new HashMappedList();
2378:
2379: tableUpdateLists.add(reftable, rowSet);
2380: }
2381: }
2382:
2383: // walk the index for all the nodes that reference delnode
2384: for (;;) {
2385: Row refrow = refiterator.next();
2386:
2387: if (refrow == null
2388: || refrow.isCascadeDeleted()
2389: || refindex.compareRowNonUnique(session,
2390: mdata, m_columns, refrow.getData()) != 0) {
2391: break;
2392: }
2393:
2394: // -- if the constraint is a 'SET [DEFAULT|NULL]' constraint we have to keep
2395: // -- a new record to be inserted after deleting the current. We also have to
2396: // -- switch over to the 'checkCascadeUpdate' method below this level
2397: if (isUpdate) {
2398: Object[] rnd = reftable.getEmptyRowData();
2399:
2400: System.arraycopy(refrow.getData(), 0, rnd, 0,
2401: rnd.length);
2402:
2403: if (c.getDeleteAction() == Constraint.SET_NULL) {
2404: for (int j = 0; j < r_columns.length; j++) {
2405: rnd[r_columns[j]] = null;
2406: }
2407: } else {
2408: for (int j = 0; j < r_columns.length; j++) {
2409: Column col = reftable
2410: .getColumn(r_columns[j]);
2411:
2412: rnd[r_columns[j]] = col
2413: .getDefaultValue(session);
2414: }
2415: }
2416:
2417: if (hasref && path.add(c)) {
2418:
2419: // fredt - avoid infinite recursion on circular references
2420: // these can be rings of two or more mutually dependent tables
2421: // so only one visit per constraint is allowed
2422: checkCascadeUpdate(session, reftable, null,
2423: refrow, rnd, r_columns, null, path);
2424: path.remove(c);
2425: }
2426:
2427: if (delete) {
2428:
2429: // foreign key referencing own table - do not update the row to be deleted
2430: if (reftable != table
2431: || !refrow.equals(row)) {
2432: mergeUpdate(rowSet, refrow, rnd,
2433: r_columns);
2434: }
2435: }
2436: } else if (hasref) {
2437: if (reftable != table) {
2438: if (path.add(c)) {
2439: checkCascadeDelete(session, reftable,
2440: tableUpdateLists, refrow,
2441: delete, path);
2442: path.remove(c);
2443: }
2444: } else {
2445:
2446: // fredt - we avoid infinite recursion on the fk's referencing the same table
2447: // but chained rows can result in very deep recursion and StackOverflowError
2448: if (refrow != row) {
2449: checkCascadeDelete(session, reftable,
2450: tableUpdateLists, refrow,
2451: delete, path);
2452: }
2453: }
2454: }
2455:
2456: if (delete && !isUpdate
2457: && !refrow.isCascadeDeleted()) {
2458: reftable.deleteNoRefCheck(session, refrow);
2459: }
2460: }
2461: } finally {
2462: refiterator.release();
2463: }
2464: }
2465: }
2466:
2467: /**
2468: * Check or perform an update cascade operation on a single row.
2469: * Check or cascade an update (delete/insert) operation.
2470: * The method takes a pair of rows (new data,old data) and checks
2471: * if Constraints permit the update operation.
2472: * A boolean arguement determines if the operation should
2473: * realy take place or if we just have to check for constraint violation.
2474: * fredt - cyclic conditions are now avoided by checking for second visit
2475: * to each constraint. The set of list of updates for all tables is passed
2476: * and filled in recursive calls.
2477: *
2478: * @param session current database session
2479: * @param table
2480: * @param tableUpdateLists lists of updates
2481: * @param orow old row data to be deleted.
2482: * @param nrow new row data to be inserted.
2483: * @param cols indices of the columns actually changed.
2484: * @param ref This should be initialized to null when the
2485: * method is called from the 'outside'. During recursion this will be the
2486: * current table (i.e. this) to indicate from where we came.
2487: * Foreign keys to this table do not have to be checked since they have
2488: * triggered the update and are valid by definition.
2489: *
2490: * @short Check or perform and update cascade operation on a single row.
2491: *
2492: *
2493: */
2494: static void checkCascadeUpdate(Session session, Table table,
2495: HashMappedList tableUpdateLists, Row orow, Object[] nrow,
2496: int[] cols, Table ref, HashSet path) throws HsqlException {
2497:
2498: // -- We iterate through all constraints associated with this table
2499: // --
2500: for (int i = 0, size = table.constraintList.length; i < size; i++) {
2501: Constraint c = table.constraintList[i];
2502:
2503: if (c.getType() == Constraint.FOREIGN_KEY
2504: && c.getRef() != null) {
2505:
2506: // -- (1) If it is a foreign key constraint we have to check if the
2507: // -- main table still holds a record which allows the new values
2508: // -- to be set in the updated columns. This test however will be
2509: // -- skipped if the reference table is the main table since changes
2510: // -- in the reference table triggered the update and therefor
2511: // -- the referential integrity is guaranteed to be valid.
2512: // --
2513: if (ref == null || c.getMain() != ref) {
2514:
2515: // -- common indexes of the changed columns and the main/ref constraint
2516: if (ArrayUtil.countCommonElements(cols, c
2517: .getRefColumns()) == 0) {
2518:
2519: // -- Table::checkCascadeUpdate -- NO common cols; reiterating
2520: continue;
2521: }
2522:
2523: c.hasMainRef(session, nrow);
2524: }
2525: } else if (c.getType() == Constraint.MAIN
2526: && c.getRef() != null) {
2527:
2528: // -- (2) If it happens to be a main constraint we check if the slave
2529: // -- table holds any records refering to the old contents. If so,
2530: // -- the constraint has to support an 'on update' action or we
2531: // -- throw an exception (all via a call to Constraint.findFkRef).
2532: // --
2533: // -- If there are no common columns between the reference constraint
2534: // -- and the changed columns, we reiterate.
2535: int[] common = ArrayUtil.commonElements(cols, c
2536: .getMainColumns());
2537:
2538: if (common == null) {
2539:
2540: // -- NO common cols between; reiterating
2541: continue;
2542: }
2543:
2544: int[] m_columns = c.getMainColumns();
2545: int[] r_columns = c.getRefColumns();
2546:
2547: // fredt - find out if the FK columns have actually changed
2548: boolean nochange = true;
2549:
2550: for (int j = 0; j < m_columns.length; j++) {
2551: if (!orow.getData()[m_columns[j]]
2552: .equals(nrow[m_columns[j]])) {
2553: nochange = false;
2554:
2555: break;
2556: }
2557: }
2558:
2559: if (nochange) {
2560: continue;
2561: }
2562:
2563: // there must be no record in the 'slave' table
2564: // sebastian@scienion -- dependent on forDelete | forUpdate
2565: RowIterator refiterator = c.findFkRef(session, orow
2566: .getData(), false);
2567:
2568: if (refiterator.hasNext()) {
2569: if (c.core.updateAction == Constraint.NO_ACTION) {
2570: throw Trace
2571: .error(
2572: Trace.INTEGRITY_CONSTRAINT_VIOLATION,
2573: Trace.Constraint_violation,
2574: new Object[] {
2575: c.core.fkName.name,
2576: c.core.refTable
2577: .getName().name });
2578: }
2579: } else {
2580:
2581: // no referencing row found
2582: continue;
2583: }
2584:
2585: Table reftable = c.getRef();
2586:
2587: // -- unused shortcut when update table has no imported constraint
2588: boolean hasref = reftable.getNextConstraintIndex(0,
2589: Constraint.MAIN) != -1;
2590: Index refindex = c.getRefIndex();
2591:
2592: // -- walk the index for all the nodes that reference update node
2593: HashMappedList rowSet = (HashMappedList) tableUpdateLists
2594: .get(reftable);
2595:
2596: if (rowSet == null) {
2597: rowSet = new HashMappedList();
2598:
2599: tableUpdateLists.add(reftable, rowSet);
2600: }
2601:
2602: for (Row refrow = refiterator.next();; refrow = refiterator
2603: .next()) {
2604: if (refrow == null
2605: || refindex.compareRowNonUnique(session,
2606: orow.getData(), m_columns, refrow
2607: .getData()) != 0) {
2608: break;
2609: }
2610:
2611: Object[] rnd = reftable.getEmptyRowData();
2612:
2613: System.arraycopy(refrow.getData(), 0, rnd, 0,
2614: rnd.length);
2615:
2616: // -- Depending on the type constraint we are dealing with we have to
2617: // -- fill up the forign key of the current record with different values
2618: // -- And handle the insertion procedure differently.
2619: if (c.getUpdateAction() == Constraint.SET_NULL) {
2620:
2621: // -- set null; we do not have to check referential integrity any further
2622: // -- since we are setting <code>null</code> values
2623: for (int j = 0; j < r_columns.length; j++) {
2624: rnd[r_columns[j]] = null;
2625: }
2626: } else if (c.getUpdateAction() == Constraint.SET_DEFAULT) {
2627:
2628: // -- set default; we check referential integrity with ref==null; since we manipulated
2629: // -- the values and referential integrity is no longer guaranteed to be valid
2630: for (int j = 0; j < r_columns.length; j++) {
2631: Column col = reftable
2632: .getColumn(r_columns[j]);
2633:
2634: rnd[r_columns[j]] = col
2635: .getDefaultValue(session);
2636: }
2637:
2638: if (path.add(c)) {
2639: checkCascadeUpdate(session, reftable,
2640: tableUpdateLists, refrow, rnd,
2641: r_columns, null, path);
2642: path.remove(c);
2643: }
2644: } else {
2645:
2646: // -- cascade; standard recursive call. We inherit values from the foreign key
2647: // -- table therefor we set ref==this.
2648: for (int j = 0; j < m_columns.length; j++) {
2649: rnd[r_columns[j]] = nrow[m_columns[j]];
2650: }
2651:
2652: if (path.add(c)) {
2653: checkCascadeUpdate(session, reftable,
2654: tableUpdateLists, refrow, rnd,
2655: common, table, path);
2656: path.remove(c);
2657: }
2658: }
2659:
2660: mergeUpdate(rowSet, refrow, rnd, r_columns);
2661: }
2662: }
2663: }
2664: }
2665:
2666: /**
2667: * Merges a triggered change with a previous triggered change, or adds to
2668: * list.
2669: */
2670: static void mergeUpdate(HashMappedList rowSet, Row row,
2671: Object[] newData, int[] cols) {
2672:
2673: Object[] data = (Object[]) rowSet.get(row);
2674:
2675: if (data != null) {
2676: for (int j = 0; j < cols.length; j++) {
2677: data[cols[j]] = newData[cols[j]];
2678: }
2679: } else {
2680: rowSet.add(row, newData);
2681: }
2682: }
2683:
2684: /**
2685: * Merge the full triggered change with the updated row, or add to list.
2686: * Return false if changes conflict.
2687: */
2688: static boolean mergeKeepUpdate(Session session,
2689: HashMappedList rowSet, int[] cols, int[] colTypes, Row row,
2690: Object[] newData) throws HsqlException {
2691:
2692: Object[] data = (Object[]) rowSet.get(row);
2693:
2694: if (data != null) {
2695: if (Index.compareRows(session, row.getData(), newData,
2696: cols, colTypes) != 0
2697: && Index.compareRows(session, newData, data, cols,
2698: colTypes) != 0) {
2699: return false;
2700: }
2701:
2702: for (int j = 0; j < cols.length; j++) {
2703: newData[cols[j]] = data[cols[j]];
2704: }
2705:
2706: rowSet.put(row, newData);
2707: } else {
2708: rowSet.add(row, newData);
2709: }
2710:
2711: return true;
2712: }
2713:
2714: static void clearUpdateLists(HashMappedList tableUpdateList) {
2715:
2716: for (int i = 0; i < tableUpdateList.size(); i++) {
2717: HashMappedList updateList = (HashMappedList) tableUpdateList
2718: .get(i);
2719:
2720: updateList.clear();
2721: }
2722: }
2723:
2724: /**
2725: * Highest level multiple row delete method. Corresponds to an SQL
2726: * DELETE.
2727: */
2728: int delete(Session session, HsqlArrayList deleteList)
2729: throws HsqlException {
2730:
2731: HashSet path = constraintPath == null ? new HashSet()
2732: : constraintPath;
2733:
2734: constraintPath = null;
2735:
2736: HashMappedList tUpdateList = tableUpdateList == null ? new HashMappedList()
2737: : tableUpdateList;
2738:
2739: tableUpdateList = null;
2740:
2741: if (database.isReferentialIntegrity()) {
2742: for (int i = 0; i < deleteList.size(); i++) {
2743: Row row = (Row) deleteList.get(i);
2744:
2745: path.clear();
2746: checkCascadeDelete(session, this , tUpdateList, row,
2747: false, path);
2748: }
2749: }
2750:
2751: // check transactions
2752: database.txManager.checkDelete(session, deleteList);
2753:
2754: for (int i = 0; i < tUpdateList.size(); i++) {
2755: Table table = (Table) tUpdateList.getKey(i);
2756: HashMappedList updateList = (HashMappedList) tUpdateList
2757: .get(i);
2758:
2759: database.txManager.checkDelete(session, updateList);
2760: }
2761:
2762: // perform delete
2763: fireAll(session, Trigger.DELETE_BEFORE);
2764:
2765: if (database.isReferentialIntegrity()) {
2766: for (int i = 0; i < deleteList.size(); i++) {
2767: Row row = (Row) deleteList.get(i);
2768:
2769: path.clear();
2770: checkCascadeDelete(session, this , tUpdateList, row,
2771: true, path);
2772: }
2773: }
2774:
2775: for (int i = 0; i < deleteList.size(); i++) {
2776: Row row = (Row) deleteList.get(i);
2777:
2778: if (!row.isCascadeDeleted()) {
2779: deleteNoRefCheck(session, row);
2780: }
2781: }
2782:
2783: for (int i = 0; i < tUpdateList.size(); i++) {
2784: Table table = (Table) tUpdateList.getKey(i);
2785: HashMappedList updateList = (HashMappedList) tUpdateList
2786: .get(i);
2787:
2788: table.updateRowSet(session, updateList, null, false);
2789: updateList.clear();
2790: }
2791:
2792: fireAll(session, Trigger.DELETE_AFTER);
2793: path.clear();
2794:
2795: constraintPath = path;
2796: tableUpdateList = tUpdateList;
2797:
2798: return deleteList.size();
2799: }
2800:
2801: /**
2802: * Mid level row delete method. Fires triggers but no integrity
2803: * constraint checks.
2804: */
2805: private void deleteNoRefCheck(Session session, Row row)
2806: throws HsqlException {
2807:
2808: Object[] data = row.getData();
2809:
2810: fireAll(session, Trigger.DELETE_BEFORE_ROW, data, null);
2811: deleteNoCheck(session, row, true);
2812:
2813: // fire the delete after statement trigger
2814: fireAll(session, Trigger.DELETE_AFTER_ROW, data, null);
2815: }
2816:
2817: /**
2818: * Low level row delete method. Removes the row from the indexes and
2819: * from the Cache.
2820: */
2821: private void deleteNoCheck(Session session, Row row, boolean log)
2822: throws HsqlException {
2823:
2824: if (row.isCascadeDeleted()) {
2825: return;
2826: }
2827:
2828: Object[] data = row.getData();
2829:
2830: row = row.getUpdatedRow();
2831:
2832: for (int i = indexList.length - 1; i >= 0; i--) {
2833: Node node = row.getNode(i);
2834:
2835: indexList[i].delete(session, node);
2836: }
2837:
2838: row.delete();
2839:
2840: if (session != null) {
2841: session.addDeleteAction(this , row);
2842: }
2843:
2844: if (log && isLogged) {
2845: database.logger.writeDeleteStatement(session, this , data);
2846: }
2847: }
2848:
2849: /**
2850: * For log statements.
2851: */
2852: public void deleteNoCheckFromLog(Session session, Object[] data)
2853: throws HsqlException {
2854:
2855: Row row = null;
2856:
2857: if (hasPrimaryKey()) {
2858: RowIterator it = getPrimaryIndex().findFirstRow(session,
2859: data, primaryKeyColsSequence);
2860:
2861: row = it.next();
2862: } else if (bestIndex == null) {
2863: RowIterator it = getPrimaryIndex().firstRow(session);
2864:
2865: while (true) {
2866: row = it.next();
2867:
2868: if (row == null) {
2869: break;
2870: }
2871:
2872: if (Index.compareRows(session, row.getData(), data,
2873: defaultColumnMap, colTypes) == 0) {
2874: break;
2875: }
2876: }
2877: } else {
2878: RowIterator it = bestIndex.findFirstRow(session, data);
2879:
2880: while (true) {
2881: row = it.next();
2882:
2883: if (row == null) {
2884: break;
2885: }
2886:
2887: Object[] rowdata = row.getData();
2888:
2889: // reached end of range
2890: if (bestIndex.compareRowNonUnique(session, data,
2891: bestIndex.getColumns(), rowdata) != 0) {
2892: row = null;
2893:
2894: break;
2895: }
2896:
2897: if (Index.compareRows(session, rowdata, data,
2898: defaultColumnMap, colTypes) == 0) {
2899: break;
2900: }
2901: }
2902: }
2903:
2904: if (row == null) {
2905: return;
2906: }
2907:
2908: // not necessary for log deletes
2909: database.txManager.checkDelete(session, row);
2910:
2911: for (int i = indexList.length - 1; i >= 0; i--) {
2912: Node node = row.getNode(i);
2913:
2914: indexList[i].delete(session, node);
2915: }
2916:
2917: row.delete();
2918:
2919: if (session != null) {
2920: session.addDeleteAction(this , row);
2921: }
2922: }
2923:
2924: /**
2925: * Low level row delete method. Removes the row from the indexes and
2926: * from the Cache. Used by rollback.
2927: */
2928: void deleteNoCheckRollback(Session session, Row row, boolean log)
2929: throws HsqlException {
2930:
2931: row = indexList[0].findRow(session, row);
2932:
2933: for (int i = indexList.length - 1; i >= 0; i--) {
2934: Node node = row.getNode(i);
2935:
2936: indexList[i].delete(session, node);
2937: }
2938:
2939: row.delete();
2940: removeRowFromStore(row);
2941:
2942: if (log && isLogged) {
2943: database.logger.writeDeleteStatement(session, this , row
2944: .getData());
2945: }
2946: }
2947:
2948: /**
2949: * Highest level multiple row update method. Corresponds to an SQL
2950: * UPDATE. To DEAL with unique constraints we need to perform all
2951: * deletes at once before the inserts. If there is a UNIQUE constraint
2952: * violation limited only to the duration of updating multiple rows,
2953: * we don't want to abort the operation. Example:
2954: * UPDATE MYTABLE SET UNIQUECOL = UNIQUECOL + 1
2955: * After performing each cascade update, delete the main row.
2956: * After all cascade ops and deletes have been performed, insert new
2957: * rows.
2958: *
2959: * The following clauses from SQL Standard section 11.8 are enforced
2960: * 9) Let ISS be the innermost SQL-statement being executed.
2961: * 10) If evaluation of these General Rules during the execution of ISS
2962: * would cause an update of some site to a value that is distinct from the
2963: * value to which that site was previously updated during the execution of
2964: * ISS, then an exception condition is raised: triggered data change
2965: * violation.
2966: * 11) If evaluation of these General Rules during the execution of ISS
2967: * would cause deletion of a row containing a site that is identified for
2968: * replacement in that row, then an exception condition is raised:
2969: * triggered data change violation.
2970: *
2971: * (fredt)
2972: */
2973: int update(Session session, HashMappedList updateList, int[] cols)
2974: throws HsqlException {
2975:
2976: HashSet path = constraintPath == null ? new HashSet()
2977: : constraintPath;
2978:
2979: constraintPath = null;
2980:
2981: HashMappedList tUpdateList = tableUpdateList == null ? new HashMappedList()
2982: : tableUpdateList;
2983:
2984: tableUpdateList = null;
2985:
2986: // set identity column where null and check columns
2987: for (int i = 0; i < updateList.size(); i++) {
2988: Object[] data = (Object[]) updateList.get(i);
2989:
2990: // this means the identity column can be set to null to force
2991: // creation of a new identity value
2992: setIdentityColumn(session, data);
2993: enforceFieldValueLimits(data, cols);
2994: enforceNullConstraints(data);
2995: }
2996:
2997: // perform check/cascade operations
2998: if (database.isReferentialIntegrity()) {
2999: for (int i = 0; i < updateList.size(); i++) {
3000: Object[] data = (Object[]) updateList.get(i);
3001: Row row = (Row) updateList.getKey(i);
3002:
3003: checkCascadeUpdate(session, this , tUpdateList, row,
3004: data, cols, null, path);
3005: }
3006: }
3007:
3008: fireAll(session, Trigger.UPDATE_BEFORE);
3009:
3010: // merge any triggered change to this table with the update list
3011: HashMappedList triggeredList = (HashMappedList) tUpdateList
3012: .get(this );
3013:
3014: if (triggeredList != null) {
3015: for (int i = 0; i < triggeredList.size(); i++) {
3016: Row row = (Row) triggeredList.getKey(i);
3017: Object[] data = (Object[]) triggeredList.get(i);
3018:
3019: mergeKeepUpdate(session, updateList, cols, colTypes,
3020: row, data);
3021: }
3022:
3023: triggeredList.clear();
3024: }
3025:
3026: // check transactions
3027: for (int i = 0; i < tUpdateList.size(); i++) {
3028: Table table = (Table) tUpdateList.getKey(i);
3029: HashMappedList updateListT = (HashMappedList) tUpdateList
3030: .get(i);
3031:
3032: database.txManager.checkDelete(session, updateListT);
3033: }
3034:
3035: database.txManager.checkDelete(session, updateList);
3036:
3037: // update lists - main list last
3038: for (int i = 0; i < tUpdateList.size(); i++) {
3039: Table table = (Table) tUpdateList.getKey(i);
3040: HashMappedList updateListT = (HashMappedList) tUpdateList
3041: .get(i);
3042:
3043: table.updateRowSet(session, updateListT, null, false);
3044: updateListT.clear();
3045: }
3046:
3047: updateRowSet(session, updateList, cols, true);
3048: fireAll(session, Trigger.UPDATE_AFTER);
3049: path.clear();
3050:
3051: constraintPath = path;
3052: tableUpdateList = tUpdateList;
3053:
3054: clearUpdateLists(tableUpdateList);
3055:
3056: return updateList.size();
3057: }
3058:
3059: void updateRowSet(Session session, HashMappedList rowSet,
3060: int[] cols, boolean nodelete) throws HsqlException {
3061:
3062: for (int i = rowSet.size() - 1; i >= 0; i--) {
3063: Row row = (Row) rowSet.getKey(i);
3064: Object[] data = (Object[]) rowSet.get(i);
3065:
3066: if (row.isCascadeDeleted()) {
3067: if (nodelete) {
3068: throw Trace.error(Trace.TRIGGERED_DATA_CHANGE);
3069: } else {
3070: rowSet.remove(i);
3071:
3072: continue;
3073: }
3074: }
3075:
3076: for (int j = 0; j < constraintList.length; j++) {
3077: Constraint c = constraintList[j];
3078:
3079: if (c.getType() == Constraint.CHECK) {
3080: c.checkCheckConstraint(session, data);
3081:
3082: continue;
3083: }
3084: }
3085:
3086: deleteNoCheck(session, row, true);
3087: }
3088:
3089: for (int i = 0; i < rowSet.size(); i++) {
3090: Row row = (Row) rowSet.getKey(i);
3091: Object[] data = (Object[]) rowSet.get(i);
3092:
3093: if (triggerLists[Trigger.UPDATE_BEFORE_ROW] != null) {
3094: fireAll(session, Trigger.UPDATE_BEFORE_ROW, row
3095: .getData(), data);
3096: checkRowDataUpdate(session, data, cols);
3097: }
3098:
3099: insertNoCheck(session, data);
3100:
3101: if (triggerLists[Trigger.UPDATE_AFTER_ROW] != null) {
3102: fireAll(session, Trigger.UPDATE_AFTER_ROW, row
3103: .getData(), data);
3104: checkRowDataUpdate(session, data, cols);
3105: }
3106: }
3107: }
3108:
3109: void checkRowDataInsert(Session session, Object[] data)
3110: throws HsqlException {
3111:
3112: enforceFieldValueLimits(data, null);
3113: enforceNullConstraints(data);
3114:
3115: if (database.isReferentialIntegrity()) {
3116: for (int i = 0, size = constraintList.length; i < size; i++) {
3117: constraintList[i].checkInsert(session, data);
3118: }
3119: }
3120: }
3121:
3122: void checkRowDataUpdate(Session session, Object[] data, int[] cols)
3123: throws HsqlException {
3124:
3125: enforceFieldValueLimits(data, cols);
3126: enforceNullConstraints(data);
3127:
3128: for (int j = 0; j < constraintList.length; j++) {
3129: Constraint c = constraintList[j];
3130:
3131: if (c.getType() == Constraint.CHECK) {
3132: c.checkCheckConstraint(session, data);
3133: }
3134: }
3135: }
3136:
3137: /**
3138: * True if table is CACHED or TEXT
3139: *
3140: * @return
3141: */
3142: public boolean isCached() {
3143: return isCached;
3144: }
3145:
3146: /**
3147: * Returns true if table is CACHED
3148: */
3149: boolean isIndexCached() {
3150: return isCached;
3151: }
3152:
3153: /**
3154: * Returns the index of the Index object of the given name or -1 if not found.
3155: */
3156: int getIndexIndex(String indexName) {
3157:
3158: Index[] indexes = indexList;
3159:
3160: for (int i = 0; i < indexes.length; i++) {
3161: if (indexName.equals(indexes[i].getName().name)) {
3162: return i;
3163: }
3164: }
3165:
3166: // no such index
3167: return -1;
3168: }
3169:
3170: /**
3171: * Returns the Index object of the given name or null if not found.
3172: */
3173: Index getIndex(String indexName) {
3174:
3175: Index[] indexes = indexList;
3176: int i = getIndexIndex(indexName);
3177:
3178: return i == -1 ? null : indexes[i];
3179: }
3180:
3181: /**
3182: * Return the position of the constraint within the list
3183: */
3184: int getConstraintIndex(String constraintName) {
3185:
3186: for (int i = 0, size = constraintList.length; i < size; i++) {
3187: if (constraintList[i].getName().name.equals(constraintName)) {
3188: return i;
3189: }
3190: }
3191:
3192: return -1;
3193: }
3194:
3195: /**
3196: * return the named constriant
3197: */
3198: Constraint getConstraint(String constraintName) {
3199:
3200: int i = getConstraintIndex(constraintName);
3201:
3202: return (i < 0) ? null : (Constraint) constraintList[i];
3203: }
3204:
3205: /**
3206: * remove a named constraint
3207: */
3208: void removeConstraint(String name) {
3209:
3210: int index = getConstraintIndex(name);
3211:
3212: constraintList = (Constraint[]) ArrayUtil.toAdjustedArray(
3213: constraintList, null, index, -1);
3214: }
3215:
3216: /**
3217: * Returns the Column object at the given index
3218: */
3219: Column getColumn(int i) {
3220: return (Column) columnList.get(i);
3221: }
3222:
3223: void renameColumn(Column column, String newName, boolean isquoted)
3224: throws HsqlException {
3225:
3226: String oldname = column.columnName.name;
3227: int i = getColumnNr(oldname);
3228:
3229: columnList.setKey(i, newName);
3230: column.columnName.rename(newName, isquoted);
3231: renameColumnInCheckConstraints(oldname, newName, isquoted);
3232: }
3233:
3234: /**
3235: * Returns an array of int valuse indicating the SQL type of the columns
3236: */
3237: public int[] getColumnTypes() {
3238: return colTypes;
3239: }
3240:
3241: /**
3242: * Returns the Index object at the given index
3243: */
3244: public Index getIndex(int i) {
3245: return indexList[i];
3246: }
3247:
3248: public Index[] getIndexes() {
3249: return indexList;
3250: }
3251:
3252: /**
3253: * Used by CACHED tables to fetch a Row from the Cache, resulting in the
3254: * Row being read from disk if it is not in the Cache.
3255: *
3256: * TEXT tables pass the memory resident Node parameter so that the Row
3257: * and its index Nodes can be relinked.
3258: */
3259: CachedRow getRow(int pos, Node primarynode) throws HsqlException {
3260:
3261: if (isText) {
3262: CachedDataRow row = (CachedDataRow) rowStore.get(pos);
3263:
3264: row.nPrimaryNode = primarynode;
3265:
3266: return row;
3267: } else if (isCached) {
3268: return (CachedRow) rowStore.get(pos);
3269: }
3270:
3271: return null;
3272: }
3273:
3274: /**
3275: * As above, only for CACHED tables
3276: */
3277: CachedRow getRow(int pos) {
3278: return (CachedRow) rowStore.get(pos);
3279: }
3280:
3281: /**
3282: * As above, only for CACHED tables
3283: */
3284: CachedRow getRow(long id) {
3285: return (CachedRow) rowStore.get((int) id);
3286: }
3287:
3288: /**
3289: * called in autocommit mode or by transaction manager when a a delete is committed
3290: */
3291: void removeRowFromStore(Row row) throws HsqlException {
3292:
3293: if (isCached || isText && cache != null) {
3294: rowStore.remove(row.getPos());
3295: }
3296: }
3297:
3298: void releaseRowFromStore(Row row) throws HsqlException {
3299:
3300: if (isCached || isText && cache != null) {
3301: rowStore.release(row.getPos());
3302: }
3303: }
3304:
3305: void commitRowToStore(Row row) {
3306:
3307: if (isText && cache != null) {
3308: rowStore.commit(row);
3309: }
3310: }
3311:
3312: void indexRow(Session session, Row row) throws HsqlException {
3313:
3314: int i = 0;
3315:
3316: try {
3317: for (; i < indexList.length; i++) {
3318: indexList[i].insert(session, row, i);
3319: }
3320: } catch (HsqlException e) {
3321: Index index = indexList[i];
3322: boolean isconstraint = index.isConstraint;
3323:
3324: // unique index violation - rollback insert
3325: for (--i; i >= 0; i--) {
3326: Node n = row.getNode(i);
3327:
3328: indexList[i].delete(session, n);
3329: }
3330:
3331: row.delete();
3332: removeRowFromStore(row);
3333:
3334: if (isconstraint) {
3335: Constraint c = getUniqueOrPKConstraintForIndex(index);
3336: String name = c == null ? index.getName().name : c
3337: .getName().name;
3338:
3339: throw Trace.error(Trace.VIOLATION_OF_UNIQUE_CONSTRAINT,
3340: new Object[] { name });
3341: }
3342:
3343: throw e;
3344: }
3345: }
3346:
3347: /**
3348: *
3349: */
3350: void clearAllRows(Session session) {
3351:
3352: for (int i = 0; i < indexList.length; i++) {
3353: indexList[i].clearAll(session);
3354: }
3355:
3356: if (!isTemp) {
3357: identitySequence.reset();
3358: rowIdSequence.reset();
3359: }
3360: }
3361:
3362: /** @todo -- release the rows */
3363: void drop() throws HsqlException {
3364: }
3365:
3366: boolean isWritable() {
3367: return !isDataReadOnly()
3368: && !database.databaseReadOnly
3369: && !(database.isFilesReadOnly() && (isCached || isText));
3370: }
3371:
3372: /**
3373: * Returns the catalog name or null, depending on a database property.
3374: */
3375: String getCatalogName() {
3376:
3377: // PRE: database is never null
3378: return database.getProperties().isPropertyTrue(
3379: "hsqldb.catalogs") ? database.getURI() : null;
3380: }
3381:
3382: /**
3383: * Returns the schema name.
3384: */
3385: public String getSchemaName() {
3386: return tableName.schema.name;
3387: }
3388:
3389: public int getRowCount(Session session) throws HsqlException {
3390: return getPrimaryIndex().size(session);
3391: }
3392:
3393: /**
3394: * Necessary when over Integer.MAX_VALUE Row objects have been generated
3395: * for a memory table.
3396: */
3397: public void resetRowId(Session session) throws HsqlException {
3398:
3399: if (isCached) {
3400: return;
3401: }
3402:
3403: rowIdSequence = new NumberSequence(null, 0, 1, Types.BIGINT);
3404:
3405: RowIterator it = getPrimaryIndex().firstRow(session);
3406: ;
3407:
3408: while (it.hasNext()) {
3409: Row row = it.next();
3410: int pos = (int) rowIdSequence.getValue();
3411:
3412: row.setPos(pos);
3413: }
3414: }
3415:
3416: /**
3417: * Factory method instantiates a Row based on table type.
3418: */
3419: Row newRow(Object[] o) throws HsqlException {
3420:
3421: Row row;
3422:
3423: try {
3424: if (isMemory) {
3425: row = new Row(this , o);
3426:
3427: int pos = (int) rowIdSequence.getValue();
3428:
3429: row.setPos(pos);
3430: } else {
3431: row = CachedRow.newCachedRow(this , o);
3432:
3433: rowStore.add(row);
3434: }
3435: } catch (IOException e) {
3436: throw new HsqlException(e, Trace
3437: .getMessage(Trace.GENERAL_IO_ERROR),
3438: Trace.GENERAL_IO_ERROR);
3439: }
3440:
3441: return row;
3442: }
3443:
3444: Row restoreRow(Row oldrow) throws HsqlException {
3445:
3446: Row row;
3447:
3448: try {
3449: if (isMemory) {
3450: row = new Row(this , oldrow.oData);
3451:
3452: row.setPos(oldrow.getPos());
3453: } else {
3454: row = CachedRow.newCachedRow(this , oldrow.oData);
3455:
3456: row.setStorageSize(oldrow.getStorageSize());
3457: row.setPos(oldrow.getPos());
3458: rowStore.restore(row);
3459: }
3460: } catch (IOException e) {
3461: throw new HsqlException(e, Trace
3462: .getMessage(Trace.GENERAL_IO_ERROR),
3463: Trace.GENERAL_IO_ERROR);
3464: }
3465:
3466: return row;
3467: }
3468:
3469: public class RowStore implements PersistentStore {
3470:
3471: public CachedObject get(int i) {
3472:
3473: try {
3474: return cache.get(i, this , false);
3475: } catch (HsqlException e) {
3476: return null;
3477: }
3478: }
3479:
3480: public CachedObject getKeep(int i) {
3481:
3482: try {
3483: return cache.get(i, this , true);
3484: } catch (HsqlException e) {
3485: return null;
3486: }
3487: }
3488:
3489: public int getStorageSize(int i) {
3490:
3491: try {
3492: return cache.get(i, this , false).getStorageSize();
3493: } catch (HsqlException e) {
3494: return 0;
3495: }
3496: }
3497:
3498: public void add(CachedObject row) throws IOException {
3499: cache.add(row);
3500: }
3501:
3502: public void restore(CachedObject row) throws IOException {
3503: cache.restore(row);
3504: }
3505:
3506: public CachedObject get(RowInputInterface in) {
3507:
3508: try {
3509: if (Table.this .isText) {
3510: return new CachedDataRow(Table.this , in);
3511: }
3512:
3513: CachedObject row = new CachedRow(Table.this , in);
3514:
3515: return row;
3516: } catch (HsqlException e) {
3517: return null;
3518: } catch (IOException e) {
3519: return null;
3520: }
3521: }
3522:
3523: public CachedObject getNewInstance(int size) {
3524: return null;
3525: }
3526:
3527: public void remove(int i) {
3528:
3529: try {
3530: cache.remove(i, this );
3531: } catch (IOException e) {
3532: }
3533: }
3534:
3535: public void removePersistence(int i) {
3536:
3537: try {
3538: cache.removePersistence(i, this );
3539: } catch (IOException e) {
3540:
3541: //
3542: }
3543: }
3544:
3545: public void release(int i) {
3546: cache.release(i);
3547: }
3548:
3549: public void commit(CachedObject row) {
3550:
3551: try {
3552: if (Table.this .isText) {
3553: cache.saveRow(row);
3554: }
3555: } catch (IOException e) {
3556:
3557: //
3558: }
3559: }
3560: }
3561: }
|