Example #1
0
 private void readHeader() throws IOException {
   ArrayList<String> list = New.arrayList();
   while (true) {
     String v = readValue();
     if (v == null) {
       if (endOfLine) {
         if (endOfFile || list.size() > 0) {
           break;
         }
       } else {
         v = "COLUMN" + list.size();
         list.add(v);
       }
     } else {
       if (v.length() == 0) {
         v = "COLUMN" + list.size();
       } else if (!caseSensitiveColumnNames && isSimpleColumnName(v)) {
         v = v.toUpperCase();
       }
       list.add(v);
       if (endOfLine) {
         break;
       }
     }
   }
   columnNames = new String[list.size()];
   list.toArray(columnNames);
 }
Example #2
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);
 }
Example #3
0
 /**
  * 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);
   }
 }
Example #4
0
 /**
  * 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 (undoLog.size() > 0) {
     // commit the rows when using MVCC
     if (database.isMultiVersion()) {
       ArrayList<Row> 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;
     }
   }
   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();
       v.close();
     }
     unlinkLobMap = null;
   }
   unlockAll();
 }
Example #5
0
 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;
 }
Example #6
0
 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);
         }
       }
     }
   }
 }
Example #7
0
// TODO 合并MVStore的更新(2013-04-09、2013-05-04)
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++;
  protected final Database database;
  private ConnectionInfo connectionInfo;
  private final User user;
  private final int id;
  private final ArrayList<Table> 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<String, Integer> 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 undoLogEnabled = true;
  private boolean redoLogBinary = true;
  private boolean autoCommitAtTransactionEnd;
  private String currentTransactionName;
  private volatile long cancelAt;
  protected boolean closed;
  private final long sessionStart = System.currentTimeMillis();
  private long transactionStart;
  private long currentCommandStart;
  private HashMap<String, Value> variables;
  private HashSet<ResultInterface> temporaryResults;
  private int queryTimeout;
  private boolean commitOrRollbackDisabled;
  private Table waitForLock;
  private int modificationId;
  private int objectId;
  protected final int queryCacheSize;
  protected SmallLRUCache<String, Command> queryCache;

  private boolean isRoot = true;

  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, LobStorage.TABLE_ID_SESSION_VARIABLE);
      old = variables.put(name, value);
    }
    if (old != null) {
      // close the old value (in case it is a lob)
      old.unlink();
      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);
      }
    }
  }

  public boolean getAutoCommit() {
    return autoCommit;
  }

  public User getUser() {
    return user;
  }

  public void setAutoCommit(boolean b) {
    autoCommit = b;
  }

  public int getLockTimeout() {
    return lockTimeout;
  }

  public void setLockTimeout(int lockTimeout) {
    this.lockTimeout = lockTimeout;
  }

  /**
   * 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 = createParser();
    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) {
    return prepareCommand(sql);
  }

  @Override
  public Command prepareCommand(String sql, int fetchSize) {
    return prepareCommand(sql);
  }

  public synchronized Command prepareCommand(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);
      } else {
        command = queryCache.get(sql);
        if (command != null && command.canReuse()) {
          command.reuse();
          return command;
        }
      }
    }
    Parser parser = createParser();
    command = parser.prepareCommand(sql);
    if (queryCache != null) {
      if (command.isCacheable()) {
        queryCache.put(sql, command);
      }
    }
    return command;
  }

  public Database getDatabase() {
    return database;
  }

  public int getPowerOffCount() {
    return database.getPowerOffCount();
  }

  public void setPowerOffCount(int count) {
    database.setPowerOffCount(count);
  }

  public void commit(boolean ddl, String allLocalTransactionNames) {
    commit(ddl);
  }

  /**
   * 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 (undoLog.size() > 0) {
      // commit the rows when using MVCC
      if (database.isMultiVersion()) {
        ArrayList<Row> 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;
      }
    }
    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();
        v.close();
      }
      unlinkLobMap = null;
    }
    unlockAll();
  }

  private void checkCommitRollback() {
    if (commitOrRollbackDisabled && locks.size() > 0) {
      throw DbException.get(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED);
    }
  }

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

  /**
   * Partially roll back the current transaction.
   *
   * @param index the position to which should be rolled back
   * @param trimToSize if the list should be trimmed
   */
  public void rollbackTo(int index, boolean trimToSize) {
    while (undoLog.size() > index) {
      UndoLogRecord entry = undoLog.getLast();
      entry.undo(this);
      undoLog.removeLast(trimToSize);
    }
    if (savepoints != null) {
      String[] names = new String[savepoints.size()];
      savepoints.keySet().toArray(names);
      for (String name : names) {
        Integer savepointIndex = savepoints.get(name);
        if (savepointIndex.intValue() > index) {
          savepoints.remove(name);
        }
      }
    }
  }

  public int getUndoLogPos() {
    return undoLog.size();
  }

  public int getId() {
    return id;
  }

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

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

  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;
  }

  private 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();
    }
    savepoints.put(name, getUndoLogPos());
  }

  /**
   * 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);
    }
    Integer savepointIndex = savepoints.get(name);
    if (savepointIndex == null) {
      throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name);
    }
    int i = savepointIndex.intValue();
    rollbackTo(i, false);
  }

  /**
   * Prepare the given transaction.
   *
   * @param transactionName the name of the transaction
   */
  public void prepareCommit(String 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<InDoubtTransaction> list = database.getInDoubtTransactions();
      int state = commit ? InDoubtTransaction.COMMIT : InDoubtTransaction.ROLLBACK;
      boolean found = false;
      if (list != null) {
        for (InDoubtTransaction p : list) {
          if (p.getTransaction().equals(transactionName)) {
            p.setState(state);
            found = true;
            break;
          }
        }
      }
      if (!found) {
        throw DbException.get(ErrorCode.TRANSACTION_NOT_FOUND_1, transactionName);
      }
    }
  }

  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);
  }

  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;
  }

  public int hashCode() {
    return serialId;
  }

  public String toString() {
    return "#" + serialId + " (user: "******")";
  }

  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<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;
  }

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

  /**
   * Close all temporary result set. This also deletes all temporary files held by the result sets.
   */
  public 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;
  }

  public void setWaitForLock(Table table) {
    this.waitForLock = table;
  }

  public Table getWaitForLock() {
    return waitForLock;
  }

  public int getModificationId() {
    return modificationId;
  }

  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;
      }
    }
  }

  public void afterWriting() {
    database.afterWriting();
  }

  public SessionInterface reconnect(boolean write) {
    readSessionState();
    close();
    Session newSession = (Session) database.getDatabaseEngine().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 (undoLog.size() == 0 || !database.isPersistent()) {
      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;
  }

  public SubqueryResult createSubqueryResult(Query query, int maxrows) {
    return new SubqueryResult(query, maxrows);
  }

  public Parser createParser() {
    return new Parser(this);
  }

  public Insert createInsert() {
    return new Insert(this);
  }

  public Sequence createSequence(Schema schema, int id, String name, boolean belongsToTable) {
    return new Sequence(schema, id, name, belongsToTable);
  }

  public boolean isRoot() {
    return isRoot;
  }

  public void setRoot(boolean isRoot) {
    this.isRoot = isRoot;
  }

  public String getHostAndPort() {
    return null;
  }

  public Transaction getTransaction() {
    return null;
  }
}
Example #8
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;
 }
