Exemple #1
0
 /**
  * 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);
 }
Exemple #2
0
/**
 * 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 final User user;
  private final int id;
  private final ArrayList<Table> locks = New.arrayList();
  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<String, Savepoint> savepoints;
  private HashMap<String, Table> localTempTables;
  private HashMap<String, Index> localTempTableIndexes;
  private HashMap<String, Constraint> localTempTableConstraints;
  private int throttle;
  private long lastThrottle;
  private Command currentCommand;
  private boolean allowLiterals;
  private String currentSchemaName;
  private String[] schemaSearchPath;
  private Trace trace;
  private HashMap<String, Value> unlinkLobMap;
  private int systemIdentifier;
  private HashMap<String, Procedure> procedures;
  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<String, Value> variables;
  private HashSet<LocalResult> 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<String, Command> queryCache;
  private ArrayList<Value> temporaryLobs;
  private boolean readOnly;
  private int transactionIsolation;

  private final Map<String, Connection> connectionHolder = New.hashMap();

  public Session(Database database, User user, int id) {
    this.database = database;
    this.queryTimeout = database.getSettings().maxQueryTimeout;
    this.queryCacheSize = database.getSettings().queryCacheSize;
    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 {
      old = variables.put(name, value);
    }
    if (old != null) {
      // close the old value (in case it is a lob)
      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<Table> 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<String, Index> 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<String, Constraint> 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 {
        // ignore table structure modification
        /*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;
  }

  /**
   * 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 (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 (!ddl) {
      // do not clean the temp tables if the last command was a
      // create/drop
      cleanTempTables(false);
      if (autoCommitAtTransactionEnd) {
        autoCommit = true;
        autoCommitAtTransactionEnd = false;
      }
    }
    endTransaction();

    boolean commit = true;
    List<SQLException> commitExceptions = New.arrayList();
    StringBuilder buf = new StringBuilder();
    for (Map.Entry<String, Connection> entry : connectionHolder.entrySet()) {
      if (commit) {
        try {
          entry.getValue().commit();
          buf.append("\ncommit shard " + entry.getKey() + " transaction succeed.");
        } catch (SQLException ex) {
          commit = false;
          commitExceptions.add(ex);
          buf.append("\ncommit shard " + entry.getKey() + " transaction failure.");
        }
      } else {
        // after unsucessfull commit we must try to rollback
        // remaining connections
        try {
          entry.getValue().rollback();
          buf.append("\nrollback shard " + entry.getKey() + " transaction succeed.");
        } catch (SQLException ex) {
          buf.append("\nrollback shard " + entry.getKey() + " transaction failure.");
        }
      }
    }
    if (commitExceptions.isEmpty()) {
      trace.debug("commit multiple group transaction succeed. commit track list:{0}", buf);
    } else {
      trace.error(
          commitExceptions.get(0),
          "fail to commit multiple group transaction. commit track list:{0}",
          buf);
      DbException.convert(commitExceptions.get(0));
    }
  }

  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
      unlinkLobMap = null;
    }
    unlockAll();
  }

  /** Fully roll back the current transaction. */
  public void rollback() {
    checkCommitRollback();
    currentTransactionName = null;
    boolean needCommit = false;
    if (locks.size() > 0 || needCommit) {
      database.commit(this);
    }
    cleanTempTables(false);
    if (autoCommitAtTransactionEnd) {
      autoCommit = true;
      autoCommitAtTransactionEnd = false;
    }
    endTransaction();

    List<SQLException> rollbackExceptions = New.arrayList();
    for (Map.Entry<String, Connection> entry : connectionHolder.entrySet()) {
      try {
        entry.getValue().rollback();
      } catch (SQLException ex) {
        rollbackExceptions.add(ex);
      }
    }
    if (!rollbackExceptions.isEmpty()) {
      throw DbException.convert(rollbackExceptions.get(0));
    }
  }

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

  /**
   * Create a savepoint to allow rolling back to this state.
   *
   * @return the savepoint
   */
  public Savepoint setSavepoint() {
    Savepoint sp = new Savepoint();
    return sp;
  }

  public int getId() {
    return id;
  }

  @Override
  public void cancel() {
    cancelAt = System.currentTimeMillis();
  }

  @Override
  public void close() {
    if (!closed) {
      try {
        for (Connection conn : connectionHolder.values()) {
          JdbcUtils.closeSilently(conn);
        }
        connectionHolder.clear();
        cleanTempTables(true);
        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.contains(table)) {
        DbException.throwInternalError();
      }
    }
    locks.add(table);
  }

  /**
   * Unlock just this table.
   *
   * @param t the table to unlock
   */
  void unlock(Table t) {
    locks.remove(t);
  }

  private void unlockAll() {
    if (locks.size() > 0) {
      // 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++;
            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() {
    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();
    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) {
    throw DbException.getUnsupportedException("Does not support two-phase commit.");
  }

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

  @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) {
    throw DbException.getUnsupportedException("TODO");
  }

  /**
   * 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.contains(identifier));
    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: "******")";
  }

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

  /**
   * 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(LocalResult 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 (LocalResult 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) {
    return false;
  }

  @Override
  public SessionInterface reconnect(boolean write) {
    readSessionState();
    close();
    Session newSession = database.createSession(this.user);
    newSession.sessionState = sessionState;
    newSession.recreateSessionState();
    return newSession;
  }

  public Value getTransactionId() {
    return ValueString.get(firstUncommittedLog + "-" + firstUncommittedPos + "-" + id);
  }

  /**
   * Get the next object id.
   *
   * @return the next object id
   */
  public int nextObjectId() {
    return objectId++;
  }

  /** Start a new statement within a transaction. */
  public void startStatementWithinTransaction() {}

  /**
   * 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() {
    closeTemporaryResults();
  }

  @Override
  public void addTemporaryLob(Value v) {
    if (temporaryLobs == null) {
      temporaryLobs = new ArrayList<Value>();
    }
    temporaryLobs.add(v);
  }

  @Override
  public void setTransactionIsolation(int level) {
    switch (level) {
      case Connection.TRANSACTION_NONE:
      case Connection.TRANSACTION_READ_UNCOMMITTED:
      case Connection.TRANSACTION_READ_COMMITTED:
      case Connection.TRANSACTION_REPEATABLE_READ:
      case Connection.TRANSACTION_SERIALIZABLE:
        break;
      default:
        throw DbException.getInvalidValueException("transaction isolation", level);
    }
    transactionIsolation = level;
  }

  @Override
  public int getTransactionIsolation() {
    return transactionIsolation;
  }

  @Override
  public boolean isReadOnly() {
    return readOnly;
  }

  @Override
  public void setReadOnly(boolean readOnly) {
    this.readOnly = readOnly;
  }

  public Connection getDataNodeConnection(String dataNode) throws SQLException {
    Connection result = connectionHolder.get(dataNode);
    if (result == null) {
      DataSource ds = database.getDataNode(dataNode);
      result = ds.getConnection();
      if (result.getAutoCommit() != getAutoCommit()) {
        result.setAutoCommit(getAutoCommit());
      }
      connectionHolder.put(dataNode, result);
    }
    return result;
  }

  /** 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;
  }
}
Exemple #3
0
 public HashMap<String, Index> getLocalTempTableIndexes() {
   if (localTempTableIndexes == null) {
     return New.hashMap();
   }
   return localTempTableIndexes;
 }
Exemple #4
0
 /**
  * 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<String, Constraint> getLocalTempTableConstraints() {
   if (localTempTableConstraints == null) {
     return New.hashMap();
   }
   return localTempTableConstraints;
 }
/** Implements the integrated aggregate functions, such as COUNT, MAX, SUM. */
public class Aggregate extends Expression {

  /** The aggregate type for COUNT(*). */
  public static final int COUNT_ALL = 0;

  /** The aggregate type for COUNT(expression). */
  public static final int COUNT = 1;

  /** The aggregate type for GROUP_CONCAT(...). */
  public static final int GROUP_CONCAT = 2;

  /** The aggregate type for SUM(expression). */
  static final int SUM = 3;

  /** The aggregate type for MIN(expression). */
  static final int MIN = 4;

  /** The aggregate type for MAX(expression). */
  static final int MAX = 5;

  /** The aggregate type for AVG(expression). */
  static final int AVG = 6;

  /** The aggregate type for STDDEV_POP(expression). */
  static final int STDDEV_POP = 7;

  /** The aggregate type for STDDEV_SAMP(expression). */
  static final int STDDEV_SAMP = 8;

  /** The aggregate type for VAR_POP(expression). */
  static final int VAR_POP = 9;

  /** The aggregate type for VAR_SAMP(expression). */
  static final int VAR_SAMP = 10;

  /** The aggregate type for BOOL_OR(expression). */
  static final int BOOL_OR = 11;

  /** The aggregate type for BOOL_AND(expression). */
  static final int BOOL_AND = 12;

  /** The aggregate type for SELECTIVITY(expression). */
  static final int SELECTIVITY = 13;

  /** The aggregate type for HISTOGRAM(expression). */
  static final int HISTOGRAM = 14;

  private static final HashMap<String, Integer> AGGREGATES = New.hashMap();

  private final int type;
  private final Select select;
  private final boolean distinct;

  private Expression on;
  private Expression groupConcatSeparator;
  private ArrayList<SelectOrderBy> groupConcatOrderList;
  private SortOrder groupConcatSort;
  private int dataType, scale;
  private long precision;
  private int displaySize;
  private int lastGroupRowId;

  /**
   * Create a new aggregate object.
   *
   * @param type the aggregate type
   * @param on the aggregated expression
   * @param select the select statement
   * @param distinct if distinct is used
   */
  public Aggregate(int type, Expression on, Select select, boolean distinct) {
    this.type = type;
    this.on = on;
    this.select = select;
    this.distinct = distinct;
  }

  static {
    addAggregate("COUNT", COUNT);
    addAggregate("SUM", SUM);
    addAggregate("MIN", MIN);
    addAggregate("MAX", MAX);
    addAggregate("AVG", AVG);
    addAggregate("GROUP_CONCAT", GROUP_CONCAT);
    addAggregate("STDDEV_SAMP", STDDEV_SAMP);
    addAggregate("STDDEV", STDDEV_SAMP);
    addAggregate("STDDEV_POP", STDDEV_POP);
    addAggregate("STDDEVP", STDDEV_POP);
    addAggregate("VAR_POP", VAR_POP);
    addAggregate("VARP", VAR_POP);
    addAggregate("VAR_SAMP", VAR_SAMP);
    addAggregate("VAR", VAR_SAMP);
    addAggregate("VARIANCE", VAR_SAMP);
    addAggregate("BOOL_OR", BOOL_OR);
    // HSQLDB compatibility, but conflicts with x > EVERY(...)
    addAggregate("SOME", BOOL_OR);
    addAggregate("BOOL_AND", BOOL_AND);
    // HSQLDB compatibility, but conflicts with x > SOME(...)
    addAggregate("EVERY", BOOL_AND);
    addAggregate("SELECTIVITY", SELECTIVITY);
    addAggregate("HISTOGRAM", HISTOGRAM);
  }

  private static void addAggregate(String name, int type) {
    AGGREGATES.put(name, type);
  }

  /**
   * Get the aggregate type for this name, or -1 if no aggregate has been found.
   *
   * @param name the aggregate function name
   * @return -1 if no aggregate function has been found, or the aggregate type
   */
  public static int getAggregateType(String name) {
    Integer type = AGGREGATES.get(name);
    return type == null ? -1 : type.intValue();
  }

  /**
   * Set the order for GROUP_CONCAT() aggregate.
   *
   * @param orderBy the order by list
   */
  public void setGroupConcatOrder(ArrayList<SelectOrderBy> orderBy) {
    this.groupConcatOrderList = orderBy;
  }

  /**
   * Set the separator for the GROUP_CONCAT() aggregate.
   *
   * @param separator the separator expression
   */
  public void setGroupConcatSeparator(Expression separator) {
    this.groupConcatSeparator = separator;
  }

  private SortOrder initOrder(Session session) {
    int size = groupConcatOrderList.size();
    int[] index = new int[size];
    int[] sortType = new int[size];
    for (int i = 0; i < size; i++) {
      SelectOrderBy o = groupConcatOrderList.get(i);
      index[i] = i + 1;
      int order = o.descending ? SortOrder.DESCENDING : SortOrder.ASCENDING;
      sortType[i] = order;
    }
    return new SortOrder(session.getDatabase(), index, sortType, null);
  }

  @Override
  public void updateAggregate(Session session) {
    // TODO aggregates: check nested MIN(MAX(ID)) and so on
    // if (on != null) {
    // on.updateAggregate();
    // }
    HashMap<Expression, Object> group = select.getCurrentGroup();
    if (group == null) {
      // this is a different level (the enclosing query)
      return;
    }

    int groupRowId = select.getCurrentGroupRowId();
    if (lastGroupRowId == groupRowId) {
      // already visited
      return;
    }
    lastGroupRowId = groupRowId;

    AggregateData data = (AggregateData) group.get(this);
    if (data == null) {
      data = AggregateData.create(type);
      group.put(this, data);
    }
    Value v = on == null ? null : on.getValue(session);
    if (type == GROUP_CONCAT) {
      if (v != ValueNull.INSTANCE) {
        v = v.convertTo(Value.STRING);
        if (groupConcatOrderList != null) {
          int size = groupConcatOrderList.size();
          Value[] array = new Value[1 + size];
          array[0] = v;
          for (int i = 0; i < size; i++) {
            SelectOrderBy o = groupConcatOrderList.get(i);
            array[i + 1] = o.expression.getValue(session);
          }
          v = ValueArray.get(array);
        }
      }
    }
    data.add(session.getDatabase(), dataType, distinct, v);
  }

  @Override
  public Value getValue(Session session) {
    if (select.isQuickAggregateQuery()) {
      switch (type) {
        case COUNT:
        case COUNT_ALL:
          Table table = select.getTopTableFilter().getTable();
          return ValueLong.get(table.getRowCount(session));
        case MIN:
        case MAX:
          boolean first = type == MIN;
          Index index = getColumnIndex();
          int sortType = index.getIndexColumns()[0].sortType;
          if ((sortType & SortOrder.DESCENDING) != 0) {
            first = !first;
          }
          Cursor cursor = index.findFirstOrLast(session, first);
          SearchRow row = cursor.getSearchRow();
          Value v;
          if (row == null) {
            v = ValueNull.INSTANCE;
          } else {
            v = row.getValue(index.getColumns()[0].getColumnId());
          }
          return v;
        default:
          DbException.throwInternalError("type=" + type);
      }
    }
    HashMap<Expression, Object> group = select.getCurrentGroup();
    if (group == null) {
      throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
    }
    AggregateData data = (AggregateData) group.get(this);
    if (data == null) {
      data = AggregateData.create(type);
    }
    Value v = data.getValue(session.getDatabase(), dataType, distinct);
    if (type == GROUP_CONCAT) {
      ArrayList<Value> list = ((AggregateDataGroupConcat) data).getList();
      if (list == null || list.size() == 0) {
        return ValueNull.INSTANCE;
      }
      if (groupConcatOrderList != null) {
        final SortOrder sortOrder = groupConcatSort;
        Collections.sort(
            list,
            new Comparator<Value>() {
              @Override
              public int compare(Value v1, Value v2) {
                Value[] a1 = ((ValueArray) v1).getList();
                Value[] a2 = ((ValueArray) v2).getList();
                return sortOrder.compare(a1, a2);
              }
            });
      }
      StatementBuilder buff = new StatementBuilder();
      String sep =
          groupConcatSeparator == null ? "," : groupConcatSeparator.getValue(session).getString();
      for (Value val : list) {
        String s;
        if (val.getType() == Value.ARRAY) {
          s = ((ValueArray) val).getList()[0].getString();
        } else {
          s = val.getString();
        }
        if (s == null) {
          continue;
        }
        if (sep != null) {
          buff.appendExceptFirst(sep);
        }
        buff.append(s);
      }
      v = ValueString.get(buff.toString());
    }
    return v;
  }

  @Override
  public int getType() {
    return dataType;
  }

  @Override
  public void mapColumns(ColumnResolver resolver, int level) {
    if (on != null) {
      on.mapColumns(resolver, level);
    }
    if (groupConcatOrderList != null) {
      for (SelectOrderBy o : groupConcatOrderList) {
        o.expression.mapColumns(resolver, level);
      }
    }
    if (groupConcatSeparator != null) {
      groupConcatSeparator.mapColumns(resolver, level);
    }
  }

  @Override
  public Expression optimize(Session session) {
    if (on != null) {
      on = on.optimize(session);
      dataType = on.getType();
      scale = on.getScale();
      precision = on.getPrecision();
      displaySize = on.getDisplaySize();
    }
    if (groupConcatOrderList != null) {
      for (SelectOrderBy o : groupConcatOrderList) {
        o.expression = o.expression.optimize(session);
      }
      groupConcatSort = initOrder(session);
    }
    if (groupConcatSeparator != null) {
      groupConcatSeparator = groupConcatSeparator.optimize(session);
    }
    switch (type) {
      case GROUP_CONCAT:
        dataType = Value.STRING;
        scale = 0;
        precision = displaySize = Integer.MAX_VALUE;
        break;
      case COUNT_ALL:
      case COUNT:
        dataType = Value.LONG;
        scale = 0;
        precision = ValueLong.PRECISION;
        displaySize = ValueLong.DISPLAY_SIZE;
        break;
      case SELECTIVITY:
        dataType = Value.INT;
        scale = 0;
        precision = ValueInt.PRECISION;
        displaySize = ValueInt.DISPLAY_SIZE;
        break;
      case HISTOGRAM:
        dataType = Value.ARRAY;
        scale = 0;
        precision = displaySize = Integer.MAX_VALUE;
        break;
      case SUM:
        if (dataType == Value.BOOLEAN) {
          // example: sum(id > 3) (count the rows)
          dataType = Value.LONG;
        } else if (!DataType.supportsAdd(dataType)) {
          throw DbException.get(ErrorCode.SUM_OR_AVG_ON_WRONG_DATATYPE_1, getSQL());
        } else {
          dataType = DataType.getAddProofType(dataType);
        }
        break;
      case AVG:
        if (!DataType.supportsAdd(dataType)) {
          throw DbException.get(ErrorCode.SUM_OR_AVG_ON_WRONG_DATATYPE_1, getSQL());
        }
        break;
      case MIN:
      case MAX:
        break;
      case STDDEV_POP:
      case STDDEV_SAMP:
      case VAR_POP:
      case VAR_SAMP:
        dataType = Value.DOUBLE;
        precision = ValueDouble.PRECISION;
        displaySize = ValueDouble.DISPLAY_SIZE;
        scale = 0;
        break;
      case BOOL_AND:
      case BOOL_OR:
        dataType = Value.BOOLEAN;
        precision = ValueBoolean.PRECISION;
        displaySize = ValueBoolean.DISPLAY_SIZE;
        scale = 0;
        break;
      default:
        DbException.throwInternalError("type=" + type);
    }
    return this;
  }

  @Override
  public void setEvaluatable(TableFilter tableFilter, boolean b) {
    if (on != null) {
      on.setEvaluatable(tableFilter, b);
    }
    if (groupConcatOrderList != null) {
      for (SelectOrderBy o : groupConcatOrderList) {
        o.expression.setEvaluatable(tableFilter, b);
      }
    }
    if (groupConcatSeparator != null) {
      groupConcatSeparator.setEvaluatable(tableFilter, b);
    }
  }

  @Override
  public int getScale() {
    return scale;
  }

  @Override
  public long getPrecision() {
    return precision;
  }

  @Override
  public int getDisplaySize() {
    return displaySize;
  }

  private String getSQLGroupConcat() {
    StatementBuilder buff = new StatementBuilder("GROUP_CONCAT(");
    if (distinct) {
      buff.append("DISTINCT ");
    }
    buff.append(on.getSQL());
    if (groupConcatOrderList != null) {
      buff.append(" ORDER BY ");
      for (SelectOrderBy o : groupConcatOrderList) {
        buff.appendExceptFirst(", ");
        buff.append(o.expression.getSQL());
        if (o.descending) {
          buff.append(" DESC");
        }
      }
    }
    if (groupConcatSeparator != null) {
      buff.append(" SEPARATOR ").append(groupConcatSeparator.getSQL());
    }
    return buff.append(')').toString();
  }

  @Override
  public String getSQL() {
    String text;
    switch (type) {
      case GROUP_CONCAT:
        return getSQLGroupConcat();
      case COUNT_ALL:
        return "COUNT(*)";
      case COUNT:
        text = "COUNT";
        break;
      case SELECTIVITY:
        text = "SELECTIVITY";
        break;
      case HISTOGRAM:
        text = "HISTOGRAM";
        break;
      case SUM:
        text = "SUM";
        break;
      case MIN:
        text = "MIN";
        break;
      case MAX:
        text = "MAX";
        break;
      case AVG:
        text = "AVG";
        break;
      case STDDEV_POP:
        text = "STDDEV_POP";
        break;
      case STDDEV_SAMP:
        text = "STDDEV_SAMP";
        break;
      case VAR_POP:
        text = "VAR_POP";
        break;
      case VAR_SAMP:
        text = "VAR_SAMP";
        break;
      case BOOL_AND:
        text = "BOOL_AND";
        break;
      case BOOL_OR:
        text = "BOOL_OR";
        break;
      default:
        throw DbException.throwInternalError("type=" + type);
    }
    if (distinct) {
      return text + "(DISTINCT " + on.getSQL() + ")";
    }
    return text + StringUtils.enclose(on.getSQL());
  }

  private Index getColumnIndex() {
    if (on instanceof ExpressionColumn) {
      ExpressionColumn col = (ExpressionColumn) on;
      Column column = col.getColumn();
      TableFilter filter = col.getTableFilter();
      if (filter != null) {
        Table table = filter.getTable();
        Index index = table.getIndexForColumn(column);
        return index;
      }
    }
    return null;
  }

  @Override
  public boolean isEverything(ExpressionVisitor visitor) {
    if (visitor.getType() == ExpressionVisitor.OPTIMIZABLE_MIN_MAX_COUNT_ALL) {
      switch (type) {
        case COUNT:
          if (!distinct && on.getNullable() == Column.NOT_NULLABLE) {
            return visitor.getTable().canGetRowCount();
          }
          return false;
        case COUNT_ALL:
          return visitor.getTable().canGetRowCount();
        case MIN:
        case MAX:
          Index index = getColumnIndex();
          return index != null;
        default:
          return false;
      }
    }
    if (on != null && !on.isEverything(visitor)) {
      return false;
    }
    if (groupConcatSeparator != null && !groupConcatSeparator.isEverything(visitor)) {
      return false;
    }
    if (groupConcatOrderList != null) {
      for (int i = 0, size = groupConcatOrderList.size(); i < size; i++) {
        SelectOrderBy o = groupConcatOrderList.get(i);
        if (!o.expression.isEverything(visitor)) {
          return false;
        }
      }
    }
    return true;
  }

  @Override
  public int getCost() {
    return (on == null) ? 1 : on.getCost() + 1;
  }

  /* (non-Javadoc)
   * @see com.suning.snfddal.command.expression.Expression#exportParameters(java.util.List)
   */
  @Override
  public String exportParameters(TableFilter filter, List<Value> container) {
    String text;
    switch (type) {
      case GROUP_CONCAT:
        return getSQLGroupConcat();
      case COUNT_ALL:
        return "COUNT(*)";
      case COUNT:
        text = "COUNT";
        break;
      case SELECTIVITY:
        text = "SELECTIVITY";
        break;
      case HISTOGRAM:
        text = "HISTOGRAM";
        break;
      case SUM:
        text = "SUM";
        break;
      case MIN:
        text = "MIN";
        break;
      case MAX:
        text = "MAX";
        break;
      case AVG:
        text = "AVG";
        break;
      case STDDEV_POP:
        text = "STDDEV_POP";
        break;
      case STDDEV_SAMP:
        text = "STDDEV_SAMP";
        break;
      case VAR_POP:
        text = "VAR_POP";
        break;
      case VAR_SAMP:
        text = "VAR_SAMP";
        break;
      case BOOL_AND:
        text = "BOOL_AND";
        break;
      case BOOL_OR:
        text = "BOOL_OR";
        break;
      default:
        throw DbException.throwInternalError("type=" + type);
    }
    if (distinct) {
      return text + "(DISTINCT " + on.exportParameters(filter, container) + ")";
    }
    return text + StringUtils.enclose(on.exportParameters(filter, container));
  }
}