/* * 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; } }