StatementDML.java

/* Copyright (c) 2001-2024, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb;

import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.ParserDQL.CompileContext;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.HashSet;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.navigator.RangeIterator;
import org.hsqldb.navigator.RowIterator;
import org.hsqldb.navigator.RowSetNavigator;
import org.hsqldb.navigator.RowSetNavigatorClient;
import org.hsqldb.navigator.RowSetNavigatorDataChange;
import org.hsqldb.persist.PersistentStore;
import org.hsqldb.result.Result;
import org.hsqldb.result.ResultConstants;
import org.hsqldb.result.ResultMetaData;
import org.hsqldb.trigger.Trigger;
import org.hsqldb.types.Type;
import org.hsqldb.types.Types;

/**
 * Implementation of Statement for DML statements.<p>
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.7.3
 * @since 1.9.0
 */

// support for MERGE statement originally contributed by Justin Spadea (jzs9783@users dot sourceforge.net)
public class StatementDML extends StatementDMQL {

    Expression[] targets;
    boolean      isTruncate;
    boolean      isMergeDeleteFirst;

    //
    Expression mergeInsertCondition;
    Expression mergeUpdateCondition;
    Expression mergeDeleteCondition;

    //
    boolean        isSimpleInsert;
    int            generatedType;
    ResultMetaData generatedInputMetaData;

    //
    SortAndSlice sortAndSlice;

    /** column indexes for generated values */
    int[] generatedIndexes;

    /** ResultMetaData for generated values */
    ResultMetaData generatedResultMetaData;

    public StatementDML(int type, HsqlName schemaName) {
        super(type, StatementTypes.X_SQL_DATA_CHANGE, schemaName);
    }

    /**
     * Instantiate this as a DELETE statement
     */
    StatementDML(
            Session session,
            Table targetTable,
            RangeVariable targetRange,
            RangeVariable[] rangeVars,
            CompileContext compileContext,
            boolean restartIdentity,
            int type,
            SortAndSlice sortAndSlice) {

        super(
            StatementTypes.DELETE_WHERE,
            StatementTypes.X_SQL_DATA_CHANGE,
            session.getCurrentSchemaHsqlName());

        this.targetTable          = targetTable;
        this.baseTable            = targetTable.isTriggerDeletable()
                                    ? targetTable
                                    : targetTable.getBaseTable();
        this.targetRangeVariables = rangeVars;
        this.restartIdentity      = restartIdentity;
        this.sortAndSlice         = sortAndSlice;

        setDatabaseObjects(session, compileContext);
        checkAccessRights(session);

        if (type == StatementTypes.TRUNCATE) {
            isTruncate = true;
        }

        targetRange.addAllColumns();
    }

    /**
     * Instantiate this as an UPDATE statement.
     */
    StatementDML(
            Session session,
            Expression[] targets,
            Table targetTable,
            RangeVariable targetRange,
            RangeVariable[] rangeVars,
            int[] updateColumnMap,
            Expression[] colExpressions,
            boolean[] checkColumns,
            CompileContext compileContext,
            SortAndSlice sortAndSlice) {

        super(
            StatementTypes.UPDATE_WHERE,
            StatementTypes.X_SQL_DATA_CHANGE,
            session.getCurrentSchemaHsqlName());

        this.targets              = targets;
        this.targetTable          = targetTable;
        this.baseTable            = targetTable.isTriggerUpdatable()
                                    ? targetTable
                                    : targetTable.getBaseTable();
        this.updateColumnMap      = updateColumnMap;
        this.updateExpressions    = colExpressions;
        this.updateCheckColumns   = checkColumns;
        this.targetRangeVariables = rangeVars;
        this.sortAndSlice         = sortAndSlice;

        setupChecks();
        setDatabaseObjects(session, compileContext);
        checkAccessRights(session);
        targetRange.addAllColumns();
    }

    /**
     * Instantiate this as a MERGE statement.
     */
    StatementDML(
            Session session,
            Expression[] targets,
            RangeVariable sourceRange,
            RangeVariable targetRange,
            RangeVariable[] rangeVars,
            int[] insertColMap,
            int[] updateColMap,
            boolean[] checkColumns,
            Expression mergeCondition,
            Expression insertExpr,
            Expression[] updateExpr,
            boolean deleteFirst,
            Expression insertCondition,
            Expression updateCondition,
            Expression deleteCondition,
            CompileContext compileContext) {

        super(
            StatementTypes.MERGE,
            StatementTypes.X_SQL_DATA_CHANGE,
            session.getCurrentSchemaHsqlName());

        this.targets              = targets;
        this.sourceTable          = sourceRange.rangeTable;
        this.targetTable          = targetRange.rangeTable;
        this.baseTable            = targetTable.isTriggerUpdatable()
                                    ? targetTable
                                    : targetTable.getBaseTable();
        this.insertCheckColumns   = checkColumns;
        this.insertColumnMap      = insertColMap;
        this.updateColumnMap      = updateColMap;
        this.insertExpression     = insertExpr;
        this.updateExpressions    = updateExpr;
        this.targetRangeVariables = rangeVars;
        this.condition            = mergeCondition;
        this.mergeInsertCondition = insertCondition;
        this.mergeUpdateCondition = updateCondition;
        this.mergeDeleteCondition = deleteCondition;
        this.isMergeDeleteFirst   = deleteFirst;

        setupChecks();
        setDatabaseObjects(session, compileContext);
        checkAccessRights(session);
    }

    /**
     * Instantiate this as a CURSOR operation statement.
     */
    StatementDML() {

        super(
            StatementTypes.UPDATE_CURSOR,
            StatementTypes.X_SQL_DATA_CHANGE,
            null);
    }

    void setupChecks() {

        if (targetTable != baseTable) {
            QuerySpecification select = targetTable.getQueryExpression()
                    .getMainSelect();

            this.updatableTableCheck = select.checkQueryCondition;
            this.checkRangeVariable =
                select.rangeVariables[select.rangeVariables.length - 1];
        }
    }

    Result getResult(Session session) {

        Result result = null;
        int    limit  = Integer.MAX_VALUE;

        session.getTransactionUTC();

        if (sortAndSlice != null) {
            int[] limits = sortAndSlice.getLimits(
                session,
                null,
                Integer.MAX_VALUE);

            limit = limits[1];
        }

        switch (type) {

            case StatementTypes.UPDATE_WHERE :
                result = executeUpdateStatement(session, limit);
                break;

            case StatementTypes.MERGE :
                result = executeMergeStatement(session);
                break;

            case StatementTypes.DELETE_WHERE :
                if (isTruncate) {
                    result = executeDeleteTruncateStatement(session);
                } else {
                    result = executeDeleteStatement(session, limit);
                }

                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "StatementDML");
        }

