/* 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");
}
}