Example #9
0
 public HashMap<String, Index> getLocalTempTableIndexes() {
   if (localTempTableIndexes == null) {
     return New.hashMap();
   }
   return localTempTableIndexes;
 }
Example #10
0
 public ArrayList<Table> getLocalTempTables() {
   if (localTempTables == null) {
     return New.arrayList();
   }
   return New.arrayList(localTempTables.values());
 }
Example #11
0
/**
 * The client side part of a session when using the server mode. This object communicates with a
 * Session on the server side.
 */
public class SessionRemote extends SessionWithState implements DataHandler {

  public static final int SESSION_PREPARE = 0;
  public static final int SESSION_CLOSE = 1;
  public static final int COMMAND_EXECUTE_QUERY = 2;
  public static final int COMMAND_EXECUTE_UPDATE = 3;
  public static final int COMMAND_CLOSE = 4;
  public static final int RESULT_FETCH_ROWS = 5;
  public static final int RESULT_RESET = 6;
  public static final int RESULT_CLOSE = 7;
  public static final int COMMAND_COMMIT = 8;
  public static final int CHANGE_ID = 9;
  public static final int COMMAND_GET_META_DATA = 10;
  public static final int SESSION_PREPARE_READ_PARAMS = 11;
  public static final int SESSION_SET_ID = 12;
  public static final int SESSION_CANCEL_STATEMENT = 13;
  public static final int SESSION_CHECK_KEY = 14;
  public static final int SESSION_SET_AUTOCOMMIT = 15;
  public static final int SESSION_UNDO_LOG_POS = 16;
  public static final int LOB_READ = 17;

  public static final int COMMAND_EXECUTE_DISTRIBUTED_QUERY = 100;
  public static final int COMMAND_EXECUTE_DISTRIBUTED_UPDATE = 101;
  public static final int COMMAND_EXECUTE_DISTRIBUTED_COMMIT = 102;
  public static final int COMMAND_EXECUTE_DISTRIBUTED_ROLLBACK = 103;

  public static final int COMMAND_EXECUTE_DISTRIBUTED_SAVEPOINT_ADD = 104;
  public static final int COMMAND_EXECUTE_DISTRIBUTED_SAVEPOINT_ROLLBACK = 105;

  public static final int COMMAND_EXECUTE_BATCH_UPDATE_STATEMENT = 120;
  public static final int COMMAND_EXECUTE_BATCH_UPDATE_PREPAREDSTATEMENT = 121;

