Liquibase is a tool for managing and executing database changes.
—
This document covers Liquibase's schema comparison and diff generation tools for analyzing database differences and generating change scripts.
import liquibase.diff.DiffResult;
import liquibase.diff.DiffGeneratorFactory;
import liquibase.diff.compare.CompareControl;
import liquibase.diff.compare.SchemaComparison;
import liquibase.diff.ObjectDifferences;
// Database structure objects
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.*;
// Database connections
import liquibase.database.Database;
import liquibase.CatalogAndSchema;
// Exception handling
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import java.util.Set;
import java.util.Map;The DiffResult contains the results of comparing two database schemas.
/**
* Get objects that exist in reference database but not in target
* @return Set of missing database objects
*/
public Set<DatabaseObject> getMissingObjects()
/**
* Get objects that exist in target database but not in reference
* @return Set of unexpected database objects
*/
public Set<DatabaseObject> getUnexpectedObjects()
/**
* Get objects that exist in both databases but have differences
* @return Map of changed objects to their differences
*/
public Map<DatabaseObject, ObjectDifferences> getChangedObjects()
/**
* Check if the databases are identical
* @return true if no differences found
*/
public boolean areEqual()/**
* Get missing objects of specific type
* @param type Database object type class
* @return Set of missing objects of specified type
*/
public <T extends DatabaseObject> Set<T> getMissingObjects(Class<T> type)
/**
* Get unexpected objects of specific type
* @param type Database object type class
* @return Set of unexpected objects of specified type
*/
public <T extends DatabaseObject> Set<T> getUnexpectedObjects(Class<T> type)
/**
* Get changed objects of specific type
* @param type Database object type class
* @return Map of changed objects to differences for specified type
*/
public <T extends DatabaseObject> Map<T, ObjectDifferences> getChangedObjects(Class<T> type)The CompareControl class controls what gets compared during diff operations.
/**
* Create comparison control with schema mappings and object types
* @param schemaComparisons Array of schema comparison mappings
* @param compareTypes Set of database object types to compare
*/
public CompareControl(SchemaComparison[] schemaComparisons,
Set<Class<? extends DatabaseObject>> compareTypes)/**
* Get the database object types being compared
* @return Set of DatabaseObject classes
*/
public Set<Class<? extends DatabaseObject>> getComparedTypes()
/**
* Get schema comparison mappings
* @return Array of SchemaComparison objects
*/
public SchemaComparison[] getSchemaComparisons()Defines mapping between reference and target schemas for comparison.
/**
* Create schema comparison mapping
* @param referenceSchema Reference database schema
* @param comparisonSchema Target database schema for comparison
*/
public SchemaComparison(CatalogAndSchema referenceSchema, CatalogAndSchema comparisonSchema)
/**
* Get reference schema
* @return Reference CatalogAndSchema
*/
public CatalogAndSchema getReferenceSchema()
/**
* Get comparison schema
* @return Comparison CatalogAndSchema
*/
public CatalogAndSchema getComparisonSchema()Factory for creating and executing diff operations.
/**
* Compare two databases
* @param referenceDatabase Reference database (source of truth)
* @param targetDatabase Target database to compare against reference
* @param compareControl Controls what gets compared
* @return DiffResult containing comparison results
* @throws DatabaseException if comparison fails
*/
public DiffResult compare(Database referenceDatabase,
Database targetDatabase,
CompareControl compareControl) throws DatabaseException/**
* Get singleton DiffGeneratorFactory instance
* @return DiffGeneratorFactory instance
*/
public static DiffGeneratorFactory getInstance()// Schema structure objects
Catalog.class // Database catalogs
Schema.class // Database schemas
// Table objects
Table.class // Database tables
View.class // Database views
Column.class // Table columns
// Constraint objects
PrimaryKey.class // Primary key constraints
ForeignKey.class // Foreign key constraints
UniqueConstraint.class // Unique constraints
Index.class // Database indexes
// Other objects
Sequence.class // Database sequences
StoredProcedure.class // Stored procedures
Data.class // Table data (when enabled)import liquibase.diff.DiffGeneratorFactory;
import liquibase.diff.DiffResult;
import liquibase.diff.compare.CompareControl;
import liquibase.diff.compare.SchemaComparison;
import liquibase.database.DatabaseFactory;
import liquibase.CatalogAndSchema;
// Connect to reference database (production)
Database referenceDatabase = DatabaseFactory.getInstance().openDatabase(
"jdbc:postgresql://prod-server:5432/myapp",
"readonly_user",
"password",
null, null, null
);
// Connect to target database (development)
Database targetDatabase = DatabaseFactory.getInstance().openDatabase(
"jdbc:postgresql://dev-server:5432/myapp",
"dev_user",
"password",
null, null, null
);
// Set up schema comparison
SchemaComparison[] schemaComparisons = new SchemaComparison[] {
new SchemaComparison(
new CatalogAndSchema("myapp", "public"), // reference schema
new CatalogAndSchema("myapp", "public") // target schema
)
};
// Define what to compare
Set<Class<? extends DatabaseObject>> compareTypes = new HashSet<>(Arrays.asList(
Table.class,
Column.class,
PrimaryKey.class,
ForeignKey.class,
Index.class,
View.class
));
CompareControl compareControl = new CompareControl(schemaComparisons, compareTypes);
// Execute comparison
DiffGeneratorFactory diffFactory = DiffGeneratorFactory.getInstance();
DiffResult diffResult = diffFactory.compare(referenceDatabase, targetDatabase, compareControl);
// Analyze results
System.out.println("Databases are equal: " + diffResult.areEqual());// Check for missing tables (in reference but not target)
Set<Table> missingTables = diffResult.getMissingObjects(Table.class);
System.out.println("Missing tables: " + missingTables.size());
for (Table table : missingTables) {
System.out.println(" - " + table.getName());
}
// Check for unexpected tables (in target but not reference)
Set<Table> unexpectedTables = diffResult.getUnexpectedObjects(Table.class);
System.out.println("Unexpected tables: " + unexpectedTables.size());
for (Table table : unexpectedTables) {
System.out.println(" - " + table.getName());
}
// Check for changed tables
Map<Table, ObjectDifferences> changedTables = diffResult.getChangedObjects(Table.class);
System.out.println("Changed tables: " + changedTables.size());
for (Map.Entry<Table, ObjectDifferences> entry : changedTables.entrySet()) {
Table table = entry.getKey();
ObjectDifferences differences = entry.getValue();
System.out.println(" - " + table.getName() + ": " + differences.hasDifferences());
}// Analyze column differences
Set<Column> missingColumns = diffResult.getMissingObjects(Column.class);
for (Column column : missingColumns) {
System.out.println("Missing column: " +
column.getRelation().getName() + "." + column.getName());
}
Set<Column> unexpectedColumns = diffResult.getUnexpectedObjects(Column.class);
for (Column column : unexpectedColumns) {
System.out.println("Unexpected column: " +
column.getRelation().getName() + "." + column.getName());
}
Map<Column, ObjectDifferences> changedColumns = diffResult.getChangedObjects(Column.class);
for (Map.Entry<Column, ObjectDifferences> entry : changedColumns.entrySet()) {
Column column = entry.getKey();
ObjectDifferences differences = entry.getValue();
System.out.println("Changed column: " +
column.getRelation().getName() + "." + column.getName());
// Examine specific differences
if (differences.isDifferent("type")) {
System.out.println(" - Type changed from " +
differences.getReferenceValue("type") + " to " +
differences.getComparedValue("type"));
}
if (differences.isDifferent("nullable")) {
System.out.println(" - Nullable changed from " +
differences.getReferenceValue("nullable") + " to " +
differences.getComparedValue("nullable"));
}
}// Primary key differences
Set<PrimaryKey> missingPks = diffResult.getMissingObjects(PrimaryKey.class);
Set<PrimaryKey> unexpectedPks = diffResult.getUnexpectedObjects(PrimaryKey.class);
Map<PrimaryKey, ObjectDifferences> changedPks = diffResult.getChangedObjects(PrimaryKey.class);
System.out.println("Primary key differences:");
System.out.println(" Missing: " + missingPks.size());
System.out.println(" Unexpected: " + unexpectedPks.size());
System.out.println(" Changed: " + changedPks.size());
// Foreign key differences
Set<ForeignKey> missingFks = diffResult.getMissingObjects(ForeignKey.class);
Set<ForeignKey> unexpectedFks = diffResult.getUnexpectedObjects(ForeignKey.class);
for (ForeignKey fk : missingFks) {
System.out.println("Missing FK: " + fk.getName() +
" (" + fk.getForeignKeyTable().getName() + " -> " +
fk.getPrimaryKeyTable().getName() + ")");
}
// Index differences
Set<Index> missingIndexes = diffResult.getMissingObjects(Index.class);
Set<Index> unexpectedIndexes = diffResult.getUnexpectedObjects(Index.class);
for (Index index : missingIndexes) {
System.out.println("Missing index: " + index.getName() +
" on " + index.getRelation().getName());
}// Compare multiple schemas
SchemaComparison[] multiSchemaComparisons = new SchemaComparison[] {
new SchemaComparison(
new CatalogAndSchema("prod_db", "public"),
new CatalogAndSchema("dev_db", "public")
),
new SchemaComparison(
new CatalogAndSchema("prod_db", "reporting"),
new CatalogAndSchema("dev_db", "reporting")
),
new SchemaComparison(
new CatalogAndSchema("prod_db", "audit"),
new CatalogAndSchema("dev_db", "audit")
)
};
CompareControl multiSchemaControl = new CompareControl(multiSchemaComparisons, compareTypes);
DiffResult multiSchemaDiff = diffFactory.compare(referenceDatabase, targetDatabase, multiSchemaControl);
// Results will include differences across all specified schemas
System.out.println("Multi-schema comparison complete");
System.out.println("Total missing objects: " + multiSchemaDiff.getMissingObjects().size());
System.out.println("Total unexpected objects: " + multiSchemaDiff.getUnexpectedObjects().size());// Include data in comparison (use sparingly - can be expensive)
Set<Class<? extends DatabaseObject>> dataCompareTypes = new HashSet<>(Arrays.asList(
Table.class,
Column.class,
Data.class // Include table data
));
CompareControl dataCompareControl = new CompareControl(schemaComparisons, dataCompareTypes);
DiffResult dataResult = diffFactory.compare(referenceDatabase, targetDatabase, dataCompareControl);
// Analyze data differences
Set<Data> missingData = dataResult.getMissingObjects(Data.class);
Set<Data> unexpectedData = dataResult.getUnexpectedObjects(Data.class);
System.out.println("Data row differences:");
System.out.println(" Missing rows: " + missingData.size());
System.out.println(" Unexpected rows: " + unexpectedData.size());// Compare only specific object types
Set<Class<? extends DatabaseObject>> tableOnlyTypes = new HashSet<>(Arrays.asList(
Table.class,
Column.class
));
CompareControl tableOnlyControl = new CompareControl(schemaComparisons, tableOnlyTypes);
DiffResult tableOnlyResult = diffFactory.compare(referenceDatabase, targetDatabase, tableOnlyControl);
// Only table and column differences will be included
System.out.println("Table-only comparison:");
System.out.println("Tables equal: " + tableOnlyResult.areEqual());
// Compare only indexes
Set<Class<? extends DatabaseObject>> indexOnlyTypes = new HashSet<>(Arrays.asList(
Index.class
));
CompareControl indexOnlyControl = new CompareControl(schemaComparisons, indexOnlyTypes);
DiffResult indexOnlyResult = diffFactory.compare(referenceDatabase, targetDatabase, indexOnlyControl);
System.out.println("Index-only comparison:");
System.out.println("Missing indexes: " + indexOnlyResult.getMissingObjects(Index.class).size());public class DatabaseDiffAnalyzer {
public void analyzeDifferences(DiffResult diffResult) {
System.out.println("=== Database Comparison Report ===");
System.out.println("Databases are equal: " + diffResult.areEqual());
System.out.println();
analyzeStructuralDifferences(diffResult);
analyzeConstraintDifferences(diffResult);
analyzeIndexDifferences(diffResult);
analyzeViewDifferences(diffResult);
}
private void analyzeStructuralDifferences(DiffResult diffResult) {
System.out.println("--- Structural Differences ---");
// Schema differences
reportDifferences(diffResult, Schema.class, "Schemas");
// Table differences
reportDifferences(diffResult, Table.class, "Tables");
// Column differences
reportDifferences(diffResult, Column.class, "Columns");
}
private void analyzeConstraintDifferences(DiffResult diffResult) {
System.out.println("--- Constraint Differences ---");
reportDifferences(diffResult, PrimaryKey.class, "Primary Keys");
reportDifferences(diffResult, ForeignKey.class, "Foreign Keys");
reportDifferences(diffResult, UniqueConstraint.class, "Unique Constraints");
}
private void analyzeIndexDifferences(DiffResult diffResult) {
System.out.println("--- Index Differences ---");
reportDifferences(diffResult, Index.class, "Indexes");
}
private void analyzeViewDifferences(DiffResult diffResult) {
System.out.println("--- View Differences ---");
reportDifferences(diffResult, View.class, "Views");
}
private <T extends DatabaseObject> void reportDifferences(
DiffResult diffResult, Class<T> type, String typeName) {
Set<T> missing = diffResult.getMissingObjects(type);
Set<T> unexpected = diffResult.getUnexpectedObjects(type);
Map<T, ObjectDifferences> changed = diffResult.getChangedObjects(type);
if (missing.isEmpty() && unexpected.isEmpty() && changed.isEmpty()) {
System.out.println(typeName + ": No differences");
return;
}
System.out.println(typeName + ":");
System.out.println(" Missing: " + missing.size());
System.out.println(" Unexpected: " + unexpected.size());
System.out.println(" Changed: " + changed.size());
// Report details for missing objects
for (T obj : missing) {
System.out.println(" Missing: " + getObjectDescription(obj));
}
// Report details for unexpected objects
for (T obj : unexpected) {
System.out.println(" Unexpected: " + getObjectDescription(obj));
}
// Report details for changed objects
for (Map.Entry<T, ObjectDifferences> entry : changed.entrySet()) {
System.out.println(" Changed: " + getObjectDescription(entry.getKey()));
}
System.out.println();
}
private String getObjectDescription(DatabaseObject obj) {
if (obj instanceof Table) {
Table table = (Table) obj;
return table.getName();
} else if (obj instanceof Column) {
Column column = (Column) obj;
return column.getRelation().getName() + "." + column.getName();
} else if (obj instanceof Index) {
Index index = (Index) obj;
return index.getName() + " on " + index.getRelation().getName();
}
// Add more object types as needed
return obj.toString();
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-liquibase--liquibase-core