diff --git a/src/adql/db/DBColumn.java b/src/adql/db/DBColumn.java index c987e062cc161f5b20488c8e4652accc00455463..89f48b6a4f0d763e79463038bc9cdf05b672ea77 100644 --- a/src/adql/db/DBColumn.java +++ b/src/adql/db/DBColumn.java @@ -16,7 +16,7 @@ package adql.db; * 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,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2011-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -29,12 +29,12 @@ package adql.db; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 1.3 (10/2014) + * @version 1.4 (07/2016) */ public interface DBColumn { /** - * Gets the name of this column which must be used in an ADQL query. + * Gets the name of this column (without any prefix and double-quotes). * * @return Its ADQL name. */ diff --git a/src/adql/db/DBTable.java b/src/adql/db/DBTable.java index f72389f4c7b39cc88bd88155ae2673f91a3dd995..9f5c25c1797cc999ada5092d270a81fd35f6cc3b 100644 --- a/src/adql/db/DBTable.java +++ b/src/adql/db/DBTable.java @@ -16,7 +16,7 @@ package adql.db; * 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,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -29,12 +29,12 @@ package adql.db; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 1.3 (09/2014) + * @version 1.4 (07/2016) */ public interface DBTable extends Iterable<DBColumn> { /** - * Gets the name of this table which must be used in an ADQL query. + * Gets the name of this table (without any prefix and double-quotes). * * @return Its ADQL name. */ diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java index 7d50a587d52a61b613df498e152e8e6f31fdf269..281718d90408a6267716d8ffe32e5da0649d14e2 100644 --- a/src/tap/db/JDBCConnection.java +++ b/src/tap/db/JDBCConnection.java @@ -177,7 +177,7 @@ import uws.service.log.UWSLog.LogLevel; * </i></p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (06/2016) + * @version 2.1 (07/2016) * @since 2.0 */ public class JDBCConnection implements DBConnection { @@ -1750,8 +1750,8 @@ public class JDBCConnection implements DBConnection { // add the table entry into the DB: stmt.setString(1, table.getADQLSchemaName()); - if (table.isInitiallyQualified()) - stmt.setString(2, table.getADQLSchemaName() + "." + table.getADQLName()); + if (table instanceof TAPTable) + stmt.setString(2, ((TAPTable)table).getRawName()); else stmt.setString(2, table.getADQLName()); stmt.setString(3, table.getType().toString()); @@ -1819,8 +1819,8 @@ public class JDBCConnection implements DBConnection { appendAllInto(allKeys, col.getTargets()); // add the column entry into the DB: - if (!(col.getTable() instanceof TAPTable) || ((TAPTable)col.getTable()).isInitiallyQualified()) - stmt.setString(1, col.getTable().getADQLSchemaName() + "." + col.getTable().getADQLName()); + if (col.getTable() instanceof TAPTable) + stmt.setString(1, ((TAPTable)col.getTable()).getRawName()); else stmt.setString(1, col.getTable().getADQLName()); stmt.setString(2, col.getADQLName()); @@ -1894,12 +1894,12 @@ public class JDBCConnection implements DBConnection { // add the key entry into KEYS: stmtKeys.setString(1, key.getKeyId()); - if (key.getFromTable().isInitiallyQualified()) - stmtKeys.setString(2, key.getFromTable().getADQLSchemaName() + "." + key.getFromTable().getADQLName()); + if (key.getFromTable() instanceof TAPTable) + stmtKeys.setString(2, ((TAPTable)key.getFromTable()).getRawName()); else stmtKeys.setString(2, key.getFromTable().getADQLName()); - if (key.getTargetTable().isInitiallyQualified()) - stmtKeys.setString(3, key.getTargetTable().getADQLSchemaName() + "." + key.getTargetTable().getADQLName()); + if (key.getTargetTable() instanceof TAPTable) + stmtKeys.setString(3, ((TAPTable)key.getTargetTable()).getRawName()); else stmtKeys.setString(3, key.getTargetTable().getADQLName()); stmtKeys.setString(4, key.getDescription()); diff --git a/src/tap/metadata/TAPColumn.java b/src/tap/metadata/TAPColumn.java index 3365cc60f0053a00baa76e3685ea7759f51d950c..406af805ac95ad9334bea889e8335740a2964d73 100644 --- a/src/tap/metadata/TAPColumn.java +++ b/src/tap/metadata/TAPColumn.java @@ -16,7 +16,7 @@ package tap.metadata; * 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-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -41,10 +41,12 @@ import adql.db.DBType.DBDatatype; * </p> * * <p><i><b>Important note:</b> - * A {@link TAPColumn} object MUST always have a DB name. That's why by default, at the creation - * the DB name is the ADQL name. Once created, it is possible to set the DB name with {@link #setDBName(String)}. + * 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, - * nothing is done and the object keeps its former DB name. + * {@link #getDBName()} returns what {@link #getADQLName()} returns. * </i></p> * * <h3>Set a table</h3> @@ -52,7 +54,7 @@ import adql.db.DBType.DBDatatype; * <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()}. + * will be set automatically and you will be able to get the table with {@link #getTable()}. * </p> * * <h3>Foreign keys</h3> @@ -67,13 +69,18 @@ import adql.db.DBType.DBDatatype; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (07/2015) + * @version 2.1 (07/2016) */ public class TAPColumn implements DBColumn { /** Name that this column 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 and the table prefix if any.</p> + * @since 2.1 */ + private final boolean simplificationNeeded; + /** Name that this column have in the database. * <i>Note: It CAN NOT be NULL. By default, it is the ADQL name.</i> */ private String dbName = null; @@ -139,25 +146,48 @@ public class TAPColumn implements DBColumn { /** * <p>Build a VARCHAR {@link TAPColumn} instance with the given ADQL name.</p> * - * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. - * The datatype is set by default to VARCHAR. + * <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)}. * </i></p> * - * <p><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the column name), - * this prefix will be removed. Only the part after the '.' character will be kept. + * <p><i>Note 2: + * The datatype is set by default to VARCHAR. * </i></p> * - * @param columnName Name that this column MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </li> + * <li> + * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} 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){ - if (columnName == null || columnName.trim().length() == 0) - throw new NullPointerException("Missing column name !"); - int indPrefix = columnName.lastIndexOf('.'); - adqlName = (indPrefix >= 0) ? columnName.substring(indPrefix + 1).trim() : columnName.trim(); - dbName = adqlName; + 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); + + if (getADQLName().length() == 0) + throw new NullPointerException("Missing column name!"); + + dbName = null; + lstTargets = new ArrayList<TAPForeignKey>(1); lstSources = new ArrayList<TAPForeignKey>(1); } @@ -165,27 +195,40 @@ public class TAPColumn implements DBColumn { /** * <p>Build a {@link TAPColumn} instance with the given ADQL name and datatype.</p> * - * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. - * </i></p> - * - * <p><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the column name), - * this prefix will be removed. Only the part after the '.' character will be kept. + * <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)}. * </i></p> * - * <p><i>Note: - * The datatype is set by calling the function {@link #setDatatype(DBType)} which does not do - * anything if the given datatype is NULL. + * <p><i>Note 2: + * The datatype is set by default to VARCHAR. * </i></p> * - * @param columnName Name that this column MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </li> + * <li> + * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} 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). + * * @see #setDatatype(DBType) */ - public TAPColumn(String columnName, DBType type){ + public TAPColumn(String columnName, DBType type) throws NullPointerException{ this(columnName); setDatatype(type); } @@ -193,46 +236,78 @@ public class TAPColumn implements DBColumn { /** * <p>Build a VARCHAR {@link TAPColumn} instance with the given ADQL name and description.</p> * - * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. + * <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)}. * </i></p> * - * <p><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the column name), - * this prefix will be removed. Only the part after the '.' character will be kept. + * <p><i>Note 2: + * The datatype is set by default to VARCHAR. * </i></p> * - * @param columnName Name that this column MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </li> + * <li> + * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} 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){ + 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: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. - * </i></p> - * - * <p><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the column name), - * this prefix will be removed. Only the part after the '.' character will be kept. + * <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)}. * </i></p> * - * <p><i>Note: - * The datatype is set by calling the function {@link #setDatatype(DBType)} which does do + * <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. * </i></p> * - * @param columnName Name that this column MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </li> + * <li> + * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} 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){ + public TAPColumn(String columnName, DBType type, String description) throws NullPointerException{ this(columnName, type); this.description = description; } @@ -240,48 +315,80 @@ public class TAPColumn implements DBColumn { /** * <p>Build a VARCHAR {@link TAPColumn} instance with the given ADQL name, description and unit.</p> * - * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. + * <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)}. * </i></p> * - * <p><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the column name), - * this prefix will be removed. Only the part after the '.' character will be kept. + * <p><i>Note 2: + * The datatype is set by default to VARCHAR. * </i></p> * - * @param columnName Name that this column MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </li> + * <li> + * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} 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){ + 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: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. - * </i></p> - * - * <p><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the column name), - * this prefix will be removed. Only the part after the '.' character will be kept. + * <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)}. * </i></p> * - * <p><i>Note: - * The datatype is set by calling the function {@link #setDatatype(DBType)} which does do + * <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. * </i></p> * - * @param columnName Name that this column MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </li> + * <li> + * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} 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){ + public TAPColumn(String columnName, DBType type, String description, String unit) throws NullPointerException{ this(columnName, type, description); this.unit = unit; } @@ -289,57 +396,84 @@ public class TAPColumn implements DBColumn { /** * <p>Build a VARCHAR {@link TAPColumn} instance with the given fields.</p> * - * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. - * </i></p> - * - * <p><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the column name), - * this prefix will be removed. Only the part after the '.' character will be kept. + * <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)}. * </i></p> * - * <p><i>Note: - * The datatype is set by calling the function {@link #setDatatype(DBType)} which does do - * anything if the given datatype is NULL. + * <p><i>Note 2: + * The datatype is set by default to VARCHAR. * </i></p> * - * @param columnName Name that this column MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </li> + * <li> + * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} 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 ucd UCD describing the scientific content of this column. * @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). */ - public TAPColumn(String columnName, String description, String unit, String ucd, String utype){ + 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: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. - * </i></p> - * - * <p><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the column name), - * this prefix will be removed. Only the part after the '.' character will be kept. + * <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)}. * </i></p> * - * <p><i>Note: - * The datatype is set by calling the function {@link #setDatatype(DBType)} which does do + * <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. * </i></p> * - * @param columnName Name that this column MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </li> + * <li> + * Double quotes may surround the single column name. They will be removed by {@link #getADQLName()} 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 ucd UCD describing the scientific content of this column. * @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). */ - public TAPColumn(String columnName, DBType type, String description, String unit, String ucd, String utype){ + 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; @@ -359,12 +493,37 @@ public class TAPColumn implements DBColumn { @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. + * + * @return Get the original ADQL name. + * + * @since 2.1 + */ + public final String getRawName(){ return adqlName; } @Override public final String getDBName(){ - return dbName; + return (dbName == null) ? getADQLName() : dbName; } /** @@ -380,6 +539,8 @@ public class TAPColumn implements DBColumn { name = (name != null) ? name.trim() : name; if (name != null && name.length() > 0) dbName = name; + else + dbName = null; } @Override @@ -614,7 +775,7 @@ public class TAPColumn implements DBColumn { * * <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. + * that owns this column or that is part of the foreign key. * </i></p> * * @param key A foreign key. @@ -652,7 +813,7 @@ public class TAPColumn implements DBColumn { * * <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. + * 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. @@ -668,7 +829,7 @@ public class TAPColumn implements DBColumn { * * <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. + * that owns this column or that is part of the foreign key. * </i></p> */ protected void removeAllTargets(){ @@ -684,7 +845,7 @@ public class TAPColumn implements DBColumn { * * <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. + * that owns this column or that is part of the foreign key. * </i></p> * * @param key A foreign key. @@ -722,7 +883,7 @@ public class TAPColumn implements DBColumn { * * <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. + * 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. @@ -737,7 +898,7 @@ public class TAPColumn implements DBColumn { * * <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. + * that owns this column or that is part of the foreign key. * </i></p> */ protected void removeAllSources(){ @@ -802,7 +963,7 @@ public class TAPColumn implements DBColumn { @Override public String toString(){ - return ((table != null) ? (table.getADQLName() + ".") : "") + adqlName; + return ((table != null) ? (((table.getADQLSchemaName() != null) ? table.getADQLSchemaName() : "") + table.getADQLName() + ".") : "") + getADQLName(); } } diff --git a/src/tap/metadata/TAPMetadata.java b/src/tap/metadata/TAPMetadata.java index 8173e3f2a8cd145df4d0c9882b760a7bf9a3ba49..d0d10f91697fc17319c63cd11ab21bc0096fec68 100644 --- a/src/tap/metadata/TAPMetadata.java +++ b/src/tap/metadata/TAPMetadata.java @@ -16,7 +16,7 @@ package tap.metadata; * 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-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -33,6 +33,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import adql.db.DBTable; +import adql.db.DBType; +import adql.db.DBType.DBDatatype; import tap.metadata.TAPTable.TableType; import tap.resource.Capabilities; import tap.resource.TAPResource; @@ -40,9 +43,6 @@ import tap.resource.VOSIResource; import uk.ac.starlink.votable.VOSerializer; import uws.ClientAbortException; import uws.UWSToolBox; -import adql.db.DBTable; -import adql.db.DBType; -import adql.db.DBType.DBDatatype; /** * <p>Let listing all schemas, tables and columns available in a TAP service. @@ -64,7 +64,7 @@ import adql.db.DBType.DBDatatype; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (10/2015) + * @version 2.1 (07/2016) */ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResource { @@ -99,7 +99,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour * Otherwise, if you want customize just some part of this schema, you can also use the function * {@link #getStdTable(STDTable)} to get just the standard definition of some of its tables, either * to customize them or to merely get them and keep them like they are. - * </i></p> + * </i></p> */ public TAPMetadata(){ schemas = new LinkedHashMap<String,TAPSchema>(); @@ -518,7 +518,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour final String prefix = "\t\t"; writer.println("\t<schema>"); - writeAtt(prefix, "name", s.getADQLName(), false, writer); + writeAtt(prefix, "name", s.getRawName(), false, writer); writeAtt(prefix, "title", s.getTitle(), true, writer); writeAtt(prefix, "description", s.getDescription(), true, writer); writeAtt(prefix, "utype", s.getUtype(), true, writer); @@ -587,10 +587,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour } writer.println(">"); - if (t.isInitiallyQualified()) - writeAtt(prefix, "name", t.getADQLSchemaName() + "." + t.getADQLName(), false, writer); - else - writeAtt(prefix, "name", t.getADQLName(), false, writer); + writeAtt(prefix, "name", t.getRawName(), false, writer); writeAtt(prefix, "title", t.getTitle(), true, writer); writeAtt(prefix, "description", t.getDescription(), true, writer); writeAtt(prefix, "utype", t.getUtype(), true, writer); @@ -643,7 +640,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour writer.print(" std=\"true\""); writer.println(">"); - writeAtt(prefix, "name", c.getADQLName(), false, writer); + writeAtt(prefix, "name", c.getRawName(), false, writer); writeAtt(prefix, "description", c.getDescription(), true, writer); writeAtt(prefix, "unit", c.getUnit(), true, writer); writeAtt(prefix, "ucd", c.getUcd(), true, writer); @@ -701,7 +698,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour writer.println("\t\t\t<foreignKey>"); - writeAtt(prefix, "targetTable", fk.getTargetTable().getFullName(), false, writer); + writeAtt(prefix, "targetTable", fk.getTargetTable().getRawName(), false, writer); writeAtt(prefix, "description", fk.getDescription(), true, writer); writeAtt(prefix, "utype", fk.getUtype(), true, writer); @@ -724,8 +721,8 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour * @param prefix Prefix of the XML node. (generally, space characters) * @param attributeName Name of the metadata attribute to write (= Name of the XML node). * @param attributeValue Value of the metadata attribute (= Value of the XML node). - * @param isOptionalAttr <i>true</i> if the attribute to write is optional (in this case, if the value is NULL or an empty string, the whole attribute item won't be written), - * <i>false</i> otherwise (here, if the value is NULL or an empty string, the XML item will be written with an empty string as value). + * @param isOptionalAttr <i>true</i> if the attribute to write is optional (in this case, if the value is NULL or an empty string, the whole attribute item won't be written), + * <i>false</i> otherwise (here, if the value is NULL or an empty string, the XML item will be written with an empty string as value). * @param writer Output in which the XML node must be written. */ protected final void writeAtt(String prefix, String attributeName, String attributeValue, boolean isOptionalAttr, PrintWriter writer){ @@ -763,9 +760,9 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour tap_schema.setDBName(null); for(STDTable t : STDTable.values()){ TAPTable table = getStdTable(t); + tap_schema.addTable(table); if (!isSchemaSupported) table.setDBName(STDSchema.TAPSCHEMA.label + "_" + table.getADQLName()); - tap_schema.addTable(table); } return tap_schema; } @@ -792,16 +789,14 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour switch(tableId){ case SCHEMAS: - TAPTable schemas = new TAPTable(STDTable.SCHEMAS.toString(), TableType.table, "List of schemas published in this TAP service.", null); - schemas.setInitiallyQualifed(true); + TAPTable schemas = new TAPTable(STDSchema.TAPSCHEMA + "." + STDTable.SCHEMAS, TableType.table, "List of schemas published in this TAP service.", null); schemas.addColumn("schema_name", new DBType(DBDatatype.VARCHAR), "schema name, possibly qualified", null, null, null, true, true, true); schemas.addColumn("description", new DBType(DBDatatype.VARCHAR), "brief description of schema", null, null, null, false, false, true); schemas.addColumn("utype", new DBType(DBDatatype.VARCHAR), "UTYPE if schema corresponds to a data model", null, null, null, false, false, true); return schemas; case TABLES: - TAPTable tables = new TAPTable(STDTable.TABLES.toString(), TableType.table, "List of tables published in this TAP service.", null); - tables.setInitiallyQualifed(true); + TAPTable tables = new TAPTable(STDSchema.TAPSCHEMA + "." + STDTable.TABLES, TableType.table, "List of tables published in this TAP service.", null); tables.addColumn("schema_name", new DBType(DBDatatype.VARCHAR), "the schema name from TAP_SCHEMA.schemas", null, null, null, true, true, true); tables.addColumn("table_name", new DBType(DBDatatype.VARCHAR), "table name as it should be used in queries", null, null, null, true, true, true); tables.addColumn("table_type", new DBType(DBDatatype.VARCHAR), "one of: table, view", null, null, null, false, false, true); @@ -810,8 +805,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour return tables; case COLUMNS: - TAPTable columns = new TAPTable(STDTable.COLUMNS.toString(), TableType.table, "List of columns of all tables listed in TAP_SCHEMA.TABLES and published in this TAP service.", null); - columns.setInitiallyQualifed(true); + TAPTable columns = new TAPTable(STDSchema.TAPSCHEMA + "." + STDTable.COLUMNS, TableType.table, "List of columns of all tables listed in TAP_SCHEMA.TABLES and published in this TAP service.", null); columns.addColumn("table_name", new DBType(DBDatatype.VARCHAR), "table name from TAP_SCHEMA.tables", null, null, null, true, true, true); columns.addColumn("column_name", new DBType(DBDatatype.VARCHAR), "column name", null, null, null, true, true, true); columns.addColumn("description", new DBType(DBDatatype.VARCHAR), "brief description of column", null, null, null, false, false, true); @@ -826,8 +820,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour return columns; case KEYS: - TAPTable keys = new TAPTable(STDTable.KEYS.toString(), TableType.table, "List all foreign keys but provides just the tables linked by the foreign key. To know which columns of these tables are linked, see in TAP_SCHEMA.key_columns using the key_id.", null); - keys.setInitiallyQualifed(true); + TAPTable keys = new TAPTable(STDSchema.TAPSCHEMA + "." + STDTable.KEYS, TableType.table, "List all foreign keys but provides just the tables linked by the foreign key. To know which columns of these tables are linked, see in TAP_SCHEMA.key_columns using the key_id.", null); keys.addColumn("key_id", new DBType(DBDatatype.VARCHAR), "unique key identifier", null, null, null, true, true, true); keys.addColumn("from_table", new DBType(DBDatatype.VARCHAR), "fully qualified table name", null, null, null, false, false, true); keys.addColumn("target_table", new DBType(DBDatatype.VARCHAR), "fully qualified table name", null, null, null, false, false, true); @@ -836,8 +829,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour return keys; case KEY_COLUMNS: - TAPTable key_columns = new TAPTable(STDTable.KEY_COLUMNS.toString(), TableType.table, "List all foreign keys but provides just the columns linked by the foreign key. To know the table of these columns, see in TAP_SCHEMA.keys using the key_id.", null); - key_columns.setInitiallyQualifed(true); + TAPTable key_columns = new TAPTable(STDSchema.TAPSCHEMA + "." + STDTable.KEY_COLUMNS, TableType.table, "List all foreign keys but provides just the columns linked by the foreign key. To know the table of these columns, see in TAP_SCHEMA.keys using the key_id.", null); key_columns.addColumn("key_id", new DBType(DBDatatype.VARCHAR), "unique key identifier", null, null, null, true, true, true); key_columns.addColumn("from_column", new DBType(DBDatatype.VARCHAR), "key column name in the from_table", null, null, null, false, false, true); key_columns.addColumn("target_column", new DBType(DBDatatype.VARCHAR), "key column name in the target_table", null, null, null, false, false, true); @@ -853,11 +845,11 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour * * <p><i>Note: * This function is case sensitive. Indeed TAP_SCHEMA tables are defined by the TAP standard by a given case. - * Thus, this case is expected here. + * Thus, this case is expected here. * </i></p> * * @param tableName Unqualified table name. - * + * * @return The corresponding {@link STDTable} or NULL if the given table is not part of the TAP standard. * * @since 2.0 diff --git a/src/tap/metadata/TAPSchema.java b/src/tap/metadata/TAPSchema.java index 182c421cf71a92f22adb39f972b2f79d0079b6db..cac404920cc42e19b4a24c59498f6ea13eebfac7 100644 --- a/src/tap/metadata/TAPSchema.java +++ b/src/tap/metadata/TAPSchema.java @@ -16,7 +16,7 @@ package tap.metadata; * 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,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS) * Astronomisches Rechen Institut (ARI) */ @@ -39,20 +39,27 @@ import tap.metadata.TAPTable.TableType; * * <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 ADQL name. 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. + * 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.0 (02/2015) + * @version 2.1 (07/2016) */ 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; + /** Name that this schema have in the database. - * <i>Note: It MAY be NULL. By default, it is the ADQL name.</i> */ + * <i>Note: NULL by default. When NULL, {@link #getDBName()} returns exactly what {@link #getADQLName()} returns.</i> */ private String dbName = null; /** Descriptive, human-interpretable name of the schema. @@ -80,23 +87,42 @@ public class TAPSchema implements Iterable<TAPTable> { * <p>Build a {@link TAPSchema} instance with the given ADQL name.</p> * * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. + * 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><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the schema name), - * this prefix will be removed. Only the part after the '.' character will be kept. - * </i></p> - * - * @param schemaName Name that this schema MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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> + * </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()}. + * </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). */ - public TAPSchema(String schemaName){ - if (schemaName == null || schemaName.trim().length() == 0) + public TAPSchema(String schemaName) throws NullPointerException{ + if (schemaName == null) throw new NullPointerException("Missing schema name!"); - int indPrefix = schemaName.lastIndexOf('.'); - adqlName = (indPrefix >= 0) ? schemaName.substring(indPrefix + 1).trim() : schemaName.trim(); - dbName = adqlName; + + adqlName = schemaName.trim(); + simplificationNeeded = adqlName.matches("\"[^\"]*\""); + + if (getADQLName().length() == 0) + throw new NullPointerException("Missing schema name!"); + + dbName = getADQLName(); + tables = new LinkedHashMap<String,TAPTable>(); } @@ -104,19 +130,32 @@ public class TAPSchema implements Iterable<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 with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. + * 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><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the schema name), - * this prefix will be removed. Only the part after the '.' character will be kept. - * </i></p> - * - * @param schemaName Name that this schema MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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> + * </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()}. + * </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 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). */ - public TAPSchema(String schemaName, String description){ + public TAPSchema(String schemaName, String description) throws NullPointerException{ this(schemaName, description, null); } @@ -124,20 +163,33 @@ public class TAPSchema implements Iterable<TAPTable> { * <p>Build a {@link TAPSchema} instance with the given ADQL name, description and UType.</p> * * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. + * 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><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the schema name), - * this prefix will be removed. Only the part after the '.' character will be kept. - * </i></p> - * - * @param schemaName Name that this schema MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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> + * </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()}. + * </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 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). */ - public TAPSchema(String schemaName, String description, String utype){ + public TAPSchema(String schemaName, String description, String utype) throws NullPointerException{ this(schemaName); this.description = description; this.utype = utype; @@ -156,11 +208,26 @@ public class TAPSchema implements Iterable<TAPTable> { } /** - * Get the name this schema MUST have in ADQL queries. + * 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> */ public final String getADQLName(){ + return simplificationNeeded ? adqlName.substring(1, adqlName.length() - 1) : adqlName; + } + + /** + * Get the full ADQL name of this schema, as it has been provided at initialization. + * + * @return Get the original ADQL name. + * + * @since 2.1 + */ + public final String getRawName(){ return adqlName; } @@ -176,6 +243,17 @@ public class TAPSchema implements Iterable<TAPTable> { /** * Set the name this schema MUST have in the database. * + * <p>Notes:</p> + * <ul> + * <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. + * </li> + * </ul> + * * @param name Its new DB name. <i>MAY be NULL</i> */ public final void setDBName(String name){ @@ -282,8 +360,8 @@ public class TAPSchema implements Iterable<TAPTable> { */ public final void addTable(TAPTable newTable){ if (newTable != null && newTable.getADQLName() != null){ - tables.put(newTable.getADQLName(), newTable); newTable.setSchema(this); + tables.put(newTable.getADQLName(), newTable); } } @@ -416,7 +494,7 @@ public class TAPSchema implements Iterable<TAPTable> { * </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. */ @@ -459,4 +537,9 @@ public class TAPSchema implements Iterable<TAPTable> { return tables.values().iterator(); } + @Override + public String toString(){ + return getADQLName(); + } + } diff --git a/src/tap/metadata/TAPTable.java b/src/tap/metadata/TAPTable.java index f3030d0aea914e9b6340d2fec5407db949a5cf21..905394cdf1c3f7fce2fb92c750cb5f77b220249b 100644 --- a/src/tap/metadata/TAPTable.java +++ b/src/tap/metadata/TAPTable.java @@ -16,7 +16,7 @@ package tap.metadata; * 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,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS) * Astronomisches Rechen Institut (ARI) */ @@ -27,10 +27,10 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; -import tap.TAPException; import adql.db.DBColumn; import adql.db.DBTable; import adql.db.DBType; +import tap.TAPException; /** * <p>Represent a table as described by the IVOA standard in the TAP protocol definition.</p> @@ -43,14 +43,16 @@ import adql.db.DBType; * </p> * * <p><i><b>Important note:</b> - * A {@link TAPTable} object MUST always have a DB name. That's why by default, at the creation - * the DB name is the ADQL name. Once created, it is possible to set the DB name with {@link #setDBName(String)}. + * 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, - * nothing is done and the object keeps its former DB name. + * {@link #getDBName()} returns what {@link #getADQLName()} returns. * </i></p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (02/2015) + * @version 2.1 (07/2016) */ public class TAPTable implements DBTable { @@ -70,13 +72,20 @@ public class TAPTable implements DBTable { /** Name that this table 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 and the schema prefix if any.</p> + * @since 2.1 */ + private final boolean simplificationNeeded; + /** <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 */ + * @since 2.0 + * @deprecated See {@link #simplificationNeeded}, {@link #getRawName()} and {@link #getADQLName()}. */ + @Deprecated private boolean isInitiallyQualified; /** Name that this table have in the database. - * <i>Note: It CAN'T be NULL. By default, it is the ADQL name.</i> */ + * <i>Note: If NULL, {@link #getDBName()} returns what {@link #getADQLName()} returns.</i> */ private String dbName = null; /** The schema which owns this table. @@ -117,26 +126,49 @@ public class TAPTable implements DBTable { /** * <p>Build a {@link TAPTable} instance with the given ADQL name.</p> * - * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. - * The table type is set by default to "table". + * <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)}. * </i></p> * - * <p><i>Note: - * If the given ADQL name is prefixed (= it has some text separated by a '.' before the table name), - * this prefix will be removed. Only the part after the '.' character will be kept. + * <p><i>Note 2: + * The table type is set by default to "table". * </i></p> * - * @param tableName Name that this table MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </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()}. + * </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). */ - public TAPTable(String tableName){ - if (tableName == null || tableName.trim().length() == 0) - throw new NullPointerException("Missing table name !"); - int indPrefix = tableName.lastIndexOf('.'); - adqlName = (indPrefix >= 0) ? tableName.substring(indPrefix + 1).trim() : tableName.trim(); - isInitiallyQualified = (indPrefix >= 0); - dbName = adqlName; + public TAPTable(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); + + if (getADQLName().length() == 0) + throw new NullPointerException("Missing table name!"); + + dbName = null; + columns = new LinkedHashMap<String,TAPColumn>(); foreignKeys = new ArrayList<TAPForeignKey>(); } @@ -144,22 +176,40 @@ public class TAPTable implements DBTable { /** * <p>Build a {@link TAPTable} instance with the given ADQL name and table type.</p> * - * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. + * <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)}. * </i></p> * - * <p><i>Note: - * The table type is set by calling the function {@link #setType(TableType)} which does not do - * anything if the given table type is NULL. + * <p><i>Note 2: + * The table type is set by default to "table". * </i></p> * - * @param tableName Name that this table MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </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()}. + * </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). + * * @see #setType(TableType) */ - public TAPTable(String tableName, TableType tableType){ + public TAPTable(String tableName, TableType tableType) throws NullPointerException{ this(tableName); setType(tableType); } @@ -167,24 +217,42 @@ public class TAPTable implements DBTable { /** * <p>Build a {@link TAPTable} instance with the given ADQL name, table type, description and UType.</p> * - * <p><i>Note: - * The DB name is set by default with the ADQL name. To set the DB name, - * you MUST call then {@link #setDBName(String)}. + * <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)}. * </i></p> * - * <p><i>Note: - * The table type is set by calling the function {@link #setType(TableType)} which does not do - * anything if the given table type is NULL. + * <p><i>Note 2: + * The table type is set by default to "table". * </i></p> * - * @param tableName Name that this table MUST have in ADQL queries. <i>CAN'T be NULL ; this name can never be changed after.</i> + * <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. + * </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()}. + * </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) */ - public TAPTable(String tableName, TableType tableType, String description, String utype){ + public TAPTable(String tableName, TableType tableType, String description, String utype) throws NullPointerException{ this(tableName, tableType); this.description = description; this.utype = utype; @@ -206,7 +274,7 @@ public class TAPTable implements DBTable { */ public final String getFullName(){ if (schema != null) - return schema.getADQLName() + "." + adqlName; + return schema.getADQLName() + "." + getADQLName(); else return adqlName; } @@ -225,6 +293,28 @@ public class TAPTable implements DBTable { @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; + } + + /** + * 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; } @@ -239,7 +329,10 @@ public class TAPTable implements DBTable { * @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()}. */ + @Deprecated public final boolean isInitiallyQualified(){ return isInitiallyQualified; } @@ -255,21 +348,24 @@ public class TAPTable implements DBTable { * <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 String getDBName(){ - return dbName; + 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, nothing is done ; the DB name keeps is former value. + * 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. @@ -278,6 +374,8 @@ public class TAPTable implements DBTable { name = (name != null) ? name.trim() : name; if (name != null && name.length() > 0) dbName = name; + else + dbName = null; } @Override @@ -314,7 +412,7 @@ public class TAPTable implements DBTable { * * <p><i><b>Warning:</b> * For consistency reasons, this function SHOULD be called only by the {@link TAPSchema} - * that owns this table. + * that owns this table. * </i></p> * * <p><i><b>Important note:</b> @@ -452,8 +550,8 @@ public class TAPTable implements DBTable { */ public final void addColumn(final TAPColumn newColumn){ if (newColumn != null && newColumn.getADQLName() != null){ - columns.put(newColumn.getADQLName(), newColumn); newColumn.setTable(this); + columns.put(newColumn.getADQLName(), newColumn); } } @@ -668,7 +766,7 @@ public class TAPTable implements DBTable { * * @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){ @@ -764,9 +862,9 @@ public class TAPTable implements DBTable { TAPTable targetTable = key.getTargetTable(); for(Map.Entry<String,String> relation : key){ if (!hasColumn(relation.getKey())) - throw new TAPException(errorMsgPrefix + "the source column \"" + relation.getKey() + "\" doesn't exist in \"" + getName() + "\" !"); + 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.getName() + "\" !"); + 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); @@ -929,7 +1027,7 @@ public class TAPTable implements DBTable { @Override public String toString(){ - return ((schema != null) ? (schema.getADQLName() + ".") : "") + adqlName; + return ((schema != null) ? (schema.getADQLName() + ".") : "") + getADQLName(); } @Override @@ -939,7 +1037,7 @@ public class TAPTable implements DBTable { copy.setSchema(schema); Collection<TAPColumn> collColumns = columns.values(); for(TAPColumn col : collColumns) - copy.addColumn((TAPColumn)col.copy(col.getDBName(), col.getADQLName(), null)); + copy.addColumn((TAPColumn)col.copy(col.getDBName(), col.getADQLName(), copy)); copy.setDescription(description); copy.setOtherData(otherData); copy.setType(type); diff --git a/test/tap/metadata/TestMetadataNames.java b/test/tap/metadata/TestMetadataNames.java new file mode 100644 index 0000000000000000000000000000000000000000..1e95f5c46492a02aad6bb60ce7690c41f26bba2c --- /dev/null +++ b/test/tap/metadata/TestMetadataNames.java @@ -0,0 +1,452 @@ +package tap.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Iterator; + +import org.junit.Test; + +/** + * This class aims to ensure schema, table and column names are interpreted correctly + * when quoted and/or qualified (with a schema or table prefix). + */ +public class TestMetadataNames { + + /** TEST SCHEMA NAME */ + @Test + public void testSchemaName(){ + TAPSchema schema; + + // NULL + try{ + new TAPSchema(null); + fail("It should be impossible to create a TAPSchema with a NULL name."); + }catch(NullPointerException npe){ + assertEquals("Missing schema name!", npe.getMessage()); + } + + // Empty string (not a single character): + try{ + new TAPSchema(""); + fail("It should be impossible to create a TAPSchema with an empty name."); + }catch(NullPointerException npe){ + assertEquals("Missing schema name!", npe.getMessage()); + } + + // String with only space characters: + try{ + new TAPSchema(" "); + fail("It should be impossible to create a TAPSchema with a name just composed of space characters."); + }catch(NullPointerException npe){ + assertEquals("Missing schema name!", npe.getMessage()); + } + + // Empty quoted string: + 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{ + schema = new TAPSchema("foo"); + assertEquals("foo", schema.getADQLName()); + assertEquals("foo", schema.getRawName()); + + schema = new TAPSchema(" foo "); + assertEquals("foo", schema.getADQLName()); + assertEquals("foo", schema.getRawName()); + + // Qualified name => Not supported as a catalog name! + schema = new TAPSchema("myCat.foo"); + assertEquals("myCat.foo", schema.getADQLName()); + assertEquals("myCat.foo", schema.getRawName()); + + }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()); + + schema = new TAPSchema("\"foo\""); + assertEquals("foo", schema.getADQLName()); + assertEquals("\"foo\"", schema.getRawName()); + + schema = new TAPSchema(" \" foo \" "); + assertEquals(" foo ", schema.getADQLName()); + assertEquals("\" foo \"", schema.getRawName()); + + // Qualified name => Not supported as a catalog name! + schema = new TAPSchema("myCat.\"foo\""); + assertEquals("myCat.\"foo\"", schema.getADQLName()); + assertEquals("myCat.\"foo\"", schema.getRawName()); + + }catch(NullPointerException npe){ + npe.printStackTrace(System.err); + fail("Unexpected error! The schema name is not empty or NULL. (see console for more details)"); + } + } + + /** TEST TABLE NAME */ + @Test + public void testTableName(){ + TAPTable table, table2, table3; + + // NULL + try{ + new TAPTable(null); + fail("It should be impossible to create a TAPTable with a NULL name."); + }catch(NullPointerException npe){ + assertEquals("Missing table name!", npe.getMessage()); + } + + // Empty string (not a single character): + try{ + new TAPTable(""); + fail("It should be impossible to create a TAPTable with an empty name."); + }catch(NullPointerException npe){ + assertEquals("Missing table name!", npe.getMessage()); + } + + // String with only space characters: + try{ + new TAPTable(" "); + fail("It should be impossible to create a TAPTable with a name just composed of space characters."); + }catch(NullPointerException npe){ + assertEquals("Missing table name!", npe.getMessage()); + } + + // Empty quoted string: + 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{ + table = new TAPTable("foo"); + assertEquals("foo", table.getADQLName()); + assertEquals("foo", table.getRawName()); + + table = new TAPTable(" foo "); + assertEquals("foo", table.getADQLName()); + assertEquals("foo", table.getRawName()); + + // Qualified name => Without a schema link, no prefix can be removed! + table = new TAPTable("mySchema.foo"); + assertEquals("mySchema.foo", table.getADQLName()); + assertEquals("mySchema.foo", table.getRawName()); + + // Qualified name + Schema with the WRONG name: + table.setSchema(new TAPSchema("Blabla")); + assertEquals("mySchema.foo", table.getADQLName()); + assertEquals("mySchema.foo", table.getRawName()); + + // Qualified name + Schema with the RIGHT name: + table.setSchema(new TAPSchema("mySchema")); + assertEquals("foo", table.getADQLName()); + assertEquals("mySchema.foo", table.getRawName()); + + }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()); + + table = new TAPTable("\"foo\""); + assertEquals("foo", table.getADQLName()); + assertEquals("\"foo\"", table.getRawName()); + + table = new TAPTable(" \" foo \" "); + assertEquals(" foo ", table.getADQLName()); + assertEquals("\" foo \"", table.getRawName()); + + // Qualified name => Without a schema link, no prefix can be removed! + table = new TAPTable("mySchema.\"foo\""); + assertEquals("mySchema.\"foo\"", table.getADQLName()); + assertEquals("mySchema.\"foo\"", table.getRawName()); + table2 = new TAPTable(" \"mySchema\". \"foo\""); + assertEquals("\"mySchema\". \"foo\"", table2.getADQLName()); + assertEquals("\"mySchema\". \"foo\"", table2.getRawName()); + table3 = new TAPTable(" \"mySchema\". foo"); + assertEquals("\"mySchema\". foo", table3.getADQLName()); + assertEquals("\"mySchema\". foo", table3.getRawName()); + + // 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()); + + // Qualified name + Schema with the RIGHT name: + table.setSchema(new TAPSchema("mySchema")); + assertEquals("foo", table.getADQLName()); + assertEquals("mySchema.\"foo\"", table.getRawName()); + table2.setSchema(new TAPSchema("\"mySchema\"")); + assertEquals("foo", table2.getADQLName()); + assertEquals("\"mySchema\". \"foo\"", table2.getRawName()); + table3.setSchema(new TAPSchema("\"mySchema\"")); + assertEquals("foo", table3.getADQLName()); + assertEquals("\"mySchema\". foo", table3.getRawName()); + + }catch(NullPointerException npe){ + npe.printStackTrace(System.err); + fail("Unexpected error! The table name is not empty or NULL. (see console for more details)"); + } + } + + /** TEST COLUMN NAME */ + @Test + public void testColumnName(){ + TAPColumn column, column2, column3, column4, column5; + + // NULL + try{ + new TAPColumn(null); + fail("It should be impossible to create a TAPColumn with a NULL name."); + }catch(NullPointerException npe){ + assertEquals("Missing column name!", npe.getMessage()); + } + + // Empty string (not a single character): + try{ + new TAPColumn(""); + fail("It should be impossible to create a TAPColumn with an empty name."); + }catch(NullPointerException npe){ + assertEquals("Missing column name!", npe.getMessage()); + } + + // String with only space characters: + try{ + new TAPColumn(" "); + fail("It should be impossible to create a TAPColumn with a name just composed of space characters."); + }catch(NullPointerException npe){ + assertEquals("Missing column name!", npe.getMessage()); + } + + // Empty quoted string: + 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{ + column = new TAPColumn("foo"); + assertEquals("foo", column.getADQLName()); + assertEquals("foo", column.getRawName()); + + column = new TAPColumn(" foo "); + assertEquals("foo", column.getADQLName()); + assertEquals("foo", column.getRawName()); + + // Qualified name => Without a table link, no prefix can be removed! + column = new TAPColumn("myTable.foo"); + assertEquals("myTable.foo", column.getADQLName()); + assertEquals("myTable.foo", column.getRawName()); + + // Qualified name + Table with the WRONG name: + column.setTable(new TAPTable("Blabla")); + assertEquals("myTable.foo", column.getADQLName()); + assertEquals("myTable.foo", column.getRawName()); + + // Qualified name + Table with the RIGHT name: + column.setTable(new TAPTable("myTable")); + assertEquals("foo", column.getADQLName()); + assertEquals("myTable.foo", column.getRawName()); + + }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()); + + column = new TAPColumn("\"foo\""); + assertEquals("foo", column.getADQLName()); + assertEquals("\"foo\"", column.getRawName()); + + column = new TAPColumn(" \" foo \" "); + assertEquals(" foo ", column.getADQLName()); + assertEquals("\" foo \"", column.getRawName()); + + // Qualified name => Without a table link, no prefix can be removed! + column = new TAPColumn("myTable.\"foo\""); + assertEquals("myTable.\"foo\"", column.getADQLName()); + assertEquals("myTable.\"foo\"", column.getRawName()); + column2 = new TAPColumn(" \"myTable\". \"foo\""); + assertEquals("\"myTable\". \"foo\"", column2.getADQLName()); + assertEquals("\"myTable\". \"foo\"", column2.getRawName()); + column3 = new TAPColumn(" \"myTable\". foo"); + assertEquals("\"myTable\". foo", column3.getADQLName()); + assertEquals("\"myTable\". foo", column3.getRawName()); + column4 = new TAPColumn(" mySchema.\"myTable\". foo"); + assertEquals("mySchema.\"myTable\". foo", column4.getADQLName()); + assertEquals("mySchema.\"myTable\". foo", column4.getRawName()); + column5 = new TAPColumn(" \"mySchema\".\"myTable\". foo"); + assertEquals("\"mySchema\".\"myTable\". foo", column5.getADQLName()); + assertEquals("\"mySchema\".\"myTable\". foo", column5.getRawName()); + + // Qualified name + Table with the WRONG name: + column.setTable(new TAPTable("Blabla")); + assertEquals("myTable.\"foo\"", column.getADQLName()); + assertEquals("myTable.\"foo\"", column.getRawName()); + column2.setTable(new TAPTable("myTable")); + assertEquals("\"myTable\". \"foo\"", column2.getADQLName()); + assertEquals("\"myTable\". \"foo\"", column2.getRawName()); + column3.setTable(new TAPTable("myTable")); + assertEquals("\"myTable\". foo", column3.getADQLName()); + assertEquals("\"myTable\". foo", column3.getRawName()); + 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()); + 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: + column.setTable(new TAPTable("myTable")); + assertEquals("foo", column.getADQLName()); + assertEquals("myTable.\"foo\"", column.getRawName()); + column2.setTable(new TAPTable("\"myTable\"")); + assertEquals("foo", column2.getADQLName()); + assertEquals("\"myTable\". \"foo\"", column2.getRawName()); + column3.setTable(new TAPTable("\"myTable\"")); + assertEquals("foo", column3.getADQLName()); + assertEquals("\"myTable\". foo", column3.getRawName()); + t = new TAPTable("mySchema.\"myTable\""); + t.setSchema(new TAPSchema("mySchema")); + column4.setTable(t); + assertEquals("foo", column4.getADQLName()); + assertEquals("mySchema.\"myTable\". foo", column4.getRawName()); + t = new TAPTable("\"mySchema\".\"myTable\""); + t.setSchema(new TAPSchema("\"mySchema\"")); + column5.setTable(t); + assertEquals("foo", column5.getADQLName()); + assertEquals("\"mySchema\".\"myTable\". foo", column5.getRawName()); + + }catch(NullPointerException npe){ + npe.printStackTrace(System.err); + fail("Unexpected error! The column name is not empty or NULL. (see console for more details)"); + } + } + + /** TEST XML METADATA */ + @Test + public void testXMLMetadata(){ + TAPMetadata metadata = new TAPMetadata(); + + TAPSchema schema = new TAPSchema("blabla"); + assertEquals("blabla", schema.getADQLName()); + + TAPTable table = new TAPTable("foo"); + table.addColumn(new TAPColumn("col1")); + table.addColumn(new TAPColumn("col2")); + table.addColumn(new TAPColumn("foo.col3")); + table.addColumn(new TAPColumn("blabla.foo.col4")); + table.addColumn(new TAPColumn("foo2.col5")); + schema.addTable(table); + + assertEquals("foo", table.getADQLName()); + Iterator<TAPColumn> itCol = table.getColumns(); + assertEquals("col1", itCol.next().getADQLName()); + assertEquals("col2", itCol.next().getADQLName()); + assertEquals("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\""); + table.addColumn(new TAPColumn("foo.bar.col1")); + table.addColumn(new TAPColumn("\"foo.bar\".col2")); + table.addColumn(new TAPColumn("blabla.foo.bar.col3")); + table.addColumn(new TAPColumn("blabla.\"foo.bar\".col4")); + schema.addTable(table); + + assertEquals("foo.bar", table.getADQLName()); + itCol = table.getColumns(); + assertEquals("foo.bar.col1", itCol.next().getADQLName()); + assertEquals("col2", itCol.next().getADQLName()); + assertEquals("blabla.foo.bar.col3", itCol.next().getADQLName()); + assertEquals("blabla.\"foo.bar\".col4", itCol.next().getADQLName()); + /* Note for below: + * Same as for the 4th column of the table "foo". */ + + metadata.addSchema(schema); + + schema = new TAPSchema("myCat.bloblo"); + assertEquals("myCat.bloblo", schema.getADQLName()); + + table = new TAPTable("myCat.bloblo.bar"); + table.addColumn(new TAPColumn("col1")); + table.addColumn(new TAPColumn("bloblo.col2")); + table.addColumn(new TAPColumn("bar.col3")); + table.addColumn(new TAPColumn("bloblo.bar.col4")); + table.addColumn(new TAPColumn("myCat.bloblo.bar.col5")); + schema.addTable(table); + + assertEquals("bar", table.getADQLName()); + itCol = table.getColumns(); + assertEquals("col1", itCol.next().getADQLName()); + assertEquals("bloblo.col2", itCol.next().getADQLName()); + assertEquals("bar.col3", itCol.next().getADQLName()); + /* 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()); + + metadata.addSchema(schema); + + schema = new TAPSchema("\"Mon Super Schema\""); + assertEquals("Mon Super Schema", schema.getADQLName()); + metadata.addSchema(schema); + + try{ + StringWriter str = new StringWriter(); + metadata.write(new PrintWriter(str)); + //System.out.println(str.toString()); + assertEquals(expectedXMLMetadata, str.toString()); + }catch(Exception ex){ + ex.printStackTrace(System.err); + fail("Unexpected error when writing TAP metadata into an XML format! (see console for more details)"); + } + + } + + public final static String expectedXMLMetadata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<vosi:tableset xmlns:vosi=\"http://www.ivoa.net/xml/VOSITables/v1.0\" xmlns:vod=\"http://www.ivoa.net/xml/VODataService/v1.1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.ivoa.net/xml/VODataService/v1.1 http://www.ivoa.net/xml/VODataService/v1.1 http://www.ivoa.net/xml/VOSITables/v1.0 http://vo.ari.uni-heidelberg.de/docs/schemata/VOSITables-v1.0.xsd\">\n\t<schema>\n\t\t<name>blabla</name>\n\t\t<table>\n\t\t\t<name>foo</name>\n\t\t\t<column>\n\t\t\t\t<name>col1</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>col2</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>foo.col3</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>blabla.foo.col4</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>foo2.col5</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t</table>\n\t\t<table>\n\t\t\t<name>\"foo.bar\"</name>\n\t\t\t<column>\n\t\t\t\t<name>foo.bar.col1</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>\"foo.bar\".col2</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>blabla.foo.bar.col3</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>blabla.\"foo.bar\".col4</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t</table>\n\t</schema>\n\t<schema>\n\t\t<name>myCat.bloblo</name>\n\t\t<table>\n\t\t\t<name>myCat.bloblo.bar</name>\n\t\t\t<column>\n\t\t\t\t<name>col1</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>bloblo.col2</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>bar.col3</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>bloblo.bar.col4</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t\t<column>\n\t\t\t\t<name>myCat.bloblo.bar.col5</name>\n\t\t\t\t<dataType xsi:type=\"vod:TAPType\">UNKNOWN</dataType>\n\t\t\t</column>\n\t\t</table>\n\t</schema>\n\t<schema>\n\t\t<name>\"Mon Super Schema\"</name>\n\t</schema>\n</vosi:tableset>\n"; + +}