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