package liquibase.diff.core; import liquibase.CatalogAndSchema; import liquibase.database.Database; import liquibase.diff.*; import liquibase.diff.compare.CompareControl; import liquibase.exception.DatabaseException; import liquibase.exception.UnexpectedLiquibaseException; import liquibase.snapshot.DatabaseSnapshot; import liquibase.snapshot.EmptyDatabaseSnapshot; import liquibase.snapshot.InvalidExampleException; import liquibase.snapshot.JdbcDatabaseSnapshot; import liquibase.structure.DatabaseObject; import liquibase.diff.compare.DatabaseObjectComparatorFactory; import liquibase.structure.core.Catalog; import liquibase.structure.core.Schema; import liquibase.util.StringUtils; import java.util.Set; public class StandardDiffGenerator implements DiffGenerator { @Override public int getPriority() { return PRIORITY_DEFAULT; } @Override public boolean supports(Database referenceDatabase, Database comparisonDatabase) { return true; } @Override public DiffResult compare(DatabaseSnapshot referenceSnapshot, DatabaseSnapshot comparisonSnapshot, CompareControl compareControl) throws DatabaseException { if (comparisonSnapshot == null) { try { comparisonSnapshot = new EmptyDatabaseSnapshot(referenceSnapshot.getDatabase()); //, compareControl.toSnapshotControl(CompareControl.DatabaseRole.REFERENCE)); } catch (InvalidExampleException e) { throw new UnexpectedLiquibaseException(e); } } DiffResult diffResult = new DiffResult(referenceSnapshot, comparisonSnapshot, compareControl); checkVersionInfo(referenceSnapshot, comparisonSnapshot, diffResult); Set<Class<? extends DatabaseObject>> typesToCompare = compareControl.getComparedTypes(); typesToCompare.retainAll(referenceSnapshot.getSnapshotControl().getTypesToInclude()); typesToCompare.retainAll(comparisonSnapshot.getSnapshotControl().getTypesToInclude()); for (Class<? extends DatabaseObject> typeToCompare : typesToCompare) { compareObjectType(typeToCompare, referenceSnapshot, comparisonSnapshot, diffResult); } // // Hack: Sometimes Indexes or Unique Constraints with multiple columns get added twice (1 for each column), // // so we're combining them back to a single Index or Unique Constraint here. // removeDuplicateIndexes( diffResult.getMissingIndexes() ); // removeDuplicateIndexes( diffResult.getUnexpectedIndexes() ); // removeDuplicateUniqueConstraints( diffResult.getMissingUniqueConstraints() ); // removeDuplicateUniqueConstraints( diffResult.getUnexpectedUniqueConstraints() ); return diffResult; } protected void checkVersionInfo(DatabaseSnapshot referenceSnapshot, DatabaseSnapshot comparisonSnapshot, DiffResult diffResult) throws DatabaseException { if (comparisonSnapshot != null && comparisonSnapshot.getDatabase() != null) { diffResult.setProductNameDiff(new StringDiff(referenceSnapshot.getDatabase().getDatabaseProductName(), comparisonSnapshot.getDatabase().getDatabaseProductName())); diffResult.setProductVersionDiff(new StringDiff(referenceSnapshot.getDatabase().getDatabaseProductVersion(), comparisonSnapshot.getDatabase().getDatabaseProductVersion())); } } protected <T extends DatabaseObject> void compareObjectType(Class<T> type, DatabaseSnapshot referenceSnapshot, DatabaseSnapshot comparisonSnapshot, DiffResult diffResult) { Database comparisonDatabase = comparisonSnapshot.getDatabase(); Database referenceDatabase = referenceSnapshot.getDatabase(); CompareControl.SchemaComparison[] schemaComparisons = diffResult.getCompareControl().getSchemaComparisons(); if (schemaComparisons != null) { for (CompareControl.SchemaComparison schemaComparison : schemaComparisons) { for (T referenceObject : referenceSnapshot.get(type)) { // if (referenceObject instanceof Table && referenceSnapshot.getDatabase().isLiquibaseTable(referenceSchema, referenceObject.getName())) { // continue; // } Schema referenceObjectSchema = referenceObject.getSchema(); if (referenceObjectSchema != null && referenceObjectSchema.getName() != null) { //don't filter out null-named schemas. May actually be catalog-level objects that should be included if (!StringUtils.trimToEmpty(referenceObjectSchema.toCatalogAndSchema().standardize(referenceDatabase).getSchemaName()).equalsIgnoreCase(StringUtils.trimToEmpty(schemaComparison.getReferenceSchema().standardize(referenceDatabase).getSchemaName()))) { continue; } } T comparisonObject = comparisonSnapshot.get(referenceObject); if (comparisonObject == null) { diffResult.addMissingObject(referenceObject); } else { ObjectDifferences differences = DatabaseObjectComparatorFactory.getInstance().findDifferences(referenceObject, comparisonObject, comparisonDatabase, diffResult.getCompareControl()); if (differences.hasDifferences()) { diffResult.addChangedObject(referenceObject, differences); } } } // for (T comparisonObject : comparisonSnapshot.get(type)) { // if (targetObject instanceof Table && comparisonSnapshot.getDatabase().isLiquibaseTable(comparisonSchema, targetObject.getName())) { // continue; // } Schema comparisonObjectSchema = comparisonObject.getSchema(); if (comparisonObjectSchema != null) { String comparisonObjectSchemaName = StringUtils.trimToEmpty(comparisonObjectSchema.toCatalogAndSchema().standardize(comparisonDatabase).getSchemaName()); String schemaComparisonName1 = StringUtils.trimToEmpty(schemaComparison.getComparisonSchema().standardize(comparisonDatabase).getSchemaName()); String schemaComparisonName2 = StringUtils.trimToEmpty(schemaComparison.getReferenceSchema().standardize(comparisonDatabase).getSchemaName()); if (comparisonObjectSchemaName.equals("") && !schemaComparisonName1.equals("") && !schemaComparisonName2.equals("")) { comparisonObjectSchemaName = StringUtils.trimToEmpty(comparisonObjectSchema.getName()); } if (!(comparisonObjectSchemaName.equalsIgnoreCase(schemaComparisonName1) || comparisonObjectSchemaName.equals(schemaComparisonName2))) { continue; } } if (referenceSnapshot.get(comparisonObject) == null) { diffResult.addUnexpectedObject(comparisonObject); } // } } } //todo: add logic for when container is missing or unexpected also } // /** // * Removes duplicate Indexes from the DiffResult object. // * // * @param indexes [IN/OUT] - A set of Indexes to be updated. // */ // private void removeDuplicateIndexes( SortedSet<Index> indexes ) // { // SortedSet<Index> combinedIndexes = new TreeSet<Index>(); // SortedSet<Index> indexesToRemove = new TreeSet<Index>(); // // // Find Indexes with the same name, copy their columns into the first one, // // then remove the duplicate Indexes. // for ( Index idx1 : indexes ) // { // if ( !combinedIndexes.contains( idx1 ) ) // { // for ( Index idx2 : indexes.tailSet( idx1 ) ) // { // if ( idx1 == idx2 ) { // continue; // } // // String index1Name = StringUtils.trimToEmpty(idx1.getName()); // String index2Name = StringUtils.trimToEmpty(idx2.getName()); // if ( index1Name.equalsIgnoreCase(index2Name) // && idx1.getTable().getName().equalsIgnoreCase( idx2.getTable().getName() ) ) // { // for ( String column : idx2.getColumns() ) // { // if ( !idx1.getColumns().contains( column ) ) { // idx1.getColumns().add( column ); // } // } // // indexesToRemove.add( idx2 ); // } // } // // combinedIndexes.add( idx1 ); // } // } // // indexes.removeAll( indexesToRemove ); // } // // /** // * Removes duplicate Unique Constraints from the DiffResult object. // * // * @param uniqueConstraints [IN/OUT] - A set of Unique Constraints to be updated. // */ // private void removeDuplicateUniqueConstraints( SortedSet<UniqueConstraint> uniqueConstraints ) { // SortedSet<UniqueConstraint> combinedConstraints = new TreeSet<UniqueConstraint>(); // SortedSet<UniqueConstraint> constraintsToRemove = new TreeSet<UniqueConstraint>(); // // // Find UniqueConstraints with the same name, copy their columns into the first one, // // then remove the duplicate UniqueConstraints. // for ( UniqueConstraint uc1 : uniqueConstraints ) // { // if ( !combinedConstraints.contains( uc1 ) ) // { // for ( UniqueConstraint uc2 : uniqueConstraints.tailSet( uc1 ) ) // { // if ( uc1 == uc2 ) { // continue; // } // // if ( uc1.getName().equalsIgnoreCase( uc2.getName() ) // && uc1.getTable().getName().equalsIgnoreCase( uc2.getTable().getName() ) ) // { // for ( String column : uc2.getColumns() ) // { // if ( !uc1.getColumns().contains( column ) ) { // uc1.getColumns().add( column ); // } // } // // constraintsToRemove.add( uc2 ); // } // } // // combinedConstraints.add( uc1 ); // } // } // // uniqueConstraints.removeAll( constraintsToRemove ); // } } }