  public static final int STATUS_ERROR = 0;
  public static final int STATUS_OK = 1;
  public static final int STATUS_CLOSED = 2;
  public static final int STATUS_OK_STATE_CHANGED = 3;

  private static final Random random = new Random(System.currentTimeMillis());

  private SessionFactory sessionFactory;

  private TraceSystem traceSystem;
  private Trace trace;
  private ArrayList<Transfer> transferList = New.arrayList();
  private int nextId;
  private boolean autoCommit = true;
  private CommandInterface autoCommitFalse, autoCommitTrue;
  private ConnectionInfo connectionInfo;
  private String databaseName;
  private String cipher;
  private byte[] fileEncryptionKey;
  private Object lobSyncObject = new Object();
  private String sessionId;
  private int clientVersion;
  private boolean autoReconnect;
  private int lastReconnect;
  private SessionInterface embedded;
  private DatabaseEventListener eventListener;
  private LobStorage lobStorage;
  private boolean cluster;
  private Transaction transaction;

  public SessionRemote(ConnectionInfo ci) {
    this.connectionInfo = ci;
  }

  private Transfer initTransfer(ConnectionInfo ci, String db, String server) throws IOException {
    Socket socket = NetUtils.createSocket(server, Constants.DEFAULT_TCP_PORT, ci.isSSL());
    Transfer trans = new Transfer(this, socket);
    trans.setSSL(ci.isSSL());
    trans.init();
    trans.writeInt(Constants.TCP_PROTOCOL_VERSION_6);
    trans.writeInt(Constants.TCP_PROTOCOL_VERSION_12);
    trans.writeString(db);
    trans.writeString(ci.getOriginalURL());
    trans.writeString(ci.getUserName());
    trans.writeBytes(ci.getUserPasswordHash());
    trans.writeBytes(ci.getFilePasswordHash());
    String[] keys = ci.getKeys();
    trans.writeInt(keys.length);
    for (String key : keys) {
      trans.writeString(key).writeString(ci.getProperty(key));
    }
    try {
      done(trans);
      clientVersion = trans.readInt();
      trans.setVersion(clientVersion);
      trans.writeInt(SessionRemote.SESSION_SET_ID);
      trans.writeString(sessionId);
      done(trans);
    } catch (DbException e) {
      trans.close();
      throw e;
    }
    autoCommit = true;
    return trans;
  }

  public int getUndoLogPos() {
    if (clientVersion < Constants.TCP_PROTOCOL_VERSION_10) {
      return 1;
    }
    for (int i = 0, count = 0; i < transferList.size(); i++) {
      Transfer transfer = transferList.get(i);
      try {
        traceOperation("SESSION_UNDO_LOG_POS", 0);
        transfer.writeInt(SessionRemote.SESSION_UNDO_LOG_POS);
        done(transfer);
        return transfer.readInt();
      } catch (IOException e) {
        removeServer(e, i--, ++count);
      }
    }
    return 1;
  }

  public void cancel() {
    // this method is called when closing the connection
    // the statement that is currently running is not canceled in this case
    // however Statement.cancel is supported
  }

  /**
   * Cancel the statement with the given id.
   *
   * @param id the statement id
   */
  public void cancelStatement(int id) {
    for (Transfer transfer : transferList) {
      try {
        Transfer trans = transfer.openNewConnection();
        trans.init();
        trans.writeInt(clientVersion);
        trans.writeInt(clientVersion);
        trans.writeString(null);
        trans.writeString(null);
        trans.writeString(sessionId);
        trans.writeInt(SessionRemote.SESSION_CANCEL_STATEMENT);
        trans.writeInt(id);
        trans.close();
      } catch (IOException e) {
        trace.debug(e, "could not cancel statement");
      }
    }
  }

  private void checkClusterDisableAutoCommit(String serverList) {
    if (autoCommit && transferList.size() > 1) {
      setAutoCommitSend(false);
      CommandInterface c = prepareCommand("SET CLUSTER " + serverList, Integer.MAX_VALUE);
      // this will set autoCommit to false
      c.executeUpdate();
      // so we need to switch it on
      autoCommit = true;
      cluster = true;
    }
  }

  public boolean getAutoCommit() {
    return autoCommit;
  }

  public void setAutoCommit(boolean autoCommit) {
    if (!cluster) {
      setAutoCommitSend(autoCommit);
    }
    this.autoCommit = autoCommit;
  }

  public void setAutoCommitFromServer(boolean autoCommit) {
    if (cluster) {
      if (autoCommit) {
        // the user executed SET AUTOCOMMIT TRUE
        setAutoCommitSend(false);
        this.autoCommit = true;
      }
    } else {
      this.autoCommit = autoCommit;
    }
  }

