/*
* Copyright 2014-2015 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the “License”);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an “AS IS” BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wplatform.ddal.dbobject.schema;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import com.wplatform.ddal.command.ddl.CreateTableData;
import com.wplatform.ddal.dbobject.DbObject;
import com.wplatform.ddal.dbobject.DbObjectBase;
import com.wplatform.ddal.dbobject.FunctionAlias;
import com.wplatform.ddal.dbobject.User;
import com.wplatform.ddal.dbobject.index.Index;
import com.wplatform.ddal.dbobject.table.Table;
import com.wplatform.ddal.dispatch.rule.TableNode;
import com.wplatform.ddal.engine.Database;
import com.wplatform.ddal.engine.Session;
import com.wplatform.ddal.engine.SysProperties;
import com.wplatform.ddal.message.DbException;
import com.wplatform.ddal.message.ErrorCode;
import com.wplatform.ddal.message.Trace;
import com.wplatform.ddal.util.New;
/**
* A schema as created by the SQL statement CREATE SCHEMA
*/
public class Schema extends DbObjectBase {
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, 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();
private User owner;
private TableNode matedataNode;
/**
* 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();
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;
}
@Override
public int getType() {
return DbObject.SCHEMA;
}
@Override
public void removeChildrenAndResources(Session session) {
// 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;
invalidate();
}
@Override
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.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 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 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 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 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() {
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) {
data.schema = this;
throw DbException.getUnsupportedException("Create table unsupported");
}
}
/**
* @return the matedataNode
*/
public TableNode getMatedataNode() {
return matedataNode;
}
/**
* @param matedataNode the matedataNode to set
*/
public void setMatedataNode(TableNode matedataNode) {
this.matedataNode = matedataNode;
}
}