From 2a5a337b4e6525d2483f6528fb372e61b71272c2 Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Tue, 18 Oct 2011 11:42:57 +0000 Subject: [PATCH] ALF-10772: schema comparator Compares two abstract database schemas. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@31312 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/util/schemacomp/Differences.java | 105 ++++++++- .../org/alfresco/util/schemacomp/Result.java | 80 +++++-- .../util/schemacomp/SchemaComparator.java | 49 ++-- .../util/schemacomp/SchemaComparatorTest.java | 214 ++++++++++++++++-- .../alfresco/util/schemacomp/SchemaUtils.java | 132 +++++++++++ .../schemacomp/model/AbstractDbObject.java | 76 ++++++- .../util/schemacomp/model/Column.java | 29 ++- .../util/schemacomp/model/DbObject.java | 31 ++- .../util/schemacomp/model/ForeignKey.java | 38 +++- .../alfresco/util/schemacomp/model/Index.java | 57 +++-- .../util/schemacomp/model/PrimaryKey.java | 13 +- .../util/schemacomp/model/Schema.java | 46 ++-- .../util/schemacomp/model/Sequence.java | 5 +- .../alfresco/util/schemacomp/model/Table.java | 63 ++++-- 14 files changed, 799 insertions(+), 139 deletions(-) create mode 100644 source/java/org/alfresco/util/schemacomp/SchemaUtils.java diff --git a/source/java/org/alfresco/util/schemacomp/Differences.java b/source/java/org/alfresco/util/schemacomp/Differences.java index 668f1694d2..f4289c2023 100644 --- a/source/java/org/alfresco/util/schemacomp/Differences.java +++ b/source/java/org/alfresco/util/schemacomp/Differences.java @@ -19,15 +19,112 @@ package org.alfresco.util.schemacomp; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Stack; + +import org.alfresco.util.schemacomp.Result.Where; +import org.alfresco.util.schemacomp.model.DbObject; +import org.apache.commons.lang.StringUtils; /** - * Keeps a record of what differences, if any, exist between the two - * schemas being compared. + * Collects differences so that tools can report on or respond to differences between database schemas. * * @author Matt Ward */ -public class Differences +public class Differences implements Iterable { - private List results = new ArrayList(); + private final List items = new ArrayList(); + private final Path path = new Path(); + + + /** + * @see Path + */ + public void pushPath(String component) + { + this.path.push(component); + } + + /** + * @see Path + */ + public String popPath() + { + return this.path.pop(); + } + + /** + * Record a difference between two objects, or specify that an object only appears in either the + * 'left' or 'right' schemas. + * + * @param where The type of difference, see {@link Where} + * @param left Left value, or null if the item appears in the right, but not left schema. + * @param right Right value, or null if the item appears in the left, but not right schema. + */ + public void add(Where where, Object left, Object right) + { + Result result = new Result(where, left, right, path.getCurrentPath()); + items.add(result); + } + + /** + * Obtain an iterator for the top-level items held in this schema - since this is a hierarchical model, + * deeper items are obtained by navigating through the top-level items. + */ + @Override + public Iterator iterator() + { + return items.iterator(); + } + + /** + * @return How many top-level items are in the schema. + */ + public int size() + { + return items.size(); + } + + /** + * Specifies where in a database schema a difference occurs - this is largely the same as the fully qualified + * name of a database object, e.g. public.alf_node except that if an object doesn't have a name + * then a suitable label will be provided for that path element. + *

+ * Elements can be pushed onto the path and popped from the path, making it easier for a + * {@link DbObject} to process that object's data as part of a heirachy - the parent object should push an + * appropriate label onto the path for itself, before invoking any child objects' + * {@link DbObject#diff(DbObject, Differences)} method. + * + * @author Matt Ward + */ + private static class Path + { + private Stack components = new Stack(); + private String current; + + public void push(String component) + { + components.push(component); + makeCurrentPath(); + } + + public String pop() + { + String component = components.pop(); + makeCurrentPath(); + return component; + } + + public String getCurrentPath() + { + return current; + } + + private void makeCurrentPath() + { + current = StringUtils.join(components, "."); + } + + } } diff --git a/source/java/org/alfresco/util/schemacomp/Result.java b/source/java/org/alfresco/util/schemacomp/Result.java index 0894bd618f..5b694ba517 100644 --- a/source/java/org/alfresco/util/schemacomp/Result.java +++ b/source/java/org/alfresco/util/schemacomp/Result.java @@ -18,39 +18,73 @@ */ package org.alfresco.util.schemacomp; -import org.alfresco.util.schemacomp.model.DbObject; + /** * Result of a comparison between two database objects. * * @author Matt Ward */ -public class Result +public final class Result { - public enum Type { ONLY_IN_LEFT, ONLY_IN_RIGHT, IN_BOTH_NO_DIFFERENCE, IN_BOTH_BUT_DIFFERENCE }; - private Type type; - - // Field list, where differences lie - recorded for single level only, otherwise, an error deep down would end - // up having the whole schema as being different, but that isn't useful. During reporting, climb back up the tree - // to produce a path, e.g. "my_schema.my_table.my_column.nullable has differences" + /** Specifies the type of differences */ + public enum Where { ONLY_IN_LEFT, ONLY_IN_RIGHT, IN_BOTH_NO_DIFFERENCE, IN_BOTH_BUT_DIFFERENCE }; + private final Where where; + private final Object left; + private final Object right; + private final String path; - // Could hold the two items that (may - see type above) differ? - // These objects are already structured, no field names required. - DbObject left; - DbObject right; - - // or, could... - // Have differences - - - public static class DiffField + public Result(Where where, Object left, Object right, String path) { - String leftFieldName; - String leftVal; - - String rightFieldName; - String rightVal; + this.where = where; + this.left = left; + this.right = right; + this.path = path; } + + + /** + * @return the where + */ + public Where getWhere() + { + return this.where; + } + + + /** + * @return the left + */ + public Object getLeft() + { + return this.left; + } + + + /** + * @return the right + */ + public Object getRight() + { + return this.right; + } + + + /** + * @return the path + */ + public String getPath() + { + return this.path; + } + + + @Override + public String toString() + { + return "Result [where=" + this.where + ", left=" + this.left + ", right=" + this.right + + ", path=" + this.path + "]"; + } } diff --git a/source/java/org/alfresco/util/schemacomp/SchemaComparator.java b/source/java/org/alfresco/util/schemacomp/SchemaComparator.java index adafe053f2..130fa058a4 100644 --- a/source/java/org/alfresco/util/schemacomp/SchemaComparator.java +++ b/source/java/org/alfresco/util/schemacomp/SchemaComparator.java @@ -18,17 +18,19 @@ */ package org.alfresco.util.schemacomp; -import org.alfresco.util.schemacomp.model.DbObject; import org.alfresco.util.schemacomp.model.Schema; /** - * TODO: comment me! + * Compares two abstract database schemas, a 'left' schema and a 'right' schema. The left schema is the primary + * schema and all objects in the right schema are judged relative to it. + * * @author Matt Ward */ public class SchemaComparator { - private Schema leftSchema; - private Schema rightSchema; + private final Schema leftSchema; + private final Schema rightSchema; + private final Differences differences = new Differences(); /** * Construct a comparator to compare schemas left and right. @@ -42,34 +44,19 @@ public class SchemaComparator this.rightSchema = right; } + public void compare() { - for (DbObject leftObj : leftSchema) - { - DbObject rightObj = rightSchema.get(leftObj.getIdentifier()); - if (rightObj != null) - { - // There is an equivalent object in the right hand schema as in the left. - System.out.println("Both schemas have object: " + leftObj.getIdentifier() + - "(" + leftObj.getClass().getSimpleName() + ")"); - } - else - { - // No equivalent object in the right hand schema. - System.out.println("No matching object in right schema: " + leftObj.getIdentifier() + - "(" + leftObj.getClass().getSimpleName() + ")"); - } - } - - // Identify objects in the right schema but not the left - for (DbObject rightObj : rightSchema) - { - if (!leftSchema.contains(rightObj.getIdentifier())) - { - // No equivalent object in the left hand schema. - System.out.println("No matching object in left schema: " + rightObj.getIdentifier() + - "(" + rightObj.getClass().getSimpleName() + ")"); - } - } + // Check the left schema against the right schema and record any differences. + leftSchema.diff(rightSchema, differences); + } + + + /** + * @return the differences + */ + public Differences getDifferences() + { + return this.differences; } } diff --git a/source/java/org/alfresco/util/schemacomp/SchemaComparatorTest.java b/source/java/org/alfresco/util/schemacomp/SchemaComparatorTest.java index efc8b13511..a4712a0835 100644 --- a/source/java/org/alfresco/util/schemacomp/SchemaComparatorTest.java +++ b/source/java/org/alfresco/util/schemacomp/SchemaComparatorTest.java @@ -19,13 +19,27 @@ package org.alfresco.util.schemacomp; +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.alfresco.util.schemacomp.Result.Where; +import org.alfresco.util.schemacomp.model.Column; +import org.alfresco.util.schemacomp.model.ForeignKey; +import org.alfresco.util.schemacomp.model.Index; +import org.alfresco.util.schemacomp.model.PrimaryKey; import org.alfresco.util.schemacomp.model.Schema; import org.alfresco.util.schemacomp.model.Table; +import org.apache.commons.lang.ArrayUtils; import org.junit.Before; import org.junit.Test; /** - * TODO: comment me! + * Tests for the SchmeaComparator class. + * * @author Matt Ward */ public class SchemaComparatorTest @@ -37,8 +51,8 @@ public class SchemaComparatorTest @Before public void setup() { - left = new Schema(); - right = new Schema(); + left = new Schema("left_schema"); + right = new Schema("right_schema"); } @Test @@ -47,19 +61,183 @@ public class SchemaComparatorTest } -// @Test -// public void canDetermineSameTables() -// { -// left.put(new Table("alf_node")); -// left.put(new Table("table_in_left")); -// left.put(new Table("in_both_but_different")); -// right.put(new Table("alf_node")); -// // Note this table is in different position in the RHS list. -// Table rightTable = new Table("in_both_but_different"); -// right.put(rightTable); -// right.put(new Table("table_in_right")); -// -// comparator = new SchemaComparator(left, right); -// comparator.compare(); -// } + @Test + public void canPerformDiff() + { + // Left hand side's database objects. + left.add(new Table("tbl_no_diff", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"), + pk("pk_tbl_no_diff", "id"), fkeys(fk("fk_tbl_no_diff", "nodeRef", "node", "nodeRef")), + indexes("idx_node id nodeRef"))); + left.add(table("table_in_left")); + left.add(new Table("tbl_has_diff_pk", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)"), + pk("pk_is_diff", "id"), fkeys(), indexes())); + + // Right hand side's database objects. + right.add(new Table("tbl_no_diff", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"), + pk("pk_tbl_no_diff", "id"), fkeys(fk("fk_tbl_no_diff", "nodeRef", "node", "nodeRef")), + indexes("idx_node id nodeRef"))); + right.add(new Table("tbl_has_diff_pk", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)"), + pk("pk_is_diff", "nodeRef"), fkeys(), indexes())); + right.add(table("table_in_right")); + + + comparator = new SchemaComparator(left, right); + comparator.compare(); + + dumpDiffs(comparator.getDifferences(), true); + + Iterator it = comparator.getDifferences().iterator(); + assertNoDifference("left_schema.tbl_no_diff", "tbl_no_diff", it.next()); + assertNoDifference("left_schema.tbl_no_diff.id", "id", it.next()); + assertNoDifference("left_schema.tbl_no_diff.id", "NUMBER(10)", it.next()); + assertNoDifference("left_schema.tbl_no_diff.id", Boolean.FALSE, it.next()); // nullable + assertNoDifference("left_schema.tbl_no_diff.nodeRef", "nodeRef", it.next()); + assertNoDifference("left_schema.tbl_no_diff.nodeRef", "VARCHAR2(200)", it.next()); + assertNoDifference("left_schema.tbl_no_diff.nodeRef", Boolean.FALSE, it.next()); // nullable + assertNoDifference("left_schema.tbl_no_diff.name", "name", it.next()); + assertNoDifference("left_schema.tbl_no_diff.name", "VARCHAR2(150)", it.next()); + assertNoDifference("left_schema.tbl_no_diff.name", Boolean.FALSE, it.next()); // nullable + assertNoDifference("left_schema.tbl_no_diff.pk_tbl_no_diff", "pk_tbl_no_diff", it.next()); // name field + assertNoDifference("left_schema.tbl_no_diff.pk_tbl_no_diff", "id", it.next()); // first (& only) column of list + assertNoDifference("left_schema.tbl_no_diff.fk_tbl_no_diff", "fk_tbl_no_diff", it.next()); // name field + assertNoDifference("left_schema.tbl_no_diff.fk_tbl_no_diff", "nodeRef", it.next()); // localColumn + assertNoDifference("left_schema.tbl_no_diff.fk_tbl_no_diff", "node", it.next()); // targetTable + assertNoDifference("left_schema.tbl_no_diff.fk_tbl_no_diff", "nodeRef", it.next()); // targetColumn + assertNoDifference("left_schema.tbl_no_diff.idx_node", "idx_node", it.next()); // index name + assertNoDifference("left_schema.tbl_no_diff.idx_node", "id", it.next()); // first indexed column + assertNoDifference("left_schema.tbl_no_diff.idx_node", "nodeRef", it.next()); // second indexed column + // TODO: why are diffs for table not flattened out as for index? + assertOnlyInOne("left_schema", Where.ONLY_IN_LEFT, table("table_in_left"), it.next()); + + assertNoDifference("left_schema.tbl_has_diff_pk", "tbl_has_diff_pk", it.next()); + assertNoDifference("left_schema.tbl_has_diff_pk.id", "id", it.next()); + assertNoDifference("left_schema.tbl_has_diff_pk.id", "NUMBER(10)", it.next()); + assertNoDifference("left_schema.tbl_has_diff_pk.id", Boolean.FALSE, it.next()); // nullable + assertNoDifference("left_schema.tbl_has_diff_pk.nodeRef", "nodeRef", it.next()); + assertNoDifference("left_schema.tbl_has_diff_pk.nodeRef", "VARCHAR2(200)", it.next()); + assertNoDifference("left_schema.tbl_has_diff_pk.nodeRef", Boolean.FALSE, it.next()); // nullable + assertNoDifference("left_schema.tbl_has_diff_pk.pk_is_diff", "pk_is_diff", it.next()); // name field + + // TODO: surely this should be a diff rather than a ONLY_IN_LEFT plus ONLY_IN_RIGHT? +// assertHasDifference("left_schema.tbl_has_diff_pk.pk_is_diff", "id", "nodeRef", it.next()); // first (& only) column of list + assertOnlyInOne("left_schema.tbl_has_diff_pk.pk_is_diff", Where.ONLY_IN_LEFT, "id", it.next()); // first (& only) column of list + + // This belong to the pk_is_diff above. + assertOnlyInOne("left_schema.tbl_has_diff_pk.pk_is_diff", Where.ONLY_IN_RIGHT, "nodeRef", it.next()); // first (& only) column of list + + // Items that are ONLY_IN_RIGHT always come at the end + assertOnlyInOne("left_schema", Where.ONLY_IN_RIGHT, table("table_in_right"), it.next()); + } + + + /** + * Assert that the result shows the value to have different values in the left and right items. + */ + private void assertHasDifference(String path, Object leftValue, Object rightValue, Result result) + { + assertEquals(Where.IN_BOTH_BUT_DIFFERENCE, result.getWhere()); + assertEquals(path, result.getPath()); + assertEquals(leftValue, result.getLeft()); + assertEquals(rightValue, result.getRight()); + } + + /** + * Assert that the result shows the value to be present only in either the left or right items. + */ + private void assertOnlyInOne(String path, Where which, Object value, Result result) + { + assertEquals(which, result.getWhere()); + assertEquals(path, result.getPath()); + + if (which == Where.ONLY_IN_LEFT) + { + assertEquals(value, result.getLeft()); + assertNull(result.getRight()); + } + else if (which == Where.ONLY_IN_RIGHT) + { + assertNull(result.getLeft()); + assertEquals(value, result.getRight()); + } + else + { + throw new IllegalArgumentException("The 'which' argument should be ONLY_IN_LEFT or ONLY_IN_RIGHT."); + } + } + + /** + * Assert that the result shows no differences between the left and right items. + */ + private void assertNoDifference(String path, Object value, Result result) + { + assertEquals(Where.IN_BOTH_NO_DIFFERENCE, result.getWhere()); + assertEquals(path, result.getPath()); + assertEquals(value, result.getLeft()); + assertEquals(value, result.getRight()); + } + + /** + * @param differences + */ + private void dumpDiffs(Differences differences, boolean showNonDifferences) + { + System.out.println("Differences (" + differences.size() + ")"); + for (Result d : differences) + { + if (d.getWhere() != Where.IN_BOTH_NO_DIFFERENCE || showNonDifferences) + { + System.out.println(d); + } + } + } + + private Table table(String name) + { + return new Table(name, columns("id NUMBER(10)"), pk("pk_" + name, "id"), fkeys(), indexes()); + } + + private Collection columns(String... colDefs) + { + assertTrue("Tables must have columns", colDefs.length > 0); + Column[] columns = new Column[colDefs.length]; + + for (int i = 0; i < colDefs.length; i++) + { + String[] parts = colDefs[i].split(" "); + columns[i] = new Column(parts[0], parts[1], false); + } + return Arrays.asList(columns); + } + + private PrimaryKey pk(String name, String... columnNames) + { + assertTrue("No columns specified", columnNames.length > 0); + PrimaryKey pk = new PrimaryKey(); + pk.setName(name); + pk.setColumnNames(Arrays.asList(columnNames)); + return pk; + } + + private List fkeys(ForeignKey... fkeys) + { + return Arrays.asList(fkeys); + } + + private ForeignKey fk(String fkName, String localColumn, String targetTable, String targetColumn) + { + return new ForeignKey(fkName, localColumn, targetTable, targetColumn); + } + + private Collection indexes(String... indexDefs) + { + Index[] indexes = new Index[indexDefs.length]; + for (int i = 0; i < indexDefs.length; i++) + { + String[] parts = indexDefs[i].split(" "); + String name = parts[0]; + String[] columns = (String[]) ArrayUtils.subarray(parts, 1, parts.length); + indexes[i] = new Index(name, Arrays.asList(columns)); + } + return Arrays.asList(indexes); + } } diff --git a/source/java/org/alfresco/util/schemacomp/SchemaUtils.java b/source/java/org/alfresco/util/schemacomp/SchemaUtils.java new file mode 100644 index 0000000000..146e523d7c --- /dev/null +++ b/source/java/org/alfresco/util/schemacomp/SchemaUtils.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.util.schemacomp; + +import java.util.Collection; + +import org.alfresco.util.schemacomp.Result.Where; +import org.alfresco.util.schemacomp.model.DbObject; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.Predicate; + +/** + * TODO: comment me! + * + * @author Matt Ward + */ +public abstract class SchemaUtils +{ + /** + * @param objToFind + * @return + */ + public static DbObject findSameObjectAs(Collection objects, + final DbObject objToFind) + { + return (DbObject) CollectionUtils.find(objects, new Predicate() + { + @Override + public boolean evaluate(Object o) + { + DbObject object = (DbObject) o; + return object.sameAs(objToFind); + } + }); + } + + public static void compareSimpleCollections(Collection leftCollection, + Collection rightCollection, Differences differences) + { + for (Object leftObj : leftCollection) + { + if (rightCollection.contains(leftObj)) + { + // The same valued object in the right hand collection as in the left. + differences.add(Where.IN_BOTH_NO_DIFFERENCE, leftObj, leftObj); + } + else + { + // No equivalent object in the right hand collection. + differences.add(Where.ONLY_IN_LEFT, leftObj, null); + } + } + + // Identify objects in the right collection but not the left + for (Object rightObj : rightCollection) + { + if (!leftCollection.contains(rightObj)) + { + // No equivalent object in the left hand collection. + differences.add(Where.ONLY_IN_RIGHT, null, rightObj); + } + } + } + + public static void compareCollections(Collection leftCollection, + Collection rightCollection, Differences differences) + { + for (DbObject leftObj : leftCollection) + { + DbObject rightObj = SchemaUtils.findSameObjectAs(rightCollection, leftObj); + + if (rightObj != null) + { + // There is an equivalent object in the right hand collection as + // in the left. + leftObj.diff(rightObj, differences); + } + else + { + // No equivalent object in the right hand collection. + differences.add(Where.ONLY_IN_LEFT, leftObj, null); + } + } + + // Identify objects in the right collection but not the left + for (DbObject rightObj : rightCollection) + { + if (!leftCollection.contains(rightObj)) + { + // No equivalent object in the left hand collection. + differences.add(Where.ONLY_IN_RIGHT, null, rightObj); + } + } + } + + public static void compareSimple(Object left, Object right, Differences differences) + { + if (left == null && right == null) + { + differences.add(Where.IN_BOTH_NO_DIFFERENCE, null, null); + } + else if (left != null && left.equals(right)) + { + differences.add(Where.IN_BOTH_NO_DIFFERENCE, left, right); + } + else if (left == null && right != null) + { + differences.add(Where.ONLY_IN_RIGHT, null, right); + } + else if (left != null && right == null) + { + differences.add(Where.ONLY_IN_LEFT, left, null); + } + } +} diff --git a/source/java/org/alfresco/util/schemacomp/model/AbstractDbObject.java b/source/java/org/alfresco/util/schemacomp/model/AbstractDbObject.java index ff66f88637..178e8eb020 100644 --- a/source/java/org/alfresco/util/schemacomp/model/AbstractDbObject.java +++ b/source/java/org/alfresco/util/schemacomp/model/AbstractDbObject.java @@ -18,8 +18,13 @@ */ package org.alfresco.util.schemacomp.model; +import org.alfresco.util.schemacomp.Differences; +import org.alfresco.util.schemacomp.SchemaUtils; +import org.springframework.util.StringUtils; + /** - * TODO: comment me! + * Useful base class for many, if not all the {@link DbObject} implementations. + * * @author Matt Ward */ public abstract class AbstractDbObject implements DbObject @@ -60,9 +65,18 @@ public abstract class AbstractDbObject implements DbObject } @Override - public Object getIdentifier() + public boolean sameAs(DbObject other) { - return getName(); + if (getName() != null && other != null && other.getName() != null) + { + return getName().equals(other.getName()); + } + else + { + // Only other way we can know if they are the same is if they are + // the exact same object reference. + return this == other; + } } @Override @@ -88,4 +102,60 @@ public abstract class AbstractDbObject implements DbObject else if (!this.name.equals(other.name)) return false; return true; } + + @Override + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append("[name="); + + if (getName() != null) + { + sb.append(getName()); + } + else + { + sb.append("null"); + } + + sb.append("]"); + + return sb.toString(); + } + + /** + * Provides an implementation of {@link DbObject#diff(DbObject, Differences)}. The template + * method {@link #doDiff(DbObject, Differences)} provides the subclass specific diffing logic, + * whilst this method handles the workflow required in most cases: set the path's prefix that will be + * used to explain where differences occur; compare the name fields of the two objects; delegate to the + * subclass specific diffing (if any); remove the last path addition ready for the next object to perform + * its diff correctly. + */ + @Override + public void diff(DbObject right, Differences differences) + { + if (name != null && StringUtils.hasText(name)) + { + differences.pushPath(name); + } + else + { + differences.pushPath("<" + getClass().getSimpleName() + ">"); + } + SchemaUtils.compareSimple(name, right.getName(), differences); + doDiff(right, differences); + differences.popPath(); + } + + + /** + * Override this method to provide subclass specific diffing logic. + * + * @param right + * @param differences + */ + protected void doDiff(DbObject right, Differences differences) + { + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/Column.java b/source/java/org/alfresco/util/schemacomp/model/Column.java index f7405d86ba..bd3891125d 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Column.java +++ b/source/java/org/alfresco/util/schemacomp/model/Column.java @@ -18,8 +18,12 @@ */ package org.alfresco.util.schemacomp.model; +import org.alfresco.util.schemacomp.Differences; +import org.alfresco.util.schemacomp.SchemaUtils; + /** - * TODO: comment me! + * Represents a column in a database table. + * * @author Matt Ward */ public class Column extends AbstractDbObject @@ -27,6 +31,21 @@ public class Column extends AbstractDbObject private String type; private boolean nullable; + + /** + * Construct a Column. + * + * @param name + * @param type + * @param nullable + */ + public Column(String name, String type, boolean nullable) + { + super(name); + this.type = type; + this.nullable = nullable; + } + /** * @return the type */ @@ -84,4 +103,12 @@ public class Column extends AbstractDbObject else if (!this.type.equals(other.type)) return false; return true; } + + @Override + protected void doDiff(DbObject right, Differences differences) + { + Column rightColumn = (Column) right; + SchemaUtils.compareSimple(type, rightColumn.type, differences); + SchemaUtils.compareSimple(nullable, rightColumn.nullable, differences); + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/DbObject.java b/source/java/org/alfresco/util/schemacomp/model/DbObject.java index 3ea5c681a0..af002fe3ae 100644 --- a/source/java/org/alfresco/util/schemacomp/model/DbObject.java +++ b/source/java/org/alfresco/util/schemacomp/model/DbObject.java @@ -18,12 +18,39 @@ */ package org.alfresco.util.schemacomp.model; +import org.alfresco.util.schemacomp.Differences; + /** - * All database objects to be modelled for schema comparisons must implement this interface. + * All database objects to be modelled for schema comparisons must implement this interface, examples + * of which include: schemas, tables, indexes, primary keys, foreign keys, sequences and columns. * * @author Matt Ward */ public interface DbObject { - Object getIdentifier(); + /** + * Are the two DbObjects logically the same? For example two Index objects may have + * different names, but are the same index as they both index the same columns for the same table. + * + * @param other + * @return + */ + boolean sameAs(DbObject other); + + /** + * All items can be asked for their name, but it may be null. + * + * @return Name if available, null otherwise. + */ + String getName(); + + /** + * Generate a report of differences between this object ('left') and another object ('right'). + * Differences between the left and right objects under inspection are captured in the {@link Differences} + * object passed in to this method. + * + * @param right The object to compare against. + * @param differences A collector of differences. + */ + void diff(DbObject right, Differences differences); } diff --git a/source/java/org/alfresco/util/schemacomp/model/ForeignKey.java b/source/java/org/alfresco/util/schemacomp/model/ForeignKey.java index 3b753ef2c2..5dddb6dddf 100644 --- a/source/java/org/alfresco/util/schemacomp/model/ForeignKey.java +++ b/source/java/org/alfresco/util/schemacomp/model/ForeignKey.java @@ -18,9 +18,14 @@ */ package org.alfresco.util.schemacomp.model; +import org.alfresco.util.schemacomp.Differences; +import org.alfresco.util.schemacomp.SchemaUtils; + /** - * TODO: comment me! + * Represents a foreign key on a database table (localColumn) that references + * targetTable.targetColumn + * * @author Matt Ward */ public class ForeignKey extends AbstractDbObject @@ -30,6 +35,22 @@ public class ForeignKey extends AbstractDbObject private String targetColumn; + /** + * Constructor. + * + * @param fkName + * @param localColumn + * @param targetTable + * @param targetColumn + */ + public ForeignKey(String fkName, String localColumn, String targetTable, String targetColumn) + { + super(fkName); + this.localColumn = localColumn; + this.targetTable = targetTable; + this.targetColumn = targetColumn; + } + /** * @return the localColumn */ @@ -37,6 +58,7 @@ public class ForeignKey extends AbstractDbObject { return this.localColumn; } + /** * @param localColumn the localColumn to set */ @@ -44,6 +66,7 @@ public class ForeignKey extends AbstractDbObject { this.localColumn = localColumn; } + /** * @return the targetTable */ @@ -51,6 +74,7 @@ public class ForeignKey extends AbstractDbObject { return this.targetTable; } + /** * @param targetTable the targetTable to set */ @@ -58,6 +82,7 @@ public class ForeignKey extends AbstractDbObject { this.targetTable = targetTable; } + /** * @return the targetColumn */ @@ -65,6 +90,7 @@ public class ForeignKey extends AbstractDbObject { return this.targetColumn; } + /** * @param targetColumn the targetColumn to set */ @@ -107,5 +133,15 @@ public class ForeignKey extends AbstractDbObject } else if (!this.targetTable.equals(other.targetTable)) return false; return true; + } + + + @Override + protected void doDiff(DbObject right, Differences differences) + { + ForeignKey rightFK = (ForeignKey) right; + SchemaUtils.compareSimple(localColumn, rightFK.localColumn, differences); + SchemaUtils.compareSimple(targetTable, rightFK.targetTable, differences); + SchemaUtils.compareSimple(targetColumn, rightFK.targetColumn, differences); } } diff --git a/source/java/org/alfresco/util/schemacomp/model/Index.java b/source/java/org/alfresco/util/schemacomp/model/Index.java index f6f9387cfc..5e7a0f774b 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Index.java +++ b/source/java/org/alfresco/util/schemacomp/model/Index.java @@ -20,8 +20,12 @@ package org.alfresco.util.schemacomp.model; import java.util.List; +import org.alfresco.util.schemacomp.Differences; +import org.alfresco.util.schemacomp.SchemaUtils; + /** - * TODO: comment me! + * Represents an index on a table. + * * @author Matt Ward */ public class Index extends AbstractDbObject @@ -29,6 +33,15 @@ public class Index extends AbstractDbObject private List columnNames; + /** + * @param columnNames + */ + public Index(String name, List columnNames) + { + super(name); + this.columnNames = columnNames; + } + /** * @return the columnNames */ @@ -45,18 +58,6 @@ public class Index extends AbstractDbObject this.columnNames = columnNames; } - /** - * TODO: column names should be fully qualified, OR an Index should have a table name field - * and the identifier should include it. - * - * @return the Index identifier - */ - @Override - public Object getIdentifier() - { - return getColumnNames(); - } - @Override public int hashCode() { @@ -80,4 +81,34 @@ public class Index extends AbstractDbObject else if (!this.columnNames.equals(other.columnNames)) return false; return true; } + + @Override + public boolean sameAs(DbObject o) + { + if (o != null && o instanceof Index) + { + Index other = (Index) o; + + if (getName() != null) + { + if (getName().equals(other.getName())) + { + return true; + } + else + { + return columnNames.equals(other.getColumnNames()); + } + } + } + + return false; + } + + @Override + protected void doDiff(DbObject right, Differences differences) + { + Index rightIndex = (Index) right; + SchemaUtils.compareSimpleCollections(columnNames, rightIndex.columnNames, differences); + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/PrimaryKey.java b/source/java/org/alfresco/util/schemacomp/model/PrimaryKey.java index 6c92480eb3..c740d5cf98 100644 --- a/source/java/org/alfresco/util/schemacomp/model/PrimaryKey.java +++ b/source/java/org/alfresco/util/schemacomp/model/PrimaryKey.java @@ -20,8 +20,12 @@ package org.alfresco.util.schemacomp.model; import java.util.List; +import org.alfresco.util.schemacomp.Differences; +import org.alfresco.util.schemacomp.SchemaUtils; + /** - * TODO: comment me! + * Primary key on a table. + * * @author Matt Ward */ public class PrimaryKey extends AbstractDbObject @@ -68,4 +72,11 @@ public class PrimaryKey extends AbstractDbObject else if (!this.columnNames.equals(other.columnNames)) return false; return true; } + + @Override + protected void doDiff(DbObject right, Differences differences) + { + PrimaryKey rightPK = (PrimaryKey) right; + SchemaUtils.compareSimpleCollections(columnNames, rightPK.columnNames, differences); + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/Schema.java b/source/java/org/alfresco/util/schemacomp/model/Schema.java index 7b88058a23..89f65cbd5f 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Schema.java +++ b/source/java/org/alfresco/util/schemacomp/model/Schema.java @@ -18,50 +18,50 @@ */ package org.alfresco.util.schemacomp.model; +import java.util.ArrayList; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.List; + +import org.alfresco.util.schemacomp.Differences; +import org.alfresco.util.schemacomp.SchemaUtils; /** - * Instances of this class will represent a database schema. + * Instances of this class represent a database schema. * * @author Matt Ward */ -public class -Schema extends AbstractDbObject implements Iterable +public class Schema extends AbstractDbObject implements Iterable { - private final Map objects = new LinkedHashMap(); + private final List objects = new ArrayList(); /** - * @param key - * @return + * Construct a schema with the given name. + * + * @param name */ - public DbObject get(Object key) + public Schema(String name) { - return this.objects.get(key); + super(name); } - /** - * @param table - */ - public void put(DbObject dbObject) + public void add(DbObject dbObject) { - objects.put(dbObject.getIdentifier(), dbObject); + objects.add(dbObject); } - + @Override public Iterator iterator() { - return objects.values().iterator(); + return objects.iterator(); } /** * @param identifier * @return */ - public boolean contains(Object identifier) + public boolean contains(DbObject object) { - return objects.containsKey(identifier); + return objects.contains(object); } @Override @@ -87,4 +87,12 @@ Schema extends AbstractDbObject implements Iterable else if (!this.objects.equals(other.objects)) return false; return true; } + + + @Override + protected void doDiff(DbObject right, Differences differences) + { + Schema rightSchema = (Schema) right; + SchemaUtils.compareCollections(objects, rightSchema.objects, differences); + } } diff --git a/source/java/org/alfresco/util/schemacomp/model/Sequence.java b/source/java/org/alfresco/util/schemacomp/model/Sequence.java index ca082ce546..c6c0a61b3a 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Sequence.java +++ b/source/java/org/alfresco/util/schemacomp/model/Sequence.java @@ -18,10 +18,13 @@ */ package org.alfresco.util.schemacomp.model; + /** - * TODO: comment me! + * Represents a database sequence. + * * @author Matt Ward */ public class Sequence extends AbstractDbObject { + // No subclass specific data/behaviour at present. } diff --git a/source/java/org/alfresco/util/schemacomp/model/Table.java b/source/java/org/alfresco/util/schemacomp/model/Table.java index d7fdaf1c00..e79fc36614 100644 --- a/source/java/org/alfresco/util/schemacomp/model/Table.java +++ b/source/java/org/alfresco/util/schemacomp/model/Table.java @@ -22,8 +22,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import org.alfresco.util.schemacomp.Differences; +import org.alfresco.util.schemacomp.SchemaUtils; + /** - * Instances of this class will represent a database table. + * Instances of this class represent a database table. * * @author Matt Ward */ @@ -31,23 +34,26 @@ public class Table extends AbstractDbObject { private List columns = new ArrayList(); private PrimaryKey primaryKey; - private ForeignKey foreignKey; + private List foreignKeys = new ArrayList(); private List indexes = new ArrayList(); public Table(String name, Collection columns, PrimaryKey primaryKey, - ForeignKey foreignKey, Collection indexes) + Collection foreignKeys, Collection indexes) { super(name); if (columns != null) { - columns.addAll(columns); + this.columns.addAll(columns); } this.primaryKey = primaryKey; - this.foreignKey = foreignKey; + if (foreignKeys != null) + { + this.foreignKeys.addAll(foreignKeys); + } if (indexes != null) { - indexes.addAll(indexes); + this.indexes.addAll(indexes); } } @@ -89,20 +95,20 @@ public class Table extends AbstractDbObject /** - * @return the foreignKey + * @return the foreignKeys */ - public ForeignKey getForeignKey() + public List getForeignKeys() { - return this.foreignKey; + return this.foreignKeys; } /** - * @param foreignKey the foreignKey to set + * @param foreignKeys the foreignKeys to set */ - public void setForeignKey(ForeignKey foreignKey) + public void setForeignKeys(List foreignKeys) { - this.foreignKey = foreignKey; + this.foreignKeys = foreignKeys; } @@ -130,7 +136,7 @@ public class Table extends AbstractDbObject final int prime = 31; int result = super.hashCode(); result = prime * result + ((this.columns == null) ? 0 : this.columns.hashCode()); - result = prime * result + ((this.foreignKey == null) ? 0 : this.foreignKey.hashCode()); + result = prime * result + ((this.foreignKeys == null) ? 0 : this.foreignKeys.hashCode()); result = prime * result + ((this.indexes == null) ? 0 : this.indexes.hashCode()); result = prime * result + ((this.primaryKey == null) ? 0 : this.primaryKey.hashCode()); return result; @@ -149,21 +155,34 @@ public class Table extends AbstractDbObject if (other.columns != null) return false; } else if (!this.columns.equals(other.columns)) return false; - if (this.foreignKey == null) + if (this.foreignKeys == null) { - if (other.foreignKey != null) return false; + if (other.foreignKeys != null) return false; } - else if (!this.foreignKey.equals(other.foreignKey)) return false; + else if (!this.foreignKeys.equals(other.foreignKeys)) return false; if (this.indexes == null) { if (other.indexes != null) return false; } else if (!this.indexes.equals(other.indexes)) return false; - if (this.primaryKey == null) - { - if (other.primaryKey != null) return false; - } - else if (!this.primaryKey.equals(other.primaryKey)) return false; + // TODO: this is difficult, equals probably should include this, but diffs shouldn't - + // decide what to do about this. +// if (this.primaryKey == null) +// { +// if (other.primaryKey != null) return false; +// } +// else if (!this.primaryKey.equals(other.primaryKey)) return false; return true; - } + } + + + @Override + protected void doDiff(DbObject other, Differences differences) + { + Table rightTable = (Table) other; + SchemaUtils.compareCollections(columns, rightTable.columns, differences); + primaryKey.diff(rightTable.primaryKey, differences); + SchemaUtils.compareCollections(foreignKeys, rightTable.foreignKeys, differences); + SchemaUtils.compareCollections(indexes, rightTable.indexes, differences); + } }