Thanks Sebastian, I will look a the code later today.
Regarding identity, yes, internally each table has an integer identity column. If the table has not got a primary key with identity specified, the identity column is hidden. For visible identity columns, it is possible to use the identity() function to get the last ID when a row was inserted. This value is the last ID for the connection concerned. If another connection has inserted a row, it gets a different ID. There is a small issue with the identity values and although it does not affect normal operations, it would be nice to fix it. When an update occurs in a table, the next available ID is also incremented (this is the issue) . This results in ID values going up unnecessarily. If a table that contains only thousands of rows is heavily updated over a long period of time (perhaps months), the ID can reach MAX_INTEGER, making it impossible to insert new rows in the table. The code for this is all in Table.java in insertNoCheck(). The problem is caused by the following: if (isText) { //-- Always inserted at end of file. nextId = ((CachedRow) r).iPos + ((CachedRow) r).storageSize; } else { nextId++; } nextId++ is incremented regardless of the check earlier in the same method to see if the id column contains null and requires a new identity value, or it already contains a value (the insert is actually for an update). Something to fix if someone has the time. Fred Toussi ----- Original Message ----- From: "Sebastian Kloska" <[EMAIL PROTECTED]> To: "Fred Toussi" <[EMAIL PROTECTED]>; "HSQLDB Developers" <[EMAIL PROTECTED]> Sent: 29 October 2002 11:05 Subject: Re: [Hsqldb-developers] ON UPDATE CASCADE -- 1st Bug Hi, Rescanned my stuff and found the first bug. Actually not realy a bug, but a documentation of my lack of understanding to which tables the contraints point when they are MAIN or FK Constraints ;-) I think it should be just a few modifications to get the ON [UPDATE|DELETE] SET [NULL|DEFAULT] going. BtW Are there any plans to include a 'identity' variable for SQL queries.(like '@@identity' in sybase). There is an identity function but you can't use it for thinks like SELECT * FROM "GaGa" WHERE "GaGaId"=@@identity which is usefull to fetch the last inserted row with autoincrement or identity column Cheers S., Fred Toussi wrote: > Thanks Sebastian, > > It all sounds good. I will check this within a day or so and get back to > you. > > Regards > > Fred > > ----- Original Message ----- > From: "Sebastian Kloska" <[EMAIL PROTECTED]> > To: "Fred Toussi" <[EMAIL PROTECTED]> > Sent: 28 October 2002 13:36 > Subject: Re: [Hsqldb-developers] ON UPDATE CASCADE > > > Find attached my work > on the 'ON UPDATE CASCADE' stuff. > > The centerpiece still is Table.checkCascadeUpdate > which like checkCascadeDelete tries to check/update > referential integrity. > > Now is checks all Constraint. > > (1) Those refering to main tables are checked > if they still hold a valid row supporting the > new data (via the new method Constraint.findMainRef). > > (2) Those refering to foreign key are checked if there > are references to the old table. If so we check if the > constraint supports 'ON UPDATE CASCADE'. We then > recursively try to propagate the change through the > tree of tables. > > > I already try to restrict my search to those column which > are actually changed. For this purpose I have added to new > methods to ArrayUtil which give me the common column indexes. > > countCommonElements & commonElements > > > > I've checked my files with the latest CVS distribution and > it seems to work. > > Cheers > > Sebastian > > > Fred Toussi wrote: > >>Thanks Sebastian, >> >>findMainRef() would probably be a subset of Constraint.checkInsert() > > without > >>the other checks. Once you get the general framework working, we can >>optimise it to use subsets of modified columns and avoid unnecessary > > checks > >>for columns that have not changed in the update or those that are not part >>of any FK constrainst. >> >>Later, I am planning to change the internal handling of updates so that we >>don't do a delete and an insert for each update. We will make the ON > > UPDATE > >>CASCADE compatible with such a change. >> >>I will correct the error in DatabaseScript. >> >>Regards >> >>Fred > > > -- > ********************************** > Dr. Sebastian Kloska > Head of Bioinformatics > Scienion AG > Volmerstr. 7a > 12489 Berlin > phone: +49-(30)-6392-1708 > fax: +49-(30)-6392-1701 > http://www.scienion.de > ********************************** > -- ********************************** Dr. Sebastian Kloska Head of Bioinformatics Scienion AG Volmerstr. 7a 12489 Berlin phone: +49-(30)-6392-1708 fax: +49-(30)-6392-1701 http://www.scienion.de ********************************** ---------------------------------------------------------------------------- ---- /* Copyrights and Licenses * * This product includes Hypersonic SQL. * Originally developed by Thomas Mueller and the Hypersonic SQL Group. * * Copyright (c) 1995-2000 by the Hypersonic SQL Group. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * - All advertising materials mentioning features or use of this software must display the * following acknowledgment: "This product includes Hypersonic SQL." * - Products derived from this software may not be called "Hypersonic SQL" nor may * "Hypersonic SQL" appear in their names without prior written permission of the * Hypersonic SQL Group. * - Redistributions of any form whatsoever must retain the following acknowledgment: "This * product includes Hypersonic SQL." * This software is provided "as is" and any expressed or implied warranties, including, but * not limited to, the implied warranties of merchantability and fitness for a particular purpose are * disclaimed. In no event shall the Hypersonic SQL Group or its contributors be liable for any * direct, indirect, incidental, special, exemplary, or consequential damages (including, but * not limited to, procurement of substitute goods or services; loss of use, data, or profits; * or business interruption). However caused any on any theory of liability, whether in contract, * strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this * software, even if advised of the possibility of such damage. * This software consists of voluntary contributions made by many individuals on behalf of the * Hypersonic SQL Group. * * * For work added by the HSQL Development Group: * * Copyright (c) 2001-2002, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer, including earlier * license statements (above) and comply with all above license conditions. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution, including earlier * license statements (above) and comply with all above license conditions. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb; import org.hsqldb.lib.ArrayUtil; import org.hsqldb.lib.HsqlArrayList; import org.hsqldb.lib.HsqlHashMap; import org.hsqldb.lib.StringUtil; import java.sql.SQLException; import java.sql.Types; // fredt@users 20020405 - patch 1.7.0 by fredt - quoted identifiers // for sql standard quoted identifiers for column and table names and aliases // applied to different places // fredt@users 20020225 - patch 1.7.0 - restructuring // some methods moved from Database.java, some rewritten // changes to several methods // fredt@users 20020225 - patch 1.7.0 - CASCADING DELETES // fredt@users 20020225 - patch 1.7.0 - named constraints // boucherb@users 20020225 - patch 1.7.0 - multi-column primary keys // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP) // tony_lai@users 20020820 - patch 595099 by - user defined PK name // tony_lai@users 20020820 - patch 595172 by - drop constraint fix /** * Holds the data structures and methods for creation of a database table. * * * @version 1.7.0 */ class Table { // types of table static final int SYSTEM_TABLE = 0; static final int TEMP_TABLE = 1; static final int MEMORY_TABLE = 2; static final int CACHED_TABLE = 3; static final int TEMP_TEXT_TABLE = 4; static final int TEXT_TABLE = 5; static final int VIEW = 6; // name of the column added to tables without primary key static final String DEFAULT_PK = ""; // main properties private HsqlArrayList vColumn; // columns in table private HsqlArrayList vIndex; // vIndex(0) is the primary key index private int[] iPrimaryKey; // column numbers for primary key private int iIndexCount; // size of vIndex private int iIdentityColumn; // -1 means no such row private int iIdentityId; // next value of identity column HsqlArrayList vConstraint; // constrainst for the table HsqlArrayList vTrigs[]; // array of trigger lists private int[] colTypes; // fredt - types of columns private boolean isSystem; private boolean isText; private boolean isView; // properties for subclasses protected int iColumnCount; // inclusive the hidden primary key protected int iVisibleColumns; // exclusive of hidden primary key protected Database dDatabase; protected Cache cCache; protected HsqlName tableName; // SQL name protected int tableType; protected Session ownerSession; // fredt - set for temp tables only protected boolean isReadOnly; protected boolean isTemp; protected boolean isCached; protected int indexType; // fredt - type of index used /** * Constructor declaration * * @param db * @param isTemp * @param name * @param cached * @param nameQuoted Description of the Parameter * @exception SQLException Description of the Exception */ Table(Database db, HsqlName name, int type, Session session) throws SQLException { dDatabase = db; if (type == SYSTEM_TABLE) { isTemp = true; } else if (type == TEMP_TABLE) { Trace.doAssert(session != null); isTemp = true; ownerSession = session; } else if (type == CACHED_TABLE) { cCache = db.logger.getCache(); if (cCache != null) { isCached = true; } else { type = MEMORY_TABLE; } } else if (type == TEMP_TEXT_TABLE) { Trace.doAssert(session != null); if (!db.logger.hasLog()) { throw Trace.error(Trace.DATABASE_IS_MEMORY_ONLY); } isTemp = true; isText = true; isReadOnly = true; isCached = true; ownerSession = session; } else if (type == TEXT_TABLE) { if (!db.logger.hasLog()) { throw Trace.error(Trace.DATABASE_IS_MEMORY_ONLY); } isText = true; isCached = true; } else if (type == VIEW) { isView = true; } if (isText) { indexType = Index.POINTER_INDEX; } else if (isCached) { indexType = Index.DISK_INDEX; } // type may have changed above for CACHED tables tableType = type; tableName = name; iPrimaryKey = null; iIdentityColumn = -1; vColumn = new HsqlArrayList(); vIndex = new HsqlArrayList(); vConstraint = new HsqlArrayList(); vTrigs = new HsqlArrayList[TriggerDef.numTrigs()]; for (int vi = 0; vi < TriggerDef.numTrigs(); vi++) { vTrigs[vi] = new HsqlArrayList(); } } boolean equals(String other, Session c) { if (isTemp && c.getId() != ownerSession.getId()) { return false; } return (tableName.name.equals(other)); } boolean equals(String other) { return (tableName.name.equals(other)); } final boolean isText() { return isText; } final boolean isTemp() { return isTemp; } final boolean isView() { return isView; } final int getIndexType() { return indexType; } final boolean isDataReadOnly() { return isReadOnly; } void setDataReadOnly(boolean value) throws SQLException { isReadOnly = value; } Session getOwnerSession() { return ownerSession; } protected void setDataSource(String source, boolean isDesc, Session s) throws SQLException { // Same exception as setIndexRoots. throw (Trace.error(Trace.TABLE_NOT_FOUND)); } protected String getDataSource() throws SQLException { return null; } protected boolean isDescDataSource() throws SQLException { return (false); } /** * Method declaration * * @param c */ void addConstraint(Constraint c) { vConstraint.add(c); } /** * Method declaration * * @return */ HsqlArrayList getConstraints() { return vConstraint; } /** * Get the index supporting a constraint that can be used as an index * of the given type and index column signature. * * @param col column list array * @param unique for the index * @return */ Index getConstraintIndexForColumns(int[] col, boolean unique) { Index currentIndex = getPrimaryIndex(); if (ArrayUtil.haveEquality(currentIndex.getColumns(), col, col.length, unique)) { return currentIndex; } for (int i = 0; i < vConstraint.size(); i++) { Constraint c = (Constraint) vConstraint.get(i); currentIndex = c.getMainIndex(); if (ArrayUtil.haveEquality(currentIndex.getColumns(), col, col.length, unique)) { return currentIndex; } } return null; } /** * Method declaration * * @param from * @param type * @return */ int getNextConstraintIndex(int from, int type) { for (int i = from; i < vConstraint.size(); i++) { Constraint c = (Constraint) vConstraint.get(i); if (c.getType() == type) { return i; } } return -1; } /** * Method declaration * * @param name * @param type * @throws SQLException */ void addColumn(String name, int type) throws SQLException { Column column = new Column(new HsqlName(name, false), true, type, 0, 0, false, false, null); addColumn(column); } // fredt@users 20020220 - patch 475199 - duplicate column /** * Performs the table level checks and adds a column to the table at the * DDL level. * * @param column new column to add * @throws SQLException when table level checks fail */ void addColumn(Column column) throws SQLException { if (searchColumn(column.columnName.name) >= 0) { throw Trace.error(Trace.COLUMN_ALREADY_EXISTS); } if (column.isIdentity()) { Trace.check(column.getType() == Types.INTEGER, Trace.WRONG_DATA_TYPE, column.columnName.name); Trace.check(iIdentityColumn == -1, Trace.SECOND_PRIMARY_KEY, column.columnName.name); iIdentityColumn = iColumnCount; } Trace.doAssert(iPrimaryKey == null, "Table.addColumn"); vColumn.add(column); iColumnCount++; } /** * Method declaration * * @param result * @throws SQLException */ void addColumns(Result result) throws SQLException { for (int i = 0; i < result.getColumnCount(); i++) { Column column = new Column( new HsqlName(result.sLabel[i], result.isLabelQuoted[i]), true, result.colType[i], result.colSize[i], result.colScale[i], false, false, null); addColumn(column); } } /** * Method declaration * * @return */ HsqlName getName() { return tableName; } /** * Changes table name. Used by 'alter table rename to' * * @param name * @param isquoted * @throws SQLException */ void setName(String name, boolean isquoted) { tableName.rename(name, isquoted); if (HsqlName.isReservedName(getPrimaryIndex().getName().name)) { getPrimaryIndex().getName().rename("SYS_PK", name, isquoted); } } /** * Method declaration * * @return */ int getInternalColumnCount() { // todo: this is a temporary solution; // the the hidden column is not really required return iColumnCount; } protected Table duplicate() throws SQLException { Table t = (new Table(dDatabase, tableName, tableType, ownerSession)); return t; } /** * Match two columns arrays for length and type of coluns * * @param col column array from this Table * @param other the other Table object * @param othercol column array from the other Table * @throws SQLException if there is a mismatch */ void checkColumnsMatch(int[] col, Table other, int[] othercol) throws SQLException { if (col.length != othercol.length) { throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH); } for (int i = 0; i < col.length; i++) { // integrity check - should not throw in normal operation if (col[i] >= iColumnCount || othercol[i] >= other.iColumnCount) { throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH); } if (getColumn(col[i]).getType() != other.getColumn(othercol[i]).getType()) { throw Trace.error(Trace.COLUMN_TYPE_MISMATCH); } } } // fredt@users 20020405 - patch 1.7.0 by fredt - DROP and CREATE INDEX bug /** * DROP INDEX and CREATE INDEX on non empty tables both recreate the table * and the data to reflect the new indexing structure. The new structure * should be reflected in the DDL script, otherwise if a * SHUTDOWN IMMEDIATE occures, the following will happen:<br> * If the table is cached, the index roots will be different from what * is specified in SET INDEX ROOTS. <br> * If the table is memory, the old index will be used until the script * reaches drop index etc. and data is recreated again.<b> * * The fix avoids scripting the row insert and delete ops. * * Constraints that need removing are removed outside this (fredt@users) * @param withoutindex * @param newcolumn * @param colindex * @param adjust -1 or 0 or +1 * @return * @throws SQLException */ Table moveDefinition(String withoutindex, Column newcolumn, int colindex, int adjust) throws SQLException { Table tn = duplicate(); for (int i = 0; i < iVisibleColumns + 1; i++) { if (i == colindex) { if (adjust > 0) { tn.addColumn(newcolumn); } else if (adjust < 0) { continue; } } if (i == iVisibleColumns) { break; } tn.addColumn(getColumn(i)); } // treat it the same as new table creation and // take account of the a hidden column int[] primarykey = (iPrimaryKey[0] == iVisibleColumns) ? null : iPrimaryKey; if (primarykey != null) { int[] newpk = ArrayUtil.toAdjustedColumnArray(primarykey, colindex, adjust); // fredt - we don't drop pk column // in future we can drop signle column pk wih no fk reference if (primarykey.length != newpk.length) { throw Trace.error(Trace.DROP_PRIMARY_KEY); } else { primarykey = newpk; } } // tony_lai@users - 20020820 - patch 595099 - primary key names tn.createPrimaryKey(getIndex(0).getName(), primarykey); tn.vConstraint = vConstraint; for (int i = 1; i < getIndexCount(); i++) { Index idx = getIndex(i); if (withoutindex != null && idx.getName().name.equals(withoutindex)) { continue; } Index newidx = tn.createAdjustedIndex(idx, colindex, adjust); if (newidx == null) { // fredt - todo - better error message throw Trace.error(Trace.INDEX_ALREADY_EXISTS); } } return tn; } void updateConstraints(Table to, int colindex, int adjust) throws SQLException { for (int j = 0; j < vConstraint.size(); j++) { Constraint c = (Constraint) vConstraint.get(j); c.replaceTable(to, this, colindex, adjust); } } /** * Method declaration * * @return */ int getColumnCount() { return iVisibleColumns; } /** * Method declaration * * @return */ int getIndexCount() { return iIndexCount; } /** * Method declaration * * @return */ int getIdentityColumn() { return iIdentityColumn; } /** * Method declaration * * @param c * @return * @throws SQLException */ int getColumnNr(String c) throws SQLException { int i = searchColumn(c); if (i == -1) { throw Trace.error(Trace.COLUMN_NOT_FOUND, c); } return i; } /** * Method declaration * * @param c * @return */ int searchColumn(String c) { for (int i = 0; i < iColumnCount; i++) { if (c.equals(((Column) vColumn.get(i)).columnName.name)) { return i; } } return -1; } /** * Method declaration * * @return * @throws SQLException */ Index getPrimaryIndex() { if (iPrimaryKey == null) { return null; } return getIndex(0); } /** * Method declaration * * @param column * @return * @throws SQLException */ Index getIndexForColumn(int column) throws SQLException { for (int i = 0; i < iIndexCount; i++) { Index h = getIndex(i); if (h.getColumns()[0] == column) { return h; } } return null; } /** * Finds an existing index for a foreign key column group * * @param col * @return * @throws SQLException */ Index getIndexForColumns(int col[], boolean unique) throws SQLException { for (int i = 0; i < iIndexCount; i++) { Index currentindex = getIndex(i); int indexcol[] = currentindex.getColumns(); if (ArrayUtil.haveEquality(indexcol, col, col.length, unique)) { if (!unique || currentindex.isUnique()) { return currentindex; } } } return null; } /** * Return the list of file pointers to root nodes for this table's * indexes. */ int[] getIndexRootsArray() throws SQLException { int[] roots = new int[iIndexCount]; for (int i = 0; i < iIndexCount; i++) { Node f = getIndex(i).getRoot(); roots[i] = (f != null) ? f.getKey() : -1; } return roots; } /** * Method declaration * * @return * @throws SQLException */ String getIndexRoots() throws SQLException { Trace.doAssert(isCached, "Table.getIndexRootData"); String roots = StringUtil.getList(getIndexRootsArray(), " ", ""); StringBuffer s = new StringBuffer(roots); s.append(' '); s.append(iIdentityId); return s.toString(); } /** * Method declaration * * @param s * @throws SQLException */ void setIndexRoots(int[] roots) throws SQLException { Trace.check(isCached, Trace.TABLE_NOT_FOUND); for (int i = 0; i < iIndexCount; i++) { int p = roots[i]; Row r = null; if (p != -1) { r = cCache.getRow(p, this); } Node f = null; if (r != null) { f = r.getNode(i); } getIndex(i).setRoot(f); } } /** * Method declaration * * @param s * @throws SQLException */ void setIndexRoots(String s) throws SQLException { // the user may try to set this; this is not only internal problem Trace.check(isCached, Trace.TABLE_NOT_FOUND); int[] roots = new int[iIndexCount]; int j = 0; for (int i = 0; i < iIndexCount; i++) { int n = s.indexOf(' ', j); int p = Integer.parseInt(s.substring(j, n)); roots[i] = p; j = n + 1; } setIndexRoots(roots); iIdentityId = Integer.parseInt(s.substring(j)); } /** * Method declaration * * @param index * @return */ Index getNextIndex(Index index) { int i = 0; if (index != null) { for (; i < iIndexCount && getIndex(i) != index; i++) { ; } i++; } if (i < iIndexCount) { return getIndex(i); } return null; // no more indexes } /** * Shortcut for creating default PK's * * @throws SQLException */ void createPrimaryKey() throws SQLException { // tony_lai@users 20020820 - patch 595099 createPrimaryKey(null, null); } /** * Adds the SYSTEM_ID column if no primary key is specified in DDL. * Creates a single or multi-column primary key and index. sets the * colTypes array. Finalises the creation of the table. (fredt@users) * * @param columns primary key column(s) or null if no primary key in DDL * @throws SQLException */ // tony_lai@users 20020820 - patch 595099 void createPrimaryKey(HsqlName pkName, int[] columns) throws SQLException { Trace.doAssert(iPrimaryKey == null, "Table.createPrimaryKey(column)"); iVisibleColumns = iColumnCount; if (columns == null) { columns = new int[]{ iColumnCount }; Column column = new Column(new HsqlName(DEFAULT_PK, false), false, Types.INTEGER, 0, 0, true, true, null); addColumn(column); } else { for (int i = 0; i < columns.length; i++) { getColumn(columns[i]).setNullable(false); getColumn(columns[i]).setPrimaryKey(true); } } iPrimaryKey = columns; // tony_lai@users 20020820 - patch 595099 HsqlName name = pkName != null ? pkName : new HsqlName("SYS_PK", tableName.name, tableName.isNameQuoted); createIndexPrivate(columns, name, true); colTypes = new int[iColumnCount]; for (int i = 0; i < iColumnCount; i++) { colTypes[i] = getColumn(i).getType(); } } /** * Create new index taking into account removal or addition a column of * the table. * * @param index * @param colindex * @param ajdust -1 or 0 or 1 * @return new index or null if a column is removed from index * @throws SQLException */ private Index createAdjustedIndex(Index index, int colindex, int adjust) throws SQLException { int[] colarr = ArrayUtil.getAdjustedColumnArray(index.getColumns(), index.getVisibleColumns(), colindex, adjust); if (colarr.length != index.getVisibleColumns()) { return null; } return createIndexPrivate(colarr, index.getName(), index.isUnique()); } /** * Method declaration * * @param column * @param name * @param unique * @return Description of the Return Value * @throws SQLException */ Index createIndexPrivate(int column[], HsqlName name, boolean unique) throws SQLException { Trace.doAssert(iPrimaryKey != null, "createIndex"); int s = column.length; int t = iPrimaryKey.length; // The primary key field is added for non-unique indexes // making all indexes unique int col[] = new int[unique ? s : s + t]; int type[] = new int[unique ? s : s + t]; for (int j = 0; j < s; j++) { col[j] = column[j]; type[j] = getColumn(col[j]).getType(); } if (!unique) { for (int j = 0; j < t; j++) { col[s + j] = iPrimaryKey[j]; type[s + j] = getColumn(iPrimaryKey[j]).getType(); } } // fredt - visible columns of index is 0 for system generated PK if (col[0] == iVisibleColumns) { s = 0; } Index newindex = new Index(name, this, col, type, unique, s); // fredt@users 20020225 - comment // in future we can avoid duplicate indexes /* for (int i = 0; i < iIndexCount; i++) { if ( newindex.isEquivalent(getIndex(i))){ return; } } */ Trace.doAssert(isEmpty(), "createIndex"); vIndex.add(newindex); iIndexCount++; return newindex; } // fredt@users 20020315 - patch 1.7.0 - drop index bug // don't drop an index used for a foreign key /** * Checks for use of a named index in table constraints * * @param indexname * @param ignore null or a set of constraints that should be ignored in checks * @throws SQLException if index is used in a constraint */ void checkDropIndex(String indexname, HsqlHashMap ignore) throws SQLException { Index index = this.getIndex(indexname); if (index == null) { throw Trace.error(Trace.INDEX_NOT_FOUND, indexname); } if (index.equals(getIndex(0))) { throw Trace.error(Trace.DROP_PRIMARY_KEY, indexname); } for (int i = 0; i < vConstraint.size(); i++) { Constraint c = (Constraint) vConstraint.get(i); if (ignore.get(c) != null) { continue; } if (c.isIndexFK(index)) { throw Trace.error(Trace.DROP_FK_INDEX, indexname); } if (c.isIndexUnique(index)) { throw Trace.error(Trace.SYSTEM_INDEX, indexname); } } return; } /** * Method declaration * * @return */ boolean isEmpty() { if (iIndexCount == 0) { return true; } return getIndex(0).getRoot() == null; } /** * Method declaration * * @return */ Object[] getNewRow() { return new Object[iColumnCount]; } /** * Method declaration * * @param from * @param colindex index of the column that was added or removed * @throws SQLException normally for lack of resources */ void moveData(Table from, int colindex, int adjust) throws SQLException { Object colvalue = null; if (adjust > 0) { Column column = getColumn(colindex); colvalue = Column.convertObject(column.getDefaultString(), column.getType()); } Index index = from.getPrimaryIndex(); Node n = index.first(); while (n != null) { if (Trace.STOP) { Trace.stop(); } Object o[] = n.getData(); Object newrow[] = this.getNewRow(); ArrayUtil.copyAdjustArray(o, newrow, colvalue, colindex, adjust); insertNoCheck(newrow, null, false); n = index.next(n); } index = from.getPrimaryIndex(); n = index.first(); while (n != null) { if (Trace.STOP) { Trace.stop(); } Node nextnode = index.next(n); Object o[] = n.getData(); from.deleteNoCheck(o, null, false); n = nextnode; } } /** * Method declaration * * @param col * @param deleted * @param inserted * @throws SQLException */ void checkUpdate(int col[], Result deleted, Result inserted) throws SQLException { Trace.check(!isReadOnly, Trace.DATA_IS_READONLY); if (dDatabase.isReferentialIntegrity()) { for (int i = 0; i < vConstraint.size(); i++) { Constraint v = (Constraint) vConstraint.get(i); v.checkUpdate(col, deleted, inserted); } } } /** * Method declaration * * @param result * @param c * @throws SQLException */ void insert(Result result, Session c) throws SQLException { // if violation of constraints can occur, insert must be rolled back // outside of this function! Record r = result.rRoot; int len = result.getColumnCount(); while (r != null) { Object row[] = getNewRow(); for (int i = 0; i < len; i++) { row[i] = r.data[i]; } insert(row, c); r = r.next; } } /** * Method declaration * * @param row * @param c * @throws SQLException */ void insert(Object row[], Session c) throws SQLException { Trace.check(!isReadOnly, Trace.DATA_IS_READONLY); fireAll(TriggerDef.INSERT_BEFORE, row); if (dDatabase.isReferentialIntegrity()) { for (int i = 0; i < vConstraint.size(); i++) { ((Constraint) vConstraint.get(i)).checkInsert(row); } } insertNoCheck(row, c, true); fireAll(TriggerDef.INSERT_AFTER, row); } /** * Method declaration * * @param row * @param c * @param log * @throws SQLException */ void insertNoCheck(Object row[], Session c, boolean log) throws SQLException { for (int i = 0; i < iColumnCount; i++) { if (row[i] == null) { Column col = getColumn(i); boolean nullOK = col.isNullable() || col.isIdentity(); if (!nullOK) { throw Trace.error(Trace.TRY_TO_INSERT_NULL); } } } int nextId = iIdentityId; if (iIdentityColumn != -1) { Number id = (Number) row[iIdentityColumn]; if (id == null) { row[iIdentityColumn] = new Integer(iIdentityId); } else { int columnId = id.intValue(); if (iIdentityId < columnId) { iIdentityId = nextId = columnId; } } } Row r = Row.newRow(this, row); if (isText) { //-- Always inserted at end of file. nextId = ((CachedRow) r).iPos + ((CachedRow) r).storageSize; } else { nextId++; } indexRow(r, true); if (c != null) { c.setLastIdentity(iIdentityId); c.addTransactionInsert(this, row); } iIdentityId = nextId; if (log &&!isTemp &&!isReadOnly && dDatabase.logger.hasLog()) { dDatabase.logger.writeToLog(c, getInsertStatement(row)); } } /** * Method declaration * * @param trigVecIndx * @param row */ void fireAll(int trigVecIndx, Object row[]) { if (!dDatabase.isReferentialIntegrity()) { // reloading db return; } HsqlArrayList trigVec = vTrigs[trigVecIndx]; int trCount = trigVec.size(); for (int i = 0; i < trCount; i++) { TriggerDef td = (TriggerDef) trigVec.get(i); td.push(row); // tell the trigger thread to fire with this row } } // statement-level triggers /** * Method declaration * * @param trigVecIndx */ void fireAll(int trigVecIndx) { Object row[] = new Object[1]; row[0] = new String("Statement-level"); fireAll(trigVecIndx, row); } /** * Method declaration * * @param trigDef */ void addTrigger(TriggerDef trigDef) { if (Trace.TRACE) { Trace.trace("Trigger added " + String.valueOf(trigDef.vectorIndx)); } vTrigs[trigDef.vectorIndx].add(trigDef); } // fredt@users 20020225 - patch 1.7.0 - CASCADING DELETES /** * Method is called recursively on a tree of tables from the current one * until no referring foreign-key table is left. In the process, if a * non-cascading foreign-key referring table contains data, an exception * is thrown. Parameter delete indicates whether to delete refering rows. * The method is called first to check if the row can be deleted, then to * delete the row and all the refering rows. (fredt@users) * * @param row * @param session * @param delete * @throws SQLException */ void checkCascadeDelete(Object[] row, Session session, boolean delete) throws SQLException { for (int i = 0; i < vConstraint.size(); i++) { Constraint c = (Constraint) vConstraint.get(i); if (c.getType() != Constraint.MAIN || c.getRef() == null) { continue; } Node refnode = c.findFkRef(row, true); if (refnode == null) { // no referencing row found continue; } Table reftable = c.getRef(); // shortcut when deltable has no imported constraint boolean hasref = reftable.getNextConstraintIndex(0, Constraint.MAIN) != -1; if (delete == false && hasref == false) { return; } Index refindex = c.getRefIndex(); int maincolumns[] = c.getMainColumns(); Object[] mainobjects = new Object[maincolumns.length]; ArrayUtil.copyColumnValues(row, maincolumns, mainobjects); // walk the index for all the nodes that reference delnode for (Node n = refnode; refindex.comparePartialRowNonUnique( mainobjects, n.getData()) == 0; ) { // get the next node before n is deleted Node nextn = refindex.next(n); if (hasref) { reftable.checkCascadeDelete(n.getData(), session, delete); } if (delete) { reftable.deleteNoRefCheck(n.getData(), session); // foreign key referencing own table if (reftable == this) { nextn = c.findFkRef(row, true); } } if (nextn == null) { break; } n = nextn; } } } /** Check or perform and update cascade operation on a single row. Check or cascade an update (delete/insert) operation. The method takes a pair of rows (new data,old data) and checks if <code>Constraints</code> permit the update operation. A boolean arguement determines if the operation should realy take place or if we just have to check for constraint violation. @param orow Object[]; old row data to be deleted. @param nroe Object[]; new roe data to be inserted. @param session Session; current database session @param cols int[]; indices of the columns actually changed. @param ref Table; This should be initialized to <code>null</code> when the method is called from the 'soutside'. During recursion this will be the current table (i.e. <code>this</code>) to indicate from where we came. Foreign keys to this table do not have to be checked since they have triggered the update and are valid <i>per definitionem</i>. @param update boolean; if true the update will take place. @short Check or perform and update cascade operation on a single row. */ void checkCascadeUpdate(Object[] orow,Object[] nrow,Session session,int[] cols,Table ref,boolean update) throws SQLException { int[] common; // -- common indexes of the changed columns and the main/ref constraint // -- We iterate through all constraints associated with this table // -- for (int i = 0; i<vConstraint.size() ; i++) { Constraint c = (Constraint) vConstraint.get(i); if(c.getType() == Constraint.FOREIGN_KEY && c.getRef()!=null) { // -- (1) If it is a foreign key constraint we have to check if the // -- main table still holds a record which allows the new values // -- to be set in the updated columns. This test however will be // -- skipped if the reference table is the main table since changes // -- in the reference table triggered the update and therefor // -- the referential integrity is guaranteed to be valid. // -- if( ref==null || c.getMain()!=ref) { if( (common = ArrayUtil.commonElements(cols , c.getRefColumns()) ) == null ) { continue; } Node n=c.findMainRef(nrow); } else if(ref!=null) { //System.err.println(" ## Table::checkCascadeUpdate -- skipping FK ref to '" + ref.getName().name + "'; we came from there"); } } else if(c.getType() == Constraint.MAIN && c.getRef()!=null) { // -- (2) If it happens to be a main constraint we check if the slave // -- table holds any records refering to the old contents. If so // -- the constraint has to suppert an 'on update' action or we // -- throw an exception (all via a call to Constraint.findFkRef). // -- // -- if there are no common columns between the reference constraint // -- and the changed columns we reiterate. if( (common = ArrayUtil.commonElements(cols,c.getMainColumns()) ) == null ) { continue; } Node refnode = c.findFkRef(orow,false); if (refnode == null) { // no referencing row found continue; } Table reftable = c.getRef(); // shortcut when deltable has no imported constraint boolean hasref = reftable.getNextConstraintIndex(0, Constraint.MAIN) != -1; Index refindex = c.getRefIndex(); int maincolumns[] = c.getMainColumns(); int refcolumns[] = c.getRefColumns(); Object[] mainobjects = new Object[maincolumns.length]; Object[] refobjects = new Object[refcolumns.length]; ArrayUtil.copyColumnValues(orow, maincolumns, mainobjects); ArrayUtil.copyColumnValues(nrow, refcolumns, refobjects); // walk the index for all the nodes that reference update node Result ri = new Result(); for (Node n = refnode;refindex.comparePartialRowNonUnique(mainobjects, n.getData()) == 0; ) { // -- get the next node before n is deleted Node nextn = refindex.next(n); Object[] rnd = reftable.getNewRow(); System.arraycopy(n.getData(),0,rnd,0,rnd.length); for(int j=0;j<maincolumns.length;j++) { rnd[refcolumns[j]] = nrow[maincolumns[j]]; } reftable.checkCascadeUpdate(n.getData(),rnd,session,common,this,update); if(update) { ri.add(rnd); reftable.deleteNoRefCheck(n.getData(),session); if (reftable == this) { nextn = c.findFkRef(orow,false); } } if (nextn == null) { break; } n = nextn; } if(update) { Record r = ri.rRoot; while(r!=null) { reftable.insertNoCheck(r.data, session, true); r=r.next; } } } } } /** * Method declaration * * @param row * @param session Description of the Parameter * @throws SQLException */ void delete(Object row[], Session session) throws SQLException { fireAll(TriggerDef.DELETE_BEFORE_ROW, row); if (dDatabase.isReferentialIntegrity()) { checkCascadeDelete(row, session, false); checkCascadeDelete(row, session, true); } deleteNoCheck(row, session, true); // fire the delete after statement trigger fireAll(TriggerDef.DELETE_AFTER_ROW, row); } /** * Method declaration * * @param row * @param session Description of the Parameter * @throws SQLException */ private void deleteNoRefCheck(Object row[], Session session) throws SQLException { fireAll(TriggerDef.DELETE_BEFORE_ROW, row); deleteNoCheck(row, session, true); // fire the delete after statement trigger fireAll(TriggerDef.DELETE_AFTER_ROW, row); } /** * Method declaration * * @param row * @param c * @param log * @throws SQLException */ void deleteNoCheck(Object row[], Session c, boolean log) throws SQLException { for (int i = 1; i < iIndexCount; i++) { getIndex(i).delete(row, false); } // must delete data last getIndex(0).delete(row, true); if (c != null) { c.addTransactionDelete(this, row); } if (log &&!isTemp &&!isReadOnly && dDatabase.logger.hasLog()) { dDatabase.logger.writeToLog(c, getDeleteStatement(row)); } } /** * Method declaration * * @param row * @return * @throws SQLException */ String getInsertStatement(Object row[]) throws SQLException { StringBuffer a = new StringBuffer(128); a.append("INSERT INTO "); a.append(tableName.statementName); a.append(" VALUES("); for (int i = 0; i < iVisibleColumns; i++) { a.append(Column.createSQLString(row[i], getColumn(i).getType())); a.append(','); } a.setCharAt(a.length() - 1, ')'); return a.toString(); } /** * Method declaration * * @return */ boolean isCached() { return isCached; } /** * Method declaration * * @return */ boolean isIndexCached() { return isCached; } /** * Method declaration * * @param s * @return */ Index getIndex(String s) { for (int i = 0; i < iIndexCount; i++) { Index h = getIndex(i); if (s.equals(h.getName().name)) { return h; } } // no such index return null; } /** * Return the position of the constraint within the list * * @param s * @return */ int getConstraintIndex(String s) { for (int j = 0; j < vConstraint.size(); j++) { Constraint tempc = (Constraint) vConstraint.get(j); if (tempc.getName().name.equals(s)) { return j; } } return -1; } /** * return the named constriant * * @param s * @return */ Constraint getConstraint(String s) { int j = getConstraintIndex(s); if (j >= 0) { return (Constraint) vConstraint.get(j); } else { return null; } } /** * Method declaration * * @param i * @return */ Column getColumn(int i) { return (Column) vColumn.get(i); } /** * Method declaration * * @return */ int[] getColumnTypes() { return colTypes; } /** * Method declaration * * @param i * @return */ protected Index getIndex(int i) { return (Index) vIndex.get(i); } /** * Method declaration * * @param row * @return * @throws SQLException */ private String getDeleteStatement(Object row[]) throws SQLException { StringBuffer a = new StringBuffer(128); a.append("DELETE FROM "); a.append(tableName.statementName); a.append(" WHERE "); if (iVisibleColumns < iColumnCount) { for (int i = 0; i < iVisibleColumns; i++) { Column c = getColumn(i); a.append(c.columnName.statementName); a.append('='); a.append(Column.createSQLString(row[i], c.getType())); if (i < iVisibleColumns - 1) { a.append(" AND "); } } } else { for (int i = 0; i < iPrimaryKey.length; i++) { Column c = getColumn(iPrimaryKey[i]); a.append(c.columnName.statementName); a.append('='); a.append(Column.createSQLString(row[iPrimaryKey[i]], c.getType())); if (i < iPrimaryKey.length - 1) { a.append(" AND "); } } } return a.toString(); } /** * Method declaration * * @param pos * @return * @throws SQLException */ Row getRow(int pos) throws SQLException { if (isCached) { return (cCache.getRow(pos, this)); } return null; } void putRow(CachedRow r) throws SQLException { int size = 0; if (cCache != null) { cCache.add(r); } } void removeRow(CachedRow r) throws SQLException { if (cCache != null) { cCache.free(r); } } void cleanUp() throws SQLException { if (cCache != null) { cCache.cleanUp(); } } void indexRow(Row r, boolean inserted) throws SQLException { if (inserted) { int i = 0; try { Node n = null; for (; i < iIndexCount; i++) { n = r.getNextNode(n); getIndex(i).insert(n); } } catch (SQLException e) { // rollback insert for (--i; i >= 0; i--) { getIndex(i).delete(r.getData(), i == 0); } throw e; // and throw error again } } } } ------------------------------------------------------- This sf.net email is sponsored by:ThinkGeek Welcome to geek heaven. http://thinkgeek.com/sf _______________________________________________ hsqldb-developers mailing list [EMAIL PROTECTED] https://lists.sourceforge.net/lists/listinfo/hsqldb-developers