/** * 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.AISCloner; import com.foundationdb.ais.model.validation.AISValidations; import com.foundationdb.ais.protobuf.ProtobufWriter; import com.foundationdb.ais.util.ChangedTableDescription; import com.foundationdb.server.error.DuplicateIndexException; import com.foundationdb.server.error.IndexLacksColumnsException; import com.foundationdb.server.error.JoinToMultipleParentsException; import com.foundationdb.server.error.JoinToUnknownTableException; import com.foundationdb.server.error.JoinToWrongColumnsException; import com.foundationdb.server.error.NoSuchColumnException; import com.foundationdb.server.error.NoSuchGroupException; import com.foundationdb.server.error.NoSuchTableException; import com.foundationdb.server.error.ProtectedIndexException; import com.foundationdb.server.error.TableNotInGroupException; import com.foundationdb.server.store.format.StorageFormatRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * AISMerge is designed to merge a single Table definition into an existing AIS. The merge process * does not assume that Table.getAIS() returns a validated and complete * AkibanInformationSchema object. * * AISMerge makes a copy of the primaryAIS (from the constructor) before performing the merge process. * The final results is this copies AIS, plus new table, with the full AISValidations suite run, and * frozen. If you pass a frozen AIS into the merge, the copy process unfreeze the copy. */ public class AISMerge { public enum MergeType { ADD_TABLE, MODIFY_TABLE, ADD_INDEX, OTHER } private static class JoinChange { public final Join join; public final TableName newParentName; public final Map<String,String> parentCols; public final TableName newChildName; public final Map<String,String> childCols; public final boolean isNewGroup; public final StorageDescription storage; private JoinChange(Join join, TableName newParentName, Map<String, String> parentCols, TableName newChildName, Map<String, String> childCols, boolean isNewGroup, StorageDescription storage) { this.join = join; this.newParentName = newParentName; this.parentCols = parentCols; this.newChildName = newChildName; this.childCols = childCols; this.isNewGroup = isNewGroup; this.storage = storage; } } private static class IndexInfo { public final Integer id; public final StorageDescription storage; private IndexInfo(Integer id, StorageDescription storage) { this.id = id; this.storage = storage; } } private static class IdentityInfo { public final TableName tableName; public final String columnName; public final boolean defaultIdentity; public final Sequence sequence; public IdentityInfo(TableName tableName, String columnName, boolean defaultIdentity, Sequence sequence) { this.tableName = tableName; this.columnName = columnName; this.defaultIdentity = defaultIdentity; this.sequence = sequence; } } private static final Logger LOG = LoggerFactory.getLogger(AISMerge.class); /* state */ private final AISCloner aisCloner; private final AkibanInformationSchema targetAIS; private final Table sourceTable; private final NameGenerator nameGenerator; private final MergeType mergeType; private final List<JoinChange> changedJoins; private final Map<IndexName,IndexInfo> indexesToFix; private final List<IdentityInfo> identityToFix; private final Set<TableName> groupsToClear; /** Legacy test constructor. Creates an AISMerge for adding a table with a new {@link DefaultNameGenerator}. */ AISMerge(AISCloner aisCloner, AkibanInformationSchema sourceAIS, Table newTable) { this(aisCloner, new DefaultNameGenerator(sourceAIS), copyAISForAdd(aisCloner, sourceAIS), newTable, MergeType.ADD_TABLE, null, null, null, null); } /** Create a new AISMerge to be used for adding a new table. */ public static AISMerge newForAddTable(AISCloner aisCloner, NameGenerator generator, AkibanInformationSchema sourceAIS, Table newTable) { return new AISMerge(aisCloner, generator, copyAISForAdd(aisCloner, sourceAIS), newTable, MergeType.ADD_TABLE, null, null, null, null); } /** Create a new AISMerge to be used for modifying a table. */ public static AISMerge newForModifyTable(AISCloner aisCloner, NameGenerator generator, AkibanInformationSchema sourceAIS, Collection<ChangedTableDescription> alteredTables) { List<JoinChange> changedJoins = new ArrayList<>(); Map<IndexName,IndexInfo> indexesToFix = new HashMap<>(); List<IdentityInfo> identityToFix = new ArrayList<>(); Set<TableName> groupsToClear = new HashSet<>(); AkibanInformationSchema targetAIS = copyAISForModify(aisCloner, sourceAIS, indexesToFix, changedJoins, identityToFix, alteredTables, groupsToClear); return new AISMerge(aisCloner, generator, targetAIS, null, MergeType.MODIFY_TABLE, changedJoins, indexesToFix, identityToFix, groupsToClear); } /** Create a new AISMerge to be used for adding one, or more, index to a table. Also see {@link #mergeIndex(Index)}. */ public static AISMerge newForAddIndex(AISCloner aisCloner, NameGenerator generator, AkibanInformationSchema sourceAIS) { return new AISMerge(aisCloner, generator, copyAISForAdd(aisCloner, sourceAIS), null, MergeType.ADD_INDEX, null, null, null, null); } public static AISMerge newForOther(AISCloner aisCloner, NameGenerator generator, AkibanInformationSchema sourceAIS) { return new AISMerge(aisCloner, generator, copyAISForAdd(aisCloner, sourceAIS), null, MergeType.OTHER, null, null, null, null); } private AISMerge(AISCloner aisCloner, NameGenerator nameGenerator, AkibanInformationSchema targetAIS, Table sourceTable, MergeType mergeType, List<JoinChange> changedJoins, Map<IndexName,IndexInfo> indexesToFix, List<IdentityInfo> identityToFix, Set<TableName> groupsToClear) { this.aisCloner = aisCloner; this.nameGenerator = nameGenerator; this.targetAIS = targetAIS; this.sourceTable = sourceTable; this.mergeType = mergeType; this.changedJoins = changedJoins; this.indexesToFix = indexesToFix; this.identityToFix = identityToFix; this.groupsToClear = groupsToClear; } public static AkibanInformationSchema copyAISForAdd(AISCloner aisCloner, AkibanInformationSchema oldAIS) { return aisCloner.clone(oldAIS); } private static AkibanInformationSchema copyAISForModify(AISCloner aisCloner, AkibanInformationSchema oldAIS, Map<IndexName,IndexInfo> indexesToFix, final List<JoinChange> joinsToFix, List<IdentityInfo> identityToFix, Collection<ChangedTableDescription> changedTables, Set<TableName> groupsToClear) { final Set<Sequence> excludedSequences = new HashSet<>(); final Set<Group> excludedGroups = new HashSet<>(); final Map<TableName,Table> filteredTables = new HashMap<>(); for(ChangedTableDescription desc : changedTables) { // Copy tree names and IDs for pre-existing table and it's indexes Table oldTable = oldAIS.getTable(desc.getOldName()); Table newTable = desc.getNewDefinition(); // These don't affect final outcome and may be reset later. Needed by clone process. if((newTable != null) && (newTable.getGroup() != null)) { newTable.setOrdinal(oldTable.getOrdinal()); newTable.setTableId(oldTable.getTableId()); } // If we're changing table rows *at all*, generate new group tree(s) if(desc.isPKAffected() || desc.isTableAffected()) { groupsToClear.add(oldTable.getGroup().getName()); if(newTable != null && newTable.getGroup() != null) { groupsToClear.add(newTable.getGroup().getName()); } } switch(desc.getParentChange()) { case NONE: // None: Handled by cloning process break; case META: case UPDATE: { Join join = (newTable != null) ? newTable.getParentJoin() : oldTable.getParentJoin(); joinsToFix.add(new JoinChange(join, desc.getParentName(), desc.getParentColNames(), desc.getNewName(), desc.getColNames(), false, null)); } break; case ADD: if(newTable == null) { throw new IllegalArgumentException("Invalid change description: " + desc); } joinsToFix.add(new JoinChange(null, null, desc.getParentColNames(), desc.getNewName(), desc.getColNames(), false, null)); break; case DROP: { final Join join; if(newTable != null) { join = newTable.getParentJoin(); excludedGroups.add(newTable.getGroup()); } else { join = oldTable.getParentJoin(); } Group groupParent = oldTable.getGroup(); joinsToFix.add(new JoinChange(join, null, desc.getParentColNames(), desc.getNewName(), desc.getColNames(), true, groupParent.getStorageDescription().cloneForObjectWithoutState(groupParent))); } break; default: throw new IllegalStateException("Unhandled GroupChange: " + desc.getParentChange()); } Table indexSearchTable = newTable; if(newTable == null) { indexSearchTable = oldTable; } else { filteredTables.put(desc.getOldName(), newTable); } for(Index newIndex : indexSearchTable.getIndexesIncludingInternal()) { String newName = newIndex.getIndexName().getName(); String oldName = desc.getPreserveIndexes().get(newName); Index oldIndex = (oldName != null) ? oldTable.getIndexIncludingInternal(oldName) : null; if(oldIndex != null) { indexesToFix.put(newIndex.getIndexName(), new IndexInfo(oldIndex.getIndexId(), oldIndex.getStorageDescription())); } else if(desc.getIndexesAdded().contains(newName)) { indexesToFix.put(newIndex.getIndexName(), new IndexInfo(null, newIndex.getStorageDescription())); } else { indexesToFix.put(newIndex.getIndexName(), new IndexInfo(null, null)); } LOG.debug("Indexes to fix: {} -> {}", oldName, newIndex.getIndexName()); } for(TableName name : desc.getDroppedSequences()) { excludedSequences.add(oldAIS.getSequence(name)); } for(String name : desc.getIdentityAdded()) { Column col = newTable.getColumn(name); identityToFix.add(new IdentityInfo(desc.getNewName(), name, col.getDefaultIdentity(), col.getIdentityGenerator())); } } return aisCloner.clone( oldAIS, new ProtobufWriter.TableFilterSelector() { @Override public Columnar getSelected(Columnar columnar) { if(columnar.isTable()) { Columnar filtered = filteredTables.get(columnar.getName()); if(filtered != null) { return filtered; } } return columnar; } @Override public boolean isSelected(Group group) { return !excludedGroups.contains(group); } @Override public boolean isSelected(Join join) { for(JoinChange tnj : joinsToFix) { if(tnj.join == join) { return false; } } return true; } @Override public boolean isSelected(Sequence sequence) { return !excludedSequences.contains(sequence); } } ); } protected StorageFormatRegistry getStorageFormatRegistry() { return aisCloner.getStorageFormatRegistry(); } /** * Returns the final, updated AkibanInformationSchema. This AIS has been fully * validated and is frozen (no more changes), hence ready for update into the * server. * @return - the primaryAIS, after merge() with the Table added. */ public AkibanInformationSchema getAIS () { return targetAIS; } public AISMerge merge() { switch(mergeType) { case ADD_TABLE: doAddTableMerge(); break; case MODIFY_TABLE: doModifyTableMerge(); break; case ADD_INDEX: doAddIndexMerge(); break; default: throw new IllegalStateException("Unknown MergeType: " + mergeType); } return this; } public Index mergeIndex(Index index) { if(index.isPrimaryKey()) { throw new ProtectedIndexException("PRIMARY", index.getIndexName().getFullTableName()); } final IndexName indexName = index.getIndexName(); final Index curIndex; final Index newIndex; final Group newGroup; switch(index.getIndexType()) { case TABLE: { final TableName tableName = new TableName(indexName.getSchemaName(), indexName.getTableName()); final Table newTable = targetAIS.getTable(tableName); if(newTable == null) { throw new NoSuchTableException(tableName); } curIndex = newTable.getIndex(indexName.getName()); newGroup = newTable.getGroup(); Integer newId = newIndexID(newGroup); newIndex = TableIndex.create(targetAIS, newTable, indexName.getName(), newId, index.isUnique(), index.isPrimaryKey(), index.getConstraintName()); } break; case GROUP: { GroupIndex gi = (GroupIndex)index; newGroup = targetAIS.getGroup(gi.getGroup().getName()); if(newGroup == null) { throw new NoSuchGroupException(gi.getGroup().getName()); } curIndex = newGroup.getIndex(indexName.getName()); Integer newId = newIndexID(newGroup); newIndex = GroupIndex.create(targetAIS, newGroup, indexName.getName(), newId, index.isUnique(), index.isPrimaryKey(), index.getJoinType()); } break; case FULL_TEXT: { final TableName tableName = new TableName(indexName.getSchemaName(), indexName.getTableName()); final Table newTable = targetAIS.getTable(tableName); if(newTable == null) { throw new NoSuchTableException(tableName); } curIndex = newTable.getFullTextIndex(indexName.getName()); newGroup = newTable.getGroup(); Integer newId = newIndexID(newGroup); newIndex = FullTextIndex.create(targetAIS, newTable, indexName.getName(), newId); } break; default: throw new IllegalArgumentException("Unknown index type: " + index); } if(index.isSpatial()) { newIndex.markSpatial(index.firstSpatialArgument(), index.lastSpatialArgument() - index.firstSpatialArgument() + 1, index.getIndexMethod()); } if(curIndex != null) { throw new DuplicateIndexException(indexName); } if(index.getKeyColumns().isEmpty()) { throw new IndexLacksColumnsException(indexName); } for(IndexColumn indexCol : index.getKeyColumns()) { final TableName refTableName = indexCol.getColumn().getTable().getName(); final Table newRefTable = targetAIS.getTable(refTableName); if(newRefTable == null) { throw new NoSuchTableException(refTableName); } if(!newRefTable.getGroup().equals(newGroup)) { throw new TableNotInGroupException(refTableName); } final Column column = indexCol.getColumn(); final Column newColumn = newRefTable.getColumn(column.getName()); if(newColumn == null) { throw new NoSuchColumnException(column.getName()); } // API call problem, not something user can generate if(!column.getType().typeClass().equals(newColumn.getType().typeClass())) { throw new IllegalArgumentException( "Column type mismatch for " + column.getName() + ": " + column.getTypeName() + " vs " + newColumn.getTypeName() ); } // Calls (Group)Index.addColumn(), which checks all are in same branch IndexColumn.create(newIndex, newColumn, indexCol, indexCol.getPosition()); } newIndex.copyStorageDescription(index); getStorageFormatRegistry().finishStorageDescription(newIndex, nameGenerator); newIndex.freezeColumns(); return newIndex; } private void doAddTableMerge() { // I should use TableSubsetWriter(new AISTarget(targetAIS)) // but that assumes the Table.getAIS() is complete and valid. // i.e. has a group and group table, joins are accurate, etc. // this may not be true // Also the tableIDs need to be assigned correctly, which // TableSubsetWriter doesn't do. LOG.debug("Merging new table {} into targetAIS", sourceTable.getName()); final AISBuilder builder = new AISBuilder(targetAIS, nameGenerator, getStorageFormatRegistry()); Group targetGroup = null; if (sourceTable.getParentJoin() != null) { String parentSchemaName = sourceTable.getParentJoin().getParent().getName().getSchemaName(); String parentTableName = sourceTable.getParentJoin().getParent().getName().getTableName(); Table parentTable = targetAIS.getTable(parentSchemaName, parentTableName); if (parentTable == null) { throw new JoinToUnknownTableException (sourceTable.getName(), new TableName(parentSchemaName, parentTableName)); } targetGroup = parentTable.getGroup(); } else { StorageDescription storage = null; if (sourceTable.getGroup() != null) { storage = sourceTable.getGroup().getStorageDescription(); } TableName groupName = sourceTable.getName(); builder.createGroup(groupName.getTableName(), groupName.getSchemaName(), storage); targetGroup = builder.akibanInformationSchema().getGroup(groupName); } // Add the user table to the targetAIS addTable (builder, sourceTable, targetGroup.getName()); // Joins or group table? if (sourceTable.getParentJoin() != null) { // Normally there should be only one candidate parent join. // But since the AIS supports multiples, so does the merge. // This gets flagged in JoinToOneParent validation. for (Join join : sourceTable.getCandidateParentJoins()) { addJoin(builder, join, sourceTable); } } else { targetGroup.setRootTable(builder.akibanInformationSchema().getTable(sourceTable.getName())); } if (sourceTable.getPrimaryKeyIncludingInternal() != null) { TableIndex index = sourceTable.getPrimaryKeyIncludingInternal().getIndex(); IndexName indexName = index.getIndexName(); builder.index(sourceTable.getName().getSchemaName(), sourceTable.getName().getTableName(), indexName.getName(), index.isUnique(), index.isPrimaryKey(), index.getConstraintName(), index.getStorageDescription()); for (IndexColumn col : index.getKeyColumns()) { builder.indexColumn(sourceTable.getName().getSchemaName(), sourceTable.getName().getTableName(), index.getIndexName().getName(), col.getColumn().getName(), col.getPosition(), col.isAscending(), col.getIndexedLength()); } } builder.basicSchemaIsComplete(); builder.groupingIsComplete(); for (TableIndex index : sourceTable.getIndexes()) { // PRIMARY is added above as it is needed before groupingIsComplete() if (!index.isPrimaryKey()) { mergeIndex(index); } } for (FullTextIndex index : sourceTable.getFullTextIndexes()) { mergeIndex(index); } for (ForeignKey fk : sourceTable.getReferencingForeignKeys()) { List<String> referencingColumnNames = new ArrayList<>(); for (Column column : fk.getReferencingColumns()) { referencingColumnNames.add(column.getName()); } List<String> referencedColumnNames = new ArrayList<>(); for (Column column : fk.getReferencedColumns()) { referencedColumnNames.add(column.getName()); } builder.foreignKey(sourceTable.getName().getSchemaName(), sourceTable.getName().getTableName(), referencingColumnNames, fk.getReferencedTable().getName().getSchemaName(), fk.getReferencedTable().getName().getTableName(), referencedColumnNames, fk.getDeleteAction(), fk.getUpdateAction(), fk.isDeferrable(), fk.isInitiallyDeferred(), fk.getConstraintName().getTableName()); } builder.akibanInformationSchema().validate(AISValidations.BASIC_VALIDATIONS).throwIfNecessary(); builder.akibanInformationSchema().freeze(); } private void doModifyTableMerge() { AISBuilder builder = new AISBuilder(targetAIS, nameGenerator, getStorageFormatRegistry()); // Fix up groups for(JoinChange tnj : changedJoins) { final Table table = targetAIS.getTable(tnj.newChildName); if(tnj.isNewGroup) { addNewGroup(builder, table, tnj.storage); } else if(tnj.newParentName != null) { addJoin(builder, tnj.newParentName, tnj.parentCols, tnj.join, tnj.childCols, table); } } for(TableName name : groupsToClear) { Group group = targetAIS.getGroup(name); if(group != null && group.getStorageDescription() != null) { group.setStorageDescription((group.getStorageDescription()).cloneForObjectWithoutState(group)); } } // Ugly: groupingIsComplete() will set PRIMARY index tree names if missing. // Clear them here as to only set them once. for(Map.Entry<IndexName,IndexInfo> entry : indexesToFix.entrySet()) { IndexName name = entry.getKey(); IndexInfo info = entry.getValue(); Table table = targetAIS.getTable(name.getSchemaName(), name.getTableName()); Index index = table.getIndexIncludingInternal(name.getName()); if(info.storage == null) { index.setStorageDescription(null); } else { index.setStorageDescription(info.storage.cloneForObject(index)); } } for(IdentityInfo info : identityToFix) { addIdentitySequence(builder, info.tableName.getSchemaName(), info.tableName.getTableName(), info.columnName, info.defaultIdentity, info.sequence); } builder.basicSchemaIsComplete(); builder.groupingIsComplete(); for(Map.Entry<IndexName,IndexInfo> entry : indexesToFix.entrySet()) { IndexName name = entry.getKey(); IndexInfo info = entry.getValue(); Table table = targetAIS.getTable(name.getSchemaName(), name.getTableName()); Index index = table.getIndexIncludingInternal(name.getName()); index.setIndexId((info.id != null) ? info.id : newIndexID(table.getGroup())); getStorageFormatRegistry().finishStorageDescription(index, nameGenerator); } builder.akibanInformationSchema().validate(AISValidations.BASIC_VALIDATIONS).throwIfNecessary(); builder.akibanInformationSchema().freeze(); } private void doAddIndexMerge() { AISBuilder builder = new AISBuilder(targetAIS, nameGenerator, getStorageFormatRegistry()); builder.groupingIsComplete(); builder.akibanInformationSchema().validate(AISValidations.BASIC_VALIDATIONS).throwIfNecessary(); builder.akibanInformationSchema().freeze(); } private void addTable(AISBuilder builder, final Table table, final TableName groupName) { // I should use TableSubsetWriter(new AISTarget(targetAIS)) or AISCloner.clone() // but both assume the Table.getAIS() is complete and valid. // i.e. has a group and group table, and the joins point to a valid table // which, given the use of AISMerge, is not true. final String schemaName = table.getName().getSchemaName(); final String tableName = table.getName().getTableName(); builder.table(schemaName, tableName); Table targetTable = targetAIS.getTable(schemaName, tableName); targetTable.setCharsetAndCollation(table.getCharsetName(), table.getCollationName()); targetTable.setPendingOSC(table.getPendingOSC()); targetTable.setUuid(table.getUuid()); //Add the table to the group only if the root table (or single table) // The join processing adds others to the group later. if (table.getParentJoin() == null) { builder.addTableToGroup(groupName, schemaName, tableName); } // columns for (Column column : table.getColumnsIncludingInternal()) { builder.column(schemaName, tableName, column.getName(), column.getPosition(), column.getType(), false, column.getDefaultValue(), column.getDefaultFunction()); Column newColumn = targetTable.getColumnsIncludingInternal().get(column.getPosition()); newColumn.setUuid(column.getUuid()); if (!table.isVirtual() && column.getDefaultIdentity() != null) { addIdentitySequence(builder, schemaName, tableName, column.getName(), column.getDefaultIdentity(), column.getIdentityGenerator()); } // Proactively cache, can go away if Column ever cleans itself up newColumn.getMaxStorageSize(); newColumn.getPrefixSize(); } } /** Adds **NEW** sequence with same properties (except name & schema) as the given sequence. */ private void addIdentitySequence(AISBuilder builder, String schemaName, String tableName, String column, boolean defaultIdentity, Sequence sequence) { // This enforces that the sequence is always in the same schema as the table. Some things depend on that // such as dropSchema. If you change this to allow a different schema, make sure to test the behavior // of those things when the sequence is not in the same schema. TableName sequenceName = nameGenerator.generateIdentitySequenceName(builder.akibanInformationSchema(), new TableName(schemaName, tableName), column); Sequence newSeq = builder.sequence(sequenceName.getSchemaName(), sequenceName.getTableName(), sequence.getStartsWith(), sequence.getIncrement(), sequence.getMinValue(), sequence.getMaxValue(), sequence.isCycle()); builder.columnAsIdentity(schemaName, tableName, column, sequenceName.getTableName(), defaultIdentity); LOG.debug("Generated sequence: {}, with storage; {}", sequenceName, newSeq.getStorageNameString()); } private void addNewGroup (AISBuilder builder, Table rootTable, StorageDescription copyStorage) { TableName groupName = rootTable.getName(); builder.createGroup(groupName.getTableName(), groupName.getSchemaName(), copyStorage); builder.addTableToGroup(groupName, rootTable.getName().getSchemaName(), rootTable.getName().getTableName()); } private void addJoin (AISBuilder builder, Join join, Table childTable) { Map<String,String> emptyMap = Collections.emptyMap(); addJoin(builder, join.getParent().getName(), emptyMap, join, emptyMap, childTable); } private static String getOrDefault(Map<String, String> map, String key) { String val = map.get(key); return (val != null) ? val : key; } private void addJoin (AISBuilder builder, TableName parentName, Map<String,String> parentCols, Join join, Map<String,String> childCols, Table childTable) { String parentSchemaName = parentName.getSchemaName(); String parentTableName = parentName.getTableName(); Table parentTable = targetAIS.getTable(parentSchemaName, parentTableName); if (parentTable == null) { throw new JoinToUnknownTableException(childTable.getName(), new TableName(parentSchemaName, parentTableName)); } LOG.debug(String.format("Table is child of table %s", parentTable.getName().toString())); String joinName = join.getConstraintName().getTableName(); builder.joinTables(joinName, parentSchemaName, parentTableName, childTable.getName().getSchemaName(), childTable.getName().getTableName() ); for (JoinColumn joinColumn : join.getJoinColumns()) { try { builder.joinColumns(joinName, parentSchemaName, parentTableName, getOrDefault(parentCols, joinColumn.getParent().getName()), childTable.getName().getSchemaName(), childTable.getName().getTableName(), getOrDefault(childCols, joinColumn.getChild().getName())); } catch (AISBuilder.NoSuchObjectException ex) { throw new JoinToWrongColumnsException ( childTable.getName(), joinColumn.getChild().getName(), new TableName(parentSchemaName, parentTableName), joinColumn.getParent().getName()); } } builder.basicSchemaIsComplete(); try { builder.addJoinToGroup(parentTable.getGroup().getName(), joinName, 0); } catch (AISBuilder.GroupStructureException ex) { throw new JoinToMultipleParentsException(join.getChild().getName()); } } private int newIndexID(Group group) { return newIndexID(group.getRoot().getTableId()); } private int newIndexID(int rootTableID) { return nameGenerator.generateIndexID(rootTableID); } public static AkibanInformationSchema mergeView(AISCloner aisCloner, AkibanInformationSchema oldAIS, View view) { AkibanInformationSchema newAIS = copyAISForAdd(aisCloner, oldAIS); copyView(newAIS, view); newAIS.validate(AISValidations.BASIC_VALIDATIONS).throwIfNecessary(); newAIS.freeze(); return newAIS; } public static void copyView(AkibanInformationSchema newAIS, View oldView) { Map<TableName,Collection<String>> newReferences = new HashMap<>(); for (Map.Entry<TableName,Collection<String>> entry : oldView.getTableColumnReferences().entrySet()) { newReferences.put(entry.getKey(), new HashSet<>(entry.getValue())); } View newView = View.create(newAIS, oldView.getName().getSchemaName(), oldView.getName().getTableName(), oldView.getDefinition(), oldView.getDefinitionProperties(), newReferences); for (Column col : oldView.getColumns()) { Column.create(newView, col.getName(), col.getPosition(), col.getType()); } newAIS.addView(newView); } public AkibanInformationSchema mergeSequence(Sequence sequence) { mergeSequenceInternal(sequence); targetAIS.validate(AISValidations.BASIC_VALIDATIONS).throwIfNecessary(); targetAIS.freeze(); return targetAIS; } private Sequence mergeSequenceInternal(Sequence sequence) { Sequence newSeq = Sequence.create(targetAIS, sequence); newSeq.copyStorageDescription(sequence); getStorageFormatRegistry().finishStorageDescription(newSeq, nameGenerator); return newSeq; } public static AkibanInformationSchema mergeRoutine(AISCloner aisCloner, AkibanInformationSchema oldAIS, Routine routine) { AkibanInformationSchema newAIS = copyAISForAdd(aisCloner, oldAIS); newAIS.addRoutine(routine); newAIS.validate(AISValidations.BASIC_VALIDATIONS).throwIfNecessary(); newAIS.freeze(); return newAIS; } public static AkibanInformationSchema mergeSQLJJar(AISCloner aisCloner, AkibanInformationSchema oldAIS, SQLJJar sqljJar) { AkibanInformationSchema newAIS = copyAISForAdd(aisCloner, oldAIS); newAIS.addSQLJJar(sqljJar); newAIS.validate(AISValidations.BASIC_VALIDATIONS).throwIfNecessary(); newAIS.freeze(); return newAIS; } }