/* Copyright (c) 2001-2008, 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; /** * A simple structure class for holding the products of * statement compilation for later execution. * * @author boucherb@users * @version 1.7.2 * @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) final class CompiledStatement { static final String PCOL_PREFIX = "@p"; static final String RETURN_COLUMN_NAME = "@p0"; static final int UNKNOWN = 0; // enumeration of allowable CompiledStatement types static final int INSERT_VALUES = 1; static final int INSERT_SELECT = 2; static final int UPDATE = 3; static final int DELETE = 4; static final int SELECT = 5; static final int SELECT_INTO = 6; static final int CALL = 7; // enumeration of catagories static final int DML = 7; static final int DQL = 8; static final int DDL = 9; /** id in CompiledStatementManager */ int id; /** false when cleared */ boolean isValid = true; /** target table for INSERT_XXX, UPDATE and DELETE */ Table targetTable; /** table filter for UPDATE and DELETE */ TableFilter targetFilter; /** condition expression for UPDATE and DELETE */ Expression condition; /** column map for INSERT_XXX, UPDATE */ int[] columnMap; /** Column value Expressions for INSERT_VALUES and UPDATE. */ Expression[] columnValues; /** * Flags indicating which columns' values will/will not be * explicitly set. */ boolean[] checkColumns; /** Expression to be evaluated when this is a CALL statement. */ Expression expression; /** * Select to be evaluated when this is an INSERT_SELECT or * SELECT statement */ Select select; /** * Parse-order array of Expression objects, all of iType == PARAM , * involved in some way in any INSERT_XXX, UPDATE, DELETE, SELECT or * CALL CompiledStatement */ Expression[] parameters; /** * int[] contains type of each parameter */ int[] paramTypes; /** * Subqueries inverse parse depth order */ SubQuery[] subqueries; /** * The type of this CompiledStatement. <p> * * One of: <p> * * <ol> * <li>UNKNOWN * <li>INSERT_VALUES * <li>INSERT_SELECT * <li>UPDATE * <li>DELETE * <li>SELECT * <li>CALL * <li>DDL * </ol> */ int type; /** * The SQL string that produced this compiled statement */ String sql; /** * The default schema name used to resolve names in the sql */ final HsqlName schemaHsqlName; /** * Creates a new instance of CompiledStatement for DDL * */ CompiledStatement(HsqlName schema) { parameters = new Expression[0]; paramTypes = new int[0]; subqueries = new SubQuery[0]; type = DDL; schemaHsqlName = schema; } /** * Initializes this as a DELETE statement * * @param targetFilter * @param deleteCondition * @param parameters */ CompiledStatement(Session session, Database database, HsqlName schema, TableFilter targetFilter, Expression deleteCondition, SubQuery[] subqueries, Expression[] params) throws HsqlException { schemaHsqlName = schema; this.targetFilter = targetFilter; targetTable = targetFilter.filterTable; if (deleteCondition != null) { condition = new Expression(deleteCondition); condition.resolveTables(targetFilter); condition.resolveTypes(session); targetFilter.setConditions(session, condition); } setParameters(params); setSubqueries(subqueries); type = DELETE; } /** * Instantiate this as an UPDATE statement. * * @param targetTable * @param columnMap * @param columnValues * @param updateCondition * @param params */ CompiledStatement(Session session, Database database, HsqlName schema, TableFilter targetFilter, int[] columnMap, Expression[] columnValues, Expression updateCondition, SubQuery[] subqueries, Expression[] params) throws HsqlException { schemaHsqlName = schema; this.targetFilter = targetFilter; targetTable = targetFilter.filterTable; this.columnMap = columnMap; this.columnValues = columnValues; for (int i = 0; i < columnValues.length; i++) { Expression cve = columnValues[i]; if (cve.isParam()) { cve.setTableColumnAttributes(targetTable, columnMap[i]); } else { cve.resolveTables(targetFilter); cve.resolveTypes(session); } } if (updateCondition != null) { condition = new Expression(updateCondition); condition.resolveTables(targetFilter); condition.resolveTypes(session); targetFilter.setConditions(session, condition); } setParameters(params); setSubqueries(subqueries); type = UPDATE; } /** * Instantiate this as an INSERT_VALUES statement. * * @param targetTable * @param columnMap * @param columnValues * @param checkColumns * @param params */ CompiledStatement(HsqlName schema, Table targetTable, int[] columnMap, Expression[] columnValues, boolean[] checkColumns, SubQuery[] subqueries, Expression[] params) throws HsqlException { schemaHsqlName = schema; this.targetTable = targetTable; this.columnMap = columnMap; this.checkColumns = checkColumns; this.columnValues = columnValues; for (int i = 0; i < columnValues.length; i++) { Expression cve = columnValues[i]; // If its not a param, it's already been resolved in // Parser.getColumnValueExpressions if (cve.isParam()) { cve.setTableColumnAttributes(targetTable, columnMap[i]); } } setParameters(params); setSubqueries(subqueries); type = INSERT_VALUES; } /** * Instantiate this as an INSERT_SELECT statement. * * @param targetTable * @param columnMap * @param checkColumns * @param select * @param params */ CompiledStatement(Session session, Database database, HsqlName schema, Table targetTable, int[] columnMap, boolean[] checkColumns, Select select, SubQuery[] subqueries, Expression[] params) throws HsqlException { schemaHsqlName = schema; this.targetTable = targetTable; this.columnMap = columnMap; this.checkColumns = checkColumns; this.select = select; // resolve any parameters in SELECT resolveInsertParameterTypes(); // set select result metadata etc. select.prepareResult(session); setParameters(params); setSubqueries(subqueries); type = INSERT_SELECT; } /** * Instantiate this as a SELECT statement. * * @param select * @param params */ CompiledStatement(Session session, Database database, HsqlName schema, Select select, SubQuery[] subqueries, Expression[] params) throws HsqlException { schemaHsqlName = schema; this.select = select; // resolve any parameters in SELECT as VARCHAR for (int i = 0; i < select.iResultLen; i++) { Expression colexpr = select.exprColumns[i]; if (colexpr.getDataType() == Types.NULL) { colexpr.setDataType(Types.VARCHAR); } } // set select result metadata etc. select.prepareResult(session); setParameters(params); setSubqueries(subqueries); type = SELECT; } /** * Instantiate this as a CALL statement. * * @param expression * @param params */ CompiledStatement(Session session, Database database, HsqlName schema, Expression expression, SubQuery[] subqueries, Expression[] params) throws HsqlException { schemaHsqlName = schema; this.expression = expression; expression.resolveTypes(session); expression.paramMode = Expression.PARAM_OUT; setParameters(params); setSubqueries(subqueries); type = CALL; } /** * For parameters in INSERT_VALUES and INSERT_SELECT lists */ private void resolveInsertParameterTypes() { for (int i = 0; i < select.iResultLen; i++) { Expression colexpr = select.exprColumns[i]; if (colexpr.getDataType() == Types.NULL) { Column col = targetTable.getColumn(columnMap[i]); colexpr.setDataType(col.getType()); } } } private void setParameters(Expression[] params) { this.parameters = params; int[] types = new int[parameters.length]; for (int i = 0; i < parameters.length; i++) { types[i] = parameters[i].getDataType(); } this.paramTypes = types; } private void setSubqueries(SubQuery[] subqueries) { this.subqueries = subqueries; } void materializeSubQueries(Session session) throws HsqlException { for (int i = 0; i < subqueries.length; i++) { SubQuery sq = subqueries[i]; // VIEW working table contents are filled only once per query and reused if (sq.isMaterialised) { continue; } if (sq.isResolved) { sq.populateTable(session); sq.isMaterialised = true; } } } void dematerializeSubQueries(Session session) { if (subqueries == null) { return; } for (int i = 0; i < subqueries.length; i++) { subqueries[i].table.clearAllRows(session); subqueries[i].isMaterialised = false; } } void clearVariables() { isValid = false; targetTable = null; targetFilter = null; condition = null; columnMap = null; columnValues = null; checkColumns = null; expression = null; select = null; parameters = null; paramTypes = null; subqueries = null; } boolean canExecute(Session session) throws HsqlException { switch (type) { case CALL : {} case SELECT : for (int i = 0; i < select.tFilter.length; i++) { HsqlName name = select.tFilter[i].filterTable.getName(); session.check(name, UserManager.SELECT); } break; case INSERT_SELECT : break; case DELETE : session.check(targetTable.getName(), UserManager.DELETE); break; case INSERT_VALUES : session.check(targetTable.getName(), UserManager.INSERT); break; case UPDATE : session.check(targetTable.getName(), UserManager.UPDATE); break; case DDL : } return true; } void checkTableWriteAccess(Session session, Table table) throws HsqlException { // session level user rights session.checkReadWrite(); // object type if (table.isView()) { throw Trace.error(Trace.NOT_A_TABLE, table.getName().name); } // object readonly table.checkDataReadOnly(); } private static final Result updateCountResult = new Result(ResultConstants.UPDATECOUNT); Result describeResult() { switch (type) { case CALL : { // TODO: // // 1.) standard to register metadata for columns of // the primary result set, if any, generated by call // // 2.) Represent the return value, if any (which is // not, in truth, a result set), as an OUT parameter // // For now, I've reverted a bunch of code I had in place // and instead simply reflect things as the are, describing // a single column result set that communicates // the return value. If the expression generating the // return value has a void return type, a result set // is described whose single column is of type NULL Expression e; Result r; e = expression; r = Result.newSingleColumnResult( CompiledStatement.RETURN_COLUMN_NAME, e.getDataType()); r.metaData.classNames[0] = e.getValueClassName(); // no more setup for r; all the defaults apply return r; } case SELECT : return select.sIntoTable == null ? select.describeResult() : updateCountResult; case DELETE : case INSERT_SELECT : case INSERT_VALUES : case UPDATE : case DDL : // will result in return updateCountResult; default : return new Result( Trace.runtimeError( Trace.UNSUPPORTED_INTERNAL_OPERATION, "CompiledStatement.describeResult()"), null); } } Result describeParameters() { Result out; Expression e; int outlen; int offset; int idx; boolean hasReturnValue; outlen = parameters.length; offset = 0; // NO: Not yet // hasReturnValue = (type == CALL && !expression.isProcedureCall()); // // if (hasReturnValue) { // outlen++; // offset = 1; // } out = Result.newParameterDescriptionResult(outlen); // 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++) { e = parameters[i]; idx = i + offset; // always i + 1. We currently use the convention of @p0 to name the // return value OUT parameter out.metaData.colNames[idx] = CompiledStatement.PCOL_PREFIX + (i + 1); // sLabel is meaningless in this context. out.metaData.classNames[idx] = e.getValueClassName(); out.metaData.colTypes[idx] = e.getDataType(); out.metaData.colSizes[idx] = e.getColumnSize(); out.metaData.colScales[idx] = e.getColumnScale(); out.metaData.colNullable[idx] = e.nullability; out.metaData.isIdentity[idx] = e.isIdentity; // currently will always be Expression.PARAM_IN out.metaData.paramMode[idx] = e.paramMode; } return out; } /** * Retrieves a String representation of this object. * * @return the String representation of this object */ public String describe(Session session) { try { return describeImpl(session); } catch (Exception e) { return e.toString(); } } /** * Provides the toString() implementation. * * @throws Exception if a database access or io error occurs * @return the String representation of this object */ private String describeImpl(Session session) throws Exception { StringBuffer sb; sb = new StringBuffer(); switch (type) { case SELECT : { sb.append(select.describe(session)); appendParms(sb).append('\n'); appendSubqueries(sb); return sb.toString(); } case INSERT_VALUES : { sb.append("INSERT VALUES"); sb.append('[').append('\n'); appendColumns(sb).append('\n'); appendTable(sb).append('\n'); appendParms(sb).append('\n'); appendSubqueries(sb).append(']'); return sb.toString(); } case INSERT_SELECT : { sb.append("INSERT SELECT"); sb.append('[').append('\n'); appendColumns(sb).append('\n'); appendTable(sb).append('\n'); sb.append(select.describe(session)).append('\n'); appendParms(sb).append('\n'); appendSubqueries(sb).append(']'); return sb.toString(); } case UPDATE : { sb.append("UPDATE"); sb.append('[').append('\n'); appendColumns(sb).append('\n'); appendTable(sb).append('\n'); appendCondition(session, sb); sb.append(targetFilter.describe(session)).append('\n'); appendParms(sb).append('\n'); appendSubqueries(sb).append(']'); return sb.toString(); } case DELETE : { sb.append("DELETE"); sb.append('[').append('\n'); appendTable(sb).append('\n'); appendCondition(session, sb); sb.append(targetFilter.describe(session)).append('\n'); appendParms(sb).append('\n'); appendSubqueries(sb).append(']'); return sb.toString(); } case CALL : { sb.append("CALL"); sb.append('['); sb.append(expression.describe(session)).append('\n'); appendParms(sb).append('\n'); appendSubqueries(sb).append(']'); return sb.toString(); } default : { return "UNKNOWN"; } } } private StringBuffer appendSubqueries(StringBuffer sb) { sb.append("SUBQUERIES["); for (int i = 0; i < subqueries.length; i++) { sb.append("\n[level=").append(subqueries[i].level).append( '\n').append("hasParams=").append( subqueries[i].hasParams).append('\n'); if (subqueries[i].select != null) { sb.append("org.hsqldb.Select@").append( Integer.toHexString(subqueries[i].select.hashCode())); } sb.append("]"); } sb.append(']'); return sb; } private StringBuffer appendTable(StringBuffer sb) { sb.append("TABLE[").append(targetTable.getName().name).append(']'); return sb; } private StringBuffer appendColumns(StringBuffer 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]).columnName.name).append( '[').append(columnValues[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]).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)).append( "]\n"); } }