1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
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
499
500
501
502 }
503 }
504
505
506
507
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
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
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
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
997 } else if (column.isAutoIncrement()) {
998
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
1050
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 {
1124 for (Map row : rs) {
1125 InsertDataChange change = new InsertDataChange();
1126 change.setSchemaName(schema);
1127 change.setTableName(table.getName());
1128
1129
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 {
1144 column.setValue(value.toString().replace("\\","\\\\"));
1145 }
1146
1147 change.addColumn(column);
1148
1149 }
1150
1151
1152
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 }