/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.schema;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.h2.api.ErrorCode;
import org.h2.command.ddl.CreateSynonymData;
import org.h2.command.ddl.CreateTableData;
import org.h2.constraint.Constraint;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
import org.h2.engine.DbObjectBase;
import org.h2.engine.DbSettings;
import org.h2.engine.FunctionAlias;
import org.h2.engine.Right;
import org.h2.engine.Session;
import org.h2.engine.SysProperties;
import org.h2.engine.User;
import org.h2.index.Index;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.mvstore.db.MVTableEngine;
import org.h2.table.PageStoreTable;
import org.h2.table.Table;
import org.h2.table.TableLink;
import org.h2.table.TableSynonym;
import org.h2.util.StringUtils;
import org.h2.util.Utils;

A schema as created by the SQL statement CREATE SCHEMA
/** * A schema as created by the SQL statement * CREATE SCHEMA */
public class Schema extends DbObjectBase { private User owner; private final boolean system; private ArrayList<String> tableEngineParams; private final ConcurrentHashMap<String, Table> tablesAndViews; private final ConcurrentHashMap<String, TableSynonym> synonyms; private final ConcurrentHashMap<String, Index> indexes; private final ConcurrentHashMap<String, Sequence> sequences; private final ConcurrentHashMap<String, TriggerObject> triggers; private final ConcurrentHashMap<String, Constraint> constraints; private final ConcurrentHashMap<String, Constant> constants; private final ConcurrentHashMap<String, FunctionAlias> functions;
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.
/** * 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.
Params:
  • database – the database
  • id – the object id
  • schemaName – the schema name
  • owner – the owner of the schema
  • system – if this is a system schema (such a schema can not be dropped)
/** * 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.newConcurrentStringMap(); synonyms = database.newConcurrentStringMap(); indexes = database.newConcurrentStringMap(); sequences = database.newConcurrentStringMap(); triggers = database.newConcurrentStringMap(); constraints = database.newConcurrentStringMap(); constants = database.newConcurrentStringMap(); functions = database.newConcurrentStringMap(); this.owner = owner; this.system = system; }
Check if this schema can be dropped. System schemas can not be dropped.
Returns:true if it can be dropped
/** * 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 getCreateSQLForCopy(Table table, String quotedName) { throw DbException.throwInternalError(toString()); } @Override public String getDropSQL() { return null; } @Override public String getCreateSQL() { if (system) { return null; } StringBuilder builder = new StringBuilder("CREATE SCHEMA IF NOT EXISTS "); getSQL(builder, true).append(" AUTHORIZATION "); owner.getSQL(builder, true); return builder.toString(); } @Override public int getType() { return DbObject.SCHEMA; }
Return whether is this schema is empty (does not contain any objects).
Returns:true if this schema is empty, false otherwise
/** * Return whether is this schema is empty (does not contain any objects). * * @return {@code true} if this schema is empty, {@code false} otherwise */
public boolean isEmpty() { return tablesAndViews.isEmpty() && synonyms.isEmpty() && indexes.isEmpty() && sequences.isEmpty() && triggers.isEmpty() && constraints.isEmpty() && constants.isEmpty() && functions.isEmpty(); } @Override public ArrayList<DbObject> getChildren() { ArrayList<DbObject> children = Utils.newSmallArrayList(); ArrayList<Right> rights = database.getAllRights(); for (Right right : rights) { if (right.getGrantedObject() == this) { children.add(right); } } return children; } @Override public void removeChildrenAndResources(Session 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); } for (Right right : database.getAllRights()) { if (right.getGrantedObject() == this) { database.removeDatabaseObject(session, right); } } database.removeMeta(session, getId()); owner = null; invalidate(); } @Override public void checkRename() { // ok }
Get the owner of this schema.
Returns:the owner
/** * Get the owner of this schema. * * @return the owner */
public User getOwner() { return owner; }
Get table engine params of this schema.
Returns:default table engine params
/** * Get table engine params of this schema. * * @return default table engine params */
public ArrayList<String> getTableEngineParams() { return tableEngineParams; }
Set table engine params of this schema.
Params:
  • tableEngineParams – default table engine params
/** * Set table engine params of this schema. * @param tableEngineParams default table engine params */
public void setTableEngineParams(ArrayList<String> tableEngineParams) { this.tableEngineParams = tableEngineParams; } @SuppressWarnings("unchecked") private Map<String, SchemaObject> getMap(int type) { Map<String, ? extends SchemaObject> result; switch (type) { case DbObject.TABLE_OR_VIEW: result = tablesAndViews; break; case DbObject.SYNONYM: result = synonyms; break; case DbObject.SEQUENCE: result = sequences; break; case DbObject.INDEX: result = indexes; break; case DbObject.TRIGGER: result = triggers; break; case DbObject.CONSTRAINT: result = constraints; break; case DbObject.CONSTANT: result = constants; break; case DbObject.FUNCTION_ALIAS: result = functions; break; default: throw DbException.throwInternalError("type=" + type); } return (Map<String, SchemaObject>) result; }
Add an object to this schema. This method must not be called within CreateSchemaObject; use Database.addSchemaObject() instead
Params:
  • obj – the object to add
/** * 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 (obj.getSchema() != this) { DbException.throwInternalError("wrong schema"); } String name = obj.getName(); Map<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.
Params:
  • obj – the object to rename
  • newName – the new name
/** * Rename an object. * * @param obj the object to rename * @param newName the new name */
public void rename(SchemaObject obj, String newName) { int type = obj.getType(); Map<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. Synonyms are not returned or resolved.
Params:
  • session – the session
  • name – the object name
Returns:the object or null
/** * 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. Synonyms are not returned or resolved. * * @param session the session * @param name the object name * @return the object or null */
public Table findTableOrView(Session session, String name) { Table table = tablesAndViews.get(name); if (table == null && session != null) { table = session.findLocalTempTable(name); } return table; }
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. If a synonym with this name exists, the backing table of the synonym is returned
Params:
  • session – the session
  • name – the object name
Returns:the object or null
/** * 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. If a synonym with this name exists, the backing table of the * synonym is returned * * @param session the session * @param name the object name * @return the object or null */
public Table resolveTableOrView(Session session, String name) { Table table = findTableOrView(session, name); if (table == null) { TableSynonym synonym = synonyms.get(name); if (synonym != null) { return synonym.getSynonymFor(); } } return table; }
Try to find a synonym with this name. This method returns null if no object with this name exists.
Params:
  • name – the object name
Returns:the object or null
/** * Try to find a synonym 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 TableSynonym getSynonym(String name) { return synonyms.get(name); }
Try to find an index with this name. This method returns null if no object with this name exists.
Params:
  • session – the session
  • name – the object name
Returns:the object or null
/** * 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(Session 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.
Params:
  • name – the object name
Returns:the object or null
/** * 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.
Params:
  • sequenceName – the object name
Returns:the object or null
/** * 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.
Params:
  • session – the session
  • name – the object name
Returns:the object or null
/** * 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(Session 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.
Params:
  • constantName – the object name
Returns:the object or null
/** * 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.
Params:
  • functionAlias – the object name
Returns:the object or null
/** * 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.
Params:
  • name – the object name
/** * 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, Map<String, ? extends SchemaObject> map, String prefix) { String hash = StringUtils.toUpperEnglish(Integer.toHexString(obj.getName().hashCode())); 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.
Params:
  • session – the session
  • table – the constraint table
Returns:the unique name
/** * Create a unique constraint name. * * @param session the session * @param table the constraint table * @return the unique name */
public String getUniqueConstraintName(Session session, Table table) { Map<String, Constraint> tableConstraints; if (table.isTemporary() && !table.isGlobalTemporary()) { tableConstraints = session.getLocalTempTableConstraints(); } else { tableConstraints = constraints; } return getUniqueName(table, tableConstraints, "CONSTRAINT_"); }
Create a unique index name.
Params:
  • session – the session
  • table – the indexed table
  • prefix – the index name prefix
Returns:the unique name
/** * 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(Session session, Table table, String prefix) { Map<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.
Params:
  • session – the session
  • name – the table or view name
Throws:
Returns:the table or view
/** * 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(Session 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.
Params:
  • name – the index name
Throws:
Returns:the index
/** * 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.
Params:
  • name – the constraint name
Throws:
Returns:the constraint
/** * 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.
Params:
  • constantName – the constant name
Throws:
Returns:the constant
/** * 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.
Params:
  • sequenceName – the sequence name
Throws:
Returns:the sequence
/** * 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.
Params:
  • addTo – list to add objects to, or null to allocate a new list
Returns:the specified list with added objects, or a new (possibly empty) list with all objects
/** * Get all objects. * * @param addTo * list to add objects to, or {@code null} to allocate a new * list * @return the specified list with added objects, or a new (possibly empty) list * with all objects */
public ArrayList<SchemaObject> getAll(ArrayList<SchemaObject> addTo) { if (addTo == null) { addTo = Utils.newSmallArrayList(); } addTo.addAll(tablesAndViews.values()); addTo.addAll(synonyms.values()); addTo.addAll(sequences.values()); addTo.addAll(indexes.values()); addTo.addAll(triggers.values()); addTo.addAll(constraints.values()); addTo.addAll(constants.values()); addTo.addAll(functions.values()); return addTo; }
Get all objects of the given type.
Params:
  • type – the object type
  • addTo – list to add objects to, or null to allocate a new list
Returns:the specified list with added objects, or a new (possibly empty) list with objects of the given type
/** * Get all objects of the given type. * * @param type * the object type * @param addTo * list to add objects to, or {@code null} to allocate a new * list * @return the specified list with added objects, or a new (possibly empty) list * with objects of the given type */
public ArrayList<SchemaObject> getAll(int type, ArrayList<SchemaObject> addTo) { Collection<SchemaObject> values = getMap(type).values(); if (addTo != null) { addTo.addAll(values); } else { addTo = new ArrayList<>(values); } return addTo; }
Get all tables and views.
Returns:a (possible empty) list of all objects
/** * Get all tables and views. * * @return a (possible empty) list of all objects */
public ArrayList<Table> getAllTablesAndViews() { synchronized (database) { return new ArrayList<>(tablesAndViews.values()); } } public ArrayList<TableSynonym> getAllSynonyms() { synchronized (database) { return new ArrayList<>(synonyms.values()); } }
Get the table with the given name, if any.
Params:
  • name – the table name
Returns:the table or null if not found
/** * Get the table with the given name, if any. * * @param name the table name * @return the table or null if not found */
public Table getTableOrViewByName(String name) { synchronized (database) { return tablesAndViews.get(name); } }
Remove an object from this schema.
Params:
  • obj – the object to remove
/** * Remove an object from this schema. * * @param obj the object to remove */
public void remove(SchemaObject obj) { String objName = obj.getName(); Map<String, SchemaObject> map = getMap(obj.getType()); if (map.remove(objName) == null) { DbException.throwInternalError("not found: " + objName); } freeUniqueName(objName); }
Add a table to the schema.
Params:
  • data – the create table information
Returns:the created Table object
/** * 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.tableEngine == null) { DbSettings s = database.getSettings(); if (s.defaultTableEngine != null) { data.tableEngine = s.defaultTableEngine; } else if (s.mvStore) { data.tableEngine = MVTableEngine.class.getName(); } } if (data.tableEngine != null) { if (data.tableEngineParams == null) { data.tableEngineParams = this.tableEngineParams; } return database.getTableEngine(data.tableEngine).createTable(data); } return new PageStoreTable(data); } }
Add a table synonym to the schema.
Params:
  • data – the create synonym information
Returns:the created TableSynonym object
/** * Add a table synonym to the schema. * * @param data the create synonym information * @return the created {@link TableSynonym} object */
public TableSynonym createSynonym(CreateSynonymData data) { synchronized (database) { database.lockMeta(data.session); data.schema = this; return new TableSynonym(data); } }
Add a linked table to the schema.
Params:
  • id – the object id
  • tableName – the table name of the alias
  • driver – the driver class name
  • url – the database URL
  • user – the user name
  • password – the password
  • originalSchema – the schema name of the target table
  • originalTable – the table name of the target table
  • emitUpdates – if updates should be emitted instead of delete/insert
  • force – create the object even if the database can not be accessed
Returns:the TableLink object
/** * Add a linked table to the schema. * * @param id the object id * @param tableName the table name of the alias * @param driver the driver class name * @param url the database URL * @param user the user name * @param password the password * @param originalSchema the schema name of the target table * @param originalTable the table name of the target table * @param emitUpdates if updates should be emitted instead of delete/insert * @param force create the object even if the database can not be accessed * @return the {@link TableLink} object */
public TableLink createTableLink(int id, String tableName, String driver, String url, String user, String password, String originalSchema, String originalTable, boolean emitUpdates, boolean force) { synchronized (database) { return new TableLink(this, id, tableName, driver, url, user, password, originalSchema, originalTable, emitUpdates, force); } } }