/* 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-2009, 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.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Random;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.jdbc.JDBCConnection;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.CountUpDownLatch;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.lib.SimpleLog;
import org.hsqldb.lib.java.JavaSystem;
import org.hsqldb.navigator.RowSetNavigator;
import org.hsqldb.navigator.RowSetNavigatorClient;
import org.hsqldb.persist.HsqlDatabaseProperties;
import org.hsqldb.result.Result;
import org.hsqldb.result.ResultConstants;
import org.hsqldb.result.ResultLob;
import org.hsqldb.rights.Grantee;
import org.hsqldb.rights.User;
import org.hsqldb.store.ValuePool;
import org.hsqldb.types.BlobDataID;
import org.hsqldb.types.ClobDataID;
import org.hsqldb.types.TimeData;
import org.hsqldb.types.TimestampData;
import org.hsqldb.types.Type;
/**
* Implementation of SQL sessions.
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 1.9.0
* @since 1.7.0
*/
public class Session implements SessionInterface {
//
private volatile boolean isClosed;
//
public Database database;
private User user;
// transaction support
private volatile boolean isAutoCommit;
private volatile boolean isReadOnly;
boolean isReadOnlyDefault;
int isolationModeDefault = SessionInterface.TX_READ_COMMITTED;
int isolationMode = SessionInterface.TX_READ_COMMITTED;
int actionIndex;
long actionTimestamp;
long transactionTimestamp;
boolean isTransaction;
boolean isBatch;
volatile boolean abortTransaction;
volatile boolean redoAction;
HsqlArrayList rowActionList;
volatile boolean tempUnlocked;
OrderedHashSet waitingSessions;
OrderedHashSet tempSet;
CountUpDownLatch latch = new CountUpDownLatch();
Statement currentStatement;
Statement lockStatement;
// current settings
final int sessionTimeZoneSeconds;
int timeZoneSeconds;
boolean isNetwork;
private int currentMaxRows;
private int sessionMaxRows;
private Number lastIdentity = ValuePool.INTEGER_0;
private final long sessionId;
private boolean script;
// internal connection
private JDBCConnection intConnection;
// schema
public HsqlName currentSchema;
public HsqlName loggedSchema;
// query processing
ParserCommand parser;
boolean isProcessingScript;
boolean isProcessingLog;
public SessionContext sessionContext;
int resultMaxMemoryRows;
//
public SessionData sessionData;
/** @todo 1.9.0 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 isInitiallyLogged,
boolean autocommit, boolean readonly, long id,
int timeZoneSeconds) {
sessionId = id;
database = db;
this.user = user;
this.sessionTimeZoneSeconds = timeZoneSeconds;
this.timeZoneSeconds = timeZoneSeconds;
rowActionList = new HsqlArrayList(true);
waitingSessions = new OrderedHashSet();
tempSet = new OrderedHashSet();
isAutoCommit = autocommit;
isReadOnly = readonly;
isolationMode = isolationModeDefault;
sessionContext = new SessionContext(this);
parser = new ParserCommand(this, new Scanner());
setResultMemoryRowCount(database.getResultMaxMemoryRows());
resetSchema();
sessionData = new SessionData(database, this);
try {
if (isInitiallyLogged) {
database.logger.writeToLog(this, getSetSchemaStatement());
}
} catch (HsqlException e) {}
}
void resetSchema() {
loggedSchema = null;
currentSchema = user.getInitialOrDefaultSchema();
}
/**
* Retrieves the session identifier for this Session.
*
* @return the session identifier for this Session
*/
public long getId() {
return sessionId;
}
/**
* Closes this Session.
*/
public synchronized void close() {
if (isClosed) {
return;
}
rollback(false);
try {
database.logger.writeToLog(this, Tokens.T_DISCONNECT);
} catch (HsqlException e) {}
sessionData.closeAllNavigators();
sessionData.persistentStoreCollection.clearAllTables();
sessionData.closeResultCache();
database.compiledStatementManager.removeSession(sessionId);
database.sessionManager.removeSession(this);
database.closeIfLast();
database = null;
user = null;
rowActionList = null;
sessionContext.savepoints = null;
intConnection = null;
sessionContext = null;
lastIdentity = null;
isClosed = true;
}
/**
* Retrieves whether this Session is closed.
*
* @return true if this Session is closed
*/
public boolean isClosed() {
return isClosed;
}
public synchronized void setIsolationDefault(int level) {
if (level == SessionInterface.TX_READ_UNCOMMITTED) {
isReadOnlyDefault = true;
}
if (level == isolationModeDefault) {
return;
}
isolationModeDefault = level;
if (!isInMidTransaction()) {
isolationMode = isolationModeDefault;
}
database.logger.writeToLog(this, getSessionIsolationSQL());
}
/**
* sets ISOLATION for the next transaction only
*/
public void setIsolation(int level) {
if (isInMidTransaction()) {
throw Error.error(ErrorCode.X_25001);
}
if (level == SessionInterface.TX_READ_UNCOMMITTED) {
isReadOnly = true;
}
isolationMode = level;
if (isolationMode != isolationModeDefault) {
database.logger.writeToLog(this, getTransactionIsolationSQL());
}
}
public synchronized int getIsolation() {
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
*/
public 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
*/
public 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
*/
public String getUsername() {
return user.getNameString();
}
/**
* Retrieves the User object representing the user currently controlling
* this Session.
*
* @return this Session's User object
*/
public User getUser() {
return (User) user;
}
public Grantee getGrantee() {
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;
}
public 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.
*/
void checkAdmin() {
user.checkAdmin();
}
/**
* This is used for reading - writing to existing tables.
* @throws HsqlException
*/
void checkReadWrite() {
if (isReadOnly) {
throw Error.error(ErrorCode.X_25006);
}
}
/**
* This is used for creating new database objects such as tables.
* @throws HsqlException
*/
void checkDDLWrite() {
checkReadWrite();
if (isProcessingScript || isProcessingLog) {
return;
}
if (database.isFilesReadOnly()) {
throw Error.error(ErrorCode.DATABASE_IS_READONLY);
}
}
public long getActionTimestamp() {
return actionTimestamp;
}
/**
* Adds a delete action to the row and the transaction manager.
*
* @param table the table of the row
* @param row the deleted row
* @throws HsqlException
*/
void addDeleteAction(Table table, Row row) {
// tempActionHistory.add("add delete action " + actionTimestamp);
if (abortTransaction) {
// throw Error.error(ErrorCode.X_40001);
}
database.txManager.addDeleteAction(this, table, row);
}
void addInsertAction(Table table, Row row) {
// tempActionHistory.add("add insert to transaction " + actionTimestamp);
database.txManager.addInsertAction(this, table, row);
// abort only after adding so that the new row gets removed from indexes
if (abortTransaction) {
// throw Error.error(ErrorCode.X_40001);
}
}
/**
* Setter for the autocommit attribute.
*
* @param autocommit the new value
* @throws HsqlException
*/
public synchronized void setAutoCommit(boolean autocommit) {
if (isClosed) {
return;
}
if (autocommit != isAutoCommit) {
commit(false);
isAutoCommit = autocommit;
}
}
public void beginAction(Statement cs) {
actionIndex = rowActionList.size();
database.txManager.beginAction(this, cs);
// tempActionHistory.add("beginAction ends " + actionTimestamp);
}
public void endAction(Result r) {
// tempActionHistory.add("endAction " + actionTimestamp);
sessionData.persistentStoreCollection.clearStatementTables();
if (r.isError()) {
sessionData.persistentStoreCollection.clearResultTables(
actionTimestamp);
database.txManager.rollbackAction(this);
} else {
database.txManager.completeActions(this);
}
// tempActionHistory.add("endAction ends " + actionTimestamp);
}
public boolean hasLocks() {
return currentStatement == lockStatement;
}
public void startTransaction() {
database.txManager.beginTransaction(this);
}
public synchronized void startPhasedTransaction() {}
/**
* @todo - fredt - for two phased pre-commit - after this call, further
* state changing calls should fail
*/
public synchronized void prepareCommit() {
if (isClosed) {
throw Error.error(ErrorCode.X_08003);
}
if (!database.txManager.prepareCommitActions(this)) {
// tempActionHistory.add("commit aborts " + actionTimestamp);
rollback(false);
throw Error.error(ErrorCode.X_40001);
}
}
/**
* Commits any uncommited transaction this Session may have open
*
* @throws HsqlException
*/
public synchronized void commit(boolean chain) {
// tempActionHistory.add("commit " + actionTimestamp);
if (isClosed) {
return;
}
if (!isTransaction) {
isReadOnly = isReadOnlyDefault;
isolationMode = isolationModeDefault;
return;
}
if (!database.txManager.commitTransaction(this)) {
// tempActionHistory.add("commit aborts " + actionTimestamp);
rollback(false);
throw Error.error(ErrorCode.X_40001);
}
endTransaction(true);
}
/**
* Rolls back any uncommited transaction this Session may have open.
*
* @throws HsqlException
*/
public synchronized void rollback(boolean chain) {
// tempActionHistory.add("rollback " + actionTimestamp);
if (isClosed) {
return;
}
if (!isTransaction) {
isReadOnly = isReadOnlyDefault;
isolationMode = isolationModeDefault;
return;
}
try {
database.logger.writeToLog(this, Tokens.T_ROLLBACK);
} catch (HsqlException e) {}
database.txManager.rollback(this);
endTransaction(false);
}
private void endTransaction(boolean commit) {
sessionData.updateLobUsage(commit);
sessionContext.savepoints.clear();
sessionContext.savepointTimestamps.clear();
rowActionList.clear();
sessionData.persistentStoreCollection.clearTransactionTables();
sessionData.closeAllTransactionNavigators();
isReadOnly = isReadOnlyDefault;
isolationMode = isolationModeDefault;
lockStatement = null;
/* debug 190
tempActionHistory.add("commit ends " + actionTimestamp);
tempActionHistory.clear();
//*/
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");
}
}
}
/**
* @todo no-op in this implementation. To be implemented for connection pooling
*/
public synchronized void resetSession() {
throw new HsqlException("", "", 0);
}
/**
* Registers a transaction SAVEPOINT. A new SAVEPOINT with the
* name of an existing one replaces the old SAVEPOINT.
*
* @param name name of the savepoint
* @throws HsqlException if there is no current transaction
*/
public synchronized void savepoint(String name) {
int index = sessionContext.savepoints.getIndex(name);
if (index != -1) {
sessionContext.savepoints.remove(name);
sessionContext.savepointTimestamps.remove(index);
}
sessionContext.savepoints.add(name,
ValuePool.getInt(rowActionList.size()));
sessionContext.savepointTimestamps.addLast(actionTimestamp);
try {
database.logger.writeToLog(this, getSavepointSQL(name));
} catch (HsqlException e) {}
}
/**
* Performs a partial transaction ROLLBACK to savepoint.
*
* @param name name of savepoint
* @throws HsqlException
*/
public synchronized void rollbackToSavepoint(String name) {
if (isClosed) {
return;
}
int index = sessionContext.savepoints.getIndex(name);
if (index < 0) {
throw Error.error(ErrorCode.X_3B001, name);
}
database.txManager.rollbackSavepoint(this, index);
try {
database.logger.writeToLog(this, getSavepointRollbackSQL(name));
} catch (HsqlException e) {}
}
/**
* Performs a partial transaction ROLLBACK of current savepoint level.
*
* @throws HsqlException
*/
public synchronized void rollbackToSavepoint() {
if (isClosed) {
return;
}
String name = (String) sessionContext.savepoints.getKey(0);
database.txManager.rollbackSavepoint(this, 0);
try {
database.logger.writeToLog(this, getSavepointRollbackSQL(name));
} catch (HsqlException e) {}
}
/**
* Releases a savepoint
*
* @param name name of savepoint
* @throws HsqlException if name does not correspond to a savepoint
*/
public synchronized void releaseSavepoint(String name) {
// remove this and all later savepoints
int index = sessionContext.savepoints.getIndex(name);
if (index < 0) {
throw Error.error(ErrorCode.X_3B001, name);
}
while (sessionContext.savepoints.size() > index) {
sessionContext.savepoints.remove(sessionContext.savepoints.size()
- 1);
sessionContext.savepointTimestamps.removeLast();
}
}
/**
* sets READ ONLY for next transaction only
*
* @param readonly the new value
*/
public void setReadOnly(boolean readonly) {
if (!readonly && database.databaseReadOnly) {
throw Error.error(ErrorCode.DATABASE_IS_READONLY);
}
if (isInMidTransaction()) {
throw Error.error(ErrorCode.X_25001);
}
isReadOnly = readonly;
}
public synchronized void setReadOnlyDefault(boolean readonly) {
if (!readonly && database.databaseReadOnly) {
throw Error.error(ErrorCode.DATABASE_IS_READONLY);
}
isReadOnlyDefault = readonly;
if (!isInMidTransaction()) {
isReadOnly = isReadOnlyDefault;
}
}
/**
* Getter for readonly attribute.
*
* @return the current value
*/
public boolean isReadOnly() {
return isReadOnly;
}
public synchronized boolean isReadOnlyDefault() {
return isReadOnlyDefault;
}
/**
* Getter for autoCommit attribute.
*
* @return the current value
*/
public synchronized boolean isAutoCommit() {
return isAutoCommit;
}
public synchronized int getStreamBlockSize() {
return 512 * 1024;
}
public boolean isInMidTransaction() {
return isTransaction;
}
/**
* A switch to set scripting on the basis of type of statement executed.
* Afterwards the method reponsible for logging uses
* isScripting() 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 isScripting() {
return script;
}
/**
* Retrieves an internal Connection object equivalent to the one
* that created this Session.
*
* @return internal connection.
*/
JDBCConnection getInternalConnection() {
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
*/
public boolean isAdmin() {
return user.isAdmin();
}
/**
* Getter for connectTime attribute.
*
* @return the value
*/
public long getConnectTime() {
return connectTime;
}
/**
* Count of acctions in current transaction.
*
* @return the current value
*/
public int getTransactionSize() {
return rowActionList.size();
}
public long getTransactionTimestamp() {
return transactionTimestamp;
}
public Statement compileStatement(String sql) {
parser.reset(sql);
Statement cs = parser.compileStatement();
return cs;
}
/**
* Executes the command encapsulated by the cmd argument.
*
* @param cmd the command to execute
* @return the result of executing the command
*/
public synchronized Result execute(Result cmd) {
if (isClosed) {
return Result.newErrorResult(Error.error(ErrorCode.X_08503));
}
// synchronized (database) {
int type = cmd.getType();
if (sessionMaxRows == 0) {
currentMaxRows = cmd.getUpdateCount();
}
JavaSystem.gc();
switch (type) {
case ResultConstants.LARGE_OBJECT_OP : {
return performLOBOperation((ResultLob) cmd);
}
case ResultConstants.EXECUTE : {
Result result = executeCompiledStatement(cmd);
result = performPostExecute(cmd, result);
return result;
}
case ResultConstants.BATCHEXECUTE : {
Result result = executeCompiledBatchStatement(cmd);
result = performPostExecute(cmd, result);
return result;
}
case ResultConstants.EXECDIRECT : {
Result result = executeDirectStatement(cmd);
result = performPostExecute(cmd, result);
return result;
}
case ResultConstants.BATCHEXECDIRECT : {
Result result = executeDirectBatchStatement(cmd);
result = performPostExecute(cmd, result);
return result;
}
case ResultConstants.PREPARE : {
Statement cs;
try {
cs = database.compiledStatementManager.compile(this, cmd);
} catch (Throwable t) {
String errorString = cmd.getMainString();
if (database.getProperties().getErrorLevel()
== HsqlDatabaseProperties.NO_MESSAGE) {
errorString = null;
}
return Result.newErrorResult(t, errorString);
}
cs.setGeneratedColumnInfo(cmd.getGeneratedResultType(),
cmd.getGeneratedResultMetaData());
Result result = Result.newPrepareResponse(cs);
if (cs.getType() == StatementTypes.SELECT_CURSOR) {
sessionData.setResultSetProperties(cmd, result);
}
return result;
}
case ResultConstants.CLOSE_RESULT : {
closeNavigator(cmd.getResultId());
return Result.updateZeroResult;
}
case ResultConstants.UPDATE_RESULT : {
Result result = this.executeResultUpdate(cmd);
return result;
}
case ResultConstants.FREESTMT : {
database.compiledStatementManager.freeStatement(
cmd.getStatementID(), sessionId, false);
return Result.updateZeroResult;
}
case ResultConstants.GETSESSIONATTR : {
int id = cmd.getStatementType();
return getAttributesResult(id);
}
case ResultConstants.SETSESSIONATTR : {
return setAttributes(cmd);
}
case ResultConstants.ENDTRAN : {
switch (cmd.getActionType()) {
case ResultConstants.TX_COMMIT :
try {
commit(false);
} catch (Throwable t) {
return Result.newErrorResult(t);
}
break;
case ResultConstants.TX_COMMIT_AND_CHAIN :
try {
commit(true);
} catch (Throwable t) {
return Result.newErrorResult(t);
}
break;
case ResultConstants.TX_ROLLBACK :
rollback(false);
break;
case ResultConstants.TX_ROLLBACK_AND_CHAIN :
rollback(true);
break;
case ResultConstants.TX_SAVEPOINT_NAME_RELEASE :
try {
String name = cmd.getMainString();
releaseSavepoint(name);
} catch (Throwable t) {
return Result.newErrorResult(t);
}
break;
case ResultConstants.TX_SAVEPOINT_NAME_ROLLBACK :
try {
rollbackToSavepoint(cmd.getMainString());
} catch (Throwable t) {
return Result.newErrorResult(t);
}
break;
}
return Result.updateZeroResult;
}
case ResultConstants.SETCONNECTATTR : {
switch (cmd.getConnectionAttrType()) {
case ResultConstants.SQL_ATTR_SAVEPOINT_NAME :
try {
savepoint(cmd.getMainString());
} catch (Throwable t) {
return Result.newErrorResult(t);
}
// case ResultConstants.SQL_ATTR_AUTO_IPD
// - always true
// default: throw - case never happens
}
return Result.updateZeroResult;
}
case ResultConstants.REQUESTDATA : {
return sessionData.getDataResultSlice(cmd.getResultId(),
cmd.getUpdateCount(),
cmd.getFetchSize());
}
case ResultConstants.DISCONNECT : {
close();
return Result.updateZeroResult;
}
default : {
return Result.newErrorResult(
Error.runtimeError(
ErrorCode.U_S0500, "Session.execute()"));
}
}
// }
}
private Result performPostExecute(Result command, Result result) {
if (result.isData()) {
result = sessionData.getDataResultHead(command, result, isNetwork);
}
return result;
}
public RowSetNavigatorClient getRows(long navigatorId, int offset,
int blockSize) {
return sessionData.getRowSetSlice(navigatorId, offset, blockSize);
}
public synchronized void closeNavigator(long id) {
sessionData.closeNavigator(id);
}
public Result executeDirectStatement(Result cmd) {
String sql = cmd.getMainString();
HsqlArrayList list;
try {
list = parser.compileStatements(sql, cmd.getStatementType());
} catch (HsqlException e) {
return Result.newErrorResult(e);
}
Result result = null;
for (int i = 0; i < list.size(); i++) {
Statement cs = (Statement) list.get(i);
result = executeCompiledStatement(cs, ValuePool.emptyObjectArray);
if (result.isError()) {
break;
}
}
return result;
}
public Result executeDirectStatement(String sql) {
Statement cs;
parser.reset(sql);
try {
cs = parser.compileStatement();
} catch (HsqlException e) {
return Result.newErrorResult(e);
}
Result result = executeCompiledStatement(cs,
ValuePool.emptyObjectArray);
return result;
}
public Result executeCompiledStatement(Statement cs, Object[] pvals) {
Result r;
if (abortTransaction) {
// tempActionHistory.add("beginAction aborts" + actionTimestamp);
rollback(false);
return Result.newErrorResult(Error.error(ErrorCode.X_40001));
}
currentStatement = cs;
if (cs.isAutoCommitStatement()) {
try {
if (isReadOnly()) {
throw Error.error(ErrorCode.X_25006);
}
/** @todo - special autocommit for backward compatibility */
commit(false);
} catch (HsqlException e) {}
}
if (!cs.isTransactionStatement()) {
r = cs.execute(this);
currentStatement = null;
return r;
}
while (true) {
beginAction(cs);
if (abortTransaction) {
rollback(false);
currentStatement = null;
return Result.newErrorResult(Error.error(ErrorCode.X_40001));
}
try {
latch.await();
} catch (InterruptedException e) {
// System.out.println("interrupted");
}
if (abortTransaction) {
rollback(false);
currentStatement = null;
return Result.newErrorResult(Error.error(ErrorCode.X_40001));
}
// tempActionHistory.add("sql execute " + cs.sql + " " + actionTimestamp + " " + rowActionList.size());
sessionContext.pushDynamicArguments(pvals);
r = cs.execute(this);
sessionContext.popDynamicArguments();
lockStatement = currentStatement;
// tempActionHistory.add("sql execute end " + actionTimestamp + " " + rowActionList.size());
endAction(r);
if (abortTransaction) {
rollback(false);
currentStatement = null;
return Result.newErrorResult(Error.error(ErrorCode.X_40001));
}
if (redoAction) {
redoAction = false;
try {
latch.await();
} catch (InterruptedException e) {
//
System.out.println("interrupted");
}
} else {
break;
}
}
if (sessionContext.depth == 0
&& (isAutoCommit || cs.isAutoCommitStatement())) {
try {
if (r.isError()) {
rollback(false);
} else {
commit(false);
}
} catch (Exception e) {
currentStatement = null;
return Result.newErrorResult(Error.error(ErrorCode.X_40001));
}
}
currentStatement = null;
return r;
}
private Result executeCompiledBatchStatement(Result cmd) {
long csid;
Statement cs;
int[] updateCounts;
int count;
csid = cmd.getStatementID();
cs = database.compiledStatementManager.getStatement(this, csid);
if (cs == null) {
// invalid sql has been removed already
return Result.newErrorResult(Error.error(ErrorCode.X_07501));
}
count = 0;
RowSetNavigator nav = cmd.initialiseNavigator();
updateCounts = new int[nav.getSize()];
Result generatedResult = null;
if (cs.hasGeneratedColumns()) {
generatedResult =
Result.newDataResult(cs.generatedResultMetaData());
}
Result error = null;
isBatch = true;
while (nav.hasNext()) {
Object[] pvals = (Object[]) nav.getNext();
Result in = executeCompiledStatement(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.isUpdateCount()) {
if (cs.hasGeneratedColumns()) {
Object generatedRow = in.getNavigator().getNext();
generatedResult.getNavigator().add(generatedRow);
}
updateCounts[count++] = in.getUpdateCount();
} 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 if (in.isError()) {
updateCounts = ArrayUtil.arraySlice(updateCounts, 0, count);
error = in;
break;
} else {
throw Error.runtimeError(ErrorCode.U_S0500, "Session");
}
}
isBatch = false;
sessionData.updateLobUsageForBatch();
return Result.newBatchedExecuteResponse(updateCounts, generatedResult,
error);
}
private Result executeDirectBatchStatement(Result cmd) {
int[] updateCounts;
int count;
count = 0;
RowSetNavigator nav = cmd.initialiseNavigator();
updateCounts = new int[nav.getSize()];
Result error = null;
isBatch = true;
while (nav.hasNext()) {
Result in;
Object[] data = (Object[]) nav.getNext();
String sql = (String) data[0];
try {
in = executeDirectStatement(sql);
} catch (Throwable t) {
in = Result.newErrorResult(t);
// if (t instanceof OutOfMemoryError) {
// System.gc();
// }
// "in" alread equals "err"
// maybe test for OOME and do a gc() ?
// t.printStackTrace();
}
if (in.isUpdateCount()) {
updateCounts[count++] = in.getUpdateCount();
} 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 if (in.isError()) {
updateCounts = ArrayUtil.arraySlice(updateCounts, 0, count);
error = in;
break;
} else {
throw Error.runtimeError(ErrorCode.U_S0500, "Session");
}
}
isBatch = false;
sessionData.updateLobUsageForBatch();
return Result.newBatchedExecuteResponse(updateCounts, null, error);
}
/**
* 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 executeCompiledStatement(Result cmd) {
Statement cs = cmd.getStatement();
if (!cs.isValid()) {
long csid = cmd.getStatementID();
cs = database.compiledStatementManager.getStatement(this, csid);
if (cs == null) {
// invalid sql has been removed already
return Result.newErrorResult(Error.error(ErrorCode.X_07502));
}
}
Object[] pvals = cmd.getParameterData();
return executeCompiledStatement(cs, pvals);
}
/**
* Retrieves the result of inserting, updating or deleting a row
* from an updatable result.
*
* @return the result of executing the statement
*/
private Result executeResultUpdate(Result cmd) {
long id = cmd.getResultId();
int actionType = cmd.getActionType();
Result result = sessionData.getDataResult(id);
if (result == null) {
return Result.newErrorResult(Error.error(ErrorCode.X_24501));
}
Object[] pvals = cmd.getParameterData();
Type[] types = cmd.metaData.columnTypes;
StatementQuery statement = (StatementQuery) result.getStatement();
QueryExpression qe = statement.queryExpression;
Table baseTable = qe.getBaseTable();
int[] columnMap = qe.getBaseTableColumnMap();
sessionContext.rowUpdateStatement.setRowActionProperties(actionType,
baseTable, types, columnMap);
Result resultOut =
executeCompiledStatement(sessionContext.rowUpdateStatement, pvals);
return resultOut;
}
// session DATETIME functions
long currentDateSCN;
long currentTimestampSCN;
long currentMillis;
private TimestampData currentDate;
private TimestampData currentTimestamp;
private TimestampData localTimestamp;
private TimeData currentTime;
private TimeData localTime;
/**
* 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)
*/
public synchronized TimestampData getCurrentDate() {
resetCurrentTimestamp();
if (currentDate == null) {
currentDate = (TimestampData) Type.SQL_DATE.getValue(currentMillis
/ 1000, 0, timeZoneSeconds);
}
return currentDate;
}
/**
* Returns the current time, unchanged for the duration of the current
* execution unit (statement)
*/
synchronized TimeData getCurrentTime(boolean withZone) {
resetCurrentTimestamp();
if (withZone) {
if (currentTime == null) {
int seconds =
(int) (HsqlDateTime.getNormalisedTime(currentMillis))
/ 1000;
int nanos = (int) (currentMillis % 1000) * 1000000;
currentTime = new TimeData(seconds, nanos, timeZoneSeconds);
}
return currentTime;
} else {
if (localTime == null) {
int seconds =
(int) (HsqlDateTime.getNormalisedTime(
currentMillis + +timeZoneSeconds * 1000)) / 1000;
int nanos = (int) (currentMillis % 1000) * 1000000;
localTime = new TimeData(seconds, nanos, 0);
}
return localTime;
}
}
/**
* Returns the current timestamp, unchanged for the duration of the current
* execution unit (statement)
*/
synchronized TimestampData getCurrentTimestamp(boolean withZone) {
resetCurrentTimestamp();
if (withZone) {
if (currentTimestamp == null) {
int nanos = (int) (currentMillis % 1000) * 1000000;
currentTimestamp = new TimestampData((currentMillis / 1000),
nanos, timeZoneSeconds);
}
return currentTimestamp;
} else {
if (localTimestamp == null) {
int nanos = (int) (currentMillis % 1000) * 1000000;
localTimestamp = new TimestampData(currentMillis / 1000
+ timeZoneSeconds, nanos,
0);
}
return localTimestamp;
}
}
private void resetCurrentTimestamp() {
if (currentTimestampSCN != actionTimestamp) {
currentTimestampSCN = actionTimestamp;
currentMillis = System.currentTimeMillis();
currentDate = null;
currentTimestamp = null;
localTimestamp = null;
currentTime = null;
localTime = null;
}
}
public int getZoneSeconds() {
return timeZoneSeconds;
}
private Result getAttributesResult(int id) {
Result r = Result.newSessionAttributesResult();
Object[] data = r.getSingleRowData();
data[SessionInterface.INFO_ID] = ValuePool.getInt(id);
switch (id) {
case SessionInterface.INFO_ISOLATION :
data[SessionInterface.INFO_INTEGER] =
ValuePool.getInt(isolationMode);
break;
case SessionInterface.INFO_AUTOCOMMIT :
data[SessionInterface.INFO_BOOLEAN] =
ValuePool.getBoolean(isAutoCommit);
break;
case SessionInterface.INFO_CONNECTION_READONLY :
data[SessionInterface.INFO_BOOLEAN] =
ValuePool.getBoolean(isReadOnly);
break;
case SessionInterface.INFO_CATALOG :
data[SessionInterface.INFO_VARCHAR] =
database.getCatalogName().name;
break;
}
return r;
}
private Result setAttributes(Result r) {
Object[] row = r.getSessionAttributes();
int id = ((Integer) row[SessionInterface.INFO_ID]).intValue();
try {
switch (id) {
case SessionInterface.INFO_AUTOCOMMIT : {
boolean value =
((Boolean) row[SessionInterface.INFO_BOOLEAN])
.booleanValue();
this.setAutoCommit(value);
break;
}
case SessionInterface.INFO_CONNECTION_READONLY : {
boolean value =
((Boolean) row[SessionInterface.INFO_BOOLEAN])
.booleanValue();
this.setReadOnlyDefault(value);
break;
}
case SessionInterface.INFO_ISOLATION : {
int value =
((Integer) row[SessionInterface.INFO_INTEGER])
.intValue();
this.setIsolation(value);
break;
}
case SessionInterface.INFO_CATALOG : {
String value =
((String) row[SessionInterface.INFO_VARCHAR]);
this.setCatalog(value);
}
}
} catch (HsqlException e) {
return Result.newErrorResult(e);
}
return Result.updateZeroResult;
}
public synchronized Object getAttribute(int id) {
switch (id) {
case SessionInterface.INFO_ISOLATION :
return ValuePool.getInt(isolationMode);
case SessionInterface.INFO_AUTOCOMMIT :
return ValuePool.getBoolean(isAutoCommit);
case SessionInterface.INFO_CONNECTION_READONLY :
return ValuePool.getBoolean(isReadOnly);
case SessionInterface.INFO_CATALOG :
return database.getCatalogName().name;
}
return null;
}
public synchronized void setAttribute(int id, Object object) {
switch (id) {
case SessionInterface.INFO_AUTOCOMMIT : {
boolean value = ((Boolean) object).booleanValue();
this.setAutoCommit(value);
break;
}
case SessionInterface.INFO_CONNECTION_READONLY : {
boolean value = ((Boolean) object).booleanValue();
this.setReadOnlyDefault(value);
break;
}
case SessionInterface.INFO_ISOLATION : {
int value = ((Integer) object).intValue();
this.setIsolation(value);
break;
}
case SessionInterface.INFO_CATALOG : {
String value = ((String) object);
this.setCatalog(value);
}
}
}
// lobs
public BlobDataID createBlob(long length) {
long lobID = database.lobManager.createBlob(length);
if (lobID == 0) {
throw Error.error(ErrorCode.X_0F502);
}
sessionData.addToCreatedLobs(lobID);
return new BlobDataID(lobID);
}
public ClobDataID createClob(long length) {
long lobID = database.lobManager.createClob(length);
if (lobID == 0) {
throw Error.error(ErrorCode.X_0F502);
}
sessionData.addToCreatedLobs(lobID);
return new ClobDataID(lobID);
}
public void registerResultLobs(Result result) {
sessionData.registerLobForResult(result);
}
public void allocateResultLob(ResultLob result, InputStream inputStream) {
sessionData.allocateLobForResult(result, inputStream);
}
Result performLOBOperation(ResultLob cmd) {
long id = cmd.getLobID();
int operation = cmd.getSubType();
switch (operation) {
case ResultLob.LobResultTypes.REQUEST_GET_LOB : {
return database.lobManager.getLob(this, id, cmd.getOffset(),
cmd.getBlockLength());
}
case ResultLob.LobResultTypes.REQUEST_GET_LENGTH : {
return database.lobManager.getLength(this, id);
}
case ResultLob.LobResultTypes.REQUEST_GET_BYTES : {
return database.lobManager.getBytes(
this, id, cmd.getOffset(), (int) cmd.getBlockLength());
}
case ResultLob.LobResultTypes.REQUEST_SET_BYTES : {
return database.lobManager.setBytes(this, id,
cmd.getByteArray(),
cmd.getOffset());
}
case ResultLob.LobResultTypes.REQUEST_GET_CHARS : {
return database.lobManager.getChars(
this, id, cmd.getOffset(), (int) cmd.getBlockLength());
}
case ResultLob.LobResultTypes.REQUEST_SET_CHARS : {
return database.lobManager.setChars(this, id, cmd.getOffset(),
cmd.getCharArray());
}
case ResultLob.LobResultTypes.REQUEST_TRUNCATE : {
return database.lobManager.truncate(this, id, cmd.getOffset());
}
case ResultLob.LobResultTypes.REQUEST_CREATE_BYTES :
case ResultLob.LobResultTypes.REQUEST_CREATE_CHARS :
case ResultLob.LobResultTypes.REQUEST_GET_BYTE_PATTERN_POSITION :
case ResultLob.LobResultTypes.REQUEST_GET_CHAR_PATTERN_POSITION :
default : {
throw Error.runtimeError(ErrorCode.U_S0500, "Session");
}
}
}
// 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;
}
// schema object methods
public void setSchema(String schema) {
currentSchema = database.schemaManager.getSchemaHsqlName(schema);
}
public void setCatalog(String catalog) {
if (database.getCatalogName().name.equals(catalog)) {
return;
}
throw Error.error(ErrorCode.X_3D000);
}
/**
* 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) {
return name == null ? currentSchema
: database.schemaManager.getSchemaHsqlName(name);
}
/**
* Same as above, but return string
*/
public String getSchemaName(String name) {
return name == null ? currentSchema.name
: database.schemaManager.getSchemaName(name);
}
public HsqlName getCurrentSchemaHsqlName() {
return currentSchema;
}
// session tables
Table[] transitionTables = Table.emptyArray;
public void setSessionTables(Table[] tables) {
transitionTables = tables;
}
public Table findSessionTable(String name) {
for (int i = 0; i < transitionTables.length; i++) {
if (name.equals(transitionTables[i].getName().name)) {
return transitionTables[i];
}
}
return null;
}
//
public int getResultMemoryRowCount() {
return resultMaxMemoryRows;
}
public void setResultMemoryRowCount(int count) {
if (database.getTempDirectoryPath() != null) {
if (count <= 0) {
count = Integer.MAX_VALUE;
}
resultMaxMemoryRows = count;
}
}
// 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;
}
public HsqlException getLastWarnings() {
if (sqlWarnings == null || sqlWarnings.size() == 0) {
return null;
}
return (HsqlException) sqlWarnings.get(sqlWarnings.size() - 1);
}
public void clearWarnings() {
if (sqlWarnings != null) {
sqlWarnings.clear();
}
}
// services
Scanner secondaryScanner;
SimpleDateFormat simpleDateFormat;
SimpleDateFormat simpleDateFormatGMT;
Random randomGenerator = new Random();
public double random(long seed) {
randomGenerator.setSeed(seed);
return randomGenerator.nextDouble();
}
public double random() {
return randomGenerator.nextDouble();
}
public Scanner getScanner() {
if (secondaryScanner == null) {
secondaryScanner = new Scanner();
}
return secondaryScanner;
}
public SimpleDateFormat getSimpleDateFormat() {
if (simpleDateFormat == null) {
simpleDateFormat = new SimpleDateFormat("MMMM", Locale.ENGLISH);
SimpleTimeZone zone = new SimpleTimeZone(timeZoneSeconds,
"hsqldb");
Calendar cal = new GregorianCalendar(zone);
simpleDateFormat.setCalendar(cal);
}
return simpleDateFormat;
}
public SimpleDateFormat getSimpleDateFormatGMT() {
if (simpleDateFormatGMT == null) {
simpleDateFormatGMT = new SimpleDateFormat("MMMM", Locale.ENGLISH);
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
simpleDateFormatGMT.setCalendar(cal);
}
return simpleDateFormatGMT;
}
// SEQUENCE current values
void logSequences() {
OrderedHashSet set = sessionData.sequenceUpdateSet;
if (set == null || set.isEmpty()) {
return;
}
for (int i = 0, size = set.size(); i < size; i++) {
NumberSequence sequence = (NumberSequence) set.get(i);
database.logger.writeSequenceStatement(this, sequence);
}
sessionData.sequenceUpdateSet.clear();
}
//
static String getSavepointSQL(String name) {
StringBuffer sb = new StringBuffer(Tokens.T_SAVEPOINT);
sb.append(' ').append('"').append(name).append('"');
return sb.toString();
}
static String getSavepointRollbackSQL(String name) {
StringBuffer sb = new StringBuffer();
sb.append(Tokens.T_ROLLBACK).append(' ').append(Tokens.T_TO).append(
' ');
sb.append(Tokens.T_SAVEPOINT).append(' ');
sb.append('"').append(name).append('"');
return sb.toString();
}
String getStartTransactionSQL() {
StringBuffer sb = new StringBuffer();
sb.append(Tokens.T_START).append(' ').append(Tokens.T_TRANSACTION);
if (isolationMode != isolationModeDefault) {
sb.append(' ');
appendIsolationSQL(sb, isolationMode);
}
return sb.toString();
}
String getTransactionIsolationSQL() {
StringBuffer sb = new StringBuffer();
sb.append(Tokens.T_SET).append(' ').append(Tokens.T_TRANSACTION);
sb.append(' ');
appendIsolationSQL(sb, isolationMode);
return sb.toString();
}
String getSessionIsolationSQL() {
StringBuffer sb = new StringBuffer();
sb.append(Tokens.T_SET).append(' ').append(Tokens.T_SESSION);
sb.append(' ').append(Tokens.T_CHARACTERISTICS).append(' ');
sb.append(Tokens.T_AS).append(' ');
appendIsolationSQL(sb, isolationModeDefault);
return sb.toString();
}
static void appendIsolationSQL(StringBuffer sb, int isolationLevel) {
sb.append(Tokens.T_ISOLATION).append(' ');
sb.append(Tokens.T_LEVEL).append(' ');
switch (isolationLevel) {
case SessionInterface.TX_READ_UNCOMMITTED :
case SessionInterface.TX_READ_COMMITTED :
sb.append(Tokens.T_READ).append(' ');
sb.append(Tokens.T_COMMITTED);
break;
case SessionInterface.TX_REPEATABLE_READ :
case SessionInterface.TX_SERIALIZABLE :
sb.append(Tokens.T_SERIALIZABLE);
break;
}
}
String getSetSchemaStatement() {
return "SET SCHEMA " + currentSchema.statementName;
}
}