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); } } }
private void compactRewrite(ArrayList<BTreeChunk> old) { HashSet<Integer> set = New.hashSet(); for (BTreeChunk c : old) { set.add(c.id); } if (!map.rewrite(set)) { return; } freeUnusedChunks(); commitAndSave(); }
/** * Get all objects. * * @return a (possible empty) list of all objects */ public ArrayList<SchemaObject> getAll() { ArrayList<SchemaObject> all = New.arrayList(); all.addAll(getMap(DbObjectType.TABLE_OR_VIEW).values()); all.addAll(getMap(DbObjectType.SEQUENCE).values()); all.addAll(getMap(DbObjectType.INDEX).values()); all.addAll(getMap(DbObjectType.TRIGGER).values()); all.addAll(getMap(DbObjectType.CONSTRAINT).values()); all.addAll(getMap(DbObjectType.CONSTANT).values()); all.addAll(getMap(DbObjectType.FUNCTION_ALIAS).values()); return all; }
private void checkDefaultReferencesTable(Expression defaultExpression) { if (defaultExpression == null) { return; } HashSet<DbObject> dependencies = New.hashSet(); ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor(dependencies); defaultExpression.isEverything(visitor); if (dependencies.contains(table)) { throw DbException.get(ErrorCode.COLUMN_IS_REFERENCED_1, defaultExpression.getSQL()); } }
@Override public void removeChildrenAndResources(ServerSession session) { while (triggers != null && triggers.size() > 0) { TriggerObject obj = (TriggerObject) triggers.values().toArray()[0]; database.removeSchemaObject(session, obj); } while (constraints != null && constraints.size() > 0) { Constraint obj = (Constraint) constraints.values().toArray()[0]; database.removeSchemaObject(session, obj); } // There can be dependencies between tables e.g. using computed columns, // so we might need to loop over them multiple times. boolean runLoopAgain = false; do { runLoopAgain = false; if (tablesAndViews != null) { // Loop over a copy because the map is modified underneath us. for (Table obj : New.arrayList(tablesAndViews.values())) { // Check for null because multiple tables might be deleted // in one go underneath us. if (obj.getName() != null) { if (database.getDependentTable(obj, obj) == null) { database.removeSchemaObject(session, obj); } else { runLoopAgain = true; } } } } } while (runLoopAgain); while (indexes != null && indexes.size() > 0) { Index obj = (Index) indexes.values().toArray()[0]; database.removeSchemaObject(session, obj); } while (sequences != null && sequences.size() > 0) { Sequence obj = (Sequence) sequences.values().toArray()[0]; database.removeSchemaObject(session, obj); } while (constants != null && constants.size() > 0) { Constant obj = (Constant) constants.values().toArray()[0]; database.removeSchemaObject(session, obj); } while (functions != null && functions.size() > 0) { FunctionAlias obj = (FunctionAlias) functions.values().toArray()[0]; database.removeSchemaObject(session, obj); } owner = null; super.removeChildrenAndResources(session); }
private ArrayList<BTreeChunk> compactGetOldChunks(int targetFillRate, int write) { if (lastChunk == null) { // nothing to do return null; } readAllChunks(); // calculate the fill rate long maxLengthSum = 0; long maxLengthLiveSum = 0; long time = getTimeSinceCreation(); for (BTreeChunk c : chunks.values()) { // ignore young chunks, because we don't optimize those if (c.time + retentionTime > time) { continue; } maxLengthSum += c.maxLen; maxLengthLiveSum += c.maxLenLive; } if (maxLengthLiveSum < 0) { // no old data return null; } // the fill rate of all chunks combined if (maxLengthSum <= 0) { // avoid division by 0 maxLengthSum = 1; } int fillRate = (int) (100 * maxLengthLiveSum / maxLengthSum); if (fillRate >= targetFillRate) { return null; } // the 'old' list contains the chunks we want to free up ArrayList<BTreeChunk> old = New.arrayList(); BTreeChunk last = chunks.get(lastChunk.id); for (BTreeChunk c : chunks.values()) { // only look at chunk older than the retention time // (it's possible to compact chunks earlier, but right // now we don't do that) if (c.time + retentionTime > time) { continue; } long age = last.version - c.version + 1; c.collectPriority = (int) (c.getFillRate() * 1000 / age); old.add(c); } if (old.size() == 0) { return null; } // sort the list, so the first entry should be collected first Collections.sort( old, new Comparator<BTreeChunk>() { @Override public int compare(BTreeChunk o1, BTreeChunk o2) { int comp = new Integer(o1.collectPriority).compareTo(o2.collectPriority); if (comp == 0) { comp = new Long(o1.maxLenLive).compareTo(o2.maxLenLive); } return comp; } }); // find out up to were in the old list we need to move long written = 0; int chunkCount = 0; BTreeChunk move = null; for (BTreeChunk c : old) { if (move != null) { if (c.collectPriority > 0 && written > write) { break; } } written += c.maxLenLive; chunkCount++; move = c; } if (chunkCount < 1) { return null; } // remove the chunks we want to keep from this list boolean remove = false; for (Iterator<BTreeChunk> it = old.iterator(); it.hasNext(); ) { BTreeChunk c = it.next(); if (move == c) { remove = true; } else if (remove) { it.remove(); } } return old; }
/** * Get all tables and views. * * @return a (possible empty) list of all objects */ public ArrayList<Table> getAllTablesAndViews() { synchronized (database) { return New.arrayList(tablesAndViews.values()); } }
/** * Get all objects of the given type. * * @param type the object type * @return a (possible empty) list of all objects */ public ArrayList<SchemaObject> getAll(DbObjectType type) { HashMap<String, SchemaObject> map = getMap(type); return New.arrayList(map.values()); }
/** A schema as created by the SQL statement CREATE SCHEMA */ public class Schema extends DbObjectBase { private User owner; private final boolean system; private final HashMap<String, Table> tablesAndViews; private final HashMap<String, Index> indexes; private final HashMap<String, Sequence> sequences; private final HashMap<String, TriggerObject> triggers; private final HashMap<String, Constraint> constraints; private final HashMap<String, Constant> constants; private final HashMap<String, FunctionAlias> functions; private Map<String, String> replicationProperties; private ReplicationPropertiesChangeListener replicationPropertiesChangeListener; /** * The set of returned unique names that are not yet stored. It is used to avoid returning the * same unique name twice when multiple threads concurrently create objects. */ private final HashSet<String> temporaryUniqueNames = New.hashSet(); /** * Create a new schema object. * * @param database the database * @param id the object id * @param schemaName the schema name * @param owner the owner of the schema * @param system if this is a system schema (such a schema can not be dropped) */ public Schema(Database database, int id, String schemaName, User owner, boolean system) { super(database, id, schemaName, Trace.SCHEMA); tablesAndViews = database.newStringMap(); indexes = database.newStringMap(); sequences = database.newStringMap(); triggers = database.newStringMap(); constraints = database.newStringMap(); constants = database.newStringMap(); functions = database.newStringMap(); this.owner = owner; this.system = system; } @Override public DbObjectType getType() { return DbObjectType.SCHEMA; } public Map<String, String> getReplicationProperties() { return replicationProperties; } public void setReplicationProperties(Map<String, String> replicationProperties) { this.replicationProperties = replicationProperties; if (replicationPropertiesChangeListener != null) replicationPropertiesChangeListener.replicationPropertiesChanged(this); } public void setReplicationPropertiesChangeListener(ReplicationPropertiesChangeListener listener) { replicationPropertiesChangeListener = listener; } public static interface ReplicationPropertiesChangeListener { void replicationPropertiesChanged(Schema schema); } /** * Check if this schema can be dropped. System schemas can not be dropped. * * @return true if it can be dropped */ public boolean canDrop() { return !system; } @Override public String getCreateSQL() { if (system) { return null; } StatementBuilder sql = new StatementBuilder(); sql.append("CREATE SCHEMA IF NOT EXISTS ") .append(database.getSQL()) .append(getSQL()) .append(" AUTHORIZATION ") .append(owner.getSQL()); if (replicationProperties != null && !replicationProperties.isEmpty()) { sql.append(" WITH REPLICATION = ("); for (Map.Entry<String, String> e : replicationProperties.entrySet()) { sql.appendExceptFirst(","); sql.append('\'').append(e.getKey()).append("':'").append(e.getValue()).append('\''); } sql.append(')'); } return sql.toString(); } @Override public void removeChildrenAndResources(ServerSession session) { while (triggers != null && triggers.size() > 0) { TriggerObject obj = (TriggerObject) triggers.values().toArray()[0]; database.removeSchemaObject(session, obj); } while (constraints != null && constraints.size() > 0) { Constraint obj = (Constraint) constraints.values().toArray()[0]; database.removeSchemaObject(session, obj); } // There can be dependencies between tables e.g. using computed columns, // so we might need to loop over them multiple times. boolean runLoopAgain = false; do { runLoopAgain = false; if (tablesAndViews != null) { // Loop over a copy because the map is modified underneath us. for (Table obj : New.arrayList(tablesAndViews.values())) { // Check for null because multiple tables might be deleted // in one go underneath us. if (obj.getName() != null) { if (database.getDependentTable(obj, obj) == null) { database.removeSchemaObject(session, obj); } else { runLoopAgain = true; } } } } } while (runLoopAgain); while (indexes != null && indexes.size() > 0) { Index obj = (Index) indexes.values().toArray()[0]; database.removeSchemaObject(session, obj); } while (sequences != null && sequences.size() > 0) { Sequence obj = (Sequence) sequences.values().toArray()[0]; database.removeSchemaObject(session, obj); } while (constants != null && constants.size() > 0) { Constant obj = (Constant) constants.values().toArray()[0]; database.removeSchemaObject(session, obj); } while (functions != null && functions.size() > 0) { FunctionAlias obj = (FunctionAlias) functions.values().toArray()[0]; database.removeSchemaObject(session, obj); } owner = null; super.removeChildrenAndResources(session); } /** * Get the owner of this schema. * * @return the owner */ public User getOwner() { return owner; } @SuppressWarnings("unchecked") private HashMap<String, SchemaObject> getMap(DbObjectType type) { HashMap<String, ? extends SchemaObject> result; switch (type) { case TABLE_OR_VIEW: result = tablesAndViews; break; case SEQUENCE: result = sequences; break; case INDEX: result = indexes; break; case TRIGGER: result = triggers; break; case CONSTRAINT: result = constraints; break; case CONSTANT: result = constants; break; case FUNCTION_ALIAS: result = functions; break; default: throw DbException.throwInternalError("type=" + type); } return (HashMap<String, SchemaObject>) result; } /** * Add an object to this schema. This method must not be called within CreateSchemaObject; use * Database.addSchemaObject() instead * * @param obj the object to add */ public void add(SchemaObject obj) { if (SysProperties.CHECK && obj.getSchema() != this) { DbException.throwInternalError("wrong schema"); } String name = obj.getName(); HashMap<String, SchemaObject> map = getMap(obj.getType()); if (SysProperties.CHECK && map.get(name) != null) { DbException.throwInternalError("object already exists: " + name); } map.put(name, obj); freeUniqueName(name); } /** * Rename an object. * * @param obj the object to rename * @param newName the new name */ public void rename(SchemaObject obj, String newName) { DbObjectType type = obj.getType(); HashMap<String, SchemaObject> map = getMap(type); if (SysProperties.CHECK) { if (!map.containsKey(obj.getName())) { DbException.throwInternalError("not found: " + obj.getName()); } if (obj.getName().equals(newName) || map.containsKey(newName)) { DbException.throwInternalError("object already exists: " + newName); } } obj.checkRename(); map.remove(obj.getName()); freeUniqueName(obj.getName()); obj.rename(newName); map.put(newName, obj); freeUniqueName(newName); } /** * Try to find a table or view with this name. This method returns null if no object with this * name exists. Local temporary tables are also returned. * * @param session the session * @param name the object name * @return the object or null */ public Table findTableOrView(ServerSession session, String name) { Table table = tablesAndViews.get(name); if (table == null && session != null) { table = session.findLocalTempTable(name); } return table; } /** * Try to find an index with this name. This method returns null if no object with this name * exists. * * @param session the session * @param name the object name * @return the object or null */ public Index findIndex(ServerSession session, String name) { Index index = indexes.get(name); if (index == null) { index = session.findLocalTempTableIndex(name); } return index; } /** * Try to find a trigger with this name. This method returns null if no object with this name * exists. * * @param name the object name * @return the object or null */ public TriggerObject findTrigger(String name) { return triggers.get(name); } /** * Try to find a sequence with this name. This method returns null if no object with this name * exists. * * @param sequenceName the object name * @return the object or null */ public Sequence findSequence(String sequenceName) { return sequences.get(sequenceName); } /** * Try to find a constraint with this name. This method returns null if no object with this name * exists. * * @param session the session * @param name the object name * @return the object or null */ public Constraint findConstraint(ServerSession session, String name) { Constraint constraint = constraints.get(name); if (constraint == null) { constraint = session.findLocalTempTableConstraint(name); } return constraint; } /** * Try to find a user defined constant with this name. This method returns null if no object with * this name exists. * * @param constantName the object name * @return the object or null */ public Constant findConstant(String constantName) { return constants.get(constantName); } /** * Try to find a user defined function with this name. This method returns null if no object with * this name exists. * * @param functionAlias the object name * @return the object or null */ public FunctionAlias findFunction(String functionAlias) { return functions.get(functionAlias); } /** * Release a unique object name. * * @param name the object name */ public void freeUniqueName(String name) { if (name != null) { synchronized (temporaryUniqueNames) { temporaryUniqueNames.remove(name); } } } private String getUniqueName( DbObject obj, HashMap<String, ? extends SchemaObject> map, String prefix) { String hash = Integer.toHexString(obj.getName().hashCode()).toUpperCase(); String name = null; synchronized (temporaryUniqueNames) { for (int i = 1, len = hash.length(); i < len; i++) { name = prefix + hash.substring(0, i); if (!map.containsKey(name) && !temporaryUniqueNames.contains(name)) { break; } name = null; } if (name == null) { prefix = prefix + hash + "_"; for (int i = 0; ; i++) { name = prefix + i; if (!map.containsKey(name) && !temporaryUniqueNames.contains(name)) { break; } } } temporaryUniqueNames.add(name); } return name; } /** * Create a unique constraint name. * * @param session the session * @param table the constraint table * @return the unique name */ public String getUniqueConstraintName(ServerSession session, Table table) { HashMap<String, Constraint> tableConstraints; if (table.isTemporary() && !table.isGlobalTemporary()) { tableConstraints = session.getLocalTempTableConstraints(); } else { tableConstraints = constraints; } return getUniqueName(table, tableConstraints, "CONSTRAINT_"); } /** * Create a unique index name. * * @param session the session * @param table the indexed table * @param prefix the index name prefix * @return the unique name */ public String getUniqueIndexName(ServerSession session, Table table, String prefix) { HashMap<String, Index> tableIndexes; if (table.isTemporary() && !table.isGlobalTemporary()) { tableIndexes = session.getLocalTempTableIndexes(); } else { tableIndexes = indexes; } return getUniqueName(table, tableIndexes, prefix); } /** * Get the table or view with the given name. Local temporary tables are also returned. * * @param session the session * @param name the table or view name * @return the table or view * @throws DbException if no such object exists */ public Table getTableOrView(ServerSession session, String name) { Table table = tablesAndViews.get(name); if (table == null) { if (session != null) { table = session.findLocalTempTable(name); } if (table == null) { throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, name); } } return table; } /** * Get the index with the given name. * * @param name the index name * @return the index * @throws DbException if no such object exists */ public Index getIndex(String name) { Index index = indexes.get(name); if (index == null) { throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, name); } return index; } /** * Get the constraint with the given name. * * @param name the constraint name * @return the constraint * @throws DbException if no such object exists */ public Constraint getConstraint(String name) { Constraint constraint = constraints.get(name); if (constraint == null) { throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, name); } return constraint; } /** * Get the user defined constant with the given name. * * @param constantName the constant name * @return the constant * @throws DbException if no such object exists */ public Constant getConstant(String constantName) { Constant constant = constants.get(constantName); if (constant == null) { throw DbException.get(ErrorCode.CONSTANT_NOT_FOUND_1, constantName); } return constant; } /** * Get the sequence with the given name. * * @param sequenceName the sequence name * @return the sequence * @throws DbException if no such object exists */ public Sequence getSequence(String sequenceName) { Sequence sequence = sequences.get(sequenceName); if (sequence == null) { throw DbException.get(ErrorCode.SEQUENCE_NOT_FOUND_1, sequenceName); } return sequence; } /** * Get all objects. * * @return a (possible empty) list of all objects */ public ArrayList<SchemaObject> getAll() { ArrayList<SchemaObject> all = New.arrayList(); all.addAll(getMap(DbObjectType.TABLE_OR_VIEW).values()); all.addAll(getMap(DbObjectType.SEQUENCE).values()); all.addAll(getMap(DbObjectType.INDEX).values()); all.addAll(getMap(DbObjectType.TRIGGER).values()); all.addAll(getMap(DbObjectType.CONSTRAINT).values()); all.addAll(getMap(DbObjectType.CONSTANT).values()); all.addAll(getMap(DbObjectType.FUNCTION_ALIAS).values()); return all; } /** * Get all objects of the given type. * * @param type the object type * @return a (possible empty) list of all objects */ public ArrayList<SchemaObject> getAll(DbObjectType type) { HashMap<String, SchemaObject> map = getMap(type); return New.arrayList(map.values()); } /** * Get all tables and views. * * @return a (possible empty) list of all objects */ public ArrayList<Table> getAllTablesAndViews() { synchronized (database) { return New.arrayList(tablesAndViews.values()); } } /** * Remove an object from this schema. * * @param obj the object to remove */ public void remove(SchemaObject obj) { String objName = obj.getName(); HashMap<String, SchemaObject> map = getMap(obj.getType()); if (SysProperties.CHECK && !map.containsKey(objName)) { DbException.throwInternalError("not found: " + objName); } map.remove(objName); freeUniqueName(objName); } /** * Add a table to the schema. * * @param data the create table information * @return the created {@link Table} object */ public Table createTable(CreateTableData data) { synchronized (database) { if (!data.temporary || data.globalTemporary) { database.lockMeta(data.session); } data.schema = this; if (data.isMemoryTable()) data.storageEngineName = MemoryStorageEngine.NAME; // 用默认的数据库参数 if (data.storageEngineName == null) { data.storageEngineName = database.getDefaultStorageEngineName(); } if (data.storageEngineName != null) { StorageEngine engine = StorageEngineManager.getInstance().getEngine(data.storageEngineName); if (engine == null) { try { engine = (StorageEngine) Utils.loadUserClass(data.storageEngineName).newInstance(); StorageEngineManager.getInstance().registerEngine(engine); } catch (Exception e) { throw DbException.convert(e); } } if (engine instanceof TableFactory) { return ((TableFactory) engine).createTable(data); } return new StandardTable(data, engine); } throw DbException.convert(new NullPointerException("table engine is null")); } } }
/** A simple hash table with an optimization for the last recently used object. */ public class SmallMap { private final HashMap<Integer, Object> map = New.hashMap(); private Object cache; private int cacheId; private int lastId; private final int maxElements; /** * Create a map with the given maximum number of entries. * * @param maxElements the maximum number of entries */ public SmallMap(int maxElements) { this.maxElements = maxElements; } /** * Add an object to the map. If the size of the map is larger than twice the maximum size, objects * with a low id are removed. * * @param id the object id * @param o the object * @return the id */ public int addObject(int id, Object o) { if (map.size() > maxElements * 2) { Iterator<Integer> it = map.keySet().iterator(); while (it.hasNext()) { Integer k = it.next(); if (k.intValue() + maxElements < lastId) { it.remove(); } } } if (id > lastId) { lastId = id; } map.put(id, o); cacheId = id; cache = o; return id; } /** * Remove an object from the map. * * @param id the id of the object to remove */ public void freeObject(int id) { if (cacheId == id) { cacheId = -1; cache = null; } map.remove(id); } /** * Get an object from the map if it is stored. * * @param id the id of the object * @param ifAvailable only return it if available, otherwise return null * @return the object or null * @throws DbException if isAvailable is false and the object has not been found */ public Object getObject(int id, boolean ifAvailable) { if (id == cacheId) { return cache; } Object obj = map.get(id); if (obj == null && !ifAvailable) { throw DbException.get(ErrorCode.OBJECT_CLOSED); } return obj; } }
/** * The engine contains a map of all open databases. It is also responsible for opening and creating * new databases. This is a singleton class. */ public class DatabaseEngine implements SessionFactory { private static final HashMap<String, Database> DATABASES = New.hashMap(); private static final DatabaseEngine INSTANCE = new DatabaseEngine(); private static String hostAndPort; public static String getHostAndPort() { return hostAndPort; } public static DatabaseEngine getInstance() { return INSTANCE; } public static Collection<Database> getDatabases() { return DATABASES.values(); } public static synchronized void init(String host, int port) { hostAndPort = host + ":" + port; StorageEngineManager.initStorageEngines(); SQLEngineManager.initSQLEngines(); SystemDatabase.init(); for (String dbName : SystemDatabase.findAll()) DATABASES.put(dbName, new Database(INSTANCE, true)); } private volatile long wrongPasswordDelay = SysProperties.DELAY_WRONG_PASSWORD_MIN; protected DatabaseEngine() {} public Database createDatabase(boolean persistent) { return new Database(this, persistent); } /** * Called after a database has been closed, to remove the object from the list of open databases. * * @param dbName the database name */ public synchronized void closeDatabase(String dbName) { DATABASES.remove(dbName); } @Override public synchronized Session createSession(ConnectionInfo ci) { return createSessionAndValidate(ci); } private Session createSessionAndValidate(ConnectionInfo ci) { try { boolean ifExists = ci.getProperty("IFEXISTS", false); Session session; for (int i = 0; ; i++) { session = createSession(ci, ifExists); if (session != null) { break; } // we found a database that is currently closing // wait a bit to avoid a busy loop (the method is synchronized) if (i > 60 * 1000) { // retry at most 1 minute throw DbException.get( ErrorCode.DATABASE_ALREADY_OPEN_1, "Waited for database closing longer than 1 minute"); } try { Thread.sleep(1); } catch (InterruptedException e) { // ignore } } initSession(session, ci); validateUserAndPassword(true); return session; } catch (DbException e) { if (e.getErrorCode() == ErrorCode.WRONG_USER_OR_PASSWORD) { validateUserAndPassword(false); } throw e; } } private Session createSession(ConnectionInfo ci, boolean ifExists) { String name = ci.getDatabaseName(); name = Database.parseDatabaseShortName(ci.getDbSettings(), name); Database database; boolean openNew = ci.getProperty("OPEN_NEW", false); if (openNew) { database = null; } else { database = DATABASES.get(name); } User user = null; boolean opened = false; if (database == null) { if (ifExists && !SystemDatabase.exists(name)) { throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, name); } database = createDatabase(ci.isPersistent()); database.init(ci, name); opened = true; if (database.getAllUsers().isEmpty()) { // users is the last thing we add, so if no user is around, // the database is new (or not initialized correctly) user = new User(database, database.allocateObjectId(), ci.getUserName(), false); user.setAdmin(true); user.setUserPasswordHash(ci.getUserPasswordHash()); database.setMasterUser(user); } DATABASES.put(name, database); if (database.isPersistent()) SystemDatabase.addDatabase(database.getShortName(), database.getStorageEngineName()); } else { if (!database.isInitialized()) database.init(ci, name); } synchronized (database) { if (opened) { // start the thread when already synchronizing on the database // otherwise a deadlock can occur when the writer thread // opens a new database (as in recovery testing) database.opened(); } if (database.isClosing()) { return null; } if (user == null) { if (database.validateFilePasswordHash( ci.getProperty("CIPHER", null), ci.getFilePasswordHash())) { user = database.findUser(ci.getUserName()); if (user != null) { if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) { user = null; } } } if (opened && (user == null || !user.isAdmin())) { // reset - because the user is not an admin, and has no // right to listen to exceptions database.setEventListener(null); } } if (user == null) { database.removeSession(null); throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD); } Session session = database.createSession(user); session.setConnectionInfo(ci); return session; } } private void initSession(Session session, ConnectionInfo ci) { boolean ignoreUnknownSetting = ci.getProperty("IGNORE_UNKNOWN_SETTINGS", false); String init = ci.getProperty("INIT", null); session.setAllowLiterals(true); CommandInterface command; for (String setting : ci.getKeys()) { if (SetTypes.contains(setting)) { String value = ci.getProperty(setting); try { command = session.prepareCommandLocal( "SET " + session.getDatabase().quoteIdentifier(setting) + " " + value); command.executeUpdate(); } catch (DbException e) { if (!ignoreUnknownSetting) { session.close(); throw e; } } } } if (init != null) { try { command = session.prepareCommand(init, Integer.MAX_VALUE); command.executeUpdate(); } catch (DbException e) { if (!ignoreUnknownSetting) { session.close(); throw e; } } } session.setAllowLiterals(false); session.commit(true); } /** * This method is called after validating user name and password. If user name and password were * correct, the sleep time is reset, otherwise this method waits some time (to make brute force / * rainbow table attacks harder) and then throws a 'wrong user or password' exception. The delay * is a bit randomized to protect against timing attacks. Also the delay doubles after each * unsuccessful logins, to make brute force attacks harder. * * <p>There is only one exception message both for wrong user and for wrong password, to make it * harder to get the list of user names. This method must only be called from one place, so it is * not possible from the stack trace to see if the user name was wrong or the password. * * @param correct if the user name or the password was correct * @throws DbException the exception 'wrong user or password' */ private void validateUserAndPassword(boolean correct) { int min = SysProperties.DELAY_WRONG_PASSWORD_MIN; if (correct) { long delay = wrongPasswordDelay; if (delay > min && delay > 0) { // the first correct password must be blocked, // otherwise parallel attacks are possible synchronized (this) { // delay up to the last delay // an attacker can't know how long it will be delay = MathUtils.secureRandomInt((int) delay); try { Thread.sleep(delay); } catch (InterruptedException e) { // ignore } wrongPasswordDelay = min; } } } else { // this method is not synchronized on the Engine, so that // regular successful attempts are not blocked synchronized (this) { long delay = wrongPasswordDelay; int max = SysProperties.DELAY_WRONG_PASSWORD_MAX; if (max <= 0) { max = Integer.MAX_VALUE; } wrongPasswordDelay += wrongPasswordDelay; if (wrongPasswordDelay > max || wrongPasswordDelay < 0) { wrongPasswordDelay = max; } if (min > 0) { // a bit more to protect against timing attacks delay += Math.abs(MathUtils.secureRandomLong() % 100); try { Thread.sleep(delay); } catch (InterruptedException e) { // ignore } } throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD); } } } }
private Table cloneTableStructure( Column[] columns, Database db, String tempName, ArrayList<Column> newColumns) { for (Column col : columns) { newColumns.add(col.getClone()); } if (type == CommandInterface.ALTER_TABLE_DROP_COLUMN) { int position = oldColumn.getColumnId(); newColumns.remove(position); } else if (type == CommandInterface.ALTER_TABLE_ADD_COLUMN) { int position; if (addBefore != null) { position = table.getColumn(addBefore).getColumnId(); } else if (addAfter != null) { position = table.getColumn(addAfter).getColumnId() + 1; } else { position = columns.length; } for (Column column : columnsToAdd) { newColumns.add(position++, column); } } else if (type == CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE) { int position = oldColumn.getColumnId(); newColumns.remove(position); newColumns.add(position, newColumn); } // create a table object in order to get the SQL statement // can't just use this table, because most column objects are 'shared' // with the old table // still need a new id because using 0 would mean: the new table tries // to use the rows of the table 0 (the meta table) int id = db.allocateObjectId(); CreateTableData data = new CreateTableData(); data.tableName = tempName; data.id = id; data.columns = newColumns; data.temporary = table.isTemporary(); data.persistData = table.isPersistData(); data.persistIndexes = table.isPersistIndexes(); data.isHidden = table.isHidden(); data.create = true; data.session = session; data.storageEngineName = table.getStorageEngineName(); Table newTable = getSchema().createTable(data); newTable.setComment(table.getComment()); StringBuilder buff = new StringBuilder(); buff.append(newTable.getCreateSQL()); if (table.supportsAlterColumnWithCopyData()) { StringBuilder columnList = new StringBuilder(); for (Column nc : newColumns) { if (columnList.length() > 0) { columnList.append(", "); } if (type == CommandInterface.ALTER_TABLE_ADD_COLUMN && columnsToAdd.contains(nc)) { Expression def = (Expression) nc.getDefaultExpression(); columnList.append(def == null ? "NULL" : def.getSQL()); } else { columnList.append(nc.getSQL()); } } buff.append(" AS SELECT "); if (columnList.length() == 0) { // special case: insert into test select * from buff.append('*'); } else { buff.append(columnList); } buff.append(" FROM ").append(table.getSQL()); } String newTableSQL = buff.toString(); String newTableName = newTable.getName(); Schema newTableSchema = newTable.getSchema(); newTable.removeChildrenAndResources(session); execute(newTableSQL, true); newTable = newTableSchema.getTableOrView(session, newTableName); ArrayList<String> triggers = New.arrayList(); for (DbObject child : table.getChildren()) { if (child instanceof Sequence) { continue; } else if (child instanceof Index) { Index idx = (Index) child; if (idx.getIndexType().getBelongsToConstraint()) { continue; } } String createSQL = child.getCreateSQL(); if (createSQL == null) { continue; } if (child instanceof TableView) { continue; } else if (child.getType() == DbObject.TABLE_OR_VIEW) { DbException.throwInternalError(); } String quotedName = Parser.quoteIdentifier(tempName + "_" + child.getName()); String sql = null; if (child instanceof ConstraintReferential) { ConstraintReferential r = (ConstraintReferential) child; if (r.getTable() != table) { sql = r.getCreateSQLForCopy(r.getTable(), newTable, quotedName, false); } } if (sql == null) { sql = child.getCreateSQLForCopy(newTable, quotedName); } if (sql != null) { if (child instanceof TriggerObject) { triggers.add(sql); } else { execute(sql, true); } } } table.setModified(); // remove the sequences from the columns (except dropped columns) // otherwise the sequence is dropped if the table is dropped for (Column col : newColumns) { Sequence seq = col.getSequence(); if (seq != null) { table.removeSequence(seq); col.setSequence(null); } } for (String sql : triggers) { execute(sql, true); } return newTable; }
@Override public int update() { if (!transactional) { session.commit(true); } Database db = session.getDatabase(); if (!db.isPersistent()) { data.persistIndexes = false; } if (getSchema().findTableOrView(session, data.tableName) != null) { if (ifNotExists) { return 0; } throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.tableName); } if (asQuery != null) { asQuery.prepare(); if (data.columns.isEmpty()) { generateColumnsFromQuery(); } else if (data.columns.size() != asQuery.getColumnCount()) { throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); } } if (pkColumns != null) { for (Column c : data.columns) { for (IndexColumn idxCol : pkColumns) { if (c.getName().equals(idxCol.columnName)) { c.setNullable(false); } } } } data.id = getObjectId(); data.create = create; data.session = session; boolean isSessionTemporary = data.temporary && !data.globalTemporary; if (!isSessionTemporary) { db.lockMeta(session); } Table table = createTable(data); ArrayList<Sequence> sequences = New.arrayList(); for (Column c : data.columns) { if (c.isAutoIncrement()) { int objId = getObjectId(); c.convertAutoIncrementToSequence(session, getSchema(), objId, data.temporary); } Sequence seq = c.getSequence(); if (seq != null) { sequences.add(seq); } } table.setComment(comment); if (isSessionTemporary) { if (onCommitDrop) { table.setOnCommitDrop(true); } if (onCommitTruncate) { table.setOnCommitTruncate(true); } session.addLocalTempTable(table); } else { db.lockMeta(session); db.addSchemaObject(session, table); } try { for (Column c : data.columns) { c.prepareExpression(session); } for (Sequence sequence : sequences) { table.addSequence(sequence); } for (DefineCommand command : constraintCommands) { command.setTransactional(transactional); command.update(); } if (asQuery != null) { Insert insert = null; insert = new Insert(session); insert.setSortedInsertMode(sortedInsertMode); insert.setQuery(asQuery); insert.setTable(table); insert.setInsertFromSelect(true); insert.prepare(); insert.update(); } } catch (DbException e) { db.checkPowerOff(); db.removeSchemaObject(session, table); if (!transactional) { session.commit(true); } throw e; } return 0; }
/** This class represents the statement CREATE TABLE */ public class CreateTable extends SchemaCommand { protected final CreateTableData data = new CreateTableData(); protected IndexColumn[] pkColumns; protected boolean ifNotExists; private final ArrayList<DefineCommand> constraintCommands = New.arrayList(); private boolean onCommitDrop; private boolean onCommitTruncate; private Query asQuery; private String comment; private boolean sortedInsertMode; public CreateTable(Session session, Schema schema) { super(session, schema); data.persistIndexes = true; data.persistData = true; } public void setQuery(Query query) { this.asQuery = query; } public void setTemporary(boolean temporary) { data.temporary = temporary; } public void setTableName(String tableName) { data.tableName = tableName; } /** * Add a column to this table. * * @param column the column to add */ public void addColumn(Column column) { data.columns.add(column); } /** * Add a constraint statement to this statement. The primary key definition is one possible * constraint statement. * * @param command the statement to add */ public void addConstraintCommand(DefineCommand command) { if (command instanceof CreateIndex) { constraintCommands.add(command); } else { AlterTableAddConstraint con = (AlterTableAddConstraint) command; boolean alreadySet; if (con.getType() == CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY) { alreadySet = setPrimaryKeyColumns(con.getIndexColumns()); } else { alreadySet = false; } if (!alreadySet) { constraintCommands.add(command); } } } public void setIfNotExists(boolean ifNotExists) { this.ifNotExists = ifNotExists; } @Override public int update() { if (!transactional) { session.commit(true); } Database db = session.getDatabase(); if (!db.isPersistent()) { data.persistIndexes = false; } if (getSchema().findTableOrView(session, data.tableName) != null) { if (ifNotExists) { return 0; } throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.tableName); } if (asQuery != null) { asQuery.prepare(); if (data.columns.isEmpty()) { generateColumnsFromQuery(); } else if (data.columns.size() != asQuery.getColumnCount()) { throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); } } if (pkColumns != null) { for (Column c : data.columns) { for (IndexColumn idxCol : pkColumns) { if (c.getName().equals(idxCol.columnName)) { c.setNullable(false); } } } } data.id = getObjectId(); data.create = create; data.session = session; boolean isSessionTemporary = data.temporary && !data.globalTemporary; if (!isSessionTemporary) { db.lockMeta(session); } Table table = createTable(data); ArrayList<Sequence> sequences = New.arrayList(); for (Column c : data.columns) { if (c.isAutoIncrement()) { int objId = getObjectId(); c.convertAutoIncrementToSequence(session, getSchema(), objId, data.temporary); } Sequence seq = c.getSequence(); if (seq != null) { sequences.add(seq); } } table.setComment(comment); if (isSessionTemporary) { if (onCommitDrop) { table.setOnCommitDrop(true); } if (onCommitTruncate) { table.setOnCommitTruncate(true); } session.addLocalTempTable(table); } else { db.lockMeta(session); db.addSchemaObject(session, table); } try { for (Column c : data.columns) { c.prepareExpression(session); } for (Sequence sequence : sequences) { table.addSequence(sequence); } for (DefineCommand command : constraintCommands) { command.setTransactional(transactional); command.update(); } if (asQuery != null) { Insert insert = null; insert = new Insert(session); insert.setSortedInsertMode(sortedInsertMode); insert.setQuery(asQuery); insert.setTable(table); insert.setInsertFromSelect(true); insert.prepare(); insert.update(); } } catch (DbException e) { db.checkPowerOff(); db.removeSchemaObject(session, table); if (!transactional) { session.commit(true); } throw e; } return 0; } protected Table createTable(CreateTableData data) { return getSchema().createTable(data); } private void generateColumnsFromQuery() { int columnCount = asQuery.getColumnCount(); ArrayList<Expression> expressions = asQuery.getExpressions(); for (int i = 0; i < columnCount; i++) { Expression expr = expressions.get(i); int type = expr.getType(); String name = expr.getAlias(); long precision = expr.getPrecision(); int displaySize = expr.getDisplaySize(); DataType dt = DataType.getDataType(type); if (precision > 0 && (dt.defaultPrecision == 0 || (dt.defaultPrecision > precision && dt.defaultPrecision < Byte.MAX_VALUE))) { // dont' set precision to MAX_VALUE if this is the default precision = dt.defaultPrecision; } int scale = expr.getScale(); if (scale > 0 && (dt.defaultScale == 0 || (dt.defaultScale > scale && dt.defaultScale < precision))) { scale = dt.defaultScale; } if (scale > precision) { precision = scale; } Column col = new Column(name, type, precision, scale, displaySize); addColumn(col); } } /** * 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; } public void setPersistIndexes(boolean persistIndexes) { data.persistIndexes = persistIndexes; } public void setGlobalTemporary(boolean globalTemporary) { data.globalTemporary = globalTemporary; } /** This temporary table is dropped on commit. */ public void setOnCommitDrop() { this.onCommitDrop = true; } /** This temporary table is truncated on commit. */ public void setOnCommitTruncate() { this.onCommitTruncate = true; } public void setComment(String comment) { this.comment = comment; } public void setPersistData(boolean persistData) { data.persistData = persistData; if (!persistData) { data.persistIndexes = false; } } public void setSortedInsertMode(boolean sortedInsertMode) { this.sortedInsertMode = sortedInsertMode; } public void setStorageEngine(String storageEngine) { data.storageEngine = storageEngine; } public void setHidden(boolean isHidden) { data.isHidden = isHidden; } @Override public int getType() { return CommandInterface.CREATE_TABLE; } }