  private void setAutoCommitSend(boolean autoCommit) {
    if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_8) {
      for (int i = 0, count = 0; i < transferList.size(); i++) {
        Transfer transfer = transferList.get(i);
        try {
          traceOperation("SESSION_SET_AUTOCOMMIT", autoCommit ? 1 : 0);
          transfer.writeInt(SessionRemote.SESSION_SET_AUTOCOMMIT).writeBoolean(autoCommit);
          done(transfer);
        } catch (IOException e) {
          removeServer(e, i--, ++count);
        }
      }
    } else {
      if (autoCommit) {
        if (autoCommitTrue == null) {
          autoCommitTrue = prepareCommand("SET AUTOCOMMIT TRUE", Integer.MAX_VALUE);
        }
        autoCommitTrue.executeUpdate();
      } else {
        if (autoCommitFalse == null) {
          autoCommitFalse = prepareCommand("SET AUTOCOMMIT FALSE", Integer.MAX_VALUE);
        }
        autoCommitFalse.executeUpdate();
      }
    }
  }

  /** Calls COMMIT if the session is in cluster mode. */
  public void autoCommitIfCluster() {
    if (autoCommit && cluster) {
      // server side auto commit is off because of race conditions
      // (update set id=1 where id=0, but update set id=2 where id=0 is
      // faster)
      for (int i = 0, count = 0; i < transferList.size(); i++) {
        Transfer transfer = transferList.get(i);
        try {
          traceOperation("COMMAND_COMMIT", 0);
          transfer.writeInt(SessionRemote.COMMAND_COMMIT);
          done(transfer);
        } catch (IOException e) {
          removeServer(e, i--, ++count);
        }
      }
    }
  }

  private String getFilePrefix(String dir) {
    StringBuilder buff = new StringBuilder(dir);
    buff.append('/');
    for (int i = 0; i < databaseName.length(); i++) {
      char ch = databaseName.charAt(i);
      if (Character.isLetterOrDigit(ch)) {
        buff.append(ch);
      } else {
        buff.append('_');
      }
    }
    return buff.toString();
  }

  public int getPowerOffCount() {
    return 0;
  }

  public void setPowerOffCount(int count) {
    throw DbException.getUnsupportedException("remote");
  }

  /**
   * Open a new (remote or embedded) session.
   *
   * @param openNew whether to open a new session in any case
   * @return the session
   */
  public SessionInterface connectEmbeddedOrServer(boolean openNew) {
    ConnectionInfo ci = connectionInfo;
    if (ci.isRemote() || ci.isDynamic()) {
      connectServer(ci);
      return this;
    }
    // create the session using reflection,
    // so that the JDBC layer can be compiled without it
    boolean autoServerMode = Boolean.valueOf(ci.getProperty("AUTO_SERVER", "false")).booleanValue();
    ConnectionInfo backup = null;
    try {
      if (autoServerMode) {
        backup = (ConnectionInfo) ci.clone();
        connectionInfo = (ConnectionInfo) ci.clone();
      }
      if (openNew) {
        ci.setProperty("OPEN_NEW", "true");
      }
      if (sessionFactory == null) {
        sessionFactory = ci.getSessionFactory();
      }
      return sessionFactory.createSession(ci);
    } catch (Exception re) {
      DbException e = DbException.convert(re);
      if (e.getErrorCode() == ErrorCode.DATABASE_ALREADY_OPEN_1) {
        if (autoServerMode) {
          String serverKey = ((JdbcSQLException) e.getSQLException()).getSQL();
          if (serverKey != null) {
            backup.setServerKey(serverKey);
            // OPEN_NEW must be removed now, otherwise
            // opening a session with AUTO_SERVER fails
            // if another connection is already open
            backup.removeProperty("OPEN_NEW", null);
            connectServer(backup);
            return this;
          }
        }
      }
      throw e;
    }
  }

  private void connectServer(ConnectionInfo ci) {
    String name = ci.getName();
    if (name.startsWith("//")) {
      name = name.substring("//".length());
    }
    int idx = name.indexOf('/');
    if (idx < 0) {
      throw ci.getFormatException();
    }
    databaseName = name.substring(idx + 1);
    String server = name.substring(0, idx);
    traceSystem = new TraceSystem(null);
    String traceLevelFile = ci.getProperty(SetTypes.TRACE_LEVEL_FILE, null);
    if (traceLevelFile != null) {
      int level = Integer.parseInt(traceLevelFile);
      String prefix = getFilePrefix(SysProperties.CLIENT_TRACE_DIRECTORY);
      try {
        traceSystem.setLevelFile(level);
        if (level > 0) {
          String file = FileUtils.createTempFile(prefix, Constants.SUFFIX_TRACE_FILE, false, false);
          traceSystem.setFileName(file);
        }
      } catch (IOException e) {
        throw DbException.convertIOException(e, prefix);
      }
    }
    String traceLevelSystemOut = ci.getProperty(SetTypes.TRACE_LEVEL_SYSTEM_OUT, null);
    if (traceLevelSystemOut != null) {
      int level = Integer.parseInt(traceLevelSystemOut);
      traceSystem.setLevelSystemOut(level);
    }
    trace = traceSystem.getTrace(Trace.JDBC);
    String serverList = null;
    if (server.indexOf(',') >= 0) {
      serverList = StringUtils.quoteStringSQL(server);
      ci.setProperty("CLUSTER", Constants.CLUSTERING_ENABLED);
    }
    autoReconnect = Boolean.valueOf(ci.getProperty("AUTO_RECONNECT", "false")).booleanValue();
    // AUTO_SERVER implies AUTO_RECONNECT
    boolean autoServer = Boolean.valueOf(ci.getProperty("AUTO_SERVER", "false")).booleanValue();
    if (autoServer && serverList != null) {
      throw DbException.getUnsupportedException("autoServer && serverList != null");
    }
    autoReconnect |= autoServer;
    if (autoReconnect) {
      String className = ci.getProperty("DATABASE_EVENT_LISTENER");
      if (className != null) {
        className = StringUtils.trim(className, true, true, "'");
        try {
          eventListener = (DatabaseEventListener) Utils.loadUserClass(className).newInstance();
        } catch (Throwable e) {
          throw DbException.convert(e);
        }
      }
    }
    cipher = ci.getProperty("CIPHER");
    if (cipher != null) {
      fileEncryptionKey = MathUtils.secureRandomBytes(32);
    }

    String[] servers;
    if (ci.isDynamic()) {
      servers = new String[] {ci.getOnlineServer(server)};
    } else {
      servers = StringUtils.arraySplit(server, ',', true);

      if (servers.length > 1 && !ci.removeProperty("USE_H2_CLUSTER_MODE", false))
        servers = new String[] {servers[random.nextInt(servers.length)]};
    }
    int len = servers.length;
    transferList.clear();
    sessionId = StringUtils.convertBytesToHex(MathUtils.secureRandomBytes(32));
    // TODO cluster: support more than 2 connections
    boolean switchOffCluster = false;
    try {
      for (int i = 0; i < len; i++) {
        String s = servers[i];
        try {
          Transfer trans = initTransfer(ci, databaseName, s);
          transferList.add(trans);
        } catch (IOException e) {
          if (len == 1) {
            throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, e, e + ": " + s);
          }
          switchOffCluster = true;
        }
      }
      checkClosed();
      if (switchOffCluster) {
        switchOffCluster();
      }
      checkClusterDisableAutoCommit(serverList);
    } catch (DbException e) {
      traceSystem.close();
      throw e;
    }
  }

  private void switchOffCluster() {
    CommandInterface ci = prepareCommand("SET CLUSTER ''", Integer.MAX_VALUE);
    ci.executeUpdate();
  }

  /**
   * Remove a server from the list of cluster nodes and disables the cluster mode.
   *
   * @param e the exception (used for debugging)
   * @param i the index of the server to remove
   * @param count the retry count index
   */
  public void removeServer(IOException e, int i, int count) {
    transferList.remove(i);
    if (transferList.size() == 0 && autoReconnect(count)) {
      return;
    }
    checkClosed();
    switchOffCluster();
  }

  public synchronized CommandInterface prepareCommand(String sql, int fetchSize) {
    checkClosed();
    return new CommandRemote(this, transferList, sql, fetchSize);
  }

  /**
   * Automatically re-connect if necessary and if configured to do so.
   *
   * @param count the retry count index
   * @return true if reconnected
   */
  private boolean autoReconnect(int count) {
    if (!isClosed()) {
      return false;
    }
    if (!autoReconnect) {
      return false;
    }
    if (!cluster && !autoCommit) {
      return false;
    }
    if (count > SysProperties.MAX_RECONNECT) {
      return false;
    }
    lastReconnect++;
    while (true) {
      try {
        embedded = connectEmbeddedOrServer(false);
        break;
      } catch (DbException e) {
        if (e.getErrorCode() != ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE) {
          throw e;
        }
        // exclusive mode: re-try endlessly
        try {
          Thread.sleep(500);
        } catch (Exception e2) {
          // ignore
        }
      }
    }
    if (embedded == this) {
      // connected to a server somewhere else
      embedded = null;
    } else {
      // opened an embedded connection now -
      // must connect to this database in server mode
      // unfortunately
      connectEmbeddedOrServer(true);
    }
    recreateSessionState();
    if (eventListener != null) {
      eventListener.setProgress(
          DatabaseEventListener.STATE_RECONNECTED,
          databaseName,
          count,
          SysProperties.MAX_RECONNECT);
    }
    return true;
  }

  /**
   * Check if this session is closed and throws an exception if so.
   *
   * @throws DbException if the session is closed
   */
  public void checkClosed() {
    if (isClosed()) {
      throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "session closed");
    }
  }

  public void close() {
    RuntimeException closeError = null;
    if (transferList != null) {
      synchronized (this) {
        for (Transfer transfer : transferList) {
          try {
            traceOperation("SESSION_CLOSE", 0);
            transfer.writeInt(SessionRemote.SESSION_CLOSE);
            done(transfer);
            transfer.close();
          } catch (RuntimeException e) {
            trace.error(e, "close");
            closeError = e;
          } catch (Exception e) {
            trace.error(e, "close");
          }
        }
      }
      transferList = null;
    }
    traceSystem.close();
    if (embedded != null) {
      embedded.close();
      embedded = null;
    }
    if (closeError != null) {
      throw closeError;
    }
  }

  public Trace getTrace() {
    return traceSystem.getTrace(Trace.JDBC);
  }

  public int getNextId() {
    return nextId++;
  }

  public int getCurrentId() {
    return nextId;
  }

  /**
   * Called to flush the output after data has been sent to the server and just before receiving
   * data. This method also reads the status code from the server and throws any exception the
   * server sent.
   *
   * @param transfer the transfer object
   * @throws DbException if the server sent an exception
   * @throws IOException if there is a communication problem between client and server
   */
  public void done(Transfer transfer) throws IOException {
    // 正常来讲不会出现这种情况,如果出现了,说明存在bug,找出为什么transfer的输入流没正常读完的原因
    if (transfer.available() > 0) {
      throw DbException.throwInternalError(
          "before transfer flush, the available bytes was " + transfer.available());
    }

    transfer.flush();
    int status = transfer.readInt();
    if (status == STATUS_ERROR) {
      parseError(transfer);
    } else if (status == STATUS_CLOSED) {
      transferList = null;
    } else if (status == STATUS_OK_STATE_CHANGED) {
      sessionStateChanged = true;
    } else if (status == STATUS_OK) {
      // ok
    } else {
      throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "unexpected status " + status);
    }
  }

  public void parseError(Transfer transfer) throws IOException {
    String sqlstate = transfer.readString();
    String message = transfer.readString();
    String sql = transfer.readString();
    int errorCode = transfer.readInt();
    String stackTrace = transfer.readString();
    JdbcSQLException s = new JdbcSQLException(message, sql, sqlstate, errorCode, null, stackTrace);
    if (errorCode == ErrorCode.CONNECTION_BROKEN_1) {
      // allow re-connect
      IOException e = new IOException(s.toString());
      e.initCause(s);
      throw e;
    }
    throw DbException.convert(s);
  }

  /**
   * Returns true if the connection was opened in cluster mode.
   *
   * @return true if it is
   */
  public boolean isClustered() {
    return cluster;
  }

  public boolean isClosed() {
    return transferList == null || transferList.size() == 0;
  }

  /**
   * Write the operation to the trace system if debug trace is enabled.
   *
   * @param operation the operation performed
   * @param id the id of the operation
   */
  public void traceOperation(String operation, int id) {
    if (trace.isDebugEnabled()) {
      trace.debug("{0} {1}", operation, id);
    }
  }

  public void checkPowerOff() {
    // ok
  }

  public void checkWritingAllowed() {
    // ok
  }

  public String getDatabasePath() {
    return "";
  }

  public String getLobCompressionAlgorithm(int type) {
    return null;
  }

  public int getMaxLengthInplaceLob() {
    return SysProperties.LOB_CLIENT_MAX_SIZE_MEMORY;
  }

  public FileStore openFile(String name, String mode, boolean mustExist) {
    if (mustExist && !FileUtils.exists(name)) {
      throw DbException.get(ErrorCode.FILE_NOT_FOUND_1, name);
    }
    FileStore store;
    if (cipher == null) {
      store = FileStore.open(this, name, mode);
    } else {
      store = FileStore.open(this, name, mode, cipher, fileEncryptionKey, 0);
    }
    store.setCheckedWriting(false);
    try {
      store.init();
    } catch (DbException e) {
      store.closeSilently();
      throw e;
    }
    return store;
  }

  public DataHandler getDataHandler() {
    return this;
  }

  public Object getLobSyncObject() {
    return lobSyncObject;
  }

  public SmallLRUCache<String, String[]> getLobFileListCache() {
    return null;
  }

  public int getLastReconnect() {
    return lastReconnect;
  }

  public TempFileDeleter getTempFileDeleter() {
    return TempFileDeleter.getInstance();
  }

  public boolean isReconnectNeeded(boolean write) {
    return false;
  }

  public SessionInterface reconnect(boolean write) {
    return this;
  }

  public void afterWriting() {
    // nothing to do
  }

  public LobStorage getLobStorage() {
    if (lobStorage == null) {
      lobStorage = new LobStorage(this);
    }
    return lobStorage;
  }

  public Connection getLobConnection() {
    return null;
  }

  public synchronized int readLob(
      long lobId, byte[] hmac, long offset, byte[] buff, int off, int length) {
    for (int i = 0, count = 0; i < transferList.size(); i++) {
      Transfer transfer = transferList.get(i);
      try {
        traceOperation("LOB_READ", (int) lobId);
        transfer.writeInt(SessionRemote.LOB_READ);
        transfer.writeLong(lobId);
        if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_12) {
          transfer.writeBytes(hmac);
        }
        transfer.writeLong(offset);
        transfer.writeInt(length);
        done(transfer);
        length = transfer.readInt();
        if (length <= 0) {
          return length;
        }
        transfer.readBytes(buff, off, length);
        return length;
      } catch (IOException e) {
        removeServer(e, i--, ++count);
      }
    }
    return 1;
  }

  public synchronized void commitTransaction(String allLocalTransactionNames) {
    checkClosed();
    for (int i = 0, count = 0; i < transferList.size(); i++) {
      Transfer transfer = transferList.get(i);
      try {
        transfer
            .writeInt(SessionRemote.COMMAND_EXECUTE_DISTRIBUTED_COMMIT)
            .writeString(allLocalTransactionNames);
        done(transfer);
      } catch (IOException e) {
        removeServer(e, i--, ++count);
      }
    }
  }

  public synchronized void rollbackTransaction() {
    checkClosed();
    for (int i = 0, count = 0; i < transferList.size(); i++) {
      Transfer transfer = transferList.get(i);
      try {
        transfer.writeInt(SessionRemote.COMMAND_EXECUTE_DISTRIBUTED_ROLLBACK);
        done(transfer);
      } catch (IOException e) {
        removeServer(e, i--, ++count);
      }
    }
  }

  public synchronized void addSavepoint(String name) {
    checkClosed();
    for (int i = 0, count = 0; i < transferList.size(); i++) {
      Transfer transfer = transferList.get(i);
      try {
        transfer
            .writeInt(SessionRemote.COMMAND_EXECUTE_DISTRIBUTED_SAVEPOINT_ADD)
            .writeString(name);
        done(transfer);
      } catch (IOException e) {
        removeServer(e, i--, ++count);
      }
    }
  }

  public synchronized void rollbackToSavepoint(String name) {
    checkClosed();
    for (int i = 0, count = 0; i < transferList.size(); i++) {
      Transfer transfer = transferList.get(i);
      try {
        transfer
            .writeInt(SessionRemote.COMMAND_EXECUTE_DISTRIBUTED_SAVEPOINT_ROLLBACK)
            .writeString(name);
        done(transfer);
      } catch (IOException e) {
        removeServer(e, i--, ++count);
      }
    }
  }

  public void setTransaction(Transaction transaction) {
    this.transaction = transaction;
  }

  public Transaction getTransaction() {
    return transaction;
  }

  public synchronized FrontendBatchCommand getFrontendBatchCommand(
      ArrayList<String> batchCommands) {
    checkClosed();
    return new FrontendBatchCommand(this, transferList, batchCommands);
  }

  public synchronized FrontendBatchCommand getFrontendBatchCommand(
      CommandInterface preparedCommand, ArrayList<Value[]> batchParameters) {
    checkClosed();
    return new FrontendBatchCommand(this, transferList, preparedCommand, batchParameters);
  }

  public String getURL() {
    return connectionInfo.getURL();
  }

  public synchronized void checkTransfers() {
    if (transferList != null) {
      for (int i = 0; i < transferList.size(); i++) {
        Transfer transfer = transferList.get(i);

        try {
          if (transfer.available() > 0)
            throw DbException.throwInternalError(
                "the transfer available bytes was " + transfer.available());
        } catch (IOException e) {
          throw DbException.convert(e);
        }
      }
    }
  }
}
Example #12
0
 /**
  * Add a value to this aggregate.
  *
  * @param database the database
  * @param distinct if the calculation should be distinct
  * @param v the value
  */
 void add(Database database, boolean distinct, Value v) {
   if (aggregateType == Aggregate.SELECTIVITY) {
     count++;
     if (distinctHashes == null) {
       distinctHashes = new IntIntHashMap();
     }
     int size = distinctHashes.size();
     if (size > Constants.SELECTIVITY_DISTINCT_COUNT) {
       distinctHashes = new IntIntHashMap();
       m2 += size;
     }
     int hash = v.hashCode();
     // the value -1 is not supported
     distinctHashes.put(hash, 1);
     return;
   } else if (aggregateType == Aggregate.COUNT_ALL) {
     count++;
     return;
   } else if (aggregateType == Aggregate.HISTOGRAM) {
     if (distinctValues == null) {
       distinctValues = ValueHashMap.newInstance();
     }
     AggregateData a = distinctValues.get(v);
     if (a == null) {
       if (distinctValues.size() < Constants.SELECTIVITY_DISTINCT_COUNT) {
         a = new AggregateData(Aggregate.HISTOGRAM, dataType);
         distinctValues.put(v, a);
       }
     }
     if (a != null) {
       a.count++;
     }
     return;
   }
   if (v == ValueNull.INSTANCE) {
     return;
   }
   count++;
   if (distinct) {
     if (distinctValues == null) {
       distinctValues = ValueHashMap.newInstance();
     }
     distinctValues.put(v, this);
     return;
   }
   switch (aggregateType) {
     case Aggregate.COUNT:
     case Aggregate.HISTOGRAM:
       return;
     case Aggregate.SUM:
       if (value == null) {
         value = v.convertTo(dataType);
       } else {
         v = v.convertTo(value.getType());
         value = value.add(v);
       }
       break;
     case Aggregate.AVG:
       if (value == null) {
         value = v.convertTo(DataType.getAddProofType(dataType));
       } else {
         v = v.convertTo(value.getType());
         value = value.add(v);
       }
       break;
     case Aggregate.MIN:
       if (value == null || database.compare(v, value) < 0) {
         value = v;
       }
       break;
     case Aggregate.MAX:
       if (value == null || database.compare(v, value) > 0) {
         value = v;
       }
       break;
     case Aggregate.GROUP_CONCAT:
       {
         if (list == null) {
           list = New.arrayList();
         }
         list.add(v);
         break;
       }
     case Aggregate.STDDEV_POP:
     case Aggregate.STDDEV_SAMP:
     case Aggregate.VAR_POP:
     case Aggregate.VAR_SAMP:
       {
         // Using Welford's method, see also
         // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
         // http://www.johndcook.com/standard_deviation.html
         double x = v.getDouble();
         if (count == 1) {
           mean = x;
           m2 = 0;
         } else {
           double delta = x - mean;
           mean += delta / count;
           m2 += delta * (x - mean);
         }
         break;
       }
     case Aggregate.BOOL_AND:
       v = v.convertTo(Value.BOOLEAN);
       if (value == null) {
         value = v;
       } else {
         value =
             ValueBoolean.get(value.getBoolean().booleanValue() && v.getBoolean().booleanValue());
       }
       break;
     case Aggregate.BOOL_OR:
       v = v.convertTo(Value.BOOLEAN);
       if (value == null) {
         value = v;
       } else {
         value =
             ValueBoolean.get(value.getBoolean().booleanValue() || v.getBoolean().booleanValue());
       }
       break;
     default:
       DbException.throwInternalError("type=" + aggregateType);
   }
 }
