0
# Diff and Comparison
1
2
This document covers Liquibase's schema comparison and diff generation tools for analyzing database differences and generating change scripts.
3
4
## Imports
5
6
```java { .api }
7
import liquibase.diff.DiffResult;
8
import liquibase.diff.DiffGeneratorFactory;
9
import liquibase.diff.compare.CompareControl;
10
import liquibase.diff.compare.SchemaComparison;
11
import liquibase.diff.ObjectDifferences;
12
13
// Database structure objects
14
import liquibase.structure.DatabaseObject;
15
import liquibase.structure.core.*;
16
17
// Database connections
18
import liquibase.database.Database;
19
import liquibase.CatalogAndSchema;
20
21
// Exception handling
22
import liquibase.exception.DatabaseException;
23
import liquibase.exception.LiquibaseException;
24
25
import java.util.Set;
26
import java.util.Map;
27
```
28
29
## DiffResult Class
30
31
The DiffResult contains the results of comparing two database schemas.
32
33
### Result Access Methods
34
35
```java { .api }
36
/**
37
* Get objects that exist in reference database but not in target
38
* @return Set of missing database objects
39
*/
40
public Set<DatabaseObject> getMissingObjects()
41
42
/**
43
* Get objects that exist in target database but not in reference
44
* @return Set of unexpected database objects
45
*/
46
public Set<DatabaseObject> getUnexpectedObjects()
47
48
/**
49
* Get objects that exist in both databases but have differences
50
* @return Map of changed objects to their differences
51
*/
52
public Map<DatabaseObject, ObjectDifferences> getChangedObjects()
53
54
/**
55
* Check if the databases are identical
56
* @return true if no differences found
57
*/
58
public boolean areEqual()
59
```
60
61
### Filtered Result Access
62
63
```java { .api }
64
/**
65
* Get missing objects of specific type
66
* @param type Database object type class
67
* @return Set of missing objects of specified type
68
*/
69
public <T extends DatabaseObject> Set<T> getMissingObjects(Class<T> type)
70
71
/**
72
* Get unexpected objects of specific type
73
* @param type Database object type class
74
* @return Set of unexpected objects of specified type
75
*/
76
public <T extends DatabaseObject> Set<T> getUnexpectedObjects(Class<T> type)
77
78
/**
79
* Get changed objects of specific type
80
* @param type Database object type class
81
* @return Map of changed objects to differences for specified type
82
*/
83
public <T extends DatabaseObject> Map<T, ObjectDifferences> getChangedObjects(Class<T> type)
84
```
85
86
## CompareControl Class
87
88
The CompareControl class controls what gets compared during diff operations.
89
90
### Constructor
91
92
```java { .api }
93
/**
94
* Create comparison control with schema mappings and object types
95
* @param schemaComparisons Array of schema comparison mappings
96
* @param compareTypes Set of database object types to compare
97
*/
98
public CompareControl(SchemaComparison[] schemaComparisons,
99
Set<Class<? extends DatabaseObject>> compareTypes)
100
```
101
102
### Access Methods
103
104
```java { .api }
105
/**
106
* Get the database object types being compared
107
* @return Set of DatabaseObject classes
108
*/
109
public Set<Class<? extends DatabaseObject>> getComparedTypes()
110
111
/**
112
* Get schema comparison mappings
113
* @return Array of SchemaComparison objects
114
*/
115
public SchemaComparison[] getSchemaComparisons()
116
```
117
118
## SchemaComparison Class
119
120
Defines mapping between reference and target schemas for comparison.
121
122
### Constructor and Methods
123
124
```java { .api }
125
/**
126
* Create schema comparison mapping
127
* @param referenceSchema Reference database schema
128
* @param comparisonSchema Target database schema for comparison
129
*/
130
public SchemaComparison(CatalogAndSchema referenceSchema, CatalogAndSchema comparisonSchema)
131
132
/**
133
* Get reference schema
134
* @return Reference CatalogAndSchema
135
*/
136
public CatalogAndSchema getReferenceSchema()
137
138
/**
139
* Get comparison schema
140
* @return Comparison CatalogAndSchema
141
*/
142
public CatalogAndSchema getComparisonSchema()
143
```
144
145
## DiffGeneratorFactory Class
146
147
Factory for creating and executing diff operations.
148
149
### Core Diff Method
150
151
```java { .api }
152
/**
153
* Compare two databases
154
* @param referenceDatabase Reference database (source of truth)
155
* @param targetDatabase Target database to compare against reference
156
* @param compareControl Controls what gets compared
157
* @return DiffResult containing comparison results
158
* @throws DatabaseException if comparison fails
159
*/
160
public DiffResult compare(Database referenceDatabase,
161
Database targetDatabase,
162
CompareControl compareControl) throws DatabaseException
163
```
164
165
### Factory Access
166
167
```java { .api }
168
/**
169
* Get singleton DiffGeneratorFactory instance
170
* @return DiffGeneratorFactory instance
171
*/
172
public static DiffGeneratorFactory getInstance()
173
```
174
175
## Database Object Types
176
177
### Core Object Types for Comparison
178
179
```java { .api }
180
// Schema structure objects
181
Catalog.class // Database catalogs
182
Schema.class // Database schemas
183
184
// Table objects
185
Table.class // Database tables
186
View.class // Database views
187
Column.class // Table columns
188
189
// Constraint objects
190
PrimaryKey.class // Primary key constraints
191
ForeignKey.class // Foreign key constraints
192
UniqueConstraint.class // Unique constraints
193
Index.class // Database indexes
194
195
// Other objects
196
Sequence.class // Database sequences
197
StoredProcedure.class // Stored procedures
198
Data.class // Table data (when enabled)
199
```
200
201
## Example Usage
202
203
### Basic Database Comparison
204
205
```java { .api }
206
import liquibase.diff.DiffGeneratorFactory;
207
import liquibase.diff.DiffResult;
208
import liquibase.diff.compare.CompareControl;
209
import liquibase.diff.compare.SchemaComparison;
210
import liquibase.database.DatabaseFactory;
211
import liquibase.CatalogAndSchema;
212
213
// Connect to reference database (production)
214
Database referenceDatabase = DatabaseFactory.getInstance().openDatabase(
215
"jdbc:postgresql://prod-server:5432/myapp",
216
"readonly_user",
217
"password",
218
null, null, null
219
);
220
221
// Connect to target database (development)
222
Database targetDatabase = DatabaseFactory.getInstance().openDatabase(
223
"jdbc:postgresql://dev-server:5432/myapp",
224
"dev_user",
225
"password",
226
null, null, null
227
);
228
229
// Set up schema comparison
230
SchemaComparison[] schemaComparisons = new SchemaComparison[] {
231
new SchemaComparison(
232
new CatalogAndSchema("myapp", "public"), // reference schema
233
new CatalogAndSchema("myapp", "public") // target schema
234
)
235
};
236
237
// Define what to compare
238
Set<Class<? extends DatabaseObject>> compareTypes = new HashSet<>(Arrays.asList(
239
Table.class,
240
Column.class,
241
PrimaryKey.class,
242
ForeignKey.class,
243
Index.class,
244
View.class
245
));
246
247
CompareControl compareControl = new CompareControl(schemaComparisons, compareTypes);
248
249
// Execute comparison
250
DiffGeneratorFactory diffFactory = DiffGeneratorFactory.getInstance();
251
DiffResult diffResult = diffFactory.compare(referenceDatabase, targetDatabase, compareControl);
252
253
// Analyze results
254
System.out.println("Databases are equal: " + diffResult.areEqual());
255
```
256
257
### Analyzing Diff Results
258
259
```java { .api }
260
// Check for missing tables (in reference but not target)
261
Set<Table> missingTables = diffResult.getMissingObjects(Table.class);
262
System.out.println("Missing tables: " + missingTables.size());
263
for (Table table : missingTables) {
264
System.out.println(" - " + table.getName());
265
}
266
267
// Check for unexpected tables (in target but not reference)
268
Set<Table> unexpectedTables = diffResult.getUnexpectedObjects(Table.class);
269
System.out.println("Unexpected tables: " + unexpectedTables.size());
270
for (Table table : unexpectedTables) {
271
System.out.println(" - " + table.getName());
272
}
273
274
// Check for changed tables
275
Map<Table, ObjectDifferences> changedTables = diffResult.getChangedObjects(Table.class);
276
System.out.println("Changed tables: " + changedTables.size());
277
for (Map.Entry<Table, ObjectDifferences> entry : changedTables.entrySet()) {
278
Table table = entry.getKey();
279
ObjectDifferences differences = entry.getValue();
280
System.out.println(" - " + table.getName() + ": " + differences.hasDifferences());
281
}
282
```
283
284
### Column-Level Comparison
285
286
```java { .api }
287
// Analyze column differences
288
Set<Column> missingColumns = diffResult.getMissingObjects(Column.class);
289
for (Column column : missingColumns) {
290
System.out.println("Missing column: " +
291
column.getRelation().getName() + "." + column.getName());
292
}
293
294
Set<Column> unexpectedColumns = diffResult.getUnexpectedObjects(Column.class);
295
for (Column column : unexpectedColumns) {
296
System.out.println("Unexpected column: " +
297
column.getRelation().getName() + "." + column.getName());
298
}
299
300
Map<Column, ObjectDifferences> changedColumns = diffResult.getChangedObjects(Column.class);
301
for (Map.Entry<Column, ObjectDifferences> entry : changedColumns.entrySet()) {
302
Column column = entry.getKey();
303
ObjectDifferences differences = entry.getValue();
304
System.out.println("Changed column: " +
305
column.getRelation().getName() + "." + column.getName());
306
307
// Examine specific differences
308
if (differences.isDifferent("type")) {
309
System.out.println(" - Type changed from " +
310
differences.getReferenceValue("type") + " to " +
311
differences.getComparedValue("type"));
312
}
313
314
if (differences.isDifferent("nullable")) {
315
System.out.println(" - Nullable changed from " +
316
differences.getReferenceValue("nullable") + " to " +
317
differences.getComparedValue("nullable"));
318
}
319
}
320
```
321
322
### Constraint Comparison
323
324
```java { .api }
325
// Primary key differences
326
Set<PrimaryKey> missingPks = diffResult.getMissingObjects(PrimaryKey.class);
327
Set<PrimaryKey> unexpectedPks = diffResult.getUnexpectedObjects(PrimaryKey.class);
328
Map<PrimaryKey, ObjectDifferences> changedPks = diffResult.getChangedObjects(PrimaryKey.class);
329
330
System.out.println("Primary key differences:");
331
System.out.println(" Missing: " + missingPks.size());
332
System.out.println(" Unexpected: " + unexpectedPks.size());
333
System.out.println(" Changed: " + changedPks.size());
334
335
// Foreign key differences
336
Set<ForeignKey> missingFks = diffResult.getMissingObjects(ForeignKey.class);
337
Set<ForeignKey> unexpectedFks = diffResult.getUnexpectedObjects(ForeignKey.class);
338
339
for (ForeignKey fk : missingFks) {
340
System.out.println("Missing FK: " + fk.getName() +
341
" (" + fk.getForeignKeyTable().getName() + " -> " +
342
fk.getPrimaryKeyTable().getName() + ")");
343
}
344
345
// Index differences
346
Set<Index> missingIndexes = diffResult.getMissingObjects(Index.class);
347
Set<Index> unexpectedIndexes = diffResult.getUnexpectedObjects(Index.class);
348
349
for (Index index : missingIndexes) {
350
System.out.println("Missing index: " + index.getName() +
351
" on " + index.getRelation().getName());
352
}
353
```
354
355
### Multi-Schema Comparison
356
357
```java { .api }
358
// Compare multiple schemas
359
SchemaComparison[] multiSchemaComparisons = new SchemaComparison[] {
360
new SchemaComparison(
361
new CatalogAndSchema("prod_db", "public"),
362
new CatalogAndSchema("dev_db", "public")
363
),
364
new SchemaComparison(
365
new CatalogAndSchema("prod_db", "reporting"),
366
new CatalogAndSchema("dev_db", "reporting")
367
),
368
new SchemaComparison(
369
new CatalogAndSchema("prod_db", "audit"),
370
new CatalogAndSchema("dev_db", "audit")
371
)
372
};
373
374
CompareControl multiSchemaControl = new CompareControl(multiSchemaComparisons, compareTypes);
375
DiffResult multiSchemaDiff = diffFactory.compare(referenceDatabase, targetDatabase, multiSchemaControl);
376
377
// Results will include differences across all specified schemas
378
System.out.println("Multi-schema comparison complete");
379
System.out.println("Total missing objects: " + multiSchemaDiff.getMissingObjects().size());
380
System.out.println("Total unexpected objects: " + multiSchemaDiff.getUnexpectedObjects().size());
381
```
382
383
### Data Comparison
384
385
```java { .api }
386
// Include data in comparison (use sparingly - can be expensive)
387
Set<Class<? extends DatabaseObject>> dataCompareTypes = new HashSet<>(Arrays.asList(
388
Table.class,
389
Column.class,
390
Data.class // Include table data
391
));
392
393
CompareControl dataCompareControl = new CompareControl(schemaComparisons, dataCompareTypes);
394
DiffResult dataResult = diffFactory.compare(referenceDatabase, targetDatabase, dataCompareControl);
395
396
// Analyze data differences
397
Set<Data> missingData = dataResult.getMissingObjects(Data.class);
398
Set<Data> unexpectedData = dataResult.getUnexpectedObjects(Data.class);
399
400
System.out.println("Data row differences:");
401
System.out.println(" Missing rows: " + missingData.size());
402
System.out.println(" Unexpected rows: " + unexpectedData.size());
403
```
404
405
### Filtered Object Comparison
406
407
```java { .api }
408
// Compare only specific object types
409
Set<Class<? extends DatabaseObject>> tableOnlyTypes = new HashSet<>(Arrays.asList(
410
Table.class,
411
Column.class
412
));
413
414
CompareControl tableOnlyControl = new CompareControl(schemaComparisons, tableOnlyTypes);
415
DiffResult tableOnlyResult = diffFactory.compare(referenceDatabase, targetDatabase, tableOnlyControl);
416
417
// Only table and column differences will be included
418
System.out.println("Table-only comparison:");
419
System.out.println("Tables equal: " + tableOnlyResult.areEqual());
420
421
// Compare only indexes
422
Set<Class<? extends DatabaseObject>> indexOnlyTypes = new HashSet<>(Arrays.asList(
423
Index.class
424
));
425
426
CompareControl indexOnlyControl = new CompareControl(schemaComparisons, indexOnlyTypes);
427
DiffResult indexOnlyResult = diffFactory.compare(referenceDatabase, targetDatabase, indexOnlyControl);
428
429
System.out.println("Index-only comparison:");
430
System.out.println("Missing indexes: " + indexOnlyResult.getMissingObjects(Index.class).size());
431
```
432
433
### Complete Diff Analysis
434
435
```java { .api }
436
public class DatabaseDiffAnalyzer {
437
438
public void analyzeDifferences(DiffResult diffResult) {
439
System.out.println("=== Database Comparison Report ===");
440
System.out.println("Databases are equal: " + diffResult.areEqual());
441
System.out.println();
442
443
analyzeStructuralDifferences(diffResult);
444
analyzeConstraintDifferences(diffResult);
445
analyzeIndexDifferences(diffResult);
446
analyzeViewDifferences(diffResult);
447
}
448
449
private void analyzeStructuralDifferences(DiffResult diffResult) {
450
System.out.println("--- Structural Differences ---");
451
452
// Schema differences
453
reportDifferences(diffResult, Schema.class, "Schemas");
454
455
// Table differences
456
reportDifferences(diffResult, Table.class, "Tables");
457
458
// Column differences
459
reportDifferences(diffResult, Column.class, "Columns");
460
}
461
462
private void analyzeConstraintDifferences(DiffResult diffResult) {
463
System.out.println("--- Constraint Differences ---");
464
465
reportDifferences(diffResult, PrimaryKey.class, "Primary Keys");
466
reportDifferences(diffResult, ForeignKey.class, "Foreign Keys");
467
reportDifferences(diffResult, UniqueConstraint.class, "Unique Constraints");
468
}
469
470
private void analyzeIndexDifferences(DiffResult diffResult) {
471
System.out.println("--- Index Differences ---");
472
reportDifferences(diffResult, Index.class, "Indexes");
473
}
474
475
private void analyzeViewDifferences(DiffResult diffResult) {
476
System.out.println("--- View Differences ---");
477
reportDifferences(diffResult, View.class, "Views");
478
}
479
480
private <T extends DatabaseObject> void reportDifferences(
481
DiffResult diffResult, Class<T> type, String typeName) {
482
483
Set<T> missing = diffResult.getMissingObjects(type);
484
Set<T> unexpected = diffResult.getUnexpectedObjects(type);
485
Map<T, ObjectDifferences> changed = diffResult.getChangedObjects(type);
486
487
if (missing.isEmpty() && unexpected.isEmpty() && changed.isEmpty()) {
488
System.out.println(typeName + ": No differences");
489
return;
490
}
491
492
System.out.println(typeName + ":");
493
System.out.println(" Missing: " + missing.size());
494
System.out.println(" Unexpected: " + unexpected.size());
495
System.out.println(" Changed: " + changed.size());
496
497
// Report details for missing objects
498
for (T obj : missing) {
499
System.out.println(" Missing: " + getObjectDescription(obj));
500
}
501
502
// Report details for unexpected objects
503
for (T obj : unexpected) {
504
System.out.println(" Unexpected: " + getObjectDescription(obj));
505
}
506
507
// Report details for changed objects
508
for (Map.Entry<T, ObjectDifferences> entry : changed.entrySet()) {
509
System.out.println(" Changed: " + getObjectDescription(entry.getKey()));
510
}
511
512
System.out.println();
513
}
514
515
private String getObjectDescription(DatabaseObject obj) {
516
if (obj instanceof Table) {
517
Table table = (Table) obj;
518
return table.getName();
519
} else if (obj instanceof Column) {
520
Column column = (Column) obj;
521
return column.getRelation().getName() + "." + column.getName();
522
} else if (obj instanceof Index) {
523
Index index = (Index) obj;
524
return index.getName() + " on " + index.getRelation().getName();
525
}
526
// Add more object types as needed
527
return obj.toString();
528
}
529
}
530
```