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
            }
        }
    }
}

Reply via email to