org.hsqldb.StatementDML Maven / Gradle / Ivy
Show all versions of sqltool Show documentation
/* 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.
*
* @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.
*
* 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);
}
}