/* Copyright (c) 2001-2010, 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.types.Type; /** * Implementation of Statement for DML statements.<p> * * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 2.0.0 * @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; Expression updatableTableCheck; RangeVariable checkRangeVariable; boolean isTruncate; public StatementDML(int type, int group, HsqlName schemaName) { super(type, group, schemaName); } /** * Instantiate this as a DELETE statement */ StatementDML(Session session, Table targetTable, RangeVariable[] rangeVars, CompileContext compileContext, boolean restartIdentity, int type) { super(StatementTypes.DELETE_WHERE, StatementTypes.X_SQL_DATA_CHANGE, session.getCurrentSchemaHsqlName()); this.targetTable = targetTable; this.baseTable = targetTable.getBaseTable() == null ? targetTable : targetTable .getBaseTable(); this.targetRangeVariables = rangeVars; this.restartIdentity = restartIdentity; setDatabseObjects(session, compileContext); checkAccessRights(session); if (type == StatementTypes.TRUNCATE) { isTruncate = true; } targetRangeVariables[0].addAllColumns(); } /** * Instantiate this as an UPDATE statement. */ StatementDML(Session session, Expression[] targets, Table targetTable, RangeVariable rangeVars[], int[] updateColumnMap, Expression[] colExpressions, boolean[] checkColumns, CompileContext compileContext) { super(StatementTypes.UPDATE_WHERE, StatementTypes.X_SQL_DATA_CHANGE, session.getCurrentSchemaHsqlName()); this.targets = targets; this.targetTable = targetTable; this.baseTable = targetTable.getBaseTable() == null ? targetTable : targetTable .getBaseTable(); this.updateColumnMap = updateColumnMap; this.updateExpressions = colExpressions; this.updateCheckColumns = checkColumns; this.targetRangeVariables = rangeVars; setDatabseObjects(session, compileContext); checkAccessRights(session); setupChecks(); targetRangeVariables[0].addAllColumns(); } /** * Instantiate this as a MERGE statement. */ StatementDML(Session session, Expression[] targets, RangeVariable[] targetRangeVars, int[] insertColMap, int[] updateColMap, boolean[] checkColumns, Expression mergeCondition, Expression insertExpr, Expression[] updateExpr, CompileContext compileContext) { super(StatementTypes.MERGE, StatementTypes.X_SQL_DATA_CHANGE, session.getCurrentSchemaHsqlName()); this.targets = targets; this.sourceTable = targetRangeVars[0].rangeTable; this.targetTable = targetRangeVars[1].rangeTable; this.baseTable = targetTable.getBaseTable() == null ? targetTable : targetTable .getBaseTable(); this.insertCheckColumns = checkColumns; this.insertColumnMap = insertColMap; this.updateColumnMap = updateColMap; this.insertExpression = insertExpr; this.updateExpressions = updateExpr; this.targetRangeVariables = targetRangeVars; this.condition = mergeCondition; setDatabseObjects(session, compileContext); checkAccessRights(session); setupChecks(); } /** * 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 = ((TableDerived) targetTable).getQueryExpression() .getMainSelect(); this.updatableTableCheck = select.checkQueryCondition; this.checkRangeVariable = select.rangeVariables[0]; } } Result getResult(Session session) { Result result = null; switch (type) { case StatementTypes.UPDATE_WHERE : result = executeUpdateStatement(session); break; case StatementTypes.MERGE : result = executeMergeStatement(session); break; case StatementTypes.DELETE_WHERE : if (isTruncate) { result = executeDeleteTruncateStatement(session); } else { result = executeDeleteStatement(session); } break; default : throw Error.runtimeError(ErrorCode.U_S0500, "StatementDML"); } 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]; if (type == StatementTypes.UPDATE_WHERE || type == StatementTypes.MERGE) { if (ArrayUtil.haveCommonElement(constraint.getRefColumns(), updateColumnMap)) { set.add( baseTable.fkConstraints[i].getMain().getName()); } } else if (type == StatementTypes.INSERT) { set.add(baseTable.fkConstraints[i].getMain().getName()); } } 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.isReadOnly() || 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) { baseTable.collectFKWriteLocks(updateColumnMap, set); } else if (type == StatementTypes.DELETE_WHERE) { baseTable.collectFKWriteLocks(null, set); } getTriggerTableNames(set, true); } } 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 count = 0; Expression[] colExpressions = updateExpressions; RowSetNavigatorDataChange rowset = new RowSetNavigatorDataChange(); Type[] colTypes = baseTable.getColumnTypes(); RangeIterator it = RangeVariable.getIterator(session, targetRangeVariables); while (it.next()) { session.sessionData.startRowProcessing(); Row row = it.getCurrentRow(); Object[] data = row.getData(); Object[] newData = getUpdatedData(session, targets, baseTable, updateColumnMap, colExpressions, colTypes, data); if (updatableTableCheck != null) { it.setCurrent(newData); boolean check = updatableTableCheck.testCondition(session); if (!check) { it.release(); throw Error.error(ErrorCode.X_44000); } } rowset.addRow(session, row, newData, colTypes, updateColumnMap); } 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); if (count == 1) { return Result.updateOneResult; } else if (count == 0) { return Result.updateZeroResult; } return new Result(ResultConstants.UPDATECOUNT, count); } static Object[] getUpdatedData(Session session, Expression[] targets, Table targetTable, int[] columnMap, Expression[] colExpressions, Type[] colTypes, Object[] oldData) { Object[] data = targetTable.getEmptyRowData(); System.arraycopy(oldData, 0, data, 0, data.length); 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; } 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.subQuery.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; } 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++; } } return data; } /** * Executes a MERGE statement. * * @return Result object */ Result executeMergeStatement(Session session) { Type[] colTypes = baseTable.getColumnTypes(); Result resultOut = null; RowSetNavigator generatedNavigator = null; 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 = new RowSetNavigatorDataChange(); RangeVariable[] joinRangeIterators = targetRangeVariables; // populate insert and update lists RangeIterator[] rangeIterators = new RangeIterator[joinRangeIterators.length]; for (int i = 0; i < joinRangeIterators.length; i++) { rangeIterators[i] = joinRangeIterators[i].getIterator(session); } for (int currentIndex = 0; currentIndex >= 0; ) { RangeIterator it = rangeIterators[currentIndex]; boolean beforeFirst = it.isBeforeFirst(); if (it.next()) { if (currentIndex < joinRangeIterators.length - 1) { currentIndex++; continue; } } else { if (currentIndex == 1 && beforeFirst && insertExpression != null) { Object[] data = getInsertData(session, colTypes, insertExpression.nodes[0].nodes); if (data != null) { newData.add(data); } } it.reset(); currentIndex--; continue; } // row matches! if (updateExpressions.length != 0) { Row row = it.getCurrentRow(); // this is always the second iterator Object[] data = getUpdatedData(session, targets, baseTable, updateColumnMap, updateExpressions, colTypes, row.getData()); try { updateRowSet.addRow(session, row, data, colTypes, updateColumnMap); } catch (HsqlException e) { for (int i = 0; i < joinRangeIterators.length; i++) { rangeIterators[i].reset(); } throw Error.error(ErrorCode.X_21000); } } } for (int i = 0; i < joinRangeIterators.length; i++) { rangeIterators[i].reset(); } // run the transaction as a whole, updating and inserting where needed // update any matched rows if (updateExpressions.length != 0) { count = update(session, baseTable, updateRowSet); } // 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; } 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 = checkRangeVariable.getIterator(session); } newData.beforeFirst(); if (baseTable.triggerLists[Trigger.INSERT_BEFORE_ROW].length > 0) { while (newData.hasNext()) { Object[] data = (Object[]) newData.getNext(); baseTable.fireTriggers(session, Trigger.INSERT_BEFORE_ROW, null, data, null); } newData.beforeFirst(); } while (newData.hasNext()) { Object[] data = (Object[]) newData.getNext(); baseTable.insertSingleRow(session, store, data, null); if (checkIterator != 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.hasNext()) { Object[] data = (Object[]) newData.getNext(); performIntegrityChecks(session, baseTable, null, data, null); } newData.beforeFirst(); if (baseTable.triggerLists[Trigger.INSERT_AFTER_ROW].length > 0) { while (newData.hasNext()) { Object[] data = (Object[]) newData.getNext(); baseTable.fireTriggers(session, Trigger.INSERT_AFTER_ROW, null, data, null); } newData.beforeFirst(); } } Result insertSingleRow(Session session, PersistentStore store, Object[] 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 (session.database.isReferentialIntegrity()) { for (int i = 0, size = baseTable.fkConstraints.length; i < size; i++) { baseTable.fkConstraints[i].checkInsert(session, baseTable, data, true); } } 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); } 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; } continue; } Object value = e.getValue(session); Type type = colTypes[colIndex]; if (colTypes[colIndex] != e.dataType) { 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 * @param updateList RowSetNavigatorDataChange * @return int */ int update(Session session, Table table, RowSetNavigatorDataChange navigator) { int rowCount = navigator.getSize(); // set identity column where null and check columns for (int i = 0; i < rowCount; i++) { navigator.next(); Object[] data = navigator.getCurrentChangedData(); /** * @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); } navigator.beforeFirst(); if (table.fkMainConstraints.length > 0) { HashSet path = session.sessionContext.getConstraintPath(); for (int i = 0; i < rowCount; i++) { Row row = navigator.getNextRow(); Object[] data = navigator.getCurrentChangedData(); performReferentialActions(session, table, navigator, row, data, this.updateColumnMap, path); path.clear(); } navigator.beforeFirst(); } for (int i = 0; i < navigator.getSize(); i++) { Row row = navigator.getNextRow(); Object[] data = navigator.getCurrentChangedData(); int[] changedColumns = navigator.getCurrentChangedColumns(); Table currentTable = ((Table) row.getTable()); if (currentTable.triggerLists[Trigger.UPDATE_BEFORE_ROW].length > 0) { currentTable.fireTriggers(session, Trigger.UPDATE_BEFORE_ROW, row.getData(), data, changedColumns); currentTable.enforceRowConstraints(session, data); } } if (table.isView) { return rowCount; } navigator.beforeFirst(); for (int i = 0; i < navigator.getSize(); i++) { Row row = navigator.getNextRow(); Table currentTable = ((Table) row.getTable()); int[] changedColumns = navigator.getCurrentChangedColumns(); session.addDeleteAction(currentTable, row, changedColumns); } navigator.beforeFirst(); for (int i = 0; i < navigator.getSize(); i++) { Row row = navigator.getNextRow(); Object[] data = navigator.getCurrentChangedData(); Table currentTable = ((Table) row.getTable()); int[] changedColumns = navigator.getCurrentChangedColumns(); PersistentStore store = currentTable.getRowStore(session); if (data == null) { continue; } Row newRow = currentTable.insertSingleRow(session, store, data, changedColumns); // newRow.rowAction.updatedAction = row.rowAction; } navigator.beforeFirst(); OrderedHashSet extraUpdateTables = null; boolean hasAfterRowTriggers = table.triggerLists[Trigger.UPDATE_AFTER_ROW].length > 0; for (int i = 0; i < navigator.getSize(); i++) { Row row = navigator.getNextRow(); 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) { for (int i = 0; i < navigator.getSize(); i++) { Row row = navigator.getNextRow(); 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 count = 0; RangeIterator it = RangeVariable.getIterator(session, targetRangeVariables); RowSetNavigatorDataChange navigator = new RowSetNavigatorDataChange(); while (it.next()) { Row currentRow = it.getCurrentRow(); navigator.addRow(currentRow); } it.release(); if (navigator.getSize() > 0) { count = delete(session, baseTable, navigator); } else { 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.getPrimaryIndex().firstRow(store); try { while (it.hasNext()) { Row row = it.getNextRow(); session.addDeleteAction((Table) row.getTable(), row, null); } if (restartIdentity && targetTable.identitySequence != null) { targetTable.identitySequence.reset(); } } finally { it.release(); } 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(); for (int i = 0; i < rowCount; i++) { navigator.next(); Row row = navigator.getCurrentRow(); performReferentialActions(session, table, navigator, row, null, null, path); path.clear(); } navigator.beforeFirst(); } while (navigator.hasNext()) { navigator.next(); Row row = navigator.getCurrentRow(); Object[] changedData = navigator.getCurrentChangedData(); int[] changedColumns = navigator.getCurrentChangedColumns(); Table currentTable = ((Table) row.getTable()); 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; for (int i = 0; i < navigator.getSize(); i++) { Row row = navigator.getNextRow(); Object[] data = navigator.getCurrentChangedData(); Table currentTable = ((Table) row.getTable()); session.addDeleteAction(currentTable, row, null); if (data != null) { hasUpdate = true; } } navigator.beforeFirst(); if (hasUpdate) { for (int i = 0; i < navigator.getSize(); i++) { Row row = navigator.getNextRow(); Object[] data = navigator.getCurrentChangedData(); Table currentTable = ((Table) row.getTable()); int[] changedColumns = navigator.getCurrentChangedColumns(); PersistentStore store = currentTable.getRowStore(session); 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.hasNext()) { 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.hasNext()) { 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; } static void performIntegrityChecks(Session session, Table table, Object[] oldData, Object[] newData, int[] updatedColumns) { if (newData == null) { return; } 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, Table table, RowSetNavigatorDataChange navigator, Row row, Object[] data, int[] changedCols, HashSet path) { if (!session.database.isReferentialIntegrity()) { return; } boolean delete = data == null; for (int i = 0, size = table.fkMainConstraints.length; i < size; i++) { Constraint c = table.fkMainConstraints[i]; int action = delete ? c.core.deleteAction : c.core.updateAction; 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()); if (!refiterator.hasNext()) { refiterator.release(); continue; } while (refiterator.hasNext()) { Row refRow = refiterator.getNextRow(); 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; } switch (action) { case SchemaObject.ReferentialAction.CASCADE : { if (delete) { if (navigator.addRow(refRow)) { performReferentialActions(session, c.core.refTable, navigator, refRow, null, null, path); } 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 : case SchemaObject.ReferentialAction.RESTRICT : { if (navigator.containsDeletedRow(refRow)) { continue; } int errorCode = c.core.deleteAction == SchemaObject.ReferentialAction .NO_ACTION ? ErrorCode.X_23504 : ErrorCode.X_23001; String[] info = new String[] { c.core.refName.name, c.core.refTable.getName().name }; refiterator.release(); throw Error.error(null, errorCode, ErrorCode.CONSTRAINT, info); } default : continue; } refData = navigator.addRow(session, refRow, refData, table.getColumnTypes(), c.core.refCols); if (!path.add(c)) { continue; } performReferentialActions(session, c.core.refTable, navigator, refRow, refData, c.core.refCols, path); path.remove(c); } refiterator.release(); } } }