/** * 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.sql.aisddl; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import com.foundationdb.ais.AISCloner; import com.foundationdb.ais.model.Columnar; import com.foundationdb.ais.model.DefaultNameGenerator; import com.foundationdb.ais.model.Routine; import com.foundationdb.ais.model.SQLJJar; import com.foundationdb.ais.model.Sequence; import com.foundationdb.ais.protobuf.ProtobufWriter.TableSelector; import com.foundationdb.sql.parser.IndexDefinitionNode; import com.foundationdb.sql.server.ServerSession; import com.foundationdb.sql.parser.TableElementList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.foundationdb.ais.model.AISBuilder; import com.foundationdb.ais.model.AkibanInformationSchema; import com.foundationdb.ais.model.Column; import com.foundationdb.ais.model.DefaultIndexNameGenerator; import com.foundationdb.ais.model.ForeignKey; import com.foundationdb.ais.model.Group; import com.foundationdb.ais.model.HasStorage; import com.foundationdb.ais.model.Index; import com.foundationdb.ais.model.IndexColumn; import com.foundationdb.ais.model.IndexNameGenerator; import com.foundationdb.ais.model.PrimaryKey; import com.foundationdb.ais.model.Table; import com.foundationdb.ais.model.TableIndex; import com.foundationdb.ais.model.TableName; import com.foundationdb.ais.protobuf.FDBProtobuf.TupleUsage; import com.foundationdb.qp.operator.QueryContext; import com.foundationdb.server.api.DDLFunctions; import com.foundationdb.server.error.*; import com.foundationdb.server.service.session.Session; import com.foundationdb.server.store.format.tuple.TupleStorageDescription; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.common.types.TypesTranslator; import com.foundationdb.sql.optimizer.FunctionsTypeComputer; import com.foundationdb.sql.parser.ColumnDefinitionNode; import com.foundationdb.sql.parser.ConstantNode; import com.foundationdb.sql.parser.ConstraintDefinitionNode; import com.foundationdb.sql.parser.CreateTableNode; import com.foundationdb.sql.parser.CurrentDatetimeOperatorNode; import com.foundationdb.sql.parser.DropGroupNode; import com.foundationdb.sql.parser.DropTableNode; import com.foundationdb.sql.parser.FKConstraintDefinitionNode; import com.foundationdb.sql.parser.IndexColumnList; import com.foundationdb.sql.parser.IndexDefinition; import com.foundationdb.sql.parser.RenameNode; import com.foundationdb.sql.parser.ResultColumn; import com.foundationdb.sql.parser.ResultColumnList; import com.foundationdb.sql.parser.SpecialFunctionNode; import com.foundationdb.sql.parser.StatementType; import com.foundationdb.sql.parser.StorageFormatNode; import com.foundationdb.sql.parser.TableElementNode; import com.foundationdb.sql.parser.ValueNode; import com.foundationdb.sql.parser.JavaToSQLValueNode; import com.foundationdb.sql.parser.MethodCallNode; import com.foundationdb.sql.types.DataTypeDescriptor; import com.foundationdb.sql.types.TypeId; import static com.foundationdb.sql.aisddl.DDLHelper.convertName; import static com.foundationdb.sql.aisddl.DDLHelper.skipOrThrow; /** DDL operations on Tables */ public class TableDDL { private static final Logger logger = LoggerFactory.getLogger(TableDDL.class); private TableDDL() { } public static void dropTable (DDLFunctions ddlFunctions, Session session, String defaultSchemaName, DropTableNode dropTable, QueryContext context) { TableName tableName = convertName(defaultSchemaName, dropTable.getObjectName()); AkibanInformationSchema ais = ddlFunctions.getAIS(session); Table table = ais.getTable(tableName); if(table == null) { if(skipOrThrow(context, dropTable.getExistenceCheck(), table, new NoSuchTableException(tableName))) { return; } } ViewDDL.checkDropTable(ddlFunctions, session, tableName); checkForeignKeyDropTable(table); ddlFunctions.dropTable(session, tableName); } public static void dropGroup (DDLFunctions ddlFunctions, Session session, String defaultSchemaName, DropGroupNode dropGroup, QueryContext context) { TableName tableName = convertName(defaultSchemaName, dropGroup.getObjectName()); AkibanInformationSchema ais = ddlFunctions.getAIS(session); Table curTable = ais.getTable(tableName); if((curTable == null) && skipOrThrow(context, dropGroup.getExistenceCheck(), curTable, new NoSuchTableException(tableName))) { return; } if (!curTable.isRoot()) { throw new DropGroupNotRootException (tableName); } final Group root = curTable.getGroup(); for (Table table : ais.getTables().values()) { if (table.getGroup() == root) { ViewDDL.checkDropTable(ddlFunctions, session, table.getName()); checkForeignKeyDropTable(table); } } ddlFunctions.dropGroup(session, root.getName()); } private static void checkForeignKeyDropTable(Table table) { for (ForeignKey foreignKey : table.getReferencedForeignKeys()) { if (table != foreignKey.getReferencingTable()) { throw new ForeignKeyPreventsDropTableException(table.getName(), foreignKey.getConstraintName().getTableName(), foreignKey.getReferencingTable().getName()); } } } public static void renameTable (DDLFunctions ddlFunctions, Session session, String defaultSchemaName, RenameNode renameTable) { TableName oldName = convertName(defaultSchemaName, renameTable.getObjectName()); TableName newName = convertName(defaultSchemaName, renameTable.getNewTableName()); ddlFunctions.renameTable(session, oldName, newName); } public static void createTable(DDLFunctions ddlFunctions, Session session, String defaultSchemaName, CreateTableNode createTable, QueryContext context) { if (createTable.getQueryExpression() != null) throw new UnsupportedCreateSelectException(); TableName fullName = convertName(defaultSchemaName, createTable.getObjectName()); String schemaName = fullName.getSchemaName(); String tableName = fullName.getTableName(); AkibanInformationSchema ais = ddlFunctions.getAIS(session); Table curTable = ais.getTable(fullName); if((curTable != null) && skipOrThrow(context, createTable.getExistenceCheck(), curTable, new DuplicateTableNameException(fullName))) { return; } TypesTranslator typesTranslator = ddlFunctions.getTypesTranslator(); AISBuilder builder = new AISBuilder(); builder.getNameGenerator().mergeAIS(ais); builder.table(schemaName, tableName); Table table = builder.akibanInformationSchema().getTable(schemaName, tableName); IndexNameGenerator namer = DefaultIndexNameGenerator.forTable(table); cloneReferencedTables(defaultSchemaName, ddlFunctions.getAISCloner(), ais, builder.akibanInformationSchema(), createTable.getTableElementList()); // First pass: Columns. int colpos = 0; for (TableElementNode tableElement : createTable.getTableElementList()) { if (tableElement instanceof ColumnDefinitionNode) { addColumn (builder, typesTranslator, (ColumnDefinitionNode)tableElement, schemaName, tableName, colpos++); } } // Second pass: GROUPING, PRIMARY, UNIQUE and INDEX. // Requires the columns to have already been created. for (TableElementNode tableElement : createTable.getTableElementList()) { if (tableElement instanceof FKConstraintDefinitionNode) { FKConstraintDefinitionNode fkdn = (FKConstraintDefinitionNode)tableElement; if (fkdn.isGrouping()) { addJoin (builder, fkdn, defaultSchemaName, schemaName, tableName); } // else: regular FK, done in third pass below } else if (tableElement instanceof ConstraintDefinitionNode) { addIndex (namer, builder, (ConstraintDefinitionNode)tableElement, schemaName, tableName, context); } else if (tableElement instanceof IndexDefinitionNode) { addIndex (namer, builder, (IndexDefinitionNode)tableElement, schemaName, tableName, context, ddlFunctions); } else if (!(tableElement instanceof ColumnDefinitionNode)) { throw new UnsupportedSQLException("Unexpected TableElement", tableElement); } } // Third pass: FOREIGN KEY. // Separate pass as to not create extraneous indexes, if possible. for (TableElementNode tableElement : createTable.getTableElementList()) { if (tableElement instanceof FKConstraintDefinitionNode) { FKConstraintDefinitionNode fkdn = (FKConstraintDefinitionNode)tableElement; if (!fkdn.isGrouping()) { addForeignKey(builder, ddlFunctions.getAIS(session), fkdn, defaultSchemaName, schemaName, tableName); } } } setTableStorage(ddlFunctions, createTable, builder, tableName, table, schemaName); builder.basicSchemaIsComplete(); builder.groupingIsComplete(); ddlFunctions.createTable(session, table); } public static void createTable(DDLFunctions ddlFunctions, Session session, String defaultSchemaName, CreateTableNode createTable, QueryContext context, List<DataTypeDescriptor> descriptors, List<String> columnNames, ServerSession server) { if (createTable.getQueryExpression() == null) throw new IllegalArgumentException("Expected queryExpression"); TableName fullName = convertName(defaultSchemaName, createTable.getObjectName()); String schemaName = fullName.getSchemaName(); String tableName = fullName.getTableName(); AkibanInformationSchema ais = ddlFunctions.getAIS(session); Table curTable = ais.getTable(fullName); if((curTable != null) && skipOrThrow(context, createTable.getExistenceCheck(), curTable, new DuplicateTableNameException(fullName))) { return; } TypesTranslator typesTranslator = ddlFunctions.getTypesTranslator(); AISBuilder builder = new AISBuilder(); builder.table(schemaName, tableName); Table table = builder.akibanInformationSchema().getTable(schemaName, tableName); ResultColumnList resultColumns = null; if(createTable != null) resultColumns = createTable.getResultColumns(); String newColumnName; ResultColumn resultColumn; if(resultColumns != null && resultColumns.size() > descriptors.size()) throw new InvalidCreateAsException("More columns names in create than in select query"); int colpos = 0; for (DataTypeDescriptor descriptor : descriptors) { if ((resultColumns != null) && (resultColumns.size() > colpos)){ resultColumn = resultColumns.getResultColumn(colpos+ 1); if(resultColumn != null) { newColumnName = resultColumn.getName(); }else { newColumnName = columnNames.get(colpos); } } else { newColumnName = columnNames.get(colpos); } addColumn(builder, schemaName, tableName, colpos++, newColumnName,typesTranslator, descriptor); } builder.basicSchemaIsComplete(); builder.groupingIsComplete(); setTableStorage(ddlFunctions, createTable, builder, tableName, table, schemaName); if(createTable.isWithData()) { ddlFunctions.createTable(session, table, createTable.getCreateAsQuery().toLowerCase(), context, server); return; } ddlFunctions.createTable(session, table); } /** Copy groups of tables referenced from {@code nodes}. Ones already present in {@code targetAIS} are skipped. */ static void cloneReferencedTables(String defaultSchema, AISCloner cloner, AkibanInformationSchema curAIS, final AkibanInformationSchema targetAIS, TableElementList nodes) { final Set<Group> groups = new HashSet<>(); for(TableElementNode elem : nodes) { if(elem instanceof FKConstraintDefinitionNode) { FKConstraintDefinitionNode fkdn = (FKConstraintDefinitionNode)elem; if(fkdn.getRefTableName() != null) { TableName name = getReferencedName(defaultSchema, (FKConstraintDefinitionNode)elem); Table t = curAIS.getTable(name); if(t != null) { groups.add(t.getGroup()); } } } // else if(elem instanceof IndexDefinitionNode) { // when inline group indexes are supported } cloner.clone(targetAIS, curAIS, new TableSelector() { @Override public boolean isSelected(Columnar columnar) { return (columnar instanceof Table) && (targetAIS.getTable(columnar.getName()) == null) && groups.contains(((Table)columnar).getGroup()); } @Override public boolean isSelected(Sequence sequence) { return (targetAIS.getSequence(sequence.getSequenceName()) == null); } @Override public boolean isSelected(Routine routine) { return false; } @Override public boolean isSelected(SQLJJar sqljJar) { return false; } @Override public boolean isSelected(ForeignKey foreignKey) { return false; } }); } private static void setTableStorage(DDLFunctions ddlFunctions, CreateTableNode createTable, AISBuilder builder, String tableName, Table table, String schemaName){ if (createTable.getStorageFormat() != null) { if (!table.isRoot()) { throw new SetStorageNotRootException(tableName, schemaName); } setGroup(table, builder, tableName, schemaName); setStorage(ddlFunctions, table.getGroup(), createTable.getStorageFormat()); } else if (table.isRoot()) { setGroup(table, builder, tableName, schemaName); setStorage(ddlFunctions, table.getGroup(), null); } } static void setGroup(Table table, AISBuilder builder, String tableName, String schemaName) { if (table.getGroup() == null) { builder.createGroup(tableName, schemaName); builder.addTableToGroup(tableName, schemaName, tableName); } } public static void setStorage(DDLFunctions ddlFunctions, HasStorage object, StorageFormatNode storage) { if (storage != null) { object.setStorageDescription(ddlFunctions.getStorageFormatRegistry().parseSQL(storage, object)); return; } object.setStorageDescription(ddlFunctions.getStorageFormatRegistry().getDefaultStorageDescription(object)); } static void addColumn (final AISBuilder builder, final TypesTranslator typesTranslator, final ColumnDefinitionNode cdn, final String schemaName, final String tableName, int colpos) { String typeName = cdn.getType().getTypeName(); // Special handling for the "[BIG]SERIAL" column type -> which is transformed to // [BIG]INT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) boolean isSerial = "serial".equalsIgnoreCase(typeName); boolean isBigSerial = "bigserial".equalsIgnoreCase(typeName); if (isSerial || isBigSerial) { // [BIG]INT NOT NULL DataTypeDescriptor typeDesc = new DataTypeDescriptor(isBigSerial ? TypeId.BIGINT_ID : TypeId.INTEGER_ID, false); addColumn (builder, typesTranslator, schemaName, tableName, cdn.getColumnName(), colpos, typeDesc, null, null); // GENERATED BY DEFAULT AS IDENTITY setAutoIncrement (builder, schemaName, tableName, cdn.getColumnName(), true, 1, 1); } else { String[] defaultValueFunction = getColumnDefault(cdn, schemaName, tableName); addColumn(builder, typesTranslator, schemaName, tableName, cdn.getColumnName(), colpos, cdn.getType(), defaultValueFunction[0], defaultValueFunction[1]); if (cdn.isAutoincrementColumn()) { setAutoIncrement(builder, schemaName, tableName, cdn); } } } static void addColumn (final AISBuilder builder, final String schemaName, final String tableName, int colpos, final String columnName, final TypesTranslator typesTranslator, final DataTypeDescriptor d) { TInstance type = typesTranslator.typeForSQLType(d, schemaName, tableName, columnName); builder.column(schemaName, tableName, columnName, colpos, type, false, null, null); } public static void setAutoIncrement(AISBuilder builder, String schema, String table, ColumnDefinitionNode cdn) { // if the cdn has a default node-> GENERATE BY DEFAULT // if no default node -> GENERATE ALWAYS Boolean defaultIdentity = cdn.getDefaultNode() != null; setAutoIncrement(builder, schema, table, cdn.getColumnName(), defaultIdentity, cdn.getAutoincrementStart(), cdn.getAutoincrementIncrement()); } public static void setAutoIncrement(AISBuilder builder, String schemaName, String tableName, String columnName, boolean defaultIdentity, long start, long increment) { // make the column an identity column builder.columnAsIdentity(schemaName, tableName, columnName, start, increment, defaultIdentity); } static String[] getColumnDefault(ColumnDefinitionNode cdn, String schemaName, String tableName) { String defaultValue = null, defaultFunction = null; if (cdn.getDefaultNode() != null) { ValueNode valueNode = cdn.getDefaultNode().getDefaultTree(); if (valueNode == null) { } else if (valueNode instanceof ConstantNode) { defaultValue = ((ConstantNode)valueNode).getValue().toString(); } else if (valueNode instanceof SpecialFunctionNode) { defaultFunction = FunctionsTypeComputer.specialFunctionName((SpecialFunctionNode)valueNode); } else if (valueNode instanceof CurrentDatetimeOperatorNode) { defaultFunction = FunctionsTypeComputer.currentDatetimeFunctionName((CurrentDatetimeOperatorNode)valueNode); } else if ((valueNode instanceof JavaToSQLValueNode) && (((JavaToSQLValueNode) valueNode).getJavaValueNode() instanceof MethodCallNode) && (((MethodCallNode) ((JavaToSQLValueNode) valueNode).getJavaValueNode()).getMethodParameters().length == 0)) { // if default is a method with no arguments: defaultFunction = ((MethodCallNode) ((JavaToSQLValueNode) valueNode).getJavaValueNode()).getMethodName(); } else { throw new BadColumnDefaultException(schemaName, tableName, cdn.getColumnName(), cdn.getDefaultNode().getDefaultText()); } } return new String[] { defaultValue, defaultFunction }; } static void addColumn(final AISBuilder builder, final TypesTranslator typesTranslator, final String schemaName, final String tableName, final String columnName, int colpos, DataTypeDescriptor sqlType, final String defaultValue, final String defaultFunction) { TInstance type = typesTranslator.typeForSQLType(sqlType, schemaName, tableName, columnName); builder.column(schemaName, tableName, columnName, colpos, type, false, defaultValue, defaultFunction); } public static String addIndex(IndexNameGenerator namer, AISBuilder builder, ConstraintDefinitionNode cdn, String schemaName, String tableName, QueryContext context) { // We don't (yet) have a constraint representation so override any provided Table table = builder.akibanInformationSchema().getTable(schemaName, tableName); boolean isUnique; boolean isPrimary; String indexName = cdn.getName(); int colPos = 0; if (cdn.getConstraintType() == ConstraintDefinitionNode.ConstraintType.CHECK) { throw new UnsupportedCheckConstraintException (); } else if (cdn.getConstraintType() == ConstraintDefinitionNode.ConstraintType.PRIMARY_KEY) { indexName = Index.PRIMARY; isPrimary = isUnique = true; } else if (cdn.getConstraintType() == ConstraintDefinitionNode.ConstraintType.UNIQUE) { isPrimary = false; isUnique = true; } else { throw new UnsupportedCheckConstraintException(); } if(indexName == null) { indexName = namer.generateIndexName(null, cdn.getColumnList().get(0).getName()); } // index is unique or primary TableName constraintName = null; if (cdn.getConstraintName() != null) { constraintName = DDLHelper.convertName(schemaName, cdn.getConstraintName()); } if (isPrimary) { if (constraintName == null) { builder.pk(schemaName, tableName); } else { builder.pkConstraint(schemaName, tableName, constraintName); } } else if (isUnique) { if (constraintName == null) { builder.unique(schemaName, tableName, indexName); } else { builder.uniqueConstraint(schemaName, tableName, indexName, constraintName); } } for (ResultColumn col : cdn.getColumnList()) { if(table.getColumn(col.getName()) == null) { throw new NoSuchColumnException(col.getName()); } // Per SQL Specification: Feature ID: E141-08 - Not Null implied on Primary Key if (isPrimary) { Column tableColumn = table.getColumn(col.getName()); tableColumn.setType(tableColumn.getType().withNullable(false)); } builder.indexColumn(schemaName, tableName, indexName, col.getName(), colPos++, true, null); } return indexName; } public static String addIndex(IndexNameGenerator namer, AISBuilder builder, IndexDefinitionNode idn, String schemaName, String tableName, QueryContext context, DDLFunctions ddl) { if(idn.getJoinType() != null) { throw new UnsupportedSQLException("CREATE TABLE containing group index"); } String indexName = idn.getName(); Table table = builder.akibanInformationSchema().getTable(schemaName, tableName); return generateTableIndex(namer, builder, idn, indexName, table, context, ddl); } public static TableName getReferencedName(String schemaName, FKConstraintDefinitionNode fkdn) { return convertName(schemaName, fkdn.getRefTableName()); } public static void addJoin(final AISBuilder builder, final FKConstraintDefinitionNode fkdn, final String defaultSchemaName, final String schemaName, final String tableName) { TableName parentName = getReferencedName(defaultSchemaName, fkdn); AkibanInformationSchema ais = builder.akibanInformationSchema(); // Check parent table exists Table parentTable = ais.getTable(parentName); if (parentTable == null) { throw new JoinToUnknownTableException(new TableName(schemaName, tableName), parentName); } // Check child table exists Table childTable = ais.getTable(schemaName, tableName); if (childTable == null) { throw new NoSuchTableException(schemaName, tableName); } // Check that we aren't joining to ourselves if (parentTable == childTable) { throw new JoinToSelfException(schemaName, tableName); } // Check that fk list and pk list are the same size String[] fkColumns = columnNamesFromListOrPK(fkdn.getColumnList(), null); // No defaults for child table String[] pkColumns = columnNamesFromListOrPK(fkdn.getRefResultColumnList(), parentTable.getPrimaryKey()); int actualPkColCount = parentTable.getPrimaryKeyIncludingInternal().getColumns().size(); if ((fkColumns.length != actualPkColCount) || (pkColumns.length != actualPkColCount)) { throw new JoinColumnMismatchException(fkdn.getColumnList().size(), new TableName(schemaName, tableName), parentName, parentTable.getPrimaryKeyIncludingInternal().getColumns().size()); } int colPos = 0; while((colPos < fkColumns.length) && (colPos < pkColumns.length)) { String fkColumn = fkColumns[colPos]; String pkColumn = pkColumns[colPos]; if (childTable.getColumn(fkColumn) == null) { throw new NoSuchColumnException(String.format("%s.%s.%s", schemaName, tableName, fkColumn)); } if (parentTable.getColumn(pkColumn) == null) { throw new JoinToWrongColumnsException(new TableName(schemaName, tableName), fkColumn, parentName, pkColumn); } ++colPos; } String joinName = builder.getNameGenerator().generateJoinName(parentName, childTable.getName(), pkColumns, fkColumns); if (fkdn.getConstraintName() != null) { joinName = fkdn.getConstraintName().getTableName(); } builder.joinTables(joinName, parentName.getSchemaName(), parentName.getTableName(), schemaName, tableName); colPos = 0; while(colPos < fkColumns.length) { builder.joinColumns(joinName, parentName.getSchemaName(), parentName.getTableName(), pkColumns[colPos], schemaName, tableName, fkColumns[colPos]); ++colPos; } builder.addJoinToGroup(parentTable.getGroup().getName(), joinName, 0); } private static String[] columnNamesFromListOrPK(ResultColumnList list, PrimaryKey pk) { String[] names = (list == null) ? null: list.getColumnNames(); if(((names == null) || (names.length == 0)) && (pk != null)) { Index index = pk.getIndex(); names = new String[index.getKeyColumns().size()]; int i = 0; for(IndexColumn iCol : index.getKeyColumns()) { names[i++] = iCol.getColumn().getName(); } } if(names == null) { names = new String[0]; } return names; } private static String generateTableIndex(IndexNameGenerator namer, AISBuilder builder, IndexDefinition id, String indexName, Table table, QueryContext context, DDLFunctions ddl ) { IndexColumnList columnList = id.getIndexColumnList(); Index tableIndex; TableName constraintName = null; if(indexName == null) { indexName = namer.generateIndexName(null, columnList.get(0).getColumnName()); } if(id.isUnique()) { constraintName = builder.getNameGenerator().generateUniqueConstraintName(table.getName().getSchemaName(), indexName); } String functionName = columnList.functionName(); IndexDDL.checkFunctionNameValidity(functionName); if (IndexDDL.isFullText(functionName)) { logger.debug ("Building Full text index on table {}", table.getName()) ; tableIndex = IndexDDL.buildFullTextIndex(builder, table.getName(), indexName, id, null, null); } else if (IndexDDL.checkIndexType (id, table.getName()) == Index.IndexType.TABLE) { logger.debug ("Building Table index on table {}", table.getName()) ; tableIndex = IndexDDL.buildTableIndex (builder, table.getName(), indexName, id, constraintName, null, null); } else { logger.debug ("Building Group index on table {}", table.getName()); tableIndex = IndexDDL.buildGroupIndex(builder, table.getName(), indexName, id, null, null); } boolean indexIsSpatial = IndexDDL.isSpatial(functionName); // Can't check isSpatialCompatible before the index columns have been added. if (indexIsSpatial && !Index.isSpatialCompatible(tableIndex)) { throw new BadSpatialIndexException(tableIndex.getIndexName().getTableName(), null); } StorageFormatNode sfn = id.getStorageFormat(); if (sfn != null) { tableIndex.setStorageDescription(ddl.getStorageFormatRegistry().parseSQL(sfn, tableIndex)); } return tableIndex.getIndexName().getName(); } protected static void addForeignKey(AISBuilder builder, AkibanInformationSchema sourceAIS, FKConstraintDefinitionNode fkdn, String defaultSchemaName, String referencingSchemaName, String referencingTableName) { AkibanInformationSchema targetAIS = builder.akibanInformationSchema(); Table referencingTable = targetAIS.getTable(referencingSchemaName, referencingTableName); TableName referencedName = getReferencedName(defaultSchemaName, fkdn); Table referencedTable = sourceAIS.getTable(referencedName); if (referencedTable == null) { if (referencedName.equals(referencingTable.getName())) { referencedTable = referencingTable; // Circular reference to self. } else { throw new JoinToUnknownTableException(new TableName(referencingSchemaName, referencingTableName), referencedName); } } if (fkdn.getMatchType() != FKConstraintDefinitionNode.MatchType.SIMPLE) { throw new UnsupportedFKMatchException(fkdn); } String constraintName = fkdn.getName(); if (constraintName == null) { constraintName = builder.getNameGenerator().generateFKConstraintName(referencingSchemaName, referencingTableName).getTableName(); } String[] referencingColumnNames = columnNamesFromListOrPK(fkdn.getColumnList(), null); String[] referencedColumnNames = columnNamesFromListOrPK(fkdn.getRefResultColumnList(), referencedTable.getPrimaryKey()); if (referencingColumnNames.length != referencedColumnNames.length) { throw new JoinColumnMismatchException(referencingColumnNames.length, new TableName(referencingSchemaName, referencingTableName), referencedName, referencedColumnNames.length); } List<Column> referencedColumns = new ArrayList<>(referencedColumnNames.length); List<Column> referencingColumns = new ArrayList<>(referencingColumnNames.length); for (int i = 0; i < referencingColumnNames.length; i++) { Column referencingColumn = referencingTable.getColumn(referencingColumnNames[i]); if (referencingColumn == null) { throw new NoSuchColumnException(referencingColumnNames[i]); } referencingColumns.add(referencingColumn); Column referencedColumn = referencedTable.getColumn(referencedColumnNames[i]); if (referencedColumn == null) { throw new NoSuchColumnException(referencedColumnNames[i]); } referencedColumns.add(referencedColumn); } // Note: Referenced side index checked in validation // Pick (or create) a referencing side index TableIndex referencingIndex = ForeignKey.findReferencingIndex(referencingTable, referencingColumns); if (referencingIndex == null) { List<String> allIndexNames = new ArrayList<>(); for(Index index : referencingTable.getIndexesIncludingInternal()) { allIndexNames.add(index.getIndexName().getName()); } for(Index index : referencingTable.getFullTextIndexes()) { allIndexNames.add(index.getIndexName().getName()); } String name = DefaultNameGenerator.findUnique(allIndexNames, constraintName, DefaultNameGenerator.MAX_IDENT); builder.index(referencingSchemaName, referencingTableName, name); for(int i = 0; i < referencingColumnNames.length; ++i) { builder.indexColumn(referencingSchemaName, referencingTableName, name, referencingColumnNames[i], i, true, null /*indexedLength*/); } } builder.foreignKey(referencingSchemaName, referencingTableName, Arrays.asList(referencingColumnNames), referencedName.getSchemaName(), referencedName.getTableName(), Arrays.asList(referencedColumnNames), convertReferentialAction(fkdn.getRefActionDeleteRule()), convertReferentialAction(fkdn.getRefActionUpdateRule()), fkdn.isDeferrable(), fkdn.isInitiallyDeferred(), constraintName); } private static ForeignKey.Action convertReferentialAction(int action) { switch (action) { case StatementType.RA_NOACTION: default: return ForeignKey.Action.NO_ACTION; case StatementType.RA_RESTRICT: return ForeignKey.Action.RESTRICT; case StatementType.RA_CASCADE: return ForeignKey.Action.CASCADE; case StatementType.RA_SETNULL: return ForeignKey.Action.SET_NULL; case StatementType.RA_SETDEFAULT: return ForeignKey.Action.SET_DEFAULT; } } }