Package com.foundationdb.ais.util

Source Code of com.foundationdb.ais.util.TableChangeValidator

/**
* 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.util;

import com.foundationdb.ais.model.AbstractVisitor;
import com.foundationdb.ais.model.Column;
import com.foundationdb.ais.model.ColumnName;
import com.foundationdb.ais.model.ForeignKey;
import com.foundationdb.ais.model.GroupIndex;
import com.foundationdb.ais.model.Index;
import com.foundationdb.ais.model.IndexColumn;
import com.foundationdb.ais.model.Join;
import com.foundationdb.ais.model.JoinColumn;
import com.foundationdb.ais.model.Sequence;
import com.foundationdb.ais.model.Table;
import com.foundationdb.ais.model.TableIndex;
import com.foundationdb.ais.model.TableName;
import com.foundationdb.ais.util.TableChange.ChangeType;
import com.foundationdb.server.store.format.columnkeys.ColumnKeysStorageDescription;
import com.foundationdb.util.ArgumentValidation;
import com.google.common.base.Objects;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import static com.foundationdb.ais.util.ChangedTableDescription.ParentChange;
import static com.foundationdb.ais.util.TableChangeValidatorException.*;

public class TableChangeValidator {
    private static final Map<String,String> EMPTY_STRING_MAP = Collections.emptyMap();
    private static final List<TableName> EMPTY_TABLE_NAME_LIST = Collections.emptyList();

    public static enum ChangeLevel {
        NONE,
        METADATA,
        METADATA_CONSTRAINT,
        INDEX,
        // TODO: Ugly. Final change level should be a Set and/or constraint changes passed separately.
        INDEX_CONSTRAINT,
        TABLE,
        GROUP
    }

    private final Table oldTable;
    private final Table newTable;
    private final TableChangeValidatorState state;
    private final List<RuntimeException> unmodifiedChanges;

    private ChangeLevel finalChangeLevel;
    private ChangedTableDescription.ParentChange parentChange;
    private boolean primaryKeyChanged;
    private boolean didCompare;

    public TableChangeValidator(Table oldTable,
                                Table newTable,
                                List<TableChange> columnChanges,
                                List<TableChange> tableIndexChanges) {
        ArgumentValidation.notNull("oldTable", oldTable);
        ArgumentValidation.notNull("newTable", newTable);
        ArgumentValidation.notNull("columnChanges", columnChanges);
        ArgumentValidation.notNull("tableIndexChanges", tableIndexChanges);
        this.oldTable = oldTable;
        this.newTable = newTable;
        this.state = new TableChangeValidatorState(columnChanges, tableIndexChanges);
        this.unmodifiedChanges = new ArrayList<>();
        this.finalChangeLevel = ChangeLevel.NONE;
        this.parentChange = ParentChange.NONE;
    }

    public ChangeLevel getFinalChangeLevel() {
        return finalChangeLevel;
    }

    public TableChangeValidatorState getState() {
        return state;
    }

    public boolean isParentChanged() {
        return (parentChange == ParentChange.ADD) || (parentChange == ParentChange.DROP);
    }

    public boolean isPrimaryKeyChanged() {
        return primaryKeyChanged;
    }

    public List<RuntimeException> getUnmodifiedChanges() {
        return unmodifiedChanges;
    }

    public void compare() {
        if(!didCompare) {
            compareTable();
            compareColumns();
            compareIndexes();
            compareGrouping();
            compareGroupIndexes();
            compareForeignKeys();
            updateFinalChangeLevel(ChangeLevel.NONE);
            checkFinalChangeLevel();
            didCompare = true;
        }
    }

    private void updateFinalChangeLevel(ChangeLevel level) {
        if(level.ordinal() > finalChangeLevel.ordinal()) {
            finalChangeLevel = level;
        }
    }

    private void compareTable() {
        TableName oldName = oldTable.getName();
        TableName newName = newTable.getName();
        if(!oldName.equals(newName) ||
           !Objects.equal(oldTable.getCharsetName(), newTable.getCharsetName()) ||
           !Objects.equal(oldTable.getCollationName(), newTable.getCollationName())) {
            updateFinalChangeLevel(ChangeLevel.METADATA);
        }
    }

    private void compareColumns() {
        Map<String,Column> oldColumns = new HashMap<>();
        Map<String,Column> newColumns = new HashMap<>();
        for(Column column : oldTable.getColumnsIncludingInternal()) {
            oldColumns.put(column.getName(), column);
        }
        for(Column column : newTable.getColumnsIncludingInternal()) {
            newColumns.put(column.getName(), column);
        }
        checkChanges(ChangeLevel.TABLE, state.columnChanges, oldColumns, newColumns, false);

        // Look for position changes, not required to be declared
        for(Map.Entry<String, Column> oldEntry : oldColumns.entrySet()) {
            Column newColumn = newColumns.get(findNewName(state.columnChanges, oldEntry.getKey()));
            if((newColumn != null) && !oldEntry.getValue().getPosition().equals(newColumn.getPosition())) {
                updateFinalChangeLevel(ChangeLevel.TABLE);
                break;
            }
        }
    }

    private void compareIndexes() {
        Map<String,TableIndex> oldIndexes = new HashMap<>();
        Map<String,TableIndex> newIndexes = new HashMap<>();
        for(TableIndex index : oldTable.getIndexesIncludingInternal()) {
            oldIndexes.put(index.getIndexName().getName(), index);
        }

        // Look for incompatible spatial changes
        for(TableIndex oldIndex : oldTable.getIndexesIncludingInternal()) {
            String oldName = oldIndex.getIndexName().getName();
            String newName = findNewName(state.tableIndexChanges, oldName);
            TableIndex newIndex = (newName != null) ? newTable.getIndexIncludingInternal(newName) : null;
            if((newIndex != null) && oldIndex.isSpatial() && !Index.isSpatialCompatible(newIndex)) {
                newTable.removeIndexes(Collections.singleton(newIndex));

                // Remove any entry that already exists (e.g. MODIFY from compareColumns())
                Iterator<TableChange> it = state.tableIndexChanges.iterator();
                while(it.hasNext()) {
                    TableChange c = it.next();
                    if(oldName.equals(c.getOldName())) {
                        it.remove();
                        break;
                    }
                }
            }
        }

        for(TableIndex index : newTable.getIndexesIncludingInternal()) {
            newIndexes.put(index.getIndexName().getName(), index);
        }
        checkChanges(ChangeLevel.INDEX, state.tableIndexChanges, oldIndexes, newIndexes, true);
    }

    private void compareGroupIndexes() {
        final Set<Table> keepTables = new HashSet<>();
        final Table traverseStart;
        if(parentChange == ParentChange.DROP) {
            traverseStart = oldTable;
        } else {
           traverseStart = oldTable.getGroup().getRoot();
        }

        traverseStart.visit(new AbstractVisitor() {
            @Override
            public void visit(Table table) {
                keepTables.add(table);
            }
        });

        for(GroupIndex index : oldTable.getGroupIndexes()) {
            boolean dataChange = (finalChangeLevel == ChangeLevel.GROUP);
            List<ColumnName> remainingCols = new ArrayList<>();
            for(IndexColumn iCol : index.getKeyColumns()) {
                Column column = iCol.getColumn();
                if(!keepTables.contains(column.getTable())) {
                    remainingCols.clear();
                    break;
                }
                String oldName = column.getName();
                boolean isTargetTable = column.getTable() == oldTable;
                String newName = isTargetTable ? findNewName(state.columnChanges, oldName) : oldName;
                if(newName != null) {
                    TableName tableName = isTargetTable ? newTable.getName() : column.getTable().getName();
                    remainingCols.add(new ColumnName(tableName, newName));
                    if(column.getTable() == oldTable) {
                        Column oldColumn = oldTable.getColumn(oldName);
                        Column newColumn = newTable.getColumn(newName);
                        dataChange |= (compare(oldColumn, newColumn) == ChangeLevel.TABLE);
                    }
                } else {
                    dataChange = true;
                }
            }
            if(remainingCols.size() <= 1) {
                remainingCols.clear();
                state.droppedGI.add(index.getIndexName().getName());
            } else {
                state.affectedGI.put(index.getIndexName().getName(), remainingCols);
                if(dataChange) {
                    state.dataAffectedGI.put(index.getIndexName().getName(), remainingCols);
                }
            }
        }
    }

    private <T> void checkChanges(ChangeLevel level, List<TableChange> changeList, Map<String,T> oldMap, Map<String,T> newMap, boolean doAutoChanges) {
        final boolean isIndex = (level == ChangeLevel.INDEX);
        Set<String> oldExcludes = new HashSet<>();
        Set<String> newExcludes = new HashSet<>();

        List<TableChange> autoChanges = doAutoChanges ? new ArrayList<TableChange>() : null;

        // Check declared changes
        for(TableChange change : changeList) {
            String oldName = change.getOldName();
            String newName = change.getNewName();
            switch(change.getChangeType()) {
                case ADD: {
                    if(newMap.get(newName) == null) {
                        if(doAutoChanges) {
                            autoChanges.add(TableChange.createAdd(newName));
                        } else {
                            addNotPresent(isIndex, change);
                        }
                    } else {
                        updateFinalChangeLevel(level);
                        newExcludes.add(newName);
                    }
                }
                break;

                case DROP: {
                    if(oldMap.get(oldName) == null) {
                        dropNotPresent(isIndex, change);
                    } else {
                        updateFinalChangeLevel(level);
                        oldExcludes.add(oldName);
                    }
                }
                break;

                case MODIFY: {
                    T oldVal = oldMap.get(oldName);
                    T newVal = newMap.get(newName);
                    if((oldVal == null) || (newVal == null)) {
                        modifyNotPresent(isIndex, change);
                    } else {
                        ChangeLevel curChange = compare(oldVal, newVal);
                        if(curChange == ChangeLevel.NONE) {
                            unmodifiedChanges.add(modifyNotChanged(isIndex, change));
                        } else {
                            updateFinalChangeLevel(curChange);
                            oldExcludes.add(oldName);
                            newExcludes.add(newName);
                        }
                    }
                }
                break;
            }
        }

        // Check remaining elements in old table
        for(Map.Entry<String,T> entry : oldMap.entrySet()) {
            String name = entry.getKey();
            if(!oldExcludes.contains(name)) {
                T newVal = newMap.get(name);
                if(newVal == null) {
                    if(doAutoChanges) {
                        autoChanges.add(TableChange.createDrop(name));
                    } else {
                        unchangedNotPresent(isIndex, name);
                    }
                } else {
                    ChangeLevel change = compare(entry.getValue(), newVal);
                    if(change != ChangeLevel.NONE) {
                        if(doAutoChanges) {
                            autoChanges.add(TableChange.createModify(name, name));
                        } else {
                            undeclaredChange(isIndex, name);
                        }
                    }
                    newExcludes.add(name);
                }
            }
        }

        // Check remaining elements in new table (should be none)
        for(String name : newMap.keySet()) {
            if(!newExcludes.contains(name)) {
                if(doAutoChanges) {
                    autoChanges.add(TableChange.createAdd(name));
                } else {
                    undeclaredChange(isIndex, name);
                }
            }
        }

        if(doAutoChanges && !autoChanges.isEmpty()) {
            changeList.addAll(autoChanges);
            updateFinalChangeLevel(level);
        }
    }

    private void compareGrouping() {
        parentChange = compareParentJoin(state.columnChanges, oldTable.getParentJoin(), newTable.getParentJoin());
        primaryKeyChanged = containsOldOrNew(state.tableIndexChanges, Index.PRIMARY);

        List<TableName> droppedSequences = new ArrayList<>();
        List<String> addedIdentity = new ArrayList<>();
        List<String> addedIndexes = new ArrayList<>();
        Map<String,String> renamedColumns = new HashMap<>();
        for(TableChange change : state.columnChanges) {
            switch(change.getChangeType()) {
                case MODIFY: {
                    if(!change.getOldName().equals(change.getNewName())) {
                        renamedColumns.put(change.getOldName(), change.getNewName());
                    }
                    Column oldColumn = oldTable.getColumn(change.getOldName());
                    Column newColumn = newTable.getColumn(change.getNewName());
                    if((oldColumn != null)) {
                        Sequence oldSeq = oldColumn.getIdentityGenerator();
                        Sequence newSeq = newColumn.getIdentityGenerator();
                        if((oldSeq == null) && (newSeq != null)) {
                            addedIdentity.add(newColumn.getName());
                        } else if((oldSeq != null) && (newSeq == null)) {
                            droppedSequences.add(oldSeq.getSequenceName());
                        }
                        // else both not null and not equal, not yet supported
                    }
                } break;
                case DROP: {
                    Column oldColumn = oldTable.getColumn(change.getOldName());
                    if((oldColumn != null) && (oldColumn.getIdentityGenerator() != null)) {
                        droppedSequences.add(oldColumn.getIdentityGenerator().getSequenceName());
                    }
                } break;
                case ADD: {
                    Column newColumn = newTable.getColumn(change.getNewName());
                    Sequence newSeq = newColumn.getIdentityGenerator();
                    if(newSeq != null) {
                        addedIdentity.add(newColumn.getName());
                    }
                } break;
            }
        }

        for(TableChange change : state.tableIndexChanges) {
            if(change.getChangeType() == ChangeType.ADD) {
                addedIndexes.add(change.getNewName());
            }
        }

        boolean renamed = !oldTable.getName().equals(newTable.getName()) || !renamedColumns.isEmpty();

        Map<String,String> preserveIndexes = new TreeMap<>();
        TableName parentName = (newTable.getParentJoin() != null) ? newTable.getParentJoin().getParent().getName() : null;
        state.descriptions.add(
            new ChangedTableDescription(
                oldTable.getTableId(),
                oldTable.getName(),
                newTable,
                renamedColumns,
                parentChange,
                parentName,
                EMPTY_STRING_MAP,
                preserveIndexes,
                droppedSequences,
                addedIdentity,
                addedIndexes,
                finalChangeLevel == ChangeLevel.TABLE,
                isParentChanged() || primaryKeyChanged
            )
        );

        if(!isParentChanged() && !primaryKeyChanged) {
            for(Index index : newTable.getIndexesIncludingInternal()) {
                String oldName = index.getIndexName().getName();
                String newName = findNewName(state.tableIndexChanges, oldName);
                if(!containsOldOrNew(state.tableIndexChanges, oldName)) {
                    preserveIndexes.put(oldName, newName);
                }
            }
        }

        Collection<Join> oldChildJoins = new ArrayList<>(oldTable.getCandidateChildJoins());
        for(Join join : oldChildJoins) {
            Table oldChildTable = join.getChild();

            // If referenced column has anymore has a TABLE change (or is missing), join needs dropped
            boolean dropParent = false;
            for(JoinColumn joinCol : join.getJoinColumns()) {
                Column oldColumn = joinCol.getParent().getColumn();
                String newName = findNewName(state.columnChanges, oldColumn.getName());
                if(newName == null) {
                    dropParent = true;
                } else {
                    Column newColumn = newTable.getColumn(newName);
                    if(compare(oldColumn, newColumn) == ChangeLevel.TABLE) {
                        dropParent = true;
                    }
                }
            }

            boolean preserve = false;
            ParentChange change = null;

            // If PK changed and table had children, PK was dropped
            if(primaryKeyChanged || dropParent) {
                updateFinalChangeLevel(ChangeLevel.GROUP);
                change = ParentChange.DROP;
            } else if(isParentChanged() || (parentChange == ParentChange.ADD)) {
                updateFinalChangeLevel(ChangeLevel.GROUP);
                change = ParentChange.UPDATE;
            } else if(renamed) {
                updateFinalChangeLevel(ChangeLevel.METADATA);
                change = ParentChange.META;
                preserve = true;
            }

            if(change != null) {
                TableName newParent = (change == ParentChange.DROP) ? null : newTable.getName();
                trackChangedTable(oldChildTable, change, newParent, renamedColumns, preserve);
                propagateChildChange(oldChildTable, ParentChange.UPDATE, preserve);
            }
        }

        if(isParentChanged() || primaryKeyChanged) {
            updateFinalChangeLevel(ChangeLevel.GROUP);
        }
    }

    private void compareForeignKeys() {
        // Flag referenced table as having metadata changed
        // No way to rename or alter a FK definition so only need to check presence change.
        Set<TableName> referencedChanges = new HashSet<>();
        for(ForeignKey fk : oldTable.getReferencingForeignKeys()) {
            if(newTable.getReferencingForeignKey(fk.getConstraintName().getTableName()) == null) {
                referencedChanges.add(fk.getReferencedTable().getName());
            }
        }
        for(ForeignKey fk : newTable.getReferencingForeignKeys()) {
            if(oldTable.getReferencingForeignKey(fk.getConstraintName().getTableName()) == null) {
                referencedChanges.add(fk.getReferencedTable().getName());
            }
        }
        // TODO: Would be nice to track complete details (e.g. constraint name) instead of just table
        for(TableName refName : referencedChanges) {
            if(!state.hasOldTable(refName)) {
                Table table = oldTable.getAIS().getTable(refName);
                TableName parentName = (table.getParentJoin() != null) ? table.getParentJoin().getParent().getName() : null;
                trackChangedTable(table, ParentChange.NONE, parentName, null, true);
            }
        }
        if(!referencedChanges.isEmpty()) {
            switch(finalChangeLevel) {
                case NONE:
                case METADATA:
                case METADATA_CONSTRAINT:
                    updateFinalChangeLevel(ChangeLevel.METADATA_CONSTRAINT);
                    break;
                case INDEX:
                    if(!state.dataAffectedGI.isEmpty()) {
                        throw new IllegalStateException("New FOREIGN KEY and group index?");
                    }
                    updateFinalChangeLevel(ChangeLevel.INDEX_CONSTRAINT);
                case TABLE:
                case GROUP:
                    // None. These already have constraints checked.
                    break;
                default:
                    assert false : finalChangeLevel;
            }
        }
    }

    private void propagateChildChange(final Table table, final ParentChange change, final boolean allIndexes) {
        table.visitBreadthFirst(new AbstractVisitor() {
            @Override
            public void visit(Table curTable) {
                if(table != curTable) {
                    TableName parentName = curTable.getParentJoin().getParent().getName();
                    trackChangedTable(curTable, change, parentName, null, allIndexes);
                }
            }
        });
    }

    private void trackChangedTable(Table table, ParentChange parentChange, TableName parentName,
                                   Map<String, String> parentRenames, boolean doPreserve) {
        Map<String,String> preserved = new HashMap<>();
        if(doPreserve) {
            for(Index index : table.getIndexesIncludingInternal()) {
                preserved.put(index.getIndexName().getName(), index.getIndexName().getName());
            }
        }
        parentRenames = (parentRenames != null) ? parentRenames : EMPTY_STRING_MAP;
        state.descriptions.add(
            new ChangedTableDescription(
                table.getTableId(),
                table.getName(),
                null,
                EMPTY_STRING_MAP,
                parentChange,
                parentName,
                parentRenames,
                preserved,
                EMPTY_TABLE_NAME_LIST,
                Collections.<String>emptyList(),
                Collections.<String>emptyList(),
                false,
                !doPreserve
            )
        );
    }

    private static boolean containsOldOrNew(List<TableChange> changes, String name) {
        for(TableChange change : changes) {
            if(name.equals(change.getOldName()) || name.equals(change.getNewName())) {
                return true;
            }
        }
        return false;
    }

    private static String findNewName(List<TableChange> changes, String oldName) {
        for(TableChange change : changes) {
            if(oldName.equals(change.getOldName())) {
                switch(change.getChangeType()) {
                    case ADD:       throw new IllegalStateException("Old name was added? " + oldName);
                    case DROP:      return null;
                    case MODIFY:    return change.getNewName();
                }
            }
        }
        return oldName;
    }

    private <T> ChangeLevel compare(T oldVal, T newVal) {
        if(oldVal instanceof Column) {
            return compare((Column)oldVal, (Column)newVal);
        }
        if(oldVal instanceof Index) {
            return compare((Index)oldVal, (Index)newVal);
        }
        throw new IllegalStateException("Cannot compare: " + oldVal + " and " + newVal);
    }

    private static ChangeLevel compare(Column oldCol, Column newCol) {
        if(!oldCol.getType().equalsExcludingNullable(newCol.getType())) {
            return ChangeLevel.TABLE;
        }
        if(oldCol.getTable().getGroup().getStorageDescription() instanceof ColumnKeysStorageDescription &&
                !oldCol.getName().equals(newCol.getName())) {
            return ChangeLevel.TABLE;
        }
        boolean oldNull = oldCol.getNullable();
        boolean newNull = newCol.getNullable();
        if((oldNull == true) && (newNull == false)) {
            return ChangeLevel.METADATA_CONSTRAINT;
        }
        if((oldNull != newNull) ||
           !oldCol.getName().equals(newCol.getName()) ||
           !Objects.equal(oldCol.getDefaultValue(), newCol.getDefaultValue()) ||
           !Objects.equal(oldCol.getDefaultIdentity(), newCol.getDefaultIdentity()) ||
           sequenceChanged(oldCol.getIdentityGenerator(), newCol.getIdentityGenerator())) {
          return ChangeLevel.METADATA;
        }
        return ChangeLevel.NONE;
    }

    private ChangeLevel compare(Index oldIndex, Index newIndex) {
        if(oldIndex.getKeyColumns().size() != newIndex.getKeyColumns().size()) {
            return ChangeLevel.INDEX;
        }

        Iterator<IndexColumn> oldIt = oldIndex.getKeyColumns().iterator();
        Iterator<IndexColumn> newIt = newIndex.getKeyColumns().iterator();
        while(oldIt.hasNext()) {
            IndexColumn oldICol = oldIt.next();
            IndexColumn newICol = newIt.next();
            String newColName = findNewName(state.columnChanges, oldICol.getColumn().getName());
            // Column the same?
            if((newColName == null) || !newICol.getColumn().getName().equals(newColName)) {
                return ChangeLevel.INDEX;
            }
            // IndexColumn properties
            if(!Objects.equal(oldICol.getIndexedLength(), newICol.getIndexedLength()) ||
               !Objects.equal(oldICol.isAscending(), newICol.isAscending())) {
                return ChangeLevel.INDEX;
            }
            // Column being indexed
            if(compare(oldICol.getColumn(), newICol.getColumn()) == ChangeLevel.TABLE) {
                return ChangeLevel.INDEX;
            }
        }

        if(!oldIndex.getIndexName().getName().equals(newIndex.getIndexName().getName())) {
            return ChangeLevel.METADATA;
        }
        return ChangeLevel.NONE;
    }

    private static boolean sequenceChanged(Sequence oldSeq, Sequence newSeq) {
        if(oldSeq == null && newSeq == null) {
            return false;
        }
        if(oldSeq != null && newSeq == null) {
            return true;
        }
        if(oldSeq == null /*&& newSeq != null**/) {
            return true;
        }
        return (oldSeq.getStartsWith() != newSeq.getStartsWith()) ||
               (oldSeq.getIncrement() != newSeq.getIncrement()) ||
               (oldSeq.getMinValue() != newSeq.getMinValue()) ||
               (oldSeq.getMaxValue() != newSeq.getMaxValue()) ||
               (oldSeq.isCycle() != newSeq.isCycle());
    }

    private static ParentChange compareParentJoin(List<TableChange> columnChanges, Join oldJoin, Join newJoin) {
        if(oldJoin == null && newJoin == null) {
            return ParentChange.NONE;
        }
        if(oldJoin != null && newJoin == null) {
            return ParentChange.DROP;
        }
        if(oldJoin == null /*&& newJoin != null*/) {
            return ParentChange.ADD;
        }

        Table oldParent = oldJoin.getParent();
        Table newParent = newJoin.getParent();
        if(!oldParent.getName().equals(newParent.getName()) ||
           (oldJoin.getJoinColumns().size() != newJoin.getJoinColumns().size())) {
            return ParentChange.ADD;
        }

        boolean sawRename = false;
        Iterator<JoinColumn> oldIt = oldJoin.getJoinColumns().iterator();
        Iterator<JoinColumn> newIt = newJoin.getJoinColumns().iterator();
        while(oldIt.hasNext()) {
            Column oldCol = oldIt.next().getChild();
            String newName = findNewName(columnChanges, newIt.next().getChild().getName());
            if(newName == null) {
                return ParentChange.DROP;
            } else {
                Column newCol = newJoin.getChild().getColumn(newName);
                if(compare(oldCol, newCol) == ChangeLevel.TABLE) {
                    return ParentChange.DROP;
                } else if(!oldCol.getName().equals(newName)) {
                    sawRename = true;
                }
            }
        }
        return sawRename ? ParentChange.META : ParentChange.NONE;
    }

    private void addNotPresent(boolean isIndex, TableChange change) {
        String detail = change.toString();
        throw isIndex ? new AddIndexNotPresentException(detail) : new AddColumnNotPresentException(detail);
    }

    private void dropNotPresent(boolean isIndex, TableChange change) {
        String detail = change.toString();
        throw isIndex ? new DropIndexNotPresentException(detail) : new DropColumnNotPresentException(detail);
    }

    private static RuntimeException modifyNotChanged(boolean isIndex, TableChange change) {
        String detail = change.toString();
        return isIndex ? new ModifyIndexNotChangedException(detail) : new ModifyColumnNotChangedException(detail);
    }

    private void modifyNotPresent(boolean isIndex, TableChange change) {
        String detail = change.toString();
        throw isIndex ? new ModifyIndexNotPresentException(detail) : new ModifyColumnNotPresentException(detail);
    }

    private void unchangedNotPresent(boolean isIndex, String detail) {
        assert !isIndex;
        throw new UnchangedColumnNotPresentException(detail);
    }

    private void undeclaredChange(boolean isIndex, String detail) {
        assert !isIndex;
        throw new UndeclaredColumnChangeException(detail);
    }

    private void checkFinalChangeLevel() {
        if(finalChangeLevel == null) {
            return;
        }
        // Internal consistency checks
        switch(finalChangeLevel) {
            case NONE:
                if(!state.affectedGI.isEmpty()) {
                    throw new IllegalStateException("NONE but had affected GI: " + state.affectedGI);
                }
            break;
            case METADATA:
            case METADATA_CONSTRAINT:
                if(!state.droppedGI.isEmpty()) {
                    throw new IllegalStateException("META but had dropped GI: " + state.droppedGI);
                }
                if(!state.dataAffectedGI.isEmpty()) {
                    throw new IllegalStateException("META but had data affected GI: " + state.dataAffectedGI);
                }
            break;
            case INDEX:
            case INDEX_CONSTRAINT:
                if(!state.dataAffectedGI.isEmpty()) {
                    throw new IllegalStateException("INDEX but had data affected GI: " + state.dataAffectedGI);
                }
                break;
            case TABLE:
            case GROUP:
                break;
            default:
                throw new IllegalStateException(finalChangeLevel.toString());
        }
    }
}
TOP

Related Classes of com.foundationdb.ais.util.TableChangeValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.