diff --git a/src/adql/db/DBChecker.java b/src/adql/db/DBChecker.java index 8f32aed0bfbf796cbf693ea7326382219d43b92c..dd8c2b9f40d1de448d6c15ac4a5604a8154fedf6 100644 --- a/src/adql/db/DBChecker.java +++ b/src/adql/db/DBChecker.java @@ -2,20 +2,20 @@ 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-2017 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -78,28 +78,28 @@ import adql.search.SimpleSearchHandler; * <li>Check whether all used coordinate systems are supported</li> * <li>Check that types of columns and UDFs match with their context</li> * </ol> - * + * * <h3>Check tables and columns</h3> * <p> * In addition to check the existence of tables and columns referenced in the query, * this checked will also attach database metadata on these references ({@link ADQLTable} * and {@link ADQLColumn} instances when they are resolved. * </p> - * + * * <p>These information are:</p> * <ul> * <li>the corresponding {@link DBTable} or {@link DBColumn} (see getter and setter for DBLink in {@link ADQLTable} and {@link ADQLColumn})</li> * <li>the link between an {@link ADQLColumn} and its {@link ADQLTable}</li> * </ul> - * + * * <p><i><u>Note:</u> * Knowing DB metadata of {@link ADQLTable} and {@link ADQLColumn} is particularly useful for the translation of the ADQL query to SQL, * because the ADQL name of columns and tables can be replaced in SQL by their DB name, if different. This mapping is done automatically * by {@link adql.translator.JDBCTranslator}. * </i></p> - * + * * @author Grégory Mantelet (CDS;ARI) - * @version 1.4 (09/2017) + * @version 1.4 (11/2017) */ public class DBChecker implements QueryChecker { @@ -151,7 +151,7 @@ public class DBChecker implements QueryChecker { /* ************ */ /** * <p>Builds a {@link DBChecker} with an empty list of tables.</p> - * + * * <p>Verifications done by this object after creation:</p> * <ul> * <li>Existence of tables and columns: <b>NO <i>(even unknown or fake tables and columns are allowed)</i></b></li> @@ -166,7 +166,7 @@ public class DBChecker implements QueryChecker { /** * <p>Builds a {@link DBChecker} with the given list of known tables.</p> - * + * * <p>Verifications done by this object after creation:</p> * <ul> * <li>Existence of tables and columns: <b>OK</b></li> @@ -174,7 +174,7 @@ public class DBChecker implements QueryChecker { * <li>Support of geometrical functions: <b>NO <i>(all valid geometrical functions are allowed)</i></b></li> * <li>Support of coordinate systems: <b>NO <i>(all valid coordinate systems are allowed)</i></b></li> * </ul> - * + * * @param tables List of all available tables. */ public DBChecker(final Collection<? extends DBTable> tables){ @@ -183,7 +183,7 @@ public class DBChecker implements QueryChecker { /** * <p>Builds a {@link DBChecker} with the given list of known tables and with a restricted list of user defined functions.</p> - * + * * <p>Verifications done by this object after creation:</p> * <ul> * <li>Existence of tables and columns: <b>OK</b></li> @@ -191,13 +191,13 @@ public class DBChecker implements QueryChecker { * <li>Support of geometrical functions: <b>NO <i>(all valid geometrical functions are allowed)</i></b></li> * <li>Support of coordinate systems: <b>NO <i>(all valid coordinate systems are allowed)</i></b></li> * </ul> - * + * * @param tables List of all available tables. * @param allowedUdfs List of all allowed user defined functions. * If NULL, no verification will be done (and so, all UDFs are allowed). * If empty list, no "unknown" (or UDF) is allowed. * <i>Note: match with items of this list are done case insensitively.</i> - * + * * @since 1.3 */ public DBChecker(final Collection<? extends DBTable> tables, final Collection<? extends FunctionDef> allowedUdfs){ @@ -228,7 +228,7 @@ public class DBChecker implements QueryChecker { /** * <p>Builds a {@link DBChecker} with the given list of known tables and with a restricted list of user defined functions.</p> - * + * * <p>Verifications done by this object after creation:</p> * <ul> * <li>Existence of tables and columns: <b>OK</b></li> @@ -236,7 +236,7 @@ public class DBChecker implements QueryChecker { * <li>Support of geometrical functions: <b>OK</b></li> * <li>Support of coordinate systems: <b>OK</b></li> * </ul> - * + * * @param tables List of all available tables. * @param allowedGeoFcts List of all allowed geometrical functions (i.e. CONTAINS, POINT, UNION, CIRCLE, COORD1). * If NULL, no verification will be done (and so, all geometries are allowed). @@ -249,7 +249,7 @@ public class DBChecker implements QueryChecker { * For instance: "ICRS (GEOCENTER|heliocenter) *". * If the given list is NULL, no verification will be done (and so, all coordinate systems are allowed). * If it is empty, no coordinate system is allowed (except the default values - generally expressed by an empty string: ''). - * + * * @since 1.3 */ public DBChecker(final Collection<? extends DBTable> tables, final Collection<String> allowedGeoFcts, final Collection<String> allowedCoordSys) throws ParseException{ @@ -258,7 +258,7 @@ public class DBChecker implements QueryChecker { /** * <p>Builds a {@link DBChecker}.</p> - * + * * <p>Verifications done by this object after creation:</p> * <ul> * <li>Existence of tables and columns: <b>OK</b></li> @@ -266,7 +266,7 @@ public class DBChecker implements QueryChecker { * <li>Support of geometrical functions: <b>OK</b></li> * <li>Support of coordinate systems: <b>OK</b></li> * </ul> - * + * * @param tables List of all available tables. * @param allowedUdfs List of all allowed user defined functions. * If NULL, no verification will be done (and so, all UDFs are allowed). @@ -283,7 +283,7 @@ public class DBChecker implements QueryChecker { * For instance: "ICRS (GEOCENTER|heliocenter) *". * If the given list is NULL, no verification will be done (and so, all coordinate systems are allowed). * If it is empty, no coordinate system is allowed (except the default values - generally expressed by an empty string: ''). - * + * * @since 1.3 */ public DBChecker(final Collection<? extends DBTable> tables, final Collection<? extends FunctionDef> allowedUdfs, final Collection<String> allowedGeoFcts, final Collection<String> allowedCoordSys) throws ParseException{ @@ -301,11 +301,11 @@ public class DBChecker implements QueryChecker { /** * Transform the given collection of string elements in a sorted array. * Only non-NULL and non-empty strings are kept. - * + * * @param items Items to copy and sort. - * + * * @return A sorted array containing all - except NULL and empty strings - items of the given collection. - * + * * @since 1.3 */ protected final static String[] specialSort(final Collection<String> items){ @@ -336,13 +336,13 @@ public class DBChecker implements QueryChecker { /* ****** */ /** * <p>Sets the list of all available tables.</p> - * + * * <p><i><u>Note:</u> * Only if the given collection is NOT an implementation of * {@link SearchTableApi}, the collection will be copied inside a new * {@link SearchTableList}, otherwise it is used as provided. * </i></p> - * + * * @param tables List of {@link DBTable}s. */ public final void setTables(final Collection<? extends DBTable> tables){ @@ -359,16 +359,16 @@ public class DBChecker implements QueryChecker { /* ************* */ /** * <p>Check all the columns, tables and UDFs references inside the given query.</p> - * + * * <p><i> * <u>Note:</u> This query has already been parsed ; thus it is already syntactically correct. * Only the consistency with the published tables, columns and all the defined UDFs must be checked. * </i></p> - * + * * @param query The query to check. - * + * * @throws ParseException An {@link UnresolvedIdentifiersException} if some tables or columns can not be resolved. - * + * * @see #check(ADQLQuery, Stack) */ @Override @@ -378,7 +378,7 @@ public class DBChecker implements QueryChecker { /** * <p>Process several (semantic) verifications in the given ADQL query.</p> - * + * * <p>Main verifications done in this function:</p> * <ol> * <li>Existence of DB items (tables and columns)</li> @@ -387,17 +387,17 @@ public class DBChecker implements QueryChecker { * <li>Support of every encountered geometries (functions, coordinate systems and STC-S expressions)</li> * <li>Consistency of types still unknown (because the syntactic parser could not yet resolve them)</li> * </ol> - * + * * @param query The query to check. * @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. * Each item of this stack is a list of columns available in each father-level query. * <i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i> - * + * * @throws UnresolvedIdentifiersException An {@link UnresolvedIdentifiersException} if one or several of the above listed tests have detected * some semantic errors (i.e. unresolved table, columns, function). - * + * * @since 1.2 - * + * * @see #checkDBItems(ADQLQuery, Stack, UnresolvedIdentifiersException) * @see #checkSubQueries(ADQLQuery, Stack, SearchColumnList, UnresolvedIdentifiersException) * @see #checkUDFs(ADQLQuery, UnresolvedIdentifiersException) @@ -434,26 +434,26 @@ public class DBChecker implements QueryChecker { /** * <p>Check DB items (tables and columns) used in the given ADQL query.</p> - * + * * <p>Operations done in this function:</p> * <ol> * <li>Resolve all found tables</li> * <li>Get the whole list of all available columns <i>Note: this list is returned by this function.</i></li> * <li>Resolve all found columns</li> * </ol> - * + * * @param query Query in which the existence of DB items must be checked. * @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. * Each item of this stack is a list of columns available in each father-level query. * <i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i> * @param errors List of errors to complete in this function each time an unknown table or column is encountered. - * + * * @return List of all columns available in the given query. - * + * * @see #resolveTables(ADQLQuery, Stack, UnresolvedIdentifiersException) * @see FromContent#getDBColumns() * @see #resolveColumns(ADQLQuery, Stack, Map, SearchColumnList, UnresolvedIdentifiersException) - * + * * @since 1.3 */ protected SearchColumnList checkDBItems(final ADQLQuery query, final Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors){ @@ -476,29 +476,54 @@ public class DBChecker implements QueryChecker { } /** - * <p>Search all table references inside the given query, resolve them against the available tables, and if there is only one match, - * attach the matching metadata to them.</p> - * + * Search all table references inside the given query, resolve them against + * the available tables, and if there is only one match, attach the matching + * metadata to them. + * * <b>Management of sub-query tables</b> * <p> - * If a table is not a DB table reference but a sub-query, this latter is first checked (using {@link #check(ADQLQuery, Stack)} ; - * but the father list must not contain tables of the given query, because on the same level) and then corresponding table metadata - * are generated (using {@link #generateDBTable(ADQLQuery, String)}) and attached to it. + * If a table is not a DB table reference but a sub-query, this latter is + * first checked (using {@link #check(ADQLQuery, Stack)} ; but the father + * list must not contain tables of the given query, because on the same + * level) and then corresponding table metadata are generated (using + * {@link #generateDBTable(ADQLQuery, String)}) and attached to it. * </p> - * + * * <b>Management of "{table}.*" in the SELECT clause</b> * <p> - * For each of this SELECT item, this function tries to resolve the table name. If only one match is found, the corresponding ADQL table object - * is got from the list of resolved tables and attached to this SELECT item (thus, the joker item will also have the good metadata, - * particularly if the referenced table is a sub-query). + * For each of this SELECT item, this function tries to resolve the table + * name. If only one match is found, the corresponding ADQL table object + * is got from the list of resolved tables and attached to this SELECT item + * (thus, the joker item will also have the good metadata, particularly if + * the referenced table is a sub-query). * </p> - * - * @param query Query in which the existence of tables must be checked. - * @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. - * Each item of this stack is a list of columns available in each father-level query. - * <i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i> - * @param errors List of errors to complete in this function each time an unknown table or column is encountered. - * + * + * <b>Table alias</b> + * <p> + * When a simple table (i.e. not a sub-query) is aliased, the metadata of + * this table will be wrapped inside a {@link DBTableAlias} in order to + * keep the original metadata but still declare use the table with the + * alias instead of its original name. The original name will be used + * only when translating the corresponding FROM item ; the rest of the time + * (i.e. for references when using a column), the alias name must be used. + * </p> + * <p> + * In order to avoid unpredictable behavior at execution of the SQL query, + * the alias will be put in lower case if not defined between double + * quotes. + * </p> + * + * @param query Query in which the existence of tables must be + * checked. + * @param fathersList List of all columns available in the father queries + * and that should be accessed in sub-queries. + * Each item of this stack is a list of columns + * available in each father-level query. + * <i>Note: this parameter is NULL if this function is + * called with the root/father query as parameter.</i> + * @param errors List of errors to complete in this function each + * time an unknown table or column is encountered. + * * @return An associative map of all the resolved tables. */ protected Map<DBTable,ADQLTable> resolveTables(final ADQLQuery query, final Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors){ @@ -521,8 +546,9 @@ public class DBChecker implements QueryChecker { dbTable = generateDBTable(table.getSubQuery(), table.getAlias()); }else{ dbTable = resolveTable(table); - if (table.hasAlias()) - dbTable = dbTable.copy(null, table.getAlias()); + // wrap this table metadata if an alias should be used: + if (dbTable != null && table.hasAlias()) + dbTable = new DBTableAlias(dbTable, (table.isCaseSensitive(IdentifierField.ALIAS) ? table.getAlias() : table.getAlias().toLowerCase())); } // link with the matched DBTable: @@ -569,11 +595,11 @@ public class DBChecker implements QueryChecker { /** * Resolve the given table, that's to say search for the corresponding {@link DBTable}. - * + * * @param table The table to resolve. - * + * * @return The corresponding {@link DBTable} if found, <i>null</i> otherwise. - * + * * @throws ParseException An {@link UnresolvedTableException} if the given table can't be resolved. */ protected DBTable resolveTable(final ADQLTable table) throws ParseException{ @@ -593,7 +619,7 @@ public class DBChecker implements QueryChecker { /** * <p>Search all column references inside the given query, resolve them thanks to the given tables' metadata, * and if there is only one match, attach the matching metadata to them.</p> - * + * * <b>Management of selected columns' references</b> * <p> * A column reference is not only a direct reference to a table column using a column name. @@ -605,7 +631,7 @@ public class DBChecker implements QueryChecker { * These references are also checked, in a second step, in this function. Thus, column metadata are * also attached to them, as common columns. * </p> - * + * * @param query Query in which the existence of tables must be checked. * @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. * Each item of this stack is a list of columns available in each father-level query. @@ -674,21 +700,21 @@ public class DBChecker implements QueryChecker { /** * <p>Resolve the given column, that's to say search for the corresponding {@link DBColumn}.</p> - * + * * <p> * The third parameter is used only if this function is called inside a sub-query. In this case, * the column is tried to be resolved with the first list (dbColumns). If no match is found, * the resolution is tried with the father columns list (fathersList). * </p> - * + * * @param column The column to resolve. * @param dbColumns List of all available {@link DBColumn}s. * @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. * Each item of this stack is a list of columns available in each father-level query. * <i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i> - * + * * @return The corresponding {@link DBColumn} if found. Otherwise an exception is thrown. - * + * * @throws ParseException An {@link UnresolvedColumnException} if the given column can't be resolved * or an {@link UnresolvedTableException} if its table reference can't be resolved. */ @@ -718,20 +744,20 @@ public class DBChecker implements QueryChecker { /** * Check whether the given column corresponds to a selected item's alias or to an existing column. - * + * * @param col The column to check. * @param select The SELECT clause of the ADQL query. * @param dbColumns The list of all available columns. - * + * * @return The corresponding {@link DBColumn} if this column corresponds to an existing column, * <i>NULL</i> otherwise. - * + * * @throws ParseException An {@link UnresolvedColumnException} if the given column can't be resolved * or an {@link UnresolvedTableException} if its table reference can't be resolved. - * + * * @see ClauseSelect#searchByAlias(String) * @see #resolveColumn(ADQLColumn, SearchColumnList, Stack) - * + * * @since 1.4 */ protected DBColumn checkGroupByItem(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException{ @@ -751,16 +777,16 @@ public class DBChecker implements QueryChecker { /** * Check whether the given column reference corresponds to a selected item (column or an expression with an alias) * or to an existing column. - * + * * @param colRef The column reference which must be checked. * @param select The SELECT clause of the ADQL query. * @param dbColumns The list of all available columns. - * + * * @return The corresponding {@link DBColumn} if this reference is actually the name of a column, <i>null</i> otherwise. - * + * * @throws ParseException An {@link UnresolvedColumnException} if the given column can't be resolved * or an {@link UnresolvedTableException} if its table reference can't be resolved. - * + * * @see ClauseSelect#searchByAlias(String) * @see #resolveColumn(ADQLColumn, SearchColumnList, Stack) */ @@ -795,12 +821,12 @@ public class DBChecker implements QueryChecker { /** * Generate a {@link DBTable} corresponding to the given sub-query with the given table name. * This {@link DBTable} will contain all {@link DBColumn} returned by {@link ADQLQuery#getResultingColumns()}. - * + * * @param subQuery Sub-query in which the specified table must be searched. * @param tableName Name of the table to search. - * + * * @return The corresponding {@link DBTable} if the table has been found in the given sub-query, <i>null</i> otherwise. - * + * * @throws ParseException Can be used to explain why the table has not been found. <i>Note: not used by default.</i> */ public static DBTable generateDBTable(final ADQLQuery subQuery, final String tableName) throws ParseException{ @@ -820,7 +846,7 @@ public class DBChecker implements QueryChecker { /** * <p>Search all UDFs (User Defined Functions) inside the given query, and then * check their signature against the list of allowed UDFs.</p> - * + * * <p><i>Note: * When more than one allowed function match, the function is considered as correct * and no error is added. @@ -830,10 +856,10 @@ public class DBChecker implements QueryChecker { * then need to cast some parameters to help the parser identifying the right function. * But the type-casting ability is not yet possible in ADQL. * </i></p> - * + * * @param query Query in which UDFs must be checked. * @param errors List of errors to complete in this function each time a UDF does not match to any of the allowed UDFs. - * + * * @since 1.3 */ protected void checkUDFs(final ADQLQuery query, final UnresolvedIdentifiersException errors){ @@ -903,18 +929,18 @@ public class DBChecker implements QueryChecker { /** * <p>Tell whether the type of all parameters of the given ADQL function * is resolved.</p> - * + * * <p>A parameter type may not be resolved for 2 main reasons:</p> * <ul> * <li>the parameter is a <b>column</b>, but this column has not been successfully resolved. Thus its type is still unknown.</li> * <li>the parameter is a <b>UDF</b>, but this UDF has not been already resolved. Thus, as for the column, its return type is still unknown. * But it could be known later if the UDF is resolved later ; a second try should be done afterwards.</li> * </ul> - * + * * @param fct ADQL function whose the parameters' type should be checked. - * + * * @return <i>true</i> if the type of all parameters is known, <i>false</i> otherwise. - * + * * @since 1.3 */ protected final boolean isAllParamTypesResolved(final ADQLFunction fct){ @@ -931,7 +957,7 @@ public class DBChecker implements QueryChecker { /** * <p>Check all geometries.</p> - * + * * <p>Operations done in this function:</p> * <ol> * <li>Check that all geometrical functions are supported</li> @@ -939,14 +965,14 @@ public class DBChecker implements QueryChecker { * <li>Check all STC-S expressions (only in {@link RegionFunction} for the moment) and * Apply the 2 previous checks on them</li> * </ol> - * + * * @param query Query in which geometries must be checked. * @param errors List of errors to complete in this function each time a geometry item is not supported. - * + * * @see #resolveGeometryFunctions(ADQLQuery, BinarySearch, UnresolvedIdentifiersException) * @see #resolveCoordinateSystems(ADQLQuery, UnresolvedIdentifiersException) * @see #resolveSTCSExpressions(ADQLQuery, BinarySearch, UnresolvedIdentifiersException) - * + * * @since 1.3 */ protected void checkGeometries(final ADQLQuery query, final UnresolvedIdentifiersException errors){ @@ -972,12 +998,12 @@ public class DBChecker implements QueryChecker { /** * Search for all geometrical functions and check whether they are allowed. - * + * * @param query Query in which geometrical functions must be checked. * @param errors List of errors to complete in this function each time a geometrical function is not supported. - * + * * @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException) - * + * * @since 1.3 */ protected void resolveGeometryFunctions(final ADQLQuery query, final BinarySearch<String,String> binSearch, final UnresolvedIdentifiersException errors){ @@ -993,12 +1019,12 @@ public class DBChecker implements QueryChecker { /** * <p>Check whether the specified geometrical function is allowed by this implementation.</p> - * + * * <p><i>Note: * If the list of allowed geometrical functions is empty, this function will always add an errors to the given list. * Indeed, it means that no geometrical function is allowed and so that the specified function is automatically not supported. * </i></p> - * + * * @param fctName Name of the geometrical function to test. * @param fct The function instance being or containing the geometrical function to check. <i>Note: this function can be the function to test or a function embedding the function under test (i.e. RegionFunction). * @param binSearch The object to use in order to search a function name inside the list of allowed functions. @@ -1006,7 +1032,7 @@ public class DBChecker implements QueryChecker { * this object is its compare function which must be overridden and tells how to compare the item * to search and the items of the array (basically, a non-case-sensitive comparison between 2 strings). * @param errors List of errors to complete in this function each time a geometrical function is not supported. - * + * * @since 1.3 */ protected void checkGeometryFunction(final String fctName, final ADQLFunction fct, final BinarySearch<String,String> binSearch, final UnresolvedIdentifiersException errors){ @@ -1019,18 +1045,18 @@ public class DBChecker implements QueryChecker { /** * <p>Search all explicit coordinate system declarations, check their syntax and whether they are allowed by this implementation.</p> - * + * * <p><i>Note: * "explicit" means here that all {@link StringConstant} instances. Only coordinate systems expressed as string can * be parsed and so checked. So if a coordinate system is specified by a column, no check can be done at this stage... * it will be possible to perform such test only at the execution. * </i></p> - * + * * @param query Query in which coordinate systems must be checked. * @param errors List of errors to complete in this function each time a coordinate system has a wrong syntax or is not supported. - * + * * @see #checkCoordinateSystem(StringConstant, UnresolvedIdentifiersException) - * + * * @since 1.3 */ protected void resolveCoordinateSystems(final ADQLQuery query, final UnresolvedIdentifiersException errors){ @@ -1042,13 +1068,13 @@ public class DBChecker implements QueryChecker { /** * Parse and then check the coordinate system contained in the given {@link StringConstant} instance. - * + * * @param adqlCoordSys The {@link StringConstant} object containing the coordinate system to check. * @param errors List of errors to complete in this function each time a coordinate system has a wrong syntax or is not supported. - * + * * @see STCS#parseCoordSys(String) * @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException) - * + * * @since 1.3 */ protected void checkCoordinateSystem(final StringConstant adqlCoordSys, final UnresolvedIdentifiersException errors){ @@ -1062,11 +1088,11 @@ public class DBChecker implements QueryChecker { /** * Check whether the given coordinate system is allowed by this implementation. - * + * * @param coordSys Coordinate system to test. * @param operand The operand representing or containing the coordinate system under test. * @param errors List of errors to complete in this function each time a coordinate system is not supported. - * + * * @since 1.3 */ protected void checkCoordinateSystem(final CoordSys coordSys, final ADQLOperand operand, final UnresolvedIdentifiersException errors){ @@ -1090,21 +1116,21 @@ public class DBChecker implements QueryChecker { /** * <p>Search all STC-S expressions inside the given query, parse them (and so check their syntax) and then determine * whether the declared coordinate system and the expressed region are allowed in this implementation.</p> - * + * * <p><i>Note: * In the current ADQL language definition, STC-S expressions can be found only as only parameter of the REGION function. * </i></p> - * + * * @param query Query in which STC-S expressions must be checked. * @param binSearch The object to use in order to search a region name inside the list of allowed functions/regions. * It is able to perform a binary search inside a sorted array of String objects. The interest of * this object is its compare function which must be overridden and tells how to compare the item * to search and the items of the array (basically, a non-case-sensitive comparison between 2 strings). * @param errors List of errors to complete in this function each time the STC-S syntax is wrong or each time the declared coordinate system or region is not supported. - * + * * @see STCS#parseRegion(String) * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException) - * + * * @since 1.3 */ protected void resolveSTCSExpressions(final ADQLQuery query, final BinarySearch<String,String> binSearch, final UnresolvedIdentifiersException errors){ @@ -1133,22 +1159,22 @@ public class DBChecker implements QueryChecker { /** * <p>Check the given region.</p> - * + * * <p>The following points are checked in this function:</p> * <ul> * <li>whether the coordinate system is allowed</li> * <li>whether the type of region is allowed</li> * <li>whether the inner regions are correct (here this function is called recursively on each inner region).</li> * </ul> - * + * * @param r The region to check. * @param fct The REGION function containing the region to check. * @param errors List of errors to complete in this function if the given region or its inner regions are not supported. - * + * * @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException) * @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException) * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException) - * + * * @since 1.3 */ protected void checkRegion(final Region r, final RegionFunction fct, final BinarySearch<String,String> binSearch, final UnresolvedIdentifiersException errors){ @@ -1181,13 +1207,13 @@ public class DBChecker implements QueryChecker { /** * <p>Search all operands whose the type is not yet known and try to resolve it now * and to check whether it matches the type expected by the syntactic parser.</p> - * + * * <p> * Only two operands may have an unresolved type: columns and user defined functions. * Indeed, their type can be resolved only if the list of available columns and UDFs is known, * and if columns and UDFs used in the query are resolved successfully. * </p> - * + * * <p> * When an operand type is still unknown, they will own the three kinds of type and * so this function won't raise an error: it is thus automatically on the expected type. @@ -1195,17 +1221,17 @@ public class DBChecker implements QueryChecker { * that means the item/operand has not been resolved in the previous steps and so that * an error about this item has already been raised. * </p> - * + * * <p><i><b>Important note:</b> * This function does not check the types exactly, but just roughly by considering only three categories: * string, numeric and geometry. * </i></p> - * + * * @param query Query in which unknown types must be resolved and checked. * @param errors List of errors to complete in this function each time a types does not match to the expected one. - * + * * @see UnknownType - * + * * @since 1.3 */ protected void checkTypes(final ADQLQuery query, final UnresolvedIdentifiersException errors){ @@ -1244,7 +1270,7 @@ public class DBChecker implements QueryChecker { /** * <p>Search all sub-queries found in the given query but not in the clause FROM. * These sub-queries are then checked using {@link #check(ADQLQuery, Stack)}.</p> - * + * * <b>Fathers stack</b> * <p> * Each time a sub-query must be checked with {@link #check(ADQLQuery, Stack)}, @@ -1257,15 +1283,15 @@ public class DBChecker implements QueryChecker { * This modification of the given stack is just the execution time of this function. * Before returning, this function removes the last item of the stack. * </p> - * - * + * + * * @param query Query in which sub-queries must be checked. * @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. * Each item of this stack is a list of columns available in each father-level query. * <i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i> * @param availableColumns List of all columns resolved in the given query. * @param errors List of errors to complete in this function each time a semantic error is encountered. - * + * * @since 1.3 */ protected void checkSubQueries(final ADQLQuery query, Stack<SearchColumnList> fathersList, final SearchColumnList availableColumns, final UnresolvedIdentifiersException errors){ @@ -1301,12 +1327,12 @@ public class DBChecker implements QueryChecker { /** * Lets searching all {@link ADQLColumn} in the given object, EXCEPT in the GROUP BY clause. - * + * * <p> * {@link ADQLColumn}s of the GROUP BY may be aliases and so, they can not be checked * exactly as a normal column. * </p> - * + * * @author Grégory Mantelet (ARI) * @version 1.4 (05/2017) * @since 1.4 @@ -1320,7 +1346,7 @@ public class DBChecker implements QueryChecker { /** * Lets searching all tables. - * + * * @author Grégory Mantelet (CDS) * @version 1.0 (07/2011) */ @@ -1333,7 +1359,7 @@ public class DBChecker implements QueryChecker { /** * Lets searching all wildcards. - * + * * @author Grégory Mantelet (CDS) * @version 1.0 (09/2011) */ @@ -1346,7 +1372,7 @@ public class DBChecker implements QueryChecker { /** * Lets searching column references. - * + * * @author Grégory Mantelet (CDS) * @version 1.0 (11/2011) */ @@ -1359,12 +1385,12 @@ public class DBChecker implements QueryChecker { /** * <p>Lets searching subqueries in every clause except the FROM one (hence the modification of the {@link #goInto(ADQLObject)}.</p> - * + * * <p><i> * <u>Note:</u> The function {@link #addMatch(ADQLObject, ADQLIterator)} has been modified in order to * not have the root search object (here: the main query) in the list of results. * </i></p> - * + * * @author Grégory Mantelet (ARI) * @version 1.2 (12/2013) * @since 1.2 @@ -1389,7 +1415,7 @@ public class DBChecker implements QueryChecker { /** * Let searching user defined functions. - * + * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 @@ -1403,11 +1429,11 @@ public class DBChecker implements QueryChecker { /** * <p>Let replacing every {@link DefaultUDF}s whose a {@link FunctionDef} is set by their corresponding {@link UserDefinedFunction} class.</p> - * + * * <p><i><b>Important note:</b> * If the replacer can not be created using the class returned by {@link FunctionDef#getUDFClass()}, no replacement is performed. * </i></p> - * + * * @author Grégory Mantelet (ARI) * @version 1.3 (02/2015) * @since 1.3 @@ -1448,7 +1474,7 @@ public class DBChecker implements QueryChecker { /** * Let searching geometrical functions. - * + * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 @@ -1463,12 +1489,12 @@ public class DBChecker implements QueryChecker { /** * <p>Let searching all ADQL objects whose the type was not known while checking the syntax of the ADQL query. * These objects are {@link ADQLColumn}s and {@link UserDefinedFunction}s.</p> - * + * * <p><i><b>Important note:</b> * Only {@link UnknownType} instances having an expected type equals to 'S' (or 's' ; for string) or 'N' (or 'n' ; for numeric) * are kept by this handler. Others are ignored. * </i></p> - * + * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 @@ -1487,7 +1513,7 @@ public class DBChecker implements QueryChecker { /** * Let searching all explicit declaration of coordinate systems. * So, only {@link StringConstant} objects will be returned. - * + * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 @@ -1510,7 +1536,7 @@ public class DBChecker implements QueryChecker { /** * Let searching all {@link RegionFunction}s. - * + * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) * @since 1.3 @@ -1528,23 +1554,23 @@ public class DBChecker implements QueryChecker { /** * <p>Implement the binary search algorithm over a sorted array.</p> - * + * * <p> * The only difference with the standard implementation of Java is * that this object lets perform research with a different type * of object than the types of array items. * </p> - * + * * <p> * For that reason, the "compare" function must always be implemented. * </p> - * + * * @author Grégory Mantelet (ARI) * @version 1.3 (10/2014) - * + * * @param <T> Type of items stored in the array. * @param <S> Type of the item to search. - * + * * @since 1.3 */ protected static abstract class BinarySearch< T, S > { @@ -1552,16 +1578,16 @@ public class DBChecker implements QueryChecker { /** * <p>Search the given item in the given array.</p> - * + * * <p> * In case the given object matches to several items of the array, * this function will return the smallest index, pointing thus to the first * of all matches. * </p> - * + * * @param searchItem Object for which a corresponding array item must be searched. * @param array Array in which the given object must be searched. - * + * * @return The array index of the first item of all matches. */ public int search(final S searchItem, final T[] array){ @@ -1587,10 +1613,10 @@ public class DBChecker implements QueryChecker { /** * Compare the search item and the array item. - * + * * @param searchItem Item whose a corresponding value must be found in the array. * @param arrayItem An item of the array. - * + * * @return Negative value if searchItem is less than arrayItem, 0 if they are equals, or a positive value if searchItem is greater. */ protected abstract int compare(final S searchItem, final T arrayItem); diff --git a/src/adql/db/DBTableAlias.java b/src/adql/db/DBTableAlias.java new file mode 100644 index 0000000000000000000000000000000000000000..aef451e0a74a3ffc999cd656b1f24d913f34cf75 --- /dev/null +++ b/src/adql/db/DBTableAlias.java @@ -0,0 +1,77 @@ +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 2017 - Astronomisches Rechen Institut (ARI) + */ + +/** + * This {@link DBTable} wraps another {@link DBTable} with a different ADQL and + * DB name. + * + * <p> + * This wrapper aims to represent in the metadata the aliasing of a table. + * This table should not be part of any schema, in ADQL but also in SQL...it is + * just an alias of an existing table. + * </p> + * + * <p> + * All columns of the origin table are completely copied into this + * {@link DBTable} thanks to {@link DBColumn#copy(String, String, DBTable)}, + * with the same ADQL and DB name but a different parent table (this one is + * used of the original one). + * </p> + * + * <p><i>Note: + * The origin table is still available thanks to the function + * {@link #getOriginTable()}. + * </i></p> + * + * @author Grégory Mantelet (ARI) + * @version 1.4 (11/2017) + * @since 1.4 + */ +public class DBTableAlias extends DefaultDBTable { + + /** Wrapped table. */ + protected final DBTable originTable; + + /** + * Wrap the given table under the given ADQL/DB name. + * + * @param originTable The table to wrap/alias. + * @param tableAlias The alias name. + */ + public DBTableAlias(final DBTable originTable, final String tableAlias){ + super(null, null, tableAlias); + + this.originTable = originTable; + + for(DBColumn col : originTable) + addColumn(col.copy(col.getDBName(), col.getADQLName(), this)); + } + + /** + * Get the aliased/wrapped table. + * + * @return The aliased table. + */ + public DBTable getOriginTable(){ + return originTable; + } + +} diff --git a/src/adql/translator/JDBCTranslator.java b/src/adql/translator/JDBCTranslator.java index 64a301f6dd3e7ca895a8b73ca6ca75b97e301643..2339b932863342f8ffea6be41fea08418c2b4c85 100644 --- a/src/adql/translator/JDBCTranslator.java +++ b/src/adql/translator/JDBCTranslator.java @@ -2,20 +2,20 @@ package adql.translator; /* * 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 2017 - Astronomisches Rechen Institut (ARI) */ @@ -25,6 +25,7 @@ import java.util.List; import adql.db.DBColumn; import adql.db.DBTable; +import adql.db.DBTableAlias; import adql.db.DBType; import adql.db.STCS.Region; import adql.db.exception.UnresolvedJoinException; @@ -80,27 +81,27 @@ import adql.query.operand.function.geometry.RegionFunction; /** * <p>Implementation of {@link ADQLTranslator} which translates ADQL queries in SQL queries.</p> - * + * * <p> * It is already able to translate all SQL standard features, but lets abstract the translation of all * geometrical functions. So, this translator must be extended as {@link PostgreSQLTranslator} and * {@link PgSphereTranslator} are doing. * </p> - * + * * <p><i>Note: * Its default implementation of the SQL syntax has been inspired by the PostgreSQL one. * However, it should work also with SQLite and MySQL, but some translations might be needed * (as it is has been done for PostgreSQL about the mathematical functions). * </i></p> - * + * * <h3>PostgreSQLTranslator and PgSphereTranslator</h3> - * + * * <p> * {@link PgSphereTranslator} extends {@link PostgreSQLTranslator} and is able to translate geometrical * functions according to the syntax given by PgSphere. But it can also convert geometrical types * (from and toward the database), translate PgSphere regions into STC expression and vice-versa. * </p> - * + * * <p> * {@link PostgreSQLTranslator} overwrites the translation of mathematical functions whose some have * a different name or signature. Besides, it is also implementing the translation of the geometrical @@ -110,9 +111,9 @@ import adql.query.operand.function.geometry.RegionFunction; * using this translator will not work. It is just a default implementation in case there is no interest * of these geometrical functions. * </p> - * + * * <h3>SQL with or without case sensitivity?</h3> - * + * * <p> * In ADQL and in SQL, it is possible to tell the parser to respect the exact case or not of an identifier (schema, table or column name) * by surrounding it with double quotes. However ADQL identifiers and SQL ones may be different. In that way, the case sensitivity specified @@ -121,9 +122,9 @@ import adql.query.operand.function.geometry.RegionFunction; * The functions translating column and table names will call this function in order to surround the identifiers by double quotes or not. * So, <b>be careful if you want to override the functions translating columns and tables!</b> * </p> - * + * * <h3>Translation of "SELECT TOP"</h3> - * + * * <p> * The default behavior of this translator is to translate the ADQL "TOP" into the SQL "LIMIT" at the end of the query. * This is ok for some DBMS, but not all. So, if your DBMS does not know the "LIMIT" keyword, you should override the function @@ -144,31 +145,31 @@ import adql.query.operand.function.geometry.RegionFunction; * sql.append("\nLimit ").append(query.getSelect().getLimit()); * return sql.toString(); * </pre> - * + * * <h3>Translation of ADQL functions</h3> - * + * * <p> * All ADQL functions are by default not translated. Consequently, the SQL translation is * actually the ADQL expression. Generally the ADQL expression is generic enough. However some mathematical functions may need * to be translated differently. For instance {@link PostgreSQLTranslator} is translating differently: LOG, LOG10, RAND and TRUNC. * </p> - * + * * <p><i>Note: * Geometrical regions and types have not been managed here. They stay abstract because it is obviously impossible to have a generic * translation and conversion ; it totally depends from the database system. * </i></p> - * + * * <h3>Translation of "FROM" with JOINs</h3> - * + * * <p> * The FROM clause is translated into SQL as written in ADQL. There is no differences except the identifiers that are replaced. * The tables' aliases and their case sensitivity are kept like in ADQL. * </p> - * + * * @author Grégory Mantelet (ARI) - * @version 1.4 (09/2017) + * @version 1.4 (11/2017) * @since 1.4 - * + * * @see PostgreSQLTranslator * @see PgSphereTranslator */ @@ -178,30 +179,30 @@ public abstract class JDBCTranslator implements ADQLTranslator { * <p>Tell whether the specified identifier MUST be translated so that being interpreted case sensitively or not. * By default, an identifier that must be translated with case sensitivity will be surrounded by double quotes. * But, if this function returns FALSE, the SQL name will be written just as given in the metadata, without double quotes.</p> - * + * * <p><b>WARNING</b>: * An {@link IdentifierField} object can be a SCHEMA, TABLE, COLUMN and ALIAS. However, in this translator, * aliases are translated like in ADQL (so, with the same case sensitivity specification as in ADQL). * So, this function will never be used to know the case sensitivity to apply to an alias. It is then * useless to write a special behavior for the ALIAS value. * </p> - * + * * @param field The identifier whose the case sensitive to apply is asked. - * + * * @return <i>true</i> if the specified identifier must be translated case sensitivity, <i>false</i> otherwise (included if ALIAS or NULL). */ public abstract boolean isCaseSensitive(final IdentifierField field); /** * <p>Get the qualified DB name of the schema containing the given table.</p> - * + * * <p><i>Note: * This function will, by default, add double quotes if the schema name must be case sensitive in the SQL query. * This information is provided by {@link #isCaseSensitive(IdentifierField)}. * </i></p> - * + * * @param table A table of the schema whose the qualified DB name is asked. - * + * * @return The qualified (with DB catalog name prefix if any, and with double quotes if needed) DB schema name, * or an empty string if there is no schema or no DB name. */ @@ -221,17 +222,17 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * <p>Get the qualified DB name of the given table.</p> - * + * * <p><i>Note: * This function will, by default, add double quotes if the table name must be case sensitive in the SQL query. * This information is provided by {@link #isCaseSensitive(IdentifierField)}. * </i></p> - * + * * @param table The table whose the qualified DB name is asked. - * + * * @return The qualified (with DB catalog and schema prefix if any, and with double quotes if needed) DB table name, * or an empty string if the given table is NULL or if there is no DB name. - * + * * @see #getTableName(DBTable, boolean) */ public String getQualifiedTableName(final DBTable table){ @@ -241,18 +242,18 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * <p>Get the DB name of the given table. * The second parameter lets specify whether the table name must be prefixed by the qualified schema name or not.</p> - * + * * <p><i>Note: * This function will, by default, add double quotes if the table name must be case sensitive in the SQL query. * This information is provided by {@link #isCaseSensitive(IdentifierField)}. * </i></p> - * + * * @param table The table whose the DB name is asked. * @param withSchema <i>true</i> if the qualified schema name must prefix the table name, <i>false</i> otherwise. - * + * * @return The DB table name (prefixed by the qualified schema name if asked, and with double quotes if needed), * or an empty string if the given table is NULL or if there is no DB name. - * + * * @since 2.0 */ public String getTableName(final DBTable table, final boolean withSchema){ @@ -272,19 +273,19 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * <p>Get the DB name of the given column</p> - * + * * <p><i>Note: * This function will, by default, add double quotes if the column name must be case sensitive in the SQL query. * This information is provided by {@link #isCaseSensitive(IdentifierField)}. * </i></p> - * + * * <p><b>Caution: * The given column may be NULL and in this case an empty string will be returned. * But if the given column is not NULL, its DB name MUST NOT BE NULL! * </b></p> - * + * * @param column The column whose the DB name is asked. - * + * * @return The DB column name (with double quotes if needed), * or an empty string if the given column is NULL. */ @@ -294,24 +295,24 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * <p>Appends the given identifier in the given StringBuffer.</p> - * + * * <p> * This function just call {@link #appendIdentifier(StringBuffer, String, boolean)} * with the same 2 first parameters. The third one is the result of: * <code>{@link #isCaseSensitive(IdentifierField) isCaseSensitive(field)}</code>. * </p> - * + * * <p><i>Note: * In order to keep a consistent output of the <code>appendIdentifier(...)</code> functions, * this function can not be overwritten ; it is just a shortcut function. * </i></p> - * + * * @param str The string buffer. * @param id The identifier to append. * @param field The type of identifier (column, table, schema, catalog or alias ?). - * + * * @return The string buffer + identifier. - * + * * @see #appendIdentifier(StringBuffer, String, boolean) */ public final StringBuffer appendIdentifier(final StringBuffer str, final String id, final IdentifierField field){ @@ -320,11 +321,11 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * Appends the given identifier to the given StringBuffer. - * + * * @param str The string buffer. * @param id The identifier to append. * @param caseSensitive <i>true</i> to format the identifier so that preserving the case sensitivity, <i>false</i> otherwise. - * + * * @return The string buffer + identifier. */ public StringBuffer appendIdentifier(final StringBuffer str, final String id, final boolean caseSensitive){ @@ -396,11 +397,11 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * Gets the default SQL output for a list of ADQL objects. - * + * * @param list List to format into SQL. - * + * * @return The corresponding SQL. - * + * * @throws TranslationException If there is an error during the translation. */ protected String getDefaultADQLList(ADQLList<? extends ADQLObject> list) throws TranslationException{ @@ -513,11 +514,11 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * Gets the default SQL output for a column reference. - * + * * @param ref The column reference to format into SQL. - * + * * @return The corresponding SQL. - * + * * @throws TranslationException If there is an error during the translation. */ protected String getDefaultColumnReference(ColumnReference ref) throws TranslationException{ @@ -573,8 +574,16 @@ public abstract class JDBCTranslator implements ADQLTranslator { // CASE: TABLE REFERENCE: else{ // Use the corresponding DB table, if known: - if (table.getDBLink() != null) - sql.append(getQualifiedTableName(table.getDBLink())); + if (table.getDBLink() != null){ + /* Note: if the table is aliased, the aliased table is wrapped + * inside a DBTableAlias. So, to get the real table name + * we should get first the original table thanks to + * DBTableAlias.getOriginTable(). */ + if (table.getDBLink() instanceof DBTableAlias) + sql.append(getQualifiedTableName(((DBTableAlias)table.getDBLink()).getOriginTable())); + else + sql.append(getQualifiedTableName(table.getDBLink())); + } // Otherwise, use the whole table name given in the ADQL query: else sql.append(table.getFullTableName()); @@ -583,7 +592,19 @@ public abstract class JDBCTranslator implements ADQLTranslator { // Add the table alias, if any: if (table.hasAlias()){ sql.append(" AS "); - appendIdentifier(sql, table.getAlias(), table.isCaseSensitive(IdentifierField.ALIAS)); + /* In case where metadata are known, the alias must always be + * written case sensitively in order to ensure a translation + * stability (i.e. all references clearly point toward this alias + * whatever is their character case). */ + if (table.getDBLink() != null){ + if (table.isCaseSensitive(IdentifierField.ALIAS)) + appendIdentifier(sql, table.getAlias(), true); + else + appendIdentifier(sql, table.getAlias().toLowerCase(), true); + } + /* Otherwise, just write what is written in ADQL: */ + else + appendIdentifier(sql, table.getAlias(), table.isCaseSensitive(IdentifierField.ALIAS)); } return sql.toString(); @@ -651,13 +672,16 @@ public abstract class JDBCTranslator implements ADQLTranslator { if (column.getDBLink() != null){ DBColumn dbCol = column.getDBLink(); StringBuffer colName = new StringBuffer(); - // Use the table alias if any: - if (column.getAdqlTable() != null && column.getAdqlTable().hasAlias()) - appendIdentifier(colName, column.getAdqlTable().getAlias(), column.getAdqlTable().isCaseSensitive(IdentifierField.ALIAS)).append('.'); // Use the DBTable if any: - else if (dbCol.getTable() != null && dbCol.getTable().getDBName() != null) - colName.append(getQualifiedTableName(dbCol.getTable())).append('.'); + if (dbCol.getTable() != null && dbCol.getTable().getDBName() != null){ + /* Note: if the table is aliased, ensure no schema is prefixing + * this alias thanks to getTableName(..., false). */ + if (dbCol.getTable() instanceof DBTableAlias) + colName.append(getTableName(dbCol.getTable(), false)).append('.'); + else + colName.append(getQualifiedTableName(dbCol.getTable())).append('.'); + } // Otherwise, use the prefix of the column given in the ADQL query: else if (column.getTableName() != null) @@ -772,11 +796,11 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * Gets the default SQL output for the given ADQL function. - * + * * @param fct The ADQL function to format into SQL. - * + * * @return The corresponding SQL. - * + * * @throws TranslationException If there is an error during the translation. */ protected final String getDefaultADQLFunction(ADQLFunction fct) throws TranslationException{ @@ -846,40 +870,40 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * Convert any type provided by a JDBC driver into a type understandable by the ADQL/TAP library. - * + * * @param dbmsType Type returned by a JDBC driver. <i>Note: this value is returned by ResultSetMetadata.getColumnType(int) and correspond to a type of java.sql.Types</i> * @param rawDbmsTypeName Full name of the type returned by a JDBC driver. <i>Note: this name is returned by ResultSetMetadata.getColumnTypeName(int) ; this name may contain parameters</i> * @param dbmsTypeName Name of type, without the eventual parameters. <i>Note: this name is extracted from rawDbmsTypeName.</i> * @param typeParams The eventual type parameters (e.g. char string length). <i>Note: these parameters are extracted from rawDbmsTypeName.</i> - * + * * @return The corresponding ADQL/TAP type or NULL if the specified type is unknown. */ public abstract DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, final String dbmsTypeName, final String[] typeParams); /** * <p>Convert any type provided by the ADQL/TAP library into a type understandable by a JDBC driver.</p> - * + * * <p><i>Note: * The returned DBMS type may contain some parameters between brackets. * </i></p> - * + * * @param type The ADQL/TAP library's type to convert. - * + * * @return The corresponding DBMS type or NULL if the specified type is unknown. */ public abstract String convertTypeToDB(final DBType type); /** * <p>Parse the given JDBC column value as a geometry object and convert it into a {@link Region}.</p> - * + * * <p><i>Note: * Generally the returned object will be used to get its STC-S expression. * </i></p> - * + * * <p><i>Note: * If the given column value is NULL, NULL will be returned. * </i></p> - * + * * <p><i><b>Important note:</b> * This function is called ONLY for value of columns flagged as geometries by * {@link #convertTypeFromDB(int, String, String, String[])}. So the value should always @@ -887,11 +911,11 @@ public abstract class JDBCTranslator implements ADQLTranslator { * and that the conversion is finally impossible, this function SHOULD throw a * {@link tap.data.DataReadException}. * </i></p> - * + * * @param jdbcColValue A JDBC column value (returned by ResultSet.getObject(int)). - * + * * @return The corresponding {@link Region} if the given value is a geometry. - * + * * @throws ParseException If the given object is not a geometrical object * or can not be transformed into a {@link Region} object. */ @@ -899,20 +923,20 @@ public abstract class JDBCTranslator implements ADQLTranslator { /** * <p>Convert the given STC region into a DB column value.</p> - * + * * <p><i>Note: * This function is used only by the UPLOAD feature, to import geometries provided as STC-S expression in * a VOTable document inside a DB column. * </i></p> - * + * * <p><i>Note: * If the given region is NULL, NULL will be returned. * </i></p> - * + * * @param region The region to store in the DB. - * + * * @return The corresponding DB column object. - * + * * @throws ParseException If the given STC Region can not be converted into a DB object. */ public abstract Object translateGeometryToDB(final Region region) throws ParseException; diff --git a/test/adql/db/TestSeveralSubQueries.java b/test/adql/db/TestSubQueries.java similarity index 57% rename from test/adql/db/TestSeveralSubQueries.java rename to test/adql/db/TestSubQueries.java index bf9f485a1e7211d2f9ad6a8995231f12eda38db9..df4e177ecbda67ce184d5b8db97e0fdab4de54f3 100644 --- a/test/adql/db/TestSeveralSubQueries.java +++ b/test/adql/db/TestSubQueries.java @@ -12,17 +12,18 @@ import org.junit.Test; import adql.parser.ADQLParser; import adql.query.ADQLQuery; +import adql.translator.PostgreSQLTranslator; import tap.metadata.TAPMetadata; import tap.metadata.TAPTable; import tap.metadata.TableSetParser; -public class TestSeveralSubQueries { +public class TestSubQueries { @Before public void setUp() throws Exception{} @Test - public void test(){ + public void testSeveralSubqueries(){ try{ TableSetParser tsParser = new TableSetParser(); TAPMetadata esaMetaData = tsParser.parse(new File("test/adql/db/subquery_test_tables.xml")); @@ -41,4 +42,25 @@ public class TestSeveralSubQueries { } } + @Test + public void testFatherTableAliasIntoSubqueries(){ + try{ + TableSetParser tsParser = new TableSetParser(); + TAPMetadata esaMetaData = tsParser.parse(new File("test/adql/db/subquery_test_tables.xml")); + ArrayList<DBTable> esaTables = new ArrayList<DBTable>(esaMetaData.getNbTables()); + Iterator<TAPTable> itTables = esaMetaData.getTables(); + while(itTables.hasNext()) + esaTables.add(itTables.next()); + + ADQLParser adqlParser = new ADQLParser(new DBChecker(esaTables)); + + ADQLQuery query = adqlParser.parseQuery("SELECT oid FROM table1 as MyAlias WHERE oid IN (SELECT oid2 FROM table2 WHERE oid2 = myAlias.oid)"); + System.out.println((new PostgreSQLTranslator()).translate(query)); + assertEquals("SELECT \"myalias\".\"oid\" AS \"oid\"\nFROM \"public\".\"table1\" AS \"myalias\"\nWHERE \"myalias\".\"oid\" IN (SELECT \"public\".\"table2\".\"oid2\" AS \"oid2\"\nFROM \"public\".\"table2\"\nWHERE \"public\".\"table2\".\"oid2\" = \"myalias\".\"oid\")", (new PostgreSQLTranslator()).translate(query)); + }catch(Exception ex){ + ex.printStackTrace(System.err); + fail("No error expected! (see console for more details)"); + } + } + } diff --git a/test/adql/translator/TestSQLServerTranslator.java b/test/adql/translator/TestSQLServerTranslator.java index 49685a7d10562c671d638b31c1561d30ed7754e8..ed605d8a46b3beb71be3f7ca67cace9d7462349b 100644 --- a/test/adql/translator/TestSQLServerTranslator.java +++ b/test/adql/translator/TestSQLServerTranslator.java @@ -46,10 +46,10 @@ public class TestSQLServerTranslator { SQLServerTranslator translator = new SQLServerTranslator(); // Test the FROM part: - assertEquals("\"aTable\" AS A INNER JOIN \"anotherTable\" AS B ON A.\"id\"=B.\"id\" AND A.\"name\"=B.\"name\"", translator.translate(query.getFrom())); + assertEquals("\"aTable\" AS \"a\" INNER JOIN \"anotherTable\" AS \"b\" ON \"a\".\"id\"=\"b\".\"id\" AND \"a\".\"name\"=\"b\".\"name\"", translator.translate(query.getFrom())); // Test the SELECT part (in order to ensure the usual common columns (due to NATURAL) are actually translated as columns of the first joined table): - assertEquals("SELECT A.\"id\" AS \"id\" , A.\"name\" AS \"name\" , A.\"aColumn\" AS \"aColumn\" , B.\"anotherColumn\" AS \"anotherColumn\"", translator.translate(query.getSelect())); + assertEquals("SELECT \"a\".\"id\" AS \"id\" , \"a\".\"name\" AS \"name\" , \"a\".\"aColumn\" AS \"aColumn\" , \"b\".\"anotherColumn\" AS \"anotherColumn\"", translator.translate(query.getSelect())); }catch(ParseException pe){ pe.printStackTrace(); @@ -69,10 +69,10 @@ public class TestSQLServerTranslator { SQLServerTranslator translator = new SQLServerTranslator(); // Test the FROM part: - assertEquals("\"aTable\" AS A INNER JOIN \"anotherTable\" AS B ON A.\"name\"=B.\"name\"", translator.translate(query.getFrom())); + assertEquals("\"aTable\" AS \"a\" INNER JOIN \"anotherTable\" AS \"b\" ON \"a\".\"name\"=\"b\".\"name\"", translator.translate(query.getFrom())); // Test the SELECT part (in order to ensure the usual common columns (due to USING) are actually translated as columns of the first joined table): - assertEquals("SELECT B.\"id\" AS \"id\" , A.\"name\" AS \"name\" , A.\"aColumn\" AS \"aColumn\" , B.\"anotherColumn\" AS \"anotherColumn\"", translator.translate(query.getSelect())); + assertEquals("SELECT \"b\".\"id\" AS \"id\" , \"a\".\"name\" AS \"name\" , \"a\".\"aColumn\" AS \"aColumn\" , \"b\".\"anotherColumn\" AS \"anotherColumn\"", translator.translate(query.getSelect())); }catch(ParseException pe){ pe.printStackTrace();