/* 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.jdbc.jdbcResultSet; import org.hsqldb.lib.HashMappedList; import org.hsqldb.lib.HsqlArrayList; import org.hsqldb.lib.java.JavaSystem; // boucherb@users 200404xx - fixed broken CALL statement result set unwrapping; // fixed broken support for prepared SELECT...INTO /** * Provides execution of CompiledStatement objects. <p> * * If multiple threads access a CompiledStatementExecutor.execute() * concurrently, they must be synchronized externally, relative to both * this object's Session and the Session's Database object. Internally, this * is accomplished in Session.execute() by synchronizing on the Session * object's Database object. * * @author boucherb@users * @version 1.7.2 * @since 1.7.2 */ final class CompiledStatementExecutor { private Session session; private Result updateResult; private static Result emptyZeroResult = new Result(ResultConstants.UPDATECOUNT); private static Result updateOneResult = new Result(ResultConstants.UPDATECOUNT); static { updateOneResult.updateCount = 1; } /** * Creates a new instance of CompiledStatementExecutor. * * @param session the context in which to perform the execution */ CompiledStatementExecutor(Session session) { this.session = session; updateResult = new Result(ResultConstants.UPDATECOUNT); } /** * Executes a generic CompiledStatement. Execution includes first building * any subquery result dependencies and clearing them after the main result * is built. * * @return the result of executing the statement * @param cs any valid CompiledStatement */ Result execute(CompiledStatement cs, Object[] paramValues) { Result result = null; JavaSystem.gc(); for (int i = 0; i < cs.parameters.length; i++) { cs.parameters[i].bind(paramValues[i]); } try { cs.materializeSubQueries(session); result = executeImpl(cs); } catch (Throwable t) { result = new Result(t, cs.sql); } // clear redundant data cs.dematerializeSubQueries(session); if (result == null) { result = emptyZeroResult; } return result; } /** * Executes a generic CompiledStatement. Execution excludes building * subquery result dependencies and clearing them after the main result * is built. * * @param cs any valid CompiledStatement * @throws HsqlException if a database access error occurs * @return the result of executing the statement */ private Result executeImpl(CompiledStatement cs) throws HsqlException { switch (cs.type) { case CompiledStatement.SELECT : return executeSelectStatement(cs); case CompiledStatement.INSERT_SELECT : return executeInsertSelectStatement(cs); case CompiledStatement.INSERT_VALUES : return executeInsertValuesStatement(cs); case CompiledStatement.UPDATE : return executeUpdateStatement(cs); case CompiledStatement.DELETE : return executeDeleteStatement(cs); case CompiledStatement.CALL : return executeCallStatement(cs); case CompiledStatement.DDL : return executeDDLStatement(cs); default : throw Trace.runtimeError( Trace.UNSUPPORTED_INTERNAL_OPERATION, "CompiledStatementExecutor.executeImpl()"); } } /** * Executes a CALL statement. It is assumed that the argument is * of the correct type. * * @param cs a CompiledStatement of type CompiledStatement.CALL * @throws HsqlException if a database access error occurs * @return the result of executing the statement */ private Result executeCallStatement(CompiledStatement cs) throws HsqlException { Expression e = cs.expression; // representing CALL Object o = e.getValue(session); // expression return value Result r; if (o instanceof Result) { return (Result) o; } else if (o instanceof jdbcResultSet) { return ((jdbcResultSet) o).rResult; } r = Result.newSingleColumnResult(CompiledStatement.RETURN_COLUMN_NAME, e.getDataType()); Object[] row = new Object[1]; row[0] = o; r.metaData.classNames[0] = e.getValueClassName(); r.add(row); return r; } // fredt - currently deletes that fail due to referential constraints are caught prior to // actual delete operation, so no nested transaction is required /** * Executes a DELETE statement. It is assumed that the argument is * of the correct type. * * @param cs a CompiledStatement of type CompiledStatement.DELETE * @throws HsqlException if a database access error occurs * @return the result of executing the statement */ private Result executeDeleteStatement(CompiledStatement cs) throws HsqlException { Table table = cs.targetTable; TableFilter filter = cs.targetFilter; int count = 0; if (filter.findFirst(session)) { Expression c = cs.condition; HsqlArrayList del; del = new HsqlArrayList(); do { if (c == null || c.testCondition(session)) { del.add(filter.currentRow); } } while (filter.next(session)); count = table.delete(session, del); } updateResult.updateCount = count; return updateResult; } /** * Executes an INSERT_SELECT statement. It is assumed that the argument * is of the correct type. * * @param cs a CompiledStatement of type CompiledStatement.INSERT_SELECT * @throws HsqlException if a database access error occurs * @return the result of executing the statement */ private Result executeInsertSelectStatement(CompiledStatement cs) throws HsqlException { Table t = cs.targetTable; Select s = cs.select; int[] ct = t.getColumnTypes(); // column types Result r = s.getResult(session, Integer.MAX_VALUE); Record rc = r.rRoot; int[] cm = cs.columnMap; // column map boolean[] ccl = cs.checkColumns; // column check list int len = cm.length; Object[] row; int count; boolean success = false; session.beginNestedTransaction(); try { while (rc != null) { row = t.getNewRowData(session, ccl); for (int i = 0; i < len; i++) { int j = cm[i]; if (ct[j] != r.metaData.colTypes[i]) { row[j] = Column.convertObject(rc.data[i], ct[j]); } else { row[j] = rc.data[i]; } } rc.data = row; rc = rc.next; } count = t.insert(session, r); success = true; } finally { session.endNestedTransaction(!success); } updateResult.updateCount = count; return updateResult; } /** * Executes an INSERT_VALUES statement. It is assumed that the argument * is of the correct type. * * @param cs a CompiledStatement of type CompiledStatement.INSERT_VALUES * @throws HsqlException if a database access error occurs * @return the result of executing the statement */ private Result executeInsertValuesStatement(CompiledStatement cs) throws HsqlException { Table t = cs.targetTable; Object[] row = t.getNewRowData(session, cs.checkColumns); int[] cm = cs.columnMap; // column map Expression[] acve = cs.columnValues; Expression cve; int[] ct = t.getColumnTypes(); // column types int ci; // column index int len = acve.length; for (int i = 0; i < len; i++) { cve = acve[i]; ci = cm[i]; row[ci] = cve.getValue(session, ct[ci]); } t.insert(session, row); return updateOneResult; } /** * Executes a SELECT statement. It is assumed that the argument * is of the correct type. * * @param cs a CompiledStatement of type CompiledStatement.SELECT * @throws HsqlException if a database access error occurs * @return the result of executing the statement */ private Result executeSelectStatement(CompiledStatement cs) throws HsqlException { Select select = cs.select; Result result; if (select.sIntoTable != null) { // session level user rights session.checkDDLWrite(); boolean exists = session.database.schemaManager.findUserTable( session, select.sIntoTable.name, select.sIntoTable.schema.name) != null; if (exists) { throw Trace.error(Trace.TABLE_ALREADY_EXISTS, select.sIntoTable.name); } result = select.getResult(session, Integer.MAX_VALUE); result = session.dbCommandInterpreter.processSelectInto(result, select.sIntoTable, select.intoType); session.getDatabase().setMetaDirty(false); } else { result = select.getResult(session, session.getMaxRows()); } return result; } /** * Executes an UPDATE statement. It is assumed that the argument * is of the correct type. * * @param cs a CompiledStatement of type CompiledStatement.UPDATE * @throws HsqlException if a database access error occurs * @return the result of executing the statement */ private Result executeUpdateStatement(CompiledStatement cs) throws HsqlException { Table table = cs.targetTable; TableFilter filter = cs.targetFilter; int count = 0; if (filter.findFirst(session)) { int[] colmap = cs.columnMap; // column map Expression[] colvalues = cs.columnValues; Expression condition = cs.condition; // update condition int len = colvalues.length; HashMappedList rowset = new HashMappedList(); int size = table.getColumnCount(); int[] coltypes = table.getColumnTypes(); boolean success = false; do { if (condition == null || condition.testCondition(session)) { try { Row row = filter.currentRow; Object[] ni = table.getEmptyRowData(); System.arraycopy(row.getData(), 0, ni, 0, size); for (int i = 0; i < len; i++) { int ci = colmap[i]; ni[ci] = colvalues[i].getValue(session, coltypes[ci]); } rowset.add(row, ni); } catch (HsqlInternalException e) {} } } while (filter.next(session)); session.beginNestedTransaction(); try { count = table.update(session, rowset, colmap); success = true; } finally { // update failed (constraint violation) or succeeded session.endNestedTransaction(!success); } } updateResult.updateCount = count; return updateResult; } /** * Executes a DDL statement. It is assumed that the argument * is of the correct type. * * @param cs a CompiledStatement of type CompiledStatement.DDL * @throws HsqlException if a database access error occurs * @return the result of executing the statement */ private Result executeDDLStatement(CompiledStatement cs) throws HsqlException { return session.sqlExecuteDirectNoPreChecks(cs.sql); } }