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 | |
|
254 | |
|
255 | |
|
256 | |
|
257 | |
|
258 | |
|
259 | |
|
260 | |
|
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 | |
|
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); |
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 | |
|
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 | |
|
451 | |
|
452 | |
|
453 | |
|
454 | |
|
455 | |
|
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 | } |
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 | |
|
480 | |
|
481 | 0 | snapshot.getForeignKeys().addAll(getAdditionalForeignKeys(dbSchema, database)); |
482 | |
|
483 | |
|
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 | |
|
545 | |
|
546 | |
|
547 | |
|
548 | |
|
549 | |
|
550 | |
|
551 | |
|
552 | |
|
553 | |
|
554 | |
|
555 | |
|
556 | |
public ForeignKey generateForeignKey(ForeignKeyInfo fkInfo, Database database, List<ForeignKey> fkList) |
557 | |
throws DatabaseException { |
558 | |
|
559 | |
|
560 | |
|
561 | |
|
562 | |
|
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 | |
|
616 | |
|
617 | |
|
618 | |
|
619 | |
|
620 | |
|
621 | |
|
622 | |
|
623 | |
|
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 | |
|
658 | |
|
659 | |
|
660 | |
|
661 | |
|
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 | |
|
714 | |
|
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 | |
|
731 | |
|
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 | |
|
738 | 0 | boolean nonUnique = true; |
739 | |
try { |
740 | 0 | nonUnique = rs.getBoolean("NON_UNIQUE"); |
741 | 0 | } catch (SQLException e) { |
742 | |
|
743 | 0 | } |
744 | 0 | String columnName = convertFromDatabaseName(rs.getString("COLUMN_NAME")); |
745 | 0 | short position = rs.getShort("ORDINAL_POSITION"); |
746 | |
|
747 | |
|
748 | |
|
749 | |
|
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 | |
|
761 | |
|
762 | |
|
763 | |
|
764 | 0 | if (columnName == null) { |
765 | |
|
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 | |
|
808 | |
|
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 | |
|
840 | |
|
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 | |
|
891 | |
|
892 | |
|
893 | |
|
894 | |
|
895 | |
|
896 | |
|
897 | |
|
898 | |
|
899 | |
|
900 | |
|
901 | |
|
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 | |
|
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 | |
} |