/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.schema;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import org.h2.api.TableEngine;
import org.h2.command.ddl.CreateTableData;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.constraint.Constraint;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
import org.h2.engine.DbObjectBase;
import org.h2.engine.FunctionAlias;
import org.h2.engine.Session;
import org.h2.engine.User;
import org.h2.index.Index;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.table.RegularTable;
import org.h2.table.Table;
import org.h2.table.TableLink;
import org.h2.util.New;
import org.h2.util.Utils;
/**
* 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;
/**
* 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) {
tablesAndViews = database.newStringMap();
indexes = database.newStringMap();
sequences = database.newStringMap();
triggers = database.newStringMap();
constraints = database.newStringMap();
constants = database.newStringMap();
functions = database.newStringMap();
initDbObjectBase(database, id, schemaName, Trace.SCHEMA);
this.owner = owner;
this.system = system;
}
/**
* 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;
}
public String getCreateSQLForCopy(Table table, String quotedName) {
throw DbException.throwInternalError();
}
public String getDropSQL() {
return null;
}
public String getCreateSQL() {
if (system) {
return null;
}
return "CREATE SCHEMA IF NOT EXISTS " +
getSQL() + " AUTHORIZATION " + owner.getSQL();
}
public int getType() {
return DbObject.SCHEMA;
}
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);
}
while (tablesAndViews != null && tablesAndViews.size() > 0) {
Table obj = (Table) tablesAndViews.values().toArray()[0];
database.removeSchemaObject(session, obj);
}
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);
}
database.removeMeta(session, getId());
owner = null;
invalidate();
}
public void checkRename() {
// ok
}
/**
* Get the owner of this schema.
*
* @return the owner
*/
public User getOwner() {
return owner;
}
@SuppressWarnings("unchecked")
private HashMap<String, SchemaObject> getMap(int type) {
HashMap<String, ? extends SchemaObject> result;
switch (type) {
case DbObject.TABLE_OR_VIEW:
result = tablesAndViews;
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 (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) {
int 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(Session 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(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.
*
* @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(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.
*
* @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(Session 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(Session 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(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.
*
* @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(DbObject.TABLE_OR_VIEW).values());
all.addAll(getMap(DbObject.SEQUENCE).values());
all.addAll(getMap(DbObject.INDEX).values());
all.addAll(getMap(DbObject.TRIGGER).values());
all.addAll(getMap(DbObject.CONSTRAINT).values());
all.addAll(getMap(DbObject.CONSTANT).values());
all.addAll(getMap(DbObject.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(int 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() {
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.tableEngine != null) {
TableEngine engine;
try {
engine = (TableEngine) Utils.loadUserClass(data.tableEngine).newInstance();
} catch (Exception e) {
throw DbException.convert(e);
}
return engine.createTable(data);
}
return new RegularTable(data);
}
}
/**
* 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);
}
}
}