1 package com.rsmart.kuali.tools.liquibase;
2
3 import liquibase.diff.*;
4 import liquibase.database.Database;
5 import liquibase.database.structure.*;
6 import liquibase.exception.DatabaseException;
7 import liquibase.snapshot.DatabaseSnapshot;
8 import liquibase.snapshot.DatabaseSnapshotGeneratorFactory;
9 import liquibase.util.StringUtils;
10
11 import java.util.*;
12
13 public class Diff {
14
15 private Database referenceDatabase;
16 private Database targetDatabase;
17
18 private DatabaseSnapshot referenceSnapshot;
19 private DatabaseSnapshot targetSnapshot;
20
21 private Set<DiffStatusListener> statusListeners = new HashSet<DiffStatusListener>();
22
23 private boolean diffTables = true;
24 private boolean diffColumns = true;
25 private boolean diffViews = true;
26 private boolean diffPrimaryKeys = true;
27 private boolean diffUniqueConstraints = true;
28 private boolean diffIndexes = true;
29 private boolean diffForeignKeys = true;
30 private boolean diffSequences = true;
31 private boolean diffData = false;
32
33 public Diff(Database referenceDatabase, Database targetDatabase) {
34 this.referenceDatabase = referenceDatabase;
35
36 this.targetDatabase = targetDatabase;
37 }
38
39 public Diff(Database originalDatabase, String schema)
40 throws DatabaseException {
41 targetDatabase = null;
42
43 referenceDatabase = originalDatabase;
44 referenceDatabase.setDefaultSchemaName(schema);
45 }
46
47 public Diff(DatabaseSnapshot referenceSnapshot,
48 DatabaseSnapshot targetDatabaseSnapshot) {
49 this.referenceSnapshot = referenceSnapshot;
50
51 this.targetSnapshot = targetDatabaseSnapshot;
52 }
53
54 public void addStatusListener(DiffStatusListener listener) {
55 statusListeners.add(listener);
56 }
57
58 public void removeStatusListener(DiffStatusListener listener) {
59 statusListeners.remove(listener);
60 }
61
62 public DiffResult compare() throws DatabaseException {
63 if (referenceSnapshot == null) {
64 referenceSnapshot = DatabaseSnapshotGeneratorFactory.getInstance()
65 .createSnapshot(referenceDatabase, referenceDatabase.getDefaultSchemaName(), statusListeners);
66 }
67
68 if (targetSnapshot == null) {
69 if (targetDatabase == null) {
70 targetSnapshot = new DatabaseSnapshot(referenceDatabase, null);
71 } else {
72 targetSnapshot = DatabaseSnapshotGeneratorFactory.getInstance()
73 .createSnapshot(targetDatabase, referenceDatabase.getDefaultSchemaName(), statusListeners);
74 }
75 }
76
77 DiffResult diffResult = new DiffResult(referenceSnapshot,
78 targetSnapshot);
79 checkVersionInfo(diffResult);
80 if (shouldDiffTables()) {
81 checkTables(diffResult);
82 }
83 if (shouldDiffViews()) {
84 checkViews(diffResult);
85 }
86 if (shouldDiffColumns()) {
87 checkColumns(diffResult);
88 }
89 if (shouldDiffForeignKeys()) {
90 checkForeignKeys(diffResult);
91 }
92 if (shouldDiffPrimaryKeys()) {
93 checkPrimaryKeys(diffResult);
94 }
95 if (shouldDiffUniqueConstraints()) {
96 checkUniqueConstraints(diffResult);
97 }
98 if (shouldDiffIndexes()) {
99 checkIndexes(diffResult);
100 }
101 if (shouldDiffSequences()) {
102 checkSequences(diffResult);
103 }
104 diffResult.setDiffData(shouldDiffData());
105
106
107
108 removeDuplicateIndexes( diffResult.getMissingIndexes() );
109 removeDuplicateIndexes( diffResult.getUnexpectedIndexes() );
110 removeDuplicateUniqueConstraints( diffResult.getMissingUniqueConstraints() );
111 removeDuplicateUniqueConstraints( diffResult.getUnexpectedUniqueConstraints() );
112
113 return diffResult;
114 }
115
116 public void setDiffTypes(String diffTypes) {
117 if (StringUtils.trimToNull(diffTypes) != null) {
118 Set<String> types = new HashSet<String>(Arrays.asList(diffTypes.toLowerCase().split("\\s*,\\s*")));
119
120 diffTables = types.contains("tables");
121 diffColumns = types.contains("columns");
122 diffViews = types.contains("views");
123 diffPrimaryKeys = types.contains("primaryKeys".toLowerCase());
124 diffUniqueConstraints = types.contains("uniqueConstraints".toLowerCase());
125 diffIndexes = types.contains("indexes");
126 diffForeignKeys = types.contains("foreignKeys".toLowerCase());
127 diffSequences = types.contains("sequences");
128 diffData = types.contains("data");
129 }
130 }
131
132 public boolean shouldDiffTables() {
133 return diffTables;
134 }
135
136 public void setDiffTables(boolean diffTables) {
137 this.diffTables = diffTables;
138 }
139
140 public boolean shouldDiffColumns() {
141 return diffColumns;
142 }
143
144 public void setDiffColumns(boolean diffColumns) {
145 this.diffColumns = diffColumns;
146 }
147
148 public boolean shouldDiffViews() {
149 return diffViews;
150 }
151
152 public void setDiffViews(boolean diffViews) {
153 this.diffViews = diffViews;
154 }
155
156 public boolean shouldDiffPrimaryKeys() {
157 return diffPrimaryKeys;
158 }
159
160 public void setDiffPrimaryKeys(boolean diffPrimaryKeys) {
161 this.diffPrimaryKeys = diffPrimaryKeys;
162 }
163
164 public boolean shouldDiffIndexes() {
165 return diffIndexes;
166 }
167
168 public void setDiffIndexes(boolean diffIndexes) {
169 this.diffIndexes = diffIndexes;
170 }
171
172 public boolean shouldDiffForeignKeys() {
173 return diffForeignKeys;
174 }
175
176 public void setDiffForeignKeys(boolean diffForeignKeys) {
177 this.diffForeignKeys = diffForeignKeys;
178 }
179
180 public boolean shouldDiffSequences() {
181 return diffSequences;
182 }
183
184 public void setDiffSequences(boolean diffSequences) {
185 this.diffSequences = diffSequences;
186 }
187
188 public boolean shouldDiffData() {
189 return diffData;
190 }
191
192 public void setDiffData(boolean diffData) {
193 this.diffData = diffData;
194 }
195
196 public boolean shouldDiffUniqueConstraints() {
197 return this.diffUniqueConstraints;
198 }
199
200 public void setDiffUniqueConstraints(boolean diffUniqueConstraints) {
201 this.diffUniqueConstraints = diffUniqueConstraints;
202 }
203
204 private void checkVersionInfo(DiffResult diffResult)
205 throws DatabaseException {
206
207 if (targetDatabase != null) {
208 diffResult.setProductName(new DiffComparison(referenceDatabase
209 .getDatabaseProductName(), targetDatabase
210 .getDatabaseProductName()));
211 diffResult.setProductVersion(new DiffComparison(referenceDatabase
212 .getDatabaseProductVersion(), targetDatabase
213 .getDatabaseProductVersion()));
214 }
215
216 }
217
218 private void checkTables(DiffResult diffResult) {
219 for (Table baseTable : referenceSnapshot.getTables()) {
220 if (!targetSnapshot.getTables().contains(baseTable)) {
221 diffResult.addMissingTable(baseTable);
222 }
223 }
224
225 for (Table targetTable : targetSnapshot.getTables()) {
226 if (!referenceSnapshot.getTables().contains(targetTable)) {
227 diffResult.addUnexpectedTable(targetTable);
228 }
229 }
230 }
231
232 private void checkViews(DiffResult diffResult) {
233 for (View baseView : referenceSnapshot.getViews()) {
234 if (!targetSnapshot.getViews().contains(baseView)) {
235 diffResult.addMissingView(baseView);
236 }
237 }
238
239 for (View targetView : targetSnapshot.getViews()) {
240 if (!referenceSnapshot.getViews().contains(targetView)) {
241 diffResult.addUnexpectedView(targetView);
242 } else {
243 for (View referenceView : referenceSnapshot.getViews()) {
244 if (referenceView.getName().equals(targetView.getName())) {
245 if (!referenceView.getDefinition().equals(targetView.getDefinition())) {
246 diffResult.addChangedView(referenceView);
247 }
248 }
249 }
250 }
251 }
252 }
253
254 private void checkColumns(DiffResult diffResult) {
255 for (Column baseColumn : referenceSnapshot.getColumns()) {
256 if (!targetSnapshot.getColumns().contains(baseColumn)
257 && (baseColumn.getTable() == null || !diffResult
258 .getMissingTables().contains(baseColumn.getTable()))
259 && (baseColumn.getView() == null || !diffResult
260 .getMissingViews().contains(baseColumn.getView()))) {
261 diffResult.addMissingColumn(baseColumn);
262 }
263 }
264
265 for (Column targetColumn : targetSnapshot.getColumns()) {
266 if (!referenceSnapshot.getColumns().contains(targetColumn)
267 && (targetColumn.getTable() == null || !diffResult
268 .getUnexpectedTables().contains(
269 targetColumn.getTable()))
270 && (targetColumn.getView() == null || !diffResult
271 .getUnexpectedViews().contains(
272 targetColumn.getView()))) {
273 diffResult.addUnexpectedColumn(targetColumn);
274 } else if (targetColumn.getTable() != null
275 && !diffResult.getUnexpectedTables().contains(
276 targetColumn.getTable())) {
277 Column baseColumn = referenceSnapshot.getColumn(targetColumn
278 .getTable().getName(), targetColumn.getName());
279
280 if (baseColumn == null || targetColumn.isDifferent(baseColumn)) {
281 diffResult.addChangedColumn(targetColumn);
282 }
283 }
284 }
285 }
286
287 private void checkForeignKeys(DiffResult diffResult) {
288 for (ForeignKey baseFK : referenceSnapshot.getForeignKeys()) {
289 if (!targetSnapshot.getForeignKeys().contains(baseFK)) {
290 diffResult.addMissingForeignKey(baseFK);
291 }
292 }
293
294 for (ForeignKey targetFK : targetSnapshot.getForeignKeys()) {
295 if (!referenceSnapshot.getForeignKeys().contains(targetFK)) {
296 diffResult.addUnexpectedForeignKey(targetFK);
297 }
298 }
299 }
300
301 private void checkUniqueConstraints(DiffResult diffResult) {
302 for (UniqueConstraint baseIndex : referenceSnapshot
303 .getUniqueConstraints()) {
304 if (!targetSnapshot.getUniqueConstraints().contains(baseIndex)) {
305 diffResult.addMissingUniqueConstraint(baseIndex);
306 }
307 }
308
309 for (UniqueConstraint targetIndex : targetSnapshot
310 .getUniqueConstraints()) {
311 if (!referenceSnapshot.getUniqueConstraints().contains(targetIndex)) {
312 diffResult.addUnexpectedUniqueConstraint(targetIndex);
313 }
314 }
315 }
316
317 private void checkIndexes(DiffResult diffResult) {
318 for (Index baseIndex : referenceSnapshot.getIndexes()) {
319 if (!targetSnapshot.getIndexes().contains(baseIndex)) {
320 diffResult.addMissingIndex(baseIndex);
321 }
322 }
323
324 for (Index targetIndex : targetSnapshot.getIndexes()) {
325 if (!referenceSnapshot.getIndexes().contains(targetIndex)) {
326 diffResult.addUnexpectedIndex(targetIndex);
327 }
328 }
329 }
330
331 private void checkPrimaryKeys(DiffResult diffResult) {
332 for (PrimaryKey basePrimaryKey : referenceSnapshot.getPrimaryKeys()) {
333 if (!targetSnapshot.getPrimaryKeys().contains(basePrimaryKey)) {
334 diffResult.addMissingPrimaryKey(basePrimaryKey);
335 }
336 }
337
338 for (PrimaryKey targetPrimaryKey : targetSnapshot.getPrimaryKeys()) {
339 if (!referenceSnapshot.getPrimaryKeys().contains(targetPrimaryKey)) {
340 diffResult.addUnexpectedPrimaryKey(targetPrimaryKey);
341 }
342 }
343 }
344
345 private void checkSequences(DiffResult diffResult) {
346 for (Sequence baseSequence : referenceSnapshot.getSequences()) {
347 if (!targetSnapshot.getSequences().contains(baseSequence)) {
348 diffResult.addMissingSequence(baseSequence);
349 }
350 }
351
352 for (Sequence targetSequence : targetSnapshot.getSequences()) {
353 if (!referenceSnapshot.getSequences().contains(targetSequence)) {
354 diffResult.addUnexpectedSequence(targetSequence);
355 }
356 }
357 }
358
359
360
361
362
363
364 private void removeDuplicateIndexes( SortedSet<Index> indexes )
365 {
366 SortedSet<Index> combinedIndexes = new TreeSet<Index>();
367 SortedSet<Index> indexesToRemove = new TreeSet<Index>();
368
369
370
371 for ( Index idx1 : indexes )
372 {
373 if ( !combinedIndexes.contains( idx1 ) )
374 {
375 for ( Index idx2 : indexes.tailSet( idx1 ) )
376 {
377 if ( idx1 == idx2 ) {
378 continue;
379 }
380
381 String index1Name = StringUtils.trimToEmpty(idx1.getName());
382 String index2Name = StringUtils.trimToEmpty(idx2.getName());
383 if ( index1Name.equalsIgnoreCase(index2Name)
384 && idx1.getTable().getName().equalsIgnoreCase( idx2.getTable().getName() ) )
385 {
386 for ( String column : idx2.getColumns() )
387 {
388 if ( !idx1.getColumns().contains( column ) ) {
389 idx1.getColumns().add( column );
390 }
391 }
392
393 indexesToRemove.add( idx2 );
394 }
395 }
396
397 combinedIndexes.add( idx1 );
398 }
399 }
400
401 indexes.removeAll( indexesToRemove );
402 }
403
404
405
406
407
408
409 private void removeDuplicateUniqueConstraints( SortedSet<UniqueConstraint> uniqueConstraints ) {
410 SortedSet<UniqueConstraint> combinedConstraints = new TreeSet<UniqueConstraint>();
411 SortedSet<UniqueConstraint> constraintsToRemove = new TreeSet<UniqueConstraint>();
412
413
414
415 for ( UniqueConstraint uc1 : uniqueConstraints )
416 {
417 if ( !combinedConstraints.contains( uc1 ) )
418 {
419 for ( UniqueConstraint uc2 : uniqueConstraints.tailSet( uc1 ) )
420 {
421 if ( uc1 == uc2 ) {
422 continue;
423 }
424
425 if ( uc1.getName().equalsIgnoreCase( uc2.getName() )
426 && uc1.getTable().getName().equalsIgnoreCase( uc2.getTable().getName() ) )
427 {
428 for ( String column : uc2.getColumns() )
429 {
430 if ( !uc1.getColumns().contains( column ) ) {
431 uc1.getColumns().add( column );
432 }
433 }
434
435 constraintsToRemove.add( uc2 );
436 }
437 }
438
439 combinedConstraints.add( uc1 );
440 }
441 }
442
443 uniqueConstraints.removeAll( constraintsToRemove );
444 }
445
446 }