Newer
Older
*
* @see TAPMetadata#resolveStdTable(String)
* @see TAPMetadata#getStdSchema(boolean)
* @see TAPMetadata#getStdTable(STDTable)
protected TAPTable[] mergeTAPSchemaDefs(final TAPMetadata metadata){
// 1. Get the TAP_SCHEMA schema from the given metadata:
TAPSchema tapSchema = null;
Iterator<TAPSchema> itSchema = metadata.iterator();
while(tapSchema == null && itSchema.hasNext()){
TAPSchema schema = itSchema.next();
if (schema.getADQLName().equalsIgnoreCase(STDSchema.TAPSCHEMA.label))
tapSchema = schema;
}
// 2. Get the provided definition of the standard TAP tables:
TAPTable[] customStdTables = new TAPTable[5];
if (tapSchema != null){
/* if the schemas are not supported with this DBMS,
* remove its DB name: */
if (!supportsSchema)
tapSchema.setDBName(null);
// retrieve only the standard TAP tables:
Iterator<TAPTable> itTable = tapSchema.iterator();
while(itTable.hasNext()){
TAPTable table = itTable.next();
int indStdTable = getCreationOrder(TAPMetadata.resolveStdTable(table.getADQLName()));
if (indStdTable > -1)
customStdTables[indStdTable] = table;
}
// 3. Build a common TAPSchema, if needed:
if (tapSchema == null){
// build a new TAP_SCHEMA definition based on the standard definition:
gmantele
committed
tapSchema = TAPMetadata.getStdSchema(supportsSchema);
// add the new TAP_SCHEMA definition in the given metadata object:
metadata.addSchema(tapSchema);
}
// 4. Finally, build the join between the standard tables and the custom ones:
TAPTable[] stdTables = new TAPTable[]{TAPMetadata.getStdTable(STDTable.SCHEMAS),TAPMetadata.getStdTable(STDTable.TABLES),TAPMetadata.getStdTable(STDTable.COLUMNS),TAPMetadata.getStdTable(STDTable.KEYS),TAPMetadata.getStdTable(STDTable.KEY_COLUMNS)};
for(int i = 0; i < stdTables.length; i++){
// CASE: no custom definition:
if (customStdTables[i] == null){
if (!supportsSchema)
gmantele
committed
stdTables[i].setDBName(STDSchema.TAPSCHEMA.label + "_" + stdTables[i].getADQLName());
// add the table to the fetched or built-in schema:
tapSchema.addTable(stdTables[i]);
}
// CASE: custom definition
else
stdTables[i] = customStdTables[i];
return stdTables;
/**
* <p>Ensure the TAP_SCHEMA schema exists in the database AND it must especially drop all of its standard tables
* (schemas, tables, columns, keys and key_columns), if they exist.</p>
*
* <p><i><b>Important note</b>:
* If TAP_SCHEMA already exists and contains other tables than the standard ones, they will not be dropped and they will stay in place.
* </i></p>
*
* @param stmt The statement to use in order to interact with the database.
* @param stdTables List of all standard tables that must be (re-)created.
* They will be used just to know the name of the standard tables that should be dropped here.
*
* @throws SQLException If any error occurs while querying or updating the database.
*/
protected void resetTAPSchema(final Statement stmt, final TAPTable[] stdTables) throws SQLException{
DatabaseMetaData dbMeta = connection.getMetaData();
// 1. Get the qualified DB schema name:
gmantele
committed
String dbSchemaName = (supportsSchema ? stdTables[0].getDBSchemaName() : null);
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
/* 2. Test whether the schema TAP_SCHEMA exists
* and if it does not, create it: */
if (dbSchemaName != null){
// test whether the schema TAP_SCHEMA exists:
boolean hasTAPSchema = isSchemaExisting(dbSchemaName, dbMeta);
// create TAP_SCHEMA if it does not exist:
if (!hasTAPSchema)
stmt.executeUpdate("CREATE SCHEMA " + translator.getQualifiedSchemaName(stdTables[0]) + ";");
}
// 2-bis. Drop all its standard tables:
dropTAPSchemaTables(stdTables, stmt, dbMeta);
}
/**
* <p>Remove/Drop all standard TAP_SCHEMA tables given in parameter.</p>
*
* <p><i>Note:
* To test the existence of tables to drop, {@link DatabaseMetaData#getTables(String, String, String, String[])} is called.
* Then the schema and table names are compared with the case sensitivity defined by the translator.
* Only tables matching with these comparisons will be dropped.
* </i></p>
*
* @param stdTables Tables to drop. (they should be provided ordered by their creation order (see {@link #getCreationOrder(STDTable)})).
* @param stmt Statement to use in order to interact with the database.
* @param dbMeta Database metadata. Used to list all existing tables.
*
* @throws SQLException If any error occurs while querying or updating the database.
*
* @see JDBCTranslator#isCaseSensitive(IdentifierField)
*/
private void dropTAPSchemaTables(final TAPTable[] stdTables, final Statement stmt, final DatabaseMetaData dbMeta) throws SQLException{
String[] stdTablesToDrop = new String[]{null,null,null,null,null};
ResultSet rs = null;
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
// Retrieve only the schema name and determine whether the search should be case sensitive:
String tapSchemaName = stdTables[0].getDBSchemaName();
boolean schemaCaseSensitive = translator.isCaseSensitive(IdentifierField.SCHEMA);
boolean tableCaseSensitive = translator.isCaseSensitive(IdentifierField.TABLE);
// Identify which standard TAP tables must be dropped:
rs = dbMeta.getTables(null, null, null, null);
while(rs.next()){
String rsSchema = nullifyIfNeeded(rs.getString(2)), rsTable = rs.getString(3);
if (!supportsSchema || (tapSchemaName == null && rsSchema == null) || equals(rsSchema, tapSchemaName, schemaCaseSensitive)){
int indStdTable;
indStdTable = getCreationOrder(isStdTable(rsTable, stdTables, tableCaseSensitive));
if (indStdTable > -1){
stdTablesToDrop[indStdTable] = (rsSchema != null ? "\"" + rsSchema + "\"." : "") + "\"" + rsTable + "\"";
}
}
}
}finally{
close(rs);
}
// Drop the existing tables (in the reverse order of creation):
for(int i = stdTablesToDrop.length - 1; i >= 0; i--){
if (stdTablesToDrop[i] != null)
stmt.executeUpdate("DROP TABLE " + stdTablesToDrop[i] + ";");
/**
* <p>Create the specified standard TAP_SCHEMA tables into the database.</p>
*
* <p><i><b>Important note:</b>
* Only standard TAP_SCHEMA tables (schemas, tables, columns, keys and key_columns) can be created here.
* If the given table is not part of the schema TAP_SCHEMA (comparison done on the ADQL name case-sensitively)
* and is not a standard TAP_SCHEMA table (comparison done on the ADQL name case-sensitively),
* this function will do nothing and will throw an exception.
* </i></p>
*
gmantele
committed
* <p><i>Note:
* An extra column is added in TAP_SCHEMA.schemas, TAP_SCHEMA.tables and TAP_SCHEMA.columns: {@value #DB_NAME_COLUMN}.
* This column is particularly used when getting the TAP metadata from the database to alias some schema, table and/or column names in ADQL.
* </i></p>
*
* @param table Table to create.
* @param stmt Statement to use in order to interact with the database.
*
* @throws DBException If the given table is not a standard TAP_SCHEMA table.
* @throws SQLException If any error occurs while querying or updating the database.
*/
protected void createTAPSchemaTable(final TAPTable table, final Statement stmt) throws DBException, SQLException{
// 1. ENSURE THE GIVEN TABLE IS REALLY A TAP_SCHEMA TABLE (according to the ADQL names):
if (!table.getADQLSchemaName().equalsIgnoreCase(STDSchema.TAPSCHEMA.label) || TAPMetadata.resolveStdTable(table.getADQLName()) == null)
throw new DBException("Forbidden table creation: " + table + " is not a standard table of TAP_SCHEMA!");
// 2. BUILD THE SQL QUERY TO CREATE THE TABLE:
StringBuffer sql = new StringBuffer("CREATE TABLE ");
// a. Write the fully qualified table name:
gmantele
committed
sql.append(translator.getTableName(table, supportsSchema));
// b. List all the columns:
sql.append('(');
Iterator<TAPColumn> it = table.getColumns();
while(it.hasNext()){
TAPColumn col = it.next();
// column name:
sql.append(translator.getColumnName(col));
// column type:
sql.append(' ').append(convertTypeToDB(col.getDatatype()));
// last column ?
if (it.hasNext())
gmantele
committed
sql.append(',');
gmantele
committed
// b bis. Add the extra dbName column (giving the database name of a schema, table or column):
if ((supportsSchema && table.getADQLName().equalsIgnoreCase(STDTable.SCHEMAS.label)) || table.getADQLName().equalsIgnoreCase(STDTable.TABLES.label) || table.getADQLName().equalsIgnoreCase(STDTable.COLUMNS.label))
sql.append(',').append(DB_NAME_COLUMN).append(" VARCHAR");
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
// c. Append the primary key definition, if needed:
String primaryKey = getPrimaryKeyDef(table.getADQLName());
if (primaryKey != null)
sql.append(',').append(primaryKey);
// d. End the query:
sql.append(')').append(';');
// 3. FINALLY CREATE THE TABLE:
stmt.executeUpdate(sql.toString());
}
/**
* <p>Get the primary key corresponding to the specified table.</p>
*
* <p>If the specified table is not a standard TAP_SCHEMA table, NULL will be returned.</p>
*
* @param tableName ADQL table name.
*
* @return The primary key definition (prefixed by a space) corresponding to the specified table (ex: " PRIMARY KEY(schema_name)"),
* or NULL if the specified table is not a standard TAP_SCHEMA table.
*/
private String getPrimaryKeyDef(final String tableName){
STDTable stdTable = TAPMetadata.resolveStdTable(tableName);
if (stdTable == null)
return null;
boolean caseSensitive = translator.isCaseSensitive(IdentifierField.COLUMN);
switch(stdTable){
case SCHEMAS:
return " PRIMARY KEY(" + (caseSensitive ? "\"schema_name\"" : "schema_name") + ")";
case TABLES:
gmantele
committed
return " PRIMARY KEY(" + (caseSensitive ? "\"table_name\"" : "table_name") + ")";
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
case COLUMNS:
return " PRIMARY KEY(" + (caseSensitive ? "\"table_name\"" : "table_name") + ", " + (caseSensitive ? "\"column_name\"" : "column_name") + ")";
case KEYS:
case KEY_COLUMNS:
return " PRIMARY KEY(" + (caseSensitive ? "\"key_id\"" : "key_id") + ")";
default:
return null;
}
}
/**
* <p>Create the DB indexes corresponding to the given TAP_SCHEMA table.</p>
*
* <p><i><b>Important note:</b>
* Only standard TAP_SCHEMA tables (schemas, tables, columns, keys and key_columns) can be created here.
* If the given table is not part of the schema TAP_SCHEMA (comparison done on the ADQL name case-sensitively)
* and is not a standard TAP_SCHEMA table (comparison done on the ADQL name case-sensitively),
* this function will do nothing and will throw an exception.
* </i></p>
*
* @param table Table whose indexes must be created here.
* @param stmt Statement to use in order to interact with the database.
*
* @throws DBException If the given table is not a standard TAP_SCHEMA table.
* @throws SQLException If any error occurs while querying or updating the database.
*/
protected void createTAPTableIndexes(final TAPTable table, final Statement stmt) throws DBException, SQLException{
// 1. Ensure the given table is really a TAP_SCHEMA table (according to the ADQL names):
if (!table.getADQLSchemaName().equalsIgnoreCase(STDSchema.TAPSCHEMA.label) || TAPMetadata.resolveStdTable(table.getADQLName()) == null)
throw new DBException("Forbidden index creation: " + table + " is not a standard table of TAP_SCHEMA!");
// Build the fully qualified DB name of the table:
gmantele
committed
final String dbTableName = translator.getTableName(table, supportsSchema);
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
// Build the name prefix of all the indexes to create:
final String indexNamePrefix = "INDEX_" + ((table.getADQLSchemaName() != null) ? (table.getADQLSchemaName() + "_") : "") + table.getADQLName() + "_";
Iterator<TAPColumn> it = table.getColumns();
while(it.hasNext()){
TAPColumn col = it.next();
// Create an index only for columns that have the 'indexed' flag:
if (col.isIndexed() && !isPartOfPrimaryKey(col.getADQLName()))
stmt.executeUpdate("CREATE INDEX " + indexNamePrefix + col.getADQLName() + " ON " + dbTableName + "(" + translator.getColumnName(col) + ");");
}
}
/**
* Tell whether the specified column is part of the primary key of its table.
*
* @param adqlName ADQL name of a column.
*
* @return <i>true</i> if the specified column is part of the primary key,
* <i>false</i> otherwise.
*/
private boolean isPartOfPrimaryKey(final String adqlName){
if (adqlName == null)
return false;
else
return (adqlName.equalsIgnoreCase("schema_name") || adqlName.equalsIgnoreCase("table_name") || adqlName.equalsIgnoreCase("column_name") || adqlName.equalsIgnoreCase("key_id"));
}
/**
* <p>Fill all the standard tables of TAP_SCHEMA (schemas, tables, columns, keys and key_columns).</p>
*
* <p>This function just call the following functions:</p>
* <ol>
* <li>{@link #fillSchemas(TAPTable, Iterator)}</li>
* <li>{@link #fillTables(TAPTable, Iterator)}</li>
* <li>{@link #fillColumns(TAPTable, Iterator)}</li>
* <li>{@link #fillKeys(TAPTable, TAPTable, Iterator)}</li>
* </ol>
*
* @param meta All schemas and tables to list inside the TAP_SCHEMA tables.
*
* @throws DBException If rows can not be inserted because the SQL update query has failed.
* @throws SQLException If any other SQL exception occurs.
*/
protected void fillTAPSchema(final TAPMetadata meta) throws SQLException, DBException{
TAPTable metaTable;
// 1. Fill SCHEMAS:
metaTable = meta.getTable(STDSchema.TAPSCHEMA.label, STDTable.SCHEMAS.label);
Iterator<TAPTable> allTables = fillSchemas(metaTable, meta.iterator());
// 2. Fill TABLES:
metaTable = meta.getTable(STDSchema.TAPSCHEMA.label, STDTable.TABLES.label);
Iterator<TAPColumn> allColumns = fillTables(metaTable, allTables);
allTables = null;
// Fill COLUMNS:
metaTable = meta.getTable(STDSchema.TAPSCHEMA.label, STDTable.COLUMNS.label);
Iterator<TAPForeignKey> allKeys = fillColumns(metaTable, allColumns);
allColumns = null;
// Fill KEYS and KEY_COLUMNS:
metaTable = meta.getTable(STDSchema.TAPSCHEMA.label, STDTable.KEYS.label);
TAPTable metaTable2 = meta.getTable(STDSchema.TAPSCHEMA.label, STDTable.KEY_COLUMNS.label);
fillKeys(metaTable, metaTable2, allKeys);
}
/**
* <p>Fill the standard table TAP_SCHEMA.schemas with the list of all published schemas.</p>
*
* <p><i>Note:
* Batch updates may be done here if its supported by the DBMS connection.
* In case of any failure while using this feature, it will be flagged as unsupported and one-by-one updates will be processed.
* </i></p>
*
* @param metaTable Description of TAP_SCHEMA.schemas.
* @param itSchemas Iterator over the list of schemas.
*
* @return Iterator over the full list of all tables (whatever is their schema).
*
* @throws DBException If rows can not be inserted because the SQL update query has failed.
* @throws SQLException If any other SQL exception occurs.
*/
private Iterator<TAPTable> fillSchemas(final TAPTable metaTable, final Iterator<TAPSchema> itSchemas) throws SQLException, DBException{
List<TAPTable> allTables = new ArrayList<TAPTable>();
// Build the SQL update query:
StringBuffer sql = new StringBuffer("INSERT INTO ");
gmantele
committed
sql.append(translator.getTableName(metaTable, supportsSchema)).append(" (");
sql.append(translator.getColumnName(metaTable.getColumn("schema_name")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("description")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("utype")));
gmantele
committed
if (supportsSchema){
sql.append(", ").append(DB_NAME_COLUMN);
sql.append(") VALUES (?, ?, ?, ?);");
}else
sql.append(") VALUES (?, ?, ?);");
// Prepare the statement:
PreparedStatement stmt = null;
try{
stmt = connection.prepareStatement(sql.toString());
// Execute the query for each schema:
int nbRows = 0;
while(itSchemas.hasNext()){
TAPSchema schema = itSchemas.next();
nbRows++;
// list all tables of this schema:
appendAllInto(allTables, schema.iterator());
// add the schema entry into the DB:
stmt.setString(1, schema.getADQLName());
stmt.setString(2, schema.getDescription());
stmt.setString(3, schema.getUtype());
gmantele
committed
if (supportsSchema)
stmt.setString(4, (schema.getDBName() == null || schema.getDBName().equals(schema.getADQLName())) ? null : schema.getDBName());
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
executeUpdate(stmt, nbRows);
}
executeBatchUpdates(stmt, nbRows);
}finally{
close(stmt);
}
return allTables.iterator();
}
/**
* <p>Fill the standard table TAP_SCHEMA.tables with the list of all published tables.</p>
*
* <p><i>Note:
* Batch updates may be done here if its supported by the DBMS connection.
* In case of any failure while using this feature, it will be flagged as unsupported and one-by-one updates will be processed.
* </i></p>
*
* @param metaTable Description of TAP_SCHEMA.tables.
* @param itTables Iterator over the list of tables.
*
* @return Iterator over the full list of all columns (whatever is their table).
*
* @throws DBException If rows can not be inserted because the SQL update query has failed.
* @throws SQLException If any other SQL exception occurs.
*/
private Iterator<TAPColumn> fillTables(final TAPTable metaTable, final Iterator<TAPTable> itTables) throws SQLException, DBException{
List<TAPColumn> allColumns = new ArrayList<TAPColumn>();
// Build the SQL update query:
StringBuffer sql = new StringBuffer("INSERT INTO ");
gmantele
committed
sql.append(translator.getTableName(metaTable, supportsSchema)).append(" (");
sql.append(translator.getColumnName(metaTable.getColumn("schema_name")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("table_name")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("table_type")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("description")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("utype")));
gmantele
committed
sql.append(", ").append(DB_NAME_COLUMN);
sql.append(") VALUES (?, ?, ?, ?, ?, ?);");
// Prepare the statement:
PreparedStatement stmt = null;
try{
stmt = connection.prepareStatement(sql.toString());
// Execute the query for each table:
int nbRows = 0;
while(itTables.hasNext()){
TAPTable table = itTables.next();
nbRows++;
// list all columns of this table:
appendAllInto(allColumns, table.getColumns());
// add the table entry into the DB:
stmt.setString(1, table.getADQLSchemaName());
gmantele
committed
if (table.isInitiallyQualified())
stmt.setString(2, table.getADQLSchemaName() + "." + table.getADQLName());
else
stmt.setString(2, table.getADQLName());
stmt.setString(3, table.getType().toString());
stmt.setString(4, table.getDescription());
stmt.setString(5, table.getUtype());
gmantele
committed
stmt.setString(6, (table.getDBName() == null || table.getDBName().equals(table.getADQLName())) ? null : table.getDBName());
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
executeUpdate(stmt, nbRows);
}
executeBatchUpdates(stmt, nbRows);
}finally{
close(stmt);
}
return allColumns.iterator();
}
/**
* <p>Fill the standard table TAP_SCHEMA.columns with the list of all published columns.</p>
*
* <p><i>Note:
* Batch updates may be done here if its supported by the DBMS connection.
* In case of any failure while using this feature, it will be flagged as unsupported and one-by-one updates will be processed.
* </i></p>
*
* @param metaTable Description of TAP_SCHEMA.columns.
* @param itColumns Iterator over the list of columns.
*
* @return Iterator over the full list of all foreign keys.
*
* @throws DBException If rows can not be inserted because the SQL update query has failed.
* @throws SQLException If any other SQL exception occurs.
*/
private Iterator<TAPForeignKey> fillColumns(final TAPTable metaTable, final Iterator<TAPColumn> itColumns) throws SQLException, DBException{
List<TAPForeignKey> allKeys = new ArrayList<TAPForeignKey>();
// Build the SQL update query:
StringBuffer sql = new StringBuffer("INSERT INTO ");
gmantele
committed
sql.append(translator.getTableName(metaTable, supportsSchema)).append(" (");
sql.append(translator.getColumnName(metaTable.getColumn("table_name")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("column_name")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("description")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("unit")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("ucd")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("utype")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("datatype")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("size")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("principal")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("indexed")));
sql.append(", ").append(translator.getColumnName(metaTable.getColumn("std")));
gmantele
committed
sql.append(", ").append(DB_NAME_COLUMN);
sql.append(") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
// Prepare the statement:
PreparedStatement stmt = null;
try{
stmt = connection.prepareStatement(sql.toString());
// Execute the query for each column:
int nbRows = 0;
while(itColumns.hasNext()){
TAPColumn col = itColumns.next();
nbRows++;
// list all foreign keys of this column:
appendAllInto(allKeys, col.getTargets());
// add the column entry into the DB:
gmantele
committed
if (!(col.getTable() instanceof TAPTable) || ((TAPTable)col.getTable()).isInitiallyQualified())
stmt.setString(1, col.getTable().getADQLSchemaName() + "." + col.getTable().getADQLName());
else
stmt.setString(1, col.getTable().getADQLName());
stmt.setString(2, col.getADQLName());
stmt.setString(3, col.getDescription());
stmt.setString(4, col.getUnit());
stmt.setString(5, col.getUcd());
stmt.setString(6, col.getUtype());
stmt.setString(7, col.getDatatype().type.toString());
stmt.setInt(8, col.getDatatype().length);
stmt.setInt(9, col.isPrincipal() ? 1 : 0);
stmt.setInt(10, col.isIndexed() ? 1 : 0);
stmt.setInt(11, col.isStd() ? 1 : 0);
gmantele
committed
stmt.setString(12, (col.getDBName() == null || col.getDBName().equals(col.getADQLName())) ? null : col.getDBName());
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
executeUpdate(stmt, nbRows);
}
executeBatchUpdates(stmt, nbRows);
}finally{
close(stmt);
}
return allKeys.iterator();
}
/**
* <p>Fill the standard tables TAP_SCHEMA.keys and TAP_SCHEMA.key_columns with the list of all published foreign keys.</p>
*
* <p><i>Note:
* Batch updates may be done here if its supported by the DBMS connection.
* In case of any failure while using this feature, it will be flagged as unsupported and one-by-one updates will be processed.
* </i></p>
*
* @param metaKeys Description of TAP_SCHEMA.keys.
* @param metaKeyColumns Description of TAP_SCHEMA.key_columns.
* @param itKeys Iterator over the list of foreign keys.
*
* @throws DBException If rows can not be inserted because the SQL update query has failed.
* @throws SQLException If any other SQL exception occurs.
*/
private void fillKeys(final TAPTable metaKeys, final TAPTable metaKeyColumns, final Iterator<TAPForeignKey> itKeys) throws SQLException, DBException{
// Build the SQL update query for KEYS:
StringBuffer sqlKeys = new StringBuffer("INSERT INTO ");
gmantele
committed
sqlKeys.append(translator.getTableName(metaKeys, supportsSchema)).append(" (");
sqlKeys.append(translator.getColumnName(metaKeys.getColumn("key_id")));
sqlKeys.append(", ").append(translator.getColumnName(metaKeys.getColumn("from_table")));
sqlKeys.append(", ").append(translator.getColumnName(metaKeys.getColumn("target_table")));
sqlKeys.append(", ").append(translator.getColumnName(metaKeys.getColumn("description")));
sqlKeys.append(", ").append(translator.getColumnName(metaKeys.getColumn("utype")));
sqlKeys.append(") VALUES (?, ?, ?, ?, ?);");
PreparedStatement stmtKeys = null, stmtKeyCols = null;
try{
// Prepare the statement for KEYS:
stmtKeys = connection.prepareStatement(sqlKeys.toString());
// Build the SQL update query for KEY_COLUMNS:
StringBuffer sqlKeyCols = new StringBuffer("INSERT INTO ");
gmantele
committed
sqlKeyCols.append(translator.getTableName(metaKeyColumns, supportsSchema)).append(" (");
sqlKeyCols.append(translator.getColumnName(metaKeyColumns.getColumn("key_id")));
sqlKeyCols.append(", ").append(translator.getColumnName(metaKeyColumns.getColumn("from_column")));
sqlKeyCols.append(", ").append(translator.getColumnName(metaKeyColumns.getColumn("target_column")));
sqlKeyCols.append(") VALUES (?, ?, ?);");
// Prepare the statement for KEY_COLUMNS:
stmtKeyCols = connection.prepareStatement(sqlKeyCols.toString());
// Execute the query for each column:
int nbKeys = 0, nbKeyColumns = 0;
while(itKeys.hasNext()){
TAPForeignKey key = itKeys.next();
nbKeys++;
// add the key entry into KEYS:
stmtKeys.setString(1, key.getKeyId());
gmantele
committed
if (key.getFromTable().isInitiallyQualified())
stmtKeys.setString(2, key.getFromTable().getADQLSchemaName() + "." + key.getFromTable().getADQLName());
else
stmtKeys.setString(2, key.getFromTable().getADQLName());
if (key.getTargetTable().isInitiallyQualified())
stmtKeys.setString(3, key.getTargetTable().getADQLSchemaName() + "." + key.getTargetTable().getADQLName());
else
stmtKeys.setString(3, key.getTargetTable().getADQLName());
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
stmtKeys.setString(4, key.getDescription());
stmtKeys.setString(5, key.getUtype());
executeUpdate(stmtKeys, nbKeys);
// add the key columns into KEY_COLUMNS:
Iterator<Map.Entry<String,String>> itAssoc = key.iterator();
while(itAssoc.hasNext()){
nbKeyColumns++;
Map.Entry<String,String> assoc = itAssoc.next();
stmtKeyCols.setString(1, key.getKeyId());
stmtKeyCols.setString(2, assoc.getKey());
stmtKeyCols.setString(3, assoc.getValue());
executeUpdate(stmtKeyCols, nbKeyColumns);
}
}
executeBatchUpdates(stmtKeys, nbKeys);
executeBatchUpdates(stmtKeyCols, nbKeyColumns);
}finally{
close(stmtKeys);
close(stmtKeyCols);
}
}
/* ***************** */
/* UPLOAD MANAGEMENT */
/* ***************** */
/**
* <p><i><b>Important note:</b>
* Only tables uploaded by users can be created in the database. To ensure that, the schema name of this table MUST be {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD") in ADQL.
* If it has another ADQL name, an exception will be thrown. Of course, the DB name of this schema MAY be different.
* </i></p>
*
* <p><i><b>Important note:</b>
* This function may modify the given {@link TAPTable} object if schemas are not supported by this connection.
* In this case, this function will prefix the table's DB name by the schema's DB name directly inside the given
* {@link TAPTable} object. Then the DB name of the schema will be set to NULL.
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
* </i></p>
*
* <p><i>Note:
* If the upload schema does not already exist in the database, it will be created.
* </i></p>
*
* @see tap.db.DBConnection#addUploadedTable(tap.metadata.TAPTable, tap.data.TableIterator)
* @see #checkUploadedTableDef(TAPTable)
*/
@Override
public boolean addUploadedTable(TAPTable tableDef, TableIterator data) throws DBException, DataReadException{
// If no table to upload, consider it has been dropped and return TRUE:
if (tableDef == null)
return true;
// Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD):
checkUploadedTableDef(tableDef);
Statement stmt = null;
try{
// Start a transaction:
startTransaction();
// ...create a statement:
stmt = connection.createStatement();
DatabaseMetaData dbMeta = connection.getMetaData();
// 1. Create the upload schema, if it does not already exist:
if (!isSchemaExisting(tableDef.getDBSchemaName(), dbMeta)){
stmt.executeUpdate("CREATE SCHEMA " + translator.getQualifiedSchemaName(tableDef) + ";");
if (logger != null)
logger.logDB(LogLevel.INFO, this, "SCHEMA_CREATED", "Schema \"" + tableDef.getADQLSchemaName() + "\" (in DB: " + translator.getQualifiedSchemaName(tableDef) + ") created.", null);
}
// 1bis. Ensure the table does not already exist and if it is the case, throw an understandable exception:
else if (isTableExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), dbMeta)){
gmantele
committed
DBException de = new DBException("Impossible to create the user uploaded table in the database: " + translator.getTableName(tableDef, supportsSchema) + "! This table already exists.");
if (logger != null)
logger.logDB(LogLevel.ERROR, this, "ADD_UPLOAD_TABLE", de.getMessage(), de);
throw de;
}
// 2. Create the table:
// ...build the SQL query:
StringBuffer sqlBuf = new StringBuffer("CREATE TABLE ");
gmantele
committed
sqlBuf.append(translator.getTableName(tableDef, supportsSchema)).append(" (");
Iterator<TAPColumn> it = tableDef.getColumns();
while(it.hasNext()){
TAPColumn col = it.next();
// column name:
sqlBuf.append(translator.getColumnName(col));
// column type:
sqlBuf.append(' ').append(convertTypeToDB(col.getDatatype()));
// last column ?
if (it.hasNext())
sqlBuf.append(',');
}
sqlBuf.append(");");
// ...execute the update query:
stmt.executeUpdate(sqlBuf.toString());
// 3. Fill the table:
fillUploadedTable(tableDef, data);
// Commit the transaction:
commit();
// Log the end:
if (logger != null)
gmantele
committed
logger.logDB(LogLevel.INFO, this, "TABLE_CREATED", "Table \"" + tableDef.getADQLName() + "\" (in DB: " + translator.getTableName(tableDef, supportsSchema) + ") created.", null);
return true;
}catch(SQLException se){
rollback();
if (logger != null)
gmantele
committed
logger.logDB(LogLevel.WARNING, this, "ADD_UPLOAD_TABLE", "Impossible to create the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se);
throw new DBException("Impossible to create the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se);
}catch(DBException de){
rollback();
throw de;
}catch(DataReadException dre){
rollback();
throw dre;
}finally{
close(stmt);
endTransaction();
}
}
/**
* <p>Fill the table uploaded by the user with the given data.</p>
*
* <p><i>Note:
* Batch updates may be done here if its supported by the DBMS connection.
* In case of any failure while using this feature, it will be flagged as unsupported and one-by-one updates will be processed.
* </i></p>
*
* <p><i>Note:
* This function proceeds to a formatting of TIMESTAMP and GEOMETRY (point, circle, box, polygon) values.
* </i></p>
*
* @param metaTable Description of the updated table.
* @param data Iterator over the rows to insert.
*
* @return Number of inserted rows.
*
* @throws DBException If rows can not be inserted because the SQL update query has failed.
* @throws SQLException If any other SQL exception occurs.
* @throws DataReadException If there is any error while reading the data from the given {@link TableIterator} (and particularly if a limit - in byte or row - has been reached).
*/
protected int fillUploadedTable(final TAPTable metaTable, final TableIterator data) throws SQLException, DBException, DataReadException{
// 1. Build the SQL update query:
StringBuffer sql = new StringBuffer("INSERT INTO ");
StringBuffer varParam = new StringBuffer();
// ...table name:
gmantele
committed
sql.append(translator.getTableName(metaTable, supportsSchema)).append(" (");
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
// ...list of columns:
TAPColumn[] cols = data.getMetadata();
for(int c = 0; c < cols.length; c++){
if (c > 0){
sql.append(", ");
varParam.append(", ");
}
sql.append(translator.getColumnName(cols[c]));
varParam.append('?');
}
// ...values pattern:
sql.append(") VALUES (").append(varParam).append(");");
// 2. Prepare the statement:
PreparedStatement stmt = null;
int nbRows = 0;
try{
stmt = connection.prepareStatement(sql.toString());
// 3. Execute the query for each given row:
while(data.nextRow()){
nbRows++;
int c = 1;
while(data.hasNextCol()){
Object val = data.nextCol();
if (val != null && cols[c - 1] != null){
/* TIMESTAMP FORMATTING */
if (cols[c - 1].getDatatype().type == DBDatatype.TIMESTAMP){
try{
val = new Timestamp(ISO8601Format.parse(val.toString()));
}catch(ParseException pe){
if (logger != null)
gmantele
committed
logger.logDB(LogLevel.ERROR, this, "UPLOAD", "[l. " + nbRows + ", c. " + c + "] Unexpected date format for the value: \"" + val + "\"! A date formatted in ISO8601 was expected.", pe);
throw new DBException("[l. " + nbRows + ", c. " + c + "] Unexpected date format for the value: \"" + val + "\"! A date formatted in ISO8601 was expected.", pe);
}
}
/* GEOMETRY FORMATTING */
else if (cols[c - 1].getDatatype().type == DBDatatype.POINT || cols[c - 1].getDatatype().type == DBDatatype.REGION){
Region region;
gmantele
committed
// parse the region as an STC-S expression:
try{
region = STCS.parseRegion(val.toString());
gmantele
committed
}catch(adql.parser.ParseException e){
if (logger != null)
logger.logDB(LogLevel.ERROR, this, "UPLOAD", "[l. " + nbRows + ", c. " + c + "] Incorrect STC-S syntax for the geometrical value \"" + val + "\"! " + e.getMessage(), e);
throw new DataReadException("[l. " + nbRows + ", c. " + c + "] Incorrect STC-S syntax for the geometrical value \"" + val + "\"! " + e.getMessage(), e);
}
// translate this STC region into the corresponding column value:
try{
val = translator.translateGeometryToDB(region);
}catch(adql.parser.ParseException e){
if (logger != null)
gmantele
committed
logger.logDB(LogLevel.ERROR, this, "UPLOAD", "[l. " + nbRows + ", c. " + c + "] Impossible to import the ADQL geometry \"" + val + "\" into the database! " + e.getMessage(), e);
throw new DataReadException("[l. " + nbRows + ", c. " + c + "] Impossible to import the ADQL geometry \"" + val + "\" into the database! " + e.getMessage(), e);
}
}
/* BOOLEAN CASE (more generally, type incompatibility) */
else if (val != null && cols[c - 1].getDatatype().type == DBDatatype.SMALLINT && val instanceof Boolean)
val = ((Boolean)val) ? (short)1 : (short)0;
}
stmt.setObject(c++, val);
}
executeUpdate(stmt, nbRows);
}
executeBatchUpdates(stmt, nbRows);
return nbRows;
}finally{
close(stmt);
}
}
/**
* <p><i><b>Important note:</b>
* Only tables uploaded by users can be dropped from the database. To ensure that, the schema name of this table MUST be {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD") in ADQL.
* If it has another ADQL name, an exception will be thrown. Of course, the DB name of this schema MAY be different.
* </i></p>
*
* <p><i><b>Important note:</b>
* This function may modify the given {@link TAPTable} object if schemas are not supported by this connection.
* In this case, this function will prefix the table's DB name by the schema's DB name directly inside the given
* {@link TAPTable} object. Then the DB name of the schema will be set to NULL.
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
* </i></p>
*
* <p><i>Note:
* This implementation is able to drop only one uploaded table. So if this function finds more than one table matching to the given one,
* an exception will be thrown and no table will be dropped.
* </i></p>
*
* @see tap.db.DBConnection#dropUploadedTable(tap.metadata.TAPTable)
* @see #checkUploadedTableDef(TAPTable)
*/
@Override
public boolean dropUploadedTable(final TAPTable tableDef) throws DBException{
// If no table to upload, consider it has been dropped and return TRUE:
if (tableDef == null)
return true;
// Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD):
checkUploadedTableDef(tableDef);
Statement stmt = null;
try{
// Check the existence of the table to drop:
if (!isTableExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), connection.getMetaData()))
return true;
// Execute the update:
stmt = connection.createStatement();
gmantele
committed
int cnt = stmt.executeUpdate("DROP TABLE " + translator.getTableName(tableDef, supportsSchema) + ";");
// Log the end:
if (logger != null){
gmantele
committed
if (cnt >= 0)
logger.logDB(LogLevel.INFO, this, "TABLE_DROPPED", "Table \"" + tableDef.getADQLName() + "\" (in DB: " + translator.getTableName(tableDef, supportsSchema) + ") dropped.", null);
else
gmantele
committed
logger.logDB(LogLevel.ERROR, this, "TABLE_DROPPED", "Table \"" + tableDef.getADQLName() + "\" (in DB: " + translator.getTableName(tableDef, supportsSchema) + ") NOT dropped.", null);
}
// Ensure the update is successful:
gmantele
committed
return (cnt >= 0);
}catch(SQLException se){
if (logger != null)
gmantele
committed
logger.logDB(LogLevel.WARNING, this, "DROP_UPLOAD_TABLE", "Impossible to drop the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se);
throw new DBException("Impossible to drop the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se);
}finally{
close(stmt);
}
}
/**
* <p>Ensures that the given table MUST be inside the upload schema in ADQL.</p>
*
* <p>Thus, the following cases are taken into account:</p>
* <ul>
* <li>
* The schema name of the given table MUST be {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD") in ADQL.
* If it has another ADQL name, an exception will be thrown. Of course, the DB name of this schema MAY be different.
* </li>
* <li>
* If schemas are not supported by this connection, this function will prefix the table DB name by the schema DB name directly
* inside the given {@link TAPTable} object. Then the DB name of the schema will be set to NULL.
* </li>
* </ul>
*
* @param tableDef Definition of the table to create/drop.
*
* @throws DBException If the given table is not in a schema
* or if the ADQL name of this schema is not {@link STDSchema#UPLOADSCHEMA} ("TAP_UPLOAD").
*/
protected void checkUploadedTableDef(final TAPTable tableDef) throws DBException{
// If the table has no defined schema or if the ADQL name of the schema is not TAP_UPLOAD, throw an exception:
if (tableDef.getSchema() == null || !tableDef.getSchema().getADQLName().equals(STDSchema.UPLOADSCHEMA.label))
throw new DBException("Missing upload schema! An uploaded table must be inside a schema whose the ADQL name is strictly equals to \"" + STDSchema.UPLOADSCHEMA.label + "\" (but the DB name may be different).");
gmantele
committed
if (!supportsSchema){
if (tableDef.getADQLSchemaName() != null && tableDef.getADQLSchemaName().trim().length() > 0 && !tableDef.getDBName().startsWith(tableDef.getADQLSchemaName() + "_"))
tableDef.setDBName(tableDef.getADQLSchemaName() + "_" + tableDef.getDBName());
if (tableDef.getSchema() != null)
tableDef.getSchema().setDBName(null);
}
}
/* ************** */
/* TOOL FUNCTIONS */
/* ************** */
/**
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
* <p>Convert the given TAP type into the corresponding DBMS column type.</p>
*
* <p>
* This function tries first the type conversion using the translator ({@link JDBCTranslator#convertTypeToDB(DBType)}).
* If it fails, a default conversion is done considering all the known types of the following DBMS:
* PostgreSQL, SQLite, MySQL, Oracle and JavaDB/Derby.
* </p>
*
* @param type TAP type to convert.
*
* @return The corresponding DBMS type.
*
* @see JDBCTranslator#convertTypeToDB(DBType)
* @see #defaultTypeConversion(DBType)
*/
protected String convertTypeToDB(final DBType type){
String dbmsType = translator.convertTypeToDB(type);
return (dbmsType == null) ? defaultTypeConversion(type) : dbmsType;
}
/**
* <p>Get the DBMS compatible datatype corresponding to the given column {@link DBType}.</p>
*
* <p><i>Note 1:
* This function is able to generate a DB datatype compatible with the currently used DBMS.
* In this current implementation, only Postgresql, Oracle, SQLite, MySQL and Java DB/Derby have been considered.
* Most of the TAP types have been tested only with Postgresql and SQLite without any problem.
* If the DBMS you are using has not been considered, note that this function will return the TAP type expression by default.
* </i></p>
*
* <p><i>Note 2:
* In case the given datatype is NULL or not managed here, the DBMS type corresponding to "VARCHAR" will be returned.
* </i></p>
*
* <p><i>Note 3:
* The special TAP types POINT and REGION are converted into the DBMS type corresponding to "VARCHAR".
* </i></p>
*
* @param datatype Column TAP type.
*
* @return The corresponding DB type, or NULL if the given type is not managed or is NULL.
*/
protected String defaultTypeConversion(DBType datatype){
if (datatype == null)
datatype = new DBType(DBDatatype.VARCHAR);
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
switch(datatype.type){
case SMALLINT:
return dbms.equals("sqlite") ? "INTEGER" : "SMALLINT";
case INTEGER:
case REAL:
return datatype.type.toString();
case BIGINT:
if (dbms.equals("oracle"))
return "NUMBER(19,0)";
else if (dbms.equals("sqlite"))
return "INTEGER";
else
return "BIGINT";
case DOUBLE:
if (dbms.equals("postgresql") || dbms.equals("oracle"))
return "DOUBLE PRECISION";
else if (dbms.equals("sqlite"))
return "REAL";
else
return "DOUBLE";
case BINARY:
if (dbms.equals("postgresql"))
return "bytea";
else if (dbms.equals("sqlite"))
return "BLOB";
else if (dbms.equals("oracle"))
return "RAW" + (datatype.length > 0 ? "(" + datatype.length + ")" : "");
else if (dbms.equals("derby"))
return "CHAR" + (datatype.length > 0 ? "(" + datatype.length + ")" : "") + " FOR BIT DATA";
else