/* * 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.engine; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import com.wplatform.ddal.command.dml.SetTypes; import com.wplatform.ddal.config.Configuration; import com.wplatform.ddal.config.SchemaConfig; import com.wplatform.ddal.config.TableConfig; import com.wplatform.ddal.dbobject.Comment; import com.wplatform.ddal.dbobject.DbObject; import com.wplatform.ddal.dbobject.Right; import com.wplatform.ddal.dbobject.Role; import com.wplatform.ddal.dbobject.Setting; import com.wplatform.ddal.dbobject.User; import com.wplatform.ddal.dbobject.index.Index; import com.wplatform.ddal.dbobject.schema.Schema; import com.wplatform.ddal.dbobject.schema.SchemaObject; import com.wplatform.ddal.dbobject.table.Table; import com.wplatform.ddal.dbobject.table.TableMate; import com.wplatform.ddal.dispatch.RoutingHandler; import com.wplatform.ddal.dispatch.RoutingHandlerImpl; import com.wplatform.ddal.excutor.ExecutorFactory; import com.wplatform.ddal.excutor.PreparedExecutorFactory; import com.wplatform.ddal.message.DbException; import com.wplatform.ddal.message.ErrorCode; import com.wplatform.ddal.message.Trace; import com.wplatform.ddal.message.TraceSystem; import com.wplatform.ddal.shards.DataSourceRepository; import com.wplatform.ddal.util.BitField; import com.wplatform.ddal.util.New; import com.wplatform.ddal.util.SourceCompiler; import com.wplatform.ddal.util.StringUtils; import com.wplatform.ddal.value.CaseInsensitiveMap; import com.wplatform.ddal.value.CompareMode; import com.wplatform.ddal.value.Value; /** * @author <a href="mailto:jorgie.mail@gmail.com">jorgie li</a> */ public class Database { public static final String SYSTEM_USER_NAME = "MASTER"; private final HashMap<String, Role> roles = New.hashMap(); private final HashMap<String, User> users = New.hashMap(); private final HashMap<String, Setting> settings = New.hashMap(); private final HashMap<String, Schema> schemas = New.hashMap(); private final HashMap<String, Right> rights = New.hashMap(); private final HashMap<String, Comment> comments = New.hashMap(); private final Set<Session> userSessions = Collections.synchronizedSet(new HashSet<Session>()); private final BitField objectIds = new BitField(); private final DbSettings dbSettings; private final DataSourceRepository dsRepository; private final Configuration configuration; private int nextSessionId; private TraceSystem traceSystem; private Trace trace; private CompareMode compareMode; private int allowLiterals = Constants.ALLOW_LITERALS_ALL; private volatile boolean closing; private boolean ignoreCase;// for data type VARCHAR_IGNORECASE private Mode mode = Mode.getInstance(Mode.REGULAR); private int maxMemoryRows = SysProperties.MAX_MEMORY_ROWS; private int maxOperationMemory = Constants.DEFAULT_MAX_OPERATION_MEMORY; private SourceCompiler compiler; private RoutingHandler routingHandler; private PreparedExecutorFactory peFactory; public Database(Configuration configuration) { this.configuration = configuration; this.compareMode = CompareMode.getInstance(null, 0); this.dbSettings = DbSettings.getInstance(configuration.getSettings()); String sqlMode = configuration.getProperty(SetTypes.MODE, Mode.MY_SQL); Mode settingMode = Mode.getInstance(sqlMode); if (settingMode != null) { this.mode = settingMode; } traceSystem = new TraceSystem(null); traceSystem.setLevelFile(TraceSystem.ADAPTER); trace = traceSystem.getTrace(Trace.DATABASE); dsRepository = new DataSourceRepository(this); openDatabase(); } private synchronized void openDatabase() { User systemUser = new User(this, allocateObjectId(), SYSTEM_USER_NAME); systemUser.setAdmin(true); systemUser.setUserPasswordHash(new byte[0]); users.put(SYSTEM_USER_NAME, systemUser); Schema schema = new Schema(this, allocateObjectId(), Constants.SCHEMA_MAIN, systemUser, true); schemas.put(schema.getName(), schema); Role publicRole = new Role(this, 0, Constants.PUBLIC_ROLE_NAME, true); roles.put(Constants.PUBLIC_ROLE_NAME, publicRole); Session sysSession = createSession(systemUser); try { SchemaConfig sc = configuration.getSchemaConfig(); List<TableConfig> ctList = sc.getTables(); for (TableConfig tableConfig : ctList) { String identifier = tableConfig.getName(); identifier = identifier(identifier); TableMate tableMate = new TableMate(schema, allocateObjectId(), identifier); tableMate.setTableRouter(tableConfig.getTableRouter()); tableMate.setShards(tableConfig.getShards()); tableMate.setScanLevel(tableConfig.getScanLevel()); tableMate.loadMataData(sysSession); if (tableConfig.isValidation()) { tableMate.check(); } this.addSchemaObject(tableMate); } } finally { sysSession.close(); } } /** * Check if two values are equal with the current comparison mode. * * @param a the first value * @param b the second value * @return true if both objects are equal */ public boolean areEqual(Value a, Value b) { // can not use equals because ValueDecimal 0.0 is not equal to 0.00. return a.compareTo(b, compareMode) == 0; } /** * Compare two values with the current comparison mode. The values may not * be of the same type. * * @param a the first value * @param b the second value * @return 0 if both values are equal, -1 if the first value is smaller, and * 1 otherwise */ public int compare(Value a, Value b) { return a.compareTo(b, compareMode); } /** * Compare two values with the current comparison mode. The values must be * of the same type. * * @param a the first value * @param b the second value * @return 0 if both values are equal, -1 if the first value is smaller, and * 1 otherwise */ public int compareTypeSave(Value a, Value b) { return a.compareTypeSave(b, compareMode); } /** * Get the trace object for the given module. * * @param module the module name * @return the trace object */ public Trace getTrace(String module) { return traceSystem.getTrace(module); } @SuppressWarnings("unchecked") private HashMap<String, DbObject> getMap(int type) { HashMap<String, ? extends DbObject> result; switch (type) { case DbObject.USER: result = users; break; case DbObject.SETTING: result = settings; break; case DbObject.ROLE: result = roles; break; case DbObject.RIGHT: result = rights; break; case DbObject.SCHEMA: result = schemas; break; case DbObject.COMMENT: result = comments; break; default: throw DbException.throwInternalError("type=" + type); } return (HashMap<String, DbObject>) result; } /** * Add a schema object to the database. * * @param obj the object to add */ public synchronized void addSchemaObject(SchemaObject obj) { obj.getSchema().add(obj); // trace.debug("addSchemaObject: {0}", obj.getCreateSQL()); } /** * Add an object to the database. * * @param obj the object to add */ public synchronized void addDatabaseObject(DbObject obj) { HashMap<String, DbObject> map = getMap(obj.getType()); String name = obj.getName(); if (SysProperties.CHECK && map.get(name) != null) { DbException.throwInternalError("object already exists"); } map.put(name, obj); } /** * Get the comment for the given database object if one exists, or null if * not. * * @param object the database object * @return the comment or null */ public Comment findComment(DbObject object) { if (object.getType() == DbObject.COMMENT) { return null; } String key = Comment.getKey(object); return comments.get(key); } /** * Get the role if it exists, or null if not. * * @param roleName the name of the role * @return the role or null */ public Role findRole(String roleName) { return roles.get(roleName); } /** * Get the schema if it exists, or null if not. * * @param schemaName the name of the schema * @return the schema or null */ public Schema findSchema(String schemaName) { Schema schema = schemas.get(schemaName); return schema; } /** * Get the setting if it exists, or null if not. * * @param name the name of the setting * @return the setting or null */ public Setting findSetting(String name) { return settings.get(name); } /** * Get the user if it exists, or null if not. * * @param name the name of the user * @return the user or null */ public User findUser(String name) { return users.get(name); } /** * Get user with the given name. This method throws an exception if the user * does not exist. * * @param name the user name * @return the user * @throws DbException if the user does not exist */ public User getUser(String name) { User user = findUser(name); if (user == null) { throw DbException.get(ErrorCode.USER_NOT_FOUND_1, name); } return user; } /** * Create a session for the given user. * * @param user the user * @return the session * @throws DbException if the database is in exclusive mode */ public synchronized Session createSession(User user) { Session session = new Session(this, user, ++nextSessionId); userSessions.add(session); trace.info("create session #{0}", session.getId(), "engine"); return session; } /** * Remove a session. This method is called after the user has disconnected. * * @param session the session */ public synchronized void removeSession(Session session) { if (session != null) { userSessions.remove(session); } } /** * Immediately close the database. */ public void shutdownImmediately() { close(); } /** * Close the database. */ public synchronized void close() { if (closing) { return; } closing = true; if (userSessions.size() > 0) { Session[] all = new Session[userSessions.size()]; userSessions.toArray(all); for (Session s : all) { try { // must roll back, otherwise the session is removed and // the transaction log that contains its uncommitted // operations as well s.rollback(); s.close(); } catch (DbException e) { trace.error(e, "disconnecting session #{0}", s.getId()); } } } dsRepository.close(); traceSystem.close(); } /** * Allocate a new object id. * * @return the id */ public synchronized int allocateObjectId() { int i = objectIds.nextClearBit(0); objectIds.set(i); return i; } public ArrayList<Comment> getAllComments() { return New.arrayList(comments.values()); } public int getAllowLiterals() { return allowLiterals; } public void setAllowLiterals(int value) { this.allowLiterals = value; } public ArrayList<Right> getAllRights() { return New.arrayList(rights.values()); } public ArrayList<Role> getAllRoles() { return New.arrayList(roles.values()); } /** * Get all schema objects. * * @return all objects of all types */ public ArrayList<SchemaObject> getAllSchemaObjects() { ArrayList<SchemaObject> list = New.arrayList(); for (Schema schema : schemas.values()) { list.addAll(schema.getAll()); } return list; } /** * Get all schema objects of the given type. * * @param type the int type */ public ArrayList<SchemaObject> getAllSchemaObjects(int type) { ArrayList<SchemaObject> list = New.arrayList(); for (Schema schema : schemas.values()) { list.addAll(schema.getAll(type)); } return list; } /** * Get all tables and views. * * @return all objects of that type */ public ArrayList<Table> getAllTablesAndViews() { ArrayList<Table> list = New.arrayList(); for (Schema schema : schemas.values()) { list.addAll(schema.getAllTablesAndViews()); } return list; } public ArrayList<Schema> getAllSchemas() { return New.arrayList(schemas.values()); } public ArrayList<Setting> getAllSettings() { return New.arrayList(settings.values()); } public ArrayList<User> getAllUsers() { return New.arrayList(users.values()); } public CompareMode getCompareMode() { return compareMode; } public void setCompareMode(CompareMode compareMode) { this.compareMode = compareMode; } /** * Get all sessions that are currently connected to the database. */ public Session[] getSessions() { ArrayList<Session> list; // need to synchronized on userSession, otherwise the list // may contain null elements synchronized (userSessions) { list = New.arrayList(userSessions); } Session[] array = new Session[list.size()]; list.toArray(array); return array; } /** * Rename a schema object. * * @param session the session * @param obj the object * @param newName the new name */ public synchronized void renameSchemaObject(Session session, SchemaObject obj, String newName) { obj.getSchema().rename(obj, newName); } /** * Rename a database object. * * @param session the session * @param obj the object * @param newName the new name */ public synchronized void renameDatabaseObject(Session session, DbObject obj, String newName) { int type = obj.getType(); HashMap<String, DbObject> 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()); obj.rename(newName); map.put(newName, obj); } /** * Get the schema. If the schema does not exist, an exception is thrown. * * @param schemaName the name of the schema * @return the schema * @throws DbException no schema with that name exists */ public Schema getSchema(String schemaName) { Schema schema = findSchema(schemaName); if (schema == null) { throw DbException.get(ErrorCode.SCHEMA_NOT_FOUND_1, schemaName); } return schema; } /** * Remove the object from the database. * * @param session the session * @param obj the object to remove */ public synchronized void removeDatabaseObject(Session session, DbObject obj) { String objName = obj.getName(); int type = obj.getType(); HashMap<String, DbObject> map = getMap(type); if (SysProperties.CHECK && !map.containsKey(objName)) { DbException.throwInternalError("not found: " + objName); } Comment comment = findComment(obj); if (comment != null) { removeDatabaseObject(session, comment); } obj.removeChildrenAndResources(session); map.remove(objName); } /** * Get the first table that depends on this object. * * @param obj the object to find * @param except the table to exclude (or null) * @return the first dependent table, or null */ public Table getDependentTable(SchemaObject obj, Table except) { switch (obj.getType()) { case DbObject.COMMENT: case DbObject.CONSTRAINT: case DbObject.INDEX: case DbObject.RIGHT: case DbObject.TRIGGER: case DbObject.USER: return null; default: } HashSet<DbObject> set = New.hashSet(); for (Table t : getAllTablesAndViews()) { if (except == t) { continue; } else if (Table.VIEW.equals(t.getTableType())) { continue; } set.clear(); t.addDependencies(set); if (set.contains(obj)) { return t; } } return null; } /** * Remove an object from the system table. * * @param session the session * @param obj the object to be removed */ public synchronized void removeSchemaObject(Session session, SchemaObject obj) { int type = obj.getType(); if (type == DbObject.TABLE_OR_VIEW) { Table table = (Table) obj; if (table.isTemporary() && !table.isGlobalTemporary()) { session.removeLocalTempTable(table); return; } } else if (type == DbObject.INDEX) { Index index = (Index) obj; Table table = index.getTable(); if (table.isTemporary() && !table.isGlobalTemporary()) { session.removeLocalTempTableIndex(index); return; } } Comment comment = findComment(obj); if (comment != null) { removeDatabaseObject(session, comment); } obj.getSchema().remove(obj); } public TraceSystem getTraceSystem() { return traceSystem; } /** * Commit the current transaction of the given session. * * @param session the session */ synchronized void commit(Session session) { session.setAllCommitted(); } /** * Check if the database is in the process of closing. * * @return true if the database is closing */ public boolean isClosing() { return closing; } public boolean getIgnoreCase() { return ignoreCase; } public void setIgnoreCase(boolean b) { ignoreCase = b; } public int getSessionCount() { return userSessions.size(); } public Mode getMode() { return mode; } public void setMode(Mode mode) { this.mode = mode; } public int getMaxOperationMemory() { return maxOperationMemory; } public void setMaxOperationMemory(int maxOperationMemory) { this.maxOperationMemory = maxOperationMemory; } public int getMaxMemoryRows() { return maxMemoryRows; } public void setMaxMemoryRows(int value) { this.maxMemoryRows = value; } public DbSettings getSettings() { return dbSettings; } /** * Create a new hash map. Depending on the configuration, the key is case * sensitive or case insensitive. * * @param <V> the value type * @return the hash map */ public <V> HashMap<String, V> newStringMap() { return dbSettings.databaseToUpper ? new HashMap<String, V>() : new CaseInsensitiveMap<V>(); } /** * Compare two identifiers (table names, column names,...) and verify they * are equal. Case sensitivity depends on the configuration. * * @param a the first identifier * @param b the second identifier * @return true if they match */ public boolean equalsIdentifiers(String a, String b) { if (a == b || a.equals(b)) { return true; } return !dbSettings.databaseToUpper && a.equalsIgnoreCase(b); } /** * String to database identifier against dbSettings * @param identifier * @return */ public String identifier(String identifier) { identifier = dbSettings.databaseToUpper ? StringUtils.toUpperEnglish(identifier) : identifier; return identifier; } /** * @return the configuration */ public Configuration getConfiguration() { return configuration; } public SourceCompiler getCompiler() { if (compiler == null) { compiler = new SourceCompiler(); } return compiler; } public RoutingHandler getRoutingHandler() { if (routingHandler == null) { routingHandler = new RoutingHandlerImpl(this); } return routingHandler; } /** * @return the dataSourceManager */ public DataSourceRepository getDataSourceRepository() { return dsRepository; } public PreparedExecutorFactory getPreparedExecutorFactory() { if(peFactory == null) { peFactory = new ExecutorFactory(); } return peFactory; } }