/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.ais.model; import com.foundationdb.ais.model.validation.AISValidation; import com.foundationdb.ais.model.validation.AISValidationFailure; import com.foundationdb.ais.model.validation.AISValidationOutput; import com.foundationdb.ais.model.validation.AISValidationResults; import com.foundationdb.server.types.common.types.StringFactory; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class AkibanInformationSchema implements Visitable { public static String getDefaultCharsetName() { return defaultCharsetName; } public static String getDefaultCollationName() { return defaultCollationName; } public static int getDefaultCharsetId() { return StringFactory.charsetNameToId(defaultCharsetName); } public static int getDefaultCollationId() { return StringFactory.collationNameToId(defaultCollationName); } public static void setDefaultCharsetAndCollation(String charsetName, String collationName) { defaultCharsetName = charsetName; defaultCollationName = collationName; } public AkibanInformationSchema() { charsetId = getDefaultCharsetId(); collationId = getDefaultCollationId(); } public AkibanInformationSchema(int generation) { this(); this.generation = generation; } // AkibanInformationSchema interface public String getDescription() { StringBuilder buffer = new StringBuilder(); buffer.append("AkibanInformationSchema("); boolean first = true; for (Group group : groups.values()) { if (first) { first = false; } else { buffer.append(", "); } buffer.append(group.getDescription()); } buffer.append(")"); return buffer.toString(); } /** deprecate? Use the fully qualified version {@link #getGroup(TableName)} **/ public Group getGroup(final String groupName) { Group candidate = null; for(Group group : groups.values()) { if(group.getName().getTableName().equals(groupName)) { if(candidate != null) { throw new IllegalArgumentException("Ambiguous group name: " + groupName); } candidate = group; } } return candidate; } public Group getGroup(final TableName groupName) { return groups.get(groupName); } public Map<TableName, Group> getGroups() { return groups; } public Map<TableName, Table> getTables() { return tables; } public void removeGroup(Group group) { groups.remove(group.getName()); } public Table getTable(final String schemaName, final String tableName) { return getTable(new TableName(schemaName, tableName)); } public Table getTable(final TableName tableName) { return tables.get(tableName); } public synchronized Table getTable(int tableId) { ensureTableIdLookup(); return tablesById.get(tableId); } public Map<TableName, View> getViews() { return views; } public View getView(final String schemaName, final String tableName) { return getView(new TableName(schemaName, tableName)); } public View getView(final TableName tableName) { return views.get(tableName); } public Columnar getColumnar(String schemaName, String tableName) { Columnar columnar = getTable(schemaName, tableName); if (columnar == null) { columnar = getView(schemaName, tableName); } return columnar; } public Columnar getColumnar(TableName tableName) { Columnar columnar = getTable(tableName); if (columnar == null) { columnar = getView(tableName); } return columnar; } public Map<String, Join> getJoins() { return joins; } public Join getJoin(String joinName) { return joins.get(joinName); } public Map<String, Schema> getSchemas() { return schemas; } public Schema getSchema(String schema) { return schemas.get(schema); } public Map<TableName, Sequence> getSequences() { return sequences; } public Sequence getSequence (final TableName sequenceName) { return sequences.get(sequenceName); } public Map<TableName, Routine> getRoutines() { return routines; } public Routine getRoutine(final String schemaName, final String routineName) { return getRoutine(new TableName(schemaName, routineName)); } public Routine getRoutine(final TableName routineName) { return routines.get(routineName); } public Map<TableName, SQLJJar> getSQLJJars() { return sqljJars; } public SQLJJar getSQLJJar(final String schemaName, final String jarName) { return getSQLJJar(new TableName(schemaName, jarName)); } public SQLJJar getSQLJJar(final TableName name) { return sqljJars.get(name); } public int getCharsetId() { return charsetId; } public String getCharsetName() { return StringFactory.charsetIdToName(charsetId); } public int getCollationId() { return collationId; } public String getCollationName() { return StringFactory.collationIdToName(collationId); } public Map<TableName, Constraint> getConstraints() { return constraints; } public Constraint getConstraint(final TableName constraintName) { return constraints.get(constraintName); } // AkibanInformationSchema interface public void addGroup(Group group) { groups.put(group.getName(), group); } public void addTable(Table table) { TableName tableName = table.getName(); tables.put(tableName, table); // TODO: Create on demand until Schema is more of a first class citizen Schema schema = getSchema(tableName.getSchemaName()); if (schema == null) { schema = new Schema(tableName.getSchemaName()); addSchema(schema); } schema.addTable(table); } public void addView(View view) { TableName viewName = view.getName(); views.put(viewName, view); Schema schema = getSchema(viewName.getSchemaName()); if (schema == null) { schema = new Schema(viewName.getSchemaName()); addSchema(schema); } schema.addView(view); } public void addJoin(Join join) { joins.put(join.getName(), join); } public void addSchema(Schema schema) { schemas.put(schema.getName(), schema); } public void addSequence (Sequence seq) { TableName sequenceName = seq.getSequenceName(); sequences.put(sequenceName, seq); // TODO: Create on demand until Schema is more of a first class citizen Schema schema = getSchema(sequenceName.getSchemaName()); if (schema == null) { schema = new Schema(sequenceName.getSchemaName()); addSchema(schema); } schema.addSequence(seq); } public void addRoutine(Routine routine) { TableName routineName = routine.getName(); routines.put(routineName, routine); // TODO: Create on demand until Schema is more of a first class citizen Schema schema = getSchema(routineName.getSchemaName()); if (schema == null) { schema = new Schema(routineName.getSchemaName()); addSchema(schema); } schema.addRoutine(routine); } public void addSQLJJar(SQLJJar sqljJar) { TableName jarName = sqljJar.getName(); sqljJars.put(jarName, sqljJar); // TODO: Create on demand until Schema is more of a first class citizen Schema schema = getSchema(jarName.getSchemaName()); if (schema == null) { schema = new Schema(jarName.getSchemaName()); addSchema(schema); } schema.addSQLJJar(sqljJar); } public void addConstraint(Constraint constraint){ TableName constraintName = constraint.getConstraintName(); constraints.put(constraintName, constraint); Schema schema = getSchema(constraintName.getSchemaName()); if(schema == null) { schema = new Schema(constraintName.getSchemaName()); addSchema(schema); } schema.addConstraint(constraint); } public void deleteGroup(Group group) { Group removedGroup = groups.remove(group.getName()); assert removedGroup == group; } /** * Validates this AIS against the given validations. All validations will run, even if one fails (unless any * throw an unchecked exception). * @param validations the validations to run * @return the result of the validations */ public AISValidationResults validate(Collection<? extends AISValidation> validations) { AISFailureList validationFailures = new AISFailureList(); for (AISValidation v : validations) { v.validate(this, validationFailures); } return validationFailures; } /** * Marks this AIS as frozen; it is now immutable, and any safe publication to another thread will guarantee * that the AIS will not change from under that thread. */ public void freeze() { isFrozen = true; } public boolean isFrozen() { return isFrozen; } /** For use within the AIS package; throws an exception if isFrozen is false */ void checkMutability() throws IllegalStateException { if (isFrozen) { throw new IllegalStateException ("Attempting to modify a frozen AIS"); } } synchronized void invalidateTableIdMap() { tablesById = null; } private void ensureTableIdLookup() { if (tablesById == null) { tablesById = new HashMap<>(); for (Table table : tables.values()) { tablesById.put(table.getTableId(), table); } } } void removeTable(TableName name) { tables.remove(name); Schema schema = getSchema(name.getSchemaName()); if (schema != null) { schema.removeTable(name.getTableName()); } invalidateTableIdMap(); } public void removeSequence (TableName name) { sequences.remove(name); Schema schema = getSchema(name.getSchemaName()); if (schema != null) { schema.removeSequence(name.getTableName()); } } public void removeRoutine(TableName name) { routines.remove(name); Schema schema = getSchema(name.getSchemaName()); if (schema != null) { schema.removeRoutine(name.getTableName()); } } public void removeView(TableName name) { views.remove(name); Schema schema = getSchema(name.getSchemaName()); if (schema != null) { schema.removeView(name.getTableName()); } } public void removeSQLJJar(TableName jarName) { sqljJars.remove(jarName); Schema schema = getSchema(jarName.getSchemaName()); if (schema != null) { schema.removeSQLJJar(jarName.getTableName()); } } public void removeConstraint(TableName name) { constraints.remove(name); Schema schema = getSchema(name.getSchemaName()); if (schema != null) { schema.removeConstraint(name.getTableName()); } } public long getGeneration() { return generation; } public void setGeneration(long generation) { if(this.generation != -1) { // TODO: Cleanup. Ideally add generation to constructor checkMutability(); } this.generation = generation; } @SuppressWarnings("unchecked") // Expected, value type varies public <V> V getCachedValue(Object key, CacheValueGenerator<V> generator) { Object val = cachedValues.get(key); if(val == null && generator != null) { val = generator.valueFor(this); Object firstVal = cachedValues.putIfAbsent(key, val); if(firstVal != null) { val = firstVal; // Someone got here before, use theirs } } return (V)val; } @Override public String toString() { return "AIS(" + generation + ")"; } // Visitable /** Visit every group. */ @Override public void visit(Visitor visitor) { for(Group g : groups.values()) { g.visit(visitor); } } // State private static String defaultCharsetName = "utf8"; private static String defaultCollationName = "UCS_BINARY"; private final int charsetId, collationId; private final Map<TableName, Group> groups = new TreeMap<>(); private final Map<TableName, Table> tables = new TreeMap<>(); private final Map<TableName, Sequence> sequences = new TreeMap<>(); private final Map<TableName, View> views = new TreeMap<>(); private final Map<TableName, Routine> routines = new TreeMap<>(); private final Map<TableName, SQLJJar> sqljJars = new TreeMap<>(); private final Map<TableName, Constraint> constraints = new TreeMap<>(); private final Map<String, Join> joins = new TreeMap<>(); private final Map<String, Schema> schemas = new TreeMap<>(); private final ConcurrentMap cachedValues = new ConcurrentHashMap(4,0.75f,4); // Very few, write-once entries expected private long generation = -1; private Map<Integer, Table> tablesById = null; private boolean isFrozen = false; private static class AISFailureList extends AISValidationResults implements AISValidationOutput { @Override public void reportFailure(AISValidationFailure failure) { if (failure != null) { failureList.add(failure); } } public AISFailureList() { failureList = new LinkedList<>(); } } }