Newer
Older
* </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 synchronized 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;
// Starting of new query execution => disable the cancel flag:
resetCancel();
// Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD):
checkUploadedTableDef(tableDef);
try{
// Start a transaction:
startTransaction();
// ...create a statement:
getStatement();
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:
int nbUploadedRows = fillUploadedTable(tableDef, data);
// Commit the transaction:
commit();
// Log the end:
if (logger != null)
logger.logDB(LogLevel.INFO, this, "TABLE_CREATED", "Table \"" + tableDef.getADQLName() + "\" (in DB: " + translator.getTableName(tableDef, supportsSchema) + ") created (" + nbUploadedRows + " rows).", null);
return true;
}catch(SQLException se){
rollback();
if (!isCancelled() && 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{
closeStatement();
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(" (");
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
// ...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;
/* NULL CHARACTER CASE (JUST FOR POSTGRESQL) */
else if ((dbms == null || dbms.equalsIgnoreCase(DBMS_POSTGRES)) && val instanceof Character && (Character)val == 0x00)
val = null;
}
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.
* </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 synchronized 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;
// Starting of new query execution => disable the cancel flag:
resetCancel();
// Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD):
checkUploadedTableDef(tableDef);
try{
// Check the existence of the table to drop:
if (!isTableExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), connection.getMetaData()))
return true;
// Execute the update:
int cnt = getStatement().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 (!isCancelled() && 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{
closeStatement();
}
}
/**
* <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 */
/* ************** */
/**
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
* <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 VARCHAR if the given type is not managed or is NULL.
*/
protected String defaultTypeConversion(DBType datatype){
if (datatype == null)
datatype = new DBType(DBDatatype.VARCHAR);
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
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
return datatype.type.toString();
case VARBINARY:
if (dbms.equals("postgresql"))
return "bytea";
else if (dbms.equals("sqlite"))
return "BLOB";
else if (dbms.equals("oracle"))
return "LONG RAW" + (datatype.length > 0 ? "(" + datatype.length + ")" : "");
else if (dbms.equals("derby"))
return "VARCHAR" + (datatype.length > 0 ? "(" + datatype.length + ")" : "") + " FOR BIT DATA";
else
return datatype.type.toString();
case CHAR:
if (dbms.equals("sqlite"))
return "TEXT";
else
return "CHAR";
case BLOB:
if (dbms.equals("postgresql"))
return "bytea";
else
return "BLOB";
case CLOB:
if (dbms.equals("postgresql") || dbms.equals("mysql") || dbms.equals("sqlite"))
return "TEXT";
else
return "CLOB";
case TIMESTAMP:
if (dbms.equals("sqlite"))
return "TEXT";
else
return "TIMESTAMP";
case POINT:
case REGION:
case VARCHAR:
default:
if (dbms.equals("sqlite"))
return "TEXT";
else
return "VARCHAR";
}
}
/**
* <p>Start a transaction.</p>
*
* <p>
* Basically, if transactions are supported by this connection, the flag AutoCommit is just turned off.
* It will be turned on again when {@link #endTransaction()} is called.
* </p>
*
* <p>If transactions are not supported by this connection, nothing is done.</p>
*
* <p><b><i>Important note:</b>
* If any error interrupts the START TRANSACTION operation, transactions will be afterwards considered as not supported by this connection.
* So, subsequent call to this function (and any other transaction related function) will never do anything.
* </i></p>
*
* @throws DBException If it is impossible to start a transaction though transactions are supported by this connection.
* If these are not supported, this error can never be thrown.
*/
protected void startTransaction() throws DBException{
try{
if (supportsTransaction){
connection.setAutoCommit(false);
if (logger != null)
logger.logDB(LogLevel.INFO, this, "START_TRANSACTION", "Transaction STARTED.", null);
}
}catch(SQLException se){
supportsTransaction = false;
if (logger != null)
logger.logDB(LogLevel.ERROR, this, "START_TRANSACTION", "Transaction STARTing impossible!", se);
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
throw new DBException("Transaction STARTing impossible!", se);
}
}
/**
* <p>Commit the current transaction.</p>
*
* <p>
* {@link #startTransaction()} must have been called before. If it's not the case the connection
* may throw a {@link SQLException} which will be transformed into a {@link DBException} here.
* </p>
*
* <p>If transactions are not supported by this connection, nothing is done.</p>
*
* <p><b><i>Important note:</b>
* If any error interrupts the COMMIT operation, transactions will be afterwards considered as not supported by this connection.
* So, subsequent call to this function (and any other transaction related function) will never do anything.
* </i></p>
*
* @throws DBException If it is impossible to commit a transaction though transactions are supported by this connection..
* If these are not supported, this error can never be thrown.
*/
protected void commit() throws DBException{
try{
if (supportsTransaction){
connection.commit();
if (logger != null)
logger.logDB(LogLevel.INFO, this, "COMMIT", "Transaction COMMITED.", null);
}
}catch(SQLException se){
supportsTransaction = false;
if (logger != null)
logger.logDB(LogLevel.ERROR, this, "COMMIT", "Transaction COMMIT impossible!", se);
throw new DBException("Transaction COMMIT impossible!", se);
}
}
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
/**
* <p>Rollback the current transaction.
* The success or the failure of the rollback operation is always logged (except if no logger is available).</p>
*
* <p>
* {@link #startTransaction()} must have been called before. If it's not the case the connection
* may throw a {@link SQLException} which will be transformed into a {@link DBException} here.
* </p>
*
* <p>If transactions are not supported by this connection, nothing is done.</p>
*
* <p><b><i>Important note:</b>
* If any error interrupts the ROLLBACK operation, transactions will considered afterwards as not supported by this connection.
* So, subsequent call to this function (and any other transaction related function) will never do anything.
* </i></p>
*
* @throws DBException If it is impossible to rollback a transaction though transactions are supported by this connection..
* If these are not supported, this error can never be thrown.
*
* @see #rollback(boolean)
*/
protected final void rollback(){
rollback(true);
}
/**
* <p>Rollback the current transaction.</p>
*
* <p>
* {@link #startTransaction()} must have been called before. If it's not the case the connection
* may throw a {@link SQLException} which will be transformed into a {@link DBException} here.
* </p>
*
* <p>If transactions are not supported by this connection, nothing is done.</p>
*
* <p><b><i>Important note:</b>
* If any error interrupts the ROLLBACK operation, transactions will considered afterwards as not supported by this connection.
* So, subsequent call to this function (and any other transaction related function) will never do anything.
* </i></p>
*
* @param log <code>true</code> to log the success/failure of the rollback operation,
* <code>false</code> to be quiet whatever happens.
*
* @throws DBException If it is impossible to rollback a transaction though transactions are supported by this connection..
* If these are not supported, this error can never be thrown.
*/
protected void rollback(final boolean log){
try{
if (supportsTransaction && !connection.getAutoCommit()){
connection.rollback();
if (log && logger != null)
logger.logDB(LogLevel.INFO, this, "ROLLBACK", "Transaction ROLLBACKED.", null);
}
}catch(SQLException se){
supportsTransaction = false;
if (log && logger != null)
logger.logDB(LogLevel.ERROR, this, "ROLLBACK", "Transaction ROLLBACK impossible!", se);
}
}
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
/**
* <p>End the current transaction.
* The success or the failure of the transaction ending operation is always logged (except if no logger is available).</p>
*
* <p>
* Basically, if transactions are supported by this connection, the flag AutoCommit is just turned on.
* </p>
*
* <p>If transactions are not supported by this connection, nothing is done.</p>
*
* <p><b><i>Important note:</b>
* If any error interrupts the END TRANSACTION operation, transactions will be afterwards considered as not supported by this connection.
* So, subsequent call to this function (and any other transaction related function) will never do anything.
* </i></p>
*
* @throws DBException If it is impossible to end a transaction though transactions are supported by this connection.
* If these are not supported, this error can never be thrown.
*
* @see #endTransaction(boolean)
*/
protected final void endTransaction(){
endTransaction(true);
}
/**
* <p>End the current transaction.</p>
*
* <p>
* Basically, if transactions are supported by this connection, the flag AutoCommit is just turned on.
* </p>
*
* <p>If transactions are not supported by this connection, nothing is done.</p>
*
* <p><b><i>Important note:</b>
* If any error interrupts the END TRANSACTION operation, transactions will be afterwards considered as not supported by this connection.
* So, subsequent call to this function (and any other transaction related function) will never do anything.
* </i></p>
*
* @param log <code>true</code> to log the success/failure of the transaction ending operation,
* <code>false</code> to be quiet whatever happens.
*
* @throws DBException If it is impossible to end a transaction though transactions are supported by this connection.
* If these are not supported, this error can never be thrown.
*/
protected void endTransaction(final boolean log){
try{
if (supportsTransaction){
connection.setAutoCommit(true);
if (log && logger != null)
logger.logDB(LogLevel.INFO, this, "END_TRANSACTION", "Transaction ENDED.", null);
}
}catch(SQLException se){
supportsTransaction = false;
if (log && logger != null)
logger.logDB(LogLevel.ERROR, this, "END_TRANSACTION", "Transaction ENDing impossible!", se);
}
}
/**
* <p>Close silently the given {@link ResultSet}.</p>
*
* <p>If the given {@link ResultSet} is NULL, nothing (even exception/error) happens.</p>
*
* <p>
* If any {@link SQLException} occurs during this operation, it is caught and just logged
* (see {@link TAPLog#logDB(uws.service.log.UWSLog.LogLevel, DBConnection, String, String, Throwable)}).
* No error is thrown and nothing else is done.
* </p>
*
* @param rs {@link ResultSet} to close.
*/
protected final void close(final ResultSet rs){
try{
if (rs != null)
rs.close();
}catch(SQLException se){
if (logger != null)
logger.logDB(LogLevel.WARNING, this, "CLOSE", "Can not close a ResultSet!", null);
}
}
/**
* <p>Close silently the given {@link Statement}.</p>
*
* <p>If the given {@link Statement} is NULL, nothing (even exception/error) happens.</p>
*
* <p>
* The given statement is explicitly canceled by this function before being closed.
* Thus the corresponding DBMS process is ensured to be stopped. Of course, this
* cancellation is effective only if this operation is supported by the JDBC driver
* and the DBMS.
* </p>
*
* <p><b>Important note:</b>
* In case of cancellation, <b>NO</b> rollback is performed.
* </p>
*
* <p>
* If any {@link SQLException} occurs during this operation, it is caught and just logged
* (see {@link TAPLog#logDB(uws.service.log.UWSLog.LogLevel, DBConnection, String, String, Throwable)}).
* No error is thrown and nothing else is done.
* </p>
*
* @param stmt {@link Statement} to close.
*
* @see #cancel(Statement, boolean)
*/
protected final void close(final Statement stmt){
try{
if (stmt != null){
cancel(stmt, false);
stmt.close();
}catch(SQLException se){
if (logger != null)
logger.logDB(LogLevel.WARNING, this, "CLOSE", "Can not close a Statement!", null);
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
}
}
/**
* <p>Transform the given column value in a boolean value.</p>
*
* <p>The following cases are taken into account in function of the given value's type:</p>
* <ul>
* <li><b>NULL</b>: <i>false</i> is always returned.</li>
*
* <li><b>{@link Boolean}</b>: the boolean value is returned as provided (but casted in boolean).</li>
*
* <li><b>{@link Integer}</b>: <i>true</i> is returned only if the integer value is strictly greater than 0, otherwise <i>false</i> is returned.</li>
*
* <li><b>Other</b>: toString().trim() is first called on this object. Then, an integer value is tried to be extracted from it.
* If it succeeds, the previous rule is applied. If it fails, <i>true</i> will be returned only if the string is "t" or "true" (case insensitively).</li>
* </ul>
*
* @param colValue The column value to transform in boolean.
*
* @return Its corresponding boolean value.
*/
protected final boolean toBoolean(final Object colValue){
// NULL => false:
if (colValue == null)
return false;
// Boolean value => cast in boolean and return this value:
else if (colValue instanceof Boolean)
return ((Boolean)colValue).booleanValue();
// Integer value => cast in integer and return true only if the value is positive and not null:
else if (colValue instanceof Integer){
int intFlag = ((Integer)colValue).intValue();
return (intFlag > 0);
}
// Otherwise => get the string representation and:
// 1/ try to cast it into an integer and apply the same test as before
// 2/ if the cast fails, return true only if the value is "t" or "true" (case insensitively):
else{
String strFlag = colValue.toString().trim();
try{
int intFlag = Integer.parseInt(strFlag);
return (intFlag > 0);
}catch(NumberFormatException nfe){
return strFlag.equalsIgnoreCase("t") || strFlag.equalsIgnoreCase("true");
}
}
}
/**
* Return NULL if the given column value is an empty string (or it just contains space characters) or NULL.
gmantele
committed
* Otherwise the given string is returned as provided.
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
*
* @param dbValue Value to nullify if needed.
*
* @return NULL if the given string is NULL or empty, otherwise the given value.
*/
protected final String nullifyIfNeeded(final String dbValue){
return (dbValue != null && dbValue.trim().length() <= 0) ? null : dbValue;
}
/**
* Search a {@link TAPTable} instance whose the ADQL name matches (case sensitively) to the given one.
*
* @param tableName ADQL name of the table to search.
* @param itTables Iterator over the set of tables in which the research must be done.
*
* @return The found table, or NULL if not found.
*/
private TAPTable searchTable(String tableName, final Iterator<TAPTable> itTables){
// Get the schema name, if any prefix the given table name:
String schemaName = null;
int indSep = tableName.indexOf('.');
if (indSep > 0){
schemaName = tableName.substring(0, indSep);
tableName = tableName.substring(indSep + 1);
}
// Search by schema name (if any) and then by table name:
while(itTables.hasNext()){
// get the table:
TAPTable table = itTables.next();
// test the schema name (if one was prefixing the table name) (case sensitively):
if (schemaName != null){
if (table.getADQLSchemaName() == null || !schemaName.equals(table.getADQLSchemaName()))
continue;
}
// test the table name (case sensitively):
if (tableName.equals(table.getADQLName()))
return table;
}
// NULL if no table matches:
return null;
}
/**
* <p>Tell whether the specified schema exists in the database.
* To do so, it is using the given {@link DatabaseMetaData} object to query the database and list all existing schemas.</p>
*
* <p><i>Note:
* This function is completely useless if the connection is not supporting schemas.
* </i></p>
*
* <p><i>Note:
* Test on the schema name is done considering the case sensitivity indicated by the translator
* (see {@link JDBCTranslator#isCaseSensitive(IdentifierField)}).
* </i></p>
*
* <p><i>Note:
* This functions is used by {@link #addUploadedTable(TAPTable, TableIterator)} and {@link #resetTAPSchema(Statement, TAPTable[])}.
* </i></p>
*
* @param schemaName DB name of the schema whose the existence must be checked.
* @param dbMeta Metadata about the database, and mainly the list of all existing schemas.
*
* @return <i>true</i> if the specified schema exists, <i>false</i> otherwise.
*
* @throws SQLException If any error occurs while interrogating the database about existing schema.
*/
protected boolean isSchemaExisting(String schemaName, final DatabaseMetaData dbMeta) throws SQLException{
gmantele
committed
if (!supportsSchema || schemaName == null || schemaName.length() == 0)
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
return true;
// Determine the case sensitivity to use for the equality test:
boolean caseSensitive = translator.isCaseSensitive(IdentifierField.SCHEMA);
ResultSet rs = null;
try{
// List all schemas available and stop when a schema name matches ignoring the case:
rs = dbMeta.getSchemas();
boolean hasSchema = false;
while(!hasSchema && rs.next())
hasSchema = equals(rs.getString(1), schemaName, caseSensitive);
return hasSchema;
}finally{
close(rs);
}
}
/**
* <p>Tell whether the specified table exists in the database.
* To do so, it is using the given {@link DatabaseMetaData} object to query the database and list all existing tables.</p>
*
* <p><i><b>Important note:</b>
* If schemas are not supported by this connection but a schema name is even though provided in parameter,
* the table name will be prefixed by the schema name.
* The research will then be done with NULL as schema name and this prefixed table name.
* </i></p>
*
* <p><i>Note:
* Test on the schema name is done considering the case sensitivity indicated by the translator
* (see {@link JDBCTranslator#isCaseSensitive(IdentifierField)}).
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
* </i></p>
*
* <p><i>Note:
* This function is used by {@link #addUploadedTable(TAPTable, TableIterator)} and {@link #dropUploadedTable(TAPTable)}.
* </i></p>
*
* @param schemaName DB name of the schema in which the table to search is. <i>If NULL, the table is expected in any schema but ONLY one MUST exist.</i>
* @param tableName DB name of the table to search.
* @param dbMeta Metadata about the database, and mainly the list of all existing tables.
*
* @return <i>true</i> if the specified table exists, <i>false</i> otherwise.
*
* @throws SQLException If any error occurs while interrogating the database about existing tables.
*/
protected boolean isTableExisting(String schemaName, String tableName, final DatabaseMetaData dbMeta) throws DBException, SQLException{
if (tableName == null || tableName.length() == 0)
return true;
// Determine the case sensitivity to use for the equality test:
boolean schemaCaseSensitive = translator.isCaseSensitive(IdentifierField.SCHEMA);
boolean tableCaseSensitive = translator.isCaseSensitive(IdentifierField.TABLE);
ResultSet rs = null;
try{
// List all matching tables:
if (supportsSchema){
String schemaPattern = schemaCaseSensitive ? schemaName : null;
String tablePattern = tableCaseSensitive ? tableName : null;
rs = dbMeta.getTables(null, schemaPattern, tablePattern, null);
}else{
String tablePattern = tableCaseSensitive ? tableName : null;
rs = dbMeta.getTables(null, null, tablePattern, null);
}
// Stop on the first table which match completely (schema name + table name in function of their respective case sensitivity):
int cnt = 0;
while(rs.next()){
String rsSchema = nullifyIfNeeded(rs.getString(2));
String rsTable = rs.getString(3);
if (!supportsSchema || schemaName == null || equals(rsSchema, schemaName, schemaCaseSensitive)){
if (equals(rsTable, tableName, tableCaseSensitive))
cnt++;
}
}
if (cnt > 1){
if (logger != null)
logger.logDB(LogLevel.ERROR, this, "TABLE_EXIST", "More than one table match to these criteria (schema=" + schemaName + " (case sensitive?" + schemaCaseSensitive + ") && table=" + tableName + " (case sensitive?" + tableCaseSensitive + "))!", null);
throw new DBException("More than one table match to these criteria (schema=" + schemaName + " (case sensitive?" + schemaCaseSensitive + ") && table=" + tableName + " (case sensitive?" + tableCaseSensitive + "))!");
}
return cnt == 1;
}finally{
close(rs);
}
}
/**
gmantele
committed
* <p>Tell whether the specified column exists in the specified table of the database.
* To do so, it is using the given {@link DatabaseMetaData} object to query the database and list all existing columns.</p>
*
* <p><i><b>Important note:</b>
* If schemas are not supported by this connection but a schema name is even though provided in parameter,
* the table name will be prefixed by the schema name.
gmantele
committed
* The research will then be done with NULL as schema name and this prefixed table name.
* </i></p>
*
* <p><i>Note:
* Test on the schema name is done considering the case sensitivity indicated by the translator
* (see {@link JDBCTranslator#isCaseSensitive(IdentifierField)}).
gmantele
committed
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
* </i></p>
*
* <p><i>Note:
* This function is used by {@link #loadSchemas(TAPTable, TAPMetadata, Statement)}, {@link #loadTables(TAPTable, TAPMetadata, Statement)}
* and {@link #loadColumns(TAPTable, List, Statement)}.
* </i></p>
*
* @param schemaName DB name of the table schema. <i>MAY BE NULL</i>
* @param tableName DB name of the table containing the column to search. <i>MAY BE NULL</i>
* @param columnName DB name of the column to search.
* @param dbMeta Metadata about the database, and mainly the list of all existing tables.
*
* @return <i>true</i> if the specified column exists, <i>false</i> otherwise.
*
* @throws SQLException If any error occurs while interrogating the database about existing columns.
*/
protected boolean isColumnExisting(String schemaName, String tableName, String columnName, final DatabaseMetaData dbMeta) throws DBException, SQLException{
if (columnName == null || columnName.length() == 0)
return true;
// Determine the case sensitivity to use for the equality test:
boolean schemaCaseSensitive = translator.isCaseSensitive(IdentifierField.SCHEMA);
boolean tableCaseSensitive = translator.isCaseSensitive(IdentifierField.TABLE);
boolean columnCaseSensitive = translator.isCaseSensitive(IdentifierField.COLUMN);
ResultSet rsT = null, rsC = null;
try{
/* Note:
*
* The DatabaseMetaData.getColumns(....) function does not work properly
* with the SQLite driver: when all parameters are set to null, meaning all columns of the database
* must be returned, absolutely no rows are selected.
*
* The solution proposed here, is to first search all (matching) tables, and then for each table get
* all its columns and find the matching one(s).
*/
// List all matching tables:
if (supportsSchema){
String schemaPattern = schemaCaseSensitive ? schemaName : null;
String tablePattern = tableCaseSensitive ? tableName : null;
rsT = dbMeta.getTables(null, schemaPattern, tablePattern, null);
}else{
String tablePattern = tableCaseSensitive ? tableName : null;
rsT = dbMeta.getTables(null, null, tablePattern, null);
}
// For each matching table:
int cnt = 0;
String columnPattern = columnCaseSensitive ? columnName : null;
while(rsT.next()){
String rsSchema = nullifyIfNeeded(rsT.getString(2));
String rsTable = rsT.getString(3);
// test the schema name:
if (!supportsSchema || schemaName == null || equals(rsSchema, schemaName, schemaCaseSensitive)){
// test the table name:
if ((tableName == null || equals(rsTable, tableName, tableCaseSensitive))){
// list its columns:
rsC = dbMeta.getColumns(null, rsSchema, rsTable, columnPattern);
// count all matching columns:
while(rsC.next()){
String rsColumn = rsC.getString(4);
if (equals(rsColumn, columnName, columnCaseSensitive))
cnt++;
}
close(rsC);
}
}
}
if (cnt > 1){
if (logger != null)
logger.logDB(LogLevel.ERROR, this, "COLUMN_EXIST", "More than one column match to these criteria (schema=" + schemaName + " (case sensitive?" + schemaCaseSensitive + ") && table=" + tableName + " (case sensitive?" + tableCaseSensitive + ") && column=" + columnName + " (case sensitive?" + columnCaseSensitive + "))!", null);
throw new DBException("More than one column match to these criteria (schema=" + schemaName + " (case sensitive?" + schemaCaseSensitive + ") && table=" + tableName + " (case sensitive?" + tableCaseSensitive + ") && column=" + columnName + " (case sensitive?" + columnCaseSensitive + "))!");
}
return cnt == 1;
}finally{
close(rsT);
close(rsC);
}
}
/*
* <p>Build a table prefix with the given schema name.</p>
*
* <p>By default, this function returns: schemaName + "_".</p>
*
* <p><b>CAUTION:
* This function is used only when schemas are not supported by the DBMS connection.
* It aims to propose an alternative of the schema notion by prefixing the table name by the schema name.
* </b></p>
*
* <p><i>Note:
* If the given schema is NULL or is an empty string, an empty string will be returned.
* Thus, no prefix will be set....which is very useful when the table name has already been prefixed
* (in such case, the DB name of its schema has theoretically set to NULL).
* </i></p>
*
* @param schemaName (DB) Schema name.
*
* @return The corresponding table prefix, or "" if the given schema name is an empty string or NULL.