/* Copyright (c) 2001-2020, 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.5.1
 * @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;

        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 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 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 supports INSERT_SELECT
        if (type != StatementTypes.INSERT && type != StatementTypes.MERGE) {
            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;
        }

        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 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 (resultOut == null) {
            if (count == 1) {
                return Result.updateOneResult;
            } else if (count == 0) {
                session.addWarning(HsqlException.noDataCondition);

                return Result.updateZeroResult;
            }

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

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

            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 (resultOut == null) {
            if (count == 1) {
                return Result.updateOneResult;
            }

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

                return Result.updateZeroResult;
            }

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

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

            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;
                    }
                }
            } else {

                // DYNAMIC_PARAM and PARAMETER expressions may have wider values
                if (e.dataType == null
                        || type.typeDataGroup != e.dataType.typeDataGroup
                        || type.isArrayType()) {
                    value = type.convertToType(session, value, e.dataType);
                }
            }

            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 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 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 = (Table) 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();

        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);
        } else {
            session.addWarning(HsqlException.noDataCondition);

            return Result.updateZeroResult;
        }

        if (count == 1) {
            return Result.updateOneResult;
        }

        return new Result(ResultConstants.UPDATECOUNT, count);
    }

    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) {

        int rowCount = navigator.getSize();

        navigator.beforeFirst();

        if (table.fkMainConstraints.length > 0) {
            HashSet 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 (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);

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

            navigator.beforeFirst();
        }

        OrderedHashSet extraUpdateTables = null;
        OrderedHashSet 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 = (Table) extraUpdateTables.get(i);

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

        if (extraDeleteTables != null) {
            for (int i = 0; i < extraDeleteTables.size(); i++) {
                Table currentTable = (Table) 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 == null);
        }

        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];

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

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

    static void performReferentialActions(Session session,
                                          RowSetNavigatorDataChange navigator,
                                          Row row, Object[] data,
                                          int[] changedCols, HashSet 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);
    }
}