Example #13
0
 void merge(Database database, boolean distinct, Value v) {
   if (aggregateType == Aggregate.COUNT || aggregateType == Aggregate.COUNT_ALL) {
     count += v.getLong();
     return;
   } else if (aggregateType == Aggregate.HISTOGRAM) {
     if (distinctValues == null) {
       distinctValues = ValueHashMap.newInstance();
     }
     AggregateData a = distinctValues.get(v);
     if (a == null) {
       if (distinctValues.size() < Constants.SELECTIVITY_DISTINCT_COUNT) {
         a = new AggregateData(Aggregate.HISTOGRAM, dataType);
         distinctValues.put(v, a);
       }
     }
     if (a != null) {
       a.count++;
     }
     return;
   }
   if (v == ValueNull.INSTANCE) {
     return;
   }
   count++;
   if (distinct) {
     if (distinctValues == null) {
       distinctValues = ValueHashMap.newInstance();
     }
     distinctValues.put(v, this);
     return;
   }
   switch (aggregateType) {
     case Aggregate.COUNT:
     case Aggregate.HISTOGRAM:
       return;
     case Aggregate.SUM:
     case Aggregate.SELECTIVITY:
       if (value == null) {
         value = v.convertTo(dataType);
       } else {
         v = v.convertTo(value.getType());
         value = value.add(v);
       }
       break;
     case Aggregate.AVG:
       if (value == null) {
         value = v.convertTo(DataType.getAddProofType(dataType));
       } else {
         // AVG聚合函数merge的次数不会超过1
         DbException.throwInternalError("type=" + aggregateType);
       }
       break;
     case Aggregate.MIN:
       if (value == null || database.compare(v, value) < 0) {
         value = v;
       }
       break;
     case Aggregate.MAX:
       if (value == null || database.compare(v, value) > 0) {
         value = v;
       }
       break;
     case Aggregate.GROUP_CONCAT:
       {
         if (list == null) {
           list = New.arrayList();
         }
         list.add(v);
         break;
       }
     case Aggregate.STDDEV_POP:
     case Aggregate.STDDEV_SAMP:
     case Aggregate.VAR_POP:
     case Aggregate.VAR_SAMP:
       {
         v = v.convertTo(Value.DOUBLE);
         if (value == null) {
           value = v;
         } else {
           // 这4种聚合函数merge的次数不会超过1
           DbException.throwInternalError("type=" + aggregateType);
         }
         break;
       }
     case Aggregate.BOOL_AND:
       v = v.convertTo(Value.BOOLEAN);
       if (value == null) {
         value = v;
       } else {
         value =
             ValueBoolean.get(value.getBoolean().booleanValue() && v.getBoolean().booleanValue());
       }
       break;
     case Aggregate.BOOL_OR:
       v = v.convertTo(Value.BOOLEAN);
       if (value == null) {
         value = v;
       } else {
         value =
             ValueBoolean.get(value.getBoolean().booleanValue() || v.getBoolean().booleanValue());
       }
       break;
     default:
       DbException.throwInternalError("type=" + aggregateType);
   }
 }