/* This file is part of VoltDB. * Copyright (C) 2008-2010 VoltDB Inc. * * VoltDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VoltDB 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.catalog; public class CatalogDiffEngine { // contains the text of the difference private final StringBuilder m_sb; // true if the difference is allowed in a running system private boolean m_supported; // collection of reasons why a diff is not supported private final StringBuilder m_errors; /** * Instantiate a new diff. The resulting object can return the text * of the difference and report whether the difference is allowed in a * running system. * @param prev Tip of the old catalog. * @param next Tip of the new catalog. */ public CatalogDiffEngine(final CatalogType prev, final CatalogType next) { m_sb = new StringBuilder(); m_errors = new StringBuilder(); m_supported = true; diffRecursively(prev, next); } public String commands() { return m_sb.toString(); } public boolean supported() { return m_supported; } public String errors() { return m_errors.toString(); } /** * @return true if the CatalogType can be dynamically added and removed * from a running system. */ boolean checkAddDropWhitelist(CatalogType suspect) { // should generate this from spec.txt CatalogType orig = suspect; // Support add/drop of only the top level object. if (suspect instanceof Table) return true; // Support add/drop anywhere in these sub-trees do { if (suspect instanceof User) return true; if (suspect instanceof Group) return true; if (suspect instanceof Procedure) return true; if (suspect instanceof Connector) return true; } while ((suspect = suspect.m_parent) != null); m_errors.append("May not dynamically add/drop: " + orig + "\n"); m_supported = false; return false; } /** * @return true if CatalogType can be dynamically modified * in a running system. */ boolean checkModifyWhitelist(CatalogType suspect, String field) { // should generate this from spec.txt CatalogType orig = suspect; // Support modification of these specific fields if (suspect instanceof Database && field.equals("schema")) return true; if (suspect instanceof Cluster && field.equals("securityEnabled")) return true; // Support modification of these entire sub-trees do { if (suspect instanceof User) return true; if (suspect instanceof Group) return true; if (suspect instanceof Procedure) return true; } while ((suspect = suspect.m_parent) != null); m_errors.append("May not dynamically modify field " + field + " of " + orig + "\n"); m_supported = false; return false; } /** * Add a modification */ private void writeModification(CatalogType newType, String field) { checkModifyWhitelist(newType, field); newType.writeCommandForField(m_sb, field); } /** * Add a deletion */ private void writeDeletion(CatalogType prevType, String mapName, String name) { checkAddDropWhitelist(prevType); m_sb.append("delete ").append(prevType.getParent().getPath()).append(" "); m_sb.append(mapName).append(" ").append(name).append("\n"); } /** * Add an addition */ private void writeAddition(CatalogType newType) { checkAddDropWhitelist(newType); newType.writeCreationCommand(m_sb); newType.writeFieldCommands(m_sb); newType.writeChildCommands(m_sb); } /** * Pre-order walk of catalog generating add, delete and set commands * that compose that full difference. * @param prevType * @param newType */ void diffRecursively(CatalogType prevType, CatalogType newType) { assert(prevType != null) : "Null previous object found in catalog diff traversal."; assert(newType != null) : "Null new object found in catalog diff traversal"; // diff local fields for (String field : prevType.getFields()) { if (field.equals("isUp")) { continue; } // check if the types are different // options are: both null => same // one null and one not => different // both not null => check Object.equals() Object prevValue = prevType.getField(field); Object newValue = newType.getField(field); if ((prevValue == null) != (newValue == null)) { writeModification(newType, field); } // if they're both not null (above/below ifs implies this) else if (prevValue != null) { // if comparing CatalogTypes (both must be same) if (prevValue instanceof CatalogType) { assert(newValue instanceof CatalogType); String prevPath = ((CatalogType) prevValue).getPath(); String newPath = ((CatalogType) newValue).getPath(); if (prevPath.compareTo(newPath) != 0) { writeModification(newType, field); } } // if scalar types else { if (prevValue.equals(newValue) == false) { writeModification(newType, field); } } } } // recurse for (String field : prevType.m_childCollections.keySet()) { CatalogMap<? extends CatalogType> prevMap = prevType.m_childCollections.get(field); CatalogMap<? extends CatalogType> newMap = newType.m_childCollections.get(field); getCommandsToDiff(field, prevMap, newMap); } } /** * Check if all the children in prevMap are present and identical in newMap. * Then, check if anything is in newMap that isn't in prevMap. * @param mapName * @param prevMap * @param newMap */ void getCommandsToDiff(String mapName, CatalogMap<? extends CatalogType> prevMap, CatalogMap<? extends CatalogType> newMap) { assert(prevMap != null); assert(newMap != null); // in previous, not in new for (CatalogType prevType : prevMap) { String name = prevType.getTypeName(); CatalogType newType = newMap.get(name); if (newType == null) { writeDeletion(prevType, mapName, name); continue; } diffRecursively(prevType, newType); } // in new, not in previous for (CatalogType newType : newMap) { CatalogType prevType = prevMap.get(newType.getTypeName()); if (prevType != null) continue; writeAddition(newType); } } }