Clover Coverage Report - Liquibase Core 2.0.3-SNAPSHOT
Coverage timestamp: Sat Aug 6 2011 11:33:15 EDT
../../../img/srcFileCovDistChart0.png 69% of files have more coverage
423   987   141   11.43
144   770   0.33   37
37     3.81  
1    
 
  JdbcDatabaseSnapshotGenerator       Line # 43 423 0% 141 604 0% 0.0
 
No Tests
 
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    public abstract class JdbcDatabaseSnapshotGenerator implements DatabaseSnapshotGenerator {
44   
45    private Set<DiffStatusListener> statusListeners;
46   
 
47  0 toggle protected String convertTableNameToDatabaseTableName(String tableName) {
48  0 return tableName;
49    }
50   
 
51  0 toggle protected String convertColumnNameToDatabaseTableName(String columnName) {
52  0 return columnName;
53    }
54   
 
55  0 toggle @Override
56    public Table getDatabaseChangeLogTable(Database database) throws DatabaseException {
57  0 return getTable(database.getLiquibaseSchemaName(), database.getDatabaseChangeLogTableName(), database);
58    }
59   
 
60  0 toggle @Override
61    public Table getDatabaseChangeLogLockTable(Database database) throws DatabaseException {
62  0 return getTable(database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName(), database);
63    }
64   
 
65  0 toggle @Override
66    public boolean hasDatabaseChangeLogTable(Database database) {
67  0 return hasTable(database.getLiquibaseSchemaName(), database.getDatabaseChangeLogTableName(), database);
68    }
69   
 
70  0 toggle @Override
71    public boolean hasDatabaseChangeLogLockTable(Database database) {
72  0 return hasTable(database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName(), database);
73    }
74   
 
75  0 toggle @Override
76    public boolean hasTable(String schemaName, String tableName, Database database) {
77  0 try {
78  0 ResultSet rs = getMetaData(database).getTables(database.convertRequestedSchemaToCatalog(schemaName),
79    database.convertRequestedSchemaToSchema(schemaName),
80    convertTableNameToDatabaseTableName(tableName), new String[] { "TABLE" });
81  0 try {
82  0 return rs.next();
83    } finally {
84  0 try {
85  0 rs.close();
86    } catch (SQLException ignore) {
87    }
88    }
89    } catch (Exception e) {
90  0 throw new UnexpectedLiquibaseException(e);
91    }
92    }
93   
 
94  0 toggle @Override
95    public boolean hasView(String schemaName, String viewName, Database database) {
96  0 try {
97  0 ResultSet rs = getMetaData(database).getTables(database.convertRequestedSchemaToCatalog(schemaName),
98    database.convertRequestedSchemaToSchema(schemaName), convertTableNameToDatabaseTableName(viewName),
99    new String[] { "VIEW" });
100  0 try {
101  0 return rs.next();
102    } finally {
103  0 try {
104  0 rs.close();
105    } catch (SQLException ignore) {
106    }
107    }
108    } catch (Exception e) {
109  0 throw new UnexpectedLiquibaseException(e);
110    }
111    }
112   
 
113  0 toggle @Override
114    public Table getTable(String schemaName, String tableName, Database database) throws DatabaseException {
115  0 ResultSet rs = null;
116  0 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  0 Table table;
123  0 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    }
132   
133  0 rs = metaData.getColumns(database.convertRequestedSchemaToCatalog(schemaName),
134    database.convertRequestedSchemaToSchema(schemaName),
135    convertTableNameToDatabaseTableName(tableName), null);
136  0 try {
137  0 while (rs.next()) {
138  0 table.getColumns().add(readColumn(rs, database));
139    }
140    } finally {
141  0 rs.close();
142    }
143   
144  0 return table;
145    } catch (Exception e) {
146  0 throw new DatabaseException(e);
147    } finally {
148  0 if (rs != null) {
149  0 try {
150  0 rs.close();
151    } catch (SQLException ignore) {
152    }
153    }
154    }
155    }
156   
 
157  0 toggle @Override
158    public Column getColumn(String schemaName, String tableName, String columnName, Database database)
159    throws DatabaseException {
160  0 ResultSet rs = null;
161  0 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    } catch (Exception e) {
172  0 throw new DatabaseException(e);
173    } finally {
174  0 if (rs != null) {
175  0 try {
176  0 rs.close();
177    } catch (SQLException ignore) {
178    }
179    }
180    }
181    }
182   
 
183  0 toggle 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  0 toggle 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  0 try {
208  0 view.setDefinition(database.getViewDefinition(rs.getString("TABLE_SCHEM"), name));
209    } catch (DatabaseException e) {
210  0 throw new DatabaseException("Error getting " + database.getConnection().getURL() + " view with "
211    + new GetViewDefinitionStatement(view.getSchema(), name), e);
212    }
213   
214  0 return view;
215    }
216   
 
