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); }
/** * 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(); }
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; }
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); } } } } }
// 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; } }
public ArrayList<Table> getLocalTempTables() { if (localTempTables == null) { return New.arrayList(); } return New.arrayList(localTempTables.values()); }
/** * 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); } } } } }
/** * 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); } }
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); } }