        session.sessionContext.diagnosticsVariables[ExpressionColumn.idx_row_count] =
            Integer.valueOf(
                result.getUpdateCount());

        return result;
    }

    // this fk references -> other  :  other read lock
    void collectTableNamesForRead(OrderedHashSet<HsqlName> set) {

        if (baseTable.isView()) {
            getTriggerTableNames(set, false);
        } else if (!baseTable.isTemp()) {
            for (int i = 0; i < baseTable.fkConstraints.length; i++) {
                Constraint constraint = baseTable.fkConstraints[i];

                switch (type) {

                    case StatementTypes.UPDATE_WHERE : {
                        if (ArrayUtil.haveCommonElement(
                                constraint.getRefColumns(),
                                updateColumnMap)) {
                            set.add(
                                baseTable.fkConstraints[i].getMain().getName());
                        }

                        break;
                    }

                    case StatementTypes.INSERT : {
                        set.add(baseTable.fkConstraints[i].getMain().getName());
                        break;
                    }

                    case StatementTypes.MERGE : {
                        if (updateColumnMap != null) {
                            if (ArrayUtil.haveCommonElement(
                                    constraint.getRefColumns(),
                                    updateColumnMap)) {
                                set.add(
                                    baseTable.fkConstraints[i].getMain()
                                                              .getName());
                            }
                        }

                        if (insertExpression != null) {
                            set.add(
                                baseTable.fkConstraints[i].getMain().getName());
                        }

                        break;
                    }
                }
            }

            if (type == StatementTypes.UPDATE_WHERE
                    || type == StatementTypes.MERGE) {
                baseTable.collectFKReadLocks(updateColumnMap, set);
            } else if (type == StatementTypes.DELETE_WHERE) {
                baseTable.collectFKReadLocks(null, set);
            }

            getTriggerTableNames(set, false);
        }

        for (int i = 0; i < rangeVariables.length; i++) {
            Table    rangeTable = rangeVariables[i].rangeTable;
            HsqlName name       = rangeTable.getName();

            if (rangeTable.isDataReadOnly() || rangeTable.isTemp()) {
                continue;
            }

            if (name.schema == SqlInvariants.SYSTEM_SCHEMA_HSQLNAME) {
                continue;
            }

            set.add(name);
        }

        for (int i = 0; i < subqueries.length; i++) {
            if (subqueries[i].queryExpression != null) {
                subqueries[i].queryExpression.getBaseTableNames(set);
            }
        }

        for (int i = 0; i < routines.length; i++) {
            set.addAll(routines[i].getTableNamesForRead());
        }
    }

    void collectTableNamesForWrite(OrderedHashSet<HsqlName> set) {

        // other fk references this :  if constraint trigger action  : other write lock
        if (baseTable.isView()) {
            getTriggerTableNames(set, true);
        } else if (!baseTable.isTemp()) {
            set.add(baseTable.getName());

            if (type == StatementTypes.UPDATE_WHERE
                    || type == StatementTypes.MERGE) {
                if (updateExpressions.length != 0) {
                    baseTable.collectFKWriteLocks(updateColumnMap, set);
                }
            } else if (type == StatementTypes.DELETE_WHERE) {
                baseTable.collectFKWriteLocks(null, set);
            }

            getTriggerTableNames(set, true);
        }
    }

    /*
     * @todo - fredt - low priority - this does not work with different prepare calls
     * with the same SQL statement, but different generated column requests
     * To fix, add comment encapsulating the generated column list to SQL
     * to differentiate between the two invocations
     */
    public void setGeneratedColumnInfo(int generate, ResultMetaData meta) {

        // also covers INSERT_SELECT
        switch (type) {

            case StatementTypes.INSERT :
            case StatementTypes.MERGE :
            case StatementTypes.UPDATE_WHERE :
            case StatementTypes.DELETE_WHERE :
                break;

            default :
                return;
        }

        int idColIndex = baseTable.getIdentityColumnIndex();

        generatedType          = generate;
        generatedInputMetaData = meta;

        switch (generate) {

            case ResultConstants.RETURN_NO_GENERATED_KEYS :
                return;

            case ResultConstants.RETURN_GENERATED_KEYS_COL_INDEXES :
                generatedIndexes = meta.getGeneratedColumnIndexes();

                for (int i = 0; i < generatedIndexes.length; i++) {
                    if (generatedIndexes[i] < 0
                            || generatedIndexes[i]
                               >= baseTable.getColumnCount()) {
                        throw Error.error(ErrorCode.X_42501);
                    }
                }

                break;

            case ResultConstants.RETURN_GENERATED_KEYS :
                if (baseTable.hasGeneratedColumn()) {
                    if (idColIndex >= 0) {
                        int generatedCount = ArrayUtil.countTrueElements(
                            baseTable.colGenerated) + 1;

                        generatedIndexes = new int[generatedCount];

                        for (int i = 0, j = 0;
                                i < baseTable.colGenerated.length; i++) {
                            if (baseTable.colGenerated[i] || i == idColIndex) {
                                generatedIndexes[j++] = i;
                            }
                        }
                    } else {
                        generatedIndexes = ArrayUtil.booleanArrayToIntIndexes(
                            baseTable.colGenerated);
                    }
                } else if (idColIndex >= 0) {
                    generatedIndexes = new int[]{ idColIndex };
                } else {
                    return;
                }

                break;

            case ResultConstants.RETURN_GENERATED_KEYS_COL_NAMES :
                String[] columnNames = meta.getGeneratedColumnNames();

                generatedIndexes = baseTable.findColumnIndexes(columnNames);

                for (int i = 0; i < generatedIndexes.length; i++) {
                    if (generatedIndexes[i] < 0) {
                        String upperName = columnNames[i].toUpperCase();

                        generatedIndexes[i] = baseTable.findColumn(upperName);

                        if (generatedIndexes[i] < 0) {
                            throw Error.error(
                                ErrorCode.X_42501,
                                columnNames[0]);
                        }
                    }
                }

                break;

            case ResultConstants.RETURN_PRIMARY_KEYS :
                generatedIndexes = baseTable.getPrimaryKey();
                break;
        }

        generatedResultMetaData = ResultMetaData.newResultMetaData(
            generatedIndexes.length);

        for (int i = 0; i < generatedIndexes.length; i++) {
            ColumnSchema column = baseTable.getColumn(generatedIndexes[i]);

            generatedResultMetaData.columns[i] = column;
        }

        generatedResultMetaData.prepareData();

        isSimpleInsert = false;
    }

    Object[] getGeneratedColumns(Object[] data) {

        if (generatedIndexes == null) {
            return null;
        }

        Object[] values = new Object[generatedIndexes.length];

        for (int i = 0; i < generatedIndexes.length; i++) {
            values[i] = data[generatedIndexes[i]];
        }

        return values;
    }

    public boolean hasGeneratedColumns() {
        return generatedIndexes != null;
    }

    public ResultMetaData generatedResultMetaData() {
        return generatedResultMetaData;
    }

    void getTriggerTableNames(OrderedHashSet<HsqlName> set, boolean write) {

        for (int i = 0; i < baseTable.triggerList.length; i++) {
            TriggerDef td = baseTable.triggerList[i];

            switch (type) {

                case StatementTypes.INSERT :
                    if (td.getStatementType() == StatementTypes.INSERT) {
                        break;
                    }

                    continue;
                case StatementTypes.UPDATE_WHERE :
                    if (td.getStatementType() == StatementTypes.UPDATE_WHERE) {
                        break;
                    }

                    continue;
                case StatementTypes.DELETE_WHERE :
                    if (td.getStatementType() == StatementTypes.DELETE_WHERE) {
                        break;
                    }

                    continue;
                case StatementTypes.MERGE :
                    if (td.getStatementType() == StatementTypes.INSERT
                            || td.getStatementType()
                               == StatementTypes.UPDATE_WHERE) {
                        break;
                    }

                    continue;
                default :
                    throw Error.runtimeError(ErrorCode.U_S0500, "StatementDML");
            }

            if (td.routine != null) {
                if (write) {
                    set.addAll(td.routine.getTableNamesForWrite());
                } else {
                    set.addAll(td.routine.getTableNamesForRead());
                }
            }
        }
    }

    /**
     * Executes an UPDATE statement.
     *
     * @return Result object
     */
    Result executeUpdateStatement(Session session, int limit) {

        int             count              = 0;
        RowSetNavigatorDataChange rowset =
            session.sessionContext.getRowSetDataChange();
        Type[]          colTypes           = baseTable.getColumnTypes();
        RangeIterator it = RangeVariable.getIterator(
            session,
            targetRangeVariables);
        Result          resultOut          = null;
        RowSetNavigator generatedNavigator = null;

        if (generatedIndexes != null) {
            resultOut = Result.newUpdateCountResult(generatedResultMetaData, 0);
            generatedNavigator = resultOut.getChainedResult().getNavigator();
        }

        session.sessionContext.rownum = 1;

        int rowCount = 0;

        while (it.next()) {
            session.sessionData.startRowProcessing();

            Row      row     = it.getCurrentRow();
            Object[] newData = row.getDataCopy();

            getUpdatedData(
                session,
                targets,
                baseTable,
                updateColumnMap,
                updateExpressions,
                colTypes,
                newData);
            rowset.addRow(session, row, newData, colTypes, updateColumnMap);

            session.sessionContext.rownum++;
            rowCount++;

            if (rowCount == limit) {
                break;
            }
        }

        rowset.endMainDataSet();
        it.release();

/* debug 190
        if (rowset.size() == 0) {
            System.out.println(targetTable.getName().name + " zero update: session "
                               + session.getId());
        } else if (rowset.size() >1) {
           System.out.println("multiple update: session "
                              + session.getId() + ", " + rowset.size());
       }

//* debug 190 */
        rowset.beforeFirst();

        count = update(session, baseTable, rowset, generatedNavigator);

        if (count == 0) {
            session.addWarning(HsqlException.noDataCondition);
        }

        if (resultOut == null) {
            if (count == 1) {
                return Result.updateOneResult;
            } else if (count == 0) {
                return Result.updateZeroResult;
            }

            return new Result(ResultConstants.UPDATECOUNT, count);
        } else {
            resultOut.setUpdateCount(count);

            return resultOut;
        }
    }

    static void getUpdatedData(
            Session session,
            Expression[] targets,
            Table targetTable,
            int[] columnMap,
            Expression[] colExpressions,
            Type[] colTypes,
            final Object[] data) {

        for (int i = 0, ix = 0; i < columnMap.length; ) {
            Expression expr = colExpressions[ix++];

            if (expr.getType() == OpTypes.ROW) {
                Object[] values = expr.getRowValue(session);

                for (int j = 0; j < values.length; j++, i++) {
                    int        colIndex = columnMap[i];
                    Expression e        = expr.nodes[j];

                    // transitional - still supporting null for identity generation
                    if (targetTable.identityColumn == colIndex) {
                        if (e.getType() == OpTypes.VALUE
                                && e.valueData == null) {
                            continue;
                        }
                    }

                    if (e.getType() == OpTypes.DEFAULT) {
                        if (targetTable.identityColumn == colIndex) {
                            continue;
                        }

                        if (targetTable.colDefaults[colIndex] == null) {
                            data[colIndex] = null;
                        } else {
                            data[colIndex] =
                                targetTable.colDefaults[colIndex].getValue(
                                    session);
                        }

                        continue;
                    }

                    data[colIndex] = colTypes[colIndex].convertToType(
                        session,
                        values[j],
                        e.dataType);
                }
            } else if (expr.getType() == OpTypes.ROW_SUBQUERY) {
                Object[] values = expr.getRowValue(session);

                for (int j = 0; j < values.length; j++, i++) {
                    int colIndex = columnMap[i];
                    Type colType =
                        expr.table.queryExpression.getMetaData().columnTypes[j];

                    data[colIndex] = colTypes[colIndex].convertToType(
                        session,
                        values[j],
                        colType);
                }
            } else {
                int colIndex = columnMap[i];

                if (expr.getType() == OpTypes.DEFAULT) {
                    if (targetTable.identityColumn == colIndex) {
                        i++;
                        continue;
                    }

                    if (targetTable.colDefaults[colIndex] == null) {
                        data[colIndex] = null;
                    } else {
                        data[colIndex] =
                            targetTable.colDefaults[colIndex].getValue(
                                session);
                    }

                    i++;
                    continue;
                }

                Object value = expr.getValue(session);

                if (targets[i].getType() == OpTypes.ARRAY_ACCESS) {
                    data[colIndex] =
                        ((ExpressionAccessor) targets[i]).getUpdatedArray(
                            session,
                            (Object[]) data[colIndex],
                            value,
                            true);
                } else {
                    data[colIndex] = colTypes[colIndex].convertToType(
                        session,
                        value,
                        expr.dataType);
                }

                i++;
            }
        }
    }

    /**
     * Executes a MERGE statement.
     *
     * @return Result object
     */
    Result executeMergeStatement(Session session) {

        Type[]          colTypes           = baseTable.getColumnTypes();
        Result          resultOut          = null;
        RowSetNavigator generatedNavigator = null;
        boolean hasWhenMatched = mergeDeleteCondition != null
                                 || updateExpressions.length != 0;

        if (generatedIndexes != null) {
            resultOut = Result.newUpdateCountResult(generatedResultMetaData, 0);
            generatedNavigator = resultOut.getChainedResult().getNavigator();
        }

        int count = 0;

        // data generated for non-matching rows
        RowSetNavigatorClient newData = new RowSetNavigatorClient(8);

        // rowset for update operation
        RowSetNavigatorDataChange updateRowSet =
            session.sessionContext.getRowSetDataChange();
        RangeVariable[] joinRangeVariables = targetRangeVariables;

        // populate insert and update lists
        RangeIterator[] rangeIterators =
            new RangeIterator[joinRangeVariables.length];

        for (int i = 0; i < joinRangeVariables.length; i++) {
            rangeIterators[i] = joinRangeVariables[i].getIterator(session);
        }

        for (int currentIndex = 0; currentIndex >= 0; ) {
            RangeIterator it          = rangeIterators[currentIndex];
            boolean       beforeFirst = it.isBeforeFirst();

            if (it.next()) {
                if (currentIndex < joinRangeVariables.length - 1) {
                    currentIndex++;
                    continue;
                }
            } else {
                if (currentIndex == 1
                        && beforeFirst
                        && insertExpression != null) {
                    Object[] data = getInsertData(
                        session,
                        colTypes,
                        insertExpression.nodes[0].nodes);

                    if (data != null) {
                        if (mergeInsertCondition.testCondition(session)) {
                            newData.add(data);
                        }
                    }
                }

                it.reset();

                currentIndex--;
                continue;
            }

            // row matches!
            if (hasWhenMatched) {
                Row row = it.getCurrentRow();    // this is always the second iterator

                session.sessionData.startRowProcessing();

                try {
                    boolean test = false;

                    // process whichever WHEN MATCHED action is first and exclude
                    // from subsequent WHEN MATCHED action if row was used
                    if (isMergeDeleteFirst && mergeDeleteCondition != null) {
                        test = mergeDeleteCondition.testCondition(session);

                        if (test) {
                            updateRowSet.addRow(row);
                        }
                    }

                    if (!test && mergeUpdateCondition != null) {
                        test = mergeUpdateCondition.testCondition(session);

                        if (test) {
                            Object[] data = row.getDataCopy();

                            getUpdatedData(
                                session,
                                targets,
                                baseTable,
                                updateColumnMap,
                                updateExpressions,
                                colTypes,
                                data);
                            updateRowSet.addRow(
                                session,
                                row,
                                data,
                                colTypes,
                                updateColumnMap);
                        }
                    }

                    if (!test
                            && !isMergeDeleteFirst
                            && mergeDeleteCondition != null) {
                        test = mergeDeleteCondition.testCondition(session);

                        if (test) {
                            updateRowSet.addRow(row);
                        }
                    }
                } catch (HsqlException e) {
                    for (int i = 0; i < joinRangeVariables.length; i++) {
                        rangeIterators[i].reset();
                    }

                    throw Error.error(ErrorCode.X_21000);
                }
            }
        }

        updateRowSet.endMainDataSet();

        for (int i = 0; i < joinRangeVariables.length; i++) {
            rangeIterators[i].reset();
        }

        // run the transaction as a whole, updating and inserting where needed
        // update any matched rows
        if (hasWhenMatched) {
            count = update(
                session,
                baseTable,
                updateRowSet,
                generatedNavigator);
        }

        // insert any non-matched rows
        if (newData.getSize() > 0) {
            insertRowSet(session, generatedNavigator, newData);

            count += newData.getSize();
        }

        if (insertExpression != null
                && baseTable.triggerLists[Trigger.INSERT_AFTER].length > 0) {
            baseTable.fireTriggers(session, Trigger.INSERT_AFTER, newData);
        }

        if (count == 0) {
            session.addWarning(HsqlException.noDataCondition);
        }

        if (resultOut == null) {
            if (count == 1) {
                return Result.updateOneResult;
            } else if (count == 0) {
                return Result.updateZeroResult;
            }

            return new Result(ResultConstants.UPDATECOUNT, count);
        } else {
            resultOut.setUpdateCount(count);

            return resultOut;
        }
    }

    void insertRowSet(
            Session session,
            RowSetNavigator generatedNavigator,
            RowSetNavigator newData) {

        PersistentStore store         = baseTable.getRowStore(session);
        RangeIterator   checkIterator = null;

        if (updatableTableCheck != null) {
            checkIterator = session.sessionContext.getCheckIterator(
                checkRangeVariable);
        }

        newData.beforeFirst();

        if (baseTable.identityColumn != -1) {
            while (newData.next()) {
                Object[] data = newData.getCurrent();

                session.sessionData.startRowProcessing();
                baseTable.setIdentityColumn(session, data);
            }

            newData.beforeFirst();
        }

        if (baseTable.triggerLists[Trigger.INSERT_BEFORE_ROW].length > 0) {
            while (newData.next()) {
                Object[] data = newData.getCurrent();

                session.sessionData.startRowProcessing();
                baseTable.fireTriggers(
                    session,
                    Trigger.INSERT_BEFORE_ROW,
                    null,
                    data,
                    null);
            }

            newData.beforeFirst();
        }

        while (newData.next()) {
            Object[] data = newData.getCurrent();

            // for identity using global sequence
            session.sessionData.startRowProcessing();
            baseTable.insertSingleRow(session, store, data, null);

            if (updatableTableCheck != null) {
                checkIterator.setCurrent(data);

                boolean check = updatableTableCheck.testCondition(session);

                if (!check) {
                    throw Error.error(ErrorCode.X_44000);
                }
            }

            if (generatedNavigator != null) {
                Object[] generatedValues = getGeneratedColumns(data);

                generatedNavigator.add(generatedValues);
            }
        }

        newData.beforeFirst();

        while (newData.next()) {
            Object[] data = newData.getCurrent();

            performIntegrityChecks(session, baseTable, null, data, null);
        }

        newData.beforeFirst();

        if (baseTable.triggerLists[Trigger.INSERT_AFTER_ROW].length > 0) {
            while (newData.next()) {
                Object[] data = newData.getCurrent();

                baseTable.fireTriggers(
                    session,
                    Trigger.INSERT_AFTER_ROW,
                    null,
                    data,
                    null);
            }

            newData.beforeFirst();
        }
    }

    Result insertSingleRow(
            Session session,
            PersistentStore store,
            Object[] data) {

        session.sessionData.startRowProcessing();
        baseTable.setIdentityColumn(session, data);

        if (baseTable.triggerLists[Trigger.INSERT_BEFORE_ROW].length > 0) {
            baseTable.fireTriggers(
                session,
                Trigger.INSERT_BEFORE_ROW,
                null,
                data,
                null);
        }

        baseTable.insertSingleRow(session, store, data, null);
        performIntegrityChecks(session, baseTable, null, data, null);

        if (baseTable.triggerLists[Trigger.INSERT_AFTER_ROW].length > 0) {
            baseTable.fireTriggers(
                session,
                Trigger.INSERT_AFTER_ROW,
                null,
                data,
                null);
        }

        if (baseTable.triggerLists[Trigger.INSERT_AFTER].length > 0) {
            baseTable.fireTriggers(
                session,
                Trigger.INSERT_AFTER,
                (RowSetNavigator) null);
        }

        session.sessionContext.diagnosticsVariables[ExpressionColumn.idx_row_count] =
            Integer.valueOf(
                1);

        return Result.updateOneResult;
    }

    Object[] getInsertData(
            Session session,
            Type[] colTypes,
            Expression[] rowArgs) {

        Object[] data = baseTable.getNewRowData(session);

        session.sessionData.startRowProcessing();

        for (int i = 0; i < rowArgs.length; i++) {
            Expression e        = rowArgs[i];
            int        colIndex = insertColumnMap[i];

            if (e.opType == OpTypes.DEFAULT) {
                if (baseTable.identityColumn == colIndex) {
                    continue;
                }

                if (baseTable.colDefaults[colIndex] != null) {
                    data[colIndex] = baseTable.colDefaults[colIndex].getValue(
                        session);
                }

                continue;
            }

            Object value = e.getValue(session);
            Type   type  = colTypes[colIndex];

            if (session.database.sqlSyntaxMys
                    || session.database.sqlSyntaxPgs) {
                try {
                    value = type.convertToType(session, value, e.dataType);
                } catch (HsqlException ex) {
                    if (type.typeCode == Types.SQL_DATE) {
                        value = Type.SQL_TIMESTAMP.convertToType(
                            session,
                            value,
                            e.dataType);
                        value = type.convertToType(
                            session,
                            value,
                            Type.SQL_TIMESTAMP);
                    } else if (type.typeCode == Types.SQL_TIMESTAMP) {
                        value = Type.SQL_DATE.convertToType(
                            session,
                            value,
                            e.dataType);
                        value = type.convertToType(
                            session,
                            value,
                            Type.SQL_DATE);
                    } else {
                        throw ex;
                    }
                }
            }

            data[colIndex] = value;
        }

        return data;
    }

    /**
     * Highest level multiple row update method.<p>
     *
     * Following clauses from SQL Standard section 11.8 are enforced 9) Let ISS
     * be the innermost SQL-statement being executed. 10) If evaluation of these
     * General Rules during the execution of ISS would cause an update of some
     * site to a value that is distinct from the value to which that site was
     * previously updated during the execution of ISS, then an exception
     * condition is raised: triggered data change violation. 11) If evaluation
     * of these General Rules during the execution of ISS would cause deletion
     * of a row containing a site that is identified for replacement in that
     * row, then an exception condition is raised: triggered data change
     * violation.
     *
     * @param session Session
     * @param table Table
     * @return int
     */
    int update(
            Session session,
            Table table,
            RowSetNavigatorDataChange navigator,
            RowSetNavigator generatedNavigator) {

        int           rowCount      = navigator.getSize();
        RangeIterator checkIterator = null;

        if (updatableTableCheck != null) {
            checkIterator = session.sessionContext.getCheckIterator(
                checkRangeVariable);
        }

        // set identity column where null and check columns
        for (int i = 0; i < rowCount; i++) {
            navigator.next();

            Object[] data = navigator.getCurrentChangedData();

            // for identity using global sequence
            session.sessionData.startRowProcessing();

            /*
             * @todo 1.9.0 - make optional using database property -
             * this means the identity column can be set to null to force
             * creation of a new identity value
             */
            table.setIdentityColumn(session, data);
            table.setGeneratedColumns(session, data);
            table.setUpdatedColumns(session, data, updateColumnMap);
        }

        navigator.beforeFirst();

        if (table.fkMainConstraints.length > 0) {
            HashSet<Constraint> path =
                session.sessionContext.getConstraintPath();

            for (int i = 0; i < rowCount; i++) {
                navigator.next();

                Row      row  = navigator.getCurrentRow();
                Object[] data = navigator.getCurrentChangedData();

                performReferentialActions(
                    session,
                    navigator,
                    row,
                    data,
                    this.updateColumnMap,
                    path,
                    false);
                path.clear();
            }

            navigator.beforeFirst();
        }

        while (navigator.next()) {
            Row      row            = navigator.getCurrentRow();
            Object[] data           = navigator.getCurrentChangedData();
            int[]    changedColumns = navigator.getCurrentChangedColumns();
            Table    currentTable   = ((Table) row.getTable());

            if (currentTable instanceof TableDerived) {
                currentTable = ((TableDerived) currentTable).view;
            }

            if (currentTable.triggerLists[Trigger.UPDATE_BEFORE_ROW].length
                    > 0) {
                session.sessionData.startRowProcessing();
                currentTable.fireTriggers(
                    session,
                    Trigger.UPDATE_BEFORE_ROW,
                    row.getData(),
                    data,
                    changedColumns);
                currentTable.enforceRowConstraints(session, data);
            }

            // check the view condition after all the triggered changed
            if (updatableTableCheck != null) {
                checkIterator.setCurrent(data);

                boolean check = updatableTableCheck.testCondition(session);

                if (!check) {
                    throw Error.error(ErrorCode.X_44000);
                }
            }
        }

        if (table.isView) {
            return rowCount;
        }

        navigator.beforeFirst();

        while (navigator.next()) {
            Row             row          = navigator.getCurrentRow();
            Table           currentTable = ((Table) row.getTable());
            int[] changedColumns         = navigator.getCurrentChangedColumns();
            PersistentStore store        = currentTable.getRowStore(session);

            session.addDeleteAction(currentTable, store, row, changedColumns);
        }

        navigator.beforeFirst();

        while (navigator.next()) {
            Row             row          = navigator.getCurrentRow();
            Object[]        data         = navigator.getCurrentChangedData();
            Table           currentTable = ((Table) row.getTable());
            int[] changedColumns         = navigator.getCurrentChangedColumns();
            PersistentStore store        = currentTable.getRowStore(session);

            if (currentTable.isSystemVersioned()) {
                Object[] history = row.getData();
                Row newRow = currentTable.insertSystemVersionHistoryRow(
                    session,
                    store,
                    history);
            }

            if (data == null) {
                continue;
            }

            Row newRow = currentTable.insertSingleRow(
                session,
                store,
                data,
                changedColumns);

            if (generatedNavigator != null) {
                Object[] generatedValues = getGeneratedColumns(data);

                generatedNavigator.add(generatedValues);
            }

//            newRow.rowAction.updatedAction = row.rowAction;
        }

        navigator.beforeFirst();

        OrderedHashSet<Table> extraUpdateTables = null;
        boolean hasAfterRowTriggers =
            table.triggerLists[Trigger.UPDATE_AFTER_ROW].length > 0;

        while (navigator.next()) {
            Row      row            = navigator.getCurrentRow();
            Table    currentTable   = ((Table) row.getTable());
            Object[] changedData    = navigator.getCurrentChangedData();
            int[]    changedColumns = navigator.getCurrentChangedColumns();

            performIntegrityChecks(
                session,
                currentTable,
                row.getData(),
                changedData,
                changedColumns);

            if (currentTable != table) {
                if (extraUpdateTables == null) {
                    extraUpdateTables = new OrderedHashSet<>();
                }

                extraUpdateTables.add(currentTable);

                if (currentTable.triggerLists[Trigger.UPDATE_AFTER_ROW].length
                        > 0) {
                    hasAfterRowTriggers = true;
                }
            }
        }

        navigator.beforeFirst();

        if (hasAfterRowTriggers) {
            while (navigator.next()) {
                Row      row            = navigator.getCurrentRow();
                Object[] changedData    = navigator.getCurrentChangedData();
                int[]    changedColumns = navigator.getCurrentChangedColumns();
                Table    currentTable   = ((Table) row.getTable());

                currentTable.fireTriggers(
                    session,
                    Trigger.UPDATE_AFTER_ROW,
                    row.getData(),
                    changedData,
                    changedColumns);
            }

            navigator.beforeFirst();
        }

        baseTable.fireTriggers(session, Trigger.UPDATE_AFTER, navigator);

        if (extraUpdateTables != null) {
            for (int i = 0; i < extraUpdateTables.size(); i++) {
                Table currentTable = extraUpdateTables.get(i);

                currentTable.fireTriggers(
                    session,
                    Trigger.UPDATE_AFTER,
                    navigator);
            }
        }

        return rowCount;
    }

    /**
     * Executes a DELETE statement.
     *
     * @return the result of executing the statement
     */
    Result executeDeleteStatement(Session session, int limit) {

        int             count              = 0;
        RangeIterator it = RangeVariable.getIterator(
            session,
            targetRangeVariables);
        RowSetNavigatorDataChange rowset =
            session.sessionContext.getRowSetDataChange();
        Result          resultOut          = null;
        RowSetNavigator generatedNavigator = null;

        if (generatedIndexes != null) {
            resultOut = Result.newUpdateCountResult(generatedResultMetaData, 0);
            generatedNavigator = resultOut.getChainedResult().getNavigator();
        }

        session.sessionContext.rownum = 1;

        int rowCount = 0;

        while (it.next()) {
            Row currentRow = it.getCurrentRow();

            rowset.addRow(currentRow);

            session.sessionContext.rownum++;
            rowCount++;

            if (rowCount == limit) {
                break;
            }
        }

        it.release();
        rowset.endMainDataSet();

        if (rowset.getSize() > 0) {
            count = delete(session, baseTable, rowset, generatedNavigator);
        }

        if (count == 0) {
            session.addWarning(HsqlException.noDataCondition);
        }

        if (resultOut == null) {
            if (count == 1) {
                return Result.updateOneResult;
            } else if (count == 0) {
                return Result.updateZeroResult;
            }

            return new Result(ResultConstants.UPDATECOUNT, count);
        } else {
            resultOut.setUpdateCount(count);

            return resultOut;
        }
    }

    Result executeDeleteTruncateStatement(Session session) {

        PersistentStore store   = targetTable.getRowStore(session);
        RowIterator     it      = targetTable.getDefaultIndex().firstRow(store);
        boolean         hasData = false;

        for (int i = 0; i < targetTable.fkMainConstraints.length; i++) {
            if (targetTable.fkMainConstraints[i].getRef() != targetTable) {
                HsqlName tableName = targetTable.fkMainConstraints[i].getRef()
                        .getName();
                Table refTable = session.database.schemaManager.getUserTable(
                    tableName);

                if (!refTable.isEmpty(session)) {
                    throw Error.error(
                        ErrorCode.X_23504,
                        refTable.getName().name);
                }
            }
        }

        try {
            while (it.next()) {
                Row row = it.getCurrentRow();

                session.addDeleteAction(
                    (Table) row.getTable(),
                    store,
                    row,
                    null);

                hasData = true;
            }

            if (restartIdentity && targetTable.identitySequence != null) {
                targetTable.identitySequence.reset();
            }
        } finally {
            it.release();
        }

        if (!hasData) {
            session.addWarning(HsqlException.noDataCondition);
        }

        return Result.updateOneResult;
    }

    /**
     *  Highest level multiple row delete method. Corresponds to an SQL
     *  DELETE.
     */
    int delete(
            Session session,
            Table table,
            RowSetNavigatorDataChange navigator,
            RowSetNavigator generatedNavigator) {

        int rowCount = navigator.getSize();

        navigator.beforeFirst();

        if (table.fkMainConstraints.length > 0) {
            HashSet<Constraint> path =
                session.sessionContext.getConstraintPath();

            if (table.cascadingDeletes > 0) {
                for (int i = 0; i < rowCount; i++) {
                    navigator.next();

                    Row row = navigator.getCurrentRow();

                    performReferentialActions(
                        session,
                        navigator,
                        row,
                        null,
                        null,
                        path,
                        true);
                    path.clear();
                }

                navigator.beforeFirst();
            }

            int newCount = navigator.getSize();

            for (int i = 0; i < newCount; i++) {
                navigator.next();

                Row row = navigator.getCurrentRow();

                performReferentialActions(
                    session,
                    navigator,
                    row,
                    null,
                    null,
                    path,
                    false);
                path.clear();
            }

            navigator.beforeFirst();
        }

        while (navigator.next()) {
            Row      row            = navigator.getCurrentRow();
            Object[] changedData    = navigator.getCurrentChangedData();
            int[]    changedColumns = navigator.getCurrentChangedColumns();
            Table    currentTable   = ((Table) row.getTable());

            if (currentTable instanceof TableDerived) {
                currentTable = ((TableDerived) currentTable).view;
            }

            if (changedData == null) {
                currentTable.fireTriggers(
                    session,
                    Trigger.DELETE_BEFORE_ROW,
                    row.getData(),
                    null,
                    null);
            } else {
                currentTable.fireTriggers(
                    session,
                    Trigger.UPDATE_BEFORE_ROW,
                    row.getData(),
                    changedData,
                    changedColumns);
            }
        }

        if (table.isView) {
            return rowCount;
        }

        navigator.beforeFirst();

        boolean hasUpdate = false;
        boolean hasPeriod = false;

        while (navigator.next()) {
            Row             row          = navigator.getCurrentRow();
            Object[]        data         = navigator.getCurrentChangedData();
            Table           currentTable = ((Table) row.getTable());
            PersistentStore store        = currentTable.getRowStore(session);

            session.addDeleteAction(currentTable, store, row, null);

            if (generatedNavigator != null) {
                Object[] generatedValues = getGeneratedColumns(row.getData());

                generatedNavigator.add(generatedValues);
            }

            if (data != null) {
                hasUpdate = true;
            }

            if (currentTable.isSystemVersioned()) {
                hasPeriod = true;
            }
        }

        navigator.beforeFirst();

        if (hasUpdate || hasPeriod) {
            while (navigator.next()) {
                Row             row          = navigator.getCurrentRow();
                Object[]        data         =
                    navigator.getCurrentChangedData();
                Table           currentTable = ((Table) row.getTable());
                int[] changedColumns = navigator.getCurrentChangedColumns();
                PersistentStore store        = currentTable.getRowStore(
                    session);

                if (currentTable.isSystemVersioned()) {
                    Object[] history = row.getData();
                    Row newRow = currentTable.insertSystemVersionHistoryRow(
                        session,
                        store,
                        history);
                }

                if (data == null) {
                    continue;
                }

                Row newRow = currentTable.insertSingleRow(
                    session,
                    store,
                    data,
                    changedColumns);
            }

            navigator.beforeFirst();
        }

        OrderedHashSet<Table> extraUpdateTables = null;
        OrderedHashSet<Table> extraDeleteTables = null;
        boolean hasAfterRowTriggers =
            table.triggerLists[Trigger.DELETE_AFTER_ROW].length > 0;

        if (rowCount != navigator.getSize()) {
            while (navigator.next()) {
                Row      row            = navigator.getCurrentRow();
                Object[] changedData    = navigator.getCurrentChangedData();
                int[]    changedColumns = navigator.getCurrentChangedColumns();
                Table    currentTable   = ((Table) row.getTable());

                if (changedData != null) {
                    performIntegrityChecks(
                        session,
                        currentTable,
                        row.getData(),
                        changedData,
                        changedColumns);
                }

                if (currentTable != table) {
                    if (changedData == null) {
                        if (currentTable.triggerLists[Trigger.DELETE_AFTER_ROW].length
                                > 0) {
                            hasAfterRowTriggers = true;
                        }

                        if (extraDeleteTables == null) {
                            extraDeleteTables = new OrderedHashSet<>();
                        }

                        extraDeleteTables.add(currentTable);
                    } else {
                        if (currentTable.triggerLists[Trigger.UPDATE_AFTER_ROW].length
                                > 0) {
                            hasAfterRowTriggers = true;
                        }

                        if (extraUpdateTables == null) {
                            extraUpdateTables = new OrderedHashSet<>();
                        }

                        extraUpdateTables.add(currentTable);
                    }
                }
            }

            navigator.beforeFirst();
        }

        if (hasAfterRowTriggers) {
            while (navigator.next()) {
                Row      row          = navigator.getCurrentRow();
                Object[] changedData  = navigator.getCurrentChangedData();
                Table    currentTable = ((Table) row.getTable());

                if (changedData == null) {
                    currentTable.fireTriggers(
                        session,
                        Trigger.DELETE_AFTER_ROW,
                        row.getData(),
                        null,
                        null);
                } else {
                    currentTable.fireTriggers(
                        session,
                        Trigger.UPDATE_AFTER_ROW,
                        row.getData(),
                        changedData,
                        null);
                }
            }

            navigator.beforeFirst();
        }

        table.fireTriggers(session, Trigger.DELETE_AFTER, navigator);

        if (extraUpdateTables != null) {
            for (int i = 0; i < extraUpdateTables.size(); i++) {
                Table currentTable = extraUpdateTables.get(i);

                currentTable.fireTriggers(
                    session,
                    Trigger.UPDATE_AFTER,
                    navigator);
            }
        }

        if (extraDeleteTables != null) {
            for (int i = 0; i < extraDeleteTables.size(); i++) {
                Table currentTable = extraDeleteTables.get(i);

                currentTable.fireTriggers(
                    session,
                    Trigger.DELETE_AFTER,
                    navigator);
            }
        }

        return rowCount;
    }

    void performIntegrityChecks(
            Session session,
            Table table,
            Object[] oldData,
            Object[] newData,
            int[] updatedColumns) {

        if (newData == null) {
            return;
        }

        Expression filter = rangeVariables[0].filterCondition;

        if (filter != null) {
            RangeIterator it = session.sessionContext.getCheckIterator(
                rangeVariables[0]);

            it.setCurrent(newData);

            if (!filter.testCondition(session)) {
                throw Error.error(ErrorCode.X_44000);
            }
        }

        for (int i = 0, size = table.checkConstraints.length; i < size; i++) {
            table.checkConstraints[i].checkInsert(
                session,
                table,
                newData,
                oldData);
        }

        if (!session.database.isReferentialIntegrity()) {
            return;
        }

        for (int i = 0, size = table.fkConstraints.length; i < size; i++) {
            boolean    check     = oldData == null;
            Constraint c         = table.fkConstraints[i];
            int[]      fkColumns = c.getRefColumns();

            if (!check) {
                check = ArrayUtil.haveCommonElement(fkColumns, updatedColumns);
            }

            if (check) {
                c.checkInsert(session, table, newData, oldData);
            }
        }
    }

    static void performReferentialActions(
            Session session,
            RowSetNavigatorDataChange navigator,
            Row row,
            Object[] data,
            int[] changedCols,
            HashSet<Constraint> path,
            boolean deleteCascade) {

        if (!session.database.isReferentialIntegrity()) {
            return;
        }

        boolean delete = data == null;
        Table   table  = (Table) row.getTable();

        for (int i = 0, size = table.fkMainConstraints.length; i < size; i++) {
            Constraint c      = table.fkMainConstraints[i];
            int        action = delete
                                ? c.getDeleteAction()
                                : c.getUpdateAction();

            if (deleteCascade
                    ^ (delete
                       && action == SchemaObject.ReferentialAction.CASCADE)) {
                continue;
            }

            if (!delete) {
                if (!ArrayUtil.haveCommonElement(changedCols,
                                                 c.core.mainCols)) {
                    continue;
                }

                if (c.core.mainIndex.compareRowNonUnique(session,
                        row.getData(),
                        data,
                        c.core.mainCols) == 0) {
                    continue;
                }
            }

            RowIterator refiterator = c.findFkRef(session, row.getData());

            while (refiterator.next()) {
                Row      refRow  = refiterator.getCurrentRow();
                Object[] refData = null;

                /* @todo use MATCH */
                if (c.core.refIndex.compareRowNonUnique(session,
                        refRow.getData(),
                        row.getData(),
                        c.core.mainCols) != 0) {
                    break;
                }

                if (delete && refRow.getId() == row.getId()) {
                    continue;
                }

                if (!refRow.isCurrentSystemVersion()) {
                    continue;
                }

                switch (action) {

                    case SchemaObject.ReferentialAction.CASCADE : {
                        if (delete) {
                            boolean result;

                            try {
                                result = navigator.addRow(refRow);
                            } catch (HsqlException e) {
                                String[] info = getConstraintInfo(c);

                                refiterator.release();

                                throw Error.error(
                                    null,
                                    ErrorCode.X_27000,
                                    ErrorCode.CONSTRAINT,
                                    info);
                            }

                            if (result) {
                                performReferentialActions(
                                    session,
                                    navigator,
                                    refRow,
                                    null,
                                    null,
                                    path,
                                    deleteCascade);
                            }

                            continue;
                        }

                        refData = c.core.refTable.getEmptyRowData();

                        System.arraycopy(
                            refRow.getData(),
                            0,
                            refData,
                            0,
                            refData.length);

                        for (int j = 0; j < c.core.refCols.length; j++) {
                            refData[c.core.refCols[j]] =
                                data[c.core.mainCols[j]];
                        }

                        break;
                    }

                    case SchemaObject.ReferentialAction.SET_NULL : {
                        refData = c.core.refTable.getEmptyRowData();

                        System.arraycopy(
                            refRow.getData(),
                            0,
                            refData,
                            0,
                            refData.length);

                        for (int j = 0; j < c.core.refCols.length; j++) {
                            refData[c.core.refCols[j]] = null;
                        }

                        break;
                    }

                    case SchemaObject.ReferentialAction.SET_DEFAULT : {
                        refData = c.core.refTable.getEmptyRowData();

                        System.arraycopy(
                            refRow.getData(),
                            0,
                            refData,
                            0,
                            refData.length);

                        for (int j = 0; j < c.core.refCols.length; j++) {
                            ColumnSchema col = c.core.refTable.getColumn(
                                c.core.refCols[j]);

                            refData[c.core.refCols[j]] = col.getDefaultValue(
                                session);
                        }

                        break;
                    }

                    case SchemaObject.ReferentialAction.NO_ACTION :
                        if (delete) {
                            if (navigator.containsDeletedRow(refRow)) {
                                continue;
                            }
                        } else {
                            if (navigator.containsUpdatedRow(row,
                                                             refRow,
                                                             c.core.mainCols)) {
                                continue;
                            }
                        }

                    // fall through
                    case SchemaObject.ReferentialAction.RESTRICT : {
                        int errorCode =
                            c.getDeleteAction()
                            == SchemaObject.ReferentialAction.NO_ACTION
                            ? ErrorCode.X_23504
                            : ErrorCode.X_23001;
                        String[] info = getConstraintInfo(c);

                        refiterator.release();

                        throw Error.error(
                            null,
                            errorCode,
                            ErrorCode.CONSTRAINT,
                            info);
                    }

                    default :
                        continue;
                }

                try {
                    refData = navigator.addRow(
                        session,
                        refRow,
                        refData,
                        c.core.refTable.getColumnTypes(),
                        c.core.refCols);
                } catch (HsqlException e) {
                    String[] info = getConstraintInfo(c);

                    refiterator.release();

                    throw Error.error(
                        null,
                        ErrorCode.X_27000,
                        ErrorCode.CONSTRAINT,
                        info);
                }

                if (refData == null) {

                    // happens only with enforceDeleteOrUpdate=false and updated row is already deleted
                    continue;
                }

                if (!path.add(c)) {
                    continue;
                }

                performReferentialActions(
                    session,
                    navigator,
                    refRow,
                    refData,
                    c.core.refCols,
                    path,
                    deleteCascade);
                path.remove(c);
            }

            refiterator.release();
        }
    }

    static String[] getConstraintInfo(Constraint c) {
        return new String[]{ c.core.refName.name,
                             c.core.refTable.getName().name };
    }

    public void clearStructures(Session session) {
        session.sessionContext.clearStructures(this);
    }
}