/** * Copyright 2014 Sunny Gleason and original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.kazuki.v0.internal.v2schema; import io.kazuki.v0.store.schema.model.Attribute; import io.kazuki.v0.store.schema.model.IndexAttribute; import io.kazuki.v0.store.schema.model.IndexDefinition; import io.kazuki.v0.store.schema.model.Schema; import io.kazuki.v0.store.schema.model.diff.SchemaDiff; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Objects; /** * Utility class for performing "diff" operations on Schema instances. */ public class SchemaDiffUtil { /** * Returns a list of differences between two Schema instances. * * @param oldSchema Schema instance for the "old" version * @param newSchema Schema instance for the "new" version * @return List<SchemaDiff> list of differences */ @SuppressWarnings("rawtypes") public static List<SchemaDiff> diff(Schema oldSchema, Schema newSchema) { List<SchemaDiff> diffs = new ArrayList<SchemaDiff>(); diffAttributes(oldSchema, newSchema, diffs); diffIndexes(oldSchema, newSchema, diffs); return diffs; } @SuppressWarnings("rawtypes") private static void diffAttributes(Schema oldSchema, Schema newSchema, List<SchemaDiff> diffs) { Map<String, Attribute> oldAttrs = oldSchema.getAttributeMap(); Map<String, Attribute> newAttrs = newSchema.getAttributeMap(); Set<String> oldAttrsDone = new HashSet<String>(); for (Map.Entry<String, Attribute> attrEntry : newAttrs.entrySet()) { String attrName = attrEntry.getKey(); Attribute attr = attrEntry.getValue(); if (attr.getRenameOf() != null && !oldAttrs.containsKey(attr.getRenameOf())) { throw new IllegalArgumentException("new schema contains renameOf non-existent attribute: " + attrName); } if (attr.getRenameOf() != null) { diffs.add(new SchemaDiff<Attribute>(SchemaDiff.DiffType.ATTRIBUTE_RENAME, Attribute.class, oldAttrs.get(attr.getRenameOf()), attr)); oldAttrsDone.add(attr.getRenameOf()); continue; } if (!oldAttrs.containsKey(attrName)) { diffs.add(new SchemaDiff<Attribute>(SchemaDiff.DiffType.ATTRIBUTE_ADD, Attribute.class, null, attr)); oldAttrsDone.add(attrName); continue; } Attribute oldAttr = oldAttrs.get(attrName); if (!oldAttr.getType().equals(attr.getType())) { diffs.add(new SchemaDiff<Attribute>(SchemaDiff.DiffType.ATTRIBUTE_MODIFY, Attribute.class, oldAttr, attr)); oldAttrsDone.add(attrName); continue; } if (oldAttr.isNullable() != attr.isNullable()) { diffs.add(new SchemaDiff<Attribute>(SchemaDiff.DiffType.ATTRIBUTE_MODIFY, Attribute.class, oldAttr, attr)); oldAttrsDone.add(attrName); continue; } List<String> oldVals = oldAttr.getValues(); List<String> newVals = attr.getValues(); if ((oldVals != null && (newVals == null || !oldVals.equals(newVals))) || (newVals != null && (oldVals == null || !newVals.equals(oldVals)))) { diffs.add(new SchemaDiff<Attribute>(SchemaDiff.DiffType.ATTRIBUTE_MODIFY, Attribute.class, oldAttr, attr)); continue; } } for (Map.Entry<String, Attribute> attrEntry : oldAttrs.entrySet()) { String attrName = attrEntry.getKey(); Attribute oldAttr = attrEntry.getValue(); if (oldAttrsDone.contains(attrName)) { continue; } if (!newAttrs.containsKey(attrName)) { diffs.add(new SchemaDiff<Attribute>(SchemaDiff.DiffType.ATTRIBUTE_REMOVE, Attribute.class, oldAttr, null)); oldAttrsDone.add(attrName); continue; } } } @SuppressWarnings("rawtypes") private static void diffIndexes(Schema oldSchema, Schema newSchema, List<SchemaDiff> diffs) { Map<String, IndexDefinition> oldIndexes = oldSchema.getIndexMap(); Map<String, IndexDefinition> newIndexes = newSchema.getIndexMap(); Set<String> oldIndexDone = new HashSet<String>(); for (Map.Entry<String, IndexDefinition> indexEntry : newIndexes.entrySet()) { String indexName = indexEntry.getKey(); IndexDefinition index = indexEntry.getValue(); if (index.getRenameOf() != null && !oldIndexes.containsKey(index.getRenameOf())) { throw new IllegalArgumentException("new schema contains renameOf non-existent index: " + indexName); } if (index.getRenameOf() != null) { diffs.add(new SchemaDiff<IndexDefinition>(SchemaDiff.DiffType.INDEX_RENAME, IndexDefinition.class, oldIndexes.get(index.getRenameOf()), index)); oldIndexDone.add(index.getRenameOf()); continue; } if (!oldIndexes.containsKey(indexName)) { diffs.add(new SchemaDiff<IndexDefinition>(SchemaDiff.DiffType.INDEX_ADD, IndexDefinition.class, null, index)); oldIndexDone.add(indexName); continue; } IndexDefinition oldIndex = oldIndexes.get(indexName); if (oldIndex.isUnique() != index.isUnique()) { diffs.add(new SchemaDiff<IndexDefinition>(SchemaDiff.DiffType.INDEX_MODIFY, IndexDefinition.class, oldIndex, index)); oldIndexDone.add(indexName); continue; } int oldAttrLen = oldIndex.getIndexAttributes().size(); int newAttrLen = index.getIndexAttributes().size(); if (oldAttrLen != newAttrLen) { diffs.add(new SchemaDiff<IndexDefinition>(SchemaDiff.DiffType.INDEX_MODIFY, IndexDefinition.class, oldIndex, index)); oldIndexDone.add(indexName); continue; } for (int i = 0; i < oldAttrLen; i++) { IndexAttribute oldAttr = oldIndex.getIndexAttributes().get(i); IndexAttribute newAttr = index.getIndexAttributes().get(i); if (!oldAttr.getName().equals(newAttr.getName()) || !Objects.equal(oldAttr.getSortDirection(), newAttr.getSortDirection()) || !Objects.equal(oldAttr.getTransform(), newAttr.getTransform())) { diffs.add(new SchemaDiff<IndexDefinition>(SchemaDiff.DiffType.INDEX_MODIFY, IndexDefinition.class, oldIndex, index)); oldIndexDone.add(indexName); continue; } } } for (Map.Entry<String, IndexDefinition> indexEntry : oldIndexes.entrySet()) { String indexName = indexEntry.getKey(); IndexDefinition oldIndex = indexEntry.getValue(); if (oldIndexDone.contains(indexName)) { continue; } if (!newIndexes.containsKey(indexName)) { diffs.add(new SchemaDiff<IndexDefinition>(SchemaDiff.DiffType.INDEX_REMOVE, IndexDefinition.class, oldIndex, null)); oldIndexDone.add(indexName); continue; } } } }