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

Reply via email to