CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-liquibase--liquibase-core

Liquibase is a tool for managing and executing database changes.

Pending
Overview
Eval results
Files

diff-comparison.mddocs/

Diff and Comparison

This document covers Liquibase's schema comparison and diff generation tools for analyzing database differences and generating change scripts.

Imports

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;

DiffResult Class

The DiffResult contains the results of comparing two database schemas.

Result Access Methods

/**
 * 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()

Filtered Result Access

/**
 * 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)

CompareControl Class

The CompareControl class controls what gets compared during diff operations.

Constructor

/**
 * 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)

Access Methods

/**
 * 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()

SchemaComparison Class

Defines mapping between reference and target schemas for comparison.

Constructor and Methods

/**
 * 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()

DiffGeneratorFactory Class

Factory for creating and executing diff operations.

Core Diff Method

/**
 * 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

Factory Access

/**
 * Get singleton DiffGeneratorFactory instance
 * @return DiffGeneratorFactory instance
 */
public static DiffGeneratorFactory getInstance()

Database Object Types

Core Object Types for Comparison

// 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)

Example Usage

Basic Database Comparison

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());

Analyzing Diff Results

// 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());
}

Column-Level Comparison

// 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"));
    }
}

Constraint Comparison

// 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());
}

Multi-Schema Comparison

// 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());

Data Comparison

// 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());

Filtered Object Comparison

// 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());

Complete Diff Analysis

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

docs

changelog.md

command-framework.md

configuration.md

database-operations.md

database-support.md

diff-comparison.md

exceptions.md

index.md

tile.json