All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.shaded.h2.engine.Session Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
 * Version 1.0, and under the Eclipse Public License, Version 1.0
 * (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.glowroot.shaded.h2.engine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;

import org.glowroot.shaded.h2.api.ErrorCode;
import org.glowroot.shaded.h2.command.Command;
import org.glowroot.shaded.h2.command.CommandInterface;
import org.glowroot.shaded.h2.command.Parser;
import org.glowroot.shaded.h2.command.Prepared;
import org.glowroot.shaded.h2.command.dml.SetTypes;
import org.glowroot.shaded.h2.constraint.Constraint;
import org.glowroot.shaded.h2.index.Index;
import org.glowroot.shaded.h2.jdbc.JdbcConnection;
import org.glowroot.shaded.h2.message.DbException;
import org.glowroot.shaded.h2.message.Trace;
import org.glowroot.shaded.h2.message.TraceSystem;
import org.glowroot.shaded.h2.mvstore.db.MVTable;
import org.glowroot.shaded.h2.mvstore.db.TransactionStore.Change;
import org.glowroot.shaded.h2.mvstore.db.TransactionStore.Transaction;
import org.glowroot.shaded.h2.result.ResultInterface;
import org.glowroot.shaded.h2.result.Row;
import org.glowroot.shaded.h2.schema.Schema;
import org.glowroot.shaded.h2.store.DataHandler;
import org.glowroot.shaded.h2.store.InDoubtTransaction;
import org.glowroot.shaded.h2.store.LobStorageFrontend;
import org.glowroot.shaded.h2.table.Table;
import org.glowroot.shaded.h2.util.New;
import org.glowroot.shaded.h2.util.SmallLRUCache;
import org.glowroot.shaded.h2.value.Value;
import org.glowroot.shaded.h2.value.ValueArray;
import org.glowroot.shaded.h2.value.ValueLong;
import org.glowroot.shaded.h2.value.ValueNull;
import org.glowroot.shaded.h2.value.ValueString;

/**
 * A session represents an embedded database connection. When using the server
 * mode, this object resides on the server side and communicates with a
 * SessionRemote object on the client side.
 */
public class Session extends SessionWithState {

    /**
     * This special log position means that the log entry has been written.
     */
    public static final int LOG_WRITTEN = -1;

    /**
     * The prefix of generated identifiers. It may not have letters, because
     * they are case sensitive.
     */
    private static final String SYSTEM_IDENTIFIER_PREFIX = "_";
    private static int nextSerialId;

    private final int serialId = nextSerialId++;
    private final Database database;
    private ConnectionInfo connectionInfo;
    private final User user;
    private final int id;
    private final ArrayList locks = New.arrayList();
    private final UndoLog undoLog;
    private boolean autoCommit = true;
    private Random random;
    private int lockTimeout;
    private Value lastIdentity = ValueLong.get(0);
    private Value lastScopeIdentity = ValueLong.get(0);
    private int firstUncommittedLog = Session.LOG_WRITTEN;
    private int firstUncommittedPos = Session.LOG_WRITTEN;
    private HashMap savepoints;
    private HashMap localTempTables;
    private HashMap localTempTableIndexes;
    private HashMap localTempTableConstraints;
    private int throttle;
    private long lastThrottle;
    private Command currentCommand;
    private boolean allowLiterals;
    private String currentSchemaName;
    private String[] schemaSearchPath;
    private Trace trace;
    private HashMap unlinkLobMap;
    private int systemIdentifier;
    private HashMap procedures;
    private boolean undoLogEnabled = true;
    private boolean redoLogBinary = true;
    private boolean autoCommitAtTransactionEnd;
    private String currentTransactionName;
    private volatile long cancelAt;
    private boolean closed;
    private final long sessionStart = System.currentTimeMillis();
    private long transactionStart;
    private long currentCommandStart;
    private HashMap variables;
    private HashSet temporaryResults;
    private int queryTimeout;
    private boolean commitOrRollbackDisabled;
    private Table waitForLock;
    private Thread waitForLockThread;
    private int modificationId;
    private int objectId;
    private final int queryCacheSize;
    private SmallLRUCache queryCache;
    private long modificationMetaID = -1;
    private ArrayList temporaryLobs;

    private Transaction transaction;
    private long startStatement = -1;

    public Session(Database database, User user, int id) {
        this.database = database;
        this.queryTimeout = database.getSettings().maxQueryTimeout;
        this.queryCacheSize = database.getSettings().queryCacheSize;
        this.undoLog = new UndoLog(this);
        this.user = user;
        this.id = id;
        Setting setting = database.findSetting(
                SetTypes.getTypeName(SetTypes.DEFAULT_LOCK_TIMEOUT));
        this.lockTimeout = setting == null ?
                Constants.INITIAL_LOCK_TIMEOUT : setting.getIntValue();
        this.currentSchemaName = Constants.SCHEMA_MAIN;
    }

    public boolean setCommitOrRollbackDisabled(boolean x) {
        boolean old = commitOrRollbackDisabled;
        commitOrRollbackDisabled = x;
        return old;
    }

    private void initVariables() {
        if (variables == null) {
            variables = database.newStringMap();
        }
    }

    /**
     * Set the value of the given variable for this session.
     *
     * @param name the name of the variable (may not be null)
     * @param value the new value (may not be null)
     */
    public void setVariable(String name, Value value) {
        initVariables();
        modificationId++;
        Value old;
        if (value == ValueNull.INSTANCE) {
            old = variables.remove(name);
        } else {
            // link LOB values, to make sure we have our own object
            value = value.link(database,
                    LobStorageFrontend.TABLE_ID_SESSION_VARIABLE);
            old = variables.put(name, value);
        }
        if (old != null) {
            // close the old value (in case it is a lob)
            old.unlink(database);
            old.close();
        }
    }

    /**
     * Get the value of the specified user defined variable. This method always
     * returns a value; it returns ValueNull.INSTANCE if the variable doesn't
     * exist.
     *
     * @param name the variable name
     * @return the value, or NULL
     */
    public Value getVariable(String name) {
        initVariables();
        Value v = variables.get(name);
        return v == null ? ValueNull.INSTANCE : v;
    }

    /**
     * Get the list of variable names that are set for this session.
     *
     * @return the list of names
     */
    public String[] getVariableNames() {
        if (variables == null) {
            return new String[0];
        }
        String[] list = new String[variables.size()];
        variables.keySet().toArray(list);
        return list;
    }

    /**
     * Get the local temporary table if one exists with that name, or null if
     * not.
     *
     * @param name the table name
     * @return the table, or null
     */
    public Table findLocalTempTable(String name) {
        if (localTempTables == null) {
            return null;
        }
        return localTempTables.get(name);
    }

    public ArrayList
getLocalTempTables() { if (localTempTables == null) { return New.arrayList(); } return New.arrayList(localTempTables.values()); } /** * Add a local temporary table to this session. * * @param table the table to add * @throws DbException if a table with this name already exists */ public void addLocalTempTable(Table table) { if (localTempTables == null) { localTempTables = database.newStringMap(); } if (localTempTables.get(table.getName()) != null) { throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, table.getSQL()); } modificationId++; localTempTables.put(table.getName(), table); } /** * Drop and remove the given local temporary table from this session. * * @param table the table */ public void removeLocalTempTable(Table table) { modificationId++; localTempTables.remove(table.getName()); synchronized (database) { table.removeChildrenAndResources(this); } } /** * Get the local temporary index if one exists with that name, or null if * not. * * @param name the table name * @return the table, or null */ public Index findLocalTempTableIndex(String name) { if (localTempTableIndexes == null) { return null; } return localTempTableIndexes.get(name); } public HashMap getLocalTempTableIndexes() { if (localTempTableIndexes == null) { return New.hashMap(); } return localTempTableIndexes; } /** * Add a local temporary index to this session. * * @param index the index to add * @throws DbException if a index with this name already exists */ public void addLocalTempTableIndex(Index index) { if (localTempTableIndexes == null) { localTempTableIndexes = database.newStringMap(); } if (localTempTableIndexes.get(index.getName()) != null) { throw DbException.get(ErrorCode.INDEX_ALREADY_EXISTS_1, index.getSQL()); } localTempTableIndexes.put(index.getName(), index); } /** * Drop and remove the given local temporary index from this session. * * @param index the index */ public void removeLocalTempTableIndex(Index index) { if (localTempTableIndexes != null) { localTempTableIndexes.remove(index.getName()); synchronized (database) { index.removeChildrenAndResources(this); } } } /** * Get the local temporary constraint if one exists with that name, or * null if not. * * @param name the constraint name * @return the constraint, or null */ public Constraint findLocalTempTableConstraint(String name) { if (localTempTableConstraints == null) { return null; } return localTempTableConstraints.get(name); } /** * Get the map of constraints for all constraints on local, temporary * tables, if any. The map's keys are the constraints' names. * * @return the map of constraints, or null */ public HashMap getLocalTempTableConstraints() { if (localTempTableConstraints == null) { return New.hashMap(); } return localTempTableConstraints; } /** * Add a local temporary constraint to this session. * * @param constraint the constraint to add * @throws DbException if a constraint with the same name already exists */ public void addLocalTempTableConstraint(Constraint constraint) { if (localTempTableConstraints == null) { localTempTableConstraints = database.newStringMap(); } String name = constraint.getName(); if (localTempTableConstraints.get(name) != null) { throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, constraint.getSQL()); } localTempTableConstraints.put(name, constraint); } /** * Drop and remove the given local temporary constraint from this session. * * @param constraint the constraint */ void removeLocalTempTableConstraint(Constraint constraint) { if (localTempTableConstraints != null) { localTempTableConstraints.remove(constraint.getName()); synchronized (database) { constraint.removeChildrenAndResources(this); } } } @Override public boolean getAutoCommit() { return autoCommit; } public User getUser() { return user; } @Override public void setAutoCommit(boolean b) { autoCommit = b; } public int getLockTimeout() { return lockTimeout; } public void setLockTimeout(int lockTimeout) { this.lockTimeout = lockTimeout; } @Override public synchronized CommandInterface prepareCommand(String sql, int fetchSize) { return prepareLocal(sql); } /** * Parse and prepare the given SQL statement. This method also checks the * rights. * * @param sql the SQL statement * @return the prepared statement */ public Prepared prepare(String sql) { return prepare(sql, false); } /** * Parse and prepare the given SQL statement. * * @param sql the SQL statement * @param rightsChecked true if the rights have already been checked * @return the prepared statement */ public Prepared prepare(String sql, boolean rightsChecked) { Parser parser = new Parser(this); parser.setRightsChecked(rightsChecked); return parser.prepare(sql); } /** * Parse and prepare the given SQL statement. * This method also checks if the connection has been closed. * * @param sql the SQL statement * @return the prepared statement */ public Command prepareLocal(String sql) { if (closed) { throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "session closed"); } Command command; if (queryCacheSize > 0) { if (queryCache == null) { queryCache = SmallLRUCache.newInstance(queryCacheSize); modificationMetaID = database.getModificationMetaId(); } else { long newModificationMetaID = database.getModificationMetaId(); if (newModificationMetaID != modificationMetaID) { queryCache.clear(); modificationMetaID = newModificationMetaID; } command = queryCache.get(sql); if (command != null && command.canReuse()) { command.reuse(); return command; } } } Parser parser = new Parser(this); command = parser.prepareCommand(sql); if (queryCache != null) { if (command.isCacheable()) { queryCache.put(sql, command); } } return command; } public Database getDatabase() { return database; } @Override public int getPowerOffCount() { return database.getPowerOffCount(); } @Override public void setPowerOffCount(int count) { database.setPowerOffCount(count); } /** * Commit the current transaction. If the statement was not a data * definition statement, and if there are temporary tables that should be * dropped or truncated at commit, this is done as well. * * @param ddl if the statement was a data definition statement */ public void commit(boolean ddl) { checkCommitRollback(); currentTransactionName = null; transactionStart = 0; if (transaction != null) { // increment the data mod count, so that other sessions // see the changes // TODO should not rely on locking if (locks.size() > 0) { for (int i = 0, size = locks.size(); i < size; i++) { Table t = locks.get(i); if (t instanceof MVTable) { ((MVTable) t).commit(); } } } transaction.commit(); transaction = null; } if (containsUncommitted()) { // need to commit even if rollback is not possible // (create/drop table and so on) database.commit(this); } if (temporaryLobs != null) { for (Value v : temporaryLobs) { if (!v.isLinked()) { v.close(); } } temporaryLobs.clear(); } if (undoLog.size() > 0) { // commit the rows when using MVCC if (database.isMultiVersion()) { ArrayList rows = New.arrayList(); synchronized (database) { while (undoLog.size() > 0) { UndoLogRecord entry = undoLog.getLast(); entry.commit(); rows.add(entry.getRow()); undoLog.removeLast(false); } for (int i = 0, size = rows.size(); i < size; i++) { Row r = rows.get(i); r.commit(); } } } undoLog.clear(); } if (!ddl) { // do not clean the temp tables if the last command was a // create/drop cleanTempTables(false); if (autoCommitAtTransactionEnd) { autoCommit = true; autoCommitAtTransactionEnd = false; } } endTransaction(); } private void checkCommitRollback() { if (commitOrRollbackDisabled && locks.size() > 0) { throw DbException.get(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED); } } private void endTransaction() { if (unlinkLobMap != null && unlinkLobMap.size() > 0) { // need to flush the transaction log, because we can't unlink lobs // if the commit record is not written database.flush(); for (Value v : unlinkLobMap.values()) { v.unlink(database); v.close(); } unlinkLobMap = null; } unlockAll(); } /** * Fully roll back the current transaction. */ public void rollback() { checkCommitRollback(); currentTransactionName = null; boolean needCommit = false; if (undoLog.size() > 0) { rollbackTo(null, false); needCommit = true; } if (transaction != null) { rollbackTo(null, false); needCommit = true; // rollback stored the undo operations in the transaction // committing will end the transaction transaction.commit(); transaction = null; } if (locks.size() > 0 || needCommit) { database.commit(this); } cleanTempTables(false); if (autoCommitAtTransactionEnd) { autoCommit = true; autoCommitAtTransactionEnd = false; } endTransaction(); } /** * Partially roll back the current transaction. * * @param savepoint the savepoint to which should be rolled back * @param trimToSize if the list should be trimmed */ public void rollbackTo(Savepoint savepoint, boolean trimToSize) { int index = savepoint == null ? 0 : savepoint.logIndex; while (undoLog.size() > index) { UndoLogRecord entry = undoLog.getLast(); entry.undo(this); undoLog.removeLast(trimToSize); } if (transaction != null) { long savepointId = savepoint == null ? 0 : savepoint.transactionSavepoint; HashMap tableMap = database.getMvStore().getTables(); Iterator it = transaction.getChanges(savepointId); while (it.hasNext()) { Change c = it.next(); MVTable t = tableMap.get(c.mapName); if (t != null) { long key = ((ValueLong) c.key).getLong(); ValueArray value = (ValueArray) c.value; short op; Row row; if (value == null) { op = UndoLogRecord.INSERT; row = t.getRow(this, key); } else { op = UndoLogRecord.DELETE; row = new Row(value.getList(), Row.MEMORY_CALCULATE); } row.setKey(key); UndoLogRecord log = new UndoLogRecord(t, op, row); log.undo(this); } } } if (savepoints != null) { String[] names = new String[savepoints.size()]; savepoints.keySet().toArray(names); for (String name : names) { Savepoint sp = savepoints.get(name); int savepointIndex = sp.logIndex; if (savepointIndex > index) { savepoints.remove(name); } } } } @Override public boolean hasPendingTransaction() { return undoLog.size() > 0; } /** * Create a savepoint to allow rolling back to this state. * * @return the savepoint */ public Savepoint setSavepoint() { Savepoint sp = new Savepoint(); sp.logIndex = undoLog.size(); if (database.getMvStore() != null) { sp.transactionSavepoint = getStatementSavepoint(); } return sp; } public int getId() { return id; } @Override public void cancel() { cancelAt = System.currentTimeMillis(); } @Override public void close() { if (!closed) { try { database.checkPowerOff(); cleanTempTables(true); undoLog.clear(); database.removeSession(this); } finally { closed = true; } } } /** * Add a lock for the given table. The object is unlocked on commit or * rollback. * * @param table the table that is locked */ public void addLock(Table table) { if (SysProperties.CHECK) { if (locks.indexOf(table) >= 0) { DbException.throwInternalError(); } } locks.add(table); } /** * Add an undo log entry to this session. * * @param table the table * @param operation the operation type (see {@link UndoLogRecord}) * @param row the row */ public void log(Table table, short operation, Row row) { if (table.isMVStore()) { return; } if (undoLogEnabled) { UndoLogRecord log = new UndoLogRecord(table, operation, row); // called _after_ the row was inserted successfully into the table, // otherwise rollback will try to rollback a not-inserted row if (SysProperties.CHECK) { int lockMode = database.getLockMode(); if (lockMode != Constants.LOCK_MODE_OFF && !database.isMultiVersion()) { String tableType = log.getTable().getTableType(); if (locks.indexOf(log.getTable()) < 0 && !Table.TABLE_LINK.equals(tableType) && !Table.EXTERNAL_TABLE_ENGINE.equals(tableType)) { DbException.throwInternalError(); } } } undoLog.add(log); } else { if (database.isMultiVersion()) { // see also UndoLogRecord.commit ArrayList indexes = table.getIndexes(); for (int i = 0, size = indexes.size(); i < size; i++) { Index index = indexes.get(i); index.commit(operation, row); } row.commit(); } } } /** * Unlock all read locks. This is done if the transaction isolation mode is * READ_COMMITTED. */ public void unlockReadLocks() { if (database.isMultiVersion()) { // MVCC: keep shared locks (insert / update / delete) return; } // locks is modified in the loop for (int i = 0; i < locks.size(); i++) { Table t = locks.get(i); if (!t.isLockedExclusively()) { synchronized (database) { t.unlock(this); locks.remove(i); } i--; } } } /** * Unlock just this table. * * @param t the table to unlock */ void unlock(Table t) { locks.remove(t); } private void unlockAll() { if (SysProperties.CHECK) { if (undoLog.size() > 0) { DbException.throwInternalError(); } } if (locks.size() > 0) { synchronized (database) { // don't use the enhanced for loop to save memory for (int i = 0, size = locks.size(); i < size; i++) { Table t = locks.get(i); t.unlock(this); } locks.clear(); } } savepoints = null; sessionStateChanged = true; } private void cleanTempTables(boolean closeSession) { if (localTempTables != null && localTempTables.size() > 0) { synchronized (database) { for (Table table : New.arrayList(localTempTables.values())) { if (closeSession || table.getOnCommitDrop()) { modificationId++; table.setModified(); localTempTables.remove(table.getName()); table.removeChildrenAndResources(this); if (closeSession) { // need to commit, otherwise recovery might // ignore the table removal database.commit(this); } } else if (table.getOnCommitTruncate()) { table.truncate(this); } } } } } public Random getRandom() { if (random == null) { random = new Random(); } return random; } @Override public Trace getTrace() { if (trace != null && !closed) { return trace; } String traceModuleName = Trace.JDBC + "[" + id + "]"; if (closed) { return new TraceSystem(null).getTrace(traceModuleName); } trace = database.getTrace(traceModuleName); return trace; } public void setLastIdentity(Value last) { this.lastIdentity = last; this.lastScopeIdentity = last; } public Value getLastIdentity() { return lastIdentity; } public void setLastScopeIdentity(Value last) { this.lastScopeIdentity = last; } public Value getLastScopeIdentity() { return lastScopeIdentity; } /** * Called when a log entry for this session is added. The session keeps * track of the first entry in the transaction log that is not yet * committed. * * @param logId the transaction log id * @param pos the position of the log entry in the transaction log */ public void addLogPos(int logId, int pos) { if (firstUncommittedLog == Session.LOG_WRITTEN) { firstUncommittedLog = logId; firstUncommittedPos = pos; } } public int getFirstUncommittedLog() { return firstUncommittedLog; } /** * This method is called after the transaction log has written the commit * entry for this session. */ void setAllCommitted() { firstUncommittedLog = Session.LOG_WRITTEN; firstUncommittedPos = Session.LOG_WRITTEN; } /** * Whether the session contains any uncommitted changes. * * @return true if yes */ public boolean containsUncommitted() { if (database.getMvStore() != null) { return transaction != null; } return firstUncommittedLog != Session.LOG_WRITTEN; } /** * Create a savepoint that is linked to the current log position. * * @param name the savepoint name */ public void addSavepoint(String name) { if (savepoints == null) { savepoints = database.newStringMap(); } Savepoint sp = new Savepoint(); sp.logIndex = undoLog.size(); if (database.getMvStore() != null) { sp.transactionSavepoint = getStatementSavepoint(); } savepoints.put(name, sp); } /** * Undo all operations back to the log position of the given savepoint. * * @param name the savepoint name */ public void rollbackToSavepoint(String name) { checkCommitRollback(); if (savepoints == null) { throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name); } Savepoint savepoint = savepoints.get(name); if (savepoint == null) { throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name); } rollbackTo(savepoint, false); } /** * Prepare the given transaction. * * @param transactionName the name of the transaction */ public void prepareCommit(String transactionName) { if (transaction != null) { database.prepareCommit(this, transactionName); } if (containsUncommitted()) { // need to commit even if rollback is not possible (create/drop // table and so on) database.prepareCommit(this, transactionName); } currentTransactionName = transactionName; } /** * Commit or roll back the given transaction. * * @param transactionName the name of the transaction * @param commit true for commit, false for rollback */ public void setPreparedTransaction(String transactionName, boolean commit) { if (currentTransactionName != null && currentTransactionName.equals(transactionName)) { if (commit) { commit(false); } else { rollback(); } } else { ArrayList list = database .getInDoubtTransactions(); int state = commit ? InDoubtTransaction.COMMIT : InDoubtTransaction.ROLLBACK; boolean found = false; if (list != null) { for (InDoubtTransaction p: list) { if (p.getTransactionName().equals(transactionName)) { p.setState(state); found = true; break; } } } if (!found) { throw DbException.get(ErrorCode.TRANSACTION_NOT_FOUND_1, transactionName); } } } @Override public boolean isClosed() { return closed; } public void setThrottle(int throttle) { this.throttle = throttle; } /** * Wait for some time if this session is throttled (slowed down). */ public void throttle() { if (currentCommandStart == 0) { currentCommandStart = System.currentTimeMillis(); } if (throttle == 0) { return; } long time = System.currentTimeMillis(); if (lastThrottle + Constants.THROTTLE_DELAY > time) { return; } lastThrottle = time + throttle; try { Thread.sleep(throttle); } catch (Exception e) { // ignore InterruptedException } } /** * Set the current command of this session. This is done just before * executing the statement. * * @param command the command */ public void setCurrentCommand(Command command) { this.currentCommand = command; if (queryTimeout > 0 && command != null) { long now = System.currentTimeMillis(); currentCommandStart = now; cancelAt = now + queryTimeout; } } /** * Check if the current transaction is canceled by calling * Statement.cancel() or because a session timeout was set and expired. * * @throws DbException if the transaction is canceled */ public void checkCanceled() { throttle(); if (cancelAt == 0) { return; } long time = System.currentTimeMillis(); if (time >= cancelAt) { cancelAt = 0; throw DbException.get(ErrorCode.STATEMENT_WAS_CANCELED); } } /** * Get the cancel time. * * @return the time or 0 if not set */ public long getCancel() { return cancelAt; } public Command getCurrentCommand() { return currentCommand; } public long getCurrentCommandStart() { return currentCommandStart; } public boolean getAllowLiterals() { return allowLiterals; } public void setAllowLiterals(boolean b) { this.allowLiterals = b; } public void setCurrentSchema(Schema schema) { modificationId++; this.currentSchemaName = schema.getName(); } public String getCurrentSchemaName() { return currentSchemaName; } /** * Create an internal connection. This connection is used when initializing * triggers, and when calling user defined functions. * * @param columnList if the url should be 'jdbc:columnlist:connection' * @return the internal connection */ public JdbcConnection createConnection(boolean columnList) { String url; if (columnList) { url = Constants.CONN_URL_COLUMNLIST; } else { url = Constants.CONN_URL_INTERNAL; } return new JdbcConnection(this, getUser().getName(), url); } @Override public DataHandler getDataHandler() { return database; } /** * Remember that the given LOB value must be un-linked (disconnected from * the table) at commit. * * @param v the value */ public void unlinkAtCommit(Value v) { if (SysProperties.CHECK && !v.isLinked()) { DbException.throwInternalError(); } if (unlinkLobMap == null) { unlinkLobMap = New.hashMap(); } unlinkLobMap.put(v.toString(), v); } /** * Do not unlink this LOB value at commit any longer. * * @param v the value */ public void unlinkAtCommitStop(Value v) { if (unlinkLobMap != null) { unlinkLobMap.remove(v.toString()); } } /** * Get the next system generated identifiers. The identifier returned does * not occur within the given SQL statement. * * @param sql the SQL statement * @return the new identifier */ public String getNextSystemIdentifier(String sql) { String identifier; do { identifier = SYSTEM_IDENTIFIER_PREFIX + systemIdentifier++; } while (sql.indexOf(identifier) >= 0); return identifier; } /** * Add a procedure to this session. * * @param procedure the procedure to add */ public void addProcedure(Procedure procedure) { if (procedures == null) { procedures = database.newStringMap(); } procedures.put(procedure.getName(), procedure); } /** * Remove a procedure from this session. * * @param name the name of the procedure to remove */ public void removeProcedure(String name) { if (procedures != null) { procedures.remove(name); } } /** * Get the procedure with the given name, or null * if none exists. * * @param name the procedure name * @return the procedure or null */ public Procedure getProcedure(String name) { if (procedures == null) { return null; } return procedures.get(name); } public void setSchemaSearchPath(String[] schemas) { modificationId++; this.schemaSearchPath = schemas; } public String[] getSchemaSearchPath() { return schemaSearchPath; } @Override public int hashCode() { return serialId; } @Override public String toString() { return "#" + serialId + " (user: " + user.getName() + ")"; } public void setUndoLogEnabled(boolean b) { this.undoLogEnabled = b; } public void setRedoLogBinary(boolean b) { this.redoLogBinary = b; } public boolean isUndoLogEnabled() { return undoLogEnabled; } /** * Begin a transaction. */ public void begin() { autoCommitAtTransactionEnd = true; autoCommit = false; } public long getSessionStart() { return sessionStart; } public long getTransactionStart() { if (transactionStart == 0) { transactionStart = System.currentTimeMillis(); } return transactionStart; } public Table[] getLocks() { // copy the data without synchronizing ArrayList
copy = New.arrayList(); for (int i = 0; i < locks.size(); i++) { try { copy.add(locks.get(i)); } catch (Exception e) { // ignore break; } } Table[] list = new Table[copy.size()]; copy.toArray(list); return list; } /** * Wait if the exclusive mode has been enabled for another session. This * method returns as soon as the exclusive mode has been disabled. */ public void waitIfExclusiveModeEnabled() { // Even in exclusive mode, we have to let the LOB session proceed, or we // will get deadlocks. if (database.getLobSession() == this) { return; } while (true) { Session exclusive = database.getExclusiveSession(); if (exclusive == null || exclusive == this) { break; } if (Thread.holdsLock(exclusive)) { // if another connection is used within the connection break; } try { Thread.sleep(100); } catch (InterruptedException e) { // ignore } } } /** * Remember the result set and close it as soon as the transaction is * committed (if it needs to be closed). This is done to delete temporary * files as soon as possible, and free object ids of temporary tables. * * @param result the temporary result set */ public void addTemporaryResult(ResultInterface result) { if (!result.needToClose()) { return; } if (temporaryResults == null) { temporaryResults = New.hashSet(); } if (temporaryResults.size() < 100) { // reference at most 100 result sets to avoid memory problems temporaryResults.add(result); } } private void closeTemporaryResults() { if (temporaryResults != null) { for (ResultInterface result : temporaryResults) { result.close(); } temporaryResults = null; } } public void setQueryTimeout(int queryTimeout) { int max = database.getSettings().maxQueryTimeout; if (max != 0 && (max < queryTimeout || queryTimeout == 0)) { // the value must be at most max queryTimeout = max; } this.queryTimeout = queryTimeout; // must reset the cancel at here, // otherwise it is still used this.cancelAt = 0; } public int getQueryTimeout() { return queryTimeout; } /** * Set the table this session is waiting for, and the thread that is * waiting. * * @param waitForLock the table * @param waitForLockThread the current thread (the one that is waiting) */ public void setWaitForLock(Table waitForLock, Thread waitForLockThread) { this.waitForLock = waitForLock; this.waitForLockThread = waitForLockThread; } public Table getWaitForLock() { return waitForLock; } public Thread getWaitForLockThread() { return waitForLockThread; } public int getModificationId() { return modificationId; } @Override public boolean isReconnectNeeded(boolean write) { while (true) { boolean reconnect = database.isReconnectNeeded(); if (reconnect) { return true; } if (write) { if (database.beforeWriting()) { return false; } } else { return false; } } } @Override public void afterWriting() { database.afterWriting(); } @Override public SessionInterface reconnect(boolean write) { readSessionState(); close(); Session newSession = Engine.getInstance().createSession(connectionInfo); newSession.sessionState = sessionState; newSession.recreateSessionState(); if (write) { while (!newSession.database.beforeWriting()) { // wait until we are allowed to write } } return newSession; } public void setConnectionInfo(ConnectionInfo ci) { connectionInfo = ci; } public Value getTransactionId() { if (!database.isPersistent()) { return ValueNull.INSTANCE; } if (database.getMvStore() != null) { if (transaction == null) { return ValueNull.INSTANCE; } return ValueString.get(Long.toString(getTransaction().getId())); } if (undoLog.size() == 0) { return ValueNull.INSTANCE; } return ValueString.get(firstUncommittedLog + "-" + firstUncommittedPos + "-" + id); } /** * Get the next object id. * * @return the next object id */ public int nextObjectId() { return objectId++; } public boolean isRedoLogBinaryEnabled() { return redoLogBinary; } /** * Get the transaction to use for this session. * * @return the transaction */ public Transaction getTransaction() { if (transaction == null) { if (database.getMvStore().getStore().isClosed()) { throw DbException.get(ErrorCode.DATABASE_IS_CLOSED); } transaction = database.getMvStore().getTransactionStore().begin(); startStatement = -1; } return transaction; } public long getStatementSavepoint() { if (startStatement == -1) { startStatement = getTransaction().setSavepoint(); } return startStatement; } /** * Start a new statement within a transaction. */ public void startStatementWithinTransaction() { startStatement = -1; } /** * Mark the statement as completed. This also close all temporary result * set, and deletes all temporary files held by the result sets. */ public void endStatement() { startStatement = -1; closeTemporaryResults(); } @Override public void addTemporaryLob(Value v) { if (temporaryLobs == null) { temporaryLobs = new ArrayList(); } temporaryLobs.add(v); } /** * Represents a savepoint (a position in a transaction to where one can roll * back to). */ public static class Savepoint { /** * The undo log index. */ int logIndex; /** * The transaction savepoint id. */ long transactionSavepoint; } }