217  0 toggle 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  0 toggle 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    }
271   
 
272  0 toggle @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  0 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    } catch (SQLException e) {
298  0 throw new DatabaseException(e);
299    }
300    }
301   
 
302  0 toggle 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  0 toggle 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  0 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    }
339    } finally {
340  0 try {
341  0 rs.close();
342    } catch (SQLException ignore) {
343    }
344    }
345    }
346   
 
347  0 toggle 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  0 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    }
363    } finally {
364  0 try {
365  0 rs.close();
366    } catch (SQLException ignore) {
367    }
368    }
369    }
370   
 
371  0 toggle protected String convertFromDatabaseName(String objectName) {
372  0 if (objectName == null) {
373  0 return null;
374    }
375  0 return objectName;
376    }
377   
 
378  0 toggle 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  0 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  0 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    } 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    }
433    } finally {
434  0 if (rs != null) {
435  0 try {
436  0 rs.close();
437    } catch (SQLException ignored) {
438    }
439    }
440  0 if (selectStatement != null) {
441  0 try {
442  0 selectStatement.close();
443    } catch (SQLException ignored) {
444    }
445    }
446    }
447    }
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  0 toggle protected void getColumnTypeAndDefValue(Column columnInfo, ResultSet rs, Database database) throws SQLException,
458    DatabaseException {
459  0 Object defaultValue = rs.getObject("COLUMN_DEF");
460  0 try {
461  0 columnInfo.setDefaultValue(TypeConverterFactory
462    .getInstance()
463    .findTypeConverter(database)
464    .convertDatabaseValueToObject(defaultValue, columnInfo.getDataType(), columnInfo.getColumnSize(),
465    columnInfo.getDecimalDigits(), database));
466    } catch (ParseException e) {
467  0 throw new DatabaseException(e);
468    }
469  0 columnInfo.setTypeName(TypeConverterFactory.getInstance().findTypeConverter(database)
470    .getDataType(rs.getString("TYPE_NAME"), columnInfo.isAutoIncrement()).toString());
471    } // end of method getColumnTypeAndDefValue()
472   
 
473  0 toggle 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    }
511    }
512    }
513   
 
514  0 toggle @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  0 toggle @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  0 toggle 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  0 toggle public List<ForeignKey> getAdditionalForeignKeys(String schemaName, Database database) throws DatabaseException {
626  0 return new ArrayList<ForeignKey>();
627    }
628   
 
629  0 toggle @Override
630    public List<ForeignKey> getForeignKeys(String schemaName, String foreignKeyTableName, Database database)
631    throws DatabaseException {
632  0 List<ForeignKey> fkList = new ArrayList<ForeignKey>();
633  0 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  0 try {
640  0 while (rs.next()) {
641  0 ForeignKeyInfo fkInfo = fillForeignKeyInfo(rs);
642   
643  0 fkList.add(generateForeignKey(fkInfo, database, fkList));
644    }
645    } finally {
646  0 rs.close();
647    }
648   
649  0 return fkList;
650   
651    } 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  0 toggle 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  0 toggle 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  0 toggle 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  0 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    } 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  0 try {
740  0 nonUnique = rs.getBoolean("NON_UNIQUE");
741    } catch (SQLException e) {
742    // doesn't exist in all databases
743    }
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  0 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    }
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  0 try {
791  0 rs.close();
792    } catch (SQLException ignored) {
793    }
794    }
795  0 if (statement != null) {
796  0 try {
797  0 statement.close();
798    } catch (SQLException ignored) {
799    }
800    }
801    }
802    }
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    }
833   
 
834  0 toggle 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  0 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    }
871    } finally {
872  0 rs.close();
873    }
874   
875    }
876   
877  0 snapshot.getPrimaryKeys().addAll(foundPKs);
878    }
879   
 
880  0 toggle protected String convertPrimaryKeyName(String pkName) throws SQLException {
881  0 return pkName;
882    }
883   
 
884  0 toggle 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    }
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  0 toggle 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    }
925    }
926    } else {
927  0 updateListeners("Sequences not supported for " + database.toString() + " ...");
928    }
929    }
930   
 
931  0 toggle 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    }
940   
 
941  0 toggle 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  0 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  0 try {
960  0 selectRS.close();
961    } catch (SQLException ignored) {
962    }
963    }
964  0 if (statement != null) {
965  0 try {
966  0 statement.close();
967    } catch (SQLException ignored) {
968    }
969    }
970    }
971   
972  0 return autoIncrement;
973    }
974   
 
975  0 toggle 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    }