001// Copyright 2011 Leo Przybylski. All rights reserved. 002// 003// Redistribution and use in source and binary forms, with or without modification, are 004// permitted provided that the following conditions are met: 005// 006// 1. Redistributions of source code must retain the above copyright notice, this list of 007// conditions and the following disclaimer. 008// 009// 2. Redistributions in binary form must reproduce the above copyright notice, this list 010// of conditions and the following disclaimer in the documentation and/or other materials 011// provided with the distribution. 012// 013// THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR IMPLIED 014// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 015// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR 016// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 017// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 018// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 019// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 020// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 021// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 022// 023// The views and conclusions contained in the software and documentation are those of the 024// authors and should not be interpreted as representing official policies, either expressed 025// or implied, of Leo Przybylski. 026package org.kualigan.tools.liquibase; 027 028import liquibase.change.Change; 029import liquibase.change.ColumnConfig; 030import liquibase.change.ConstraintsConfig; 031import liquibase.change.core.*; 032import liquibase.changelog.ChangeSet; 033import liquibase.database.Database; 034import liquibase.database.jvm.JdbcConnection; 035import liquibase.database.structure.*; 036import liquibase.database.typeconversion.TypeConverter; 037import liquibase.database.typeconversion.TypeConverterFactory; 038import liquibase.diff.*; 039import liquibase.exception.DatabaseException; 040import liquibase.executor.ExecutorService; 041import liquibase.logging.LogFactory; 042import liquibase.parser.core.xml.LiquibaseEntityResolver; 043import liquibase.parser.core.xml.XMLChangeLogSAXParser; 044import liquibase.serializer.ChangeLogSerializer; 045import liquibase.serializer.ChangeLogSerializerFactory; 046import liquibase.serializer.core.xml.XMLChangeLogSerializer; 047import liquibase.snapshot.DatabaseSnapshot; 048import liquibase.statement.DatabaseFunction; 049import liquibase.statement.core.RawSqlStatement; 050import liquibase.statement.ext.DescribeSequenceStatement; 051import liquibase.util.ISODateFormat; 052import liquibase.util.StringUtils; 053import liquibase.util.csv.CSVWriter; 054import liquibase.util.xml.DefaultXmlWriter; 055import liquibase.util.xml.XmlWriter; 056import org.w3c.dom.Document; 057import org.w3c.dom.Element; 058 059import javax.xml.parsers.DocumentBuilder; 060import javax.xml.parsers.DocumentBuilderFactory; 061import javax.xml.parsers.ParserConfigurationException; 062import java.io.*; 063import java.math.BigInteger; 064import java.sql.ResultSet; 065import java.sql.Statement; 066import java.util.*; 067 068public class DiffResult { 069 070 private String idRoot = String.valueOf(new Date().getTime()); 071 private int changeNumber = 1; 072 073 private DatabaseSnapshot referenceSnapshot; 074 private DatabaseSnapshot targetSnapshot; 075 076 private DiffComparison productName; 077 private DiffComparison productVersion; 078 079 private SortedSet<Table> missingTables = new TreeSet<Table>(); 080 private SortedSet<Table> unexpectedTables = new TreeSet<Table>(); 081 082 private SortedSet<View> missingViews = new TreeSet<View>(); 083 private SortedSet<View> unexpectedViews = new TreeSet<View>(); 084 private SortedSet<View> changedViews = new TreeSet<View>(); 085 086 private SortedSet<Column> missingColumns = new TreeSet<Column>(); 087 private SortedSet<Column> unexpectedColumns = new TreeSet<Column>(); 088 private SortedSet<Column> changedColumns = new TreeSet<Column>(); 089 090 private SortedSet<ForeignKey> missingForeignKeys = new TreeSet<ForeignKey>(); 091 private SortedSet<ForeignKey> unexpectedForeignKeys = new TreeSet<ForeignKey>(); 092 093 private SortedSet<Index> missingIndexes = new TreeSet<Index>(); 094 private SortedSet<Index> unexpectedIndexes = new TreeSet<Index>(); 095 096 private SortedSet<PrimaryKey> missingPrimaryKeys = new TreeSet<PrimaryKey>(); 097 private SortedSet<PrimaryKey> unexpectedPrimaryKeys = new TreeSet<PrimaryKey>(); 098 099 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}