@Override public Value createBlob(InputStream in, long maxLength) { init(); int type = Value.BLOB; if (maxLength < 0) { maxLength = Long.MAX_VALUE; } int max = (int) Math.min(maxLength, database.getMaxLengthInplaceLob()); try { if (max != 0 && max < Integer.MAX_VALUE) { BufferedInputStream b = new BufferedInputStream(in, max); b.mark(max); byte[] small = new byte[max]; int len = IOUtils.readFully(b, small, max); if (len < max) { if (len < small.length) { small = Arrays.copyOf(small, len); } return ValueLobDb.createSmallLob(type, small); } b.reset(); in = b; } if (maxLength != Long.MAX_VALUE) { in = new LimitInputStream(in, maxLength); } return createLob(in, type); } catch (IllegalStateException e) { throw DbException.get(ErrorCode.OBJECT_CLOSED, e); } catch (IOException e) { throw DbException.convertIOException(e, null); } }
@Override public void prepare() { if (columns == null) { // 如INSERT INTO InsertTest DEFAULT VALUES if (list.size() > 0 && list.get(0).length == 0) { // special case where table is used as a sequence columns = new Column[0]; } else { // 如INSERT INTO InsertTest(SELECT * FROM tmpSelectTest) columns = table.getColumns(); } } if (list.size() > 0) { for (Expression[] expr : list) { if (expr.length != columns.length) { throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); } for (int i = 0, len = expr.length; i < len; i++) { Expression e = expr[i]; if (e != null) { e = e.optimize(session); if (e instanceof Parameter) { Parameter p = (Parameter) e; p.setColumn(columns[i]); } expr[i] = e; } } } } else { query.prepare(); if (query.getColumnCount() != columns.length) { throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); } } }
@Override public int update() { session.getUser().checkAdmin(); session.commit(true); Database db = session.getDatabase(); if (db.findRole(userName) != null) { throw DbException.get(ErrorCode.ROLE_ALREADY_EXISTS_1, userName); } if (db.findUser(userName) != null) { if (ifNotExists) { return 0; } throw DbException.get(ErrorCode.USER_ALREADY_EXISTS_1, userName); } int id = getObjectId(); User user = new User(db, id, userName, false); user.setAdmin(admin); user.setComment(comment); if (hash != null && salt != null) { user.setSaltAndHash(getByteArray(salt), getByteArray(hash)); } else if (password != null) { char[] passwordChars = getCharArray(password); byte[] userPasswordHash; if (userName.length() == 0 && passwordChars.length == 0) { userPasswordHash = new byte[0]; } else { userPasswordHash = SHA256.getKeyPasswordHash(userName, passwordChars); } user.setUserPasswordHash(userPasswordHash); } else { throw DbException.throwInternalError(); } db.addDatabaseObject(session, user); return 0; }
/** * Undo all operations back to the log position of the given savepoint. * * @param name the savepoint name */ public void rollbackToSavepoint(String name) { checkCommitRollback(); if (savepoints == null) { throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name); } Savepoint savepoint = savepoints.get(name); if (savepoint == null) { throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name); } rollbackTo(savepoint, false); }
/** * INTERNAL. Check if this connection is closed. * * @param write if the next operation is possibly writing * @throws SQLException if the connection or session is closed */ protected void checkClosed(boolean write) throws SQLException { if (session == null) { throw DbException.get(ErrorCode.OBJECT_CLOSED); } if (session.isClosed()) { throw DbException.get(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN); } if (session.isReconnectNeeded(write)) { trace.debug("reconnect"); closePreparedCommands(); session = session.reconnect(write); setTrace(session.getTrace()); } }
/** * Parse a string to a ValueTime. * * @param s the string to parse * @return the time */ public static ValueTime parse(String s) { try { return fromNanos(DateTimeUtils.parseTimeNanos(s, 0, s.length(), false)); } catch (Exception e) { throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, e, "TIME", s); } }
/** * Parse and prepare the given SQL statement. This method also checks if the connection has been * closed. * * @param sql the SQL statement * @return the prepared statement */ public Command prepareLocal(String sql) { if (closed) { throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "session closed"); } Command command; if (queryCacheSize > 0) { if (queryCache == null) { queryCache = SmallLRUCache.newInstance(queryCacheSize); modificationMetaID = database.getModificationMetaId(); } else { long newModificationMetaID = database.getModificationMetaId(); if (newModificationMetaID != modificationMetaID) { queryCache.clear(); modificationMetaID = newModificationMetaID; } command = queryCache.get(sql); if (command != null && command.canReuse()) { command.reuse(); return command; } } } Parser parser = new Parser(this); command = parser.prepareCommand(sql); if (queryCache != null) { if (command.isCacheable()) { queryCache.put(sql, command); } } return command; }
@Override public Value getValue(Session session) { query.setSession(session); ResultInterface result = query.query(2); try { int rowcount = result.getRowCount(); if (rowcount > 1) { throw DbException.get(ErrorCode.SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW); } Value v; if (rowcount <= 0) { v = ValueNull.INSTANCE; } else { result.next(); Value[] values = result.currentRow(); if (result.getVisibleColumnCount() == 1) { v = values[0]; } else { v = ValueArray.get(values); } } return v; } finally { result.close(); } }
/** * Creates a new sequence. * * @param schema the schema * @param id the object id * @param name the sequence name * @param startValue the first value to return * @param increment the increment count * @param cacheSize the number of entries to pre-fetch * @param minValue the minimum value * @param maxValue the maximum value * @param cycle whether to jump back to the min value if needed * @param belongsToTable whether this sequence belongs to a table (for auto-increment columns) */ public Sequence( Schema schema, int id, String name, Long startValue, Long increment, Long cacheSize, Long minValue, Long maxValue, boolean cycle, boolean belongsToTable) { initSchemaObjectBase(schema, id, name, Trace.SEQUENCE); this.increment = increment != null ? increment : 1; this.minValue = minValue != null ? minValue : getDefaultMinValue(startValue, this.increment); this.maxValue = maxValue != null ? maxValue : getDefaultMaxValue(startValue, this.increment); this.value = startValue != null ? startValue : getDefaultStartValue(this.increment); this.valueWithMargin = value; this.cacheSize = cacheSize != null ? Math.max(1, cacheSize) : DEFAULT_CACHE_SIZE; this.cycle = cycle; this.belongsToTable = belongsToTable; if (!isValid(this.value, this.minValue, this.maxValue, this.increment)) { throw DbException.get( ErrorCode.SEQUENCE_ATTRIBUTES_INVALID, name, String.valueOf(this.value), String.valueOf(this.minValue), String.valueOf(this.maxValue), String.valueOf(this.increment)); } }
@Override public Index getScanIndex(Session session) { if (getStep(session) == 0) { throw DbException.get(ErrorCode.STEP_SIZE_MUST_NOT_BE_ZERO); } return new RangeIndex(this, IndexColumn.wrap(columns)); }
/** * The maximum number of rows, including uncommitted rows of any session. * * @return the maximum number of rows */ public long getRowCountMax() { try { return dataMap.sizeAsLongMax(); } catch (IllegalStateException e) { throw DbException.get(ErrorCode.OBJECT_CLOSED, e); } }
/** * Allows the start value, increment, min value and max value to be updated atomically, including * atomic validation. Useful because setting these attributes one after the other could otherwise * result in an invalid sequence state (e.g. min value > max value, start value < min value, etc). * * @param startValue the new start value (<code>null</code> if no change) * @param minValue the new min value (<code>null</code> if no change) * @param maxValue the new max value (<code>null</code> if no change) * @param increment the new increment (<code>null</code> if no change) */ public synchronized void modify(Long startValue, Long minValue, Long maxValue, Long increment) { if (startValue == null) { startValue = this.value; } if (minValue == null) { minValue = this.minValue; } if (maxValue == null) { maxValue = this.maxValue; } if (increment == null) { increment = this.increment; } if (!isValid(startValue, minValue, maxValue, increment)) { throw DbException.get( ErrorCode.SEQUENCE_ATTRIBUTES_INVALID, getName(), String.valueOf(this.value), String.valueOf(this.minValue), String.valueOf(this.maxValue), String.valueOf(this.increment)); } this.value = startValue; this.valueWithMargin = startValue; this.minValue = minValue; this.maxValue = maxValue; this.increment = increment; }
/** * 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.getTransactionName().equals(transactionName)) { p.setState(state); found = true; break; } } } if (!found) { throw DbException.get(ErrorCode.TRANSACTION_NOT_FOUND_1, transactionName); } } }
/** * Split the password property into file password and user password if necessary, and convert them * to the internal hash format. */ private void convertPasswords() { char[] password = removePassword(); boolean passwordHash = removeProperty("PASSWORD_HASH", false); // 如果PASSWORD_HASH参数是true那么不再进行SHA256 // 如果配置了CIPHER,则password包含两部份,用一个空格分开这两部份,第一部份是filePassword,第二部份是userPassword。 // 如果PASSWORD_HASH参数是true那么不再进行SHA256,此时必须使用16进制字符,字符个数是偶数。 // 如果PASSWORD_HASH参数是false,不必是16进制字符,会按SHA256算法进行hash if (getProperty("CIPHER", null) != null) { // split password into (filePassword+' '+userPassword) int space = -1; for (int i = 0, len = password.length; i < len; i++) { if (password[i] == ' ') { space = i; break; } } if (space < 0) { throw DbException.get(ErrorCode.WRONG_PASSWORD_FORMAT); } char[] np = new char[password.length - space - 1]; char[] filePassword = new char[space]; System.arraycopy(password, space + 1, np, 0, np.length); System.arraycopy(password, 0, filePassword, 0, space); Arrays.fill(password, (char) 0); password = np; // filePasswordHash用"file"进行hash fileEncryptionKey = FilePathEncrypt.getPasswordBytes(filePassword); filePasswordHash = hashPassword(passwordHash, "file", filePassword); } // userPasswordHash用用户名进行hash userPasswordHash = hashPassword(passwordHash, user, password); }
private void readProperties(Properties info) { Object[] list = new Object[info.size()]; info.keySet().toArray(list); DbSettings s = null; // 可在info中配三种参数,相关文档见:E:\H2\my-h2\my-h2-docs\999 可配置的参数汇总.java中的1、2、3项 for (Object k : list) { String key = StringUtils.toUpperEnglish(k.toString()); if (prop.containsKey(key)) { throw DbException.get(ErrorCode.DUPLICATE_PROPERTY_1, key); } Object value = info.get(k); // 支持org.h2.command.dml.SetTypes中的参数和ConnectionInfo与connectionTime相关的参数 if (isKnownSetting(key)) { prop.put(key, value); } else { if (s == null) { s = getDbSettings(); } // org.h2.constant.DbSettings中的参数 if (s.containsKey(key)) { prop.put(key, value); } } } }
/** * Sets the primary key columns, but also check if a primary key with different columns is already * defined. * * @param columns the primary key columns * @return true if the same primary key columns where already set */ private boolean setPrimaryKeyColumns(IndexColumn[] columns) { if (pkColumns != null) { int len = columns.length; if (len != pkColumns.length) { throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); } for (int i = 0; i < len; i++) { if (!columns[i].columnName.equals(pkColumns[i].columnName)) { throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); } } return true; } this.pkColumns = columns; return false; }
/** * 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 { transfer.flush(); int status = transfer.readInt(); if (status == STATUS_ERROR) { 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(), s); throw e; } throw DbException.convert(s); } 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); } }
@Override public void createDirectory() { if (exists() && isDirectory()) { throw DbException.get( ErrorCode.FILE_CREATION_FAILED_1, name + " (a file with this name already exists)"); } // TODO directories are not really supported }
@Override public Value modulus(Value v) { ValueByte other = (ValueByte) v; if (other.value == 0) { throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getSQL()); } return ValueByte.get((byte) (value % other.value)); }
public Index getScanIndex(Session session) { if (createException != null) { String msg = createException.getMessage(); throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, createException, getSQL(), msg); } PlanItem item = getBestPlanItem(session, null); return item.getIndex(); }
@Override public long getRowCountApproximation() { try { return dataMap.map.sizeAsLong(); } catch (IllegalStateException e) { throw DbException.get(ErrorCode.OBJECT_CLOSED); } }
public int update() { session.getUser().checkAdmin(); session.commit(true); Database db = session.getDatabase(); Sequence sequence = getSchema().findSequence(sequenceName); if (sequence == null) { if (!ifExists) { throw DbException.get(ErrorCode.SEQUENCE_NOT_FOUND_1, sequenceName); } } else { if (sequence.getBelongsToTable()) { throw DbException.get(ErrorCode.SEQUENCE_BELONGS_TO_A_TABLE_1, sequenceName); } db.removeSchemaObject(session, sequence); } return 0; }
private void copyData() { if (table.isTemporary()) { throw DbException.getUnsupportedException("TEMP TABLE"); } Database db = session.getDatabase(); String baseName = table.getName(); String tempName = db.getTempTableName(baseName, session); Column[] columns = table.getColumns(); ArrayList<Column> newColumns = New.arrayList(); Table newTable = cloneTableStructure(columns, db, tempName, newColumns); try { // check if a view would become invalid // (because the column to drop is referenced or so) checkViews(table, newTable); } catch (DbException e) { execute("DROP TABLE " + newTable.getName(), true); throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, e, getSQL(), e.getMessage()); } String tableName = table.getName(); ArrayList<TableView> views = table.getViews(); if (views != null) { views = New.arrayList(views); for (TableView view : views) { table.removeView(view); } } execute("DROP TABLE " + table.getSQL() + " IGNORE", true); db.renameSchemaObject(session, newTable, tableName); for (DbObject child : newTable.getChildren()) { if (child instanceof Sequence) { continue; } String name = child.getName(); if (name == null || child.getCreateSQL() == null) { continue; } if (name.startsWith(tempName + "_")) { name = name.substring(tempName.length() + 1); SchemaObject so = (SchemaObject) child; if (so instanceof Constraint) { if (so.getSchema().findConstraint(session, name) != null) { name = so.getSchema().getUniqueConstraintName(session, newTable); } } else if (so instanceof Index) { if (so.getSchema().findIndex(session, name) != null) { name = so.getSchema().getUniqueIndexName(session, newTable, name); } } db.renameSchemaObject(session, so, name); } } if (views != null) { for (TableView view : views) { String sql = view.getCreateSQL(true, true); execute(sql, true); } } }
/** * 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); }
@Override public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) { try { long cost = 10 * (dataMap.map.sizeAsLong() + Constants.COST_ROW_OFFSET); return cost; } catch (IllegalStateException e) { throw DbException.get(ErrorCode.OBJECT_CLOSED); } }
/** * Create a duplicate key exception with a message that contains the index name. * * @param key the key values * @return the exception */ protected DbException getDuplicateKeyException(String key) { String sql = getName() + " ON " + table.getSQL() + "(" + getColumnListSQL() + ")"; if (key != null) { sql += " VALUES " + key; } DbException e = DbException.get(ErrorCode.DUPLICATE_KEY_1, sql); e.setSource(this); return e; }
/** * Keep a collection of the columns to pass to update if a duplicate key happens, for MySQL-style * INSERT ... ON DUPLICATE KEY UPDATE .... * * @param column the column * @param expression the expression */ public void addAssignmentForDuplicate(Column column, Expression expression) { if (duplicateKeyAssignmentMap == null) { duplicateKeyAssignmentMap = New.hashMap(); } if (duplicateKeyAssignmentMap.containsKey(column)) { throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, column.getName()); } duplicateKeyAssignmentMap.put(column, expression); }
/** * 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); }
@Override public void add(Session session, Row row) { if (mainIndexColumn == -1) { if (row.getKey() == 0) { row.setKey(++lastKey); } } else { long c = row.getValue(mainIndexColumn).getLong(); row.setKey(c); } if (mvTable.getContainsLargeObject()) { for (int i = 0, len = row.getColumnCount(); i < len; i++) { Value v = row.getValue(i); Value v2 = v.link(database, getId()); if (v2.isLinked()) { session.unlinkAtCommitStop(v2); } if (v != v2) { row.setValue(i, v2); } } } TransactionMap<Value, Value> map = getMap(session); Value key = ValueLong.get(row.getKey()); Value old = map.getLatest(key); if (old != null) { String sql = "PRIMARY KEY ON " + table.getSQL(); if (mainIndexColumn >= 0 && mainIndexColumn < indexColumns.length) { sql += "(" + indexColumns[mainIndexColumn].getSQL() + ")"; } DbException e = DbException.get(ErrorCode.DUPLICATE_KEY_1, sql); e.setSource(this); throw e; } try { map.put(key, ValueArray.get(row.getValueList())); } catch (IllegalStateException e) { throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName()); } lastKey = Math.max(lastKey, row.getKey()); }
/** * 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); }