View Javadoc

1   // Copyright 2011 Leo Przybylski. All rights reserved.
2   //
3   // Redistribution and use in source and binary forms, with or without modification, are
4   // permitted provided that the following conditions are met:
5   //
6   //    1. Redistributions of source code must retain the above copyright notice, this list of
7   //       conditions and the following disclaimer.
8   //
9   //    2. Redistributions in binary form must reproduce the above copyright notice, this list
10  //       of conditions and the following disclaimer in the documentation and/or other materials
11  //       provided with the distribution.
12  //
13  // THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR IMPLIED
14  // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
15  // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
16  // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17  // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19  // ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
20  // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
21  // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22  //
23  // The views and conclusions contained in the software and documentation are those of the
24  // authors and should not be interpreted as representing official policies, either expressed
25  // or implied, of Leo Przybylski.
26  package com.rsmart.kuali.tools.liquibase;
27  
28  import liquibase.change.Change;
29  import liquibase.change.ColumnConfig;
30  import liquibase.change.ConstraintsConfig;
31  import liquibase.change.core.*;
32  import liquibase.changelog.ChangeSet;
33  import liquibase.database.Database;
34  import liquibase.database.jvm.JdbcConnection;
35  import liquibase.database.structure.*;
36  import liquibase.database.typeconversion.TypeConverter;
37  import liquibase.database.typeconversion.TypeConverterFactory;
38  import liquibase.diff.*;
39  import liquibase.exception.DatabaseException;
40  import liquibase.executor.ExecutorService;
41  import liquibase.logging.LogFactory;
42  import liquibase.parser.core.xml.LiquibaseEntityResolver;
43  import liquibase.parser.core.xml.XMLChangeLogSAXParser;
44  import liquibase.serializer.ChangeLogSerializer;
45  import liquibase.serializer.ChangeLogSerializerFactory;
46  import liquibase.serializer.core.xml.XMLChangeLogSerializer;
47  import liquibase.snapshot.DatabaseSnapshot;
48  import liquibase.statement.DatabaseFunction;
49  import liquibase.statement.core.RawSqlStatement;
50  import liquibase.statement.ext.DescribeSequenceStatement;
51  import liquibase.util.ISODateFormat;
52  import liquibase.util.StringUtils;
53  import liquibase.util.csv.CSVWriter;
54  import liquibase.util.xml.DefaultXmlWriter;
55  import liquibase.util.xml.XmlWriter;
56  import org.w3c.dom.Document;
57  import org.w3c.dom.Element;
58  
59  import javax.xml.parsers.DocumentBuilder;
60  import javax.xml.parsers.DocumentBuilderFactory;
61  import javax.xml.parsers.ParserConfigurationException;
62  import java.io.*;
63  import java.math.BigInteger;
64  import java.sql.ResultSet;
65  import java.sql.Statement;
66  import java.util.*;
67  
68  public class DiffResult {
69  
70  	private String idRoot = String.valueOf(new Date().getTime());
71  	private int changeNumber = 1;
72  
73  	private DatabaseSnapshot referenceSnapshot;
74  	private DatabaseSnapshot targetSnapshot;
75  
76  	private DiffComparison productName;
77  	private DiffComparison productVersion;
78  
79  	private SortedSet<Table> missingTables = new TreeSet<Table>();
80  	private SortedSet<Table> unexpectedTables = new TreeSet<Table>();
81  
82  	private SortedSet<View> missingViews = new TreeSet<View>();
83  	private SortedSet<View> unexpectedViews = new TreeSet<View>();
84  	private SortedSet<View> changedViews = new TreeSet<View>();
85  
86  	private SortedSet<Column> missingColumns = new TreeSet<Column>();
87  	private SortedSet<Column> unexpectedColumns = new TreeSet<Column>();
88  	private SortedSet<Column> changedColumns = new TreeSet<Column>();
89  
90  	private SortedSet<ForeignKey> missingForeignKeys = new TreeSet<ForeignKey>();
91  	private SortedSet<ForeignKey> unexpectedForeignKeys = new TreeSet<ForeignKey>();
92  
93  	private SortedSet<Index> missingIndexes = new TreeSet<Index>();
94  	private SortedSet<Index> unexpectedIndexes = new TreeSet<Index>();
95  
96  	private SortedSet<PrimaryKey> missingPrimaryKeys = new TreeSet<PrimaryKey>();
97  	private SortedSet<PrimaryKey> unexpectedPrimaryKeys = new TreeSet<PrimaryKey>();
98  
99  	private SortedSet<UniqueConstraint> missingUniqueConstraints = new TreeSet<UniqueConstraint>();
100 	private SortedSet<UniqueConstraint> unexpectedUniqueConstraints = new TreeSet<UniqueConstraint>();
101 
102 	private SortedSet<Sequence> missingSequences = new TreeSet<Sequence>();
103 	private SortedSet<Sequence> unexpectedSequences = new TreeSet<Sequence>();
104 
105 	private boolean diffData = false;
106 	private String dataDir = null;
107 	private String changeSetContext;
108 	private String changeSetAuthor;
109 
110 	private ChangeLogSerializerFactory serializerFactory = ChangeLogSerializerFactory.getInstance();
111 
112 	public DiffResult(DatabaseSnapshot referenceDatabaseSnapshot,
113 			DatabaseSnapshot targetDatabaseSnapshot) {
114 		this.referenceSnapshot = referenceDatabaseSnapshot;
115 
116 		if (targetDatabaseSnapshot == null) {
117 			targetDatabaseSnapshot = new DatabaseSnapshot(
118 					referenceDatabaseSnapshot.getDatabase(), null);
119 		}
120 		this.targetSnapshot = targetDatabaseSnapshot;
121 	}
122 
123 	public DiffComparison getProductName() {
124 		return productName;
125 	}
126 
127 	public void setProductName(DiffComparison productName) {
128 		this.productName = productName;
129 	}
130 
131 	public DiffComparison getProductVersion() {
132 		return productVersion;
133 	}
134 
135 	public void setProductVersion(DiffComparison product) {
136 		this.productVersion = product;
137 	}
138 
139 	public void addMissingTable(Table table) {
140 		missingTables.add(table);
141 	}
142 
143 	public SortedSet<Table> getMissingTables() {
144 		return missingTables;
145 	}
146 
147 	public void addUnexpectedTable(Table table) {
148 		unexpectedTables.add(table);
149 	}
150 
151 	public SortedSet<Table> getUnexpectedTables() {
152 		return unexpectedTables;
153 	}
154 
155 	public void addMissingView(View viewName) {
156 		missingViews.add(viewName);
157 	}
158 
159 	public SortedSet<View> getMissingViews() {
160 		return missingViews;
161 	}
162 
163 	public void addUnexpectedView(View viewName) {
164 		unexpectedViews.add(viewName);
165 	}
166 
167 	public SortedSet<View> getUnexpectedViews() {
168 		return unexpectedViews;
169 	}
170 
171 	public void addChangedView(View viewName) {
172 		changedViews.add(viewName);
173 	}
174 
175 	public SortedSet<View> getChangedViews() {
176 		return changedViews;
177 	}
178 
179 	public void addMissingColumn(Column columnName) {
180 		missingColumns.add(columnName);
181 	}
182 
183 	public SortedSet<Column> getMissingColumns() {
184 		return missingColumns;
185 	}
186 
187 	public void addUnexpectedColumn(Column columnName) {
188 		unexpectedColumns.add(columnName);
189 	}
190 
191 	public SortedSet<Column> getUnexpectedColumns() {
192 		return unexpectedColumns;
193 	}
194 
195 	public void addChangedColumn(Column columnName) {
196 		changedColumns.add(columnName);
197 	}
198 
199 	public SortedSet<Column> getChangedColumns() {
200 		return changedColumns;
201 	}
202 
203 	public void addMissingForeignKey(ForeignKey fkName) {
204 		missingForeignKeys.add(fkName);
205 	}
206 
207 	public SortedSet<ForeignKey> getMissingForeignKeys() {
208 		return missingForeignKeys;
209 	}
210 
211 	public void addUnexpectedForeignKey(ForeignKey fkName) {
212 		unexpectedForeignKeys.add(fkName);
213 	}
214 
215 	public SortedSet<ForeignKey> getUnexpectedForeignKeys() {
216 		return unexpectedForeignKeys;
217 	}
218 
219 	public void addMissingIndex(Index fkName) {
220 		missingIndexes.add(fkName);
221 	}
222 
223 	public SortedSet<Index> getMissingIndexes() {
224 		return missingIndexes;
225 	}
226 
227 	public void addUnexpectedIndex(Index fkName) {
228 		unexpectedIndexes.add(fkName);
229 	}
230 
231 	public SortedSet<Index> getUnexpectedIndexes() {
232 		return unexpectedIndexes;
233 	}
234 
235 	public void addMissingPrimaryKey(PrimaryKey primaryKey) {
236 		missingPrimaryKeys.add(primaryKey);
237 	}
238 
239 	public SortedSet<PrimaryKey> getMissingPrimaryKeys() {
240 		return missingPrimaryKeys;
241 	}
242 
243 	public void addUnexpectedPrimaryKey(PrimaryKey primaryKey) {
244 		unexpectedPrimaryKeys.add(primaryKey);
245 	}
246 
247 	public SortedSet<PrimaryKey> getUnexpectedPrimaryKeys() {
248 		return unexpectedPrimaryKeys;
249 	}
250 
251 	public void addMissingSequence(Sequence sequence) {
252 		missingSequences.add(sequence);
253 	}
254 
255 	public SortedSet<Sequence> getMissingSequences() {
256 		return missingSequences;
257 	}
258 
259 	public void addUnexpectedSequence(Sequence sequence) {
260 		unexpectedSequences.add(sequence);
261 	}
262 
263 	public SortedSet<Sequence> getUnexpectedSequences() {
264 		return unexpectedSequences;
265 	}
266 
267 	public void addMissingUniqueConstraint(UniqueConstraint uniqueConstraint) {
268 		missingUniqueConstraints.add(uniqueConstraint);
269 	}
270 
271 	public SortedSet<UniqueConstraint> getMissingUniqueConstraints() {
272 		return this.missingUniqueConstraints;
273 	}
274 
275 	public void addUnexpectedUniqueConstraint(UniqueConstraint uniqueConstraint) {
276 		unexpectedUniqueConstraints.add(uniqueConstraint);
277 	}
278 
279 	public SortedSet<UniqueConstraint> getUnexpectedUniqueConstraints() {
280 		return unexpectedUniqueConstraints;
281 	}
282 
283 	public boolean shouldDiffData() {
284 		return diffData;
285 	}
286 
287 	public void setDiffData(boolean diffData) {
288 		this.diffData = diffData;
289 	}
290 
291 	public String getDataDir() {
292 		return dataDir;
293 	}
294 
295 	public void setDataDir(String dataDir) {
296 		this.dataDir = dataDir;
297 	}
298 
299 	public String getChangeSetContext() {
300 		return changeSetContext;
301 	}
302 
303 	public void setChangeSetContext(String changeSetContext) {
304 		this.changeSetContext = changeSetContext;
305 	}
306 
307         public boolean differencesFound() throws DatabaseException,IOException{
308             boolean differencesInData=false;
309             if(shouldDiffData()) {
310                 List<ChangeSet> changeSets = new ArrayList<ChangeSet>();
311                 addInsertDataChanges(changeSets, dataDir);
312                 differencesInData=!changeSets.isEmpty();
313             }
314 
315             return getMissingColumns().size()>0 ||
316                     getMissingForeignKeys().size()>0 ||
317                     getMissingIndexes().size()>0 ||
318                     getMissingPrimaryKeys().size()>0 ||
319                     getMissingSequences().size()>0 ||
320                     getMissingTables().size()>0 ||
321                     getMissingUniqueConstraints().size()>0 ||
322                     getMissingViews().size()>0 ||
323                     getUnexpectedColumns().size()>0 ||
324                     getUnexpectedForeignKeys().size()>0 ||
325                     getUnexpectedIndexes().size()>0 ||
326                     getUnexpectedPrimaryKeys().size()>0 ||
327                     getUnexpectedSequences().size()>0 ||
328                     getUnexpectedTables().size()>0 ||
329                     getUnexpectedUniqueConstraints().size()>0 ||
330                     getUnexpectedViews().size()>0 ||
331                     differencesInData;
332         }
333 
334 
335 	public void printResult(PrintStream out) throws DatabaseException {
336 		out.println("Reference Database: " + referenceSnapshot.getDatabase());
337 		out.println("Target Database: " + targetSnapshot.getDatabase());
338 
339 		printComparision("Product Name", productName, out);
340 		printComparision("Product Version", productVersion, out);
341 		printSetComparison("Missing Tables", getMissingTables(), out);
342 		printSetComparison("Unexpected Tables", getUnexpectedTables(), out);
343 		printSetComparison("Missing Views", getMissingViews(), out);
344 		printSetComparison("Unexpected Views", getUnexpectedViews(), out);
345 		printSetComparison("Changed Views", getChangedViews(), out);
346 		printSetComparison("Missing Columns", getMissingColumns(), out);
347 		printSetComparison("Unexpected Columns", getUnexpectedColumns(), out);
348 		printColumnComparison(getChangedColumns(), out);
349 		printSetComparison("Missing Foreign Keys", getMissingForeignKeys(), out);
350 		printSetComparison("Unexpected Foreign Keys",
351 				getUnexpectedForeignKeys(), out);
352 		printSetComparison("Missing Primary Keys", getMissingPrimaryKeys(), out);
353 		printSetComparison("Unexpected Primary Keys",
354 				getUnexpectedPrimaryKeys(), out);
355         printSetComparison("Unexpected Unique Constraints",
356                 getUnexpectedUniqueConstraints(), out);
357 		printSetComparison("Missing Unique Constraints",
358 				getMissingUniqueConstraints(), out);
359 		printSetComparison("Missing Indexes", getMissingIndexes(), out);
360 		printSetComparison("Unexpected Indexes", getUnexpectedIndexes(), out);
361 		printSetComparison("Missing Sequences", getMissingSequences(), out);
362 		printSetComparison("Unexpected Sequences", getUnexpectedSequences(),
363 				out);
364 	}
365 
366 	private void printSetComparison(String title, SortedSet<?> objects,
367 			PrintStream out) {
368 		out.print(title + ": ");
369 		if (objects.size() == 0) {
370 			out.println("NONE");
371 		} else {
372 			out.println();
373 			for (Object object : objects) {
374 				out.println("     " + object);
375 			}
376 		}
377 	}
378 
379 	private void printColumnComparison(SortedSet<Column> changedColumns,
380 			PrintStream out) {
381 		out.print("Changed Columns: ");
382 		if (changedColumns.size() == 0) {
383 			out.println("NONE");
384 		} else {
385 			out.println();
386 			for (Column column : changedColumns) {
387 				out.println("     " + column);
388 				Column baseColumn = referenceSnapshot.getColumn(column
389 						.getTable().getName(), column.getName());
390 				if (baseColumn != null) {
391 					if (baseColumn.isDataTypeDifferent(column)) {
392 						out.println("           from "
393 								+ TypeConverterFactory.getInstance().findTypeConverter(referenceSnapshot.getDatabase()).convertToDatabaseTypeString(baseColumn, referenceSnapshot.getDatabase())
394 								+ " to "
395 								+ TypeConverterFactory.getInstance().findTypeConverter(targetSnapshot.getDatabase()).convertToDatabaseTypeString(targetSnapshot.getColumn(column.getTable().getName(), column.getName()), targetSnapshot.getDatabase()));
396 					}
397 					if (baseColumn.isNullabilityDifferent(column)) {
398 						Boolean nowNullable = targetSnapshot.getColumn(
399 								column.getTable().getName(), column.getName())
400 								.isNullable();
401 						if (nowNullable == null) {
402 							nowNullable = Boolean.TRUE;
403 						}
404 						if (nowNullable) {
405 							out.println("           now nullable");
406 						} else {
407 							out.println("           now not null");
408 						}
409 					}
410 				}
411 			}
412 		}
413 	}
414 
415 	private void printComparision(String title, DiffComparison comparison, PrintStream out) {
416 		out.print(title + ":");
417 
418         if (comparison == null) {
419             out.print("NULL");
420             return;
421         }
422 
423 		if (comparison.areTheSame()) {
424 			out.println(" EQUAL");
425 		} else {
426 			out.println();
427 			out.println("     Reference:   '"
428 					+ comparison.getReferenceVersion() + "'");
429 			out.println("     Target: '" + comparison.getTargetVersion() + "'");
430 		}
431 
432 	}
433 
434 	public void printChangeLog(String changeLogFile, Database targetDatabase)
435 			throws ParserConfigurationException, IOException, DatabaseException {
436 		ChangeLogSerializer changeLogSerializer = serializerFactory.getSerializer(changeLogFile);
437 		this.printChangeLog(changeLogFile, targetDatabase, changeLogSerializer);
438 	}
439 
440 	public void printChangeLog(PrintStream out, Database targetDatabase)
441 			throws ParserConfigurationException, IOException, DatabaseException {
442 		this.printChangeLog(out, targetDatabase, new XMLChangeLogSerializer());
443 	}
444 
445 	public void printChangeLog(String changeLogFile, Database targetDatabase,
446 			ChangeLogSerializer changeLogSerializer) throws ParserConfigurationException,
447 			IOException, DatabaseException {
448 		File file = new File(changeLogFile);
449 		if (!file.exists()) {
450 			LogFactory.getLogger().info(file + " does not exist, creating");
451 			FileOutputStream stream = new FileOutputStream(file);
452 			printChangeLog(new PrintStream(stream), targetDatabase, changeLogSerializer);
453 			stream.close();
454 		} else {
455 			LogFactory.getLogger().info(file + " exists, appending");
456 			ByteArrayOutputStream out = new ByteArrayOutputStream();
457 			printChangeLog(new PrintStream(out), targetDatabase, changeLogSerializer);
458 
459 			String xml = new String(out.toByteArray());
460 			xml = xml.replaceFirst("(?ms).*<databaseChangeLog[^>]*>", "");
461 			xml = xml.replaceFirst("</databaseChangeLog>", "");
462 			xml = xml.trim();
463 			if ("".equals( xml )) {
464 			    LogFactory.getLogger().info("No changes found, nothing to do");
465 			    return;
466 			}
467 
468 			String lineSeparator = System.getProperty("line.separator");
469 			BufferedReader fileReader = new BufferedReader(new FileReader(file));
470 			String line;
471 			long offset = 0;
472 			while ((line = fileReader.readLine()) != null) {
473 				int index = line.indexOf("</databaseChangeLog>");
474 				if (index >= 0) {
475 					offset += index;
476 				} else {
477 					offset += line.getBytes().length;
478 					offset += lineSeparator.getBytes().length;
479 				}
480 			}
481 			fileReader.close();
482 
483 			fileReader = new BufferedReader(new FileReader(file));
484 			fileReader.skip(offset);
485 
486 			fileReader.close();
487 
488 			// System.out.println("resulting XML: " + xml.trim());
489 
490 			RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
491 			randomAccessFile.seek(offset);
492 			randomAccessFile.writeBytes("    "); 
493 			randomAccessFile.write( xml.getBytes() );
494 			randomAccessFile.writeBytes(lineSeparator);
495 			randomAccessFile.writeBytes("</databaseChangeLog>" + lineSeparator);
496 			randomAccessFile.close();
497 
498 			// BufferedWriter fileWriter = new BufferedWriter(new
499 			// FileWriter(file));
500 			// fileWriter.append(xml);
501 			// fileWriter.close();
502 		}
503 	}
504 
505 	/**
506 	 * Prints changeLog that would bring the target database to be the same as
507 	 * the reference database
508 	 */
509 	public void printChangeLog(PrintStream out, Database targetDatabase,
510 			ChangeLogSerializer changeLogSerializer)
511 			throws ParserConfigurationException,
512 			IOException, DatabaseException {
513 		List<ChangeSet> changeSets = new ArrayList<ChangeSet>();
514 		addMissingTableChanges(changeSets, targetDatabase);
515 		addMissingColumnChanges(changeSets, targetDatabase);
516 		addChangedColumnChanges(changeSets);
517 		addMissingPrimaryKeyChanges(changeSets);
518 		addUnexpectedPrimaryKeyChanges(changeSets);
519         addUnexpectedForeignKeyChanges(changeSets);
520 		addMissingUniqueConstraintChanges(changeSets);
521         addMissingIndexChanges(changeSets);
522 		addUnexpectedUniqueConstraintChanges(changeSets);
523 
524 		if (diffData) {
525 			addInsertDataChanges(changeSets, dataDir);
526 		}
527 
528 		addMissingForeignKeyChanges(changeSets);
529 		addUnexpectedIndexChanges(changeSets);
530 		addUnexpectedColumnChanges(changeSets);
531 		addMissingSequenceChanges(changeSets);
532 		addUnexpectedSequenceChanges(changeSets);
533 		addMissingViewChanges(changeSets);
534 		addUnexpectedViewChanges(changeSets);
535 		addChangedViewChanges(changeSets);
536 		addUnexpectedTableChanges(changeSets);
537 
538 		changeLogSerializer.write(changeSets, out);
539 
540 		out.flush();
541 	}
542 
543 	private ChangeSet generateChangeSet(Change change) {
544 		ChangeSet changeSet = generateChangeSet();
545 		changeSet.addChange(change);
546 
547 		return changeSet;
548 	}
549 
550 	private ChangeSet generateChangeSet() {
551 		return new ChangeSet(generateId(), getChangeSetAuthor(), false, false,
552 				null, getChangeSetContext(), null);
553 	}
554 
555 	private String getChangeSetAuthor() {
556 		if (changeSetAuthor != null) {
557 			return changeSetAuthor;
558 		}
559 		String author = System.getProperty("user.name");
560 		if (StringUtils.trimToNull(author) == null) {
561 			return "diff-generated";
562 		} else {
563 			return author + " (generated)";
564 		}
565 	}
566 
567 	public void setChangeSetAuthor(String changeSetAuthor) {
568 		this.changeSetAuthor = changeSetAuthor;
569 	}
570 
571     public void setIdRoot(String idRoot) {
572         this.idRoot = idRoot;
573     }
574 
575     protected String generateId() {
576 		return idRoot + "-" + changeNumber++;
577 	}
578 
579 	private void addUnexpectedIndexChanges(List<ChangeSet> changes) {
580 		for (Index index : getUnexpectedIndexes()) {
581 
582             if (index.getAssociatedWith().contains(Index.MARK_PRIMARY_KEY) || index.getAssociatedWith().contains(Index.MARK_FOREIGN_KEY) || index.getAssociatedWith().contains(Index.MARK_UNIQUE_CONSTRAINT)) {
583                 continue;
584             }
585 
586             DropIndexChange change = new DropIndexChange();
587 			change.setTableName(index.getTable().getName());
588 			change.setSchemaName(index.getTable().getSchema());
589 			change.setIndexName(index.getName());
590             change.setAssociatedWith(index.getAssociatedWithAsString());
591 
592 			changes.add(generateChangeSet(change));
593 		}
594 	}
595 
596 	private void addMissingIndexChanges(List<ChangeSet> changes) {
597 		for (Index index : getMissingIndexes()) {
598 
599 			CreateIndexChange change = new CreateIndexChange();
600 			change.setTableName(index.getTable().getName());
601 			change.setTablespace(index.getTablespace());
602 			change.setSchemaName(index.getTable().getSchema());
603 			change.setIndexName(index.getName());
604 			change.setUnique(index.isUnique());
605 			change.setAssociatedWith(index.getAssociatedWithAsString());
606 
607             if (index.getAssociatedWith().contains(Index.MARK_PRIMARY_KEY) || index.getAssociatedWith().contains(Index.MARK_FOREIGN_KEY) || index.getAssociatedWith().contains(Index.MARK_UNIQUE_CONSTRAINT)) {
608                 continue;
609             }
610 
611 			for (String columnName : index.getColumns()) {
612 				ColumnConfig column = new ColumnConfig();
613 				column.setName(columnName);
614 				change.addColumn(column);
615 			}
616 			changes.add(generateChangeSet(change));
617 		}
618 	}
619 
620 	private void addUnexpectedPrimaryKeyChanges(List<ChangeSet> changes) {
621 		for (PrimaryKey pk : getUnexpectedPrimaryKeys()) {
622 
623 			if (!getUnexpectedTables().contains(pk.getTable())) {
624 				DropPrimaryKeyChange change = new DropPrimaryKeyChange();
625 				change.setTableName(pk.getTable().getName());
626 				change.setSchemaName(pk.getTable().getSchema());
627 				change.setConstraintName(pk.getName());
628 
629 				changes.add(generateChangeSet(change));
630 			}
631 		}
632 	}
633 
634 	private void addMissingPrimaryKeyChanges(List<ChangeSet> changes) {
635 		for (PrimaryKey pk : getMissingPrimaryKeys()) {
636 
637 			AddPrimaryKeyChange change = new AddPrimaryKeyChange();
638 			change.setTableName(pk.getTable().getName());
639 			change.setSchemaName(pk.getTable().getSchema());
640 			change.setConstraintName(pk.getName());
641 			change.setColumnNames(pk.getColumnNames());
642 			change.setTablespace(pk.getTablespace());
643 
644 			changes.add(generateChangeSet(change));
645 		}
646 	}
647 
648 	private void addUnexpectedUniqueConstraintChanges(List<ChangeSet> changes) {
649 		for (UniqueConstraint uc : getUnexpectedUniqueConstraints()) {
650 			// Need check for nulls here due to NullPointerException using Postgres
651 			if (null != uc) {
652 				if (null != uc.getTable()) {
653                     DropUniqueConstraintChange change = new DropUniqueConstraintChange();
654                     change.setTableName(uc.getTable().getName());
655                     change.setSchemaName(uc.getTable().getSchema());
656                     change.setConstraintName(uc.getName());
657 
658                     changes.add(generateChangeSet(change));
659 				}
660 			}
661 		}
662 	}
663 
664 	private void addMissingUniqueConstraintChanges(List<ChangeSet> changes) {
665 		for (UniqueConstraint uc : getMissingUniqueConstraints()) {
666 			// Need check for nulls here due to NullPointerException using Postgres
667 			if (null != uc)
668 				if (null != uc.getTable()) {
669 					AddUniqueConstraintChange change = new AddUniqueConstraintChange();
670 					change.setTableName(uc.getTable().getName());
671 					change.setTablespace(uc.getTablespace());
672 					change.setSchemaName(uc.getTable().getSchema());
673 					change.setConstraintName(uc.getName());
674 					change.setColumnNames(uc.getColumnNames());
675 					change.setDeferrable(uc.isDeferrable());
676 					change.setInitiallyDeferred(uc.isInitiallyDeferred());
677 					change.setDisabled(uc.isDisabled());
678 					changes.add(generateChangeSet(change));
679 				}
680 		}
681 	}
682 
683 	private void addUnexpectedForeignKeyChanges(List<ChangeSet> changes) {
684 		for (ForeignKey fk : getUnexpectedForeignKeys()) {
685 
686 			DropForeignKeyConstraintChange change = new DropForeignKeyConstraintChange();
687 			change.setConstraintName(fk.getName());
688 			change.setBaseTableName(fk.getForeignKeyTable().getName());
689 			change.setBaseTableSchemaName(fk.getForeignKeyTable().getSchema());
690 
691 			changes.add(generateChangeSet(change));
692 		}
693 	}
694 
695 	private void addMissingForeignKeyChanges(List<ChangeSet> changes) {
696 		for (ForeignKey fk : getMissingForeignKeys()) {
697 
698 			AddForeignKeyConstraintChange change = new AddForeignKeyConstraintChange();
699 			change.setConstraintName(fk.getName());
700 
701 			change.setReferencedTableName(fk.getPrimaryKeyTable().getName());
702 			change.setReferencedTableSchemaName(fk.getPrimaryKeyTable()
703 					.getSchema());
704 			change.setReferencedColumnNames(fk.getPrimaryKeyColumns());
705 
706 			change.setBaseTableName(fk.getForeignKeyTable().getName());
707 			change.setBaseTableSchemaName(fk.getForeignKeyTable().getSchema());
708 			change.setBaseColumnNames(fk.getForeignKeyColumns());
709 
710 			change.setDeferrable(fk.isDeferrable());
711 			change.setInitiallyDeferred(fk.isInitiallyDeferred());
712 			change.setOnUpdate(fk.getUpdateRule());
713 			change.setOnDelete(fk.getDeleteRule());
714 
715 			change.setReferencesUniqueColumn(fk.getReferencesUniqueColumn());
716 
717 			changes.add(generateChangeSet(change));
718 		}
719 	}
720 
721 	private void addUnexpectedSequenceChanges(List<ChangeSet> changes) {
722 		for (Sequence sequence : getUnexpectedSequences()) {
723 
724 			DropSequenceChange change = new DropSequenceChange();
725 			change.setSequenceName(sequence.getName());
726 			change.setSchemaName(sequence.getSchema());
727 
728 			changes.add(generateChangeSet(change));
729 		}
730 	}
731 
732 	private void addMissingSequenceChanges(List<ChangeSet> changes) {
733 		for (Sequence sequence : getMissingSequences()) {
734 
735 			CreateSequenceChange change = new CreateSequenceChange();
736 			change.setSequenceName(sequence.getName());
737 			change.setSchemaName(sequence.getSchema());
738             try {
739                 final DescribeSequenceStatement statement = new DescribeSequenceStatement(sequence.getName());
740                 final BigInteger startValue = (BigInteger) ExecutorService.getInstance().getExecutor(referenceSnapshot.getDatabase()).queryForObject(statement, BigInteger.class);
741                 change.setStartValue(startValue);
742             }
743             catch (Exception e) {
744                 e.printStackTrace();
745                 // quietly don't set start value
746             }
747 			changes.add(generateChangeSet(change));
748 		}
749 	}
750     
751 
752 	private void addUnexpectedColumnChanges(List<ChangeSet> changes) {
753 		for (Column column : getUnexpectedColumns()) {
754 			if (!shouldModifyColumn(column)) {
755 				continue;
756 			}
757 
758 			DropColumnChange change = new DropColumnChange();
759 			change.setTableName(column.getTable().getName());
760 			change.setSchemaName(column.getTable().getSchema());
761 			change.setColumnName(column.getName());
762 
763 			changes.add(generateChangeSet(change));
764 		}
765 	}
766 
767 	private void addMissingViewChanges(List<ChangeSet> changes) {
768 		for (View view : getMissingViews()) {
769 
770 			CreateViewChange change = new CreateViewChange();
771 			change.setViewName(view.getName());
772 			change.setSchemaName(view.getSchema());
773 			String selectQuery = view.getDefinition();
774 			if (selectQuery == null) {
775 				selectQuery = "COULD NOT DETERMINE VIEW QUERY";
776 			}
777 			change.setSelectQuery(selectQuery);
778 
779 			changes.add(generateChangeSet(change));
780 		}
781 	}
782 
783 	private void addChangedViewChanges(List<ChangeSet> changes) {
784 		for (View view : getChangedViews()) {
785 
786 			CreateViewChange change = new CreateViewChange();
787 			change.setViewName(view.getName());
788 			change.setSchemaName(view.getSchema());
789 			String selectQuery = view.getDefinition();
790 			if (selectQuery == null) {
791 				selectQuery = "COULD NOT DETERMINE VIEW QUERY";
792 			}
793 			change.setSelectQuery(selectQuery);
794 			change.setReplaceIfExists(true);
795 
796 			changes.add(generateChangeSet(change));
797 		}
798 	}
799 
800 	private void addChangedColumnChanges(List<ChangeSet> changes) {
801 		for (Column column : getChangedColumns()) {
802 			if (!shouldModifyColumn(column)) {
803 				continue;
804 			}
805 
806             TypeConverter targetTypeConverter = TypeConverterFactory.getInstance().findTypeConverter(targetSnapshot.getDatabase());
807 			boolean foundDifference = false;
808 			Column referenceColumn = referenceSnapshot.getColumn(column.getTable().getName(), column.getName());
809 			if (column.isDataTypeDifferent(referenceColumn)) {
810 				ModifyDataTypeChange change = new ModifyDataTypeChange();
811 				change.setTableName(column.getTable().getName());
812 				change.setSchemaName(column.getTable().getSchema());
813 				change.setColumnName(column.getName());
814                 change.setNewDataType(targetTypeConverter.convertToDatabaseTypeString(referenceColumn, targetSnapshot.getDatabase()));
815 				changes.add(generateChangeSet(change));
816 				foundDifference = true;
817 			}
818 			if (column.isNullabilityDifferent(referenceColumn)) {
819 				if (referenceColumn.isNullable() == null
820 						|| referenceColumn.isNullable()) {
821 					DropNotNullConstraintChange change = new DropNotNullConstraintChange();
822 					change.setTableName(column.getTable().getName());
823 					change.setSchemaName(column.getTable().getSchema());
824 					change.setColumnName(column.getName());
825 					change.setColumnDataType(targetTypeConverter.convertToDatabaseTypeString(referenceColumn, targetSnapshot.getDatabase()));
826 
827 					changes.add(generateChangeSet(change));
828 					foundDifference = true;
829 				} else {
830 					AddNotNullConstraintChange change = new AddNotNullConstraintChange();
831 					change.setTableName(column.getTable().getName());
832 					change.setSchemaName(column.getTable().getSchema());
833 					change.setColumnName(column.getName());
834 					change.setColumnDataType(targetTypeConverter.convertToDatabaseTypeString(referenceColumn, targetSnapshot.getDatabase()));
835 
836                     Object defaultValue = column.getDefaultValue();
837                     String defaultValueString;
838                     if (defaultValue != null) {
839                         defaultValueString = targetTypeConverter.getDataType(defaultValue).convertObjectToString(defaultValue, targetSnapshot.getDatabase());
840 
841                         if (defaultValueString != null) {
842                             change.setDefaultNullValue(defaultValueString);
843                         }
844                     }
845 
846 
847                     changes.add(generateChangeSet(change));
848 					foundDifference = true;
849 				}
850 
851 			}
852 			if (!foundDifference) {
853 				throw new RuntimeException("Unknown difference");
854 			}
855 		}
856 	}
857 
858 	private boolean shouldModifyColumn(Column column) {
859 		return column.getView() == null
860 				&& !referenceSnapshot.getDatabase().isLiquibaseTable(
861 						column.getTable().getName());
862 
863 	}
864 
865 	private void addUnexpectedViewChanges(List<ChangeSet> changes) {
866 		for (View view : getUnexpectedViews()) {
867 
868 			DropViewChange change = new DropViewChange();
869 			change.setViewName(view.getName());
870 			change.setSchemaName(view.getSchema());
871 
872 			changes.add(generateChangeSet(change));
873 		}
874 	}
875 
876 	private void addMissingColumnChanges(List<ChangeSet> changes,
877 			Database database) {
878 		for (Column column : getMissingColumns()) {
879 			if (!shouldModifyColumn(column)) {
880 				continue;
881 			}
882 
883 			AddColumnChange change = new AddColumnChange();
884 			change.setTableName(column.getTable().getName());
885 			change.setSchemaName(column.getTable().getSchema());
886 
887 			ColumnConfig columnConfig = new ColumnConfig();
888 			columnConfig.setName(column.getName());
889 
890 			String dataType = TypeConverterFactory.getInstance().findTypeConverter(database).convertToDatabaseTypeString(column, database);
891 
892 			columnConfig.setType(dataType);
893 
894 			Object defaultValue = column.getDefaultValue();
895 			if (defaultValue != null) {
896 				String defaultValueString = TypeConverterFactory.getInstance()
897 						.findTypeConverter(database).getDataType(defaultValue)
898 						.convertObjectToString(defaultValue, database);
899 				if (defaultValueString != null) {
900 					defaultValueString = defaultValueString.replaceFirst("'",
901 							"").replaceAll("'$", "");
902 				}
903 				columnConfig.setDefaultValue(defaultValueString);
904 			}
905 
906 			if (column.getRemarks() != null) {
907 				columnConfig.setRemarks(column.getRemarks());
908 			}
909             ConstraintsConfig constraintsConfig = columnConfig.getConstraints();
910 			if (column.isNullable() != null && !column.isNullable()) {
911 				if (constraintsConfig == null) {
912 					constraintsConfig = new ConstraintsConfig();
913 				}
914 				constraintsConfig.setNullable(false);
915 			}
916             if (column.isUnique()) {
917 				if (constraintsConfig == null) {
918 					constraintsConfig = new ConstraintsConfig();
919 				}
920 				constraintsConfig.setUnique(true);
921 			}
922 			if (constraintsConfig != null) {
923 				columnConfig.setConstraints(constraintsConfig);
924 			}
925 
926 			change.addColumn(columnConfig);
927 
928 			changes.add(generateChangeSet(change));
929 		}
930 	}
931 
932 	private void addMissingTableChanges(List<ChangeSet> changes,
933                                         Database database) {
934 		for (Table missingTable : getMissingTables()) {
935 			if (referenceSnapshot.getDatabase().isLiquibaseTable(
936 					missingTable.getName())) {
937 				continue;
938 			}
939 
940 			CreateTableChange change = new CreateTableChange();
941 			change.setTableName(missingTable.getName());
942 			change.setSchemaName(missingTable.getSchema());
943 			if (missingTable.getRemarks() != null) {
944 				change.setRemarks(missingTable.getRemarks());
945 			}
946 
947 			for (Column column : missingTable.getColumns()) {
948 				ColumnConfig columnConfig = new ColumnConfig();
949 				columnConfig.setName(column.getName());
950 				columnConfig.setType(TypeConverterFactory.getInstance().findTypeConverter(database).convertToDatabaseTypeString(column, database));
951 
952 				ConstraintsConfig constraintsConfig = null;
953 				if (column.isPrimaryKey()) {
954 					PrimaryKey primaryKey = null;
955 					for (PrimaryKey pk : getMissingPrimaryKeys()) {
956 						if (pk.getTable().getName().equalsIgnoreCase(missingTable.getName())) {
957 							primaryKey = pk;
958 						}
959 					}
960 
961 					if (primaryKey == null || primaryKey.getColumnNamesAsList().size() == 1) {
962 						constraintsConfig = new ConstraintsConfig();
963 						constraintsConfig.setPrimaryKey(true);
964 						constraintsConfig.setPrimaryKeyTablespace(column.getTablespace());
965 
966 						if (primaryKey != null) {
967 							constraintsConfig.setPrimaryKeyName(primaryKey.getName());
968 							getMissingPrimaryKeys().remove(primaryKey);
969 						}
970 					}
971 				}
972 
973 				if (column.isAutoIncrement()) {
974 					columnConfig.setAutoIncrement(true);
975 				}
976 
977 				if (column.isNullable() != null && !column.isNullable()) {
978 					if (constraintsConfig == null) {
979 						constraintsConfig = new ConstraintsConfig();
980 					}
981 
982 					constraintsConfig.setNullable(false);
983 				}
984                 if (column.isUnique()) {
985 					if (constraintsConfig == null) {
986 						constraintsConfig = new ConstraintsConfig();
987 					}
988 					constraintsConfig.setUnique(true);
989 				}
990 				if (constraintsConfig != null) {
991 					columnConfig.setConstraints(constraintsConfig);
992 				}
993 
994 				Object defaultValue = column.getDefaultValue();
995 				if (defaultValue == null) {
996 					// do nothing
997 				} else if (column.isAutoIncrement()) {
998 					// do nothing
999 				} else if (defaultValue instanceof Date) {
1000 					columnConfig.setDefaultValueDate((Date) defaultValue);
1001 				} else if (defaultValue instanceof Boolean) {
1002 					columnConfig.setDefaultValueBoolean(((Boolean) defaultValue));
1003 				} else if (defaultValue instanceof Number) {
1004 					columnConfig.setDefaultValueNumeric(((Number) defaultValue));
1005 				} else if (defaultValue instanceof DatabaseFunction) {
1006 				    columnConfig.setDefaultValueComputed((DatabaseFunction) defaultValue);
1007 				} else {
1008 					columnConfig.setDefaultValue(defaultValue.toString());
1009 				}
1010 
1011 				if (column.getRemarks() != null) {
1012 					columnConfig.setRemarks(column.getRemarks());
1013 				}
1014 
1015 				change.addColumn(columnConfig);
1016 			}
1017 
1018 			changes.add(generateChangeSet(change));
1019 		}
1020 	}
1021 
1022 	private void addUnexpectedTableChanges(List<ChangeSet> changes) {
1023 		for (Table unexpectedTable : getUnexpectedTables()) {
1024 			DropTableChange change = new DropTableChange();
1025 			change.setTableName(unexpectedTable.getName());
1026 			change.setSchemaName(unexpectedTable.getSchema());
1027 
1028 			changes.add(generateChangeSet(change));
1029 		}
1030 	}
1031 
1032 	private void addInsertDataChanges(List<ChangeSet> changeSets, String dataDir)
1033 			throws DatabaseException, IOException {
1034 		try {
1035 			String schema = referenceSnapshot.getSchema();
1036 			for (Table table : referenceSnapshot.getTables()) {
1037 				List<Change> changes = new ArrayList<Change>();
1038 				List<Map> rs = ExecutorService.getInstance().getExecutor(referenceSnapshot.getDatabase()).queryForList(new RawSqlStatement("SELECT * FROM "+ referenceSnapshot.getDatabase().escapeTableName(schema,table.getName())));
1039 
1040 				if (rs.size() == 0) {
1041 					continue;
1042 				}
1043 
1044                 List<String> columnNames = new ArrayList<String>();
1045                 for (Column column : table.getColumns()) {
1046                     columnNames.add(column.getName());
1047                 }
1048 
1049 				// if dataDir is not null, print out a csv file and use loadData
1050 				// tag
1051 				if (dataDir != null) {
1052 					String fileName = table.getName().toLowerCase() + ".csv";
1053 					if (dataDir != null) {
1054 						fileName = dataDir + "/" + fileName;
1055 					}
1056 
1057 					File parentDir = new File(dataDir);
1058 					if (!parentDir.exists()) {
1059 						parentDir.mkdirs();
1060 					}
1061 					if (!parentDir.isDirectory()) {
1062 						throw new RuntimeException(parentDir
1063 								+ " is not a directory");
1064 					}
1065 
1066 					CSVWriter outputFile = new CSVWriter(new FileWriter(
1067 							fileName));
1068 					String[] dataTypes = new String[columnNames.size()];
1069 					String[] line = new String[columnNames.size()];
1070 					for (int i = 0; i < columnNames.size(); i++) {
1071 						line[i] = columnNames.get(i);
1072 					}
1073 					outputFile.writeNext(line);
1074 
1075 					for (Map row : rs) {
1076 						line = new String[columnNames.size()];
1077 
1078 						for (int i = 0; i < columnNames.size(); i++) {
1079 							Object value = row.get(columnNames.get(i).toUpperCase());
1080 							if (dataTypes[i] == null && value != null) {
1081 								if (value instanceof Number) {
1082 									dataTypes[i] = "NUMERIC";
1083 								} else if (value instanceof Boolean) {
1084 									dataTypes[i] = "BOOLEAN";
1085 								} else if (value instanceof Date) {
1086 									dataTypes[i] = "DATE";
1087 								} else {
1088 									dataTypes[i] = "STRING";
1089 								}
1090 							}
1091 							if (value == null) {
1092 								line[i] = "NULL";
1093 							} else {
1094                                 if (value instanceof Date) {
1095                                     line[i] = new ISODateFormat().format(((Date) value));
1096                                 } else {
1097                                     line[i] = value.toString();
1098                                 }
1099                             }
1100 						}
1101 						outputFile.writeNext(line);
1102 					}
1103 					outputFile.flush();
1104 					outputFile.close();
1105 
1106 					LoadDataChange change = new LoadDataChange();
1107 					change.setFile(fileName);
1108 					change.setEncoding("UTF-8");
1109 					change.setSchemaName(schema);
1110 					change.setTableName(table.getName());
1111 
1112 					for (int i = 0; i < columnNames.size(); i++) {
1113 						String colName = columnNames.get(i);
1114 						LoadDataColumnConfig columnConfig = new LoadDataColumnConfig();
1115 						columnConfig.setHeader(colName);
1116 						columnConfig.setName(colName);
1117 						columnConfig.setType(dataTypes[i]);
1118 
1119 						change.addColumn(columnConfig);
1120 					}
1121 
1122 					changes.add(change);
1123 				} else { // if dataDir is null, build and use insert tags
1124 					for (Map row : rs) {
1125 						InsertDataChange change = new InsertDataChange();
1126 						change.setSchemaName(schema);
1127 						change.setTableName(table.getName());
1128 
1129 						// loop over all columns for this row
1130 						for (int i = 0; i < columnNames.size(); i++) {
1131 							ColumnConfig column = new ColumnConfig();
1132 							column.setName(columnNames.get(i));
1133 
1134 							Object value = row.get(columnNames.get(i).toUpperCase());
1135 							if (value == null) {
1136 								column.setValue(null);
1137 							} else if (value instanceof Number) {
1138 								column.setValueNumeric((Number) value);
1139 							} else if (value instanceof Boolean) {
1140 								column.setValueBoolean((Boolean) value);
1141 							} else if (value instanceof Date) {
1142 								column.setValueDate((Date) value);
1143 							} else { // string
1144 								column.setValue(value.toString().replace("\\","\\\\"));
1145 							}
1146 
1147 							change.addColumn(column);
1148 
1149 						}
1150 
1151 						// for each row, add a new change
1152 						// (there will be one group per table)
1153 						changes.add(change);
1154 					}
1155 
1156 				}
1157 				if (changes.size() > 0) {
1158 					ChangeSet changeSet = generateChangeSet();
1159 					for (Change change : changes) {
1160 						changeSet.addChange(change);
1161 					}
1162 					changeSets.add(changeSet);
1163 				}
1164 			}
1165 
1166 		} catch (Exception e) {
1167 			throw new RuntimeException(e);
1168 		}
1169 	}
1170 }