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);
     }
   }
 }
Example #2
0
 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();
 }
Example #3
0
 /**
  * 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());
   }
 }
Example #5
0
 @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);
 }
Example #6
0
  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;
  }
Example #7
0
 /**
  * Get all tables and views.
  *
  * @return a (possible empty) list of all objects
  */
 public ArrayList<Table> getAllTablesAndViews() {
   synchronized (database) {
     return New.arrayList(tablesAndViews.values());
   }
 }
Example #8
0
 /**
  * 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());
 }
Example #9
0
/** 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"));
    }
  }
}
Example #10
0
/** 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;
  }
}
Example #11
0
/**
 * 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;
  }
Example #13
0
 @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;
 }
Example #14
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;
  }
}