/* Copyright (c) 1995-2000, The Hypersonic SQL 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 Hypersonic SQL 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 THE HYPERSONIC SQL GROUP, * 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. * * This software consists of voluntary contributions made by many individuals * on behalf of the Hypersonic SQL Group. * * * For work added by the HSQL Development Group: * * 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 java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import org.hsqldb.HsqlNameManager.HsqlName; import org.hsqldb.jdbc.jdbcConnection; import org.hsqldb.lib.ArrayUtil; import org.hsqldb.lib.HashMappedList; import org.hsqldb.lib.HsqlArrayList; import org.hsqldb.lib.IntKeyHashMap; import org.hsqldb.lib.SimpleLog; import org.hsqldb.lib.java.JavaSystem; import org.hsqldb.store.ValuePool; // fredt@users 20020320 - doc 1.7.0 - update // fredt@users 20020315 - patch 1.7.0 - switch for scripting // fredt@users 20020130 - patch 476694 by velichko - transaction savepoints // additions in different parts to support savepoint transactions // fredt@users 20020910 - patch 1.7.1 by fredt - database readonly enforcement // fredt@users 20020912 - patch 1.7.1 by fredt - permanent internal connection // boucherb@users 20030512 - patch 1.7.2 - compiled statements // - session becomes execution hub // boucherb@users 20050510 - patch 1.7.2 - generalized Result packet passing // based command execution // - batch execution handling // fredt@users 20030628 - patch 1.7.2 - session proxy support // fredt@users 20040509 - patch 1.7.2 - SQL conformance for CURRENT_TIMESTAMP and other datetime functions /** * Implementation of a user session with the database. In 1.7.2 Session * becomes the public interface to an HSQLDB database, accessed locally or * remotely via SessionInterface. * * When as Session is closed, all references to internal engine objects are * set to null. But the session id and scripting mode may still be used for * scripting * * New class based based on original Hypersonic code. * Extensively rewritten and extended in successive versions of HSQLDB. * * @author Thomas Mueller (Hypersonic SQL Group) * @version 1.8.0 * @since 1.7.0 */ public class Session implements SessionInterface { // private volatile boolean isAutoCommit; private volatile boolean isReadOnly; private volatile boolean isClosed; // Database database; private User user; HsqlArrayList rowActionList; private boolean isNestedTransaction; private int nestedOldTransIndex; int isolationMode = SessionInterface.TX_READ_COMMITTED; long actionTimestamp; long transactionTimestamp; private int currentMaxRows; private int sessionMaxRows; private Number lastIdentity = ValuePool.getInt(0); private final int sessionId; HashMappedList savepoints; private boolean script; private Tokenizer tokenizer; private Parser parser; static final Result emptyUpdateCount = new Result(ResultConstants.UPDATECOUNT); // private jdbcConnection intConnection; // schema public HsqlName currentSchema; public HsqlName loggedSchema; private HsqlName oldSchema; // query processing boolean isProcessingScript; boolean isProcessingLog; // two types of temp tables private IntKeyHashMap indexArrayMap; private IntKeyHashMap indexArrayKeepMap; /** @todo fredt - clarify in which circumstances Session has to disconnect */ Session getSession() { return this; } /** * Constructs a new Session object. * * @param db the database to which this represents a connection * @param user the initial user * @param autocommit the initial autocommit value * @param readonly the initial readonly value * @param id the session identifier, as known to the database */ Session(Database db, User user, boolean autocommit, boolean readonly, int id) { sessionId = id; database = db; this.user = user; rowActionList = new HsqlArrayList(true); savepoints = new HashMappedList(4); isAutoCommit = autocommit; isReadOnly = readonly; dbCommandInterpreter = new DatabaseCommandInterpreter(this); compiledStatementExecutor = new CompiledStatementExecutor(this); compiledStatementManager = db.compiledStatementManager; tokenizer = new Tokenizer(); parser = new Parser(this, database, tokenizer); resetSchema(); } void resetSchema() { HsqlName initialSchema = user.getInitialSchema(); currentSchema = ((initialSchema == null) ? database.schemaManager.getDefaultSchemaHsqlName() : initialSchema); } /** * Retrieves the session identifier for this Session. * * @return the session identifier for this Session */ public int getId() { return sessionId; } /** * Closes this Session. */ public void close() { if (isClosed) { return; } synchronized (database) { // test again inside block if (isClosed) { return; } database.sessionManager.removeSession(this); rollback(); try { database.logger.writeToLog(this, Token.T_DISCONNECT); } catch (HsqlException e) {} clearIndexRoots(); clearIndexRootsKeep(); compiledStatementManager.removeSession(sessionId); database.closeIfLast(); database = null; user = null; rowActionList = null; savepoints = null; intConnection = null; compiledStatementExecutor = null; compiledStatementManager = null; dbCommandInterpreter = null; lastIdentity = null; isClosed = true; } } /** * Retrieves whether this Session is closed. * * @return true if this Session is closed */ public boolean isClosed() { return isClosed; } public void setIsolation(int level) throws HsqlException { isolationMode = level; } public int getIsolation() throws HsqlException { return isolationMode; } /** * Setter for iLastIdentity attribute. * * @param i the new value */ void setLastIdentity(Number i) { lastIdentity = i; } /** * Getter for iLastIdentity attribute. * * @return the current value */ Number getLastIdentity() { return lastIdentity; } /** * Retrieves the Database instance to which this * Session represents a connection. * * @return the Database object to which this Session is connected */ Database getDatabase() { return database; } /** * Retrieves the name, as known to the database, of the * user currently controlling this Session. * * @return the name of the user currently connected within this Session */ String getUsername() { return user.getName(); } /** * Retrieves the User object representing the user currently controlling * this Session. * * @return this Session's User object */ public User getUser() { return user; } /** * Sets this Session's User object to the one specified by the * user argument. * * @param user the new User object for this session */ void setUser(User user) { this.user = user; } int getMaxRows() { return currentMaxRows; } int getSQLMaxRows() { return sessionMaxRows; } /** * The SQL command SET MAXROWS n will override the Statement.setMaxRows(n) * until SET MAXROWS 0 is issued. * * NB this is dedicated to the SET MAXROWS sql statement and should not * otherwise be called. (fredt@users) */ void setSQLMaxRows(int rows) { currentMaxRows = sessionMaxRows = rows; } /** * Checks whether this Session's current User has the privileges of * the ADMIN role. * * @throws HsqlException if this Session's User does not have the * privileges of the ADMIN role. */ void checkAdmin() throws HsqlException { user.checkAdmin(); } /** * Checks whether this Session's current User has the set of rights * specified by the right argument, in relation to the database * object identifier specified by the object argument. * * @param object the database object to check * @param right the rights to check for * @throws HsqlException if the Session User does not have such rights */ void check(HsqlName object, int right) throws HsqlException { user.check(object, right); } /** * Checks the rights on a function * * @param object the function * @throws HsqlException if the Session User does not have such rights */ void check(String object) throws HsqlException { user.check(object); } /** * This is used for reading - writing to existing tables. * @throws HsqlException */ void checkReadWrite() throws HsqlException { if (isReadOnly) { throw Trace.error(Trace.DATABASE_IS_READONLY); } } /** * This is used for creating new database objects such as tables. * @throws HsqlException */ void checkDDLWrite() throws HsqlException { if (database.isFilesReadOnly() && !user.isSys()) { throw Trace.error(Trace.DATABASE_IS_READONLY); } checkReadWrite(); } /** * Adds a single-row deletion step to the transaction UNDO buffer. * * @param table the table from which the row was deleted * @param row the deleted row * @throws HsqlException */ boolean addDeleteAction(Table table, Row row) throws HsqlException { if (!isAutoCommit || isNestedTransaction) { Transaction t = new Transaction(true, table, row, actionTimestamp); rowActionList.add(t); database.txManager.addTransaction(this, t); return true; } else { table.removeRowFromStore(row); } return false; } /** * Adds a single-row insertion step to the transaction UNDO buffer. * * @param table the table into which the row was inserted * @param row the inserted row * @throws HsqlException */ boolean addInsertAction(Table table, Row row) throws HsqlException { if (!isAutoCommit || isNestedTransaction) { Transaction t = new Transaction(false, table, row, actionTimestamp); rowActionList.add(t); database.txManager.addTransaction(this, t); return true; } else { table.commitRowToStore(row); } return false; } /** * Setter for the autocommit attribute. * * @param autocommit the new value * @throws HsqlException */ public void setAutoCommit(boolean autocommit) { if (isClosed) { return; } synchronized (database) { if (autocommit != isAutoCommit) { commit(); isAutoCommit = autocommit; try { database.logger.writeToLog(this, getAutoCommitStatement()); } catch (HsqlException e) {} } } } public void startPhasedTransaction() throws HsqlException {} public void prepareCommit() throws HsqlException {} /** * Commits any uncommited transaction this Session may have open * * @throws HsqlException */ public void commit() { if (isClosed) { return; } synchronized (database) { if (!rowActionList.isEmpty()) { try { database.logger.writeCommitStatement(this); } catch (HsqlException e) {} } database.txManager.commit(this); clearIndexRoots(); } } /** * Rolls back any uncommited transaction this Session may have open. * * @throws HsqlException */ public void rollback() { if (isClosed) { return; } synchronized (database) { if (rowActionList.size() != 0) { try { database.logger.writeToLog(this, Token.T_ROLLBACK); } catch (HsqlException e) {} } database.txManager.rollback(this); clearIndexRoots(); } } /** * No-op in this implementation */ public void resetSession() throws HsqlException { throw new HsqlException("", "", 0); } /** * Implements a transaction SAVEPOINT. A new SAVEPOINT with the * name of an existing one replaces the old SAVEPOINT. * * @param name of the savepoint * @throws HsqlException if there is no current transaction */ void savepoint(String name) throws HsqlException { savepoints.remove(name); savepoints.add(name, ValuePool.getInt(rowActionList.size())); try { database.logger.writeToLog(this, Token.T_SAVEPOINT + " " + name); } catch (HsqlException e) {} } /** * Implements a partial transaction ROLLBACK. * * @param name Name of savepoint that was marked before by savepoint() * call * @throws HsqlException */ void rollbackToSavepoint(String name) throws HsqlException { if (isClosed) { return; } try { database.logger.writeToLog(this, Token.T_ROLLBACK + " " + Token.T_TO + " " + Token.T_SAVEPOINT + " " + name); } catch (HsqlException e) {} database.txManager.rollbackSavepoint(this, name); } /** * Implements release of named SAVEPOINT. * * @param name Name of savepoint that was marked before by savepoint() * call * @throws HsqlException if name does not correspond to a savepoint */ void releaseSavepoint(String name) throws HsqlException { // remove this and all later savepoints int index = savepoints.getIndex(name); Trace.check(index >= 0, Trace.SAVEPOINT_NOT_FOUND, name); while (savepoints.size() > index) { savepoints.remove(savepoints.size() - 1); } } /** * Starts a nested transaction. * * @throws HsqlException */ void beginNestedTransaction() throws HsqlException { if (isNestedTransaction) { Trace.doAssert(false, "beginNestedTransaction"); } nestedOldTransIndex = rowActionList.size(); isNestedTransaction = true; if (isAutoCommit) { try { database.logger.writeToLog(this, "SET AUTOCOMMIT FALSE"); } catch (HsqlException e) {} } } /** * @todo -- fredt 20050604 - if this method is called after an out of memory * error during update, the next block might throw out of memory too and as * a result inNestedTransaction remains true and no further update * is possible. The session must be closed at that point by the user * application. */ /** * Ends a nested transaction. * * @param rollback true to roll back or false to commit the nested transaction * @throws HsqlException */ void endNestedTransaction(boolean rollback) throws HsqlException { if (!isNestedTransaction) { Trace.doAssert(false, "endNestedTransaction"); } if (rollback) { database.txManager.rollbackTransactions(this, nestedOldTransIndex, true); } // reset after the rollback isNestedTransaction = false; if (isAutoCommit) { database.txManager.commit(this); try { database.logger.writeToLog(this, "SET AUTOCOMMIT TRUE"); } catch (HsqlException e) {} } } /** * Setter for readonly attribute. * * @param readonly the new value */ public void setReadOnly(boolean readonly) throws HsqlException { if (!readonly && database.databaseReadOnly) { throw Trace.error(Trace.DATABASE_IS_READONLY); } isReadOnly = readonly; } /** * Getter for readonly attribute. * * @return the current value */ public boolean isReadOnly() { return isReadOnly; } /** * Getter for nestedTransaction attribute. * * @return the current value */ boolean isNestedTransaction() { return isNestedTransaction; } /** * Getter for autoCommit attribute. * * @return the current value */ public boolean isAutoCommit() { return isAutoCommit; } /** * A switch to set scripting on the basis of type of statement executed. * A method in DatabaseCommandInterpreter.java sets this value to false * before other methods are called to act on an SQL statement, which may * set this to true. Afterwards the method reponsible for logging uses * getScripting() to determine if logging is required for the executed * statement. (fredt@users) * * @param script The new scripting value */ void setScripting(boolean script) { this.script = script; } /** * Getter for scripting attribute. * * @return scripting for the last statement. */ boolean getScripting() { return script; } public String getAutoCommitStatement() { return isAutoCommit ? "SET AUTOCOMMIT TRUE" : "SET AUTOCOMMIT FALSE"; } /** * Retrieves an internal Connection object equivalent to the one * that created this Session. * * @return internal connection. */ jdbcConnection getInternalConnection() throws HsqlException { if (intConnection == null) { intConnection = new jdbcConnection(this); } return intConnection; } // boucherb@users 20020810 metadata 1.7.2 //---------------------------------------------------------------- private final long connectTime = System.currentTimeMillis(); // more effecient for MetaData concerns than checkAdmin /** * Getter for admin attribute. * * @ return the current value */ boolean isAdmin() { return user.isAdmin(); } /** * Getter for connectTime attribute. * * @return the value */ long getConnectTime() { return connectTime; } /** * Getter for transactionSise attribute. * * @return the current value */ int getTransactionSize() { return rowActionList.size(); } /** * Retrieves whether the database object identifier by the dbobject * argument is accessible by the current Session User. * * @return true if so, else false */ boolean isAccessible(String dbobject) throws HsqlException { return user.isAccessible(dbobject); } boolean isAccessible(HsqlName dbobject) throws HsqlException { return user.isAccessible(dbobject); } // boucherb@users 20030417 - patch 1.7.2 - compiled statement support //------------------------------------------------------------------- DatabaseCommandInterpreter dbCommandInterpreter; CompiledStatementExecutor compiledStatementExecutor; CompiledStatementManager compiledStatementManager; CompiledStatement sqlCompileStatement(String sql) throws HsqlException { parser.reset(sql); CompiledStatement cs; int brackets = 0; String token = tokenizer.getString(); int cmd = Token.get(token); switch (cmd) { case Token.OPENBRACKET : { brackets = parser.parseOpenBracketsSelect() + 1; } case Token.SELECT : { cs = parser.compileSelectStatement(brackets); break; } case Token.INSERT : { cs = parser.compileInsertStatement(); break; } case Token.UPDATE : { cs = parser.compileUpdateStatement(); break; } case Token.DELETE : { cs = parser.compileDeleteStatement(); break; } case Token.CALL : { cs = parser.compileCallStatement(); break; } default : { // DDL statements cs = new CompiledStatement(currentSchema); break; } } // In addition to requiring that the compilation was successful, // we also require that the submitted sql represents a _single_ // valid DML or DDL statement. We do not check the DDL yet. // fredt - now accepts semicolon and whitespace at the end of statement // fredt - investigate if it should or not for prepared statements if (cs.type != CompiledStatement.DDL) { while (tokenizer.getPosition() < tokenizer.getLength()) { token = tokenizer.getString(); if (token.length() != 0 && !token.equals(Token.T_SEMICOLON)) { throw Trace.error(Trace.UNEXPECTED_TOKEN, token); } } } // - need to be able to key cs against its sql in statement pool // - also need to be able to revalidate its sql occasionally cs.sql = sql; return cs; } /** * Executes the command encapsulated by the cmd argument. * * @param cmd the command to execute * @return the result of executing the command */ public Result execute(Result cmd) { try { if (isClosed) { Trace.check(false, Trace.ACCESS_IS_DENIED, Trace.getMessage(Trace.Session_execute)); } } catch (Throwable t) { return new Result(t, null); } synchronized (database) { int type = cmd.mode; if (sessionMaxRows == 0) { currentMaxRows = cmd.updateCount; } // we simply get the next system change number - no matter what type of query actionTimestamp = database.txManager.nextActionTimestamp(); JavaSystem.gc(); switch (type) { case ResultConstants.SQLEXECUTE : { Result resultout = sqlExecute(cmd); resultout = performPostExecute(resultout); return resultout; } case ResultConstants.BATCHEXECUTE : { Result resultout = sqlExecuteBatch(cmd); resultout = performPostExecute(resultout); return resultout; } case ResultConstants.SQLEXECDIRECT : { Result resultout = sqlExecuteDirectNoPreChecks(cmd.getMainString()); resultout = performPostExecute(resultout); return resultout; } case ResultConstants.BATCHEXECDIRECT : { Result resultout = sqlExecuteBatchDirect(cmd); resultout = performPostExecute(resultout); return resultout; } case ResultConstants.SQLPREPARE : { CompiledStatement cs; try { cs = compiledStatementManager.compile( this, cmd.getMainString()); } catch (Throwable t) { return new Result(t, cmd.getMainString()); } Result rmd = cs.describeResult(); Result pmd = cs.describeParameters(); return Result.newPrepareResponse(cs.id, rmd, pmd); } case ResultConstants.SQLFREESTMT : { compiledStatementManager.freeStatement( cmd.getStatementID(), sessionId, false); return emptyUpdateCount; } case ResultConstants.GETSESSIONATTR : { return getAttributes(); } case ResultConstants.SETSESSIONATTR : { return setAttributes(cmd); } case ResultConstants.SQLENDTRAN : { switch (cmd.getEndTranType()) { case ResultConstants.COMMIT : commit(); break; case ResultConstants.ROLLBACK : rollback(); break; case ResultConstants.SAVEPOINT_NAME_RELEASE : try { String name = cmd.getMainString(); releaseSavepoint(name); } catch (Throwable t) { return new Result(t, null); } break; case ResultConstants.SAVEPOINT_NAME_ROLLBACK : try { rollbackToSavepoint(cmd.getMainString()); } catch (Throwable t) { return new Result(t, null); } break; // not yet // case ResultConstants.COMMIT_AND_CHAIN : // case ResultConstants.ROLLBACK_AND_CHAIN : } return emptyUpdateCount; } case ResultConstants.SQLSETCONNECTATTR : { switch (cmd.getConnectionAttrType()) { case ResultConstants.SQL_ATTR_SAVEPOINT_NAME : try { savepoint(cmd.getMainString()); } catch (Throwable t) { return new Result(t, null); } // case ResultConstants.SQL_ATTR_AUTO_IPD // - always true // default: throw - case never happens } return emptyUpdateCount; } case ResultConstants.SQLDISCONNECT : { close(); return emptyUpdateCount; } default : { return new Result( Trace.runtimeError( Trace.UNSUPPORTED_INTERNAL_OPERATION, "Session.execute()"), null); } } } } private Result performPostExecute(Result r) { try { if (database != null) { database.schemaManager.logSequences(this, database.logger); if (isAutoCommit) { clearIndexRoots(); database.logger.synchLog(); } } return r; } catch (Exception e) { return new Result(e, null); } finally { if (database != null && database.logger.needsCheckpoint()) { try { database.logger.checkpoint(false); } catch (HsqlException e) { database.logger.appLog.logContext( SimpleLog.LOG_ERROR, "checkpoint did not complete"); } } } } public Result sqlExecuteDirectNoPreChecks(String sql) { synchronized (database) { return dbCommandInterpreter.execute(sql); } } Result sqlExecuteCompiledNoPreChecks(CompiledStatement cs, Object[] pvals) { return compiledStatementExecutor.execute(cs, pvals); } private Result sqlExecuteBatch(Result cmd) { int csid; Record record; Result out; CompiledStatement cs; Expression[] parameters; int[] updateCounts; int count; csid = cmd.getStatementID(); cs = database.compiledStatementManager.getStatement(this, csid); if (cs == null) { // invalid sql has been removed already return new Result( Trace.runtimeError(Trace.INVALID_PREPARED_STATEMENT, null), null); } parameters = cs.parameters; count = 0; updateCounts = new int[cmd.getSize()]; record = cmd.rRoot; while (record != null) { Result in; Object[] pvals = record.data; in = sqlExecuteCompiledNoPreChecks(cs, pvals); // On the client side, iterate over the vals and throw // a BatchUpdateException if a batch status value of // esultConstants.EXECUTE_FAILED is encountered in the result if (in.mode == ResultConstants.UPDATECOUNT) { updateCounts[count++] = in.updateCount; } else if (in.isData()) { // FIXME: we don't have what it takes yet // to differentiate between things like // stored procedure calls to methods with // void return type and select statements with // a single row/column containg null updateCounts[count++] = ResultConstants.SUCCESS_NO_INFO; } else { updateCounts = ArrayUtil.arraySlice(updateCounts, 0, count); break; } record = record.next; } out = new Result(ResultConstants.SQLEXECUTE, updateCounts, 0); return out; } private Result sqlExecuteBatchDirect(Result cmd) { Record record; Result out; int[] updateCounts; int count; count = 0; updateCounts = new int[cmd.getSize()]; record = cmd.rRoot; while (record != null) { Result in; String sql = (String) record.data[0]; try { in = dbCommandInterpreter.execute(sql); } catch (Throwable t) { in = new Result(ResultConstants.ERROR); // if (t instanceof OutOfMemoryError) { // System.gc(); // } // "in" alread equals "err" // maybe test for OOME and do a gc() ? // t.printStackTrace(); } // On the client side, iterate over the colType vals and throw // a BatchUpdateException if a batch status value of // ResultConstants.EXECUTE_FAILED is encountered if (in.mode == ResultConstants.UPDATECOUNT) { updateCounts[count++] = in.updateCount; } else if (in.isData()) { // FIXME: we don't have what it takes yet // to differentiate between things like // stored procedure calls to methods with // void return type and select statements with // a single row/column containg null updateCounts[count++] = ResultConstants.SUCCESS_NO_INFO; } else { updateCounts = ArrayUtil.arraySlice(updateCounts, 0, count); break; } record = record.next; } out = new Result(ResultConstants.SQLEXECUTE, updateCounts, 0); return out; } /** * Retrieves the result of executing the prepared statement whose csid * and parameter values/types are encapsulated by the cmd argument. * * @return the result of executing the statement */ private Result sqlExecute(Result cmd) { int csid = cmd.getStatementID(); CompiledStatement cs = compiledStatementManager.getStatement(this, csid); if (cs == null) { // invalid sql has been removed already return new Result( Trace.runtimeError(Trace.INVALID_PREPARED_STATEMENT, null), null); } Object[] pvals = cmd.getParameterData(); return sqlExecute(cs, pvals); } private Result sqlExecute(CompiledStatement cs, Object[] pvals) { return sqlExecuteCompiledNoPreChecks(cs, pvals); } // session DATETIME functions long currentDateTimeSCN; long currentMillis; Date currentDate; Time currentTime; Timestamp currentTimestamp; /** * Returns the current date, unchanged for the duration of the current * execution unit (statement).<p> * * SQL standards require that CURRENT_DATE, CURRENT_TIME and * CURRENT_TIMESTAMP are all evaluated at the same point of * time in the duration of each SQL statement, no matter how long the * SQL statement takes to complete.<p> * * When this method or a corresponding method for CURRENT_TIME or * CURRENT_TIMESTAMP is first called in the scope of a system change * number, currentMillis is set to the current system time. All further * CURRENT_XXXX calls in this scope will use this millisecond value. * (fredt@users) */ Date getCurrentDate() { if (currentDateTimeSCN != actionTimestamp) { currentDateTimeSCN = actionTimestamp; currentMillis = System.currentTimeMillis(); currentDate = HsqlDateTime.getCurrentDate(currentMillis); currentTime = null; currentTimestamp = null; } else if (currentDate == null) { currentDate = HsqlDateTime.getCurrentDate(currentMillis); } return currentDate; } /** * Returns the current time, unchanged for the duration of the current * execution unit (statement) */ Time getCurrentTime() { if (currentDateTimeSCN != actionTimestamp) { currentDateTimeSCN = actionTimestamp; currentMillis = System.currentTimeMillis(); currentDate = null; currentTime = new Time(HsqlDateTime.getNormalisedTime(currentMillis)); currentTimestamp = null; } else if (currentTime == null) { currentTime = new Time(HsqlDateTime.getNormalisedTime(currentMillis)); } return currentTime; } /** * Returns the current timestamp, unchanged for the duration of the current * execution unit (statement) */ Timestamp getCurrentTimestamp() { if (currentDateTimeSCN != actionTimestamp) { currentDateTimeSCN = actionTimestamp; currentMillis = System.currentTimeMillis(); currentDate = null; currentTime = null; currentTimestamp = HsqlDateTime.getTimestamp(currentMillis); } else if (currentTimestamp == null) { currentTimestamp = HsqlDateTime.getTimestamp(currentMillis); } return currentTimestamp; } Result getAttributes() { Result r = Result.newSessionAttributesResult(); Object[] row = new Object[] { database.getURI(), getUsername(), ValuePool.getInt(sessionId), ValuePool.getInt(isolationMode), ValuePool.getBoolean(isAutoCommit), ValuePool.getBoolean(database.databaseReadOnly), ValuePool.getBoolean(isReadOnly) }; r.add(row); return r; } Result setAttributes(Result r) { Object[] row = r.rRoot.data; for (int i = 0; i < row.length; i++) { Object value = row[i]; if (value == null) { continue; } try { switch (i) { case SessionInterface.INFO_AUTOCOMMIT : { this.setAutoCommit(((Boolean) value).booleanValue()); break; } case SessionInterface.INFO_CONNECTION_READONLY : this.setReadOnly(((Boolean) value).booleanValue()); break; } } catch (HsqlException e) { return new Result(e, null); } } return emptyUpdateCount; } // DatabaseMetaData.getURL should work as specified for // internal connections too. public String getInternalConnectionURL() { return DatabaseURL.S_URL_PREFIX + database.getURI(); } boolean isProcessingScript() { return isProcessingScript; } boolean isProcessingLog() { return isProcessingLog; } boolean isSchemaDefintion() { return oldSchema != null; } void startSchemaDefinition(String schema) throws HsqlException { if (isProcessingScript) { setSchema(schema); return; } oldSchema = currentSchema; setSchema(schema); } void endSchemaDefinition() throws HsqlException { if (oldSchema == null) { return; } currentSchema = oldSchema; oldSchema = null; database.logger.writeToLog(this, "SET SCHEMA " + currentSchema.statementName); } // schema object methods public void setSchema(String schema) throws HsqlException { currentSchema = database.schemaManager.getSchemaHsqlName(schema); } /** * If schemaName is null, return the current schema name, else return * the HsqlName object for the schema. If schemaName does not exist, * throw. */ HsqlName getSchemaHsqlName(String name) throws HsqlException { return name == null ? currentSchema : database.schemaManager.getSchemaHsqlName(name); } /** * Same as above, but return string */ public String getSchemaName(String name) throws HsqlException { return name == null ? currentSchema.name : database.schemaManager.getSchemaName(name); } /** * If schemaName is null, return the current schema name, else return * the HsqlName object for the schema. If schemaName does not exist, or * schema readonly, throw. */ HsqlName getSchemaHsqlNameForWrite(String name) throws HsqlException { HsqlName schema = getSchemaHsqlName(name); if (database.schemaManager.isSystemSchema(schema)) { throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS); } return schema; } /** * Same as above, but return string */ public String getSchemaNameForWrite(String name) throws HsqlException { HsqlName schema = getSchemaHsqlNameForWrite(name); return schema.name; } /** * get the root for a temp table index */ Node getIndexRoot(HsqlName index, boolean preserve) { if (preserve) { if (indexArrayKeepMap == null) { return null; } return (Node) indexArrayKeepMap.get(index.hashCode()); } else { if (indexArrayMap == null) { return null; } return (Node) indexArrayMap.get(index.hashCode()); } } /** * set the root for a temp table index */ void setIndexRoot(HsqlName index, boolean preserve, Node root) { if (preserve) { if (indexArrayKeepMap == null) { if (root == null) { return; } indexArrayKeepMap = new IntKeyHashMap(); } indexArrayKeepMap.put(index.hashCode(), root); } else { if (indexArrayMap == null) { if (root == null) { return; } indexArrayMap = new IntKeyHashMap(); } indexArrayMap.put(index.hashCode(), root); } } void dropIndex(HsqlName index, boolean preserve) { if (preserve) { if (indexArrayKeepMap != null) { indexArrayKeepMap.remove(index.hashCode()); } } else { if (indexArrayMap != null) { indexArrayMap.remove(index.hashCode()); } } } /** * clear default temp table contents for this session */ void clearIndexRoots() { if (indexArrayMap != null) { indexArrayMap.clear(); } } /** * clear ON COMMIT PRESERVE temp table contents for this session */ void clearIndexRootsKeep() { if (indexArrayKeepMap != null) { indexArrayKeepMap.clear(); } } // warnings HsqlArrayList sqlWarnings; public void addWarning(HsqlException warning) { if (sqlWarnings == null) { sqlWarnings = new HsqlArrayList(true); } sqlWarnings.add(warning); } public HsqlException[] getAndClearWarnings() { if (sqlWarnings == null) { return new HsqlException[0]; } HsqlException[] array = new HsqlException[sqlWarnings.size()]; sqlWarnings.toArray(array); sqlWarnings.clear(); return array; } }