Coverage Report - liquibase.snapshot.jvm.JdbcDatabaseSnapshotGenerator
 
Classes in this File Line Coverage Branch Coverage Complexity
JdbcDatabaseSnapshotGenerator
0%
0/459
0%
0/206
5.135
 
 1  
 package liquibase.snapshot.jvm;
 2  
 
 3  
 import java.sql.DatabaseMetaData;
 4  
 import java.sql.ResultSet;
 5  
 import java.sql.ResultSetMetaData;
 6  
 import java.sql.SQLException;
 7  
 import java.sql.Statement;
 8  
 import java.sql.Types;
 9  
 import java.text.ParseException;
 10  
 import java.util.ArrayList;
 11  
 import java.util.HashMap;
 12  
 import java.util.HashSet;
 13  
 import java.util.List;
 14  
 import java.util.Map;
 15  
 import java.util.Set;
 16  
 
 17  
 import liquibase.database.Database;
 18  
 import liquibase.database.core.InformixDatabase;
 19  
 import liquibase.database.core.OracleDatabase;
 20  
 import liquibase.database.jvm.JdbcConnection;
 21  
 import liquibase.database.structure.Column;
 22  
 import liquibase.database.structure.ForeignKey;
 23  
 import liquibase.database.structure.ForeignKeyConstraintType;
 24  
 import liquibase.database.structure.ForeignKeyInfo;
 25  
 import liquibase.database.structure.Index;
 26  
 import liquibase.database.structure.PrimaryKey;
 27  
 import liquibase.database.structure.Sequence;
 28  
 import liquibase.database.structure.Table;
 29  
 import liquibase.database.structure.UniqueConstraint;
 30  
 import liquibase.database.structure.View;
 31  
 import liquibase.database.typeconversion.TypeConverterFactory;
 32  
 import liquibase.diff.DiffStatusListener;
 33  
 import liquibase.exception.DatabaseException;
 34  
 import liquibase.exception.UnexpectedLiquibaseException;
 35  
 import liquibase.executor.ExecutorService;
 36  
 import liquibase.logging.LogFactory;
 37  
 import liquibase.snapshot.DatabaseSnapshot;
 38  
 import liquibase.snapshot.DatabaseSnapshotGenerator;
 39  
 import liquibase.statement.core.GetViewDefinitionStatement;
 40  
 import liquibase.statement.core.SelectSequencesStatement;
 41  
 import liquibase.util.StringUtils;
 42  
 
 43  0
 public abstract class JdbcDatabaseSnapshotGenerator implements DatabaseSnapshotGenerator {
 44  
 
 45  
     private Set<DiffStatusListener> statusListeners;
 46  
 
 47  
     protected String convertTableNameToDatabaseTableName(String tableName) {
 48  0
         return tableName;
 49  
     }
 50  
 
 51  
     protected String convertColumnNameToDatabaseTableName(String columnName) {
 52  0
         return columnName;
 53  
     }
 54  
 
 55  
     @Override
 56  
     public Table getDatabaseChangeLogTable(Database database) throws DatabaseException {
 57  0
         return getTable(database.getLiquibaseSchemaName(), database.getDatabaseChangeLogTableName(), database);
 58  
     }
 59  
 
 60  
     @Override
 61  
     public Table getDatabaseChangeLogLockTable(Database database) throws DatabaseException {
 62  0
         return getTable(database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName(), database);
 63  
     }
 64  
 
 65  
     @Override
 66  
     public boolean hasDatabaseChangeLogTable(Database database) {
 67  0
         return hasTable(database.getLiquibaseSchemaName(), database.getDatabaseChangeLogTableName(), database);
 68  
     }
 69  
 
 70  
     @Override
 71  
     public boolean hasDatabaseChangeLogLockTable(Database database) {
 72  0
         return hasTable(database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName(), database);
 73  
     }
 74  
 
 75  
     @Override
 76  
     public boolean hasTable(String schemaName, String tableName, Database database) {
 77  
         try {
 78  0
             ResultSet rs = getMetaData(database).getTables(database.convertRequestedSchemaToCatalog(schemaName),
 79  
                     database.convertRequestedSchemaToSchema(schemaName),
 80  
                     convertTableNameToDatabaseTableName(tableName), new String[] { "TABLE" });
 81  
             try {
 82  0
                 return rs.next();
 83  
             } finally {
 84  0
                 try {
 85  0
                     rs.close();
 86  0
                 } catch (SQLException ignore) {
 87  0
                 }
 88  
             }
 89  0
         } catch (Exception e) {
 90  0
             throw new UnexpectedLiquibaseException(e);
 91  
         }
 92  
     }
 93  
 
 94  
     @Override
 95  
     public boolean hasView(String schemaName, String viewName, Database database) {
 96  
         try {
 97  0
             ResultSet rs = getMetaData(database).getTables(database.convertRequestedSchemaToCatalog(schemaName),
 98  
                     database.convertRequestedSchemaToSchema(schemaName), convertTableNameToDatabaseTableName(viewName),
 99  
                     new String[] { "VIEW" });
 100  
             try {
 101  0
                 return rs.next();
 102  
             } finally {
 103  0
                 try {
 104  0
                     rs.close();
 105  0
                 } catch (SQLException ignore) {
 106  0
                 }
 107  
             }
 108  0
         } catch (Exception e) {
 109  0
             throw new UnexpectedLiquibaseException(e);
 110  
         }
 111  
     }
 112  
 
 113  
     @Override
 114  
     public Table getTable(String schemaName, String tableName, Database database) throws DatabaseException {
 115  0
         ResultSet rs = null;
 116  
         try {
 117  0
             DatabaseMetaData metaData = getMetaData(database);
 118  0
             rs = metaData.getTables(database.convertRequestedSchemaToCatalog(schemaName),
 119  
                     database.convertRequestedSchemaToSchema(schemaName),
 120  
                     convertTableNameToDatabaseTableName(tableName), new String[] { "TABLE" });
 121  
 
 122  
             Table table;
 123  
             try {
 124  0
                 if (!rs.next()) {
 125  0
                     return null;
 126  
                 }
 127  
 
 128  0
                 table = readTable(rs, database);
 129  
             } finally {
 130  0
                 rs.close();
 131  0
             }
 132  
 
 133  0
             rs = metaData.getColumns(database.convertRequestedSchemaToCatalog(schemaName),
 134  
                     database.convertRequestedSchemaToSchema(schemaName),
 135  
                     convertTableNameToDatabaseTableName(tableName), null);
 136  
             try {
 137  0
                 while (rs.next()) {
 138  0
                     table.getColumns().add(readColumn(rs, database));
 139  
                 }
 140  
             } finally {
 141  0
                 rs.close();
 142  0
             }
 143  
 
 144  0
             return table;
 145  0
         } catch (Exception e) {
 146  0
             throw new DatabaseException(e);
 147  
         } finally {
 148  0
             if (rs != null) {
 149  
                 try {
 150  0
                     rs.close();
 151  0
                 } catch (SQLException ignore) {
 152  0
                 }
 153  
             }
 154  
         }
 155  
     }
 156  
 
 157  
     @Override
 158  
     public Column getColumn(String schemaName, String tableName, String columnName, Database database)
 159  
             throws DatabaseException {
 160  0
         ResultSet rs = null;
 161  
         try {
 162  0
             rs = getMetaData(database).getColumns(database.convertRequestedSchemaToCatalog(schemaName),
 163  
                     database.convertRequestedSchemaToSchema(schemaName),
 164  
                     convertTableNameToDatabaseTableName(tableName), convertColumnNameToDatabaseTableName(columnName));
 165  
 
 166  0
             if (!rs.next()) {
 167  0
                 return null;
 168  
             }
 169  
 
 170  0
             return readColumn(rs, database);
 171  0
         } catch (Exception e) {
 172  0
             throw new DatabaseException(e);
 173  
         } finally {
 174  0
             if (rs != null) {
 175  
                 try {
 176  0
                     rs.close();
 177  0
                 } catch (SQLException ignore) {
 178  0
                 }
 179  
             }
 180  
         }
 181  
     }
 182  
 
 183  
     private Table readTable(ResultSet rs, Database database) throws SQLException {
 184  0
         String name = convertFromDatabaseName(rs.getString("TABLE_NAME"));
 185  0
         String schemaName = convertFromDatabaseName(rs.getString("TABLE_SCHEM"));
 186  0
         String remarks = rs.getString("REMARKS");
 187  
 
 188  0
         Table table = new Table(name);
 189  0
         table.setRemarks(StringUtils.trimToNull(remarks));
 190  0
         table.setDatabase(database);
 191  0
         table.setSchema(schemaName);
 192  0
         table.setRawSchemaName(rs.getString("TABLE_SCHEM"));
 193  0
         table.setRawCatalogName(rs.getString("TABLE_CAT"));
 194  
 
 195  0
         return table;
 196  
     }
 197  
 
 198  
     private View readView(ResultSet rs, Database database) throws SQLException, DatabaseException {
 199  0
         String name = convertFromDatabaseName(rs.getString("TABLE_NAME"));
 200  0
         String schemaName = convertFromDatabaseName(rs.getString("TABLE_SCHEM"));
 201  
 
 202  0
         View view = new View();
 203  0
         view.setName(name);
 204  0
         view.setSchema(schemaName);
 205  0
         view.setRawSchemaName(rs.getString("TABLE_SCHEM"));
 206  0
         view.setRawCatalogName(rs.getString("TABLE_CAT"));
 207  
         try {
 208  0
             view.setDefinition(database.getViewDefinition(rs.getString("TABLE_SCHEM"), name));
 209  0
         } catch (DatabaseException e) {
 210  0
             throw new DatabaseException("Error getting " + database.getConnection().getURL() + " view with "
 211  
                     + new GetViewDefinitionStatement(view.getSchema(), name), e);
 212  0
         }
 213  
 
 214  0
         return view;
 215  
     }
 216  
 
 217  
     private Column readColumn(ResultSet rs, Database database) throws SQLException, DatabaseException {
 218  0
         Column column = new Column();
 219  
 
 220  0
         String tableName = convertFromDatabaseName(rs.getString("TABLE_NAME"));
 221  0
         String columnName = convertFromDatabaseName(rs.getString("COLUMN_NAME"));
 222  0
         String schemaName = convertFromDatabaseName(rs.getString("TABLE_SCHEM"));
 223  0
         String catalogName = convertFromDatabaseName(rs.getString("TABLE_CAT"));
 224  0
         String remarks = rs.getString("REMARKS");
 225  
 
 226  0
         if (database.isSystemTable(catalogName, schemaName, tableName)
 227  
                 || database.isSystemView(catalogName, schemaName, tableName)) {
 228  0
             return null;
 229  
         }
 230  
 
 231  0
         column.setName(columnName);
 232  
 
 233  0
         Table table = new Table(tableName);
 234  0
         table.setSchema(schemaName);
 235  0
         column.setTable(table);
 236  
 
 237  0
         configureColumnType(column, rs);
 238  
 
 239  0
         int nullable = rs.getInt("NULLABLE");
 240  0
         if (nullable == DatabaseMetaData.columnNoNulls) {
 241  0
             column.setNullable(false);
 242  0
         } else if (nullable == DatabaseMetaData.columnNullable) {
 243  0
             column.setNullable(true);
 244  
         }
 245  
 
 246  0
         getColumnTypeAndDefValue(column, rs, database);
 247  0
         column.setRemarks(remarks);
 248  
 
 249  0
         return column;
 250  
     }
 251  
 
 252  
     /**
 253  
      * Configuration of column's type.
 254  
      * 
 255  
      * @param column
 256  
      *            Column to configure
 257  
      * @param rs
 258  
      *            Result set, used as a property resource.
 259  
      * @throws java.sql.SQLException
 260  
      *             wrong Result Set content
 261  
      * */
 262  
     protected void configureColumnType(Column column, ResultSet rs) throws SQLException {
 263  0
         column.setDataType(rs.getInt("DATA_TYPE"));
 264  0
         column.setColumnSize(rs.getInt("COLUMN_SIZE"));
 265  0
         column.setDecimalDigits(rs.getInt("DECIMAL_DIGITS"));
 266  
 
 267  
         // Set true, if precision should be initialize
 268  0
         column.setInitPrecision(!((column.getDataType() == Types.DECIMAL || column.getDataType() == Types.NUMERIC || column
 269  
                 .getDataType() == Types.REAL) && rs.getString("DECIMAL_DIGITS") == null));
 270  0
     }
 271  
 
 272  
     @Override
 273  
     public DatabaseSnapshot createSnapshot(Database database, String requestedSchema, Set<DiffStatusListener> listeners)
 274  
             throws DatabaseException {
 275  
 
 276  0
         if (requestedSchema == null) {
 277  0
             requestedSchema = database.getDefaultSchemaName();
 278  
         }
 279  
 
 280  
         try {
 281  
 
 282  0
             DatabaseMetaData databaseMetaData = getMetaData(database);
 283  0
             this.statusListeners = listeners;
 284  
 
 285  0
             DatabaseSnapshot snapshot = new DatabaseSnapshot(database, requestedSchema);
 286  
 
 287  0
             readTables(snapshot, requestedSchema, databaseMetaData);
 288  0
             readViews(snapshot, requestedSchema, databaseMetaData);
 289  0
             readForeignKeyInformation(snapshot, requestedSchema, databaseMetaData);
 290  0
             readPrimaryKeys(snapshot, requestedSchema, databaseMetaData);
 291  0
             readColumns(snapshot, requestedSchema, databaseMetaData);
 292  0
             readUniqueConstraints(snapshot, requestedSchema, databaseMetaData);
 293  0
             readIndexes(snapshot, requestedSchema, databaseMetaData);
 294  0
             readSequences(snapshot, requestedSchema, databaseMetaData);
 295  
 
 296  0
             return snapshot;
 297  0
         } catch (SQLException e) {
 298  0
             throw new DatabaseException(e);
 299  
         }
 300  
     }
 301  
 
 302  
     protected DatabaseMetaData getMetaData(Database database) throws SQLException {
 303  0
         DatabaseMetaData databaseMetaData = null;
 304  0
         if (database.getConnection() != null) {
 305  0
             databaseMetaData = ((JdbcConnection) database.getConnection()).getUnderlyingConnection().getMetaData();
 306  
         }
 307  0
         return databaseMetaData;
 308  
     }
 309  
 
 310  
     protected void readTables(DatabaseSnapshot snapshot, String schema, DatabaseMetaData databaseMetaData)
 311  
             throws SQLException, DatabaseException {
 312  0
         Database database = snapshot.getDatabase();
 313  0
         updateListeners("Reading tables for " + database.toString() + " ...");
 314  
 
 315  0
         ResultSet rs = databaseMetaData.getTables(database.convertRequestedSchemaToCatalog(schema),
 316  
                 database.convertRequestedSchemaToSchema(schema), null, new String[] { "TABLE", "ALIAS" });
 317  
         try {
 318  0
             while (rs.next()) {
 319  0
                 Table table = readTable(rs, database);
 320  0
                 table.setSchema(schema); // not always set for some reason
 321  0
                 if (database.isLiquibaseTable(table.getName())) {
 322  0
                     if (table.getName().equalsIgnoreCase(database.getDatabaseChangeLogTableName())) {
 323  0
                         snapshot.setDatabaseChangeLogTable(table);
 324  0
                         continue;
 325  
                     }
 326  
 
 327  0
                     if (table.getName().equalsIgnoreCase(database.getDatabaseChangeLogLockTableName())) {
 328  0
                         snapshot.setDatabaseChangeLogLockTable(table);
 329  0
                         continue;
 330  
                     }
 331  
                 }
 332  0
                 if (database.isSystemTable(table.getRawCatalogName(), table.getRawSchemaName(), table.getName())
 333  
                         || database.isSystemView(table.getRawCatalogName(), table.getRawSchemaName(), table.getName())) {
 334  0
                     continue;
 335  
                 }
 336  
 
 337  0
                 snapshot.getTables().add(table);
 338  0
             }
 339  
         } finally {
 340  0
             try {
 341  0
                 rs.close();
 342  0
             } catch (SQLException ignore) {
 343  0
             }
 344  0
         }
 345  0
     }
 346  
 
 347  
     protected void readViews(DatabaseSnapshot snapshot, String schema, DatabaseMetaData databaseMetaData)
 348  
             throws SQLException, DatabaseException {
 349  0
         Database database = snapshot.getDatabase();
 350  0
         updateListeners("Reading views for " + database.toString() + " ...");
 351  
 
 352  0
         ResultSet rs = databaseMetaData.getTables(database.convertRequestedSchemaToCatalog(schema),
 353  
                 database.convertRequestedSchemaToSchema(schema), null, new String[] { "VIEW" });
 354  
         try {
 355  0
             while (rs.next()) {
 356  0
                 View view = readView(rs, database);
 357  0
                 if (database.isSystemView(view.getRawCatalogName(), view.getRawSchemaName(), view.getName())) {
 358  0
                     continue;
 359  
                 }
 360  
 
 361  0
                 snapshot.getViews().add(view);
 362  0
             }
 363  
         } finally {
 364  0
             try {
 365  0
                 rs.close();
 366  0
             } catch (SQLException ignore) {
 367  0
             }
 368  0
         }
 369  0
     }
 370  
 
 371  
     protected String convertFromDatabaseName(String objectName) {
 372  0
         if (objectName == null) {
 373  0
             return null;
 374  
         }
 375  0
         return objectName;
 376  
     }
 377  
 
 378  
     protected void readColumns(DatabaseSnapshot snapshot, String schema, DatabaseMetaData databaseMetaData)
 379  
             throws SQLException, DatabaseException {
 380  0
         Database database = snapshot.getDatabase();
 381  0
         updateListeners("Reading columns for " + database.toString() + " ...");
 382  
 
 383  0
         Statement selectStatement = null;
 384  0
         ResultSet rs = null;
 385  
         try {
 386  0
             selectStatement = ((JdbcConnection) database.getConnection()).getUnderlyingConnection().createStatement();
 387  0
             rs = databaseMetaData.getColumns(database.convertRequestedSchemaToCatalog(schema),
 388  
                     database.convertRequestedSchemaToSchema(schema), null, null);
 389  0
             while (rs.next()) {
 390  0
                 Column column = readColumn(rs, database);
 391  
 
 392  0
                 if (column == null) {
 393  0
                     continue;
 394  
                 }
 395  
 
 396  
                 // replace temp table in column with real table
 397  0
                 Table tempTable = column.getTable();
 398  0
                 column.setTable(null);
 399  
 
 400  
                 Table table;
 401  0
                 if (database.isLiquibaseTable(tempTable.getName())) {
 402  0
                     if (tempTable.getName().equalsIgnoreCase(database.getDatabaseChangeLogTableName())) {
 403  0
                         table = snapshot.getDatabaseChangeLogTable();
 404  0
                     } else if (tempTable.getName().equalsIgnoreCase(database.getDatabaseChangeLogLockTableName())) {
 405  0
                         table = snapshot.getDatabaseChangeLogLockTable();
 406  
                     } else {
 407  0
                         throw new UnexpectedLiquibaseException("Unknown liquibase table: " + tempTable.getName());
 408  
                     }
 409  
                 } else {
 410  0
                     table = snapshot.getTable(tempTable.getName());
 411  
                 }
 412  0
                 if (table == null) {
 413  0
                     View view = snapshot.getView(tempTable.getName());
 414  0
                     if (view == null) {
 415  0
                         LogFactory.getLogger().debug(
 416  
                                 "Could not find table or view " + tempTable.getName() + " for column "
 417  
                                         + column.getName());
 418  0
                         continue;
 419  
                     } else {
 420  0
                         column.setView(view);
 421  0
                         column.setAutoIncrement(false);
 422  0
                         view.getColumns().add(column);
 423  
                     }
 424  0
                 } else {
 425  0
                     column.setTable(table);
 426  0
                     column.setAutoIncrement(isColumnAutoIncrement(database, table.getSchema(), table.getName(),
 427  
                             column.getName()));
 428  0
                     table.getColumns().add(column);
 429  
                 }
 430  
 
 431  0
                 column.setPrimaryKey(snapshot.isPrimaryKey(column));
 432  0
             }
 433  
         } finally {
 434  0
             if (rs != null) {
 435  
                 try {
 436  0
                     rs.close();
 437  0
                 } catch (SQLException ignored) {
 438  0
                 }
 439  
             }
 440  0
             if (selectStatement != null) {
 441  
                 try {
 442  0
                     selectStatement.close();
 443  0
                 } catch (SQLException ignored) {
 444  0
                 }
 445  
             }
 446  
         }
 447  0
     }
 448  
 
 449  
     /**
 450  
      * Method assigns correct column type and default value to Column object.
 451  
      * <p/>
 452  
      * This method should be database engine specific. JDBC implementation requires database engine vendors to convert
 453  
      * native DB types to java objects. During conversion some metadata information are being lost or reported
 454  
      * incorrectly via DatabaseMetaData objects. This method, if necessary, must be overriden. It must go below
 455  
      * DatabaseMetaData implementation and talk directly to database to get correct metadata information.
 456  
      */
 457  
     protected void getColumnTypeAndDefValue(Column columnInfo, ResultSet rs, Database database) throws SQLException,
 458  
             DatabaseException {
 459  0
         Object defaultValue = rs.getObject("COLUMN_DEF");
 460  
         try {
 461  0
             columnInfo.setDefaultValue(TypeConverterFactory
 462  
                     .getInstance()
 463  
                     .findTypeConverter(database)
 464  
                     .convertDatabaseValueToObject(defaultValue, columnInfo.getDataType(), columnInfo.getColumnSize(),
 465  
                             columnInfo.getDecimalDigits(), database));
 466  0
         } catch (ParseException e) {
 467  0
             throw new DatabaseException(e);
 468  0
         }
 469  0
         columnInfo.setTypeName(TypeConverterFactory.getInstance().findTypeConverter(database)
 470  
                 .getDataType(rs.getString("TYPE_NAME"), columnInfo.isAutoIncrement()).toString());
 471  0
     } // end of method getColumnTypeAndDefValue()
 472  
 
 473  
     protected void readForeignKeyInformation(DatabaseSnapshot snapshot, String schema, DatabaseMetaData databaseMetaData)
 474  
             throws DatabaseException, SQLException {
 475  0
         Database database = snapshot.getDatabase();
 476  0
         updateListeners("Reading foreign keys for " + database.toString() + " ...");
 477  
 
 478  0
         String dbSchema = database.convertRequestedSchemaToSchema(schema);
 479  
         // First we try to find all database-specific FKs.
 480  
         // TODO: there are some filters bellow in for loop. Are they needed here too?
 481  0
         snapshot.getForeignKeys().addAll(getAdditionalForeignKeys(dbSchema, database));
 482  
 
 483  
         // Then tries to find all other standard FKs
 484  0
         for (Table table : snapshot.getTables()) {
 485  0
             for (ForeignKey fk : getForeignKeys(schema, table.getName(), snapshot.getDatabase())) {
 486  
 
 487  0
                 Table tempPKTable = fk.getPrimaryKeyTable();
 488  0
                 Table pkTable = snapshot.getTable(tempPKTable.getName());
 489  0
                 if (pkTable == null) {
 490  0
                     LogFactory
 491  
                             .getLogger()
 492  
                             .warning(
 493  
                                     "Foreign key "
 494  
                                             + fk.getName()
 495  
                                             + " references table "
 496  
                                             + tempPKTable
 497  
                                             + ", which is in a different schema. Retaining FK in diff, but table will not be diffed.");
 498  
                 }
 499  
 
 500  0
                 Table tempFkTable = fk.getForeignKeyTable();
 501  0
                 Table fkTable = snapshot.getTable(tempFkTable.getName());
 502  0
                 if (fkTable == null) {
 503  0
                     LogFactory.getLogger().warning(
 504  
                             "Foreign key " + fk.getName() + " is in table " + tempFkTable
 505  
                                     + ", which we cannot find. Ignoring.");
 506  0
                     continue;
 507  
                 }
 508  
 
 509  0
                 snapshot.getForeignKeys().add(fk);
 510  0
             }
 511  
         }
 512  0
     }
 513  
 
 514  
     @Override
 515  
     public boolean hasIndex(String schemaName, String tableName, String indexName, Database database, String columnNames)
 516  
             throws DatabaseException {
 517  0
         DatabaseSnapshot databaseSnapshot = createSnapshot(database, schemaName, null);
 518  0
         if (databaseSnapshot.getIndex(indexName) != null) {
 519  0
             return true;
 520  
         }
 521  0
         if (tableName != null && columnNames != null) {
 522  0
             for (Index index : databaseSnapshot.getIndexes()) {
 523  0
                 if (index.getColumnNames().replaceAll("\\s+", "").equalsIgnoreCase(columnNames.replaceAll("\\s+", ""))) {
 524  0
                     return true;
 525  
                 }
 526  
             }
 527  
         }
 528  0
         return false;
 529  
     }
 530  
 
 531  
     @Override
 532  
     public ForeignKey getForeignKeyByForeignKeyTable(String schemaName, String foreignKeyTableName, String fkName,
 533  
             Database database) throws DatabaseException {
 534  0
         for (ForeignKey fk : getForeignKeys(schemaName, foreignKeyTableName, database)) {
 535  0
             if (fk.getName().equalsIgnoreCase(fkName)) {
 536  0
                 return fk;
 537  
             }
 538  
         }
 539  
 
 540  0
         return null;
 541  
     }
 542  
 
 543  
     /**
 544  
      * Generation of Foreign Key based on information about it.
 545  
      * 
 546  
      * @param fkInfo
 547  
      *            contains all needed properties of FK
 548  
      * @param database
 549  
      *            current database
 550  
      * @param fkList
 551  
      *            list of already generated keys
 552  
      * @return generated Foreing Key
 553  
      * @throws liquibase.exception.DatabaseException
 554  
      *             Database Exception
 555  
      * */
 556  
     public ForeignKey generateForeignKey(ForeignKeyInfo fkInfo, Database database, List<ForeignKey> fkList)
 557  
             throws DatabaseException {
 558  
         // Simple (non-composite) keys have KEY_SEQ=1, so create the ForeignKey.
 559  
         // In case of subsequent parts of composite keys (KEY_SEQ>1) don't create new instance, just reuse the one from
 560  
         // previous call.
 561  
         // According to #getExportedKeys() contract, the result set rows are properly sorted, so the reuse of previous
 562  
         // FK instance is safe.
 563  0
         ForeignKey foreignKey = null;
 564  
 
 565  0
         if (fkInfo.getKeySeq() == 1 || (fkInfo.getReferencesUniqueColumn() && fkInfo.getKeySeq() == 0)) {
 566  0
             foreignKey = new ForeignKey();
 567  
         } else {
 568  0
             for (ForeignKey foundFK : fkList) {
 569  0
                 if (foundFK.getName().equalsIgnoreCase(fkInfo.getFkName())) {
 570  0
                     foreignKey = foundFK;
 571  
                 }
 572  
             }
 573  0
             if (foreignKey == null) {
 574  0
                 throw new DatabaseException("Database returned out of sequence foreign key column for "
 575  
                         + fkInfo.getFkName());
 576  
             }
 577  
         }
 578  
 
 579  0
         foreignKey.setName(fkInfo.getFkName());
 580  
 
 581  0
         final Table pkTable = new Table(fkInfo.getPkTableName());
 582  0
         pkTable.setSchema(fkInfo.getPkTableSchema());
 583  0
         foreignKey.setPrimaryKeyTable(pkTable);
 584  0
         foreignKey.addPrimaryKeyColumn(fkInfo.getPkColumn());
 585  
 
 586  0
         final String fkTableName = fkInfo.getFkTableName();
 587  0
         Table fkTable = new Table(fkTableName);
 588  0
         fkTable.setSchema(fkInfo.getFkSchema());
 589  0
         foreignKey.setForeignKeyTable(fkTable);
 590  0
         foreignKey.addForeignKeyColumn(fkInfo.getFkColumn());
 591  
 
 592  0
         foreignKey.setUpdateRule(fkInfo.getUpdateRule());
 593  0
         foreignKey.setDeleteRule(fkInfo.getDeleteRule());
 594  
 
 595  0
         foreignKey.setReferencesUniqueColumn(fkInfo.getReferencesUniqueColumn());
 596  
 
 597  0
         if (database.supportsInitiallyDeferrableColumns()) {
 598  
 
 599  0
             if (fkInfo.getDeferrablility() == DatabaseMetaData.importedKeyInitiallyDeferred) {
 600  0
                 foreignKey.setDeferrable(Boolean.TRUE);
 601  0
                 foreignKey.setInitiallyDeferred(Boolean.TRUE);
 602  0
             } else if (fkInfo.getDeferrablility() == DatabaseMetaData.importedKeyInitiallyImmediate) {
 603  0
                 foreignKey.setDeferrable(Boolean.TRUE);
 604  0
                 foreignKey.setInitiallyDeferred(Boolean.FALSE);
 605  0
             } else if (fkInfo.getDeferrablility() == DatabaseMetaData.importedKeyNotDeferrable) {
 606  0
                 foreignKey.setDeferrable(Boolean.FALSE);
 607  0
                 foreignKey.setInitiallyDeferred(Boolean.FALSE);
 608  
             }
 609  
         }
 610  
 
 611  0
         return foreignKey;
 612  
     }
 613  
 
 614  
     /**
 615  
      * It finds <u>only</u> all database-specific Foreign Keys. By default it returns an empty ArrayList.
 616  
      * 
 617  
      * @param schemaName
 618  
      *            current shemaName
 619  
      * @param database
 620  
      *            current database
 621  
      * @return list of database-specific Foreing Keys
 622  
      * @throws liquibase.exception.DatabaseException
 623  
      *             any kinds of SQLException errors
 624  
      * */
 625  
     public List<ForeignKey> getAdditionalForeignKeys(String schemaName, Database database) throws DatabaseException {
 626  0
         return new ArrayList<ForeignKey>();
 627  
     }
 628  
 
 629  
     @Override
 630  
     public List<ForeignKey> getForeignKeys(String schemaName, String foreignKeyTableName, Database database)
 631  
             throws DatabaseException {
 632  0
         List<ForeignKey> fkList = new ArrayList<ForeignKey>();
 633  
         try {
 634  0
             String dbCatalog = database.convertRequestedSchemaToCatalog(schemaName);
 635  0
             String dbSchema = database.convertRequestedSchemaToSchema(schemaName);
 636  0
             ResultSet rs = getMetaData(database).getImportedKeys(dbCatalog, dbSchema,
 637  
                     convertTableNameToDatabaseTableName(foreignKeyTableName));
 638  
 
 639  
             try {
 640  0
                 while (rs.next()) {
 641  0
                     ForeignKeyInfo fkInfo = fillForeignKeyInfo(rs);
 642  
 
 643  0
                     fkList.add(generateForeignKey(fkInfo, database, fkList));
 644  0
                 }
 645  
             } finally {
 646  0
                 rs.close();
 647  0
             }
 648  
 
 649  0
             return fkList;
 650  
 
 651  0
         } catch (Exception e) {
 652  0
             throw new DatabaseException(e);
 653  
         }
 654  
     }
 655  
 
 656  
     /**
 657  
      * Fill foreign key information from the current register of a getImportedKeys resultset
 658  
      * 
 659  
      * @param rs
 660  
      *            The resultset returned by getImportedKeys
 661  
      * @return Foreign key information
 662  
      */
 663  
     protected ForeignKeyInfo fillForeignKeyInfo(ResultSet rs) throws DatabaseException, SQLException {
 664  0
         ForeignKeyInfo fkInfo = new ForeignKeyInfo();
 665  0
         fkInfo.setFkName(convertFromDatabaseName(rs.getString("FK_NAME")));
 666  0
         fkInfo.setFkSchema(convertFromDatabaseName(rs.getString("FKTABLE_SCHEM")));
 667  0
         fkInfo.setFkTableName(convertFromDatabaseName(rs.getString("FKTABLE_NAME")));
 668  0
         fkInfo.setFkColumn(convertFromDatabaseName(rs.getString("FKCOLUMN_NAME")));
 669  0
         fkInfo.setPkTableSchema(rs.getString("PKTABLE_SCHEM"));
 670  0
         fkInfo.setPkTableName(convertFromDatabaseName(rs.getString("PKTABLE_NAME")));
 671  0
         fkInfo.setPkColumn(convertFromDatabaseName(rs.getString("PKCOLUMN_NAME")));
 672  0
         fkInfo.setKeySeq(rs.getInt("KEY_SEQ"));
 673  0
         ForeignKeyConstraintType updateRule = convertToForeignKeyConstraintType(rs.getInt("UPDATE_RULE"));
 674  0
         if (rs.wasNull()) {
 675  0
             updateRule = null;
 676  
         }
 677  0
         fkInfo.setUpdateRule(updateRule);
 678  0
         ForeignKeyConstraintType deleteRule = convertToForeignKeyConstraintType(rs.getInt("DELETE_RULE"));
 679  0
         if (rs.wasNull()) {
 680  0
             deleteRule = null;
 681  
         }
 682  0
         fkInfo.setDeleteRule(deleteRule);
 683  0
         fkInfo.setDeferrablility(rs.getShort("DEFERRABILITY"));
 684  0
         return fkInfo;
 685  
     }
 686  
 
 687  
     protected ForeignKeyConstraintType convertToForeignKeyConstraintType(int jdbcType) throws DatabaseException {
 688  0
         if (jdbcType == DatabaseMetaData.importedKeyCascade) {
 689  0
             return ForeignKeyConstraintType.importedKeyCascade;
 690  0
         } else if (jdbcType == DatabaseMetaData.importedKeyNoAction) {
 691  0
             return ForeignKeyConstraintType.importedKeyNoAction;
 692  0
         } else if (jdbcType == DatabaseMetaData.importedKeyRestrict) {
 693  0
             return ForeignKeyConstraintType.importedKeyRestrict;
 694  0
         } else if (jdbcType == DatabaseMetaData.importedKeySetDefault) {
 695  0
             return ForeignKeyConstraintType.importedKeySetDefault;
 696  0
         } else if (jdbcType == DatabaseMetaData.importedKeySetNull) {
 697  0
             return ForeignKeyConstraintType.importedKeySetNull;
 698  
         } else {
 699  0
             throw new DatabaseException("Unknown constraint type: " + jdbcType);
 700  
         }
 701  
     }
 702  
 
 703  
     protected void readIndexes(DatabaseSnapshot snapshot, String schema, DatabaseMetaData databaseMetaData)
 704  
             throws DatabaseException, SQLException {
 705  0
         Database database = snapshot.getDatabase();
 706  0
         updateListeners("Reading indexes for " + database.toString() + " ...");
 707  
 
 708  0
         for (Table table : snapshot.getTables()) {
 709  0
             ResultSet rs = null;
 710  0
             Statement statement = null;
 711  
             try {
 712  0
                 if (database instanceof OracleDatabase) {
 713  
                     // oracle getIndexInfo is buggy and slow. See Issue 1824548 and
 714  
                     // http://forums.oracle.com/forums/thread.jspa?messageID=578383&#578383
 715  0
                     statement = ((JdbcConnection) database.getConnection()).getUnderlyingConnection().createStatement();
 716  0
                     String sql = "SELECT INDEX_NAME, 3 AS TYPE, TABLE_NAME, COLUMN_NAME, COLUMN_POSITION AS ORDINAL_POSITION, null AS FILTER_CONDITION FROM ALL_IND_COLUMNS WHERE TABLE_OWNER='"
 717  
                             + database.convertRequestedSchemaToSchema(schema)
 718  
                             + "' AND TABLE_NAME='"
 719  
                             + table.getName()
 720  
                             + "' ORDER BY INDEX_NAME, ORDINAL_POSITION";
 721  0
                     rs = statement.executeQuery(sql);
 722  0
                 } else {
 723  0
                     rs = databaseMetaData.getIndexInfo(database.convertRequestedSchemaToCatalog(schema),
 724  
                             database.convertRequestedSchemaToSchema(schema), table.getName(), false, true);
 725  
                 }
 726  0
                 Map<String, Index> indexMap = new HashMap<String, Index>();
 727  0
                 while (rs.next()) {
 728  0
                     String indexName = convertFromDatabaseName(rs.getString("INDEX_NAME"));
 729  
                     /*
 730  
                      * TODO Informix generates indexnames with a leading blank if no name given. An identifier with a
 731  
                      * leading blank is not allowed. So here is it replaced.
 732  
                      */
 733  0
                     if (database instanceof InformixDatabase && indexName.startsWith(" ")) {
 734  0
                         indexName = "_generated_index_" + indexName.substring(1);
 735  
                     }
 736  0
                     short type = rs.getShort("TYPE");
 737  
                     // String tableName = rs.getString("TABLE_NAME");
 738  0
                     boolean nonUnique = true;
 739  
                     try {
 740  0
                         nonUnique = rs.getBoolean("NON_UNIQUE");
 741  0
                     } catch (SQLException e) {
 742  
                         // doesn't exist in all databases
 743  0
                     }
 744  0
                     String columnName = convertFromDatabaseName(rs.getString("COLUMN_NAME"));
 745  0
                     short position = rs.getShort("ORDINAL_POSITION");
 746  
                     /*
 747  
                      * TODO maybe bug in jdbc driver? Need to investigate. If this "if" is commented out
 748  
                      * ArrayOutOfBoundsException is thrown because it tries to access an element -1 of a List
 749  
                      * (position-1)
 750  
                      */
 751  0
                     if (database instanceof InformixDatabase && type != DatabaseMetaData.tableIndexStatistic
 752  
                             && position == 0) {
 753  0
                         System.out.println(this.getClass().getName() + ": corrected position to " + ++position);
 754  
                     }
 755  0
                     String filterCondition = rs.getString("FILTER_CONDITION");
 756  
 
 757  0
                     if (type == DatabaseMetaData.tableIndexStatistic) {
 758  0
                         continue;
 759  
                     }
 760  
                     // if (type == DatabaseMetaData.tableIndexOther) {
 761  
                     // continue;
 762  
                     // }
 763  
 
 764  0
                     if (columnName == null) {
 765  
                         // nothing to index, not sure why these come through sometimes
 766  0
                         continue;
 767  
                     }
 768  
                     Index indexInformation;
 769  0
                     if (indexMap.containsKey(indexName)) {
 770  0
                         indexInformation = indexMap.get(indexName);
 771  
                     } else {
 772  0
                         indexInformation = new Index();
 773  0
                         indexInformation.setTable(table);
 774  0
                         indexInformation.setName(indexName);
 775  0
                         indexInformation.setUnique(!nonUnique);
 776  0
                         indexInformation.setFilterCondition(filterCondition);
 777  0
                         indexMap.put(indexName, indexInformation);
 778  
                     }
 779  
 
 780  0
                     for (int i = indexInformation.getColumns().size(); i < position; i++) {
 781  0
                         indexInformation.getColumns().add(null);
 782  
                     }
 783  0
                     indexInformation.getColumns().set(position - 1, columnName);
 784  0
                 }
 785  0
                 for (Map.Entry<String, Index> entry : indexMap.entrySet()) {
 786  0
                     snapshot.getIndexes().add(entry.getValue());
 787  
                 }
 788  
             } finally {
 789  0
                 if (rs != null) {
 790  
                     try {
 791  0
                         rs.close();
 792  0
                     } catch (SQLException ignored) {
 793  0
                     }
 794  
                 }
 795  0
                 if (statement != null) {
 796  
                     try {
 797  0
                         statement.close();
 798  0
                     } catch (SQLException ignored) {
 799  0
                     }
 800  
                 }
 801  
             }
 802  0
         }
 803  
 
 804  0
         Set<Index> indexesToRemove = new HashSet<Index>();
 805  
 
 806  
         /*
 807  
          * marks indexes as "associated with" instead of "remove it" Index should have associations with: foreignKey,
 808  
          * primaryKey or uniqueConstraint
 809  
          */
 810  0
         for (Index index : snapshot.getIndexes()) {
 811  0
             for (PrimaryKey pk : snapshot.getPrimaryKeys()) {
 812  0
                 if (index.getTable().getName().equalsIgnoreCase(pk.getTable().getName())
 813  
                         && index.getColumnNames().equals(pk.getColumnNames())) {
 814  0
                     index.addAssociatedWith(Index.MARK_PRIMARY_KEY);
 815  
                 }
 816  
             }
 817  0
             for (ForeignKey fk : snapshot.getForeignKeys()) {
 818  0
                 if (index.getTable().getName().equalsIgnoreCase(fk.getForeignKeyTable().getName())
 819  
                         && index.getColumnNames().equals(fk.getForeignKeyColumns())) {
 820  0
                     index.addAssociatedWith(Index.MARK_FOREIGN_KEY);
 821  
                 }
 822  
             }
 823  0
             for (UniqueConstraint uc : snapshot.getUniqueConstraints()) {
 824  0
                 if (index.getTable().getName().equalsIgnoreCase(uc.getTable().getName())
 825  
                         && index.getColumnNames().equals(uc.getColumnNames())) {
 826  0
                     index.addAssociatedWith(Index.MARK_UNIQUE_CONSTRAINT);
 827  
                 }
 828  
             }
 829  
 
 830  
         }
 831  0
         snapshot.getIndexes().removeAll(indexesToRemove);
 832  0
     }
 833  
 
 834  
     protected void readPrimaryKeys(DatabaseSnapshot snapshot, String schema, DatabaseMetaData databaseMetaData)
 835  
             throws DatabaseException, SQLException {
 836  0
         Database database = snapshot.getDatabase();
 837  0
         updateListeners("Reading primary keys for " + database.toString() + " ...");
 838  
 
 839  
         // we can't add directly to the this.primaryKeys hashSet because adding columns to an exising PK changes the
 840  
         // hashCode and .contains() fails
 841  0
         List<PrimaryKey> foundPKs = new ArrayList<PrimaryKey>();
 842  
 
 843  0
         for (Table table : snapshot.getTables()) {
 844  0
             ResultSet rs = databaseMetaData.getPrimaryKeys(database.convertRequestedSchemaToCatalog(schema),
 845  
                     database.convertRequestedSchemaToSchema(schema), table.getName());
 846  
 
 847  
             try {
 848  0
                 while (rs.next()) {
 849  0
                     String tableName = convertFromDatabaseName(rs.getString("TABLE_NAME"));
 850  0
                     String columnName = convertFromDatabaseName(rs.getString("COLUMN_NAME"));
 851  0
                     short position = rs.getShort("KEY_SEQ");
 852  
 
 853  0
                     boolean foundExistingPK = false;
 854  0
                     for (PrimaryKey pk : foundPKs) {
 855  0
                         if (pk.getTable().getName().equals(tableName)) {
 856  0
                             pk.addColumnName(position - 1, columnName);
 857  
 
 858  0
                             foundExistingPK = true;
 859  
                         }
 860  
                     }
 861  
 
 862  0
                     if (!foundExistingPK) {
 863  0
                         PrimaryKey primaryKey = new PrimaryKey();
 864  0
                         primaryKey.setTable(table);
 865  0
                         primaryKey.addColumnName(position - 1, columnName);
 866  0
                         primaryKey.setName(convertPrimaryKeyName(rs.getString("PK_NAME")));
 867  
 
 868  0
                         foundPKs.add(primaryKey);
 869  
                     }
 870  0
                 }
 871  
             } finally {
 872  0
                 rs.close();
 873  0
             }
 874  
 
 875  0
         }
 876  
 
 877  0
         snapshot.getPrimaryKeys().addAll(foundPKs);
 878  0
     }
 879  
 
 880  
     protected String convertPrimaryKeyName(String pkName) throws SQLException {
 881  0
         return pkName;
 882  
     }
 883  
 
 884  
     protected void readUniqueConstraints(DatabaseSnapshot snapshot, String schema, DatabaseMetaData databaseMetaData)
 885  
             throws DatabaseException, SQLException {
 886  0
         Database database = snapshot.getDatabase();
 887  0
         updateListeners("Reading unique constraints for " + database.toString() + " ...");
 888  0
     }
 889  
 
 890  
     // private void readUniqueConstraints(String catalog, String schema) throws DatabaseException, SQLException {
 891  
     // updateListeners("Reading unique constraints for " + database.toString() + " ...");
 892  
     //
 893  
     // //noinspection unchecked
 894  
     // List<String> sequenceNamess = (List<String>) new
 895  
     // Executor(database).queryForList(database.findUniqueConstraints(schema), String.class);
 896  
     //
 897  
     // for (String sequenceName : sequenceNamess) {
 898  
     // Sequence seq = new Sequence();
 899  
     // seq.setName(sequenceName);
 900  
     //
 901  
     // sequences.add(seq);
 902  
     // }
 903  
     // }
 904  
 
 905  
     protected void readSequences(DatabaseSnapshot snapshot, String schema, DatabaseMetaData databaseMetaData)
 906  
             throws DatabaseException {
 907  0
         Database database = snapshot.getDatabase();
 908  0
         if (database.supportsSequences()) {
 909  0
             updateListeners("Reading sequences for " + database.toString() + " ...");
 910  
 
 911  0
             String convertedSchemaName = database.convertRequestedSchemaToSchema(schema);
 912  
 
 913  
             // noinspection unchecked
 914  0
             List<String> sequenceNames = (List<String>) ExecutorService.getInstance().getExecutor(database)
 915  
                     .queryForList(new SelectSequencesStatement(schema), String.class);
 916  
 
 917  0
             if (sequenceNames != null) {
 918  0
                 for (String sequenceName : sequenceNames) {
 919  0
                     Sequence seq = new Sequence();
 920  0
                     seq.setName(sequenceName.trim());
 921  0
                     seq.setSchema(convertedSchemaName);
 922  
 
 923  0
                     snapshot.getSequences().add(seq);
 924  0
                 }
 925  
             }
 926  0
         } else {
 927  0
             updateListeners("Sequences not supported for " + database.toString() + " ...");
 928  
         }
 929  0
     }
 930  
 
 931  
     protected void updateListeners(String message) {
 932  0
         if (this.statusListeners == null) {
 933  0
             return;
 934  
         }
 935  0
         LogFactory.getLogger().debug(message);
 936  0
         for (DiffStatusListener listener : this.statusListeners) {
 937  0
             listener.statusUpdate(message);
 938  
         }
 939  0
     }
 940  
 
 941  
     public boolean isColumnAutoIncrement(Database database, String schemaName, String tableName, String columnName)
 942  
             throws SQLException, DatabaseException {
 943  0
         if (!database.supportsAutoIncrement()) {
 944  0
             return false;
 945  
         }
 946  
 
 947  0
         boolean autoIncrement = false;
 948  
 
 949  0
         Statement statement = null;
 950  0
         ResultSet selectRS = null;
 951  
         try {
 952  0
             statement = ((JdbcConnection) database.getConnection()).getUnderlyingConnection().createStatement();
 953  0
             selectRS = statement.executeQuery("SELECT " + database.escapeColumnName(schemaName, tableName, columnName)
 954  
                     + " FROM " + database.escapeTableName(schemaName, tableName) + " WHERE 1 = 0");
 955  0
             ResultSetMetaData meta = selectRS.getMetaData();
 956  0
             autoIncrement = meta.isAutoIncrement(1);
 957  
         } finally {
 958  0
             if (selectRS != null) {
 959  
                 try {
 960  0
                     selectRS.close();
 961  0
                 } catch (SQLException ignored) {
 962  0
                 }
 963  
             }
 964  0
             if (statement != null) {
 965  
                 try {
 966  0
                     statement.close();
 967  0
                 } catch (SQLException ignored) {
 968  0
                 }
 969  
             }
 970  
         }
 971  
 
 972  0
         return autoIncrement;
 973  
     }
 974  
 
 975  
     public int getDatabaseType(int type, Database database) {
 976  0
         int returnType = type;
 977  0
         if (returnType == java.sql.Types.BOOLEAN) {
 978  0
             String booleanType = TypeConverterFactory.getInstance().findTypeConverter(database).getBooleanType()
 979  
                     .getDataTypeName();
 980  0
             if (!booleanType.equalsIgnoreCase("boolean")) {
 981  0
                 returnType = java.sql.Types.TINYINT;
 982  
             }
 983  
         }
 984  
 
 985  0
         return returnType;
 986  
     }
 987  
 }