/** * 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.aisb2; import com.foundationdb.ais.model.AISBuilder; import com.foundationdb.ais.model.AkibanInformationSchema; import com.foundationdb.ais.model.Group; import com.foundationdb.ais.model.Index; import com.foundationdb.ais.model.Parameter; import com.foundationdb.ais.model.Routine; import com.foundationdb.ais.model.Table; import com.foundationdb.ais.model.TableName; import com.foundationdb.ais.model.View; import com.foundationdb.ais.model.validation.AISInvariants; import com.foundationdb.ais.model.validation.AISValidationResults; import com.foundationdb.ais.model.validation.AISValidations; import com.foundationdb.server.error.InvalidParameterValueException; import com.foundationdb.server.error.InvalidSQLJJarURLException; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.common.types.TypesTranslator; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.sql.Types; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.NoSuchElementException; import java.util.Properties; public class AISBBasedBuilder { public static NewAISBuilder create(TypesTranslator typesTranslator) { return new ActualBuilder(typesTranslator); } public static NewAISBuilder create(String defaultSchema, TypesTranslator typesTranslator) { return new ActualBuilder(typesTranslator).defaultSchema(defaultSchema); } private static class ActualBuilder implements NewViewBuilder, NewAkibanJoinBuilder, NewRoutineBuilder, NewSQLJJarBuilder { // NewAISProvider interface @Override public AkibanInformationSchema ais() { return ais(true); } @Override public AkibanInformationSchema ais(boolean freezeAIS) { usable = false; aisb.basicSchemaIsComplete(); aisb.groupingIsComplete(); AISValidationResults results = aisb.akibanInformationSchema().validate(AISValidations.BASIC_VALIDATIONS); results.throwIfNecessary(); if (freezeAIS) { aisb.akibanInformationSchema().freeze(); } return aisb.akibanInformationSchema(); } @Override public AkibanInformationSchema unvalidatedAIS() { aisb.basicSchemaIsComplete(); aisb.groupingIsComplete(); return aisb.akibanInformationSchema(); } // NewAISBuilder interface @Override public NewAISBuilder defaultSchema(String schema) { this.defaultSchema = schema; return this; } @Override public NewTableBuilder table(String table) { return table(defaultSchema, table); } @Override public NewTableBuilder table(String schema, String table) { checkUsable(); AISInvariants.checkDuplicateTables(aisb.akibanInformationSchema(), schema, table); this.schema = schema; this.object = table; TableName tableName= new TableName (schema, table); aisb.table(schema, table); aisb.createGroup(table, schema); aisb.addTableToGroup(tableName, schema, table); tablesToGroups.put(TableName.create(schema, table), tableName); tableColumnPos = 0; return this; } @Override public NewTableBuilder getTable() { return this; } @Override public NewTableBuilder getTable(TableName table) { checkUsable(); this.schema = table.getSchemaName(); this.object = table.getTableName(); assert aisb.akibanInformationSchema().getTable(table) != null; tableColumnPos = aisb.akibanInformationSchema().getTable(table).getColumns().size(); return this; } @Override public NewAISBuilder sequence (String name) { return sequence (name, 1, 1, false); } @Override public NewAISBuilder sequence (String name, long start, long increment, boolean isCycle) { checkUsable(); AISInvariants.checkDuplicateSequence(aisb.akibanInformationSchema(), defaultSchema, name); aisb.sequence(defaultSchema, name, start, increment, Long.MIN_VALUE, Long.MAX_VALUE, isCycle); return this; } @Override public NewTableBuilder table(TableName tableName) { return table(tableName.getSchemaName(), tableName.getTableName()); } @Override public NewViewBuilder view(String view) { return view(defaultSchema, view); } @Override public NewViewBuilder view(String schema, String view) { checkUsable(); AISInvariants.checkDuplicateTables(aisb.akibanInformationSchema(), schema, view); this.schema = schema; this.object = view; return this; } @Override public NewViewBuilder view(TableName viewName) { return view(viewName.getSchemaName(), viewName.getTableName()); } @Override public NewRoutineBuilder procedure(String procedure) { return procedure(defaultSchema, procedure); } @Override public NewRoutineBuilder procedure(String schema, String procedure) { checkUsable(); AISInvariants.checkDuplicateRoutine(aisb.akibanInformationSchema(), schema, procedure); this.schema = schema; this.object = procedure; return this; } @Override public NewRoutineBuilder procedure(TableName procedureName) { return procedure(procedureName.getSchemaName(), procedureName.getTableName()); } @Override public NewSQLJJarBuilder sqljJar(String jarName) { return sqljJar(defaultSchema, jarName); } @Override public NewSQLJJarBuilder sqljJar(String schema, String jarName) { checkUsable(); AISInvariants.checkDuplicateSQLJJar(aisb.akibanInformationSchema(), schema, jarName); this.schema = schema; this.object = jarName; return this; } @Override public NewSQLJJarBuilder sqljJar(TableName name) { return sqljJar(name.getSchemaName(), name.getTableName()); } @Override public NewAISGroupIndexStarter groupIndex(String indexName, Index.JoinType joinType) { ActualGroupIndexBuilder actual = new ActualGroupIndexBuilder(aisb, defaultSchema); if (joinType == null) { throw new InvalidParameterValueException("JoinType cannot be null"); } return actual.groupIndex(indexName, joinType); } // NewTableBuilder interface @Override public NewTableBuilder colInt(String name) { return colLong(name, NULLABLE_DEFAULT, null, null); } @Override public NewTableBuilder colInt(String name, boolean nullable) { return colLong(name, nullable, null, null); } @Override public NewTableBuilder autoIncInt(String name, int initialValue) { return colLong(name, false, initialValue, true); } @Override public NewTableBuilder autoIncInt(String name, int initialValue, boolean always) { return colLong(name, false, initialValue, !always); } private NewTableBuilder colLong(String name, boolean nullable, Integer initialAutoInc, Boolean defaultIdentity) { checkUsable(); TInstance type = typesTranslator.typeForJDBCType(Types.INTEGER, nullable, schema, object, name); aisb.column(schema, object, name, tableColumnPos++, type, false, null, null); if (initialAutoInc != null) { assert defaultIdentity != null; String sequenceName = "temp-seq-" + object + "-" + name; long initValue = initialAutoInc.longValue(); aisb.sequence(schema, sequenceName, initValue, 1L, initValue, Long.MAX_VALUE, false); aisb.columnAsIdentity(schema, object, name, sequenceName, defaultIdentity); } return this; } @Override public NewTableBuilder colBoolean(String name, boolean nullable) { checkUsable(); TInstance type = typesTranslator.typeForJDBCType(Types.BOOLEAN, nullable, schema, object, name); aisb.column(schema, object, name, tableColumnPos++, type, false, null, null); return this; } @Override public NewTableBuilder colString(String name, int length) { return colString(name, length, NULLABLE_DEFAULT); } @Override public NewTableBuilder colString(String name, int length, boolean nullable) { return colString(name, length, nullable, CHARSET_DEFAULT); } @Override public NewTableBuilder colString(String name, int length, boolean nullable, String charset) { checkUsable(); Table table = aisb.akibanInformationSchema().getTable(schema, object); TInstance type = typesTranslator.typeForString(length, charset, null, table.getDefaultedCharsetId(), table.getDefaultedCollationId(), nullable); aisb.column(schema, object, name, tableColumnPos++, type, false, null, null); return this; } @Override public NewTableBuilder colDouble(String name) { return colDouble(name, NULLABLE_DEFAULT); } @Override public NewTableBuilder colDouble(String name, boolean nullable) { checkUsable(); TInstance type = typesTranslator.typeForJDBCType(Types.DOUBLE, nullable, schema, object, name); aisb.column(schema, object, name, tableColumnPos++, type, false, null, null); return this; } @Override public NewTableBuilder colBigInt(String name) { return colBigInt(name, NULLABLE_DEFAULT); } @Override public NewTableBuilder colBigInt(String name, boolean nullable) { TInstance type = typesTranslator.typeForJDBCType(Types.BIGINT, nullable, schema, object, name); aisb.column(schema, object, name, tableColumnPos++, type, false, null, null); return this; } @Override public NewTableBuilder colVarBinary(String name, int length) { return colVarBinary(name, length, NULLABLE_DEFAULT); } @Override public NewTableBuilder colVarBinary(String name, int length, boolean nullable) { TInstance type = typesTranslator.typeForJDBCType(Types.VARBINARY, length, nullable, schema, object, name); aisb.column(schema, object, name, tableColumnPos++, type, false, null, null); return this; } @Override public NewTableBuilder colText(String name) { return colText(name, NULLABLE_DEFAULT); } @Override public NewTableBuilder colText(String name, boolean nullable) { TInstance type = typesTranslator.typeForJDBCType(Types.LONGVARCHAR, nullable, schema, object, name); aisb.column(schema, object, name, tableColumnPos++, type, false, null, null); return this; } /* @Override public NewTableBuilder colTimestamp(String name) { return colTimestamp(name, NULLABLE_DEFAULT); } @Override public NewTableBuilder colTimestamp(String name, boolean nullable) { TInstance type = typesTranslator.typeForJDBCType(Types.TIMESTAMP, nullable, schema, object, name); aisb.column(schema, object, name, tableColumnPos++, type, false, null, null); return this; } */ @Override public NewTableBuilder colSystemTimestamp(String name) { return colSystemTimestamp(name, NULLABLE_DEFAULT); } @Override public NewTableBuilder colSystemTimestamp(String name, boolean nullable) { TInstance type = typesTranslator.typeClassForSystemTimestamp().instance(nullable); aisb.column(schema, object, name, tableColumnPos++, type, false, null, null); return this; } @Override public NewTableBuilder pk(String... columns) { aisb.pk(schema, object); return columns(Index.PRIMARY, columns); } @Override public NewTableBuilder uniqueKey(String indexName, String... columns) { aisb.unique(schema, object, indexName); return columns(indexName, columns); } @Override public NewTableBuilder uniqueConstraint(String constraintName, String indexName, String... columns) { aisb.uniqueConstraint(schema, object, indexName, new TableName(schema, constraintName)); return columns(indexName, columns); } @Override public NewTableBuilder key(String indexName, String... columns) { aisb.index(schema, object, indexName); return columns(indexName, columns); } private NewTableBuilder columns(String indexName, String[] columns) { checkUsable(); for (int i=0; i < columns.length; ++i) { aisb.indexColumn(schema, object, indexName, columns[i], i, true, null); } return this; } @Override public NewAkibanJoinBuilder joinTo(String table) { return joinTo(schema, table); } @Override public NewAkibanJoinBuilder joinTo(TableName name) { return joinTo(name.getSchemaName(), name.getTableName()); } @Override public NewAkibanJoinBuilder joinTo(String schema, String table) { String generated = "autogenerated_"+object+"_references_" + table; return joinTo(schema, table, generated); } @Override public NewAkibanJoinBuilder joinTo(String schema, String table, String fkName) { checkUsable(); this.fkIndexName = fkName; this.fkJoinName = fkIndexName; this.fkIndexPos = 0; this.referencesSchema = schema; this.referencesTable = table; Group oldGroup = aisb.akibanInformationSchema().getTable(this.schema, this.object).getGroup(); aisb.index(this.schema, this.object, fkIndexName); aisb.joinTables(fkJoinName, schema, table, this.schema, this.object); TableName fkGroupName = tablesToGroups.get(TableName.create(referencesSchema, referencesTable)); aisb.moveTreeToGroup(this.schema, this.object, fkGroupName, fkJoinName); aisb.akibanInformationSchema().removeGroup(oldGroup); TableName oldGroupName = tablesToGroups.put(TableName.create(this.schema, this.object), fkGroupName); assert oldGroup.getName().equals(oldGroupName) : oldGroup.getName() + " != " + oldGroupName; return this; } // NewAkibanJoinBuilder @Override public NewAkibanJoinBuilder on(String childColumn, String parentColumn) { checkUsable(); aisb.indexColumn(schema, object, fkIndexName, childColumn, fkIndexPos, true, null); aisb.joinColumns(fkJoinName, referencesSchema, referencesTable, parentColumn, schema, object, childColumn); return this; } @Override public NewAkibanJoinBuilder and(String childColumn, String parentColumn) { return on(childColumn, parentColumn); } // NewViewBuilder @Override public NewViewBuilder definition(String definition) { Properties properties = new Properties(); properties.put("database", schema); return definition(definition, properties); } @Override public NewViewBuilder definition(String definition, Properties properties) { aisb.view(schema, object, definition, properties, new HashMap<TableName,Collection<String>>()); return this; } @Override public NewViewBuilder references(String table) { return references(schema, table); } @Override public NewViewBuilder references(String schema, String table, String... columns) { checkUsable(); View view = aisb.akibanInformationSchema().getView(this.schema, this.object); TableName tableName = TableName.create(schema, table); Collection<String> entry = view.getTableColumnReferences().get(tableName); if (entry == null) { entry = new HashSet<>(); view.getTableColumnReferences().put(tableName, entry); } for (String colname : columns) { entry.add(colname); } return this; } // NewRoutineBuilder @Override public NewRoutineBuilder language(String language, Routine.CallingConvention callingConvention) { aisb.routine(schema, object, language, callingConvention); return this; } @Override public NewRoutineBuilder returnBoolean(String name) { TInstance type = typesTranslator.typeForJDBCType(Types.BOOLEAN, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.RETURN, type); return this; } @Override public NewRoutineBuilder returnLong(String name) { TInstance type = typesTranslator.typeForJDBCType(Types.BIGINT, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.RETURN, type); return this; } @Override public NewRoutineBuilder returnString(String name, int length) { TInstance type = typesTranslator.typeForJDBCType(Types.VARCHAR, length, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.RETURN, type); return this; } @Override public NewRoutineBuilder returnVarBinary(String name, int length) { TInstance type = typesTranslator.typeForJDBCType(Types.VARBINARY, length, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.RETURN, type); return this; } @Override public NewRoutineBuilder paramBooleanIn(String name) { TInstance type = typesTranslator.typeForJDBCType(Types.BOOLEAN, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.IN, type); return this; } @Override public NewRoutineBuilder paramLongIn(String name) { TInstance type = typesTranslator.typeForJDBCType(Types.BIGINT, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.IN, type); return this; } @Override public NewRoutineBuilder paramIntegerIn(String name) { TInstance type = typesTranslator.typeForJDBCType(Types.INTEGER, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.IN, type); return this; } @Override public NewRoutineBuilder paramStringIn(String name, int length) { TInstance type = typesTranslator.typeForJDBCType(Types.VARCHAR, length, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.IN, type); return this; } @Override public NewRoutineBuilder paramVarBinaryIn(String name, int length) { TInstance type = typesTranslator.typeForJDBCType(Types.VARBINARY, length, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.IN, type); return this; } @Override public NewRoutineBuilder paramDoubleIn(String name) { TInstance type = typesTranslator.typeForJDBCType(Types.DOUBLE, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.IN, type); return this; } @Override public NewRoutineBuilder paramLongOut(String name) { TInstance type = typesTranslator.typeForJDBCType(Types.BIGINT, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.OUT, type); return this; } @Override public NewRoutineBuilder paramStringOut(String name, int length) { TInstance type = typesTranslator.typeForJDBCType(Types.VARCHAR, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.OUT, type); return this; } @Override public NewRoutineBuilder paramDoubleOut(String name) { TInstance type = typesTranslator.typeForJDBCType(Types.DOUBLE, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.OUT, type); return this; } @Override public NewRoutineBuilder paramVarBinaryOut(String name, int length) { TInstance type = typesTranslator.typeForJDBCType(Types.VARBINARY, length, true, schema, object, name); aisb.parameter(schema, object, name, Parameter.Direction.OUT, type); return this; } @Override public NewRoutineBuilder externalName(String className) { return externalName(className, null); } @Override public NewRoutineBuilder externalName(String className, String methodName) { return externalName(null, className, methodName); } @Override public NewRoutineBuilder externalName(String jarName, String className, String methodName) { return externalName(defaultSchema, jarName, className, methodName); } @Override public NewRoutineBuilder externalName(String jarSchema, String jarName, String className, String methodName) { aisb.routineExternalName(schema, object, jarSchema, jarName, className, methodName); return this; } @Override public NewRoutineBuilder procDef(String definition) { aisb.routineDefinition(schema, object, definition); return this; } @Override public NewRoutineBuilder sqlAllowed(Routine.SQLAllowed sqlAllowed) { aisb.routineSQLAllowed(schema, object, sqlAllowed); return this; } @Override public NewRoutineBuilder dynamicResultSets(int dynamicResultSets) { aisb.routineDynamicResultSets(schema, object, dynamicResultSets); return this; } @Override public NewRoutineBuilder deterministic(boolean deterministic) { aisb.routineDeterministic(schema, object, deterministic); return this; } @Override public NewRoutineBuilder calledOnNullInput(boolean calledOnNullInput) { aisb.routineCalledOnNullInput(schema, object, calledOnNullInput); return this; } // NewSQLJJarBuilder @Override public NewSQLJJarBuilder url(String value, boolean checkReadable) { URL url; try { url = new URL(value); } catch (MalformedURLException ex1) { File file = new File(value); try { url = file.toURI().toURL(); } catch (MalformedURLException ex2) { // Report original failure. throw new InvalidSQLJJarURLException(schema, object, ex1); } if (checkReadable && file.canRead()) checkReadable = false; // Can tell quickly. } if (checkReadable) { InputStream istr = null; try { istr = url.openStream(); // Must be able to load it. } catch (IOException ex) { throw new InvalidSQLJJarURLException(schema, object, ex); } finally { if (istr != null) { try { istr.close(); } catch (IOException ex) { } } } } aisb.sqljJar(schema, object, url); return this; } // ActualBuilder interface public ActualBuilder(TypesTranslator typesTranslator) { this.aisb = new AISBuilder(); this.typesTranslator = typesTranslator; usable = true; tablesToGroups = new HashMap<>(); } // private private void checkUsable() { if (!usable) { throw new IllegalStateException("AIS has already been retrieved; can't reuse"); } } // object state private final AISBuilder aisb; private final TypesTranslator typesTranslator; private String defaultSchema; private String schema; private String object; private int tableColumnPos; private String fkIndexName; private String fkJoinName; private int fkIndexPos; private String referencesSchema; private String referencesTable; private boolean usable; private final Map<TableName,TableName> tablesToGroups; // constants private static final boolean NULLABLE_DEFAULT = false; private static final String CHARSET_DEFAULT = "UTF-8"; private static final String PRIMARY = "PRIMARY"; } private static class ActualGroupIndexBuilder implements NewAISGroupIndexStarter, NewAISGroupIndexBuilder { // NewAISProvider interface @Override public AkibanInformationSchema ais(boolean freezeAIS) { return ais(); } @Override public AkibanInformationSchema ais() { if (unstartedIndex()) { throw new IllegalStateException("a groupIndex was started but not given any columns: " + indexName); } return aisb.akibanInformationSchema(); } @Override public AkibanInformationSchema unvalidatedAIS() { return aisb.akibanInformationSchema(); } // NewAISGroupIndexBuilder interface @Override public NewAISGroupIndexStarter groupIndex(String indexName, Index.JoinType joinType) { this.indexName = indexName; this.groupName = null; this.position = -1; this.joinType = joinType; return this; } @Override public NewAISGroupIndexBuilder on(String table, String column) { return on(defaultSchema, table, column); } @Override public NewAISGroupIndexBuilder on(String schema, String table, String column) { Table aisTable = aisb.akibanInformationSchema().getTable(schema, table); if (aisTable == null) { throw new NoSuchElementException("no table " + schema + '.' + table); } if (aisTable.getGroup() == null) { throw new IllegalStateException("ungrouped table: " + schema + '.' + table); } TableName localGroupName = aisTable.getGroup().getName(); if (localGroupName == null) { throw new IllegalStateException("unnamed group for " + schema + '.' + table); } this.groupName = localGroupName; this.position = 0; aisb.groupIndex(this.groupName, this.indexName, false, joinType); return and(schema, table, column); } @Override public NewAISGroupIndexBuilder and(String table, String column) { return and(defaultSchema, table, column); } @Override public NewAISGroupIndexBuilder and(String schema, String table, String column) { if (unstartedIndex()) { throw new IllegalStateException("never called on(table,column) for " + indexName); } aisb.groupIndexColumn(groupName, indexName, schema, table, column, position++); return this; } // ActualFinisher interface public ActualGroupIndexBuilder(AISBuilder aisb, String defaultSchema) { this.aisb = aisb; this.defaultSchema = defaultSchema; } // private methods private boolean unstartedIndex() { // indexName is assigned as soon as groupIndex is invoked, but groupName is only resolved // by on. boolean hasUnstarted = (indexName != null) && (groupName == null); assert hasUnstarted == (position < 0) : String.format("%s but %d", hasUnstarted, position); return hasUnstarted; } // object states private final AISBuilder aisb; private final String defaultSchema; private int position; private Index.JoinType joinType; private String indexName; private TableName groupName; } }