diff --git a/build.gradle b/build.gradle index 9954559ff87a4596adf6a795de9b4ab7a593ec69..4ff3b0ded54db31428a4cb62bcfef0958eb6a392 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ dependencies { testCompile 'junit:junit:4.12' testCompile 'com.h2database:h2:1.4.193' - testCompile fileTree(dir: 'lib', include: 'astroh2-0.3.jar') + testCompile fileTree(dir: 'lib', include: 'astroh2-0.4.jar') testCompile 'org.slf4j:slf4j-simple:1.7.25' testRuntime 'simple-jndi:simple-jndi:0.11.4.1' diff --git a/lib/astroh2-0.3.jar b/lib/astroh2-0.3.jar deleted file mode 100644 index a79ab71479dcd4a521e9243bd63f5c7bd47911d8..0000000000000000000000000000000000000000 Binary files a/lib/astroh2-0.3.jar and /dev/null differ diff --git a/lib/astroh2-0.4.jar b/lib/astroh2-0.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..e84fe823837af2b0a9a8954da777f99d460c9f11 Binary files /dev/null and b/lib/astroh2-0.4.jar differ diff --git a/src/adql/db/DBColumn.java b/src/adql/db/DBColumn.java index 89f48b6a4f0d763e79463038bc9cdf05b672ea77..07988bfd0830e5c65002c5d233f10f30fceca177 100644 --- a/src/adql/db/DBColumn.java +++ b/src/adql/db/DBColumn.java @@ -2,78 +2,91 @@ package adql.db; /* * This file is part of ADQLLibrary. - * + * * ADQLLibrary is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * ADQLLibrary is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2011-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * + * Copyright 2011-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ /** - * <p>Definition of a valid target column.</p> - * + * Definition of a valid target column. + * * <p> - * This column can be used in an ADQL query with its ADQL name ({@link #getADQLName()}) - * and corresponds to a real column in the "database" with its DB name ({@link #getDBName()}). + * This column can be used in an ADQL query with its ADQL name + * ({@link #getADQLName()}) and corresponds to a real column in the "database" + * with its DB name ({@link #getDBName()}). * </p> - * + * * @author Grégory Mantelet (CDS;ARI) - * @version 1.4 (07/2016) + * @version 2.0 (09/2019) */ public interface DBColumn { /** * Gets the name of this column (without any prefix and double-quotes). - * + * * @return Its ADQL name. */ public String getADQLName(); + /** + * Tell whether the column name used in ADQL queries must be delimited + * (i.e. surrounded by <code>"</code>). In such case, it will be case + * sensitive. + * + * @return <code>true</code> if the ADQL column name is case sensitive, + * <code>false</code> otherwise. + * + * @since 2.0 + */ + public boolean isCaseSensitive(); + /** * Gets the name of this column in the "database". - * + * * @return Its DB name. */ public String getDBName(); /** * <p>Get the type of this column (as closed as possible from the "database" type).</p> - * + * * <p><i>Note: * The returned type should be as closed as possible from a type listed by the IVOA in the TAP protocol description into the section UPLOAD. * </i></p> - * + * * @return Its type. - * + * * @since 1.3 */ public DBType getDatatype(); /** * Gets the table which contains this {@link DBColumn}. - * + * * @return Its table or <i>null</i> if no table is specified. */ public DBTable getTable(); /** * Makes a copy of this instance of {@link DBColumn}. - * + * * @param dbName Its new DB name. * @param adqlName Its new ADQL name. * @param dbTable Its new table. - * + * * @return A modified copy of this {@link DBColumn}. */ public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable); diff --git a/src/adql/db/DBCommonColumn.java b/src/adql/db/DBCommonColumn.java index 44c6642ad34a01ddf654ec1d99c018136650d3d5..f86ca9f2d782f5e9efb74f47a17ee1ff9528e1b4 100644 --- a/src/adql/db/DBCommonColumn.java +++ b/src/adql/db/DBCommonColumn.java @@ -2,21 +2,22 @@ package adql.db; /* * This file is part of ADQLLibrary. - * + * * ADQLLibrary is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * ADQLLibrary is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2014-2015 - Astronomisches Rechen Institut (ARI) + * + * Copyright 2014-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ import java.util.ArrayList; @@ -26,15 +27,19 @@ import adql.db.exception.UnresolvedJoinException; import adql.query.ADQLQuery; /** - * This is a special column which exists only after a NATURAL JOIN or a JOIN ... USING between two tables. - * It lets unify several columns of the joined tables in a single column. - * - * Thus, the writer of an ADQL query can use the column name without table prefix (since after the join there will be only one) - * or with a prefix table of the joined tables. The list of all covered tables is stored in this object and can be extended - * in case of several JOINs. - * - * @author Grégory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de - * @version 1.3 (05/2015) + * This is a special column which exists only after a NATURAL JOIN or + * a JOIN ... USING between two tables. It lets unify several columns of the + * joined tables in a single column. + * + * <p> + * Thus, the writer of an ADQL query can use the column name without table + * prefix (since after the join there will be only one) or with a prefix table + * of the joined tables. The list of all covered tables is stored in this + * object and can be extended in case of several JOINs. + * </p> + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (09/2019) * @since 1.2 */ public class DBCommonColumn implements DBColumn { @@ -44,27 +49,42 @@ public class DBCommonColumn implements DBColumn { /** * Create a column which merges both of the given columns. - * - * This special {@link DBColumn} implementation is not associated with one table, - * and can be listed in a {@link DBTable} ONLY IF the latter is the result of a sub-query (see {@link ADQLQuery#getResultingColumns()}). - * - * A column resulting from a tables join is common only to the joined tables. That's why a list of all tables covered - * by this column is created or update at each merge. It can be accessed thanks to {@link #getCoveredTables()}. - * - * Note: In the case one or both of the columns to join are {@link DBCommonColumn}, the list of their covered tables are also merged. - * - * @param leftCol Column of the left join table. May be a {@link DBCommonColumn}. - * @param rightCol Column of the right join table. May be a {@link DBCommonColumn}. - * - * @throws UnresolvedJoinException If the type of the two given columns are not roughly (just testing numeric, string or geometry) compatible. + * + * <p> + * This special {@link DBColumn} implementation is not associated with one + * table, and can be listed in a {@link DBTable} ONLY IF the latter is the + * result of a sub-query (see {@link ADQLQuery#getResultingColumns()}). + * </p> + * + * <p> + * A column resulting from a tables join is common only to the joined + * tables. That's why a list of all tables covered by this column is + * created or update at each merge. It can be accessed thanks to + * {@link #getCoveredTables()}. + * </p> + * + * <p><i><b>Note:</b> + * In the case one or both of the columns to join are + * {@link DBCommonColumn}, the list of their covered tables are also + * merged. + * </i></p> + * + * @param leftCol Column of the left join table. + * May be a {@link DBCommonColumn}. + * @param rightCol Column of the right join table. + * May be a {@link DBCommonColumn}. + * + * @throws UnresolvedJoinException If the type of the two given columns are + * not roughly (just testing numeric, + * string or geometry) compatible. */ - public DBCommonColumn(final DBColumn leftCol, final DBColumn rightCol) throws UnresolvedJoinException{ + public DBCommonColumn(final DBColumn leftCol, final DBColumn rightCol) throws UnresolvedJoinException { // Test whether type of both columns are compatible: if (leftCol.getDatatype() != null && rightCol.getDatatype() != null && !leftCol.getDatatype().isCompatible(rightCol.getDatatype())) throw new UnresolvedJoinException("JOIN impossible: incompatible column types when trying to join the columns " + leftCol.getADQLName() + " (" + leftCol.getDatatype() + ") and " + rightCol.getADQLName() + " (" + rightCol.getDatatype() + ")!"); // LEFT COLUMN: - if (leftCol instanceof DBCommonColumn){ + if (leftCol instanceof DBCommonColumn) { // set the general column description: generalColumnDesc = ((DBCommonColumn)leftCol).generalColumnDesc; @@ -72,7 +92,7 @@ public class DBCommonColumn implements DBColumn { Iterator<DBTable> it = ((DBCommonColumn)leftCol).getCoveredTables(); while(it.hasNext()) addCoveredTable(it.next()); - }else{ + } else { // set the general column description: generalColumnDesc = leftCol.copy(leftCol.getDBName(), leftCol.getADQLName(), null); // add the table to cover: @@ -80,12 +100,12 @@ public class DBCommonColumn implements DBColumn { } // RIGHT COLUMN: - if (rightCol instanceof DBCommonColumn){ + if (rightCol instanceof DBCommonColumn) { // add all covered tables of the left common column: Iterator<DBTable> it = ((DBCommonColumn)rightCol).getCoveredTables(); while(it.hasNext()) addCoveredTable(it.next()); - }else{ + } else { // add the table to cover: addCoveredTable(rightCol.getTable()); } @@ -94,76 +114,93 @@ public class DBCommonColumn implements DBColumn { /** * Constructor by copy. * It returns a copy of this instance of {@link DBCommonColumn}. - * - * Note: The list of covered tables is NOT deeply copied! - * + * + * <p><i><b>Note:</b> + * The list of covered tables is NOT deeply copied! + * </i></p> + * * @param toCopy The {@link DBCommonColumn} to copy. * @param dbName The new DB name of this {@link DBCommonColumn}. * @param adqlName The new ADQL name of this {@link DBCommonColumn}. */ @SuppressWarnings("unchecked") - public DBCommonColumn(final DBCommonColumn toCopy, final String dbName, final String adqlName){ + public DBCommonColumn(final DBCommonColumn toCopy, final String dbName, final String adqlName) { generalColumnDesc = toCopy.generalColumnDesc.copy(dbName, adqlName, null); lstCoveredTables = (ArrayList<DBTable>)toCopy.lstCoveredTables.clone(); } @Override - public final String getADQLName(){ + public final String getADQLName() { return generalColumnDesc.getADQLName(); } @Override - public final String getDBName(){ + public final boolean isCaseSensitive() { + return generalColumnDesc.isCaseSensitive(); + } + + @Override + public final String getDBName() { return generalColumnDesc.getDBName(); } @Override - public final DBType getDatatype(){ + public final DBType getDatatype() { return generalColumnDesc.getDatatype(); } @Override - public final DBTable getTable(){ + public final DBTable getTable() { return null; } /** - * Get an iterator over the list of all tables covered by this common column. - * + * Get an iterator over the list of all tables covered by this common + * column. + * * @return Iterator over all covered tables. */ - public final Iterator<DBTable> getCoveredTables(){ + public final Iterator<DBTable> getCoveredTables() { return lstCoveredTables.iterator(); } /** * Add a table that this common column must cover from now. - * - * Warning: no unicity check is never done ! - * + * + * <p><i><b>Warning:</b> + * No unicity check is never done! + * </i></p> + * * @param table Table to add in the covered tables list. */ - protected void addCoveredTable(final DBTable table){ + protected void addCoveredTable(final DBTable table) { if (table != null) lstCoveredTables.add(table); } /** - * WARNING: This copy function does not make a real copy of this DBCommonColumn ! - * It returns a modified copy of the general column description it contains. - * - * Note: To make a real copy of this DBCommonColumn use the Constructor by copy {@link #DBCommonColumn(DBCommonColumn, String, String)}. - * + * <p><i><b>WARNING:</b> + * This copy function does not make a real copy of this DBCommonColumn! + * It returns a modified copy of the general column description it + * contains. + * </i></p> + * + * <p><i><b>Note:</b> + * To make a real copy of this DBCommonColumn use the Constructor by copy + * {@link #DBCommonColumn(DBCommonColumn, String, String)}. + * </i></p> + * * @param dbName Its new DB name. * @param adqlName Its new ADQL name. * @param dbTable Its new DBTable - * - * @return A modified copy of the general column description this common column represents. - * + * + * @return A modified copy of the general column description this common + * column represents. + * * @see adql.db.DBColumn#copy(java.lang.String, java.lang.String, adql.db.DBTable) */ @Override - public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable){ + public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable) { return generalColumnDesc.copy(dbName, adqlName, dbTable); } diff --git a/src/adql/db/DBTable.java b/src/adql/db/DBTable.java index 9f5c25c1797cc999ada5092d270a81fd35f6cc3b..8ff8dea37d38b85578fefeca7c40bcea2827cc50 100644 --- a/src/adql/db/DBTable.java +++ b/src/adql/db/DBTable.java @@ -2,93 +2,106 @@ package adql.db; /* * This file is part of ADQLLibrary. - * + * * ADQLLibrary is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * ADQLLibrary is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * + * Copyright 2012-2019- UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ /** - * <p>Definition of a valid target table.</p> - * + * Definition of a valid target table. + * * <p> - * This table can be used in an ADQL query with its ADQL name ({@link #getADQLName()}) - * and corresponds to a real table in the "database" with its DB name ({@link #getDBName()}). + * This table can be used in an ADQL query with its ADQL name + * ({@link #getADQLName()}) and corresponds to a real table in the "database" + * with its DB name ({@link #getDBName()}). * </p> - * + * * @author Grégory Mantelet (CDS;ARI) - * @version 1.4 (07/2016) + * @version 2.0 (09/2019) */ public interface DBTable extends Iterable<DBColumn> { /** * Gets the name of this table (without any prefix and double-quotes). - * + * * @return Its ADQL name. */ public String getADQLName(); + /** + * Tell whether the table name used in ADQL queries must be delimited + * (i.e. surrounded by <code>"</code>). In such case, it will be case + * sensitive. + * + * @return <code>true</code> if the ADQL table name is case sensitive, + * <code>false</code> otherwise. + * + * @since 2.0 + */ + public boolean isCaseSensitive(); + /** * Gets the name of this table in the "database". - * + * * @return Its DB name. */ public String getDBName(); /** * Gets the ADQL name of the schema which contains this table. - * + * * @return ADQL name of its schema. */ public String getADQLSchemaName(); /** * Gets the DB name of the schema which contains this table. - * + * * @return DB name of its schema. */ public String getDBSchemaName(); /** * Gets the ADQL name of the catalog which contains this table. - * + * * @return ADQL name of its catalog. */ public String getADQLCatalogName(); /** * Gets the DB name of the catalog which contains this table. - * + * * @return DB name of its catalog. */ public String getDBCatalogName(); /** * Gets the definition of the specified column if it exists in this table. - * + * * @param colName Name of the column <i>(may be the ADQL or DB name depending of the second parameter)</i>. * @param adqlName <i>true</i> means the given name is the ADQL name of the column and that the research must be done on the ADQL name of columns, * <i>false</i> means the same thing but with the DB name. - * + * * @return The corresponding column, or <i>null</i> if the specified column had not been found. */ public DBColumn getColumn(String colName, boolean adqlName); /** * <p>Makes a copy of this instance of {@link DBTable}, with the possibility to change the DB and ADQL names.</p> - * + * * <p><b>IMPORTANT:</b> * <b>The given DB and ADQL name may be NULL.</b> If NULL, the copy will contain exactly the same full name (DB and/or ADQL).<br/> * <b>And they may be qualified</b> (that's to say: prefixed by the schema name or by the catalog and schema name). It means that it is possible to @@ -99,14 +112,14 @@ public interface DBTable extends Iterable<DBColumn> { * <li><i>.copy(null, "foo") =></i> a copy with the same full DB name, but with no ADQL catalog and schema name and with an ADQL table name equals to "foo"</li> * <li><i>.copy("schema.table", ) =></i> a copy with the same full ADQL name, but with no DB catalog name, with a DB schema name equals to "schema" and with a DB table name equals to "table"</li> * </ul> - * + * * @param dbName Its new DB name. * It may be qualified. * It may also be NULL ; if so, the full DB name won't be different in the copy. * @param adqlName Its new ADQL name. * It may be qualified. * It may also be NULL ; if so, the full DB name won't be different in the copy. - * + * * @return A modified copy of this {@link DBTable}. */ public DBTable copy(final String dbName, final String adqlName); diff --git a/src/adql/db/DefaultDBColumn.java b/src/adql/db/DefaultDBColumn.java index 0336e24b865132c42460295f9ec2452f2eeac28c..f262b6f5d74a148acd00dcb602d96ebf262e8624 100644 --- a/src/adql/db/DefaultDBColumn.java +++ b/src/adql/db/DefaultDBColumn.java @@ -2,27 +2,27 @@ package adql.db; /* * This file is part of ADQLLibrary. - * + * * ADQLLibrary is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * ADQLLibrary is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. - * + * * Copyright 2012,2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ /** * Default implementation of {@link DBColumn}. - * + * * @author Grégory Mantelet (CDS;ARI) * @version 1.4 (08/2015) */ @@ -39,23 +39,28 @@ public class DefaultDBColumn implements DBColumn { /** Name that this column must have in ADQL queries. */ protected String adqlName = null; + /** Indicate whether the ADQL column name should be considered as case + * sensitive. + * @since 2.0 */ + protected boolean columnCaseSensitive = false; + /** * Builds a default {@link DBColumn} with the given DB name and DB table. - * + * * @param dbName Database column name (it will be also used for the ADQL name). * <b>Only the column name is expected. Contrary to {@link DefaultDBTable}, * if a whole column reference is given, no split will be done.</b> * @param table DB table which contains this column. - * + * * @see #DefaultDBColumn(String, String, DBType, DBTable) */ - public DefaultDBColumn(final String dbName, final DBTable table){ + public DefaultDBColumn(final String dbName, final DBTable table) { this(dbName, dbName, null, table); } /** * Builds a default {@link DBColumn} with the given DB name and DB table. - * + * * @param dbName Database column name (it will be also used for the ADQL name). * <b>Only the column name is expected. Contrary to {@link DefaultDBTable}, * if a whole column reference is given, no split will be done.</b> @@ -64,18 +69,18 @@ public class DefaultDBColumn implements DBColumn { * the type should be considered as unknown. It means that any comparison with * any type will always return 'true'.</i> * @param table DB table which contains this column. - * + * * @see #DefaultDBColumn(String, String, DBType, DBTable) - * + * * @since 1.3 */ - public DefaultDBColumn(final String dbName, final DBType type, final DBTable table){ + public DefaultDBColumn(final String dbName, final DBType type, final DBTable table) { this(dbName, dbName, type, table); } /** * Builds a default {@link DBColumn} with the given DB name, DB table and ADQL name. - * + * * @param dbName Database column name. * <b>Only the column name is expected. Contrary to {@link DefaultDBTable}, * if a whole column reference is given, no split will be done.</b> @@ -83,20 +88,20 @@ public class DefaultDBColumn implements DBColumn { * <b>Only the column name is expected. Contrary to {@link DefaultDBTable}, * if a whole column reference is given, no split will be done.</b> * @param table DB table which contains this column. - * + * * @see #DefaultDBColumn(String, String, DBType, DBTable) */ - public DefaultDBColumn(final String dbName, final String adqlName, final DBTable table){ + public DefaultDBColumn(final String dbName, final String adqlName, final DBTable table) { this(dbName, adqlName, null, table); } /** * Builds a default {@link DBColumn} with the given DB name, DB table and ADQL name. - * + * * @param dbName Database column name. * <b>Only the column name is expected. Contrary to {@link DefaultDBTable}, * if a whole column reference is given, no split will be done.</b> - * <b>REQUIRED parameter: it must be not NULL.</b> + * <b>REQUIRED parameter: it must be not NULL.</b> * @param adqlName Column name used in ADQL queries. * <b>Only the column name is expected. Contrary to {@link DefaultDBTable}, * if a whole column reference is given, no split will be done.</b> @@ -106,73 +111,103 @@ public class DefaultDBColumn implements DBColumn { * the type should be considered as unknown. It means that any comparison with * any type will always return 'true'.</i> * @param table DB table which contains this column. - * + * * @since 1.3 */ - public DefaultDBColumn(final String dbName, final String adqlName, final DBType type, final DBTable table){ + public DefaultDBColumn(final String dbName, final String adqlName, final DBType type, final DBTable table) { if (dbName == null || dbName.length() == 0) throw new NullPointerException("Missing DB name!"); this.dbName = dbName; - this.adqlName = (adqlName == null) ? dbName : adqlName; + setADQLName(adqlName); this.type = type; this.table = table; } @Override - public final String getADQLName(){ + public final String getADQLName() { return adqlName; } - public final void setADQLName(final String adqlName){ - if (adqlName != null) - this.adqlName = adqlName; + public final void setADQLName(String name) { + if (name != null) { + + // Remove leading and trailing space characters: + name = name.trim(); + + // Detect automatically case sensitivity: + boolean caseSensitive = DefaultDBTable.isDelimited(name); + if (caseSensitive) + name = name.substring(1, name.length() - 1).replaceAll("\"\"", "\""); + + // ONLY if the final name is NOT empty: + if (name.trim().length() > 0) { + adqlName = name; + columnCaseSensitive = caseSensitive; + } + } + } + + @Override + public boolean isCaseSensitive() { + return columnCaseSensitive; + } + + /** + * Change the case sensitivity of the ADQL column name. + * + * @param sensitive <code>true</code> to consider the current ADQL name as + * case sensitive, + * <code>false</code> otherwise. + */ + public void setCaseSensitive(final boolean sensitive) { + columnCaseSensitive = sensitive; } @Override - public final DBType getDatatype(){ + public final DBType getDatatype() { return type; } /** * <p>Set the type of this column.</p> - * + * * <p><i>Note 1: * The given type should be as closed as possible from a type listed by the IVOA in the TAP protocol description into the section UPLOAD. * </i></p> - * + * * <p><i>Note 2: * there is no default value. Consequently if this parameter is NULL, * the type should be considered as unknown. It means that any comparison with * any type will always return 'true'. * </i></p> - * + * * @param type New type of this column. - * + * * @since 1.3 */ - public final void setDatatype(final DBType type){ + public final void setDatatype(final DBType type) { this.type = type; } @Override - public final String getDBName(){ + public final String getDBName() { return dbName; } @Override - public final DBTable getTable(){ + public final DBTable getTable() { return table; } - public final void setTable(final DBTable table){ + public final void setTable(final DBTable table) { this.table = table; } @Override - public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable){ + public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable) { return new DefaultDBColumn(dbName, adqlName, type, dbTable); } diff --git a/src/adql/db/DefaultDBTable.java b/src/adql/db/DefaultDBTable.java index 19762a9c4ef943dae965202d8d322ffc4268a99a..fb89c046c08d46a2eb5a314a158fcb607195e92a 100644 --- a/src/adql/db/DefaultDBTable.java +++ b/src/adql/db/DefaultDBTable.java @@ -2,21 +2,21 @@ package adql.db; /* * This file is part of ADQLLibrary. - * + * * ADQLLibrary is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * ADQLLibrary is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * + * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -27,163 +27,282 @@ import java.util.Map; /** * Default implementation of {@link DBTable}. - * + * * @author Grégory Mantelet (CDS;ARI) - * @version 1.4 (08/2015) + * @version 2.0 (09/2019) */ public class DefaultDBTable implements DBTable { - protected String dbCatalogName; - protected String dbSchemaName; + protected String dbCatalogName = null; + protected String dbSchemaName = null; protected String dbName; protected String adqlCatalogName = null; protected String adqlSchemaName = null; protected String adqlName = null; - protected Map<String,DBColumn> columns = new LinkedHashMap<String,DBColumn>(); + protected boolean tableCaseSensitive = false; + + protected Map<String, DBColumn> columns = new LinkedHashMap<String, DBColumn>(); /** - * <p>Builds a default {@link DBTable} with the given DB name.</p> - * + * Builds a default {@link DBTable} with the given DB name. + * * <p>With this constructor: ADQL name = DB name.</p> - * - * <p><i><u>Note:</u> The table name can be prefixed by a schema and a catalog: t1 or schema1.t1 or cat1.schema1.t2</i></p> - * + * + * <p><i><b>Note:</b> + * The ADQL/DB schema and catalog names are set to NULL. + * </i></p> + * + * <p><i><b>WARNING:</b> + * The ADQL table name MUST NOT be qualified (i.e. prefixed by a schema + * and/or a catalog)! For instance, <code>t1</code> is ok, but not + * <code>schema1.t1</code> or <code>cat1.schema1.t2</code> which won't be + * split but instead, considered as the whole ADQL name. + * </i></p> + * + * <p><i><b>Important note:</b> + * The ADQL table name can be delimited (i.e. surrounded by double quotes). + * In such case, the surrounded name would be considered as case-sensitive. + * </i></p> + * * @param dbName Database name (it will be also used as ADQL table name). - * + * * @see #DefaultDBTable(String, String) */ - public DefaultDBTable(final String dbName){ + public DefaultDBTable(final String dbName) { this(dbName, null); } /** - * <p>Builds a default {@link DBTable} with the given DB and ADQL names.</p> - * - * <p><i><u>Note:</u> The table names can be prefixed by a schema and a catalog: t1 or schema1.t1 or cat1.schema1.t2</i></p> - * + * Builds a default {@link DBTable} with the given DB and ADQL names. + * + * <p><i><b>Note:</b> + * The ADQL/DB schema and catalog names are set to NULL. + * </i></p> + * + * <p><i><b>WARNING:</b> + * The ADQL table name MUST NOT be qualified (i.e. prefixed by a schema + * and/or a catalog)! For instance, <code>t1</code> is ok, but not + * <code>schema1.t1</code> or <code>cat1.schema1.t2</code> which won't be + * split but instead, considered as the whole ADQL name. + * </i></p> + * + * <p><i><b>Important note:</b> + * The ADQL table name can be delimited (i.e. surrounded by double quotes). + * In such case, the surrounded name would be considered as case-sensitive. + * </i></p> + * * @param dbName Database name. * @param adqlName Name used in ADQL queries. + * <i>If NULL, dbName will be used instead.</i> */ - public DefaultDBTable(final String dbName, final String adqlName){ - // DB names: - String[] names = splitTableName(dbName); - if (names[2] == null || names[2].length() == 0) - throw new NullPointerException("Missing DB name !"); - else - this.dbName = names[2]; - this.dbSchemaName = names[1]; - this.dbCatalogName = names[0]; - - // ADQL names: - names = splitTableName(adqlName); - if (names[2] == null || names[2].length() == 0){ - this.adqlName = this.dbName; - this.adqlSchemaName = this.dbSchemaName; - this.adqlCatalogName = this.dbCatalogName; - }else{ - this.adqlName = names[2]; - this.adqlSchemaName = names[1]; - this.adqlCatalogName = names[0]; - } + public DefaultDBTable(final String dbName, final String adqlName) { + if (dbName == null || dbName.trim().length() == 0) + throw new NullPointerException("Missing DB name!"); + this.dbName = dbName; + setADQLName(adqlName); } /** * Builds default {@link DBTable} with a DB catalog, schema and table names. - * - * @param dbCatName Database catalog name (it will be also used as ADQL catalog name). - * @param dbSchemName Database schema name (it will be also used as ADQL schema name). - * @param dbName Database table name (it will be also used as ADQL table name). - * + * + * <p><i><b>WARNING:</b> + * The ADQL table name MUST NOT be qualified (i.e. prefixed by a schema + * and/or a catalog)! For instance, <code>t1</code> is ok, but not + * <code>schema1.t1</code> or <code>cat1.schema1.t2</code> which won't be + * split but instead, considered as the whole ADQL name. + * </i></p> + * + * <p><i><b>Important note:</b> + * The ADQL table name can be delimited (i.e. surrounded by double quotes). + * In such case, the surrounded name would be considered as case-sensitive. + * </i></p> + * + * @param dbCatName Database catalog name (it will be also used as ADQL + * catalog name). + * @param dbSchemaName Database schema name (it will be also used as ADQL + * schema name). + * @param dbName Database table name (it will be also used as ADQL + * table name). + * <em>MUST NOT be NULL!</em> + * * @see #DefaultDBTable(String, String, String, String, String, String) */ - public DefaultDBTable(final String dbCatName, final String dbSchemName, final String dbName){ - this(dbCatName, null, dbSchemName, null, dbName, null); + public DefaultDBTable(final String dbCatName, final String dbSchemaName, final String dbName) { + this(dbCatName, null, dbSchemaName, null, dbName, null); } /** - * Builds default {@link DBTable} with the DB and ADQL names for the catalog, schema and table. - * - * @param dbCatName Database catalog name. - * @param adqlCatName Catalog name used in ADQL queries. - * <em>If NULL, it will be set to dbCatName.</em> - * @param dbSchemName Database schema name. - * @param adqlSchemName Schema name used in ADQL queries. - * <em>If NULL, it will be set to dbSchemName.</em> - * @param dbName Database table name. - * @param adqlName Table name used in ADQL queries. - * <em>If NULL, it will be set to dbName.</em> + * Builds default {@link DBTable} with the DB and ADQL names for the + * catalog, schema and table. + * + * <p><i><b>WARNING:</b> + * The ADQL table name MUST NOT be qualified (i.e. prefixed by a schema + * and/or a catalog)! For instance, <code>t1</code> is ok, but not + * <code>schema1.t1</code> or <code>cat1.schema1.t2</code> which won't be + * split but instead, considered as the whole ADQL name. + * </i></p> + * + * <p><i><b>Important note:</b> + * The ADQL table name can be delimited (i.e. surrounded by double quotes). + * In such case, the surrounded name would be considered as case-sensitive. + * </i></p> + * + * @param dbCatName Database catalog name. + * @param adqlCatName Catalog name used in ADQL queries. + * <em>If NULL, it will be set to dbCatName.</em> + * @param dbSchemaName Database schema name. + * @param adqlSchemaName Schema name used in ADQL queries. + * <em>If NULL, it will be set to dbSchemName.</em> + * @param dbName Database table name. + * <em>MUST NOT be NULL!</em> + * @param adqlName Table name used in ADQL queries. + * <em>If NULL, it will be set to dbName.</em> */ - public DefaultDBTable(final String dbCatName, final String adqlCatName, final String dbSchemName, final String adqlSchemName, final String dbName, final String adqlName){ - - if (dbName == null || dbName.length() == 0) - throw new NullPointerException("Missing DB name !"); + public DefaultDBTable(final String dbCatName, final String adqlCatName, final String dbSchemaName, final String adqlSchemaName, final String dbName, final String adqlName) { + if (dbName == null || dbName.trim().length() == 0) + throw new NullPointerException("Missing DB name!"); this.dbName = dbName; - this.adqlName = (adqlName == null) ? dbName : adqlName; + setADQLName(adqlName); - dbSchemaName = dbSchemName; - adqlSchemaName = (adqlSchemName == null) ? dbSchemName : adqlSchemName; + this.dbSchemaName = dbSchemaName; + this.adqlSchemaName = (adqlSchemaName == null) ? dbSchemaName : adqlSchemaName; - dbCatalogName = dbCatName; - adqlCatalogName = (adqlCatName == null) ? dbCatName : adqlCatName; + this.dbCatalogName = dbCatName; + this.adqlCatalogName = (adqlCatName == null) ? dbCatName : adqlCatName; } @Override - public final String getDBName(){ + public final String getDBName() { return dbName; } @Override - public final String getDBSchemaName(){ + public final String getDBSchemaName() { return dbSchemaName; } @Override - public final String getDBCatalogName(){ + public final String getDBCatalogName() { return dbCatalogName; } @Override - public final String getADQLName(){ + public final String getADQLName() { return adqlName; } - public void setADQLName(final String name){ + /** + * Change the ADQL name of this table. + * + * <p> + * The case sensitivity is automatically set. The table name will be + * considered as case sensitive if the given name is surrounded by double + * quotes (<code>"</code>). In such case, the table name is stored and then + * returned WITHOUT these double quotes. + * </p> + * + * <p><i><b>WARNING:</b> + * If the name without the double quotes (and then trimmed) is an empty + * string, the ADQL name will be set to the {@link #getDBName()} as such. + * Then the case sensitivity will be set to <code>false</code>. + * </i></p> + * + * @param name New ADQL name of this table. + */ + public void setADQLName(final String name) { + // Set the new table name (only if not NULL, otherwise use the DB name): adqlName = (name != null) ? name : dbName; + + // Detect automatically case sensitivity: + if ((tableCaseSensitive = isDelimited(adqlName))) + adqlName = adqlName.substring(1, adqlName.length() - 1).replaceAll("\"\"", "\""); + + // If the final name is empty, no case sensitivity and use the DB name: + if (adqlName.trim().length() == 0) { + adqlName = dbName; + tableCaseSensitive = false; + } + } + + /** + * Tell whether the given identifier is delimited (i.e. within the same pair + * of double quotes - <code>"</code>). + * + * <i> + * <p>The following identifiers ARE delimited:</p> + * <ul> + * <li><code>"a"</code></li> + * <li><code>" "</code> (string with spaces ; but won't be considered as a + * valid ADQL name)</li> + * <li><code>"foo.bar"</code></li> + * <li><code>"foo"".""bar"</code> (with escaped double quotes)</li> + * <li><code>""""</code> (idem)</li> + * </ul> + * </i> + * + * <i> + * <p>The following identifiers are NOT considered as delimited:</p> + * <ul> + * <li><code>""</code> (empty string)</li> + * <li><code>"foo</code> (missing ending double quote)</li> + * <li><code>foo"</code> (missing leading double quote)</li> + * <li><code>"foo"."bar"</code> (not the same pair of double quotes)</li> + * </ul> + * </i> + * + * @param name Identifier that may be delimited. + * + * @return <code>true</code> if the given identifier is delimited, + * <code>false</code> otherwise. + * + * @since 2.0 + */ + public static final boolean isDelimited(final String name) { + return name != null && name.matches("\"(\"\"|[^\"])*\""); + } + + @Override + public boolean isCaseSensitive() { + return tableCaseSensitive; + } + + public void setCaseSensitive(final boolean sensitive) { + tableCaseSensitive = sensitive; } @Override - public final String getADQLSchemaName(){ + public final String getADQLSchemaName() { return adqlSchemaName; } - public void setADQLSchemaName(final String name){ + public void setADQLSchemaName(final String name) { adqlSchemaName = (name != null) ? name : dbSchemaName; } @Override - public final String getADQLCatalogName(){ + public final String getADQLCatalogName() { return adqlCatalogName; } - public void setADQLCatalogName(final String name){ + public void setADQLCatalogName(final String name) { adqlName = (name != null) ? null : dbName; } /** * <p>Case sensitive !</p> * <p>Research optimized for researches by ADQL name.</p> - * + * * @see adql.db.DBTable#getColumn(java.lang.String, boolean) */ @Override - public DBColumn getColumn(String colName, boolean byAdqlName){ + public DBColumn getColumn(String colName, boolean byAdqlName) { if (byAdqlName) return columns.get(colName); - else{ - for(DBColumn col : columns.values()){ + else { + for(DBColumn col : columns.values()) { if (col.getDBName().equals(colName)) return col; } @@ -191,22 +310,22 @@ public class DefaultDBTable implements DBTable { } } - public boolean hasColumn(String colName, boolean byAdqlName){ + public boolean hasColumn(String colName, boolean byAdqlName) { return (getColumn(colName, byAdqlName) != null); } @Override - public Iterator<DBColumn> iterator(){ + public Iterator<DBColumn> iterator() { return columns.values().iterator(); } - public void addColumn(DBColumn column){ + public void addColumn(DBColumn column) { if (column != null) columns.put(column.getADQLName(), column); } - public void addAllColumns(Collection<DBColumn> colList){ - if (colList != null){ + public void addAllColumns(Collection<DBColumn> colList) { + if (colList != null) { for(DBColumn column : colList) addColumn(column); } @@ -214,19 +333,23 @@ public class DefaultDBTable implements DBTable { /** * Splits the given table name in 3 parts: catalog, schema, table. - * + * * @param table The table name to split. - * + * * @return A String array of 3 items: [0]=catalog, [1]=schema, [0]=table. + * + * @deprecated Since v2.0, the table name is not any more split + * automatically. */ - public static final String[] splitTableName(final String table){ - String[] splitRes = new String[]{null,null,null}; + @Deprecated + public static final String[] splitTableName(final String table) { + String[] splitRes = new String[]{ null, null, null }; if (table == null || table.trim().length() == 0) return splitRes; String[] names = table.trim().split("\\."); - switch(names.length){ + switch(names.length) { case 1: splitRes[2] = table.trim(); break; @@ -254,38 +377,43 @@ public class DefaultDBTable implements DBTable { /** * <p>Join the last 3 items of the given string array with a dot ('.'). * These three parts should be: [0]=catalog name, [1]=schema name, [2]=table name.</p> - * + * * <p> * If the array contains less than 3 items, all the given items will be though joined. * However, if it contains more than 3 items, only the three last items will be. * </p> - * + * * <p>A null item will be written as an empty string (string of length 0 ; "").</p> - * + * * <p> * In the case the first and the third items are not null, but the second is null, the final string will contain in the middle two dots. * Example: if the array is {"cat", NULL, "table"}, then the joined string will be: "cat..table". * </p> - * + * * @param nameParts String items to join. - * + * * @return A string joining the 3 last string items of the given array, * or an empty string if the given array is NULL. - * + * * @since 1.3 + * + * @deprecated Since v2.0, the table name is not any more split + * automatically. So, it is not any more needed to join all its + * parts. */ - public static final String joinTableName(final String[] nameParts){ + @Deprecated + public static final String joinTableName(final String[] nameParts) { if (nameParts == null) return ""; StringBuffer str = new StringBuffer(); boolean empty = true; - for(int i = (nameParts.length <= 3) ? 0 : (nameParts.length - 3); i < nameParts.length; i++){ + for(int i = (nameParts.length <= 3) ? 0 : (nameParts.length - 3); i < nameParts.length; i++) { if (!empty) str.append('.'); String part = (nameParts[i] == null) ? null : nameParts[i].trim(); - if (part != null && part.length() > 0){ + if (part != null && part.length() > 0) { str.append(part); empty = false; } @@ -294,11 +422,10 @@ public class DefaultDBTable implements DBTable { } @Override - public DBTable copy(String dbName, String adqlName){ - dbName = (dbName == null) ? joinTableName(new String[]{dbCatalogName,dbSchemaName,this.dbName}) : dbName; - adqlName = (adqlName == null) ? joinTableName(new String[]{adqlCatalogName,adqlSchemaName,this.adqlName}) : adqlName; - DefaultDBTable copy = new DefaultDBTable(dbName, adqlName); - for(DBColumn col : this){ + public DBTable copy(String dbName, String adqlName) { + DefaultDBTable copy = new DefaultDBTable(dbCatalogName, adqlCatalogName, dbSchemaName, adqlSchemaName, dbName, adqlName); + copy.tableCaseSensitive = tableCaseSensitive; + for(DBColumn col : this) { if (col instanceof DBCommonColumn) copy.addColumn(new DBCommonColumn((DBCommonColumn)col, col.getDBName(), col.getADQLName())); else diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java index 4e4f17ec235698ea782eca8575803e5898b3771c..6f2ac2fe38acd0edeadcfbdf5de597f7f86dbafd 100644 --- a/src/tap/db/JDBCConnection.java +++ b/src/tap/db/JDBCConnection.java @@ -16,7 +16,7 @@ package tap.db; * You should have received a copy of the GNU Lesser General Public License * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012-2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -65,123 +65,166 @@ import uws.ISO8601Format; import uws.service.log.UWSLog.LogLevel; /** - * <p>This {@link DBConnection} implementation is theoretically able to deal with any DBMS JDBC connection.</p> + * This {@link DBConnection} implementation is theoretically able to deal with + * any DBMS JDBC connection. * - * <p><i>Note: - * "Theoretically", because its design has been done using information about Postgres, SQLite, Oracle, MySQL, Java DB (Derby) and H2. - * Then it has been really tested successfully with Postgres, SQLite and H2. + * <p><i><b>Note:</b> + * "Theoretically", because its design has been done using information about + * Postgres, SQLite, Oracle, MySQL, Java DB (Derby) and H2. Then it has been + * really tested successfully with Postgres, SQLite and H2. * </i></p> * * * <h3>Only one query executed at a time!</h3> * * <p> - * With a single instance of {@link JDBCConnection} it is possible to execute only one query (whatever the type: SELECT, UPDATE, DELETE, ...) - * at a time. This is indeed the simple way chosen with this implementation in order to allow the cancellation of any query by managing only - * one {@link Statement}. Indeed, only a {@link Statement} has a cancel function able to stop any query execution on the database. - * So all queries are executed with the same {@link Statement}. Thus, allowing the execution of one query at a time lets - * abort only one query rather than several in once (though just one should have been stopped). + * With a single instance of {@link JDBCConnection} it is possible to execute + * only one query (whatever the type: SELECT, UPDATE, DELETE, ...) at a time. + * This is indeed the simple way chosen with this implementation in order to + * allow the cancellation of any query by managing only one {@link Statement}. + * Indeed, only a {@link Statement} has a cancel function able to stop any + * query execution on the database. So all queries are executed with the same + * {@link Statement}. Thus, allowing the execution of one query at a time lets + * abort only one query rather than several in once (though just one should + * have been stopped). * </p> * * <p> - * All the following functions are synchronized in order to prevent parallel execution of them by several threads: - * {@link #addUploadedTable(TAPTable, TableIterator)}, {@link #dropUploadedTable(TAPTable)}, {@link #executeQuery(ADQLQuery)}, + * All the following functions are synchronized in order to prevent parallel + * execution of them by several threads: + * {@link #addUploadedTable(TAPTable, TableIterator)}, + * {@link #dropUploadedTable(TAPTable)}, {@link #executeQuery(ADQLQuery)}, * {@link #getTAPSchema()} and {@link #setTAPSchema(TAPMetadata)}. * </p> * * <p> - * To cancel a query execution the function {@link #cancel(boolean)} must be called. No error is returned by this function in case - * no query is currently executing. When called, the flag {@link #isCancelled()} is set to <code>true</code>. Any potentially long - * running function is checking this flag and may then stop immediately by throwing a {@link DBCancelledException} as soon as - * the flag turns <code>true</code>. It should be the case for {@link #addUploadedTable(TAPTable, TableIterator)}, + * To cancel a query execution the function {@link #cancel(boolean)} must be + * called. No error is returned by this function in case no query is currently + * executing. When called, the flag {@link #isCancelled()} is set to + * <code>true</code>. Any potentially long running function is checking this + * flag and may then stop immediately by throwing a + * {@link DBCancelledException} as soon as the flag turns <code>true</code>. + * It should be the case for {@link #addUploadedTable(TAPTable, TableIterator)}, * {@link #executeQuery(ADQLQuery)} and {@link #setTAPSchema(TAPMetadata)}. * </p> * * * <h3>Deal with different DBMS features</h3> * - * <p>Update queries are taking into account whether the following features are supported by the DBMS:</p> + * <p> + * Update queries are taking into account whether the following features are + * supported by the DBMS: + * </p> * <ul> - * <li><b>data definition</b>: when not supported, no update operation will be possible. - * All corresponding functions will then throw a {@link DBException} ; - * only {@link #executeQuery(ADQLQuery)} will be possibly called.</li> + * <li><b>data definition</b>: when not supported, no update operation will be + * possible. All corresponding functions will then + * throw a {@link DBException} ; only + * {@link #executeQuery(ADQLQuery)} will be + * possibly called.</li> * - * <li><b>transactions</b>: when not supported, no transaction is started or merely used. - * It means that in case of update failure, no rollback will be possible - * and that already done modification will remain in the database.</li> + * <li><b>transactions</b>: when not supported, no transaction is started or + * merely used. It means that in case of update + * failure, no rollback will be possible and that + * already done modification will remain in the + * database.</li> * - * <li><b>schemas</b>: when the DBMS does not have the notion of schema (like SQLite), no schema creation or dropping will be obviously processed. - * Besides, if not already done, database name of all tables will be prefixed by the schema name.</li> + * <li><b>schemas</b>: when the DBMS does not have the notion of schema (like + * SQLite), no schema creation or dropping will be + * obviously processed. Besides, if not already done, + * database name of all tables will be prefixed by the + * schema name.</li> * - * <li><b>batch updates</b>: when not supported, updates will just be done, "normally, one by one. - * In one word, there will be merely no optimization. - * Anyway, this feature concerns only the insertions into tables.</li> + * <li><b>batch updates</b>: when not supported, updates will just be done, + * "normally, one by one. In one word, there will be + * merely no optimization. Anyway, this feature + * concerns only the insertions into tables.</li> * - * <li><b>case sensitivity of identifiers</b>: the case sensitivity of quoted identifier varies from the used DBMS. This {@link DBConnection} - * implementation is able to adapt itself in function of the way identifiers are stored and - * researched in the database. How the case sensitivity is managed by the DBMS is the problem - * of only one function (which can be overwritten if needed): {@link #equals(String, String, boolean)}.</li> + * <li><b>case sensitivity of identifiers</b>: the case sensitivity of quoted + * identifier varies from the used + * DBMS. This {@link DBConnection} + * implementation is able to adapt + * itself in function of the way + * identifiers are stored and + * researched in the database. How + * the case sensitivity is managed + * by the DBMS is the problem of + * only one function (which can be + * overwritten if needed): + * {@link #equals(String, String, boolean)}.</li> * </ul> * * <p><i><b>Warning</b>: - * All these features have no impact at all on ADQL query executions ({@link #executeQuery(ADQLQuery)}). + * All these features have no impact at all on ADQL query executions + * ({@link #executeQuery(ADQLQuery)}). * </i></p> * * * <h3>Datatypes</h3> * * <p> - * All datatype conversions done while fetching a query result (via a {@link ResultSet}) - * are done exclusively by the returned {@link TableIterator} (so, here {@link ResultSetTableIterator}). + * All datatype conversions done while fetching a query result (via a + * {@link ResultSet}) are done exclusively by the returned + * {@link TableIterator} (so, here {@link ResultSetTableIterator}). * </p> * * <p> - * However, datatype conversions done while uploading a table are done here by the function - * {@link #convertTypeToDB(DBType)}. This function uses first the conversion function of the translator - * ({@link JDBCTranslator#convertTypeToDB(DBType)}), and then {@link #defaultTypeConversion(DBType)} - * if it fails. + * However, datatype conversions done while uploading a table are done here by + * the function {@link #convertTypeToDB(DBType)}. This function uses first the + * conversion function of the translator + * ({@link JDBCTranslator#convertTypeToDB(DBType)}), and then + * {@link #defaultTypeConversion(DBType)} if it fails. * </p> * * <p> - * In this default conversion, all typical DBMS datatypes are taken into account, <b>EXCEPT the geometrical types</b> - * (POINT and REGION). That's why it is recommended to use a translator in which the geometrical types are supported - * and managed. + * In this default conversion, all typical DBMS datatypes are taken into + * account, <b>EXCEPT the geometrical types</b> (POINT and REGION). That's why + * it is recommended to use a translator in which the geometrical types are + * supported and managed. * </p> * * * <h3>Fetch size</h3> * * <p> - * The possibility to specify a "fetch size" to the JDBC driver (and more exactly to a {@link Statement}) may reveal - * very helpful when dealing with large datasets. Thus, it is possible to fetch rows by block of a size represented - * by this "fetch size". This is also possible with this {@link DBConnection} thanks to the function {@link #setFetchSize(int)}. + * The possibility to specify a "fetch size" to the JDBC driver (and more + * exactly to a {@link Statement}) may reveal very helpful when dealing with + * large datasets. Thus, it is possible to fetch rows by block of a size + * represented by this "fetch size". This is also possible with this + * {@link DBConnection} thanks to the function {@link #setFetchSize(int)}. * </p> * * <p> - * However, some JDBC driver or DBMS may not support this feature. In such case, it is then automatically disabled by - * {@link JDBCConnection} so that any subsequent queries do not attempt to use it again. The {@link #supportsFetchSize} - * is however reset to <code>true</code> when {@link #setFetchSize(int)} is called. + * However, some JDBC driver or DBMS may not support this feature. In such + * case, it is then automatically disabled by {@link JDBCConnection} so that + * any subsequent queries do not attempt to use it again. The + * {@link #supportsFetchSize} is however reset to <code>true</code> when + * {@link #setFetchSize(int)} is called. * </p> * - * <p><i>Note 1: - * The "fetch size" feature is used only for SELECT queries executed by {@link #executeQuery(ADQLQuery)}. In all other functions, - * results of SELECT queries are fetched with the default parameter of the JDBC driver and its {@link Statement} implementation. + * <p><i><b>Note 1:</b> + * The "fetch size" feature is used only for SELECT queries executed by + * {@link #executeQuery(ADQLQuery)}. In all other functions, results of SELECT + * queries are fetched with the default parameter of the JDBC driver and its + * {@link Statement} implementation. * </i></p> * - * <p><i>Note 2: - * By default, this feature is disabled. So the default value of the JDBC driver is used. - * To enable it, a simple call to {@link #setFetchSize(int)} is enough, whatever is the given value. + * <p><i><b>Note 2:</b> + * By default, this feature is disabled. So the default value of the JDBC + * driver is used. To enable it, a simple call to {@link #setFetchSize(int)} is + * enough, whatever is the given value. * </i></p> * - * <p><i>Note 3: - * Generally set a fetch size starts a transaction in the database. So, after the result of the fetched query - * is not needed any more, do not forget to call {@link #endQuery()} in order to end the implicitly opened transaction. - * However, generally closing the returned {@link TableIterator} is fully enough (see the sources of - * {@link ResultSetTableIterator#close()} for more details). + * <p><i><b>Note 3:</b> + * Generally set a fetch size starts a transaction in the database. So, after + * the result of the fetched query is not needed any more, do not forget to + * call {@link #endQuery()} in order to end the implicitly opened transaction. + * However, generally closing the returned {@link TableIterator} is fully + * enough (see the sources of {@link ResultSetTableIterator#close()} for more + * details). * </i></p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.3 (10/2018) + * @version 2.4 (09/2019) * @since 2.0 */ public class JDBCConnection implements DBConnection { @@ -341,7 +384,7 @@ public class JDBCConnection implements DBConnection { * * @throws DBException If the driver can not be found or if the connection can not merely be created (usually because DB parameters are wrong). */ - public JDBCConnection(final String driverPath, final String dbUrl, final String dbUser, final String dbPassword, final JDBCTranslator translator, final String connID, final TAPLog logger) throws DBException{ + public JDBCConnection(final String driverPath, final String dbUrl, final String dbUser, final String dbPassword, final JDBCTranslator translator, final String connID, final TAPLog logger) throws DBException { this(createConnection(driverPath, dbUrl, dbUser, dbPassword), translator, connID, logger); } @@ -353,7 +396,7 @@ public class JDBCConnection implements DBConnection { * @param connID ID of this connection. <i>note: may be NULL ; but in this case, logs concerning this connection will be more difficult to localize.</i> * @param logger Logger to use in case of need. <i>note: may be NULL ; in this case, error will never be logged, but sometimes DBException may be raised.</i> */ - public JDBCConnection(final Connection conn, final JDBCTranslator translator, final String connID, final TAPLog logger) throws DBException{ + public JDBCConnection(final Connection conn, final JDBCTranslator translator, final String connID, final TAPLog logger) throws DBException { if (conn == null) throw new NullPointerException("Missing SQL connection! => can not create a JDBCConnection object."); if (translator == null) @@ -365,7 +408,7 @@ public class JDBCConnection implements DBConnection { this.logger = logger; // Set the supporting features' flags + DBMS type: - try{ + try { DatabaseMetaData dbMeta = connection.getMetaData(); dbms = (dbMeta.getDatabaseProductName() != null ? dbMeta.getDatabaseProductName().toLowerCase() : null); supportsTransaction = dbMeta.supportsTransactions(); @@ -380,7 +423,7 @@ public class JDBCConnection implements DBConnection { upperCaseQuoted = dbMeta.storesUpperCaseQuotedIdentifiers(); supportsMixedCaseQuotedIdentifier = dbMeta.supportsMixedCaseQuotedIdentifiers(); - }catch(SQLException se){ + } catch(SQLException se) { throw new DBException("Unable to access to one or several DB metadata (url, supportsTransaction, supportsBatchUpdates, supportsDataDefinitionAndDataManipulationTransactions, supportsSchemasInTableDefinitions, storesLowerCaseIdentifiers, storesUpperCaseIdentifiers, supportsMixedCaseIdentifiers, storesLowerCaseQuotedIdentifiers, storesMixedCaseQuotedIdentifiers, storesUpperCaseQuotedIdentifiers and supportsMixedCaseQuotedIdentifiers) from the given Connection!"); } } @@ -397,7 +440,7 @@ public class JDBCConnection implements DBConnection { * @deprecated (since 2.1) Should be replaced by <code>{@link java.sql.DatabaseMetaData#getDatabaseProductName()}.toLowerCase()</code>. */ @Deprecated - protected static final String getDBMSName(String dbUrl) throws DBException{ + protected static final String getDBMSName(String dbUrl) throws DBException { if (dbUrl == null) throw new DBException("Missing database URL!"); @@ -428,31 +471,31 @@ public class JDBCConnection implements DBConnection { * @see DriverManager#getDriver(String) * @see Driver#connect(String, Properties) */ - private final static Connection createConnection(final String driverPath, final String dbUrl, final String dbUser, final String dbPassword) throws DBException{ + private final static Connection createConnection(final String driverPath, final String dbUrl, final String dbUser, final String dbPassword) throws DBException { // Normalize the DB URL: String url = dbUrl.startsWith(JDBC_PREFIX) ? dbUrl : (JDBC_PREFIX + dbUrl); // Select the JDBDC driver: Driver d; - try{ + try { d = DriverManager.getDriver(url); - }catch(SQLException e){ - try{ + } catch(SQLException e) { + try { // ...load it, if necessary: if (driverPath == null) throw new DBException("Missing JDBC driver path! Since the required JDBC driver is not yet loaded, this path is needed to load it."); Class.forName(driverPath); // ...and try again: d = DriverManager.getDriver(url); - }catch(ClassNotFoundException cnfe){ + } catch(ClassNotFoundException cnfe) { throw new DBException("Impossible to find the JDBC driver \"" + driverPath + "\" !", cnfe); - }catch(SQLException se){ + } catch(SQLException se) { throw new DBException("No suitable JDBC driver found for the database URL \"" + url + "\" and the driver path \"" + driverPath + "\"!", se); } } // Build a connection to the specified database: - try{ + try { Properties p = new Properties(); if (dbUser != null) p.setProperty("user", dbUser); @@ -460,13 +503,13 @@ public class JDBCConnection implements DBConnection { p.setProperty("password", dbPassword); Connection con = d.connect(url, p); return con; - }catch(SQLException se){ + } catch(SQLException se) { throw new DBException("Impossible to establish a connection to the database \"" + url + "\"!", se); } } @Override - public final String getID(){ + public final String getID() { return ID; } @@ -479,7 +522,7 @@ public class JDBCConnection implements DBConnection { * * @return The wrapped JDBC connection. */ - public final Connection getInnerConnection(){ + public final Connection getInnerConnection() { return connection; } @@ -493,7 +536,7 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected boolean hasStatement() throws SQLException{ + protected boolean hasStatement() throws SQLException { return (stmt != null && !stmt.isClosed()); } @@ -511,7 +554,7 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected Statement getStatement() throws SQLException{ + protected Statement getStatement() throws SQLException { if (hasStatement()) return stmt; else @@ -523,7 +566,7 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected void closeStatement(){ + protected void closeStatement() { close(stmt); stmt = null; } @@ -567,8 +610,8 @@ public class JDBCConnection implements DBConnection { * @since 2.1 */ @Override - public final void cancel(final boolean rollback){ - synchronized(cancelled){ + public final void cancel(final boolean rollback) { + synchronized (cancelled) { cancelled = true; boolean effectivelyCancelled = cancel(stmt, rollback); // Log the success of the cancellation: @@ -603,16 +646,16 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected boolean cancel(final Statement stmt, final boolean rollback){ - try{ + protected boolean cancel(final Statement stmt, final boolean rollback) { + try { // If the statement is not already closed, cancel its current query execution: - if (supportsCancel && stmt != null && !stmt.isClosed()){ + if (supportsCancel && stmt != null && !stmt.isClosed()) { stmt.cancel(); return true; - }else + } else return false; - }catch(SQLFeatureNotSupportedException sfnse){ + } catch(SQLFeatureNotSupportedException sfnse) { // prevent further cancel attempts: supportsCancel = false; // log a warning: @@ -620,13 +663,13 @@ public class JDBCConnection implements DBConnection { logger.logDB(LogLevel.WARNING, this, "CANCEL", "This JDBC driver does not support Statement.cancel(). No further cancel attempt will be performed with this JDBCConnection instance.", sfnse); return false; - }catch(SQLException se){ + } catch(SQLException se) { if (logger != null) logger.logDB(LogLevel.ERROR, this, "CANCEL", "Abortion of the current query apparently fails! The query may still run on the database server.", se); return false; } // Whatever happens, rollback all executed operations (only if rollback=true and if in a transaction ; that's to say if AutoCommit = false): - finally{ + finally { if (rollback && supportsTransaction) rollback((stmt != null && stmt == this.stmt)); } @@ -645,8 +688,8 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected final boolean isCancelled(){ - synchronized(cancelled){ + protected final boolean isCancelled() { + synchronized (cancelled) { return cancelled; } } @@ -661,14 +704,14 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected final void resetCancel(){ - synchronized(cancelled){ + protected final void resetCancel() { + synchronized (cancelled) { cancelled = false; } } @Override - public void endQuery(){ + public void endQuery() { // Cancel the last query processing, if still running: cancel(stmt, false); // note: this function is called instead of cancel(false) in order to avoid a log message about the cancellation operation result. // Close the statement, if still opened: @@ -683,24 +726,24 @@ public class JDBCConnection implements DBConnection { /* INTERROGATION METHODS */ /* ********************* */ @Override - public synchronized TableIterator executeQuery(final ADQLQuery adqlQuery) throws DBException{ + public synchronized TableIterator executeQuery(final ADQLQuery adqlQuery) throws DBException { // Starting of new query execution => disable the cancel flag: resetCancel(); String sql = null; ResultSet result = null; - try{ + try { // 1. Translate the ADQL query into SQL: if (logger != null) logger.logDB(LogLevel.INFO, this, "TRANSLATE", "Translating ADQL: " + adqlQuery.toADQL().replaceAll("(\t|\r?\n)+", " "), null); sql = translator.translate(adqlQuery); // 2. Create the statement and if needed, configure it for the given fetch size: - if (supportsTransaction && supportsFetchSize && fetchSize > 0){ - try{ + if (supportsTransaction && supportsFetchSize && fetchSize > 0) { + try { connection.setAutoCommit(false); - }catch(SQLException se){ - if (!isCancelled()){ + } catch(SQLException se) { + if (!isCancelled()) { supportsFetchSize = false; if (logger != null) logger.logDB(LogLevel.WARNING, this, "RESULT", "Fetch size unsupported!", null); @@ -716,11 +759,11 @@ public class JDBCConnection implements DBConnection { getStatement(); // Adjust the fetching size of this statement: - if (supportsFetchSize){ - try{ + if (supportsFetchSize) { + try { stmt.setFetchSize(fetchSize); - }catch(SQLException se){ - if (!isCancelled()){ + } catch(SQLException se) { + if (!isCancelled()) { supportsFetchSize = false; if (logger != null) logger.logDB(LogLevel.WARNING, this, "RESULT", "Fetch size unsupported!", null); @@ -742,7 +785,7 @@ public class JDBCConnection implements DBConnection { logger.logDB(LogLevel.INFO, this, "RESULT", "Returning result (" + (supportsFetchSize ? "fetch size = " + fetchSize : "all in once") + ").", null); return createTableIterator(result, adqlQuery.getResultingColumns()); - }catch(Exception ex){ + } catch(Exception ex) { // Close the ResultSet, if one was open: close(result); // End properly the query: @@ -751,14 +794,14 @@ public class JDBCConnection implements DBConnection { if (ex instanceof DBCancelledException) throw (DBCancelledException)ex; // Otherwise propagate the exception with an appropriate error message: - else if (ex instanceof SQLException){ + else if (ex instanceof SQLException) { /* ...except if the query has been aborted: * then, it is normal to receive an SQLException: */ if (isCancelled()) throw new DBCancelledException(); else throw new DBException("Unexpected error while executing a SQL query: " + ex.getMessage(), ex); - }else if (ex instanceof TranslationException) + } else if (ex instanceof TranslationException) throw new DBException("Unexpected error while translating ADQL into SQL: " + ex.getMessage(), ex); else if (ex instanceof DataReadException) throw new DBException("Impossible to read the query result, because: " + ex.getMessage(), ex); @@ -788,10 +831,10 @@ public class JDBCConnection implements DBConnection { * * @see ResultSetTableIterator#ResultSetTableIterator(DBConnection, ResultSet, DBColumn[], JDBCTranslator, String) */ - protected TableIterator createTableIterator(final ResultSet rs, final DBColumn[] resultingColumns) throws DataReadException{ - try{ + protected TableIterator createTableIterator(final ResultSet rs, final DBColumn[] resultingColumns) throws DataReadException { + try { return new ResultSetTableIterator(this, rs, resultingColumns, translator, dbms); - }catch(Throwable t){ + } catch(Throwable t) { throw (t instanceof DataReadException) ? (DataReadException)t : new DataReadException(t); } } @@ -808,11 +851,11 @@ public class JDBCConnection implements DBConnection { * @return An index between 0 and 4 (included) - 0 meaning the first table to create whereas 4 is the last one. * -1 is returned if NULL is given in parameter of if the standard table is not taken into account here. */ - protected int getCreationOrder(final STDTable table){ + protected int getCreationOrder(final STDTable table) { if (table == null) return -1; - switch(table){ + switch(table) { case SCHEMAS: return 0; case TABLES: @@ -850,10 +893,10 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - public void setDBMapping(final Map<String, String> mapping){ + public void setDBMapping(final Map<String, String> mapping) { if (mapping == null) dbMapping = null; - else{ + else { if (dbMapping == null) dbMapping = new HashMap<String, String>(mapping.size()); else @@ -871,23 +914,23 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected TAPSchema getStdSchema(){ + protected TAPSchema getStdSchema() { TAPSchema tap_schema = TAPMetadata.getStdSchema(supportsSchema); - if (dbMapping != null){ + if (dbMapping != null) { // Update the TAP_SCHEMA DB name, if needed: if (dbMapping.containsKey(tap_schema.getADQLName())) tap_schema.setDBName(dbMapping.get(tap_schema.getADQLName())); // For each table... - for(TAPTable t : tap_schema){ + for(TAPTable t : tap_schema) { // ...update the table DB name, if needed: if (dbMapping.containsKey(t.getFullName())) t.setDBName(dbMapping.get(t.getFullName())); // For each column... String fullName; - for(DBColumn c : t){ + for(DBColumn c : t) { fullName = t.getFullName() + "." + c.getADQLName(); // ...update the column DB name, if needed: if (dbMapping.containsKey(fullName)) @@ -922,7 +965,7 @@ public class JDBCConnection implements DBConnection { * @see tap.db.DBConnection#getTAPSchema() */ @Override - public synchronized TAPMetadata getTAPSchema() throws DBException{ + public synchronized TAPMetadata getTAPSchema() throws DBException { // Starting of new query execution => disable the cancel flag: resetCancel(); @@ -933,7 +976,7 @@ public class JDBCConnection implements DBConnection { TAPSchema tap_schema = getStdSchema(); // LOAD ALL METADATA FROM THE STANDARD TAP TABLES: - try{ + try { // create a common statement for all loading functions: getStatement(); @@ -949,7 +992,7 @@ public class JDBCConnection implements DBConnection { // load all coordinate systems from TAP_SCHEMA.coosys: [non standard] Map<String, TAPCoosys> mapCoosys = null; - if (isTableExisting(tap_schema.getDBName(), "coosys", stmt.getConnection().getMetaData())){ + if (isTableExisting(tap_schema.getDBName(), "coosys", stmt.getConnection().getMetaData())) { if (logger != null) logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.coosys.", null); // create the TAP_SCHEMA.coosys table: @@ -970,11 +1013,11 @@ public class JDBCConnection implements DBConnection { logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.keys and TAP_SCHEMA.key_columns.", null); loadKeys(tap_schema.getTable(STDTable.KEYS.label), tap_schema.getTable(STDTable.KEY_COLUMNS.label), lstTables, stmt); - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to create a Statement!", se); throw new DBException("Can not create a Statement!", se); - }finally{ + } finally { cancel(stmt, true); // note: this function is called instead of cancel(true) in order to avoid a log message about the cancellation operation result. closeStatement(); } @@ -999,9 +1042,9 @@ public class JDBCConnection implements DBConnection { * * @throws DBException If any error occurs while interacting with the database. */ - protected void loadSchemas(final TAPTable tableDef, final TAPMetadata metadata, final Statement stmt) throws DBException{ + protected void loadSchemas(final TAPTable tableDef, final TAPMetadata metadata, final Statement stmt) throws DBException { ResultSet rs = null; - try{ + try { // Determine whether the dbName column exists: /* note: if the schema notion is not supported by this DBMS, the column "dbname" is ignored. */ boolean hasDBName = supportsSchema && isColumnExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), DB_NAME_COLUMN, connection.getMetaData()); @@ -1016,7 +1059,7 @@ public class JDBCConnection implements DBConnection { sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("utype"))); if (hasSchemaIndex) sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("schema_index"))); - if (hasDBName){ + if (hasDBName) { sqlBuf.append(", "); translator.appendIdentifier(sqlBuf, DB_NAME_COLUMN, IdentifierField.COLUMN); } @@ -1030,7 +1073,7 @@ public class JDBCConnection implements DBConnection { rs = stmt.executeQuery(sqlBuf.toString()); // Create all schemas: - while(rs.next()){ + while(rs.next()) { String schemaName = rs.getString(1), description = rs.getString(2), utype = rs.getString(3), dbName = (hasDBName ? (hasSchemaIndex ? rs.getString(5) : rs.getString(4)) : null); @@ -1049,11 +1092,11 @@ public class JDBCConnection implements DBConnection { // add the new schema inside the given metadata: metadata.addSchema(newSchema); } - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load schemas from TAP_SCHEMA.schemas!", se); throw new DBException("Impossible to load schemas from TAP_SCHEMA.schemas!", se); - }finally{ + } finally { close(rs); } } @@ -1084,9 +1127,9 @@ public class JDBCConnection implements DBConnection { * * @throws DBException If a schema can not be found, or if any other error occurs while interacting with the database. */ - protected List<TAPTable> loadTables(final TAPTable tableDef, final TAPMetadata metadata, final Statement stmt) throws DBException{ + protected List<TAPTable> loadTables(final TAPTable tableDef, final TAPMetadata metadata, final Statement stmt) throws DBException { ResultSet rs = null; - try{ + try { // Determine whether the dbName column exists: boolean hasDBName = isColumnExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), DB_NAME_COLUMN, connection.getMetaData()); @@ -1102,7 +1145,7 @@ public class JDBCConnection implements DBConnection { sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("utype"))); if (hasTableIndex) sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("table_index"))); - if (hasDBName){ + if (hasDBName) { sqlBuf.append(", "); translator.appendIdentifier(sqlBuf, DB_NAME_COLUMN, IdentifierField.COLUMN); } @@ -1117,7 +1160,7 @@ public class JDBCConnection implements DBConnection { // Create all tables: ArrayList<TAPTable> lstTables = new ArrayList<TAPTable>(); - while(rs.next()){ + while(rs.next()) { String schemaName = rs.getString(1), tableName = rs.getString(2), typeStr = rs.getString(3), description = rs.getString(4), utype = rs.getString(5), @@ -1126,15 +1169,15 @@ public class JDBCConnection implements DBConnection { // get the schema: TAPSchema schema = metadata.getSchema(schemaName); - if (schema == null){ + if (schema == null) { if (logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to find the schema of the table \"" + tableName + "\": \"" + schemaName + "\"!", null); throw new DBException("Impossible to find the schema of the table \"" + tableName + "\": \"" + schemaName + "\"!"); } - // If the table name is qualified, check its prefix (it must match to the schema name): + /*// If the table name is qualified, check its prefix (it must match to the schema name): int endPrefix = tableName.indexOf('.'); - if (endPrefix >= 0){ + if (endPrefix >= 0) { if (endPrefix == 0) throw new DBException("Incorrect table name syntax: \"" + tableName + "\"! Missing schema name (before '.')."); else if (endPrefix == tableName.length() - 1) @@ -1143,14 +1186,14 @@ public class JDBCConnection implements DBConnection { throw new DBException("Incorrect schema prefix for the table \"" + tableName.substring(endPrefix + 1) + "\": this table is not in a schema, according to the column \"schema_name\" of TAP_SCHEMA.tables!"); else if (!tableName.substring(0, endPrefix).trim().equalsIgnoreCase(schemaName)) throw new DBException("Incorrect schema prefix for the table \"" + schemaName + "." + tableName.substring(tableName.indexOf('.') + 1) + "\": " + tableName + "! Mismatch between the schema specified in prefix of the column \"table_name\" and in the column \"schema_name\"."); - } + }*/ // resolve the table type (if any) ; by default, it will be "table": TableType type = TableType.table; - if (typeStr != null){ - try{ + if (typeStr != null) { + try { type = TableType.valueOf(typeStr.toLowerCase()); - }catch(IllegalArgumentException iae){ + } catch(IllegalArgumentException iae) { } } @@ -1160,10 +1203,9 @@ public class JDBCConnection implements DBConnection { newTable.setIndex(tableIndex); // force the dbName of TAP_SCHEMA table to be the same as the used one: - if (STDSchema.TAPSCHEMA.label.equalsIgnoreCase(schemaName)){ - String simpleTableName = (endPrefix > 0) ? tableName.substring(endPrefix + 1) : tableName; - if (tableDef.getSchema() != null && tableDef.getSchema().getTable(simpleTableName) != null) - newTable.setDBName(tableDef.getSchema().getTable(simpleTableName).getDBName()); + if (STDSchema.TAPSCHEMA.label.equalsIgnoreCase(schemaName)) { + if (tableDef.getSchema() != null && tableDef.getSchema().getTable(newTable.getADQLName()) != null) + newTable.setDBName(tableDef.getSchema().getTable(newTable.getADQLName()).getDBName()); } // add the new table inside its corresponding schema: @@ -1172,11 +1214,11 @@ public class JDBCConnection implements DBConnection { } return lstTables; - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load tables from TAP_SCHEMA.tables!", se); throw new DBException("Impossible to load tables from TAP_SCHEMA.tables!", se); - }finally{ + } finally { close(rs); } } @@ -1195,9 +1237,9 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected Map<String, TAPCoosys> loadCoosys(final TAPTable tableDef, final TAPMetadata metadata, final Statement stmt) throws DBException{ + protected Map<String, TAPCoosys> loadCoosys(final TAPTable tableDef, final TAPMetadata metadata, final Statement stmt) throws DBException { ResultSet rs = null; - try{ + try { // Build the SQL query: StringBuffer sqlBuf = new StringBuffer("SELECT "); sqlBuf.append(translator.getColumnName(tableDef.getColumn("id"))); @@ -1212,7 +1254,7 @@ public class JDBCConnection implements DBConnection { // Create all coosys: HashMap<String, TAPCoosys> mapCoosys = new HashMap<String, TAPCoosys>(); - while(rs.next()){ + while(rs.next()) { String coosysId = rs.getString(1), system = rs.getString(2), equinox = rs.getString(3), epoch = rs.getString(4); @@ -1225,11 +1267,11 @@ public class JDBCConnection implements DBConnection { } return mapCoosys; - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load coordinate systems from TAP_SCHEMA.coosys!", se); throw new DBException("Impossible to load coordinate systems from TAP_SCHEMA.coosys!", se); - }finally{ + } finally { close(rs); } } @@ -1257,7 +1299,7 @@ public class JDBCConnection implements DBConnection { * the list of declared coordinate systems. */ @Deprecated - protected void loadColumns(final TAPTable tableDef, final List<TAPTable> lstTables, final Statement stmt) throws DBException{ + protected void loadColumns(final TAPTable tableDef, final List<TAPTable> lstTables, final Statement stmt) throws DBException { loadColumns(tableDef, lstTables, null, stmt); } @@ -1283,9 +1325,9 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected void loadColumns(final TAPTable tableDef, final List<TAPTable> lstTables, final Map<String, TAPCoosys> mapCoosys, final Statement stmt) throws DBException{ + protected void loadColumns(final TAPTable tableDef, final List<TAPTable> lstTables, final Map<String, TAPCoosys> mapCoosys, final Statement stmt) throws DBException { ResultSet rs = null; - try{ + try { // Determine whether the dbName column exists: boolean hasArraysize = isColumnExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), "arraysize", connection.getMetaData()); @@ -1316,11 +1358,11 @@ public class JDBCConnection implements DBConnection { sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("std"))); if (hasColumnIndex) sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("column_index"))); - if (hasDBName){ + if (hasDBName) { sqlBuf.append(", "); translator.appendIdentifier(sqlBuf, DB_NAME_COLUMN, IdentifierField.COLUMN); } - if (hasCoosys){ + if (hasCoosys) { sqlBuf.append(", "); translator.appendIdentifier(sqlBuf, COOSYS_ID_COLUMN, IdentifierField.COLUMN); } @@ -1334,7 +1376,7 @@ public class JDBCConnection implements DBConnection { rs = stmt.executeQuery(sqlBuf.toString()); // Create all tables: - while(rs.next()){ + while(rs.next()) { String tableName = rs.getString(1), columnName = rs.getString(2), description = rs.getString(3), unit = rs.getString(4), @@ -1349,7 +1391,7 @@ public class JDBCConnection implements DBConnection { // get the table: TAPTable table = searchTable(tableName, lstTables.iterator()); - if (table == null){ + if (table == null) { if (logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to find the table of the column \"" + columnName + "\": \"" + tableName + "\"!", null); throw new DBException("Impossible to find the table of the column \"" + columnName + "\": \"" + tableName + "\"!"); @@ -1358,10 +1400,10 @@ public class JDBCConnection implements DBConnection { // resolve the column type (if any) ; by default, it will be "VARCHAR" if unknown or missing: DBDatatype tapDatatype = null; // ...try to resolve the datatype in function of all datatypes declared by the TAP standard. - if (datatype != null){ - try{ + if (datatype != null) { + try { tapDatatype = DBDatatype.valueOf(datatype.toUpperCase()); - }catch(IllegalArgumentException iae){ + } catch(IllegalArgumentException iae) { } } // ...build the column type: @@ -1380,14 +1422,14 @@ public class JDBCConnection implements DBConnection { newColumn.setIndex(colIndex); // set the coordinate system if any is specified: - if (hasCoosys){ + if (hasCoosys) { int indCoosys = 12; if (hasColumnIndex) indCoosys++; if (hasDBName) indCoosys++; String coosysId = rs.getString(indCoosys); - if (coosysId != null){ + if (coosysId != null) { newColumn.setCoosys(mapCoosys.get(coosysId)); if (logger != null && newColumn.getCoosys() == null) logger.logDB(LogLevel.WARNING, this, "LOAD_TAP_SCHEMA", "No coordinate system for the column \"" + columnName + "\"! Cause: unknown coordinate system: \"" + coosysId + "\".", null); @@ -1401,11 +1443,11 @@ public class JDBCConnection implements DBConnection { // add the new column inside its corresponding table: table.addColumn(newColumn); } - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load columns from TAP_SCHEMA.columns!", se); throw new DBException("Impossible to load columns from TAP_SCHEMA.columns!", se); - }finally{ + } finally { close(rs); } } @@ -1430,10 +1472,10 @@ public class JDBCConnection implements DBConnection { * * @throws DBException If a table or a column can not be found, or if any other error occurs while interacting with the database. */ - protected void loadKeys(final TAPTable keysDef, final TAPTable keyColumnsDef, final List<TAPTable> lstTables, final Statement stmt) throws DBException{ + protected void loadKeys(final TAPTable keysDef, final TAPTable keyColumnsDef, final List<TAPTable> lstTables, final Statement stmt) throws DBException { ResultSet rs = null; PreparedStatement keyColumnsStmt = null; - try{ + try { // Prepare the query to get the columns of each key: StringBuffer sqlBuf = new StringBuffer("SELECT "); sqlBuf.append(translator.getColumnName(keyColumnsDef.getColumn("from_column"))); @@ -1457,20 +1499,20 @@ public class JDBCConnection implements DBConnection { rs = stmt.executeQuery(sqlBuf.toString()); // Create all foreign keys: - while(rs.next()){ + while(rs.next()) { String key_id = rs.getString(1), from_table = rs.getString(2), target_table = rs.getString(3), description = rs.getString(4), utype = rs.getString(5); // get the two tables (source and target): TAPTable sourceTable = searchTable(from_table, lstTables.iterator()); - if (sourceTable == null){ + if (sourceTable == null) { if (logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to find the source table of the foreign key \"" + key_id + "\": \"" + from_table + "\"!", null); throw new DBException("Impossible to find the source table of the foreign key \"" + key_id + "\": \"" + from_table + "\"!"); } TAPTable targetTable = searchTable(target_table, lstTables.iterator()); - if (targetTable == null){ + if (targetTable == null) { if (logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to find the target table of the foreign key \"" + key_id + "\": \"" + target_table + "\"!", null); throw new DBException("Impossible to find the target table of the foreign key \"" + key_id + "\": \"" + target_table + "\"!"); @@ -1479,33 +1521,33 @@ public class JDBCConnection implements DBConnection { // get the list of columns joining the two tables of the foreign key: HashMap<String, String> columns = new HashMap<String, String>(); ResultSet rsKeyCols = null; - try{ + try { keyColumnsStmt.setString(1, key_id); rsKeyCols = keyColumnsStmt.executeQuery(); while(rsKeyCols.next()) columns.put(rsKeyCols.getString(1), rsKeyCols.getString(2)); - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load key columns from TAP_SCHEMA.key_columns for the foreign key: \"" + key_id + "\"!", se); throw new DBException("Impossible to load key columns from TAP_SCHEMA.key_columns for the foreign key: \"" + key_id + "\"!", se); - }finally{ + } finally { close(rsKeyCols); } // create and add the new foreign key inside the source table: - try{ + try { sourceTable.addForeignKey(key_id, targetTable, columns, nullifyIfNeeded(description), nullifyIfNeeded(utype)); - }catch(Exception ex){ + } catch(Exception ex) { if ((ex instanceof SQLException && !isCancelled()) && logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to create the foreign key \"" + key_id + "\" because: " + ex.getMessage(), ex); throw new DBException("Impossible to create the foreign key \"" + key_id + "\" because: " + ex.getMessage(), ex); } } - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load columns from TAP_SCHEMA.columns!", se); throw new DBException("Impossible to load columns from TAP_SCHEMA.columns!", se); - }finally{ + } finally { close(rs); close(keyColumnsStmt); } @@ -1536,11 +1578,11 @@ public class JDBCConnection implements DBConnection { * @see tap.db.DBConnection#setTAPSchema(tap.metadata.TAPMetadata) */ @Override - public synchronized void setTAPSchema(final TAPMetadata metadata) throws DBCancelledException, DBException{ + public synchronized void setTAPSchema(final TAPMetadata metadata) throws DBCancelledException, DBException { // Starting of new query execution => disable the cancel flag: resetCancel(); - try{ + try { // A. GET THE DEFINITION OF ALL STANDARD TAP TABLES: TAPTable[] stdTables = mergeTAPSchemaDefs(metadata); @@ -1561,7 +1603,7 @@ public class JDBCConnection implements DBConnection { // 2. Create all standard TAP tables: if (logger != null) logger.logDB(LogLevel.INFO, this, "CREATE_TAP_SCHEMA", "Creating TAP_SCHEMA tables.", null); - for(TAPTable table : stdTables){ + for(TAPTable table : stdTables) { createTAPSchemaTable(table, stmt); if (isCancelled()) throw new DBCancelledException(); @@ -1579,10 +1621,10 @@ public class JDBCConnection implements DBConnection { createTAPTableIndexes(table, stmt); commit(); - }catch(DBCancelledException dce){ + } catch(DBCancelledException dce) { rollback(); throw dce; - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.ERROR, this, "CREATE_TAP_SCHEMA", "Impossible to SET TAP_SCHEMA in DB!", se); rollback(); @@ -1590,7 +1632,7 @@ public class JDBCConnection implements DBConnection { throw new DBCancelledException(); else throw new DBException("Impossible to SET TAP_SCHEMA in DB!", se); - }finally{ + } finally { closeStatement(); endTransaction(); } @@ -1629,11 +1671,11 @@ public class JDBCConnection implements DBConnection { * @see TAPMetadata#getStdSchema(boolean) * @see TAPMetadata#getStdTable(STDTable) */ - protected TAPTable[] mergeTAPSchemaDefs(final TAPMetadata metadata){ + 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()){ + while(tapSchema == null && itSchema.hasNext()) { TAPSchema schema = itSchema.next(); if (schema.getADQLName().equalsIgnoreCase(STDSchema.TAPSCHEMA.label)) tapSchema = schema; @@ -1641,7 +1683,7 @@ public class JDBCConnection implements DBConnection { // 2. Get the provided definition of the standard TAP tables: TAPTable[] customStdTables = new TAPTable[5]; - if (tapSchema != null){ + if (tapSchema != null) { /* if the schemas are not supported with this DBMS, * remove its DB name: */ @@ -1650,7 +1692,7 @@ public class JDBCConnection implements DBConnection { // retrieve only the standard TAP tables: Iterator<TAPTable> itTable = tapSchema.iterator(); - while(itTable.hasNext()){ + while(itTable.hasNext()) { TAPTable table = itTable.next(); int indStdTable = getCreationOrder(TAPMetadata.resolveStdTable(table.getADQLName())); if (indStdTable > -1) @@ -1659,7 +1701,7 @@ public class JDBCConnection implements DBConnection { } // 3. Build a common TAPSchema, if needed: - if (tapSchema == null){ + if (tapSchema == null) { // build a new TAP_SCHEMA definition based on the standard definition: tapSchema = TAPMetadata.getStdSchema(supportsSchema); @@ -1670,10 +1712,10 @@ public class JDBCConnection implements DBConnection { // 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++){ + for(int i = 0; i < stdTables.length; i++) { // CASE: no custom definition: - if (customStdTables[i] == null){ + if (customStdTables[i] == null) { // add the table to the fetched or built-in schema: tapSchema.addTable(stdTables[i]); } @@ -1699,7 +1741,7 @@ public class JDBCConnection implements DBConnection { * * @throws SQLException If any error occurs while querying or updating the database. */ - protected void resetTAPSchema(final Statement stmt, final TAPTable[] stdTables) throws SQLException{ + protected void resetTAPSchema(final Statement stmt, final TAPTable[] stdTables) throws SQLException { DatabaseMetaData dbMeta = connection.getMetaData(); // 1. Get the qualified DB schema name: @@ -1707,7 +1749,7 @@ public class JDBCConnection implements DBConnection { /* 2. Test whether the schema TAP_SCHEMA exists * and if it does not, create it: */ - if (dbSchemaName != null){ + if (dbSchemaName != null) { // test whether the schema TAP_SCHEMA exists: boolean hasTAPSchema = isSchemaExisting(dbSchemaName, dbMeta); @@ -1737,11 +1779,11 @@ public class JDBCConnection implements DBConnection { * * @see JDBCTranslator#isCaseSensitive(IdentifierField) */ - private void dropTAPSchemaTables(final TAPTable[] stdTables, final Statement stmt, final DatabaseMetaData dbMeta) throws SQLException{ + 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; - try{ + try { // 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); @@ -1749,23 +1791,23 @@ public class JDBCConnection implements DBConnection { // Identify which standard TAP tables must be dropped: rs = dbMeta.getTables(null, null, null, null); - while(rs.next()){ + while(rs.next()) { String rsSchema = nullifyIfNeeded(rs.getString(2)), rsTable = rs.getString(3); - if (!supportsSchema || (tapSchemaName == null && rsSchema == null) || equals(rsSchema, tapSchemaName, schemaCaseSensitive)){ + if (!supportsSchema || (tapSchemaName == null && rsSchema == null) || equals(rsSchema, tapSchemaName, schemaCaseSensitive)) { int indStdTable; indStdTable = getCreationOrder(isStdTable(rsTable, stdTables, tableCaseSensitive)); - if (indStdTable > -1){ + if (indStdTable > -1) { stdTablesToDrop[indStdTable] = (rsSchema != null ? "\"" + rsSchema + "\"." : "") + "\"" + rsTable + "\""; } } } - }finally{ + } finally { close(rs); } // Drop the existing tables (in the reverse order of creation): - for(int i = stdTablesToDrop.length - 1; i >= 0; i--){ + for(int i = stdTablesToDrop.length - 1; i >= 0; i--) { if (stdTablesToDrop[i] != null) stmt.executeUpdate("DROP TABLE " + stdTablesToDrop[i]); } @@ -1792,7 +1834,7 @@ public class JDBCConnection implements DBConnection { * @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{ + 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!"); @@ -1806,7 +1848,7 @@ public class JDBCConnection implements DBConnection { // b. List all the columns: sql.append('('); Iterator<TAPColumn> it = table.getColumns(); - while(it.hasNext()){ + while(it.hasNext()) { TAPColumn col = it.next(); // column name: @@ -1846,13 +1888,13 @@ public class JDBCConnection implements DBConnection { * @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){ + private String getPrimaryKeyDef(final String tableName) { STDTable stdTable = TAPMetadata.resolveStdTable(tableName); if (stdTable == null) return null; boolean caseSensitive = translator.isCaseSensitive(IdentifierField.COLUMN); - switch(stdTable){ + switch(stdTable) { case SCHEMAS: return " PRIMARY KEY(" + (caseSensitive ? "\"schema_name\"" : "schema_name") + ")"; case TABLES: @@ -1884,7 +1926,7 @@ public class JDBCConnection implements DBConnection { * @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 DBCancelledException, DBException, SQLException{ + protected void createTAPTableIndexes(final TAPTable table, final Statement stmt) throws DBCancelledException, 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!"); @@ -1896,7 +1938,7 @@ public class JDBCConnection implements DBConnection { final String indexNamePrefix = "INDEX_" + ((table.getADQLSchemaName() != null) ? (table.getADQLSchemaName() + "_") : "") + table.getADQLName() + "_"; Iterator<TAPColumn> it = table.getColumns(); - while(it.hasNext()){ + while(it.hasNext()) { TAPColumn col = it.next(); // Create an index only for columns that have the 'indexed' flag: if (col.isIndexed() && !isPartOfPrimaryKey(col.getADQLName())) @@ -1915,7 +1957,7 @@ public class JDBCConnection implements DBConnection { * @return <i>true</i> if the specified column is part of the primary key, * <i>false</i> otherwise. */ - private boolean isPartOfPrimaryKey(final String adqlName){ + private boolean isPartOfPrimaryKey(final String adqlName) { if (adqlName == null) return false; else @@ -1939,7 +1981,7 @@ public class JDBCConnection implements DBConnection { * @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, DBCancelledException, DBException{ + protected void fillTAPSchema(final TAPMetadata meta) throws SQLException, DBCancelledException, DBException { TAPTable metaTable; // 1. Fill SCHEMAS: @@ -1979,7 +2021,7 @@ public class JDBCConnection implements DBConnection { * @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, DBCancelledException, DBException{ + private Iterator<TAPTable> fillSchemas(final TAPTable metaTable, final Iterator<TAPSchema> itSchemas) throws SQLException, DBCancelledException, DBException { List<TAPTable> allTables = new ArrayList<TAPTable>(); // Build the SQL update query: @@ -1988,20 +2030,20 @@ public class JDBCConnection implements DBConnection { sql.append(translator.getColumnName(metaTable.getColumn("schema_name"))); sql.append(", ").append(translator.getColumnName(metaTable.getColumn("description"))); sql.append(", ").append(translator.getColumnName(metaTable.getColumn("utype"))); - if (supportsSchema){ + if (supportsSchema) { sql.append(", ").append(DB_NAME_COLUMN); sql.append(") VALUES (?, ?, ?, ?)"); - }else + } else sql.append(") VALUES (?, ?, ?)"); // Prepare the statement: PreparedStatement stmt = null; - try{ + try { stmt = connection.prepareStatement(sql.toString()); // Execute the query for each schema: int nbRows = 0; - while(itSchemas.hasNext()){ + while(itSchemas.hasNext()) { TAPSchema schema = itSchemas.next(); nbRows++; @@ -2026,7 +2068,7 @@ public class JDBCConnection implements DBConnection { throw new DBCancelledException(); else executeBatchUpdates(stmt, nbRows); - }finally{ + } finally { close(stmt); } @@ -2050,7 +2092,7 @@ public class JDBCConnection implements DBConnection { * @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, DBCancelledException, DBException{ + private Iterator<TAPColumn> fillTables(final TAPTable metaTable, final Iterator<TAPTable> itTables) throws SQLException, DBCancelledException, DBException { List<TAPColumn> allColumns = new ArrayList<TAPColumn>(); // Build the SQL update query: @@ -2067,12 +2109,12 @@ public class JDBCConnection implements DBConnection { // Prepare the statement: PreparedStatement stmt = null; - try{ + try { stmt = connection.prepareStatement(sql.toString()); // Execute the query for each table: int nbRows = 0; - while(itTables.hasNext()){ + while(itTables.hasNext()) { TAPTable table = itTables.next(); nbRows++; @@ -2102,7 +2144,7 @@ public class JDBCConnection implements DBConnection { throw new DBCancelledException(); else executeBatchUpdates(stmt, nbRows); - }finally{ + } finally { close(stmt); } @@ -2126,7 +2168,7 @@ public class JDBCConnection implements DBConnection { * @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, DBCancelledException, DBException{ + private Iterator<TAPForeignKey> fillColumns(final TAPTable metaTable, final Iterator<TAPColumn> itColumns) throws SQLException, DBCancelledException, DBException { List<TAPForeignKey> allKeys = new ArrayList<TAPForeignKey>(); // Build the SQL update query: @@ -2150,12 +2192,12 @@ public class JDBCConnection implements DBConnection { // Prepare the statement: PreparedStatement stmt = null; - try{ + try { stmt = connection.prepareStatement(sql.toString()); // Execute the query for each column: int nbRows = 0; - while(itColumns.hasNext()){ + while(itColumns.hasNext()) { TAPColumn col = itColumns.next(); nbRows++; @@ -2192,7 +2234,7 @@ public class JDBCConnection implements DBConnection { throw new DBCancelledException(); else executeBatchUpdates(stmt, nbRows); - }finally{ + } finally { close(stmt); } @@ -2215,7 +2257,7 @@ public class JDBCConnection implements DBConnection { * @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, DBCancelledException, DBException{ + private void fillKeys(final TAPTable metaKeys, final TAPTable metaKeyColumns, final Iterator<TAPForeignKey> itKeys) throws SQLException, DBCancelledException, DBException { // Build the SQL update query for KEYS: StringBuffer sqlKeys = new StringBuffer("INSERT INTO "); sqlKeys.append(translator.getTableName(metaKeys, supportsSchema)).append(" ("); @@ -2227,7 +2269,7 @@ public class JDBCConnection implements DBConnection { sqlKeys.append(") VALUES (?, ?, ?, ?, ?)"); PreparedStatement stmtKeys = null, stmtKeyCols = null; - try{ + try { // Prepare the statement for KEYS: stmtKeys = connection.prepareStatement(sqlKeys.toString()); @@ -2244,7 +2286,7 @@ public class JDBCConnection implements DBConnection { // Execute the query for each column: int nbKeys = 0, nbKeyColumns = 0; - while(itKeys.hasNext()){ + while(itKeys.hasNext()) { TAPForeignKey key = itKeys.next(); nbKeys++; @@ -2269,7 +2311,7 @@ public class JDBCConnection implements DBConnection { // add the key columns into KEY_COLUMNS: Iterator<Map.Entry<String, String>> itAssoc = key.iterator(); - while(itAssoc.hasNext()){ + while(itAssoc.hasNext()) { nbKeyColumns++; Map.Entry<String, String> assoc = itAssoc.next(); stmtKeyCols.setString(1, key.getKeyId()); @@ -2287,11 +2329,11 @@ public class JDBCConnection implements DBConnection { // If the query has been aborted, return immediately: if (isCancelled()) throw new DBCancelledException(); - else{ + else { executeBatchUpdates(stmtKeys, nbKeys); executeBatchUpdates(stmtKeyCols, nbKeyColumns); } - }finally{ + } finally { close(stmtKeys); close(stmtKeyCols); } @@ -2321,7 +2363,7 @@ public class JDBCConnection implements DBConnection { * @see #checkUploadedTableDef(TAPTable) */ @Override - public synchronized boolean addUploadedTable(TAPTable tableDef, TableIterator data) throws DBException, DataReadException{ + 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; @@ -2332,7 +2374,7 @@ public class JDBCConnection implements DBConnection { // Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD): checkUploadedTableDef(tableDef); - try{ + try { // Start a transaction: startTransaction(); @@ -2342,13 +2384,13 @@ public class JDBCConnection implements DBConnection { DatabaseMetaData dbMeta = connection.getMetaData(); // 1. Create the upload schema, if it does not already exist: - if (!isSchemaExisting(tableDef.getDBSchemaName(), dbMeta)){ + 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)){ + else if (isTableExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), dbMeta)) { 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); @@ -2364,7 +2406,7 @@ public class JDBCConnection implements DBConnection { StringBuffer sqlBuf = new StringBuffer("CREATE TABLE "); sqlBuf.append(translator.getTableName(tableDef, supportsSchema)).append(" ("); Iterator<TAPColumn> it = tableDef.getColumns(); - while(it.hasNext()){ + while(it.hasNext()) { TAPColumn col = it.next(); // column name: sqlBuf.append(translator.getColumnName(col)); @@ -2397,20 +2439,20 @@ public class JDBCConnection implements DBConnection { return true; - }catch(SQLException se){ + } catch(SQLException se) { rollback(); if (!isCancelled() && logger != null) 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){ + } catch(DBException de) { rollback(); if (logger != null && (de instanceof DBCancelledException || isCancelled())) logger.logDB(LogLevel.INFO, this, "ADD_UPLOAD_TABLE", "Upload of the table \"" + tableDef.getADQLName() + "\" (in DB: " + translator.getTableName(tableDef, supportsSchema) + ") canceled!", null); throw de; - }catch(DataReadException dre){ + } catch(DataReadException dre) { rollback(); throw dre; - }finally{ + } finally { closeStatement(); endTransaction(); } @@ -2438,7 +2480,7 @@ public class JDBCConnection implements DBConnection { * @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, DBCancelledException, DBException, DataReadException{ + protected int fillUploadedTable(final TAPTable metaTable, final TableIterator data) throws SQLException, DBCancelledException, DBException, DataReadException { // 1. Build the SQL update query: StringBuffer sql = new StringBuffer("INSERT INTO "); StringBuffer varParam = new StringBuffer(); @@ -2446,8 +2488,8 @@ public class JDBCConnection implements DBConnection { sql.append(translator.getTableName(metaTable, supportsSchema)).append(" ("); // ...list of columns: TAPColumn[] cols = data.getMetadata(); - for(int c = 0; c < cols.length; c++){ - if (c > 0){ + for(int c = 0; c < cols.length; c++) { + if (c > 0) { sql.append(", "); varParam.append(", "); } @@ -2460,41 +2502,41 @@ public class JDBCConnection implements DBConnection { // 2. Prepare the statement: PreparedStatement stmt = null; int nbRows = 0; - try{ + try { stmt = connection.prepareStatement(sql.toString()); // 3. Execute the query for each given row: - while(data.nextRow()){ + while(data.nextRow()) { nbRows++; int c = 1; - while(data.hasNextCol()){ + while(data.hasNextCol()) { Object val = data.nextCol(); - if (val != null && cols[c - 1] != null){ + if (val != null && cols[c - 1] != null) { /* TIMESTAMP FORMATTING */ - if (cols[c - 1].getDatatype().type == DBDatatype.TIMESTAMP){ - try{ + if (cols[c - 1].getDatatype().type == DBDatatype.TIMESTAMP) { + try { val = new Timestamp(ISO8601Format.parse(val.toString())); - }catch(ParseException pe){ + } catch(ParseException pe) { if (logger != null) 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){ + else if (cols[c - 1].getDatatype().type == DBDatatype.POINT || cols[c - 1].getDatatype().type == DBDatatype.REGION) { Region region; // parse the region as an STC-S expression: - try{ + try { region = STCS.parseRegion(val.toString()); - }catch(adql.parser.grammar.ParseException e){ + } catch(adql.parser.grammar.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{ + try { val = translator.translateGeometryToDB(region); - }catch(adql.parser.grammar.ParseException e){ + } catch(adql.parser.grammar.ParseException e) { if (logger != null) 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); @@ -2530,7 +2572,7 @@ public class JDBCConnection implements DBConnection { return nbRows; - }finally{ + } finally { close(stmt); } } @@ -2556,7 +2598,7 @@ public class JDBCConnection implements DBConnection { * @see #checkUploadedTableDef(TAPTable) */ @Override - public synchronized boolean dropUploadedTable(final TAPTable tableDef) throws DBException{ + 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; @@ -2567,7 +2609,7 @@ public class JDBCConnection implements DBConnection { // Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD): checkUploadedTableDef(tableDef); - try{ + try { // Check the existence of the table to drop: if (!isTableExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), connection.getMetaData())) @@ -2577,7 +2619,7 @@ public class JDBCConnection implements DBConnection { int cnt = getStatement().executeUpdate("DROP TABLE " + translator.getTableName(tableDef, supportsSchema)); // Log the end: - if (logger != null){ + if (logger != null) { if (cnt >= 0) logger.logDB(LogLevel.INFO, this, "TABLE_DROPPED", "Table \"" + tableDef.getADQLName() + "\" (in DB: " + translator.getTableName(tableDef, supportsSchema) + ") dropped.", null); else @@ -2587,11 +2629,11 @@ public class JDBCConnection implements DBConnection { // Ensure the update is successful: return (cnt >= 0); - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) 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{ + } finally { cancel(true); closeStatement(); } @@ -2617,12 +2659,12 @@ public class JDBCConnection implements DBConnection { * @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{ + 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)."); - if (!supportsSchema){ + 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) @@ -2650,7 +2692,7 @@ public class JDBCConnection implements DBConnection { * @see JDBCTranslator#convertTypeToDB(DBType) * @see #defaultTypeConversion(DBType) */ - protected String convertTypeToDB(final DBType type){ + protected String convertTypeToDB(final DBType type) { String dbmsType = translator.convertTypeToDB(type); return (dbmsType == null) ? defaultTypeConversion(type) : dbmsType; } @@ -2677,11 +2719,11 @@ public class JDBCConnection implements DBConnection { * * @return The corresponding DB type, or VARCHAR if the given type is not managed or is NULL. */ - protected String defaultTypeConversion(DBType datatype){ + protected String defaultTypeConversion(DBType datatype) { if (datatype == null) datatype = new DBType(DBDatatype.VARCHAR); - switch(datatype.type){ + switch(datatype.type) { case SMALLINT: return dbms.equals("sqlite") ? "INTEGER" : "SMALLINT"; @@ -2783,14 +2825,14 @@ public class JDBCConnection implements DBConnection { * @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){ + 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){ + } catch(SQLException se) { supportsTransaction = false; if (logger != null) logger.logDB(LogLevel.ERROR, this, "START_TRANSACTION", "Transaction STARTing impossible!", se); @@ -2816,14 +2858,14 @@ public class JDBCConnection implements DBConnection { * @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){ + 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){ + } catch(SQLException se) { supportsTransaction = false; if (logger != null) logger.logDB(LogLevel.ERROR, this, "COMMIT", "Transaction COMMIT impossible!", se); @@ -2852,7 +2894,7 @@ public class JDBCConnection implements DBConnection { * * @see #rollback(boolean) */ - protected final void rollback(){ + protected final void rollback() { rollback(true); } @@ -2879,14 +2921,14 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected void rollback(final boolean log){ - try{ - if (supportsTransaction && !connection.getAutoCommit()){ + 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){ + } catch(SQLException se) { supportsTransaction = false; if (log && logger != null) logger.logDB(LogLevel.ERROR, this, "ROLLBACK", "Transaction ROLLBACK impossible!", se); @@ -2913,7 +2955,7 @@ public class JDBCConnection implements DBConnection { * * @see #endTransaction(boolean) */ - protected final void endTransaction(){ + protected final void endTransaction() { endTransaction(true); } @@ -2939,14 +2981,14 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected void endTransaction(final boolean log){ - try{ - if (supportsTransaction){ + 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){ + } catch(SQLException se) { supportsTransaction = false; if (log && logger != null) logger.logDB(LogLevel.ERROR, this, "END_TRANSACTION", "Transaction ENDing impossible!", se); @@ -2966,11 +3008,11 @@ public class JDBCConnection implements DBConnection { * * @param rs {@link ResultSet} to close. */ - protected final void close(final ResultSet rs){ - try{ + protected final void close(final ResultSet rs) { + try { if (rs != null) rs.close(); - }catch(SQLException se){ + } catch(SQLException se) { if (logger != null) logger.logDB(LogLevel.WARNING, this, "CLOSE", "Can not close a ResultSet!", null); } @@ -3002,13 +3044,13 @@ public class JDBCConnection implements DBConnection { * * @see #cancel(Statement, boolean) */ - protected final void close(final Statement stmt){ - try{ - if (stmt != null){ + protected final void close(final Statement stmt) { + try { + if (stmt != null) { cancel(stmt, false); stmt.close(); } - }catch(SQLException se){ + } catch(SQLException se) { if (logger != null) logger.logDB(LogLevel.WARNING, this, "CLOSE", "Can not close a Statement!", null); } @@ -3033,7 +3075,7 @@ public class JDBCConnection implements DBConnection { * * @return Its corresponding boolean value. */ - protected final boolean toBoolean(final Object colValue){ + protected final boolean toBoolean(final Object colValue) { // NULL => false: if (colValue == null) return false; @@ -3043,19 +3085,19 @@ public class JDBCConnection implements DBConnection { 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){ + 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{ + else { String strFlag = colValue.toString().trim(); - try{ + try { int intFlag = Integer.parseInt(strFlag); return (intFlag > 0); - }catch(NumberFormatException nfe){ + } catch(NumberFormatException nfe) { return strFlag.equalsIgnoreCase("t") || strFlag.equalsIgnoreCase("true"); } } @@ -3069,38 +3111,26 @@ public class JDBCConnection implements DBConnection { * * @return NULL if the given string is NULL or empty, otherwise the given value. */ - protected final String nullifyIfNeeded(final String dbValue){ + 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. + * Search a {@link TAPTable} instance whose the ADQL name matches exactly + * (and 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. + * @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); - } - + private TAPTable searchTable(String tableName, final Iterator<TAPTable> itTables) { // Search by schema name (if any) and then by table name: - while(itTables.hasNext()){ + 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())) + if (tableName.equals(table.getRawName())) return table; } @@ -3123,7 +3153,7 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected int getTableSchemaIndexInMetadata(){ + protected int getTableSchemaIndexInMetadata() { return dbms.equalsIgnoreCase(DBMS_MYSQL) ? 1 : 2; } @@ -3139,7 +3169,7 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected ResultSet getDBMetaSchemas(final DatabaseMetaData dbMeta) throws SQLException{ + protected ResultSet getDBMetaSchemas(final DatabaseMetaData dbMeta) throws SQLException { return (dbms.equalsIgnoreCase(DBMS_MYSQL) ? dbMeta.getCatalogs() : dbMeta.getSchemas()); } @@ -3161,7 +3191,7 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected ResultSet getDBMetaTables(final DatabaseMetaData dbMeta, final String schemaPattern, final String tablePattern) throws SQLException{ + protected ResultSet getDBMetaTables(final DatabaseMetaData dbMeta, final String schemaPattern, final String tablePattern) throws SQLException { if (dbms.equalsIgnoreCase(DBMS_MYSQL)) return dbMeta.getTables(schemaPattern, null, tablePattern, null); else @@ -3190,7 +3220,7 @@ public class JDBCConnection implements DBConnection { * * @since 2.1 */ - protected ResultSet getDBMetaColumns(final DatabaseMetaData dbMeta, final String schemaPattern, final String tablePattern, final String columnPattern) throws SQLException{ + protected ResultSet getDBMetaColumns(final DatabaseMetaData dbMeta, final String schemaPattern, final String tablePattern, final String columnPattern) throws SQLException { if (dbms.equalsIgnoreCase(DBMS_MYSQL)) return dbMeta.getColumns(schemaPattern, null, tablePattern, columnPattern); else @@ -3221,7 +3251,7 @@ public class JDBCConnection implements DBConnection { * * @throws SQLException If any error occurs while interrogating the database about existing schema. */ - protected boolean isSchemaExisting(String schemaName, final DatabaseMetaData dbMeta) throws SQLException{ + protected boolean isSchemaExisting(String schemaName, final DatabaseMetaData dbMeta) throws SQLException { if (!supportsSchema || schemaName == null || schemaName.length() == 0) return true; @@ -3229,15 +3259,15 @@ public class JDBCConnection implements DBConnection { boolean caseSensitive = translator.isCaseSensitive(IdentifierField.SCHEMA); ResultSet rs = null; - try{ + try { // List all schemas available and stop when a schema name matches ignoring the case: rs = getDBMetaSchemas(dbMeta); boolean hasSchema = false; - while(!hasSchema && rs.next()){ + while(!hasSchema && rs.next()) { hasSchema = equals(rs.getString(1), schemaName, caseSensitive); } return hasSchema; - }finally{ + } finally { close(rs); } } @@ -3269,7 +3299,7 @@ public class JDBCConnection implements DBConnection { * * @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{ + protected boolean isTableExisting(String schemaName, String tableName, final DatabaseMetaData dbMeta) throws DBException, SQLException { if (tableName == null || tableName.length() == 0) return true; @@ -3278,30 +3308,30 @@ public class JDBCConnection implements DBConnection { boolean tableCaseSensitive = translator.isCaseSensitive(IdentifierField.TABLE); ResultSet rs = null; - try{ + try { // List all matching tables: - if (supportsSchema){ + if (supportsSchema) { String schemaPattern = schemaCaseSensitive ? schemaName : null; String tablePattern = tableCaseSensitive ? tableName : null; rs = getDBMetaTables(dbMeta, schemaPattern, tablePattern); - }else{ + } else { String tablePattern = tableCaseSensitive ? tableName : null; rs = getDBMetaTables(dbMeta, null, tablePattern); } // 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()){ + while(rs.next()) { String rsSchema = nullifyIfNeeded(rs.getString(getTableSchemaIndexInMetadata())); String rsTable = rs.getString(3); - if (!supportsSchema || schemaName == null || equals(rsSchema, schemaName, schemaCaseSensitive)){ + if (!supportsSchema || schemaName == null || equals(rsSchema, schemaName, schemaCaseSensitive)) { if (equals(rsTable, tableName, tableCaseSensitive)) cnt++; } } - if (cnt > 1){ + 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 + "))!"); @@ -3309,7 +3339,7 @@ public class JDBCConnection implements DBConnection { return cnt == 1; - }finally{ + } finally { close(rs); } } @@ -3343,7 +3373,7 @@ public class JDBCConnection implements DBConnection { * * @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{ + protected boolean isColumnExisting(String schemaName, String tableName, String columnName, final DatabaseMetaData dbMeta) throws DBException, SQLException { if (columnName == null || columnName.length() == 0) return true; @@ -3353,7 +3383,7 @@ public class JDBCConnection implements DBConnection { boolean columnCaseSensitive = translator.isCaseSensitive(IdentifierField.COLUMN); ResultSet rsT = null, rsC = null; - try{ + try { /* Note: * * The DatabaseMetaData.getColumns(....) function does not work properly @@ -3365,11 +3395,11 @@ public class JDBCConnection implements DBConnection { */ // List all matching tables: - if (supportsSchema){ + if (supportsSchema) { String schemaPattern = schemaCaseSensitive ? schemaName : null; String tablePattern = tableCaseSensitive ? tableName : null; rsT = getDBMetaTables(dbMeta, schemaPattern, tablePattern); - }else{ + } else { String tablePattern = tableCaseSensitive ? tableName : null; rsT = getDBMetaTables(dbMeta, null, tablePattern); } @@ -3377,17 +3407,17 @@ public class JDBCConnection implements DBConnection { // For each matching table: int cnt = 0; String columnPattern = columnCaseSensitive ? columnName : null; - while(rsT.next()){ + while(rsT.next()) { String rsSchema = nullifyIfNeeded(rsT.getString(getTableSchemaIndexInMetadata())); String rsTable = rsT.getString(3); // test the schema name: - if (!supportsSchema || schemaName == null || equals(rsSchema, schemaName, schemaCaseSensitive)){ + if (!supportsSchema || schemaName == null || equals(rsSchema, schemaName, schemaCaseSensitive)) { // test the table name: - if ((tableName == null || equals(rsTable, tableName, tableCaseSensitive))){ + if ((tableName == null || equals(rsTable, tableName, tableCaseSensitive))) { // list its columns: rsC = getDBMetaColumns(dbMeta, rsSchema, rsTable, columnPattern); // count all matching columns: - while(rsC.next()){ + while(rsC.next()) { String rsColumn = rsC.getString(4); if (equals(rsColumn, columnName, columnCaseSensitive)) cnt++; @@ -3397,7 +3427,7 @@ public class JDBCConnection implements DBConnection { } } - if (cnt > 1){ + 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 + "))!"); @@ -3405,7 +3435,7 @@ public class JDBCConnection implements DBConnection { return cnt == 1; - }finally{ + } finally { close(rsT); close(rsC); } @@ -3450,9 +3480,9 @@ public class JDBCConnection implements DBConnection { * * @see TAPMetadata#resolveStdTable(String) */ - protected final STDTable isStdTable(final String dbTableName, final TAPTable[] stdTables, final boolean caseSensitive){ - if (dbTableName != null){ - for(TAPTable t : stdTables){ + protected final STDTable isStdTable(final String dbTableName, final TAPTable[] stdTables, final boolean caseSensitive) { + if (dbTableName != null) { + for(TAPTable t : stdTables) { if (equals(dbTableName, t.getDBName(), caseSensitive)) return TAPMetadata.resolveStdTable(t.getADQLName()); } @@ -3496,13 +3526,13 @@ public class JDBCConnection implements DBConnection { * @throws SQLException If {@link PreparedStatement#executeUpdate()} fails.</i> * @throws DBException If {@link PreparedStatement#addBatch()} fails and this update does not concern the first row, or if the number of updated rows is different from 1. */ - protected final void executeUpdate(final PreparedStatement stmt, int indRow) throws SQLException, DBException{ + protected final void executeUpdate(final PreparedStatement stmt, int indRow) throws SQLException, DBException { // BATCH INSERTION: (the query is queued and will be executed later) - if (supportsBatchUpdates){ + if (supportsBatchUpdates) { // Add the prepared query in the batch queue of the statement: - try{ + try { stmt.addBatch(); - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled()) supportsBatchUpdates = false; /* @@ -3512,10 +3542,10 @@ public class JDBCConnection implements DBConnection { * Otherwise, it is impossible to insert the previous batched rows ; an exception must be thrown * and must stop the whole TAP_SCHEMA initialization. */ - if (indRow == 1){ + if (indRow == 1) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.WARNING, this, "EXEC_UPDATE", "BATCH query impossible => TRYING AGAIN IN A NORMAL EXECUTION (executeUpdate())!", se); - }else{ + } else { if (!isCancelled() && logger != null) logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "BATCH query impossible!", se); throw new DBException("BATCH query impossible!", se); @@ -3524,13 +3554,13 @@ public class JDBCConnection implements DBConnection { } // NORMAL INSERTION: (immediate insertion) - if (!supportsBatchUpdates){ + if (!supportsBatchUpdates) { // Insert the row prepared in the given statement: int nbRowsWritten = stmt.executeUpdate(); // Check the row has been inserted with success: - if (nbRowsWritten != 1){ + if (nbRowsWritten != 1) { if (logger != null) logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "ROW " + indRow + " not inserted!", null); throw new DBException("ROW " + indRow + " not inserted!"); @@ -3562,14 +3592,14 @@ public class JDBCConnection implements DBConnection { * * @throws DBException If {@link PreparedStatement#executeBatch()} fails, or if the number of updated rows is different from the given one. */ - protected final void executeBatchUpdates(final PreparedStatement stmt, int nbRows) throws DBException{ - if (supportsBatchUpdates){ + protected final void executeBatchUpdates(final PreparedStatement stmt, int nbRows) throws DBException { + if (supportsBatchUpdates) { // Execute all the batch queries: int[] rows; - try{ + try { rows = stmt.executeBatch(); - }catch(SQLException se){ - if (!isCancelled()){ + } catch(SQLException se) { + if (!isCancelled()) { supportsBatchUpdates = false; if (logger != null) logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "BATCH execution impossible!", se); @@ -3578,9 +3608,9 @@ public class JDBCConnection implements DBConnection { } // Remove executed queries from the statement: - try{ + try { stmt.clearBatch(); - }catch(SQLException se){ + } catch(SQLException se) { if (!isCancelled() && logger != null) logger.logDB(LogLevel.WARNING, this, "EXEC_UPDATE", "CLEAR BATCH impossible!", se); } @@ -3591,7 +3621,7 @@ public class JDBCConnection implements DBConnection { nbRowsUpdated += rows[i]; // Check all given rows have been inserted with success: - if (nbRowsUpdated != nbRows){ + if (nbRowsUpdated != nbRows) { if (logger != null) logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "ROWS not all update (" + nbRows + " to update ; " + nbRowsUpdated + " updated)!", null); throw new DBException("ROWS not all updated (" + nbRows + " to update ; " + nbRowsUpdated + " updated)!"); @@ -3605,7 +3635,7 @@ public class JDBCConnection implements DBConnection { * @param lst List to update. * @param it All items to append inside the list. */ - private <T> void appendAllInto(final List<T> lst, final Iterator<T> it){ + private <T> void appendAllInto(final List<T> lst, final Iterator<T> it) { while(it.hasNext()) lst.add(it.next()); } @@ -3629,11 +3659,11 @@ public class JDBCConnection implements DBConnection { * * @return <i>true</i> if both names are equal, <i>false</i> otherwise. */ - protected final boolean equals(final String dbName, final String metaName, final boolean caseSensitive){ + protected final boolean equals(final String dbName, final String metaName, final boolean caseSensitive) { if (dbName == null || metaName == null) return false; - if (caseSensitive){ + if (caseSensitive) { if (supportsMixedCaseQuotedIdentifier || mixedCaseQuoted) return dbName.equals(metaName); else if (lowerCaseQuoted) @@ -3642,7 +3672,7 @@ public class JDBCConnection implements DBConnection { return dbName.equals(metaName.toUpperCase()); else return dbName.equalsIgnoreCase(metaName); - }else{ + } else { if (supportsMixedCaseUnquotedIdentifier) return dbName.equalsIgnoreCase(metaName); else if (lowerCaseUnquoted) @@ -3655,7 +3685,7 @@ public class JDBCConnection implements DBConnection { } @Override - public void setFetchSize(final int size){ + public void setFetchSize(final int size) { supportsFetchSize = true; fetchSize = (size > 0) ? size : IGNORE_FETCH_SIZE; } diff --git a/src/tap/metadata/TAPColumn.java b/src/tap/metadata/TAPColumn.java index 936909f0340d824fdfe90512e75ca070bb1730bb..6516976426b494eb8936357b95b132c9acfac054 100644 --- a/src/tap/metadata/TAPColumn.java +++ b/src/tap/metadata/TAPColumn.java @@ -2,21 +2,21 @@ package tap.metadata; /* * This file is part of TAPLibrary. - * + * * TAPLibrary is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * TAPLibrary is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * + * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -29,171 +29,216 @@ import adql.db.DBColumn; import adql.db.DBTable; import adql.db.DBType; import adql.db.DBType.DBDatatype; +import adql.db.DefaultDBTable; /** - * <p>Represent a column as described by the IVOA standard in the TAP protocol definition.</p> - * + * Represent a column as described by the IVOA standard in the TAP protocol + * definition. + * * <p> - * This object representation has exactly the same fields as the column of the table TAP_SCHEMA.columns. - * But it also provides a way to add other data. For instance, if information not listed in the standard - * may be stored here, they can be using the function {@link #setOtherData(Object)}. This object can be - * a single value (integer, string, ...), but also a {@link Map}, {@link List}, etc... + * This object representation has exactly the same fields as the column of the + * table TAP_SCHEMA.columns. But it also provides a way to add other data. For + * instance, if information not listed in the standard may be stored here, they + * can be using the function {@link #setOtherData(Object)}. This object can be + * a single value (integer, string, ...), but also a {@link Map}, {@link List}, + * etc... * </p> - * - * <p><i><b>Important note:</b> - * A {@link TAPColumn} object MUST always have a DB name. That's why, {@link #getDBName()} returns - * what {@link #getADQLName()} returns when no DB name is set. After creation, it is possible to set - * the DB name with {@link #setDBName(String)}. - * <br/> - * This DB name MUST be UNqualified and without double quotes. If a NULL or empty value is provided, - * {@link #getDBName()} returns what {@link #getADQLName()} returns. - * </i></p> - * + * + * <i> + * <p><b>Important note:</b> + * A {@link TAPColumn} object MUST always have a DB name. That's why, + * {@link #getDBName()} returns what {@link #getADQLName()} returns when no DB + * name is set. After creation, it is possible to set the DB name with + * {@link #setDBName(String)}. + * </p> + * <p> + * This DB name MUST be UNqualified and without double quotes. If a NULL or + * empty value is provided, {@link #getDBName()} returns what + * {@link #getADQLName()} returns. + * </p> + * </i> + * * <h3>Set a table</h3> - * + * * <p> - * By default a column is detached (not part of a table). To specify the table in which this column is, - * you must use {@link TAPTable#addColumn(TAPColumn)}. By doing this, the table link inside this column - * will be set automatically and you will be able to get the table with {@link #getTable()}. + * By default a column is detached (not part of a table). To specify the table + * in which this column is, you must use {@link TAPTable#addColumn(TAPColumn)}. + * By doing this, the table link inside this column will be set automatically + * and you will be able to get the table with {@link #getTable()}. * </p> - * + * * <h3>Foreign keys</h3> - * + * * <p> - * In case this column is linked to one or several of other tables, it will be possible to list all - * foreign keys where the target columns is with {@link #getTargets()}. In the same way, it will be - * possible to list all foreign keys in which this column is a target with {@link #getSources()}. - * However, in order to ensure the consistency between all metadata, these foreign key's links are - * set at the table level by the table itself using {@link #addSource(TAPForeignKey)} and - * {@link #addTarget(TAPForeignKey)}. + * In case this column is linked to one or several of other tables, it will be + * possible to list all foreign keys where the target columns is with + * {@link #getTargets()}. In the same way, it will be possible to list all + * foreign keys in which this column is a target with {@link #getSources()}. + * However, in order to ensure the consistency between all metadata, these + * foreign key's links are set at the table level by the table itself using + * {@link #addSource(TAPForeignKey)} and {@link #addTarget(TAPForeignKey)}. * </p> - * + * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (07/2017) + * @version 2.4 (09/2019) */ public class TAPColumn implements DBColumn { - /** Name that this column MUST have in ADQL queries. */ + /** ADQL name of this column. */ private final String adqlName; - /** Indicates whether the given ADQL name must be simplified by {@link #getADQLName()}. - * <p>Here, "simplification" means removing the surrounding double quotes and the table prefix if any.</p> - * @since 2.1 */ - private final boolean simplificationNeeded; + /** Indicate whether the ADQL column name is case sensitive. In such case, + * this name will be put between double quotes in ADQL. + * @since 2.4 */ + private boolean columnCaseSensitive = false; /** Name that this column have in the database. - * <i>Note: It CAN NOT be NULL. By default, it is the ADQL name.</i> */ + * <p><i><b>Note:</b> + * It CAN NOT be NULL. By default, it is the ADQL name. + * </i></p> */ private String dbName = null; /** Table which owns this column. - * <i>Note: It should be NULL only at the construction or for a quick representation of a column. - * Then, this attribute is automatically set by a {@link TAPTable} when adding this column inside it - * with {@link TAPTable#addColumn(TAPColumn)}.</i> */ + * <p><i><b>Note:</b> + * It should be NULL only at the construction or for a quick representation + * of a column. Then, this attribute is automatically set by a + * {@link TAPTable} when adding this column inside it with + * {@link TAPTable#addColumn(TAPColumn)}. + * </i></p> */ private DBTable table = null; /** Description of this column. - * <i>Note: Standard TAP column field ; MAY be NULL.</i> */ + * <p><i><b>Note:</b> + * Standard TAP column field ; MAY be NULL. + * </i></p> */ private String description = null; /** Unit of this column's values. - * <i>Note: Standard TAP column field ; MAY be NULL.</i> */ + * <p><i><b>Note:</b> + * Standard TAP column field ; MAY be NULL. + * </i></p> */ private String unit = null; /** UCD describing the scientific content of this column. - * <i>Note: Standard TAP column field ; MAY be NULL.</i> */ + * <p><i><b>Note:</b> + * Standard TAP column field ; MAY be NULL. + * </i></p> */ private String ucd = null; /** UType associating this column with a data-model. - * <i>Note: Standard TAP column field ; MAY be NULL.</i> */ + * <p><i><b>Note:</b> + * Standard TAP column field ; MAY be NULL. + * </i></p> */ private String utype = null; /** Type of this column. - * <i>Note: Standard TAP column field ; CAN'T be NULL.</i> */ + * <p><i><b>Note:</b> + * Standard TAP column field ; CAN'T be NULL. + * </i></p> */ private DBType datatype = new DBType(DBDatatype.UNKNOWN); - /** Flag indicating whether this column is one of those that should be returned by default. - * <i>Note: Standard TAP column field ; FALSE by default.</i> */ + /** Flag indicating whether this column is one of those that should be + * returned by default. + * <p><i><b>Note:</b> + * Standard TAP column field ; FALSE by default. + * </i></p> */ private boolean principal = false; /** Flag indicating whether this column is indexed in the database. - * <i>Note: Standard TAP column field ; FALSE by default.</i> */ + * <p><i><b>Note:</b> + * Standard TAP column field ; FALSE by default. + * </i></p> */ private boolean indexed = false; /** Flag indicating whether this column can be set to NULL in the database. - * <i>Note: Standard TAP column field ; FALSE by default.</i> + * <p><i><b>Note:</b> + * Standard TAP column field ; FALSE by default. + * </i></p> * @since 2.0 */ private boolean nullable = false; /** Flag indicating whether this column is defined by a standard. - * <i>Note: Standard TAP column field ; FALSE by default.</i> */ + * <p><i><b>Note:</b> + * Standard TAP column field ; FALSE by default. + * </i></p> */ private boolean std = false; /** Ordering index of this column inside its table. - * <i>Note: Standard TAP column field since TAP 1.1.</i> + * <p><i><b>Note:</b> + * Standard TAP column field since TAP 1.1. + * </i></p> * @since 2.1 */ private int index = -1; - + /** Coordinate system used by this column values. - * <i>Note: Of course, this attribute has to be set only on coordinate columns.</i> + * <p><i><b>Note:</b> + * Of course, this attribute has to be set only on coordinate columns. + * </i></p> * @since 2.1 */ private TAPCoosys coosys = null; /** Let add some information in addition of the ones of the TAP protocol. - * <i>Note: This object can be anything: an {@link Integer}, a {@link String}, a {@link Map}, a {@link List}, ... - * Its content is totally free and never used or checked.</i> */ + * <p><i><b>Note:</b> + * This object can be anything: an {@link Integer}, a {@link String}, a + * {@link Map}, a {@link List}, ... Its content is totally free and never + * used or checked. + * </i></p> */ protected Object otherData = null; /** List all foreign keys in which this column is a source. - * <p><b>CAUTION: For consistency consideration, this attribute SHOULD never be modified! - * It is set by the constructor and filled ONLY by the table.</b></p> */ + * <p><i><b>CAUTION:</b> + * For consistency consideration, this attribute SHOULD never be modified! + * It is set by the constructor and filled ONLY by the table. + * </i></p> */ protected final ArrayList<TAPForeignKey> lstTargets; /** List all foreign keys in which this column is a target. - * <p><b>CAUTION: For consistency consideration, this attribute SHOULD never be modified! - * It is set by the constructor and filled ONLY by the table.</b></p> */ + * <p><i><b>CAUTION:</b> + * For consistency consideration, this attribute SHOULD never be modified! + * It is set by the constructor and filled ONLY by the table. + * </i></p> */ protected final ArrayList<TAPForeignKey> lstSources; /** - * <p>Build a VARCHAR {@link TAPColumn} instance with the given ADQL name.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a VARCHAR {@link TAPColumn} instance with the given ADQL name. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: + * + * <p><i><b>Note 2:</b> * The datatype is set by default to VARCHAR. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the column name is prefixed by its table name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the table name must be exactly the same - * as what the function {@link TAPTable#getRawName()} of the set table returns. + * The column name MUST NOT be prefixed by its table name. * </li> * <li> - * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} but will + * Double quotes may surround the column name. In such case, they + * indicate that the column name must be considered as case sensitive. + * If present, these double quotes will be removed but will * still appear in the result of {@link #getRawName()}. * </li> * </ul> - * - * @param columnName Name that this column MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). - */ - public TAPColumn(String columnName) throws NullPointerException{ + * + * @param columnName ADQL name of this column. + * + * @throws NullPointerException If the given name is NULL or an empty string. + */ + public TAPColumn(String columnName) throws NullPointerException { if (columnName == null) throw new NullPointerException("Missing column name!"); - adqlName = columnName.trim(); - simplificationNeeded = (adqlName.indexOf('.') > 0 || adqlName.indexOf('"') >= 0); + columnName = columnName.trim(); + columnCaseSensitive = DefaultDBTable.isDelimited(columnName); + adqlName = (columnCaseSensitive ? columnName.substring(1, columnName.length() - 1).replaceAll("\"\"", "\"") : columnName); - if (getADQLName().length() == 0) + if (adqlName.trim().length() == 0) throw new NullPointerException("Missing column name!"); dbName = null; @@ -203,287 +248,287 @@ public class TAPColumn implements DBColumn { } /** - * <p>Build a {@link TAPColumn} instance with the given ADQL name and datatype.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a {@link TAPColumn} instance with the given ADQL name and datatype. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: - * The datatype is set by default to VARCHAR. + * + * <p><i><b>Note 2:</b> + * If omitted, the datatype is set by default to VARCHAR. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the column name is prefixed by its table name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the table name must be exactly the same - * as what the function {@link TAPTable#getRawName()} of the set table returns. + * The column name MUST NOT be prefixed by its table name. * </li> * <li> - * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} but will + * Double quotes may surround the column name. In such case, they + * indicate that the column name must be considered as case sensitive. + * If present, these double quotes will be removed but will * still appear in the result of {@link #getRawName()}. * </li> * </ul> - * - * @param columnName Name that this column MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * @param type Datatype of this column. <i>If NULL, VARCHAR will be the datatype of this column</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). - * + * + * @param columnName ADQL name of this column. + * @param type Datatype of this column. + * + * @throws NullPointerException If the given name is NULL or an empty string. + * * @see #setDatatype(DBType) */ - public TAPColumn(String columnName, DBType type) throws NullPointerException{ + public TAPColumn(String columnName, DBType type) throws NullPointerException { this(columnName); setDatatype(type); } /** - * <p>Build a VARCHAR {@link TAPColumn} instance with the given ADQL name and description.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a VARCHAR {@link TAPColumn} instance with the given ADQL name and + * description. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: - * The datatype is set by default to VARCHAR. + * + * <p><i><b>Note 2:</b> + * If omitted, the datatype is set by default to VARCHAR. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the column name is prefixed by its table name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the table name must be exactly the same - * as what the function {@link TAPTable#getRawName()} of the set table returns. + * The column name MUST NOT be prefixed by its table name. * </li> * <li> - * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} but will + * Double quotes may surround the column name. In such case, they + * indicate that the column name must be considered as case sensitive. + * If present, these double quotes will be removed but will * still appear in the result of {@link #getRawName()}. * </li> * </ul> - * - * @param columnName Name that this column MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * @param description Description of the column's content. <i>May be NULL</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). - */ - public TAPColumn(String columnName, String description) throws NullPointerException{ + * + * @param columnName ADQL name of this column. + * @param description Description of the column's content. + * <i>May be NULL</i> + * + * @throws NullPointerException If the given name is NULL or an empty string. + */ + public TAPColumn(String columnName, String description) throws NullPointerException { this(columnName, (DBType)null, description); } /** - * <p>Build a {@link TAPColumn} instance with the given ADQL name, datatype and description.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a {@link TAPColumn} instance with the given ADQL name, datatype and + * description. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: - * The datatype is set by calling the function {@link #setDatatype(DBType)} which does not do - * anything if the given datatype is NULL. + * + * <p><i><b>Note 2:</b> + * If omitted, the datatype is set by default to VARCHAR. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the column name is prefixed by its table name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the table name must be exactly the same - * as what the function {@link TAPTable#getRawName()} of the set table returns. + * The column name MUST NOT be prefixed by its table name. * </li> * <li> - * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} but will + * Double quotes may surround the column name. In such case, they + * indicate that the column name must be considered as case sensitive. + * If present, these double quotes will be removed but will * still appear in the result of {@link #getRawName()}. * </li> * </ul> - * - * @param columnName Name that this column MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * @param type Datatype of this column. <i>If NULL, VARCHAR will be the datatype of this column</i> - * @param description Description of the column's content. <i>May be NULL</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). - */ - public TAPColumn(String columnName, DBType type, String description) throws NullPointerException{ + * + * @param columnName ADQL name of this column. + * @param type Datatype of this column. + * @param description Description of the column's content. + * <i>May be NULL</i> + * + * @throws NullPointerException If the given name is NULL or an empty string. + */ + public TAPColumn(String columnName, DBType type, String description) throws NullPointerException { this(columnName, type); this.description = description; } /** - * <p>Build a VARCHAR {@link TAPColumn} instance with the given ADQL name, description and unit.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a VARCHAR {@link TAPColumn} instance with the given ADQL name, + * description and unit. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: - * The datatype is set by default to VARCHAR. + * + * <p><i><b>Note 2:</b> + * If omitted, the datatype is set by default to VARCHAR. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the column name is prefixed by its table name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the table name must be exactly the same - * as what the function {@link TAPTable#getRawName()} of the set table returns. + * The column name MUST NOT be prefixed by its table name. * </li> * <li> - * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} but will + * Double quotes may surround the column name. In such case, they + * indicate that the column name must be considered as case sensitive. + * If present, these double quotes will be removed but will * still appear in the result of {@link #getRawName()}. * </li> * </ul> - * - * @param columnName Name that this column MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * @param description Description of the column's content. <i>May be NULL</i> - * @param unit Unit of the column's values. <i>May be NULL</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). - */ - public TAPColumn(String columnName, String description, String unit) throws NullPointerException{ + * + * @param columnName ADQL name of this column. + * @param description Description of the column's content. + * <i>May be NULL</i> + * @param unit Unit of the column's values. + * <i>May be NULL</i> + * + * @throws NullPointerException If the given name is NULL or an empty string. + */ + public TAPColumn(String columnName, String description, String unit) throws NullPointerException { this(columnName, null, description, unit); } /** - * <p>Build a {@link TAPColumn} instance with the given ADQL name, type, description and unit.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a {@link TAPColumn} instance with the given ADQL name, type, description and unit. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: - * The datatype is set by calling the function {@link #setDatatype(DBType)} which does not do - * anything if the given datatype is NULL. + * + * <p><i><b>Note 2:</b> + * If omitted, the datatype is set by default to VARCHAR. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the column name is prefixed by its table name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the table name must be exactly the same - * as what the function {@link TAPTable#getRawName()} of the set table returns. + * The column name MUST NOT be prefixed by its table name. * </li> * <li> - * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} but will + * Double quotes may surround the column name. In such case, they + * indicate that the column name must be considered as case sensitive. + * If present, these double quotes will be removed but will * still appear in the result of {@link #getRawName()}. * </li> * </ul> - * - * @param columnName Name that this column MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * @param type Datatype of this column. <i>If NULL, VARCHAR will be the datatype of this column</i> - * @param description Description of the column's content. <i>May be NULL</i> - * @param unit Unit of the column's values. <i>May be NULL</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). - */ - public TAPColumn(String columnName, DBType type, String description, String unit) throws NullPointerException{ + * + * @param columnName ADQL name of this column. + * @param type Datatype of this column. + * @param description Description of the column's content. + * <i>May be NULL</i> + * @param unit Unit of the column's values. + * <i>May be NULL</i> + * + * @throws NullPointerException If the given name is NULL or an empty string. + */ + public TAPColumn(String columnName, DBType type, String description, String unit) throws NullPointerException { this(columnName, type, description); this.unit = unit; } /** - * <p>Build a VARCHAR {@link TAPColumn} instance with the given fields.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a VARCHAR {@link TAPColumn} instance with the given fields. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: - * The datatype is set by default to VARCHAR. + * + * <p><i><b>Note 2:</b> + * If omitted, the datatype is set by default to VARCHAR. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the column name is prefixed by its table name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the table name must be exactly the same - * as what the function {@link TAPTable#getRawName()} of the set table returns. + * The column name MUST NOT be prefixed by its table name. * </li> * <li> - * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} but will + * Double quotes may surround the column name. In such case, they + * indicate that the column name must be considered as case sensitive. + * If present, these double quotes will be removed but will * still appear in the result of {@link #getRawName()}. * </li> * </ul> - * - * @param columnName Name that this column MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * @param description Description of the column's content. <i>May be NULL</i> - * @param unit Unit of the column's values. <i>May be NULL</i> + * + * @param columnName ADQL name of this column. + * @param description Description of the column's content. + * <i>May be NULL</i> + * @param unit Unit of the column's values. + * <i>May be NULL</i> * @param ucd UCD describing the scientific content of this column. + * <i>May be NULL</i> * @param utype UType associating this column with a data-model. - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). + * <i>May be NULL</i> + * + * @throws NullPointerException If the given name is NULL or an empty string. */ - public TAPColumn(String columnName, String description, String unit, String ucd, String utype) throws NullPointerException{ + public TAPColumn(String columnName, String description, String unit, String ucd, String utype) throws NullPointerException { this(columnName, null, description, unit, ucd, utype); } /** - * <p>Build a {@link TAPColumn} instance with the given fields.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a {@link TAPColumn} instance with the given fields. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: - * The datatype is set by calling the function {@link #setDatatype(DBType)} which does not do - * anything if the given datatype is NULL. + * + * <p><i><b>Note 2:</b> + * If omitted, the datatype is set by default to VARCHAR. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the column name is prefixed by its table name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the table name must be exactly the same - * as what the function {@link TAPTable#getRawName()} of the set table returns. + * The column name MUST NOT be prefixed by its table name. * </li> * <li> - * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} but will + * Double quotes may surround the column name. In such case, they + * indicate that the column name must be considered as case sensitive. + * If present, these double quotes will be removed but will * still appear in the result of {@link #getRawName()}. * </li> * </ul> - * - * @param columnName Name that this column MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * @param type Datatype of this column. <i>If NULL, VARCHAR will be the datatype of this column</i> - * @param description Description of the column's content. <i>May be NULL</i> - * @param unit Unit of the column's values. <i>May be NULL</i> + * + * @param columnName ADQL name of this column. + * @param type Datatype of this column. + * @param description Description of the column's content. + * <i>May be NULL</i> + * @param unit Unit of the column's values. + * <i>May be NULL</i> * @param ucd UCD describing the scientific content of this column. + * <i>May be NULL</i> * @param utype UType associating this column with a data-model. - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). + * <i>May be NULL</i> + * + * @throws NullPointerException If the given name is NULL or an empty string. */ - public TAPColumn(String columnName, DBType type, String description, String unit, String ucd, String utype) throws NullPointerException{ + public TAPColumn(String columnName, DBType type, String description, String unit, String ucd, String utype) throws NullPointerException { this(columnName, type, description, unit); this.ucd = ucd; this.utype = utype; @@ -491,61 +536,55 @@ public class TAPColumn implements DBColumn { /** * Get the ADQL name (the name this column MUST have in ADQL queries). - * + * * @return Its ADQL name. * @see #getADQLName() * @deprecated Does not do anything special: just call {@link #getADQLName()}. */ @Deprecated - public final String getName(){ + public final String getName() { return getADQLName(); } @Override - public final String getADQLName(){ - if (simplificationNeeded){ - String tmp = adqlName; - // Remove the table prefix if any: - if (table != null){ - String tablePrefix = ((table instanceof TAPTable) ? ((TAPTable)table).getRawName() : table.getADQLName()) + "."; - if (tmp.startsWith(tablePrefix)) - tmp = tmp.substring(tablePrefix.length()).trim(); - } - // Remove the surrounding double-quotes if any: - if (tmp.matches("\"[^\"]*\"")) - tmp = tmp.substring(1, tmp.length() - 1); - // Finally, return the result: - return tmp; - }else - return adqlName; - } - - /** - * Get the full ADQL name of this table, as it has been provided at initialization. - * + public final String getADQLName() { + return adqlName; + } + + /** + * Get the ADQL name of this column, as it has been provided at + * initialization. + * * @return Get the original ADQL name. - * + * * @since 2.1 */ - public final String getRawName(){ - return adqlName; + public final String getRawName() { + return (columnCaseSensitive ? "\"" + adqlName.replaceAll("\"", "\"\"") + "\"" : adqlName); + } + + @Override + public final boolean isCaseSensitive() { + return columnCaseSensitive; } @Override - public final String getDBName(){ + public final String getDBName() { return (dbName == null) ? getADQLName() : dbName; } /** - * <p>Change the name that this column MUST have in the database (i.e. in SQL queries).</p> - * - * <p><i>Note: - * If the given value is NULL or an empty string, nothing is done ; the DB name keeps is former value. + * Change the name that this column MUST have in the database (i.e. in SQL + * queries). + * + * <p><i><b>Note:</b> + * If the given value is NULL or an empty string, nothing is done ; the DB + * name keeps is former value. * </i></p> - * + * * @param name The new database name of this column. */ - public final void setDBName(String name){ + public final void setDBName(String name) { name = (name != null) ? name.trim() : name; if (name != null && name.length() > 0) dbName = name; @@ -554,26 +593,27 @@ public class TAPColumn implements DBColumn { } @Override - public final DBTable getTable(){ + public final DBTable getTable() { return table; } /** - * <p>Set the table in which this column is.</p> - * + * Set the table in which this column is. + * * <p><i><b>Warning:</b> - * For consistency reasons, this function SHOULD be called only by the {@link TAPTable} - * that owns this column. + * For consistency reasons, this function SHOULD be called only by the + * {@link TAPTable} that owns this column. * </i></p> - * + * * <p><i><b>Important note:</b> - * If this column was already linked with another {@link TAPTable} object, the previous link is removed - * here, but also in the table (by calling {@link TAPTable#removeColumn(String)}). + * If this column was already linked with another {@link TAPTable} object, + * the previous link is removed here, but also in the table (by calling + * {@link TAPTable#removeColumn(String)}). * </i></p> - * + * * @param table The table that owns this column. */ - protected final void setTable(final DBTable table){ + protected final void setTable(final DBTable table) { if (this.table != null && this.table instanceof TAPTable && (table == null || !table.equals(this.table))) ((TAPTable)this.table).removeColumn(adqlName); this.table = table; @@ -581,96 +621,97 @@ public class TAPColumn implements DBColumn { /** * Get the description of this column. - * + * * @return Its description. <i>MAY be NULL</i> */ - public final String getDescription(){ + public final String getDescription() { return description; } /** * Set the description of this column. - * + * * @param description Its new description. <i>MAY be NULL</i> */ - public final void setDescription(String description){ + public final void setDescription(String description) { this.description = description; } /** * Get the unit of the column's values. - * + * * @return Its unit. <i>MAY be NULL</i> */ - public final String getUnit(){ + public final String getUnit() { return unit; } /** * Set the unit of the column's values. - * + * * @param unit Its new unit. <i>MAY be NULL</i> */ - public final void setUnit(String unit){ + public final void setUnit(String unit) { this.unit = unit; } /** * Get the UCD describing the scientific content of this column. - * + * * @return Its UCD. <i>MAY be NULL</i> */ - public final String getUcd(){ + public final String getUcd() { return ucd; } /** * Set the UCD describing the scientific content of this column. - * + * * @param ucd Its new UCD. <i>MAY be NULL</i> */ - public final void setUcd(String ucd){ + public final void setUcd(String ucd) { this.ucd = ucd; } /** * Get the UType associating this column with a data-model. - * + * * @return Its UType. <i>MAY be NULL</i> */ - public final String getUtype(){ + public final String getUtype() { return utype; } /** * Set the UType associating this column with a data-model. - * + * * @param utype Its new UType. <i>MAY be NULL</i> */ - public final void setUtype(String utype){ + public final void setUtype(String utype) { this.utype = utype; } /** * Get the type of the column's values. - * + * * @return Its datatype. <i>CAN'T be NULL</i> */ @Override - public final DBType getDatatype(){ + public final DBType getDatatype() { return datatype; } /** - * <p>Set the type of the column's values.</p> - * - * <p><i>Note: - * If the given type is NULL, an {@link DBDatatype#UNKNOWN UNKNOWN} type will be set instead. + * Set the type of the column's values. + * + * <p><i><b>Note:</b> + * If the given type is NULL, an {@link DBDatatype#UNKNOWN UNKNOWN} type + * will be set instead. * </i></p> - * + * * @param type Its new datatype. */ - public final void setDatatype(final DBType type){ + public final void setDatatype(final DBType type) { if (type != null) datatype = type; else @@ -679,299 +720,321 @@ public class TAPColumn implements DBColumn { /** * Tell whether this column is one of those returned by default. - * - * @return <i>true</i> if this column should be returned by default, <i>false</i> otherwise. + * + * @return <code>true</code> if this column should be returned by default, + * <code>false</code> otherwise. */ - public final boolean isPrincipal(){ + public final boolean isPrincipal() { return principal; } /** * Set whether this column should be one of those returned by default. - * - * @param principal <i>true</i> if this column should be returned by default, <i>false</i> otherwise. + * + * @param principal <code>true</code> if this column should be returned by + * default, + * <code>false</code> otherwise. */ - public final void setPrincipal(boolean principal){ + public final void setPrincipal(boolean principal) { this.principal = principal; } /** * Tell whether this column is indexed. - * - * @return <i>true</i> if this column is indexed, <i>false</i> otherwise. + * + * @return <code>true</code> if this column is indexed, + * <code>false</code> otherwise. */ - public final boolean isIndexed(){ + public final boolean isIndexed() { return indexed; } /** * Set whether this column is indexed or not. - * - * @param indexed <i>true</i> if this column is indexed, <i>false</i> otherwise. + * + * @param indexed <code>true</code> if this column is indexed, + * <code>false</code> otherwise. */ - public final void setIndexed(boolean indexed){ + public final void setIndexed(boolean indexed) { this.indexed = indexed; } /** * Tell whether this column is nullable. - * - * @return <i>true</i> if this column is nullable, <i>false</i> otherwise. - * + * + * @return <code>true</code> if this column is nullable, + * <code>false</code> otherwise. + * * @since 2.0 */ - public final boolean isNullable(){ + public final boolean isNullable() { return nullable; } /** * Set whether this column is nullable or not. - * - * @param nullable <i>true</i> if this column is nullable, <i>false</i> otherwise. - * + * + * @param nullable <code>true</code> if this column is nullable, + * <code>false</code> otherwise. + * * @since 2.0 */ - public final void setNullable(boolean nullable){ + public final void setNullable(boolean nullable) { this.nullable = nullable; } /** * Tell whether this column is defined by a standard. - * - * @return <i>true</i> if this column is defined by a standard, <i>false</i> otherwise. + * + * @return <code>true</code> if this column is defined by a standard, + * <code>false</code> otherwise. */ - public final boolean isStd(){ + public final boolean isStd() { return std; } /** * Set whether this column is defined by a standard. - * - * @param std <i>true</i> if this column is defined by a standard, <i>false</i> otherwise. + * + * @param std <code>true</code> if this column is defined by a standard, + * <code>false</code> otherwise. */ - public final void setStd(boolean std){ + public final void setStd(boolean std) { this.std = std; } /** * Get the ordering index of this column inside its table. - * + * * @return Its ordering index. - * + * * @since 2.1 */ - public final int getIndex(){ + public final int getIndex() { return index; } /** * Set the ordering index of this column inside its table. - * + * * @param columnIndex Its new ordering index. - * + * * @since 2.1 */ - public final void setIndex(int columnIndex){ + public final void setIndex(int columnIndex) { this.index = columnIndex; } /** * Get the used coordinate system. - * + * * @return Its coordinate system. - * + * * @since 2.1 */ - public final TAPCoosys getCoosys(){ + public final TAPCoosys getCoosys() { return coosys; } /** * Set the the coordinate system to use. - * + * * @param newCoosys Its new coordinate system. - * + * * @since 2.1 */ - public final void setCoosys(final TAPCoosys newCoosys){ + public final void setCoosys(final TAPCoosys newCoosys) { this.coosys = newCoosys; } /** - * <p>Get the other (piece of) information associated with this column.</p> - * - * <p><i>Note: - * By default, NULL is returned, but it may be any kind of value ({@link Integer}, - * {@link String}, {@link Map}, {@link List}, ...). + * Get the other (piece of) information associated with this column. + * + * <p><i><b>Note:</b> + * By default, NULL is returned, but it may be any kind of value + * ({@link Integer}, {@link String}, {@link Map}, {@link List}, ...). * </i></p> - * + * * @return The other (piece of) information. <i>MAY be NULL</i> */ - public Object getOtherData(){ + public Object getOtherData() { return otherData; } /** * Set the other (piece of) information associated with this column. - * + * * @param data Another information about this column. <i>MAY be NULL</i> */ - public void setOtherData(Object data){ + public void setOtherData(Object data) { otherData = data; } /** - * <p>Let add a foreign key in which this column is a source (= which is targeting another column).</p> - * - * <p><i>Note: + * Let add a foreign key in which this column is a source (= which is + * targeting another column). + * + * <p><i><b>Note:</b> * Nothing is done if the given value is NULL. * </i></p> - * + * * <p><i><b>Warning:</b> - * For consistency reasons, this function SHOULD be called only by the {@link TAPTable} - * that owns this column or that is part of the foreign key. + * For consistency reasons, this function SHOULD be called only by the + * {@link TAPTable} that owns this column or that is part of the foreign + * key. * </i></p> - * + * * @param key A foreign key. */ - protected void addTarget(TAPForeignKey key){ + protected void addTarget(TAPForeignKey key) { if (key != null) lstTargets.add(key); } /** * Get the number of times this column is targeting another column. - * + * * @return How many this column is source in a foreign key. */ - public int getNbTargets(){ + public int getNbTargets() { return lstTargets.size(); } /** - * Get the list of foreign keys in which this column is a source (= is targeting another column). - * + * Get the list of foreign keys in which this column is a source + * (= is targeting another column). + * * @return List of foreign keys in which this column is a source. */ - public Iterator<TAPForeignKey> getTargets(){ + public Iterator<TAPForeignKey> getTargets() { return lstTargets.iterator(); } /** - * <p>Remove the fact that this column is a source (= is targeting another column) - * in the given foreign key.</p> - * - * <p><i>Note: + * Remove the fact that this column is a source (= is targeting another + * column) in the given foreign key. + * + * <p><i><b>Note:</b> * Nothing is done if the given value is NULL. * </i></p> - * + * * <p><i><b>Warning:</b> - * For consistency reasons, this function SHOULD be called only by the {@link TAPTable} - * that owns this column or that is part of the foreign key. + * For consistency reasons, this function SHOULD be called only by the + * {@link TAPTable} that owns this column or that is part of the foreign + * key. * </i></p> - * - * @param key Foreign key in which this column was targeting another column. + * + * @param key Foreign key in which this column was targeting another + * column. */ - protected void removeTarget(TAPForeignKey key){ + protected void removeTarget(TAPForeignKey key) { if (key != null) lstTargets.remove(key); } /** - * <p>Remove the fact that this column is a source (= is targeting another column) - * in any foreign key in which it was.</p> - * + * Remove the fact that this column is a source (= is targeting another + * column) in any foreign key in which it was. + * * <p><i><b>Warning:</b> - * For consistency reasons, this function SHOULD be called only by the {@link TAPTable} - * that owns this column or that is part of the foreign key. + * For consistency reasons, this function SHOULD be called only by the + * {@link TAPTable} that owns this column or that is part of the foreign + * key. * </i></p> */ - protected void removeAllTargets(){ + protected void removeAllTargets() { lstTargets.clear(); } /** - * <p>Let add a foreign key in which this column is a target (= which is targeted by another column).</p> - * - * <p><i>Note: + * Let add a foreign key in which this column is a target (= which is + * targeted by another column). + * + * <p><i><b>Note:</b> * Nothing is done if the given value is NULL. * </i></p> - * + * * <p><i><b>Warning:</b> - * For consistency reasons, this function SHOULD be called only by the {@link TAPTable} - * that owns this column or that is part of the foreign key. + * For consistency reasons, this function SHOULD be called only by the + * {@link TAPTable} that owns this column or that is part of the foreign + * key. * </i></p> - * + * * @param key A foreign key. */ - protected void addSource(TAPForeignKey key){ + protected void addSource(TAPForeignKey key) { if (key != null) lstSources.add(key); } /** * Get the number of times this column is targeted by another column. - * + * * @return How many this column is target in a foreign key. */ - public int getNbSources(){ + public int getNbSources() { return lstSources.size(); } /** - * Get the list of foreign keys in which this column is a target (= is targeted another column). - * + * Get the list of foreign keys in which this column is a target (= is + * targeted another column). + * * @return List of foreign keys in which this column is a target. */ - public Iterator<TAPForeignKey> getSources(){ + public Iterator<TAPForeignKey> getSources() { return lstSources.iterator(); } /** - * <p>Remove the fact that this column is a target (= is targeted by another column) - * in the given foreign key.</p> - * - * <p><i>Note: + * Remove the fact that this column is a target (= is targeted by another + * column) in the given foreign key. + * + * <p><i><b>Note:</b> * Nothing is done if the given value is NULL. * </i></p> - * + * * <p><i><b>Warning:</b> - * For consistency reasons, this function SHOULD be called only by the {@link TAPTable} - * that owns this column or that is part of the foreign key. + * For consistency reasons, this function SHOULD be called only by the + * {@link TAPTable} that owns this column or that is part of the foreign + * key. * </i></p> - * - * @param key Foreign key in which this column was targeted by another column. + * + * @param key Foreign key in which this column was targeted by another + * column. */ - protected void removeSource(TAPForeignKey key){ + protected void removeSource(TAPForeignKey key) { lstSources.remove(key); } /** - * <p>Remove the fact that this column is a target (= is targeted by another column) - * in any foreign key in which it was.</p> - * + * Remove the fact that this column is a target (= is targeted by + * another column) in any foreign key in which it was. + * * <p><i><b>Warning:</b> - * For consistency reasons, this function SHOULD be called only by the {@link TAPTable} - * that owns this column or that is part of the foreign key. + * For consistency reasons, this function SHOULD be called only by the + * {@link TAPTable} that owns this column or that is part of the foreign + * key. * </i></p> */ - protected void removeAllSources(){ + protected void removeAllSources() { lstSources.clear(); } /** * <p><i><b>Warning:</b> * Since the type of the other data is not known, the copy of its value - * can not be done properly. So, this column and its copy will share the same other data object. - * If it is also needed to make a deep copy of this other data object, this function MUST be - * overridden. + * can not be done properly. So, this column and its copy will share the + * same other data object. If it is also needed to make a deep copy of this + * other data object, this function MUST be overridden. * </i></b> - * + * * @see adql.db.DBColumn#copy(java.lang.String, java.lang.String, adql.db.DBTable) */ @Override - public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable){ + public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable) { TAPColumn copy = new TAPColumn((adqlName == null) ? this.adqlName : adqlName, datatype, description, unit, ucd, utype); + copy.columnCaseSensitive = this.columnCaseSensitive; copy.setDBName((dbName == null) ? this.getDBName() : dbName); copy.setTable(dbTable); @@ -984,19 +1047,20 @@ public class TAPColumn implements DBColumn { } /** - * <p>Provide a deep copy (included the other data) of this column.</p> - * + * Provide a deep copy (included the other data) of this column. + * * <p><i><b>Warning:</b> * Since the type of the other data is not known, the copy of its value - * can not be done properly. So, this column and its copy will share the same other data object. - * If it is also needed to make a deep copy of this other data object, this function MUST be - * overridden. + * can not be done properly. So, this column and its copy will share the + * same other data object. If it is also needed to make a deep copy of this + * other data object, this function MUST be overridden. * </i></b> - * + * * @return The deep copy of this column. */ - public DBColumn copy(){ + public DBColumn copy() { TAPColumn copy = new TAPColumn(adqlName, datatype, description, unit, ucd, utype); + copy.columnCaseSensitive = this.columnCaseSensitive; copy.setDBName(dbName); copy.setTable(table); copy.setIndexed(indexed); @@ -1007,17 +1071,17 @@ public class TAPColumn implements DBColumn { } @Override - public boolean equals(Object obj){ + public boolean equals(Object obj) { if (!(obj instanceof TAPColumn)) return false; TAPColumn col = (TAPColumn)obj; - return col.getTable().equals(table) && col.getADQLName().equals(adqlName); + return col.getTable().equals(table) && col.getADQLName().equals(adqlName) && col.columnCaseSensitive == this.columnCaseSensitive; } @Override - public String toString(){ - return ((table != null) ? (((table.getADQLSchemaName() != null) ? table.getADQLSchemaName() : "") + table.getADQLName() + ".") : "") + getADQLName(); + public String toString() { + return (table != null ? table.toString() : "") + getRawName(); } } diff --git a/src/tap/metadata/TAPSchema.java b/src/tap/metadata/TAPSchema.java index e22514159eb80ca97b7a314499dc18a113588440..f1e6e0c3c33f721f6096be44d6ea392f511f4430 100644 --- a/src/tap/metadata/TAPSchema.java +++ b/src/tap/metadata/TAPSchema.java @@ -2,21 +2,21 @@ package tap.metadata; /* * This file is part of TAPLibrary. - * + * * TAPLibrary is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * TAPLibrary is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * + * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS) * Astronomisches Rechen Institut (ARI) */ @@ -25,176 +25,200 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; +import adql.db.DefaultDBTable; import tap.metadata.TAPTable.TableType; /** - * <p>Represent a schema as described by the IVOA standard in the TAP protocol definition.</p> - * + * Represent a schema as described by the IVOA standard in the TAP protocol + * definition. + * * <p> - * This object representation has exactly the same fields as the column of the table TAP_SCHEMA.schemas. - * But it also provides a way to add other data. For instance, if information not listed in the standard - * may be stored here, they can be using the function {@link #setOtherData(Object)}. This object can be - * a single value (integer, string, ...), but also a {@link Map}, {@link List}, etc... + * This object representation has exactly the same fields as the column of the + * table TAP_SCHEMA.schemas. But it also provides a way to add other data. For + * instance, if information not listed in the standard may be stored here, they + * can be using the function {@link #setOtherData(Object)}. This object can be + * a single value (integer, string, ...), but also a {@link Map}, {@link List}, + * etc... * </p> - * - * <p><i>Note: - * On the contrary to {@link TAPColumn} and {@link TAPTable}, a {@link TAPSchema} object MAY have no DB name. - * But by default, at the creation the DB name is the simplified ADQL name (i.e. as it is returned by {@link #getADQLName()}). - * Once created, it is possible to set the DB name with {@link #setDBName(String)}. This DB name MAY be qualified, - * BUT MUST BE without double quotes. + * + * <p><i><b>Note:</b> + * On the contrary to {@link TAPColumn} and {@link TAPTable}, a + * {@link TAPSchema} object MAY have no DB name. But by default, at the + * creation the DB name is the simplified ADQL name (i.e. as it is returned by + * {@link #getADQLName()}). Once created, it is possible to set the DB name + * with {@link #setDBName(String)}. This DB name MAY be qualified, BUT MUST BE + * without double quotes. * </i></p> - * + * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (07/2016) + * @version 2.4 (09/2019) */ public class TAPSchema implements Iterable<TAPTable> { /** Name that this schema MUST have in ADQL queries. */ private final String adqlName; - /** Indicates whether the given ADQL name must be simplified by {@link #getADQLName()}. - * <p>Here, "simplification" means removing the surrounding double quotes. - * Since there is no information on the catalog name, none can be detected and so removed.</p> - * @since 2.1 */ - private final boolean simplificationNeeded; + /** Indicate whether the ADQL schema name must be considered as case + * sensitive. In such case, it should be provided between double quotes in + * the constructor parameter. + * @since 2.4 */ + private boolean schemaCaseSensitive; /** Name that this schema have in the database. - * <i>Note: NULL by default. When NULL, {@link #getDBName()} returns exactly what {@link #getADQLName()} returns.</i> */ + * <p><i><b>Note:</b> + * NULL by default. When NULL, {@link #getDBName()} returns exactly what + * {@link #getADQLName()} returns. + * </i></p> */ private String dbName = null; /** Descriptive, human-interpretable name of the schema. - * <i>Note: Standard TAP schema field ; MAY be NULL.</i> + * <p><i><b>Note:</b> + * Standard TAP schema field ; MAY be NULL. + * </i></p> * @since 2.0 */ private String title = null; /** Description of this schema. - * <i>Note: Standard TAP schema field ; MAY be NULL.</i> */ + * <p><i><b>Note:</b> + * Standard TAP schema field ; MAY be NULL. + * </i></p> */ private String description = null; /** UType describing the scientific content of this schema. - * <i>Note: Standard TAP schema field ; MAY be NULL.</i> */ + * <p><i><b>Note:</b> + * Standard TAP schema field ; MAY be NULL. + * </i></p> */ private String utype = null; /** Ordering index of this schema inside its whole schema set. - * <i>Note: SHOULD be a standard TAP schema field in TAP 1.1, as table_index and column_index are resp. in TAP_SCHEMA.tables and TAP_SCHEMA.columns.</i> + * <p><i><b>Note:</b> + * SHOULD be a standard TAP schema field in TAP 1.1, as table_index and + * column_index are resp. in TAP_SCHEMA.tables and TAP_SCHEMA.columns. + * </i></p> * @since 2.1 */ private int index = -1; /** Let add some information in addition of the ones of the TAP protocol. - * <i>Note: This object can be anything: an {@link Integer}, a {@link String}, a {@link Map}, a {@link List}, ... - * Its content is totally free and never used or checked.</i> */ + * <p><i><b>Note:</b> + * This object can be anything: an {@link Integer}, a {@link String}, a + * {@link Map}, a {@link List}, ... Its content is totally free and never + * used or checked. + * </i></p> */ protected Object otherData = null; /** List all tables contained inside this schema. */ - protected final Map<String,TAPTable> tables; + protected final Map<String, TAPTable> tables; /** - * <p>Build a {@link TAPSchema} instance with the given ADQL name.</p> - * - * <p><i>Note: - * The DB name is set by default to the ADQL name (as returned by {@link #getADQLName()}). - * To set the DB name, you MUST call then {@link #setDBName(String)}. + * Build a {@link TAPSchema} instance with the given ADQL name. + * + * <p><i><b>Note:</b> + * The DB name is set by default to the ADQL name (as returned by + * {@link #getADQLName()}). To set the DB name, you MUST call then + * {@link #setDBName(String)}. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * No catalog prefix is supported. <i>For instance: <code>myCatalog.mySchema</code> will be considered - * as the schema name instead of <code>mySchema</code>.</i> + * No catalog prefix is supported. <i>For instance: + * <code>myCatalog.mySchema</code> will be considered as the schema + * name instead of <code>mySchema</code>.</i> * </li> * <li> - * Double quotes may surround the ADQL name. They will be removed by {@link #getADQLName()} but will - * still appear in the result of {@link #getRawName()}. + * Double quotes may surround the ADQL name. They will be removed by + * {@link #getADQLName()} but will still appear in the result of + * {@link #getRawName()}. * </li> * </ul> - * - * @param schemaName Name that this schema MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). + * + * @param schemaName ADQL name of this schema. + * + * @throws NullPointerException If the given name is NULL or an empty string. */ - public TAPSchema(String schemaName) throws NullPointerException{ + public TAPSchema(String schemaName) throws NullPointerException { if (schemaName == null) throw new NullPointerException("Missing schema name!"); - adqlName = schemaName.trim(); - simplificationNeeded = adqlName.matches("\"[^\"]*\""); + schemaName = schemaName.trim(); + schemaCaseSensitive = DefaultDBTable.isDelimited(schemaName); + adqlName = (schemaCaseSensitive ? schemaName.substring(1, schemaName.length() - 1).replaceAll("\"\"", "\"") : schemaName); - if (getADQLName().length() == 0) + if (getADQLName().trim().length() == 0) throw new NullPointerException("Missing schema name!"); dbName = getADQLName(); - tables = new LinkedHashMap<String,TAPTable>(); + tables = new LinkedHashMap<String, TAPTable>(); } /** - * <p>Build a {@link TAPSchema} instance with the given ADQL name and description.</p> - * - * <p><i>Note: - * The DB name is set by default to the ADQL name (as returned by {@link #getADQLName()}). - * To set the DB name, you MUST call then {@link #setDBName(String)}. + * Build a {@link TAPSchema} instance with the given ADQL name and + * description. + * + * <p><i><b>Note:</b> + * The DB name is set by default to the ADQL name (as returned by + * {@link #getADQLName()}). To set the DB name, you MUST call then + * {@link #setDBName(String)}. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * No catalog prefix is supported. <i>For instance: <code>myCatalog.mySchema</code> will be considered - * as the schema name instead of <code>mySchema</code>.</i> + * No catalog prefix is supported. <i>For instance: + * <code>myCatalog.mySchema</code> will be considered as the schema + * name instead of <code>mySchema</code>.</i> * </li> * <li> - * Double quotes may surround the ADQL name. They will be removed by {@link #getADQLName()} but will - * still appear in the result of {@link #getRawName()}. + * Double quotes may surround the ADQL name. They will be removed by + * {@link #getADQLName()} but will still appear in the result of + * {@link #getRawName()}. * </li> * </ul> - * - * @param schemaName Name that this schema MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> + * + * @param schemaName ADQL name of this schema. * @param description Description of this schema. <i>MAY be NULL</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). + * + * @throws NullPointerException If the given name is NULL or an empty string. */ - public TAPSchema(String schemaName, String description) throws NullPointerException{ + public TAPSchema(final String schemaName, final String description) throws NullPointerException { this(schemaName, description, null); } /** - * <p>Build a {@link TAPSchema} instance with the given ADQL name, description and UType.</p> - * - * <p><i>Note: + * Build a {@link TAPSchema} instance with the given ADQL name, + * description and UType. + * + * <p><i><b>Note:</b> * The DB name is set by default to the ADQL name (as returned by {@link #getADQLName()}). * To set the DB name, you MUST call then {@link #setDBName(String)}. * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * No catalog prefix is supported. <i>For instance: <code>myCatalog.mySchema</code> will be considered - * as the schema name instead of <code>mySchema</code>.</i> + * No catalog prefix is supported. <i>For instance: + * <code>myCatalog.mySchema</code> will be considered as the schema + * name instead of <code>mySchema</code>.</i> * </li> * <li> - * Double quotes may surround the ADQL name. They will be removed by {@link #getADQLName()} but will - * still appear in the result of {@link #getRawName()}. + * Double quotes may surround the ADQL name. They will be removed by + * {@link #getADQLName()} but will still appear in the result of + * {@link #getRawName()}. * </li> * </ul> - * - * @param schemaName Name that this schema MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> + * + * @param schemaName ADQL name of this schema. * @param description Description of this schema. <i>MAY be NULL</i> - * @param utype UType associating this schema with a data-model. <i>MAY be NULL</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). + * @param utype UType associating this schema with a data-model. + * <i>MAY be NULL</i> + * + * @throws NullPointerException If the given name is NULL or an empty string. */ - public TAPSchema(String schemaName, String description, String utype) throws NullPointerException{ + public TAPSchema(final String schemaName, final String description, final String utype) throws NullPointerException { this(schemaName); this.description = description; this.utype = utype; @@ -202,191 +226,214 @@ public class TAPSchema implements Iterable<TAPTable> { /** * Get the ADQL name (the name this schema MUST have in ADQL queries). - * + * * @return Its ADQL name. * @see #getADQLName() * @deprecated Does not do anything special: just call {@link #getADQLName()}. */ @Deprecated - public final String getName(){ + public final String getName() { return getADQLName(); } /** - * Get the name of this schema. - * - * <p><i>Note: - * If a simplification is needed, the double quotes surrounding the ADQL name, will be removed. - * </i></p> - * - * @return Its ADQL name. <i>CAN'T be NULL</i> + * Get the (non delimited) ADQL name of this schema. + * + * @return Its ADQL name. */ - public final String getADQLName(){ - return simplificationNeeded ? adqlName.substring(1, adqlName.length() - 1) : adqlName; + public final String getADQLName() { + return adqlName; } /** - * Get the full ADQL name of this schema, as it has been provided at initialization. - * + * Get the full ADQL name of this schema, as it has been provided at + * initialization (i.e. delimited if {@link #isCaseSensitive() case sensitive}). + * * @return Get the original ADQL name. - * + * * @since 2.1 */ - public final String getRawName(){ - return adqlName; + public final String getRawName() { + return toString(); + } + + /** + * Tell whether the ADQL name of this schema should be considered as case + * sensitive or not. + * + * @return <code>true</code> if the ADQL name is case sensitive, + * <code>false</code> otherwise. + */ + public final boolean isCaseSensitive() { + return schemaCaseSensitive; + } + + /** + * Let specify whether the ADQL name of this schema should be considered as + * case sensitive or not. + * + * @param sensitive <code>true</code> to make the ADQL name case sensitive, + * <code>false</code> otherwise. + */ + public final void setCaseSensitive(final boolean sensitive) { + schemaCaseSensitive = sensitive; } /** * Get the name this schema MUST have in the database. - * + * * @return Its DB name. <i>MAY be NULL</i> */ - public final String getDBName(){ + public final String getDBName() { return dbName; } /** * Set the name this schema MUST have in the database. - * - * <p>Notes:</p> + * + * <i> + * <p><b>Notes:</b></p> * <ul> - * <li>The given name may be NULL. In such case {@link #getDBName()} will then return NULL.</li> + * <li>The given name may be NULL. In such case {@link #getDBName()} will + * then return NULL.</li> * <li>It may be prefixed by a catalog name.</li> * <li> - * It MUST be NON delimited/double-quoted. Otherwise an SQL error will be raised when - * querying any item of this schema because the library double-quotes systematically the - * DB name of schemas, tables and columns. + * It MUST be NON delimited/double-quoted. Otherwise an SQL error will + * be raised when querying any item of this schema because the library + * double-quotes systematically the DB name of schemas, tables and + * columns. * </li> * </ul> - * + * </i> + * * @param name Its new DB name. <i>MAY be NULL</i> */ - public final void setDBName(String name){ + public final void setDBName(String name) { name = (name != null) ? name.trim() : name; dbName = name; } /** * Get the title of this schema. - * + * * @return Its title. <i>MAY be NULL</i> - * + * * @since 2.0 */ - public final String getTitle(){ + public final String getTitle() { return title; } /** * Set the title of this schema. - * + * * @param title Its new title. <i>MAY be NULL</i> - * + * * @since 2.0 */ - public final void setTitle(final String title){ + public final void setTitle(final String title) { this.title = title; } /** * Get the description of this schema. - * + * * @return Its description. <i>MAY be NULL</i> */ - public final String getDescription(){ + public final String getDescription() { return description; } /** * Set the description of this schema. - * + * * @param description Its new description. <i>MAY be NULL</i> */ - public final void setDescription(String description){ + public final void setDescription(String description) { this.description = description; } /** * Get the UType associating this schema with a data-model. - * + * * @return Its UType. <i>MAY be NULL</i> */ - public final String getUtype(){ + public final String getUtype() { return utype; } /** * Set the UType associating this schema with a data-model. - * + * * @param utype Its new UType. <i>MAY be NULL</i> */ - public final void setUtype(String utype){ + public final void setUtype(String utype) { this.utype = utype; } /** * Get the ordering index of this schema inside its whole schema set. - * + * * @return Its ordering index. - * + * * @since 2.1 */ - public final int getIndex(){ + public final int getIndex() { return index; } /** * Set the ordering index of this schema inside its whole schema set. - * + * * @param schemaIndex Its new ordering index. - * + * * @since 2.1 */ - public final void setIndex(int schemaIndex){ + public final void setIndex(int schemaIndex) { this.index = schemaIndex; } /** * <p>Get the other (piece of) information associated with this schema.</p> - * + * * <p><i>Note: * By default, NULL is returned, but it may be any kind of value ({@link Integer}, * {@link String}, {@link Map}, {@link List}, ...). * </i></p> - * + * * @return The other (piece of) information. <i>MAY be NULL</i> */ - public Object getOtherData(){ + public Object getOtherData() { return otherData; } /** * Set the other (piece of) information associated with this schema. - * + * * @param data Another information about this schema. <i>MAY be NULL</i> */ - public void setOtherData(Object data){ + public void setOtherData(Object data) { otherData = data; } /** * <p>Add the given table inside this schema.</p> - * + * * <p><i>Note: * If the given table is NULL, nothing will be done. * </i></p> - * + * * <p><i><b>Important note:</b> * By adding the given table inside this schema, it * will be linked with this schema using {@link TAPTable#setSchema(TAPSchema)}. * In this function, if the table was already linked with another {@link TAPSchema}, * the former link is removed using {@link TAPSchema#removeTable(String)}. * </i></p> - * + * * @param newTable Table to add inside this schema. */ - public final void addTable(TAPTable newTable){ - if (newTable != null && newTable.getADQLName() != null){ + public final void addTable(TAPTable newTable) { + if (newTable != null && newTable.getADQLName() != null) { newTable.setSchema(this); tables.put(newTable.getADQLName(), newTable); } @@ -395,20 +442,20 @@ public class TAPSchema implements Iterable<TAPTable> { /** * <p>Build a {@link TAPTable} object whose the ADQL and DB name will the given one. * Then, add this table inside this schema.</p> - * + * * <p><i>Note: * The built {@link TAPTable} object is returned, so that being modified afterwards if needed. * </i></p> - * + * * @param tableName ADQL name (and indirectly also the DB name) of the table to create and add. - * + * * @return The created and added {@link TAPTable} object, * or NULL if the given name is NULL or an empty string. - * + * * @see TAPTable#TAPTable(String) * @see #addTable(TAPTable) */ - public TAPTable addTable(String tableName){ + public TAPTable addTable(String tableName) { if (tableName == null) return null; @@ -420,23 +467,23 @@ public class TAPSchema implements Iterable<TAPTable> { /** * <p>Build a {@link TAPTable} object whose the ADQL and DB name will the given one. * Then, add this table inside this schema.</p> - * + * * <p><i>Note: * The built {@link TAPTable} object is returned, so that being modified afterwards if needed. * </i></p> - * + * * @param tableName ADQL name (and indirectly also the DB name) of the table to create and add. * @param tableType Type of the new table. <i>If NULL, "table" will be the type of the created table.</i> * @param description Description of the new table. <i>MAY be NULL</i> * @param utype UType associating the new column with a data-model. <i>MAY be NULL</i> - * + * * @return The created and added {@link TAPTable} object, * or NULL if the given name is NULL or an empty string. - * + * * @see TAPTable#TAPTable(String, TableType, String, String) * @see #addTable(TAPTable) */ - public TAPTable addTable(String tableName, TableType tableType, String description, String utype){ + public TAPTable addTable(String tableName, TableType tableType, String description, String utype) { if (tableName == null) return null; @@ -448,16 +495,16 @@ public class TAPSchema implements Iterable<TAPTable> { /** * <p>Tell whether this schema contains a table having the given ADQL name.</p> - * + * * <p><i><b>Important note:</b> * This function is case sensitive! * </i></p> - * + * * @param tableName Name of the table whose the existence in this schema must be checked. - * + * * @return <i>true</i> if a table with the given ADQL name exists, <i>false</i> otherwise. */ - public final boolean hasTable(String tableName){ + public final boolean hasTable(String tableName) { if (tableName == null) return false; else @@ -466,17 +513,17 @@ public class TAPSchema implements Iterable<TAPTable> { /** * <p>Search for a table having the given ADQL name.</p> - * + * * <p><i><b>Important note:</b> * This function is case sensitive! * </i></p> - * + * * @param tableName ADQL name of the table to search. - * + * * @return The table having the given ADQL name, * or NULL if no such table can be found. */ - public final TAPTable getTable(String tableName){ + public final TAPTable getTable(String tableName) { if (tableName == null) return null; else @@ -485,47 +532,47 @@ public class TAPSchema implements Iterable<TAPTable> { /** * Get the number of all tables contained inside this schema. - * + * * @return Number of its tables. */ - public final int getNbTables(){ + public final int getNbTables() { return tables.size(); } /** * Tell whether this schema contains no table. - * + * * @return <i>true</i> if this schema contains no table, * <i>false</i> if it has at least one table. */ - public final boolean isEmpty(){ + public final boolean isEmpty() { return tables.isEmpty(); } /** * <p>Remove the table having the given ADQL name.</p> - * + * * <p><i><b>Important note:</b> * This function is case sensitive! * </i></p> - * + * * <p><i>Note: * If the specified table is removed, its schema link is also deleted. * </i></p> - * + * * <p><i><b>WARNING:</b> * If the goal of this function's call is to delete definitely the specified table * from the metadata, you SHOULD also call {@link TAPTable#removeAllForeignKeys()}. * Indeed, foreign keys of the table would still link the removed table with other tables * AND columns of the whole metadata set. * </i></p> - * + * * @param tableName ADQL name of the table to remove from this schema. - * + * * @return The removed table, * or NULL if no table with the given ADQL name can be found. */ - public final TAPTable removeTable(String tableName){ + public final TAPTable removeTable(String tableName) { if (tableName == null) return null; @@ -537,11 +584,11 @@ public class TAPSchema implements Iterable<TAPTable> { /** * <p>Remove all the tables contained inside this schema.</p> - * + * * <p><i>Note: * When a table is removed, its schema link is also deleted. * </i></p> - * + * * <p><b>CAUTION: * If the goal of this function's call is to delete definitely all the tables of this schema * from the metadata, you SHOULD also call {@link TAPTable#removeAllForeignKeys()} @@ -550,23 +597,23 @@ public class TAPSchema implements Iterable<TAPTable> { * AND columns of the whole metadata set. * </b></p> */ - public final void removeAllTables(){ - Iterator<Map.Entry<String,TAPTable>> it = tables.entrySet().iterator(); - while(it.hasNext()){ - Map.Entry<String,TAPTable> entry = it.next(); + public final void removeAllTables() { + Iterator<Map.Entry<String, TAPTable>> it = tables.entrySet().iterator(); + while(it.hasNext()) { + Map.Entry<String, TAPTable> entry = it.next(); it.remove(); entry.getValue().setSchema(null); } } @Override - public Iterator<TAPTable> iterator(){ + public Iterator<TAPTable> iterator() { return tables.values().iterator(); } @Override - public String toString(){ - return getADQLName(); + public String toString() { + return (schemaCaseSensitive ? "\"" + adqlName.replaceAll("\"", "\"\"") + "\"" : adqlName); } } diff --git a/src/tap/metadata/TAPTable.java b/src/tap/metadata/TAPTable.java index 02a24027388125c2bfda39483f4d0be583f1c79e..312905ae4da8a0699b6c05e1b95d3a91ee87f6bc 100644 --- a/src/tap/metadata/TAPTable.java +++ b/src/tap/metadata/TAPTable.java @@ -2,21 +2,21 @@ package tap.metadata; /* * This file is part of TAPLibrary. - * + * * TAPLibrary is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * TAPLibrary is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * + * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS) * Astronomisches Rechen Institut (ARI) */ @@ -30,352 +30,385 @@ import java.util.Map; import adql.db.DBColumn; import adql.db.DBTable; import adql.db.DBType; +import adql.db.DefaultDBTable; import tap.TAPException; /** - * <p>Represent a table as described by the IVOA standard in the TAP protocol definition.</p> - * + * Represent a table as described by the IVOA standard in the TAP protocol + * definition. + * * <p> - * This object representation has exactly the same fields as the column of the table TAP_SCHEMA.tables. - * But it also provides a way to add other data. For instance, if information not listed in the standard - * may be stored here, they can be using the function {@link #setOtherData(Object)}. This object can be - * a single value (integer, string, ...), but also a {@link Map}, {@link List}, etc... + * This object representation has exactly the same fields as the column of the + * table TAP_SCHEMA.tables. But it also provides a way to add other data. For + * instance, if information not listed in the standard may be stored here, they + * can be using the function {@link #setOtherData(Object)}. This object can be + * a single value (integer, string, ...), but also a {@link Map}, {@link List}, + * etc... * </p> - * - * <p><i><b>Important note:</b> - * A {@link TAPTable} object MUST always have a DB name. That's why, {@link #getDBName()} returns - * what {@link #getADQLName()} returns when no DB name is set. After creation, it is possible to set - * the DB name with {@link #setDBName(String)}. - * <br/> - * This DB name MUST be UNqualified and without double quotes. If a NULL or empty value is provided, - * {@link #getDBName()} returns what {@link #getADQLName()} returns. - * </i></p> - * + * + * <i> + * <p><b>Important note:</b> + * A {@link TAPTable} object MUST always have a DB name. That's why, + * {@link #getDBName()} returns what {@link #getADQLName()} returns when no DB + * name is set. After creation, it is possible to set the DB name with + * {@link #setDBName(String)}. + * </p> + * <p> + * This DB name MUST be UNqualified and without double quotes. If a NULL or + * empty value is provided, {@link #getDBName()} returns what + * {@link #getADQLName()} returns. + * </p> + * </i> + * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (07/2016) + * @version 2.4 (09/2019) */ public class TAPTable implements DBTable { /** * Different types of table according to the TAP protocol. * The default one should be "table". - * + * * @author Grégory Mantelet (ARI) * @version 2.0 (08/2014) - * + * * @since 2.0 */ - public enum TableType{ + public enum TableType { output, table, view; } - /** Name that this table MUST have in ADQL queries. */ - private final String adqlName; + /** ADQL name of this table. + * <p><i>This name is neither qualified nor delimited.</i></p> */ + private String adqlName; - /** Indicates whether the given ADQL name must be simplified by {@link #getADQLName()}. - * <p>Here, "simplification" means removing the surrounding double quotes and the schema prefix if any.</p> - * @since 2.1 */ - private final boolean simplificationNeeded; + /** Name of this table as provided at creation. + * <p><i>This name may be qualified and/or delimited.</i></p> + * @since 2.4 */ + private final String rawName; - /** <p>Indicate whether the ADQL name has been given at creation with a schema prefix or not.</p> - * <p><i>Note: This information is used only when writing TAP_SCHEMA.tables or when writing the output of the resource /tables.</i></p> - * @since 2.0 - * @deprecated See {@link #simplificationNeeded}, {@link #getRawName()} and {@link #getADQLName()}. */ - @Deprecated - private boolean isInitiallyQualified; + /** Indicate whether the ADQL table name is case sensitive. In such case, + * this name will be put between double quotes in ADQL. + * @since 2.4 */ + private boolean tableNameCaseSensitive = false; /** Name that this table have in the database. - * <i>Note: If NULL, {@link #getDBName()} returns what {@link #getADQLName()} returns.</i> */ + * <p><i><b>Note:</b> + * If NULL, {@link #getDBName()} returns what {@link #getADQLName()} + * returns. + * </i></p> */ private String dbName = null; /** The schema which owns this table. - * <i>Note: It is NULL only at the construction. - * Then, this attribute is automatically set by a {@link TAPSchema} when adding this table inside it - * with {@link TAPSchema#addTable(TAPTable)}.</i> */ + * <p><i><b>Note:</b> + * It is NULL only at the construction. Then, this attribute is + * automatically set by a {@link TAPSchema} when adding this table inside + * it with {@link TAPSchema#addTable(TAPTable)}. + * </i></p> */ private TAPSchema schema = null; /** Type of this table. - * <i>Note: Standard TAP table field ; CAN NOT be NULL ; by default, it is "table".</i> */ + * <p><i><b>Note:</b> + * Standard TAP table field ; CAN NOT be NULL ; by default, it is "table". + * </i></p> */ private TableType type = TableType.table; /** Descriptive, human-interpretable name of the table. - * <i>Note: Standard TAP table field ; MAY be NULL.</i> + * <p><i><b>Note:</b> + * Standard TAP table field ; MAY be NULL. + * </i></p> * @since 2.0 */ private String title = null; /** Description of this table. - * <i>Note: Standard TAP table field ; MAY be NULL.</i> */ + * <p><i><b>Note:</b> + * Standard TAP table field ; MAY be NULL. + * </i></p> */ private String description = null; /** UType associating this table with a data-model. - * <i>Note: Standard TAP table field ; MAY be NULL.</i> */ + * <p><i><b>Note:</b> + * Standard TAP table field ; MAY be NULL. + * </i></p> */ private String utype = null; /** Ordering index of this table inside its schema. - * <i>Note: Standard TAP table field since TAP 1.1.</i> + * <p><i><b>Note:</b> + * Standard TAP table field since TAP 1.1. + * </i></p> * @since 2.1 */ private int index = -1; /** List of columns composing this table. - * <i>Note: all columns of this list are linked to this table from the moment they are added inside it.</i> */ - protected final Map<String,TAPColumn> columns; + * <p><i><b>Note:</b> + * All columns of this list are linked to this table from the moment they + * are added inside it. + * </i></p> */ + protected final Map<String, TAPColumn> columns; /** List of all foreign keys linking this table to others. */ protected final ArrayList<TAPForeignKey> foreignKeys; /** Let add some information in addition of the ones of the TAP protocol. - * <i>Note: This object can be anything: an {@link Integer}, a {@link String}, a {@link Map}, a {@link List}, ... - * Its content is totally free and never used or checked.</i> */ + * <p><i><b>Note:</b> + * This object can be anything: an {@link Integer}, a {@link String}, a + * {@link Map}, a {@link List}, ... Its content is totally free and never + * used or checked. + * </i></p> */ protected Object otherData = null; /** - * <p>Build a {@link TAPTable} instance with the given ADQL name.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a {@link TAPTable} instance with the given ADQL name. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: + * + * <p><i><b>Note 2:</b> * The table type is set by default to "table". * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the table name is prefixed by its schema name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the schema name must be exactly the same - * as what the function {@link TAPSchema#getRawName()} of the set schema returns. + * The schema prefix will not be removed until + * {@link #setSchema(TAPSchema)}. * </li> * <li> - * Double quotes may surround the single table name. They will be removed by {@link #getADQLName()} but will - * still appear in the result of {@link #getRawName()}. + * Double quotes may surround the table name. In such case, the ADQL + * name of this table will be considered as case sensitive and these + * double quotes will be automatically removed. + * <em>Note that this case sensitivity may not be identified just after + * this constructor ; you may have to specify the schema + * (see {@link #setSchema(TAPSchema)}) so that the schema prefix is + * removed first.</em> * </li> * </ul> - * - * @param tableName Name that this table MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). + * + * @param tableName ADQL name of this table. + * + * @throws NullPointerException If the given name is NULL or an empty string. */ - public TAPTable(String tableName) throws NullPointerException{ + public TAPTable(final String tableName) throws NullPointerException { if (tableName == null) throw new NullPointerException("Missing table name!"); - adqlName = tableName.trim(); - simplificationNeeded = (adqlName.indexOf('.') > 0 || adqlName.indexOf('"') >= 0); - isInitiallyQualified = (adqlName.indexOf('.') > 0); + rawName = tableName.trim(); + updateADQLName(); - if (getADQLName().length() == 0) + if (adqlName.trim().length() == 0) throw new NullPointerException("Missing table name!"); dbName = null; - columns = new LinkedHashMap<String,TAPColumn>(); + columns = new LinkedHashMap<String, TAPColumn>(); foreignKeys = new ArrayList<TAPForeignKey>(); } /** - * <p>Build a {@link TAPTable} instance with the given ADQL name and table type.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a {@link TAPTable} instance with the given ADQL name and table + * type. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: - * The table type is set by default to "table". + * + * <p><i><b>Note 2:</b> + * If omitted, the table type is set by default to "table". * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the table name is prefixed by its schema name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the schema name must be exactly the same - * as what the function {@link TAPSchema#getRawName()} of the set schema returns. + * The schema prefix will not be removed until + * {@link #setSchema(TAPSchema)}. * </li> * <li> - * Double quotes may surround the single table name. They will be removed by {@link #getADQLName()} but will - * still appear in the result of {@link #getRawName()}. + * Double quotes may surround the table name. In such case, the ADQL + * name of this table will be considered as case sensitive and these + * double quotes will be automatically removed. + * <em>Note that this case sensitivity may not be identified just after + * this constructor ; you may have to specify the schema + * (see {@link #setSchema(TAPSchema)}) so that the schema prefix is + * removed first.</em> * </li> * </ul> - * - * @param tableName Name that this table MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * @param tableType Type of this table. <i>If NULL, "table" will be the type of this table.</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). - * + * + * @param tableName ADQN name of this table. + * @param tableType Type of this table. + * <i>If NULL, "table" will be set by default.</i> + * + * @throws NullPointerException If the given name is NULL or an empty string. + * * @see #setType(TableType) */ - public TAPTable(String tableName, TableType tableType) throws NullPointerException{ + public TAPTable(final String tableName, final TableType tableType) throws NullPointerException { this(tableName); setType(tableType); } /** - * <p>Build a {@link TAPTable} instance with the given ADQL name, table type, description and UType.</p> - * - * <p><i>Note 1: - * The DB name is set by default to NULL so that {@link #getDBName()} returns exactly what {@link #getADQLName()} returns. - * To set a specific DB name, you MUST call {@link #setDBName(String)}. + * Build a {@link TAPTable} instance with the given ADQL name, table type, + * description and UType. + * + * <p><i><b>Note 1:</b> + * The DB name is set by default to NULL so that {@link #getDBName()} + * returns exactly what {@link #getADQLName()} returns. To set a specific + * DB name, you MUST call {@link #setDBName(String)}. * </i></p> - * - * <p><i>Note 2: - * The table type is set by default to "table". + * + * <p><i><b>Note 2:</b> + * If omitted, the table type is set by default to "table". * </i></p> - * + * * <p><b>Important notes on the given ADQL name:</b></p> * <ul> * <li>Any leading or trailing space is immediately deleted.</li> * <li> - * If the table name is prefixed by its schema name, this prefix is removed by {@link #getADQLName()} - * but will be still here when using {@link #getRawName()}. To work, the schema name must be exactly the same - * as what the function {@link TAPSchema#getRawName()} of the set schema returns. + * The schema prefix will not be removed until + * {@link #setSchema(TAPSchema)}. * </li> * <li> - * Double quotes may surround the single table name. They will be removed by {@link #getADQLName()} but will - * still appear in the result of {@link #getRawName()}. + * Double quotes may surround the table name. In such case, the ADQL + * name of this table will be considered as case sensitive and these + * double quotes will be automatically removed. + * <em>Note that this case sensitivity may not be identified just after + * this constructor ; you may have to specify the schema + * (see {@link #setSchema(TAPSchema)}) so that the schema prefix is + * removed first.</em> * </li> * </ul> - * - * @param tableName Name that this table MUST have in ADQL queries. - * <i>CAN'T be NULL ; this name can never be changed after initialization.</i> - * @param tableType Type of this table. <i>If NULL, "table" will be the type of this table.</i> - * @param description Description of this table. <i>MAY be NULL.</i> - * @param utype UType associating this table with a data-model. <i>MAY be NULL</i> - * - * @throws NullPointerException If the given name is <code>null</code>, - * or if the given string is empty after simplification - * (i.e. without the surrounding double quotes). - * - * @see #setType(TableType) + * + * @param tableName ADQL name of this table. + * @param tableType Type of this table. + * <i>If NULL, "table" will be set by default.</i> + * @param description Description of this table. + * <i>MAY be NULL.</i> + * @param utype UType associating this table with a data-model. + * <i>MAY be NULL</i> + * + * @throws NullPointerException If the given name is NULL or an empty string. */ - public TAPTable(String tableName, TableType tableType, String description, String utype) throws NullPointerException{ + public TAPTable(final String tableName, final TableType tableType, final String description, final String utype) throws NullPointerException { this(tableName, tableType); this.description = description; this.utype = utype; } /** - * <p>Get the qualified name of this table.</p> - * - * <p><i><b>Warning:</b> - * The part of the returned full name won't be double quoted! + * Get the qualified and delimited (if case sensitive) name of this table. + * + * <p><i><b>Note:</b> + * If this table is not attached to a schema, this function will just + * return the ADQL name of this table. * </i></p> - * - * <p><i>Note: - * If this table is not attached to a schema, this function will just return - * the ADQL name of this table. - * </i></p> - * - * @return Qualified ADQL name of this table. + * + * @return Qualified and delimited (if needed) ADQL name of this table. */ - public final String getFullName(){ - if (schema != null) - return schema.getADQLName() + "." + getADQLName(); - else - return adqlName; + public final String getFullName() { + return (schema != null ? schema.getADQLName() + "." : "") + (tableNameCaseSensitive ? "\"" + getADQLName().replaceAll("\"", "\"\"") + "\"" : getADQLName()); } /** * Get the ADQL name (the name this table MUST have in ADQL queries). - * + * * @return Its ADQL name. + * * @see #getADQLName() + * * @deprecated Does not do anything special: just call {@link #getADQLName()}. */ @Deprecated - public final String getName(){ + public final String getName() { return getADQLName(); } @Override - public final String getADQLName(){ - if (simplificationNeeded){ - String tmp = adqlName; - // Remove the schema prefix if any: - if (schema != null && tmp.startsWith(schema.getRawName() + ".")) - tmp = tmp.substring((schema.getRawName() + ".").length()).trim(); - // Remove the surrounding double-quotes if any: - if (tmp.matches("\"[^\"]*\"")) - tmp = tmp.substring(1, tmp.length() - 1); - // Finally, return the result: - return tmp; - }else - return adqlName; + public final String getADQLName() { + return adqlName; } /** - * Get the full ADQL name of this table, as it has been provided at initialization. - * + * Get the full ADQL name of this table, as it has been provided at + * initialization. + * * @return Get the original ADQL name. - * + * * @since 2.1 */ - public final String getRawName(){ - return adqlName; + public final String getRawName() { + return rawName; } /** - * <p>Tells whether the ADQL name of this table must be qualified in the "table_name" column of TAP_SCHEMA.tables - * and in the /schema/table/name field of the resource /tables.</p> - * - * <p><i>Note: this value is set automatically by the constructor: "true" if the table name was qualified, - * "false" otherwise. It can be changed with the function {@link #setInitiallyQualifed(boolean)}, BUT by doing so - * you may generate a mismatch between the table name of TAP_SCHEMA.tables and the one of /tables.</i></p> - * - * @return <i>true</i> if the table name must be qualified in TAP_SCHEMA.tables and in /tables, <i>false</i> otherwise. - * - * @since 2.0 - * @deprecated To get name of the table as it should be used: {@link #getRawName()}. - * To get just the table name (with no prefix and surrounding double quotes): {@link #getADQLName()}. + * Simplify the original ADQL name and set {@link #adqlName} with the + * simplified version. + * + * <p> + * The simplification consists in removing the schema prefix (if any) + * and to detect case sensitivity (i.e. surrounded by double quotes). In + * this last case, the detected double quotes are automatically removed. + * </p> + * + * @since 2.4 */ - @Deprecated - public final boolean isInitiallyQualified(){ - return isInitiallyQualified; + private void updateADQLName() { + String tmp = rawName; + + // If a schema is specified, remove the schema prefix (if any): + if (schema != null) { + // strict comparison if schema is case sensitive: + if (schema.isCaseSensitive()) { + if (tmp.startsWith(schema.getRawName() + ".")) + tmp = tmp.substring(schema.getRawName().length() + 1).trim(); + } + // if no case sensitivity... + else { + // ...search not-case-sensitively for a prefix: + if (tmp.toLowerCase().startsWith(schema.getADQLName().toLowerCase() + ".")) + tmp = tmp.substring(schema.getADQLName().length() + 1).trim(); + // ...otherwise, try with a strict comparison (as if schema was case sensitive): + else if (tmp.toLowerCase().startsWith("\"" + schema.getADQLName().toLowerCase().replaceAll("\"", "\"\"") + "\".")) + tmp = tmp.substring(schema.getADQLName().replaceAll("\"", "\"\"").length() + 3).trim(); + } + } + + // Detect if delimited (i.e. case sensitive): + if ((tableNameCaseSensitive = DefaultDBTable.isDelimited(tmp))) + tmp = tmp.substring(1, tmp.length() - 1).replaceAll("\"\"", "\""); + + // Finally, set the ADQL name: + adqlName = tmp; } - /** - * <p>Let specifying whether the table name must be qualified in TAP_SCHEMA.tables and in the resource /tables.</p> - * - * <p><b>WARNING: Calling this function may generate a mismatch between the table name of TAP_SCHEMA.tables and - * the one of the resource /tables. So, be sure to change this flag before setting the content of TAP_SCHEMA.tables - * using {@link tap.db.JDBCConnection#setTAPSchema(TAPMetadata)}.</b></p> - * - * @param mustBeQualified <i>true</i> if the table name in TAP_SCHEMA.tables and in the resource /tables must be qualified by the schema name, - * <i>false</i> otherwise. - * - * @since 2.0 - * @deprecated To get name of the table as it should be used: {@link #getRawName()}. - * To get just the table name (with no prefix and surrounding double quotes): {@link #getADQLName()}. - */ - @Deprecated - public final void setInitiallyQualifed(final boolean mustBeQualified){ - isInitiallyQualified = mustBeQualified; + @Override + public final boolean isCaseSensitive() { + return tableNameCaseSensitive; } @Override - public final String getDBName(){ + public final String getDBName() { return (dbName == null) ? getADQLName() : dbName; } /** - * <p>Change the name that this table MUST have in the database (i.e. in SQL queries).</p> - * - * <p><i>Note: - * If the given value is NULL or an empty string, {@link #getDBName()} will return exactly what {@link #getADQLName()} returns. + * Change the name that this table MUST have in the database (i.e. in SQL + * queries). + * + * <p><i><b>Note:</b> + * If the given value is NULL or an empty string, {@link #getDBName()} will + * return exactly what {@link #getADQLName()} returns. * </i></p> - * + * * @param name The new database name of this table. */ - public final void setDBName(String name){ + public final void setDBName(String name) { name = (name != null) ? name.trim() : name; if (name != null && name.length() > 0) dbName = name; @@ -384,221 +417,233 @@ public class TAPTable implements DBTable { } @Override - public String getADQLCatalogName(){ + public String getADQLCatalogName() { return null; } @Override - public String getDBCatalogName(){ + public String getDBCatalogName() { return null; } @Override - public final String getADQLSchemaName(){ + public final String getADQLSchemaName() { return schema == null ? null : schema.getADQLName(); } @Override - public final String getDBSchemaName(){ + public final String getDBSchemaName() { return schema == null ? null : schema.getDBName(); } /** * Get the schema that owns this table. - * + * * @return Its schema. <i>MAY be NULL</i> */ - public final TAPSchema getSchema(){ + public final TAPSchema getSchema() { return schema; } /** - * <p>Set the schema in which this schema is.</p> - * + * Set the schema in which this schema is. + * * <p><i><b>Warning:</b> - * For consistency reasons, this function SHOULD be called only by the {@link TAPSchema} - * that owns this table. + * For consistency reasons, this function SHOULD be called only by the + * {@link TAPSchema} that owns this table. * </i></p> - * + * * <p><i><b>Important note:</b> - * If this table was already linked with another {@link TAPSchema} object, the previous link is removed - * here, but also in the schema (by calling {@link TAPSchema#removeTable(String)}). + * If this table was already linked with another {@link TAPSchema} object, + * the previous link is removed here, but also in the schema (by calling + * {@link TAPSchema#removeTable(String)}). * </i></p> - * + * * @param schema The schema that owns this table. */ - protected final void setSchema(final TAPSchema schema){ + protected final void setSchema(final TAPSchema schema) { + // Update the former TAPSchema, if any: if (this.schema != null && (schema == null || !schema.equals(this.schema))) this.schema.removeTable(adqlName); + + // Set the new schema: this.schema = schema; + + /* Update the ADQL name of this table: + * (i.e. whether or not schema prefix should be removed) */ + updateADQLName(); } /** * Get the type of this table. - * + * * @return Its type. */ - public final TableType getType(){ + public final TableType getType() { return type; } /** * <p>Set the type of this table.</p> - * - * <p><i>Note: - * If the given type is NULL, nothing will be done ; the type of this table won't be changed. + * + * <p><i><b>Note:</b> + * If the given type is NULL, nothing will be done ; the type of this table + * won't be changed. * </i></p> - * + * * @param type Its new type. */ - public final void setType(TableType type){ + public final void setType(TableType type) { if (type != null) this.type = type; } /** * Get the title of this table. - * + * * @return Its title. <i>MAY be NULL</i> - * + * * @since 2.0 */ - public final String getTitle(){ + public final String getTitle() { return title; } /** * Set the title of this table. - * + * * @param title Its new title. <i>MAY be NULL</i> - * + * * @since 2.0 */ - public final void setTitle(final String title){ + public final void setTitle(final String title) { this.title = title; } /** * Get the description of this table. - * + * * @return Its description. <i>MAY be NULL</i> */ - public final String getDescription(){ + public final String getDescription() { return description; } /** * Set the description of this table. - * + * * @param description Its new description. <i>MAY be NULL</i> */ - public final void setDescription(String description){ + public final void setDescription(String description) { this.description = description; } /** * Get the UType associating this table with a data-model. - * + * * @return Its UType. <i>MAY be NULL</i> */ - public final String getUtype(){ + public final String getUtype() { return utype; } /** * Set the UType associating this table with a data-model. - * + * * @param utype Its new UType. <i>MAY be NULL</i> */ - public final void setUtype(String utype){ + public final void setUtype(String utype) { this.utype = utype; } /** * Get the ordering index of this table inside its schema. - * + * * @return Its ordering index. - * + * * @since 2.1 */ - public final int getIndex(){ + public final int getIndex() { return index; } /** * Set the ordering index of this table inside its schema. - * + * * @param tableIndex Its new ordering index. - * + * * @since 2.1 */ - public final void setIndex(int tableIndex){ + public final void setIndex(int tableIndex) { this.index = tableIndex; } /** - * <p>Get the other (piece of) information associated with this table.</p> - * + * Get the other (piece of) information associated with this table. + * * <p><i>Note: - * By default, NULL is returned, but it may be any kind of value ({@link Integer}, - * {@link String}, {@link Map}, {@link List}, ...). + * By default, NULL is returned, but it may be any kind of value + * ({@link Integer}, {@link String}, {@link Map}, {@link List}, ...). * </i></p> - * + * * @return The other (piece of) information. <i>MAY be NULL</i> */ - public Object getOtherData(){ + public Object getOtherData() { return otherData; } /** * Set the other (piece of) information associated with this table. - * + * * @param data Another information about this table. <i>MAY be NULL</i> */ - public void setOtherData(Object data){ + public void setOtherData(Object data) { otherData = data; } /** - * <p>Add a column to this table.</p> - * - * <p><i>Note: + * Add a column to this table. + * + * <p><i><b>Note:</b> * If the given column is NULL, nothing will be done. * </i></p> - * + * * <p><i><b>Important note:</b> * By adding the given column inside this table, it * will be linked with this table using {@link TAPColumn#setTable(DBTable)}. - * In this function, if the column was already linked with another {@link TAPTable}, - * the former link is removed using {@link TAPTable#removeColumn(String)}. + * In this function, if the column was already linked with another + * {@link TAPTable}, the former link is removed using + * {@link TAPTable#removeColumn(String)}. * </i></p> - * + * * @param newColumn Column to add inside this table. */ - public final void addColumn(final TAPColumn newColumn){ - if (newColumn != null && newColumn.getADQLName() != null){ + public final void addColumn(final TAPColumn newColumn) { + if (newColumn != null && newColumn.getADQLName() != null) { newColumn.setTable(this); columns.put(newColumn.getADQLName(), newColumn); } } /** - * <p>Build a {@link TAPColumn} object whose the ADQL and DB name will the given one. - * Then, add this column inside this table.</p> - * - * <p><i>Note: - * The built {@link TAPColumn} object is returned, so that being modified afterwards if needed. + * Build a {@link TAPColumn} object whose the ADQL and DB name will the + * given one. Then, add this column inside this table. + * + * <p><i><b>Note:</b> + * The built {@link TAPColumn} object is returned, so that being modified + * afterwards if needed. * </i></p> - * - * @param columnName ADQL name (and indirectly also the DB name) of the column to create and add. - * + * + * @param columnName ADQL name (and indirectly also the DB name) of the + * column to create and add. + * * @return The created and added {@link TAPColumn} object, * or NULL if the given name is NULL or an empty string. - * + * * @see TAPColumn#TAPColumn(String) * @see #addColumn(TAPColumn) */ - public final TAPColumn addColumn(String columnName){ + public final TAPColumn addColumn(String columnName) { if (columnName == null || columnName.trim().length() <= 0) return null; @@ -608,27 +653,32 @@ public class TAPTable implements DBTable { } /** - * <p>Build a {@link TAPColumn} object whose the ADQL and DB name will the given one. - * Then, add this column inside this table.</p> - * - * <p><i>Note: - * The built {@link TAPColumn} object is returned, so that being modified afterwards if needed. + * Build a {@link TAPColumn} object whose the ADQL and DB name will the given one. + * Then, add this column inside this table. + * + * <p><i><b>Note:</b> + * The built {@link TAPColumn} object is returned, so that being modified + * afterwards if needed. * </i></p> - * - * @param columnName ADQL name (and indirectly also the DB name) of the column to create and add. - * @param datatype Type of the new column's values. <i>If NULL, VARCHAR will be the type of the created column.</i> + * + * @param columnName ADQL name (and indirectly also the DB name) of the + * column to create and add. + * @param datatype Type of the new column's values. <i>If NULL, VARCHAR + * will be the type of the created column.</i> * @param description Description of the new column. <i>MAY be NULL</i> * @param unit Unit of the new column's values. <i>MAY be NULL</i> - * @param ucd UCD describing the scientific content of the new column. <i>MAY be NULL</i> - * @param utype UType associating the new column with a data-model. <i>MAY be NULL</i> - * + * @param ucd UCD describing the scientific content of the new + * column. <i>MAY be NULL</i> + * @param utype UType associating the new column with a data-model. + * <i>MAY be NULL</i> + * * @return The created and added {@link TAPColumn} object, * or NULL if the given name is NULL or an empty string. - * + * * @see TAPColumn#TAPColumn(String, DBType, String, String, String, String) * @see #addColumn(TAPColumn) */ - public TAPColumn addColumn(String columnName, DBType datatype, String description, String unit, String ucd, String utype){ + public TAPColumn addColumn(String columnName, DBType datatype, String description, String unit, String ucd, String utype) { if (columnName == null || columnName.trim().length() <= 0) return null; @@ -638,33 +688,41 @@ public class TAPTable implements DBTable { } /** - * <p>Build a {@link TAPColumn} object whose the ADQL and DB name will the given one. - * Then, add this column inside this table.</p> - * - * <p><i>Note: - * The built {@link TAPColumn} object is returned, so that being modified afterwards if needed. + * Build a {@link TAPColumn} object whose the ADQL and DB name will the + * given one. Then, add this column inside this table. + * + * <p><i><b>Note:</b> + * The built {@link TAPColumn} object is returned, so that being modified + * afterwards if needed. * </i></p> - * - * @param columnName ADQL name (and indirectly also the DB name) of the column to create and add. - * @param datatype Type of the new column's values. <i>If NULL, VARCHAR will be the type of the created column.</i> + * + * @param columnName ADQL name (and indirectly also the DB name) of the + * column to create and add. + * @param datatype Type of the new column's values. <i>If NULL, VARCHAR + * will be the type of the created column.</i> * @param description Description of the new column. <i>MAY be NULL</i> * @param unit Unit of the new column's values. <i>MAY be NULL</i> - * @param ucd UCD describing the scientific content of the new column. <i>MAY be NULL</i> - * @param utype UType associating the new column with a data-model. <i>MAY be NULL</i> - * @param principal <i>true</i> if the new column should be returned by default, <i>false</i> otherwise. - * @param indexed <i>true</i> if the new column is indexed, <i>false</i> otherwise. - * @param std <i>true</i> if the new column is defined by a standard, <i>false</i> otherwise. - * + * @param ucd UCD describing the scientific content of the new + * column. <i>MAY be NULL</i> + * @param utype UType associating the new column with a data-model. + * <i>MAY be NULL</i> + * @param principal <code>true</code> if the new column should be + * returned by default, <code>false</code> otherwise. + * @param indexed <code>true</code> if the new column is indexed, + * <code>false</code> otherwise. + * @param std <code>true</code> if the new column is defined by a + * standard, <code>false</code> otherwise. + * * @return The created and added {@link TAPColumn} object, * or NULL if the given name is NULL or an empty string. - * + * * @see TAPColumn#TAPColumn(String, DBType, String, String, String, String) * @see TAPColumn#setPrincipal(boolean) * @see TAPColumn#setIndexed(boolean) * @see TAPColumn#setStd(boolean) * @see #addColumn(TAPColumn) */ - public TAPColumn addColumn(String columnName, DBType datatype, String description, String unit, String ucd, String utype, boolean principal, boolean indexed, boolean std){ + public TAPColumn addColumn(String columnName, DBType datatype, String description, String unit, String ucd, String utype, boolean principal, boolean indexed, boolean std) { if (columnName == null || columnName.trim().length() <= 0) return null; @@ -677,17 +735,20 @@ public class TAPTable implements DBTable { } /** - * <p>Tell whether this table contains a column with the given ADQL name.</p> - * + * Tell whether this table contains a column with the given ADQL name. + * * <p><i><b>Important note:</b> * This function is case sensitive. * </i></p> - * - * @param columnName ADQL name (case sensitive) of the column whose the existence must be checked. - * - * @return <i>true</i> if a column having the given ADQL name exists in this table, <i>false</i> otherwise. + * + * @param columnName ADQL name (case sensitive) of the column whose the + * existence must be checked. + * + * @return <code>true</code> if a column having the given ADQL name exists + * in this table, + * <code>false</code> otherwise. */ - public final boolean hasColumn(String columnName){ + public final boolean hasColumn(String columnName) { if (columnName == null) return false; else @@ -696,21 +757,21 @@ public class TAPTable implements DBTable { /** * Get the list of all columns contained in this table. - * + * * @return An iterator over the list of this table's columns. */ - public Iterator<TAPColumn> getColumns(){ + public Iterator<TAPColumn> getColumns() { return columns.values().iterator(); } @Override - public DBColumn getColumn(String colName, boolean byAdqlName){ + public DBColumn getColumn(String colName, boolean byAdqlName) { if (byAdqlName) return getColumn(colName); - else{ - if (colName != null && colName.length() > 0){ + else { + if (colName != null && colName.length() > 0) { Collection<TAPColumn> collColumns = columns.values(); - for(TAPColumn column : collColumns){ + for(TAPColumn column : collColumns) { if (column.getDBName().equals(colName)) return column; } @@ -720,18 +781,18 @@ public class TAPTable implements DBTable { } /** - * <p>Search a column inside this table having the given ADQL name.</p> - * + * Search a column inside this table having the given ADQL name. + * * <p><i><b>Important note:</b> * This function is case sensitive. * </i></p> - * + * * @param columnName ADQL name of the column to search. - * + * * @return The matching column, * or NULL if no column with this ADQL name has been found. */ - public final TAPColumn getColumn(String columnName){ + public final TAPColumn getColumn(String columnName) { if (columnName == null) return null; else @@ -739,64 +800,67 @@ public class TAPTable implements DBTable { } /** - * <p>Tell whether this table contains a column with the given ADQL or DB name.</p> - * - * <p><i>Note: - * This functions is just calling {@link #getColumn(String, boolean)} and compare its result - * with NULL in order to check the existence of the specified column. + * Tell whether this table contains a column with the given ADQL or DB name. + * + * <p><i><b>Note:</b> + * This functions is just calling {@link #getColumn(String, boolean)} and + * compare its result with NULL in order to check the existence of the + * specified column. * </i></p> - * + * * @param colName ADQL or DB name that the column to search must have. - * @param byAdqlName <i>true</i> to search the column by ADQL name, <i>false</i> to search by DB name. - * - * @return <i>true</i> if a column has been found inside this table with the given ADQL or DB name, - * <i>false</i> otherwise. - * + * @param byAdqlName <code>true</code> to search the column by ADQL name, + * <code>false</code> to search by DB name. + * + * @return <code>true</code> if a column has been found inside this table + * with the given ADQL or DB name, + * <code>false</code> otherwise. + * * @see #getColumn(String, boolean) */ - public boolean hasColumn(String colName, boolean byAdqlName){ + public boolean hasColumn(String colName, boolean byAdqlName) { return (getColumn(colName, byAdqlName) != null); } /** * Get the number of columns composing this table. - * + * * @return Number of its columns. */ - public final int getNbColumns(){ + public final int getNbColumns() { return columns.size(); } /** * Tell whether this table contains no column. - * - * @return <i>true</i> if this table is empty (no column), - * <i>false</i> if it contains at least one column. + * + * @return <code>true</code> if this table is empty (no column), + * <code>false</code> if it contains at least one column. */ - public final boolean isEmpty(){ + public final boolean isEmpty() { return columns.isEmpty(); } /** - * <p>Remove the specified column.</p> - * + * Remove the specified column. + * * <p><i><b>Important note:</b> * This function is case sensitive! * </i></p> - * - * <p><i>Note: + * + * <p><i><b>Note:</b> * If some foreign keys were associating the column to remove, * they will be also deleted. * </i></p> - * + * * @param columnName ADQL name of the column to remove. - * + * * @return The removed column, * or NULL if no column with the given ADQL name has been found. - * + * * @see #deleteColumnRelations(TAPColumn) */ - public final TAPColumn removeColumn(String columnName){ + public final TAPColumn removeColumn(String columnName) { if (columnName == null) return null; @@ -808,11 +872,12 @@ public class TAPTable implements DBTable { } /** - * Delete all foreign keys having the given column in the sources or the targets list. - * + * Delete all foreign keys having the given column in the sources or the + * targets list. + * * @param col A column. */ - protected final void deleteColumnRelations(TAPColumn col){ + protected final void deleteColumnRelations(TAPColumn col) { // Remove the relation between the column and this table: col.setTable(null); @@ -822,7 +887,7 @@ public class TAPTable implements DBTable { removeForeignKey(it.next()); it = col.getSources(); - while(it.hasNext()){ + while(it.hasNext()) { TAPForeignKey key = it.next(); key.getFromTable().removeForeignKey(key); } @@ -832,40 +897,43 @@ public class TAPTable implements DBTable { * Remove all columns composing this table. * Foreign keys will also be deleted. */ - public final void removeAllColumns(){ - Iterator<Map.Entry<String,TAPColumn>> it = columns.entrySet().iterator(); - while(it.hasNext()){ - Map.Entry<String,TAPColumn> entry = it.next(); + public final void removeAllColumns() { + Iterator<Map.Entry<String, TAPColumn>> it = columns.entrySet().iterator(); + while(it.hasNext()) { + Map.Entry<String, TAPColumn> entry = it.next(); it.remove(); deleteColumnRelations(entry.getValue()); } } /** - * <p>Add the given foreign key to this table.</p> - * - * <p><i>Note: + * Add the given foreign key to this table. + * + * <p><i><b>Note:</b> * This function will do nothing if the given foreign key is NULL. * </i></p> - * + * * <p><i><b>WARNING:</b> - * The source table ({@link TAPForeignKey#getFromTable()}) of the given foreign key MUST be this table - * and the foreign key MUST be completely defined. - * If not, an exception will be thrown and the key won't be added. + * The source table ({@link TAPForeignKey#getFromTable()}) of the given + * foreign key MUST be this table and the foreign key MUST be completely + * defined. If not, an exception will be thrown and the key won't be added. * </i></p> - * - * <p><i>Note: - * If the given foreign key is added to this table, all the columns of this key will be - * linked to the foreign key using either {@link TAPColumn#addSource(TAPForeignKey)} or + * + * <p><i><b>Note:</b> + * If the given foreign key is added to this table, all the columns of this + * key will be linked to the foreign key using either + * {@link TAPColumn#addSource(TAPForeignKey)} or * {@link TAPColumn#addTarget(TAPForeignKey)}. * </i></p> - * - * @param key Foreign key (whose the FROM table is this table) to add inside this table. - * - * @throws TAPException If the source table of the given foreign key is not this table - * or if the given key is not completely defined. + * + * @param key Foreign key (whose the FROM table is this table) to add + * inside this table. + * + * @throws TAPException If the source table of the given foreign key is not + * this table or if the given key is not completely + * defined. */ - public final void addForeignKey(TAPForeignKey key) throws TAPException{ + public final void addForeignKey(TAPForeignKey key) throws TAPException { if (key == null) return; @@ -873,31 +941,31 @@ public class TAPTable implements DBTable { final String errorMsgPrefix = "Impossible to add the foreign key \"" + keyId + "\" because "; if (key.getFromTable() == null) - throw new TAPException(errorMsgPrefix + "no source table is specified !"); + throw new TAPException(errorMsgPrefix + "no source table is specified!"); if (!this.equals(key.getFromTable())) throw new TAPException(errorMsgPrefix + "the source table is not \"" + getADQLName() + "\""); if (key.getTargetTable() == null) - throw new TAPException(errorMsgPrefix + "no target table is specified !"); + throw new TAPException(errorMsgPrefix + "no target table is specified!"); if (key.isEmpty()) - throw new TAPException(errorMsgPrefix + "it defines no relation !"); + throw new TAPException(errorMsgPrefix + "it defines no relation!"); - if (foreignKeys.add(key)){ - try{ + if (foreignKeys.add(key)) { + try { TAPTable targetTable = key.getTargetTable(); - for(Map.Entry<String,String> relation : key){ + for(Map.Entry<String, String> relation : key) { if (!hasColumn(relation.getKey())) - throw new TAPException(errorMsgPrefix + "the source column \"" + relation.getKey() + "\" doesn't exist in \"" + getADQLName() + "\" !"); + throw new TAPException(errorMsgPrefix + "the source column \"" + relation.getKey() + "\" doesn't exist in \"" + getADQLName() + "\"!"); else if (!targetTable.hasColumn(relation.getValue())) - throw new TAPException(errorMsgPrefix + "the target column \"" + relation.getValue() + "\" doesn't exist in \"" + targetTable.getADQLName() + "\" !"); - else{ + throw new TAPException(errorMsgPrefix + "the target column \"" + relation.getValue() + "\" doesn't exist in \"" + targetTable.getADQLName() + "\"!"); + else { getColumn(relation.getKey()).addTarget(key); targetTable.getColumn(relation.getValue()).addSource(key); } } - }catch(TAPException ex){ + } catch(TAPException ex) { foreignKeys.remove(key); throw ex; } @@ -905,52 +973,59 @@ public class TAPTable implements DBTable { } /** - * <p>Build a foreign key using the ID, the target table and the given list of columns. - * Then, add the created foreign key to this table.</p> - * - * <p><i>Note: - * The source table of the created foreign key ({@link TAPForeignKey#getFromTable()}) will be this table. + * Build a foreign key using the ID, the target table and the given list of + * columns. Then, add the created foreign key to this table. + * + * <p><i><b>Note 1:</b> + * The source table of the created foreign key + * ({@link TAPForeignKey#getFromTable()}) will be this table. * </i></p> - * - * <p><i>Note: - * If the given foreign key is added to this table, all the columns of this key will be - * linked to the foreign key using either {@link TAPColumn#addSource(TAPForeignKey)} or + * + * <p><i><b>Note 2:</b> + * If the given foreign key is added to this table, all the columns of this + * key will be linked to the foreign key using either + * {@link TAPColumn#addSource(TAPForeignKey)} or * {@link TAPColumn#addTarget(TAPForeignKey)}. * </i></p> - * + * * @return The created and added foreign key. - * - * @throws TAPException If the specified key is not completely or correctly defined. - * + * + * @throws TAPException If the specified key is not completely or correctly + * defined. + * * @see TAPForeignKey#TAPForeignKey(String, TAPTable, TAPTable, Map) */ - public TAPForeignKey addForeignKey(String keyId, TAPTable targetTable, Map<String,String> columns) throws TAPException{ + public TAPForeignKey addForeignKey(String keyId, TAPTable targetTable, Map<String, String> columns) throws TAPException { TAPForeignKey key = new TAPForeignKey(keyId, this, targetTable, columns); addForeignKey(key); return key; } /** - * <p>Build a foreign key using the ID, the target table, the given list of columns, the given description and the given UType. - * Then, add the created foreign key to this table.</p> - * - * <p><i>Note: - * The source table of the created foreign key ({@link TAPForeignKey#getFromTable()}) will be this table. + * Build a foreign key using the ID, the target table, the given list of + * columns, the given description and the given UType. Then, add the created + * foreign key to this table. + * + * <p><i><b>Note 1:</b> + * The source table of the created foreign key + * ({@link TAPForeignKey#getFromTable()}) will be this table. * </i></p> - * - * <p><i>Note: - * If the given foreign key is added to this table, all the columns of this key will be - * linked to the foreign key using either {@link TAPColumn#addSource(TAPForeignKey)} or + * + * <p><i><b>Note 2:</b> + * If the given foreign key is added to this table, all the columns of this + * key will be linked to the foreign key using either + * {@link TAPColumn#addSource(TAPForeignKey)} or * {@link TAPColumn#addTarget(TAPForeignKey)}. * </i></p> - * + * * @return The created and added foreign key. - * - * @throws TAPException If the specified key is not completely or correctly defined. - * + * + * @throws TAPException If the specified key is not completely or correctly + * defined. + * * @see TAPForeignKey#TAPForeignKey(String, TAPTable, TAPTable, Map, String, String) */ - public TAPForeignKey addForeignKey(String keyId, TAPTable targetTable, Map<String,String> columns, String description, String utype) throws TAPException{ + public TAPForeignKey addForeignKey(String keyId, TAPTable targetTable, Map<String, String> columns, String description, String utype) throws TAPException { TAPForeignKey key = new TAPForeignKey(keyId, this, targetTable, columns, description, utype); addForeignKey(key); return key; @@ -958,54 +1033,56 @@ public class TAPTable implements DBTable { /** * Get the list of all foreign keys associated whose the source is this table. - * + * * @return An iterator over all its foreign keys. */ - public final Iterator<TAPForeignKey> getForeignKeys(){ + public final Iterator<TAPForeignKey> getForeignKeys() { return foreignKeys.iterator(); } /** * Get the number of all foreign keys whose the source is this table - * + * * @return Number of all its foreign keys. */ - public final int getNbForeignKeys(){ + public final int getNbForeignKeys() { return foreignKeys.size(); } /** - * <p>Remove the given foreign key from this table.</p> - * - * <p><i>Note: - * This function will also delete the link between the columns of the foreign key - * and the foreign key, using {@link #deleteRelations(TAPForeignKey)}. + * Remove the given foreign key from this table. + * + * <p><i><b>Note:</b> + * This function will also delete the link between the columns of the + * foreign key and the foreign key, using + * {@link #deleteRelations(TAPForeignKey)}. * </i></p> - * + * * @param keyToRemove Foreign key to removed from this table. - * - * @return <i>true</i> if the key has been successfully removed, - * <i>false</i> otherwise. + * + * @return <code>true</code> if the key has been successfully removed, + * <code>false</code> otherwise. */ - public final boolean removeForeignKey(TAPForeignKey keyToRemove){ - if (foreignKeys.remove(keyToRemove)){ + public final boolean removeForeignKey(TAPForeignKey keyToRemove) { + if (foreignKeys.remove(keyToRemove)) { deleteRelations(keyToRemove); return true; - }else + } else return false; } /** - * <p>Remove all the foreign keys whose the source is this table.</p> - * - * <p><i>Note: - * This function will also delete the link between the columns of all the removed foreign keys - * and the foreign keys, using {@link #deleteRelations(TAPForeignKey)}. + * Remove all the foreign keys whose the source is this table. + * + * <p><i><b>Note:</b> + * This function will also delete the link between the columns of all the + * removed foreign keys and the foreign keys, using + * {@link #deleteRelations(TAPForeignKey)}. * </i></p> */ - public final void removeAllForeignKeys(){ + public final void removeAllForeignKeys() { Iterator<TAPForeignKey> it = foreignKeys.iterator(); - while(it.hasNext()){ + while(it.hasNext()) { deleteRelations(it.next()); it.remove(); } @@ -1013,13 +1090,13 @@ public class TAPTable implements DBTable { /** * Delete the link between all columns of the given foreign key - * and this foreign key. Thus, these columns won't be anymore source or target - * of this foreign key. - * + * and this foreign key. Thus, these columns won't be anymore source or + * target of this foreign key. + * * @param key A foreign key whose links with its columns must be deleted. */ - protected final void deleteRelations(TAPForeignKey key){ - for(Map.Entry<String,String> relation : key){ + protected final void deleteRelations(TAPForeignKey key) { + for(Map.Entry<String, String> relation : key) { TAPColumn col = key.getFromTable().getColumn(relation.getKey()); if (col != null) col.removeTarget(key); @@ -1031,35 +1108,35 @@ public class TAPTable implements DBTable { } @Override - public Iterator<DBColumn> iterator(){ - return new Iterator<DBColumn>(){ + public Iterator<DBColumn> iterator() { + return new Iterator<DBColumn>() { private final Iterator<TAPColumn> it = getColumns(); @Override - public boolean hasNext(){ + public boolean hasNext() { return it.hasNext(); } @Override - public DBColumn next(){ + public DBColumn next() { return it.next(); } @Override - public void remove(){ + public void remove() { it.remove(); } }; } @Override - public String toString(){ - return ((schema != null) ? (schema.getADQLName() + ".") : "") + getADQLName(); + public String toString() { + return ((schema != null) ? (schema.toString() + ".") : "") + (tableNameCaseSensitive ? "\"" + adqlName.replaceAll("\"", "\"\"") + "\"" : getADQLName()); } @Override - public DBTable copy(final String dbName, final String adqlName){ - TAPTable copy = new TAPTable((adqlName == null) ? this.adqlName : adqlName); + public DBTable copy(final String dbName, final String adqlName) { + TAPTable copy = new TAPTable((adqlName == null) ? this.rawName : adqlName); copy.setDBName((dbName == null) ? this.getDBName() : dbName); copy.setSchema(schema); Collection<TAPColumn> collColumns = columns.values(); diff --git a/tap_schema_requirements.md b/tap_schema_requirements.md new file mode 100644 index 0000000000000000000000000000000000000000..04c1805be34e9d14126bd21f8b7ad7f809b149c9 --- /dev/null +++ b/tap_schema_requirements.md @@ -0,0 +1,55 @@ + +# TAP_SCHEMA.schemas + +`db_name` + +: - optional + - never qualified nor delimited + - if `NULL`, exactly equals to undelimited `schema_name` + +# TAP_SCHEMA.tables + +`schema_name` + +: exactly equals to `TAP_SCHEMA.schemas.schema_name` + +`table_name` + +: - may be qualified (by schema name which can be be delimited or + not ; it does not have to be exactly equals to + `TAP_SCHEMA.schemas.schema_name`) + - delimited if case sensitive (or not an ADQL regular identifier) + +`db_name` + +: - optional + - never qualified nor delimited + - if `NULL`, exactly equals to undelimited `table_name` + +# TAP_SCHEMA.columns + +`table_name` + +: exactly equals to `TAP_SCHEMA.tables.table_name` + +`column_name` + +: - delimited if case sensitive (or if not an ADQL regular identifier) + - never qualified + +`db_name` + +: - optional + - never qualified nor delimited + - if `NULL`, exactly equals to undelimited `column_name` + +# TAP_SCHEMA.keys + +`from_table` + +: exactly equals to `TAP_SCHEMA.tables.table_name` + +`target_table` + +: exactly equals to `TAP_SCHEMA.tables.table_name` + diff --git a/test/adql/db/TestDefaultDBTable.java b/test/adql/db/TestDefaultDBTable.java new file mode 100644 index 0000000000000000000000000000000000000000..016ee2bade4a7be8c80011b3aa4dca14ae739aa3 --- /dev/null +++ b/test/adql/db/TestDefaultDBTable.java @@ -0,0 +1,70 @@ +package adql.db; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class TestDefaultDBTable { + + @Test + public void testIsDelimited() { + // CASE: All correctly delimited names + assertTrue(DefaultDBTable.isDelimited("\"\"")); + assertTrue(DefaultDBTable.isDelimited("\" \"")); + assertTrue(DefaultDBTable.isDelimited("\"a\"")); + assertTrue(DefaultDBTable.isDelimited("\"\"\"\"")); + assertTrue(DefaultDBTable.isDelimited("\"foo.bar\"")); + assertTrue(DefaultDBTable.isDelimited("\"foo\"\".\"\"bar\"")); + + // CASE: NOT delimited names + assertFalse(DefaultDBTable.isDelimited(null)); + assertFalse(DefaultDBTable.isDelimited("")); + assertFalse(DefaultDBTable.isDelimited("foo")); + assertFalse(DefaultDBTable.isDelimited("\"foo")); + assertFalse(DefaultDBTable.isDelimited("foo\"")); + assertFalse(DefaultDBTable.isDelimited("\"foo\".\"bar\"")); + } + + @Test + public void testSetADQLName() { + + DefaultDBTable table = new DefaultDBTable("dbName"); + assertEquals(table.getDBName(), table.getADQLName()); + assertFalse(table.isCaseSensitive()); + + // CASE: undelimited name => OK + table.setADQLName("myTable"); + assertEquals("myTable", table.getADQLName()); + assertFalse(table.isCaseSensitive()); + + // CASE: No name => use the DBName + table.setADQLName(null); + assertEquals(table.getDBName(), table.getADQLName()); + assertFalse(table.isCaseSensitive()); + + // CASE: delimited name => stored undelimited + table.setADQLName("\"MyTable\""); + assertEquals("MyTable", table.getADQLName()); + assertTrue(table.isCaseSensitive()); + + // CASE: Empty string => use the DBName (as name=NULL) + table.setADQLName(""); + assertEquals(table.getDBName(), table.getADQLName()); + assertFalse(table.isCaseSensitive()); + + // CASE: dbName delimited and no ADQL name => adqlName = undelimited dbName + table = new DefaultDBTable("\"DBName\""); + table.setADQLName(null); + assertEquals("DBName", table.getADQLName()); + assertTrue(table.isCaseSensitive()); + + // CASE: dbName delimited but empty and no ADQL name => adqlName = delimited dbName + table = new DefaultDBTable("\" \""); + table.setADQLName(null); + assertEquals(table.getDBName(), table.getADQLName()); + assertFalse(table.isCaseSensitive()); + } + +} diff --git a/test/tap/metadata/TestMetadataNames.java b/test/tap/metadata/TestMetadataNames.java index 1e95f5c46492a02aad6bb60ce7690c41f26bba2c..4fdb9c9e98555cba22cee1f9c0b8f94b9d10f7cc 100644 --- a/test/tap/metadata/TestMetadataNames.java +++ b/test/tap/metadata/TestMetadataNames.java @@ -1,6 +1,8 @@ package tap.metadata; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.PrintWriter; @@ -17,81 +19,97 @@ public class TestMetadataNames { /** TEST SCHEMA NAME */ @Test - public void testSchemaName(){ + public void testSchemaName() { TAPSchema schema; // NULL - try{ + try { new TAPSchema(null); fail("It should be impossible to create a TAPSchema with a NULL name."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { assertEquals("Missing schema name!", npe.getMessage()); } // Empty string (not a single character): - try{ + try { new TAPSchema(""); fail("It should be impossible to create a TAPSchema with an empty name."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { assertEquals("Missing schema name!", npe.getMessage()); } // String with only space characters: - try{ + try { new TAPSchema(" "); fail("It should be impossible to create a TAPSchema with a name just composed of space characters."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { assertEquals("Missing schema name!", npe.getMessage()); } - // Empty quoted string: - try{ + // Empty quoted string I: + try { new TAPSchema("\"\""); fail("It should be impossible to create a TAPSchema with a empty name even if quoted."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { + assertEquals("Missing schema name!", npe.getMessage()); + } + + // Empty quoted string II: + try { + new TAPSchema("\" \""); + fail("It should be impossible to create a TAPSchema with a empty name even if quoted."); + } catch(NullPointerException npe) { assertEquals("Missing schema name!", npe.getMessage()); } // Non quoted names => ADQL_NAME = RAW_NAME = TRIMMED(GIVEN_NAME) - try{ + try { schema = new TAPSchema("foo"); assertEquals("foo", schema.getADQLName()); + assertFalse(schema.isCaseSensitive()); assertEquals("foo", schema.getRawName()); + assertEquals(schema.getRawName(), schema.toString()); schema = new TAPSchema(" foo "); assertEquals("foo", schema.getADQLName()); + assertFalse(schema.isCaseSensitive()); assertEquals("foo", schema.getRawName()); + assertEquals(schema.getRawName(), schema.toString()); // Qualified name => Not supported as a catalog name! schema = new TAPSchema("myCat.foo"); assertEquals("myCat.foo", schema.getADQLName()); + assertFalse(schema.isCaseSensitive()); assertEquals("myCat.foo", schema.getRawName()); + assertEquals(schema.getRawName(), schema.toString()); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { npe.printStackTrace(System.err); fail("Unexpected error! The schema name is not empty or NULL. (see console for more details)"); } // Quoted names => ADQL_NAME <> RAW_NAME = TRIMMED(GIVEN_NAME) - try{ - schema = new TAPSchema("\" \""); - assertEquals(" ", schema.getADQLName()); - assertEquals("\" \"", schema.getRawName()); - + try { schema = new TAPSchema("\"foo\""); assertEquals("foo", schema.getADQLName()); + assertTrue(schema.isCaseSensitive()); assertEquals("\"foo\"", schema.getRawName()); + assertEquals(schema.getRawName(), schema.toString()); schema = new TAPSchema(" \" foo \" "); assertEquals(" foo ", schema.getADQLName()); + assertTrue(schema.isCaseSensitive()); assertEquals("\" foo \"", schema.getRawName()); + assertEquals(schema.getRawName(), schema.toString()); // Qualified name => Not supported as a catalog name! schema = new TAPSchema("myCat.\"foo\""); assertEquals("myCat.\"foo\"", schema.getADQLName()); + assertFalse(schema.isCaseSensitive()); assertEquals("myCat.\"foo\"", schema.getRawName()); + assertEquals(schema.getRawName(), schema.toString()); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { npe.printStackTrace(System.err); fail("Unexpected error! The schema name is not empty or NULL. (see console for more details)"); } @@ -99,119 +117,137 @@ public class TestMetadataNames { /** TEST TABLE NAME */ @Test - public void testTableName(){ + public void testTableName() { TAPTable table, table2, table3; // NULL - try{ + try { new TAPTable(null); fail("It should be impossible to create a TAPTable with a NULL name."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { assertEquals("Missing table name!", npe.getMessage()); } // Empty string (not a single character): - try{ + try { new TAPTable(""); fail("It should be impossible to create a TAPTable with an empty name."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { assertEquals("Missing table name!", npe.getMessage()); } // String with only space characters: - try{ + try { new TAPTable(" "); fail("It should be impossible to create a TAPTable with a name just composed of space characters."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { assertEquals("Missing table name!", npe.getMessage()); } - // Empty quoted string: - try{ + // Empty quoted string I: + try { new TAPTable("\"\""); fail("It should be impossible to create a TAPTable with a empty name even if quoted."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { + assertEquals("Missing table name!", npe.getMessage()); + } + + // Empty quoted string II: + try { + new TAPTable("\" \""); + fail("It should be impossible to create a TAPTable with a empty name even if quoted."); + } catch(NullPointerException npe) { assertEquals("Missing table name!", npe.getMessage()); } // Non quoted names => ADQL_NAME = RAW_NAME = TRIMMED(GIVEN_NAME) - try{ + try { table = new TAPTable("foo"); assertEquals("foo", table.getADQLName()); + assertFalse(table.isCaseSensitive()); assertEquals("foo", table.getRawName()); table = new TAPTable(" foo "); assertEquals("foo", table.getADQLName()); + assertFalse(table.isCaseSensitive()); assertEquals("foo", table.getRawName()); - // Qualified name => Without a schema link, no prefix can be removed! + // Qualified name => Without a schema link, a default cut is done: table = new TAPTable("mySchema.foo"); - assertEquals("mySchema.foo", table.getADQLName()); assertEquals("mySchema.foo", table.getRawName()); + assertEquals(table.getRawName(), table.getADQLName()); + assertFalse(table.isCaseSensitive()); // Qualified name + Schema with the WRONG name: table.setSchema(new TAPSchema("Blabla")); assertEquals("mySchema.foo", table.getADQLName()); + assertFalse(table.isCaseSensitive()); assertEquals("mySchema.foo", table.getRawName()); // Qualified name + Schema with the RIGHT name: table.setSchema(new TAPSchema("mySchema")); assertEquals("foo", table.getADQLName()); + assertFalse(table.isCaseSensitive()); assertEquals("mySchema.foo", table.getRawName()); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { npe.printStackTrace(System.err); fail("Unexpected error! The table name is not empty or NULL. (see console for more details)"); } // Quoted names => ADQL_NAME <> RAW_NAME = TRIMMED(GIVEN_NAME) - try{ - table = new TAPTable("\" \""); - assertEquals(" ", table.getADQLName()); - assertEquals("\" \"", table.getRawName()); - + try { table = new TAPTable("\"foo\""); assertEquals("foo", table.getADQLName()); + assertTrue(table.isCaseSensitive()); assertEquals("\"foo\"", table.getRawName()); table = new TAPTable(" \" foo \" "); assertEquals(" foo ", table.getADQLName()); + assertTrue(table.isCaseSensitive()); assertEquals("\" foo \"", table.getRawName()); - // Qualified name => Without a schema link, no prefix can be removed! + // Qualified name => a default cut is done: table = new TAPTable("mySchema.\"foo\""); - assertEquals("mySchema.\"foo\"", table.getADQLName()); assertEquals("mySchema.\"foo\"", table.getRawName()); + assertEquals(table.getRawName(), table.getADQLName()); + assertFalse(table.isCaseSensitive()); table2 = new TAPTable(" \"mySchema\". \"foo\""); - assertEquals("\"mySchema\". \"foo\"", table2.getADQLName()); assertEquals("\"mySchema\". \"foo\"", table2.getRawName()); + assertEquals(table2.getRawName(), table2.getADQLName()); + assertFalse(table2.isCaseSensitive()); table3 = new TAPTable(" \"mySchema\". foo"); - assertEquals("\"mySchema\". foo", table3.getADQLName()); assertEquals("\"mySchema\". foo", table3.getRawName()); + assertEquals(table3.getRawName(), table3.getADQLName()); + assertFalse(table3.isCaseSensitive()); // Qualified name + Schema with the WRONG name: table.setSchema(new TAPSchema("Blabla")); - assertEquals("mySchema.\"foo\"", table.getADQLName()); assertEquals("mySchema.\"foo\"", table.getRawName()); - table2.setSchema(new TAPSchema("mySchema")); - assertEquals("\"mySchema\". \"foo\"", table2.getADQLName()); - assertEquals("\"mySchema\". \"foo\"", table2.getRawName()); - table3.setSchema(new TAPSchema("mySchema")); - assertEquals("\"mySchema\". foo", table3.getADQLName()); - assertEquals("\"mySchema\". foo", table3.getRawName()); + assertEquals(table.getRawName(), table.getADQLName()); + assertFalse(table.isCaseSensitive()); - // Qualified name + Schema with the RIGHT name: - table.setSchema(new TAPSchema("mySchema")); + // Qualified name + Schema with the RIGHT name (not case sensitive): + table.setSchema(new TAPSchema("MYSchema")); assertEquals("foo", table.getADQLName()); + assertTrue(table.isCaseSensitive()); assertEquals("mySchema.\"foo\"", table.getRawName()); - table2.setSchema(new TAPSchema("\"mySchema\"")); + table2.setSchema(new TAPSchema("MYSchema")); assertEquals("foo", table2.getADQLName()); + assertTrue(table2.isCaseSensitive()); assertEquals("\"mySchema\". \"foo\"", table2.getRawName()); + + // Qualified name + Schema with the RIGHT name (case sensitive): table3.setSchema(new TAPSchema("\"mySchema\"")); assertEquals("foo", table3.getADQLName()); + assertFalse(table3.isCaseSensitive()); assertEquals("\"mySchema\". foo", table3.getRawName()); + table3.setSchema(new TAPSchema("\"MYSchema\"")); + assertEquals("\"mySchema\". foo", table3.getRawName()); + assertEquals(table3.getRawName(), table3.getADQLName()); + assertFalse(table3.isCaseSensitive()); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { npe.printStackTrace(System.err); fail("Unexpected error! The table name is not empty or NULL. (see console for more details)"); } @@ -219,145 +255,166 @@ public class TestMetadataNames { /** TEST COLUMN NAME */ @Test - public void testColumnName(){ + public void testColumnName() { TAPColumn column, column2, column3, column4, column5; // NULL - try{ + try { new TAPColumn(null); fail("It should be impossible to create a TAPColumn with a NULL name."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { assertEquals("Missing column name!", npe.getMessage()); } // Empty string (not a single character): - try{ + try { new TAPColumn(""); fail("It should be impossible to create a TAPColumn with an empty name."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { assertEquals("Missing column name!", npe.getMessage()); } // String with only space characters: - try{ + try { new TAPColumn(" "); fail("It should be impossible to create a TAPColumn with a name just composed of space characters."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { assertEquals("Missing column name!", npe.getMessage()); } - // Empty quoted string: - try{ + // Empty quoted string I: + try { new TAPColumn("\"\""); fail("It should be impossible to create a TAPColumn with a empty name even if quoted."); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { + assertEquals("Missing column name!", npe.getMessage()); + } + + // Empty quoted string II: + try { + new TAPColumn("\" \""); + fail("It should be impossible to create a TAPColumn with a empty name even if quoted."); + } catch(NullPointerException npe) { assertEquals("Missing column name!", npe.getMessage()); } // Non quoted names => ADQL_NAME = RAW_NAME = TRIMMED(GIVEN_NAME) - try{ + try { column = new TAPColumn("foo"); - assertEquals("foo", column.getADQLName()); assertEquals("foo", column.getRawName()); + assertEquals(column.getRawName(), column.getADQLName()); + assertFalse(column.isCaseSensitive()); column = new TAPColumn(" foo "); - assertEquals("foo", column.getADQLName()); assertEquals("foo", column.getRawName()); + assertEquals(column.getRawName(), column.getADQLName()); + assertFalse(column.isCaseSensitive()); - // Qualified name => Without a table link, no prefix can be removed! + // Qualified => Not supported column = new TAPColumn("myTable.foo"); - assertEquals("myTable.foo", column.getADQLName()); assertEquals("myTable.foo", column.getRawName()); - - // Qualified name + Table with the WRONG name: + assertEquals(column.getRawName(), column.getADQLName()); + assertFalse(column.isCaseSensitive()); + // ...even with a DB link, still not supported: column.setTable(new TAPTable("Blabla")); - assertEquals("myTable.foo", column.getADQLName()); assertEquals("myTable.foo", column.getRawName()); - - // Qualified name + Table with the RIGHT name: + assertEquals(column.getRawName(), column.getADQLName()); + assertFalse(column.isCaseSensitive()); column.setTable(new TAPTable("myTable")); - assertEquals("foo", column.getADQLName()); assertEquals("myTable.foo", column.getRawName()); + assertEquals(column.getRawName(), column.getADQLName()); + assertFalse(column.isCaseSensitive()); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { npe.printStackTrace(System.err); fail("Unexpected error! The column name is not empty or NULL. (see console for more details)"); } // Quoted names => ADQL_NAME <> RAW_NAME = TRIMMED(GIVEN_NAME) - try{ - column = new TAPColumn("\" \""); - assertEquals(" ", column.getADQLName()); - assertEquals("\" \"", column.getRawName()); - + try { column = new TAPColumn("\"foo\""); assertEquals("foo", column.getADQLName()); + assertTrue(column.isCaseSensitive()); assertEquals("\"foo\"", column.getRawName()); column = new TAPColumn(" \" foo \" "); assertEquals(" foo ", column.getADQLName()); + assertTrue(column.isCaseSensitive()); assertEquals("\" foo \"", column.getRawName()); - // Qualified name => Without a table link, no prefix can be removed! + // Qualified name => not supported column = new TAPColumn("myTable.\"foo\""); - assertEquals("myTable.\"foo\"", column.getADQLName()); assertEquals("myTable.\"foo\"", column.getRawName()); + assertEquals(column.getRawName(), column.getADQLName()); + assertFalse(column.isCaseSensitive()); column2 = new TAPColumn(" \"myTable\". \"foo\""); - assertEquals("\"myTable\". \"foo\"", column2.getADQLName()); assertEquals("\"myTable\". \"foo\"", column2.getRawName()); + assertEquals(column2.getRawName(), column2.getADQLName()); + assertFalse(column2.isCaseSensitive()); column3 = new TAPColumn(" \"myTable\". foo"); - assertEquals("\"myTable\". foo", column3.getADQLName()); assertEquals("\"myTable\". foo", column3.getRawName()); + assertEquals(column3.getRawName(), column3.getADQLName()); + assertFalse(column3.isCaseSensitive()); column4 = new TAPColumn(" mySchema.\"myTable\". foo"); - assertEquals("mySchema.\"myTable\". foo", column4.getADQLName()); assertEquals("mySchema.\"myTable\". foo", column4.getRawName()); + assertEquals(column4.getRawName(), column4.getADQLName()); + assertFalse(column4.isCaseSensitive()); column5 = new TAPColumn(" \"mySchema\".\"myTable\". foo"); - assertEquals("\"mySchema\".\"myTable\". foo", column5.getADQLName()); assertEquals("\"mySchema\".\"myTable\". foo", column5.getRawName()); + assertEquals(column5.getRawName(), column5.getADQLName()); + assertFalse(column5.isCaseSensitive()); - // Qualified name + Table with the WRONG name: + // ...even with a DB link, still not supported: column.setTable(new TAPTable("Blabla")); - assertEquals("myTable.\"foo\"", column.getADQLName()); assertEquals("myTable.\"foo\"", column.getRawName()); + assertEquals(column.getRawName(), column.getADQLName()); + assertFalse(column.isCaseSensitive()); column2.setTable(new TAPTable("myTable")); - assertEquals("\"myTable\". \"foo\"", column2.getADQLName()); assertEquals("\"myTable\". \"foo\"", column2.getRawName()); + assertEquals(column2.getRawName(), column2.getADQLName()); + assertFalse(column2.isCaseSensitive()); column3.setTable(new TAPTable("myTable")); - assertEquals("\"myTable\". foo", column3.getADQLName()); assertEquals("\"myTable\". foo", column3.getRawName()); + assertEquals(column3.getRawName(), column3.getADQLName()); + assertFalse(column3.isCaseSensitive()); TAPTable t = new TAPTable("mySchema.myTable"); t.setSchema(new TAPSchema("mySchema")); column4.setTable(t); - assertEquals("mySchema.\"myTable\". foo", column4.getADQLName()); assertEquals("mySchema.\"myTable\". foo", column4.getRawName()); + assertEquals(column4.getRawName(), column4.getADQLName()); + assertFalse(column4.isCaseSensitive()); t = new TAPTable("\"mySchema\".myTable"); t.setSchema(new TAPSchema("\"mySchema\"")); column5.setTable(t); - assertEquals("\"mySchema\".\"myTable\". foo", column5.getADQLName()); assertEquals("\"mySchema\".\"myTable\". foo", column5.getRawName()); - - // Qualified name + Table with the RIGHT name: + assertEquals(column5.getRawName(), column5.getADQLName()); + assertFalse(column5.isCaseSensitive()); column.setTable(new TAPTable("myTable")); - assertEquals("foo", column.getADQLName()); assertEquals("myTable.\"foo\"", column.getRawName()); + assertEquals(column.getRawName(), column.getADQLName()); + assertFalse(column.isCaseSensitive()); column2.setTable(new TAPTable("\"myTable\"")); - assertEquals("foo", column2.getADQLName()); assertEquals("\"myTable\". \"foo\"", column2.getRawName()); + assertEquals(column2.getRawName(), column2.getADQLName()); + assertFalse(column2.isCaseSensitive()); column3.setTable(new TAPTable("\"myTable\"")); - assertEquals("foo", column3.getADQLName()); assertEquals("\"myTable\". foo", column3.getRawName()); + assertEquals(column3.getRawName(), column3.getADQLName()); + assertFalse(column3.isCaseSensitive()); t = new TAPTable("mySchema.\"myTable\""); t.setSchema(new TAPSchema("mySchema")); column4.setTable(t); - assertEquals("foo", column4.getADQLName()); assertEquals("mySchema.\"myTable\". foo", column4.getRawName()); + assertEquals(column4.getRawName(), column4.getADQLName()); + assertFalse(column4.isCaseSensitive()); t = new TAPTable("\"mySchema\".\"myTable\""); t.setSchema(new TAPSchema("\"mySchema\"")); column5.setTable(t); - assertEquals("foo", column5.getADQLName()); assertEquals("\"mySchema\".\"myTable\". foo", column5.getRawName()); + assertEquals(column5.getRawName(), column5.getADQLName()); + assertFalse(column5.isCaseSensitive()); - }catch(NullPointerException npe){ + } catch(NullPointerException npe) { npe.printStackTrace(System.err); fail("Unexpected error! The column name is not empty or NULL. (see console for more details)"); } @@ -365,7 +422,7 @@ public class TestMetadataNames { /** TEST XML METADATA */ @Test - public void testXMLMetadata(){ + public void testXMLMetadata() { TAPMetadata metadata = new TAPMetadata(); TAPSchema schema = new TAPSchema("blabla"); @@ -383,11 +440,8 @@ public class TestMetadataNames { Iterator<TAPColumn> itCol = table.getColumns(); assertEquals("col1", itCol.next().getADQLName()); assertEquals("col2", itCol.next().getADQLName()); - assertEquals("col3", itCol.next().getADQLName()); + assertEquals("foo.col3", itCol.next().getADQLName()); assertEquals("blabla.foo.col4", itCol.next().getADQLName()); - /* Note for below: - * Only ADQL allows schema.table.column. Here, the table name MUST be exactly - * the same as the one written in the TAP_SCHEMA.tables (so, the trimmed raw name). */ assertEquals("foo2.col5", itCol.next().getADQLName()); table = new TAPTable(" \"foo.bar\""); @@ -400,7 +454,7 @@ public class TestMetadataNames { assertEquals("foo.bar", table.getADQLName()); itCol = table.getColumns(); assertEquals("foo.bar.col1", itCol.next().getADQLName()); - assertEquals("col2", itCol.next().getADQLName()); + assertEquals("\"foo.bar\".col2", itCol.next().getADQLName()); assertEquals("blabla.foo.bar.col3", itCol.next().getADQLName()); assertEquals("blabla.\"foo.bar\".col4", itCol.next().getADQLName()); /* Note for below: @@ -427,7 +481,7 @@ public class TestMetadataNames { /* Note for below: * Same as for the 4th column of the table "foo". */ assertEquals("bloblo.bar.col4", itCol.next().getADQLName()); - assertEquals("col5", itCol.next().getADQLName()); + assertEquals("myCat.bloblo.bar.col5", itCol.next().getADQLName()); metadata.addSchema(schema); @@ -435,12 +489,12 @@ public class TestMetadataNames { assertEquals("Mon Super Schema", schema.getADQLName()); metadata.addSchema(schema); - try{ + try { StringWriter str = new StringWriter(); metadata.write(new PrintWriter(str)); //System.out.println(str.toString()); assertEquals(expectedXMLMetadata, str.toString()); - }catch(Exception ex){ + } catch(Exception ex) { ex.printStackTrace(System.err); fail("Unexpected error when writing TAP metadata into an XML format! (see console for more details)"); }