/* 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.ArraySort; import org.hsqldb.lib.ArrayUtil; import org.hsqldb.lib.HashSet; import org.hsqldb.lib.OrderedHashSet; import org.hsqldb.result.Result; import org.hsqldb.result.ResultMetaData; import org.hsqldb.store.ValuePool; /** * Statement implementation for DML and base DQL statements. * * @author Campbell Boucher-Burnett (boucherb@users dot sourceforge.net) * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 1.9.0 * @since 1.7.2 */ // fredt@users 20040404 - patch 1.7.2 - fixed type resolution for parameters // boucherb@users 200404xx - patch 1.7.2 - changed parameter naming scheme for SQLCI client usability/support // fredt@users 20050609 - 1.8.0 - fixed EXPLAIN PLAN by implementing describe(Session) // fredt@users - 1.9.0 - support for generated column reporting // fredt@users - 1.9.0 - support for multi-row inserts public abstract class StatementDMQL extends Statement { public static final String PCOL_PREFIX = "@p"; static final String RETURN_COLUMN_NAME = "@p0"; /** target table for INSERT_XXX, UPDATE and DELETE and MERGE */ Table targetTable; Table baseTable; /** column map of query expression */ int[] baseColumnMap; RangeVariable[] targetRangeVariables = RangeVariable.emptyArray; /** source table for MERGE */ Table sourceTable; /** condition expression for UPDATE, MERGE and DELETE */ Expression condition; /** for TRUNCATE variation of DELETE */ boolean restartIdentity; /** column map for INSERT operation direct or via MERGE */ int[] insertColumnMap = ValuePool.emptyIntArray; /** column map for UPDATE operation direct or via MERGE */ int[] updateColumnMap = ValuePool.emptyIntArray; int[] baseUpdateColumnMap = ValuePool.emptyIntArray; /** Column value Expressions for UPDATE and MERGE. */ Expression[] updateExpressions = Expression.emptyArray; /** Column value Expressions for MERGE */ Expression[][] multiColumnValues; /** INSERT_VALUES */ Expression insertExpression; /** * Flags indicating which columns' values will/will not be * explicitly set. */ boolean[] insertCheckColumns; boolean[] updateCheckColumns; /** * Select to be evaluated when this is an INSERT_SELECT or * SELECT statement */ QueryExpression queryExpression; /** * Parse-order array of Expression objects, all of type PARAMETER , * involved in some way in any INSERT_XXX, UPDATE, DELETE, SELECT or * CALL CompiledStatement */ ExpressionColumn[] parameters; /** * int[] contains column indexes for generated values */ int[] generatedIndexes; /** * ResultMetaData for generated values */ ResultMetaData generatedResultMetaData; /** * ResultMetaData for parameters */ ResultMetaData parameterMetaData; /** * Subqueries inverse usage depth order */ SubQuery[] subqueries = SubQuery.emptySubqueryArray; /** * Total number of RangeIterator objects used */ int rangeIteratorCount; /** * Database objects used */ NumberSequence[] sequences; Routine[] routines; RangeVariable[] rangeVariables; StatementDMQL(int type, int group, HsqlName schemaName) { super(type, group); this.schemaName = schemaName; this.isTransactionStatement = true; } void setBaseIndexColumnMap() { if (targetTable != baseTable) { baseColumnMap = targetTable.getBaseTableColumnMap(); } } public Result execute(Session session) { Result result; if (targetTable != null && session.isReadOnly() && !targetTable.isTemp()) { HsqlException e = Error.error(ErrorCode.X_25006); return Result.newErrorResult(e); } if (isExplain) { return getExplainResult(session); } try { if (subqueries.length > 0) { materializeSubQueries(session); } result = getResult(session); } catch (Throwable t) { result = Result.newErrorResult(t, null); result.getException().setStatementType(group, type); } session.sessionContext.clearStructures(this); return result; } private Result getExplainResult(Session session) { Result result = Result.newSingleColumnStringResult("OPERATION", describe(session)); OrderedHashSet set = getReferences(); result.navigator.add(new Object[]{ "Object References" }); for (int i = 0; i < set.size(); i++) { HsqlName name = (HsqlName) set.get(i); result.navigator.add(new Object[]{ name.getSchemaQualifiedStatementName() }); } result.navigator.add(new Object[]{ "Read Locks" }); for (int i = 0; i < readTableNames.length; i++) { HsqlName name = readTableNames[i]; result.navigator.add(new Object[]{ name.getSchemaQualifiedStatementName() }); } result.navigator.add(new Object[]{ "WriteLocks" }); for (int i = 0; i < writeTableNames.length; i++) { HsqlName name = writeTableNames[i]; result.navigator.add(new Object[]{ name.getSchemaQualifiedStatementName() }); } return result; } abstract Result getResult(Session session); abstract void collectTableNamesForRead(OrderedHashSet set); abstract void collectTableNamesForWrite(OrderedHashSet set); 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; } boolean[] getInsertOrUpdateColumnCheckList() { switch (type) { case StatementTypes.INSERT : return insertCheckColumns; case StatementTypes.UPDATE_WHERE : return updateCheckColumns; case StatementTypes.MERGE : boolean[] check = (boolean[]) ArrayUtil.duplicateArray(insertCheckColumns); ArrayUtil.orBooleanArray(updateCheckColumns, check); return check; } return null; } private void setParameters() { for (int i = 0; i < parameters.length; i++) { parameters[i].parameterIndex = i; } } void materializeSubQueries(Session session) { HashSet subqueryPopFlags = new HashSet(); for (int i = 0; i < subqueries.length; i++) { SubQuery sq = subqueries[i]; // VIEW working tables may be reused in a single query but they are filled only once if (!subqueryPopFlags.add(sq)) { continue; } if (!sq.isCorrelated()) { sq.materialise(session); } } } SubQuery[] getSubqueries(Session session) { OrderedHashSet subQueries = null; for (int i = 0; i < targetRangeVariables.length; i++) { if (targetRangeVariables[i] == null) { continue; } OrderedHashSet set = targetRangeVariables[i].getSubqueries(); subQueries = OrderedHashSet.addAll(subQueries, set); } for (int i = 0; i < updateExpressions.length; i++) { subQueries = updateExpressions[i].collectAllSubqueries(subQueries); } if (insertExpression != null) { subQueries = insertExpression.collectAllSubqueries(subQueries); } if (condition != null) { subQueries = condition.collectAllSubqueries(subQueries); } if (queryExpression != null) { OrderedHashSet set = queryExpression.getSubqueries(); subQueries = OrderedHashSet.addAll(subQueries, set); } if (subQueries == null || subQueries.size() == 0) { return SubQuery.emptySubqueryArray; } SubQuery[] subQueryArray = new SubQuery[subQueries.size()]; subQueries.toArray(subQueryArray); ArraySort.sort(subQueryArray, 0, subQueryArray.length, subQueryArray[0]); for (int i = 0; i < subQueryArray.length; i++) { subQueryArray[i].prepareTable(session); } return subQueryArray; } void setDatabseObjects(Session session, CompileContext compileContext) { parameters = compileContext.getParameters(); setParameters(); setParameterMetaData(); subqueries = getSubqueries(session); rangeIteratorCount = compileContext.getRangeVarCount(); rangeVariables = compileContext.getRangeVariables(); sequences = compileContext.getSequences(); routines = compileContext.getRoutines(); OrderedHashSet set = new OrderedHashSet(); collectTableNamesForWrite(set); if (set.size() > 0) { writeTableNames = new HsqlName[set.size()]; set.toArray(writeTableNames); set.clear(); } collectTableNamesForRead(set); set.removeAll(writeTableNames); if (set.size() > 0) { readTableNames = new HsqlName[set.size()]; set.toArray(readTableNames); } references = compileContext.getSchemaObjectNames(); if (targetTable != null) { references.add(targetTable.getName()); } } /** * Determines if the authorizations are adequate * to execute the compiled object. Completion requires the list of * all database objects in a compiled statement. */ void checkAccessRights(Session session) { if (targetTable != null && !targetTable.isTemp()) { if (targetTable.getOwner().isSystem()) { if (!session.getUser().isSystem()) { throw Error.error(ErrorCode.X_42501, targetTable.getName().name); } } if (!session.isProcessingScript) { targetTable.checkDataReadOnly(); } session.checkReadWrite(); } if (session.isAdmin()) { return; } for (int i = 0; i < sequences.length; i++) { session.getGrantee().checkAccess(sequences[i]); } for (int i = 0; i < routines.length; i++) { if (routines[i].isLibraryRoutine()) { continue; } session.getGrantee().checkAccess(routines[i]); } for (int i = 0; i < rangeVariables.length; i++) { RangeVariable range = rangeVariables[i]; if (range.rangeTable.getSchemaName() == SqlInvariants.SYSTEM_SCHEMA_HSQLNAME) { continue; } session.getGrantee().checkSelect(range.rangeTable, range.usedColumns); } switch (type) { case StatementTypes.CALL : { break; } case StatementTypes.INSERT : { session.getGrantee().checkInsert(targetTable, insertCheckColumns); break; } case StatementTypes.SELECT_CURSOR : break; case StatementTypes.DELETE_WHERE : { session.getGrantee().checkDelete(targetTable); break; } case StatementTypes.UPDATE_WHERE : { session.getGrantee().checkUpdate(targetTable, updateCheckColumns); break; } case StatementTypes.MERGE : { session.getGrantee().checkInsert(targetTable, insertCheckColumns); session.getGrantee().checkUpdate(targetTable, updateCheckColumns); break; } } } Result getWriteAccessResult(Session session) { try { if (targetTable != null && !targetTable.isTemp()) { session.checkReadWrite(); } } catch (HsqlException e) { return Result.newErrorResult(e); } return null; } /** * Returns the metadata, which is empty if the CompiledStatement does not * generate a Result. */ public ResultMetaData getResultMetaData() { switch (type) { case StatementTypes.DELETE_WHERE : case StatementTypes.INSERT : case StatementTypes.UPDATE_WHERE : case StatementTypes.MERGE : return ResultMetaData.emptyResultMetaData; default : throw Error.runtimeError(ErrorCode.U_S0500, "StatementDMQL"); } } /** @todo 1.9.0 - build the metadata only once and reuse */ /** * Returns the metadata for the placeholder parameters. */ public ResultMetaData getParametersMetaData() { return parameterMetaData; } void setParameterMetaData() { int offset; int idx; boolean hasReturnValue; offset = 0; if (parameters.length == 0) { parameterMetaData = ResultMetaData.emptyParamMetaData; return; } // NO: Not yet // hasReturnValue = (type == CALL && !expression.isProcedureCall()); // // if (hasReturnValue) { // outlen++; // offset = 1; // } parameterMetaData = ResultMetaData.newParameterMetaData(parameters.length); // NO: Not yet // if (hasReturnValue) { // e = expression; // out.sName[0] = DIProcedureInfo.RETURN_COLUMN_NAME; // out.sClassName[0] = e.getValueClassName(); // out.colType[0] = e.getDataType(); // out.colSize[0] = e.getColumnSize(); // out.colScale[0] = e.getColumnScale(); // out.nullability[0] = e.nullability; // out.isIdentity[0] = false; // out.paramMode[0] = expression.PARAM_OUT; // } for (int i = 0; i < parameters.length; i++) { idx = i + offset; // always i + 1. We currently use the convention of @p0 to name the // return value OUT parameter parameterMetaData.columnLabels[idx] = StatementDMQL.PCOL_PREFIX + (i + 1); parameterMetaData.columnTypes[idx] = parameters[i].dataType; byte parameterMode = SchemaObject.ParameterModes.PARAM_IN; if (parameters[i].column != null && parameters[i].column.getParameterMode() != SchemaObject.ParameterModes.PARAM_UNKNOWN) { parameterMode = parameters[i].column.getParameterMode(); } parameterMetaData.paramModes[idx] = parameterMode; parameterMetaData.paramNullable[idx] = parameters[i].column == null ? SchemaObject.Nullability.NULLABLE : parameters[i].column.getNullability(); } } /** * Retrieves a String representation of this object. */ public String describe(Session session) { try { return describeImpl(session); } catch (Throwable e) { e.printStackTrace(); return e.toString(); } } /** * Provides the toString() implementation. */ String describeImpl(Session session) throws Exception { StringBuffer sb; sb = new StringBuffer(); int blanks = 0; switch (type) { case StatementTypes.SELECT_CURSOR : { sb.append(queryExpression.describe(session, 0)); appendParms(sb).append('\n'); appendSubqueries(session, sb, 2); return sb.toString(); } case StatementTypes.INSERT : { if (queryExpression == null) { sb.append("INSERT VALUES"); sb.append('[').append('\n'); appendMultiColumns(sb, insertColumnMap).append('\n'); appendTable(sb).append('\n'); appendParms(sb).append('\n'); appendSubqueries(session, sb, 2).append(']'); return sb.toString(); } else { sb.append("INSERT SELECT"); sb.append('[').append('\n'); appendColumns(sb, insertColumnMap).append('\n'); appendTable(sb).append('\n'); sb.append(queryExpression.describe(session, blanks)).append('\n'); appendParms(sb).append('\n'); appendSubqueries(session, sb, 2).append(']'); return sb.toString(); } } case StatementTypes.UPDATE_WHERE : { sb.append("UPDATE"); sb.append('[').append('\n'); appendColumns(sb, updateColumnMap).append('\n'); appendTable(sb).append('\n'); appendCondition(session, sb); for (int i = 0; i < targetRangeVariables.length; i++) { sb.append(targetRangeVariables[i].describe(session, blanks)).append('\n'); } appendParms(sb).append('\n'); appendSubqueries(session, sb, 2).append(']'); return sb.toString(); } case StatementTypes.DELETE_WHERE : { sb.append("DELETE"); sb.append('[').append('\n'); appendTable(sb).append('\n'); appendCondition(session, sb); for (int i = 0; i < targetRangeVariables.length; i++) { sb.append(targetRangeVariables[i].describe(session, blanks)).append('\n'); } appendParms(sb).append('\n'); appendSubqueries(session, sb, 2).append(']'); return sb.toString(); } case StatementTypes.CALL : { sb.append("CALL"); sb.append('[').append(']'); return sb.toString(); } case StatementTypes.MERGE : { sb.append("MERGE"); sb.append('[').append('\n'); appendMultiColumns(sb, insertColumnMap).append('\n'); appendColumns(sb, updateColumnMap).append('\n'); appendTable(sb).append('\n'); appendCondition(session, sb); for (int i = 0; i < targetRangeVariables.length; i++) { sb.append(targetRangeVariables[i].describe(session, blanks)).append('\n'); } appendParms(sb).append('\n'); appendSubqueries(session, sb, 2).append(']'); return sb.toString(); } default : { return "UNKNOWN"; } } } private StringBuffer appendSubqueries(Session session, StringBuffer sb, int blanks) { sb.append("SUBQUERIES["); for (int i = 0; i < subqueries.length; i++) { sb.append("\n[level=").append(subqueries[i].level).append('\n'); if (subqueries[i].queryExpression != null) { sb.append(subqueries[i].queryExpression.describe(session, blanks)); } sb.append("]"); } sb.append(']'); return sb; } private StringBuffer appendTable(StringBuffer sb) { sb.append("TABLE[").append(targetTable.getName().name).append(']'); return sb; } private StringBuffer appendSourceTable(StringBuffer sb) { sb.append("SOURCE TABLE[").append(sourceTable.getName().name).append( ']'); return sb; } private StringBuffer appendColumns(StringBuffer sb, int[] columnMap) { if (columnMap == null || updateExpressions.length == 0) { return sb; } sb.append("COLUMNS=["); for (int i = 0; i < columnMap.length; i++) { sb.append('\n').append(columnMap[i]).append(':').append( ' ').append( targetTable.getColumn(columnMap[i]).getNameString()); } for (int i = 0; i < updateExpressions.length; i++) { sb.append('[').append(updateExpressions[i]).append(']'); } sb.append(']'); return sb; } private StringBuffer appendMultiColumns(StringBuffer sb, int[] columnMap) { if (columnMap == null || multiColumnValues == null) { return sb; } sb.append("COLUMNS=["); for (int j = 0; j < multiColumnValues.length; j++) { for (int i = 0; i < columnMap.length; i++) { sb.append('\n').append(columnMap[i]).append(':').append( ' ').append( targetTable.getColumn(columnMap[i]).getName().name).append( '[').append(multiColumnValues[j][i]).append(']'); } } sb.append(']'); return sb; } private StringBuffer appendParms(StringBuffer sb) { sb.append("PARAMETERS=["); for (int i = 0; i < parameters.length; i++) { sb.append('\n').append('@').append(i).append('[').append( parameters[i].describe(null, 0)).append(']'); } sb.append(']'); return sb; } private StringBuffer appendCondition(Session session, StringBuffer sb) { return condition == null ? sb.append("CONDITION[]\n") : sb.append("CONDITION[").append( condition.describe(session, 0)).append( "]\n"); } public void resolve(Session session) {} public RangeVariable[] getRangeVariables() { return rangeVariables; } public final boolean isCatalogChange() { return false; } }