View Javadoc

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         // Hack:  Sometimes Indexes or Unique Constraints with multiple columns get added twice (1 for each column),
107 		// so we're combining them back to a single Index or Unique Constraint here.
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 	 * Removes duplicate Indexes from the DiffResult object.
361 	 *
362 	 * @param indexes [IN/OUT] - A set of Indexes to be updated.
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 		// Find Indexes with the same name, copy their columns into the first one,
370 		// then remove the duplicate Indexes.
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 	 * Removes duplicate Unique Constraints from the DiffResult object.
406 	 *
407 	 * @param uniqueConstraints [IN/OUT] - A set of Unique Constraints to be updated.
408 	 */
409 	private void removeDuplicateUniqueConstraints( SortedSet<UniqueConstraint> uniqueConstraints ) {
410 		SortedSet<UniqueConstraint> combinedConstraints = new TreeSet<UniqueConstraint>();
411 		SortedSet<UniqueConstraint> constraintsToRemove = new TreeSet<UniqueConstraint>();
412 
413 		// Find UniqueConstraints with the same name, copy their columns into the first one,
414 		// then remove the duplicate UniqueConstraints.
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 }