diff --git a/src/adql/db/DBChecker.java b/src/adql/db/DBChecker.java index 89bb1a25a8feb1b1082eb7acabca1c66213b4495..b83e7fefc3f4f2a37481ef165d4c08e765bcd153 100644 --- a/src/adql/db/DBChecker.java +++ b/src/adql/db/DBChecker.java @@ -42,6 +42,7 @@ import adql.parser.QueryChecker; import adql.parser.grammar.ParseException; import adql.query.ADQLIterator; import adql.query.ADQLObject; +import adql.query.ADQLOrder; import adql.query.ADQLQuery; import adql.query.ClauseADQL; import adql.query.ClauseSelect; @@ -668,28 +669,38 @@ 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> + * 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. * - * <b>Management of selected columns' references</b> + * <h4>Management of selected columns' references</h4> * <p> - * A column reference is not only a direct reference to a table column using a column name. - * It can also be a reference to an item of the SELECT clause (which will then call a "selected column"). - * That kind of reference can be either an index (an unsigned integer starting from 1 to N, where N is the - * number selected columns), or the name/alias of the column. + * A column reference is not only a direct reference to a table column + * using a column name. It can also be a reference to an item of the SELECT + * clause (which will then call a "selected column"). That kind of + * reference can be either an index (an unsigned integer starting from + * 1 to N, where N is the number selected columns), or the name/alias of + * the column. * </p> * <p> - * These references are also checked, in a second step, in this function. Thus, column metadata are - * also attached to them, as common columns. + * These references are also checked, in second steps, 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. - * <i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i> + * @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><b>Note:</b> this parameter is NULL if this + * function is called with the root/father query as + * parameter.</i> * @param mapTables List of all resolved tables. - * @param list List of column metadata to complete in this function each time a column reference is resolved. - * @param errors List of errors to complete in this function each time an unknown table or column is encountered. + * @param list List of column metadata to complete in this function + * each time a column reference is resolved. + * @param errors List of errors to complete in this function each + * time an unknown table or column is encountered. */ protected void resolveColumns(final ADQLQuery query, final Stack<SearchColumnList> fathersList, final Map<DBTable, ADQLTable> mapTables, final SearchColumnList list, final UnresolvedIdentifiersException errors) { ISearchHandler sHandler; @@ -699,12 +710,7 @@ public class DBChecker implements QueryChecker { sHandler.search(query); for(ADQLObject result : sHandler) { try { - ADQLColumn adqlColumn = (ADQLColumn)result; - // resolve the column: - DBColumn dbColumn = resolveColumn(adqlColumn, list, fathersList); - // link with the matched DBColumn: - adqlColumn.setDBLink(dbColumn); - adqlColumn.setAdqlTable(mapTables.get(dbColumn.getTable())); + resolveColumn((ADQLColumn)result, list, mapTables, fathersList); } catch(ParseException pe) { errors.addException(pe); } @@ -712,26 +718,16 @@ public class DBChecker implements QueryChecker { // Check the GROUP BY items: ClauseSelect select = query.getSelect(); - sHandler = new SearchColumnHandler(); - sHandler.search(query.getGroupBy()); - for(ADQLObject result : sHandler) { - try { - ADQLColumn adqlColumn = (ADQLColumn)result; - // resolve the column: - DBColumn dbColumn = checkGroupByItem(adqlColumn, select, list); - // link with the matched DBColumn: - if (dbColumn != null) { - adqlColumn.setDBLink(dbColumn); - adqlColumn.setAdqlTable(mapTables.get(dbColumn.getTable())); - } - } catch(ParseException pe) { - errors.addException(pe); - } - } + checkGroupBy(query.getGroupBy(), select, list, mapTables, fathersList, errors); + + // Check the ORDER BY items: + checkOrderBy(query.getOrderBy(), select, list, mapTables, fathersList, errors); - // Check the correctness of all column references (= references to selected columns): - /* Note: no need to provide the father tables when resolving column references, - * because no father column can be used in ORDER BY. */ + /* Check the correctness of all column references (= references to + * selected columns): + * + * NOTE: no need to provide the father tables when resolving column + * references, because no father column can be used in ORDER BY. */ sHandler = new SearchColReferenceHandler(); sHandler.search(query); for(ADQLObject result : sHandler) { @@ -750,24 +746,78 @@ public class DBChecker implements QueryChecker { } /** - * <p>Resolve the given column, that's to say search for the corresponding {@link DBColumn}.</p> + * Resolve the given column (i.e. search for the corresponding + * {@link DBColumn}) and update the given {@link ADQLColumn} with these + * found DB metadata. * * <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). + * The fourth 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> + * @param mapTables List of all resolved tables. + * @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. + * @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. + * @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 #resolveColumn(ADQLColumn, SearchColumnList, Stack) + * + * @since 2.0 + */ + protected final DBColumn resolveColumn(final ADQLColumn column, final SearchColumnList dbColumns, final Map<DBTable, ADQLTable> mapTables, Stack<SearchColumnList> fathersList) throws ParseException { + // Resolve the column: + DBColumn dbColumn = resolveColumn(column, dbColumns, fathersList); + + // Link the given ADQLColumn with the matched DBColumn: + column.setDBLink(dbColumn); + if (mapTables != null) + column.setAdqlTable(mapTables.get(dbColumn.getTable())); + + return dbColumn; + } + + /** + * Resolve the given column, that's to say search for the corresponding + * {@link DBColumn}. + * + * <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. */ protected DBColumn resolveColumn(final ADQLColumn column, final SearchColumnList dbColumns, Stack<SearchColumnList> fathersList) throws ParseException { List<DBColumn> foundColumns = dbColumns.search(column); @@ -794,24 +844,156 @@ public class DBChecker implements QueryChecker { } /** - * Check whether the given column corresponds to a selected item's alias or to an existing column. + * Check and resolve all columns (or column references) inside the given + * GROUP BY clause. + * + * @param groupBy The GROUP BY to check. + * @param select The SELECT clause (and all its selected items). + * @param list List of all available {@link DBColumn}s. + * @param mapTables List of all resolved tables. + * @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. + * + * @since 2.0 + */ + protected void checkGroupBy(final ClauseADQL<ADQLOperand> groupBy, final ClauseSelect select, final SearchColumnList list, final Map<DBTable, ADQLTable> mapTables, Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors) { + for(ADQLOperand obj : groupBy) { + try { + if (obj instanceof ADQLColumn) { + ADQLColumn adqlColumn = (ADQLColumn)obj; + /* resolve the column either as a selected column reference + * or as a normal column: */ + if (adqlColumn.getTableName() == null) + resolveColumnNameReference(adqlColumn, select, list, mapTables); + else + resolveColumn(adqlColumn, list, fathersList); + } else { + ISearchHandler sHandler = new SearchColumnHandler(); + sHandler.search(obj); + for(ADQLObject result : sHandler) { + try { + resolveColumn((ADQLColumn)result, list, mapTables, fathersList); + } catch(ParseException pe) { + errors.addException(pe); + } + } + } + } catch(ParseException pe) { + errors.addException(pe); + } + } + } + + /** + * Check and resolve all columns (or column references) inside the given + * ORDER BY clause. + * + * @param orderBy The ORDER BY to check. + * @param select The SELECT clause (and all its selected items). + * @param list List of all available {@link DBColumn}s. + * @param mapTables List of all resolved tables. + * @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. + * + * @since 2.0 + */ + protected void checkOrderBy(final ClauseADQL<ADQLOrder> orderBy, final ClauseSelect select, final SearchColumnList list, final Map<DBTable, ADQLTable> mapTables, Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors) { + for(ADQLObject obj : orderBy) { + try { + ADQLOrder order = (ADQLOrder)obj; + if (order.getExpression() != null) { + ADQLOperand expr = order.getExpression(); + if (expr instanceof ADQLColumn) { + ADQLColumn adqlColumn = (ADQLColumn)expr; + /* resolve the column either as a selected column reference + * or as a normal column: */ + if (adqlColumn.getTableName() == null) + resolveColumnNameReference(adqlColumn, select, list, mapTables); + else + resolveColumn(adqlColumn, list, fathersList); + } else { + ISearchHandler sHandler = new SearchColumnHandler(); + sHandler.search(expr); + for(ADQLObject result : sHandler) { + try { + resolveColumn((ADQLColumn)result, list, mapTables, fathersList); + } catch(ParseException pe) { + errors.addException(pe); + } + } + } + } + } catch(ParseException pe) { + errors.addException(pe); + } + } + } + + /** + * 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, + * @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. + * @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 + * + * @deprecated Since 2.0, this function has been renamed into + * {@link #resolveColumnNameReference(ADQLColumn, ClauseSelect, SearchColumnList, Map)}. */ - protected DBColumn checkGroupByItem(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException { + @Deprecated + protected final DBColumn checkGroupByItem(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException { + return resolveColumnNameReference(col, select, dbColumns, null); + } + + /** + * 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, Map, Stack) + * + * @since 2.0 + */ + protected DBColumn resolveColumnNameReference(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns, final Map<DBTable, ADQLTable> mapTables) throws ParseException { /* If the column name is not qualified, it may be a SELECT-item's alias. * So, try resolving the name as an alias. * If it fails, perform the normal column resolution.*/ @@ -822,51 +1004,36 @@ public class DBChecker implements QueryChecker { else if (founds.size() > 1) throw new UnresolvedColumnException(col, founds.get(0).getAlias(), founds.get(1).getAlias()); } - return resolveColumn(col, dbColumns, null); + return resolveColumn(col, dbColumns, mapTables, null); } /** - * Check whether the given column reference corresponds to a selected item (column or an expression with an alias) - * or to an existing column. + * Check whether the given column reference corresponds to a selected item + * (column or an expression with an alias). * * @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. + * @return The corresponding {@link DBColumn} if this reference is + * actually referencing a selected column, + * NULL 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) + * @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. */ protected DBColumn checkColumnReference(final ColumnReference colRef, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException { - if (colRef.isIndex()) { - int index = colRef.getColumnIndex(); - if (index > 0 && index <= select.size()) { - SelectItem item = select.get(index - 1); - if (item.getOperand() instanceof ADQLColumn) - return ((ADQLColumn)item.getOperand()).getDBLink(); - else - return null; - } else - throw new ParseException("Column index out of bounds: " + index + " (must be between 1 and " + select.size() + ") !", colRef.getPosition()); - } else { - ADQLColumn col = new ADQLColumn(null, colRef.getColumnName()); - col.setCaseSensitive(colRef.isCaseSensitive()); - col.setPosition(colRef.getPosition()); - - // search among the select_item aliases: - List<SelectItem> founds = select.searchByAlias(colRef.getColumnName(), colRef.isCaseSensitive()); - if (founds.size() == 1) + int index = colRef.getColumnIndex(); + if (index > 0 && index <= select.size()) { + SelectItem item = select.get(index - 1); + if (item.getOperand() instanceof ADQLColumn) + return ((ADQLColumn)item.getOperand()).getDBLink(); + else return null; - else if (founds.size() > 1) - throw new UnresolvedColumnException(col, founds.get(0).getAlias(), founds.get(1).getAlias()); - - // check the corresponding column: - return resolveColumn(col, dbColumns, null); - } + } else + throw new ParseException("Column index out of bounds: " + index + " (must be between 1 and " + select.size() + ") !", colRef.getPosition()); } /** @@ -1426,21 +1593,31 @@ public class DBChecker implements QueryChecker { /* *************** */ /** - * Lets searching all {@link ADQLColumn} in the given object, EXCEPT in the GROUP BY clause. + * Lets searching all {@link ADQLColumn} in the given object, + * EXCEPT in the GROUP BY and ORDER BY clauses. * * <p> - * {@link ADQLColumn}s of the GROUP BY may be aliases and so, they can not be checked - * exactly as a normal column. + * {@link ADQLColumn}s of the GROUP BY and ORDER 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) + * <p> + * {@link ADQLColumn} of a {@link ColumnReference} may be an alias, they + * can not be checked exactly as a normal column. + * </p> + * + * @author Grégory Mantelet (ARI;CDS) + * @version 2.0 (08/2019) * @since 1.4 */ private static class SearchColumnOutsideGroupByHandler extends SearchColumnHandler { @Override protected boolean goInto(final ADQLObject obj) { - return !(obj instanceof ClauseADQL<?> && ((ClauseADQL<?>)obj).getName() != null && ((ClauseADQL<?>)obj).getName().equalsIgnoreCase("GROUP BY")) && super.goInto(obj); + if (obj instanceof ClauseADQL<?> && ((ClauseADQL<?>)obj).getName() != null) { + ClauseADQL<?> clause = (ClauseADQL<?>)obj; + return !(clause.getName().equalsIgnoreCase("GROUP BY") || clause.getName().equalsIgnoreCase("ORDER BY")); + } else + return super.goInto(obj); } } diff --git a/src/adql/parser/ADQLParser.java b/src/adql/parser/ADQLParser.java index dec0fe5b8ec9c3d0893667f8494507e8d1e7b79c..0df8211d4ac4114ef1d8013d8b8fc97bf42a9466 100644 --- a/src/adql/parser/ADQLParser.java +++ b/src/adql/parser/ADQLParser.java @@ -50,7 +50,6 @@ import adql.query.ClauseADQL; import adql.query.ClauseConstraints; import adql.query.ClauseSelect; import adql.query.from.FromContent; -import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLOperand; import adql.query.operand.StringConstant; import adql.query.operand.function.geometry.BoxFunction; @@ -1019,7 +1018,7 @@ public class ADQLParser { * * @throws ParseException If the syntax of the given clause is incorrect. */ - public final ClauseADQL<ADQLColumn> parseGroupBy(java.lang.String adql) throws ParseException { + public final ClauseADQL<ADQLOperand> parseGroupBy(java.lang.String adql) throws ParseException { // Reset the parser with the string to parse: try { grammarParser.reset(new java.io.ByteArrayInputStream(adql.getBytes())); diff --git a/src/adql/parser/ADQLQueryFactory.java b/src/adql/parser/ADQLQueryFactory.java index fb78f4c15279f0f896da4fb039398402a616aa9e..e6558e66cdfc57526aa691244c53de881ee09c95 100644 --- a/src/adql/parser/ADQLQueryFactory.java +++ b/src/adql/parser/ADQLQueryFactory.java @@ -60,11 +60,11 @@ import adql.query.operand.OperationType; import adql.query.operand.StringConstant; import adql.query.operand.WrappedOperand; import adql.query.operand.function.DefaultUDF; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.MathFunction; import adql.query.operand.function.MathFunctionType; import adql.query.operand.function.SQLFunction; import adql.query.operand.function.SQLFunctionType; -import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.UserDefinedFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; @@ -459,18 +459,30 @@ public class ADQLQueryFactory { @Deprecated public ADQLOrder createOrder(final int ind, final boolean desc, final TextPosition position) throws Exception { ADQLOrder order = new ADQLOrder(ind, desc); - if (order != null) - order.setPosition(position); + order.setPosition(position); return order; } + /** + * @deprecated Since 2.0, a column reference can be a qualified + * column (i.e. an {@link ADQLColumn}). You should use + * {@link #createOrder(ADQLOperand, boolean)} instead. + */ + @Deprecated public ADQLOrder createOrder(final IdentifierItem colName, final boolean desc) throws Exception { ADQLOrder order = new ADQLOrder(colName.identifier, desc); - if (order != null) - order.setCaseSensitive(colName.caseSensitivity); + ((ADQLColumn)order.getExpression()).setCaseSensitive(colName.caseSensitivity); + ((ADQLColumn)order.getExpression()).setPosition(colName.position); return order; } + /** + * @since 2.0 + */ + public ADQLOrder createOrder(final ADQLOperand expr, final boolean desc) throws Exception { + return new ADQLOrder(expr, desc); + } + /** * @deprecated since 1.4 ; Former version's mistake: an ORDER BY item is * either a regular/delimited column name or an @@ -481,33 +493,14 @@ public class ADQLQueryFactory { @Deprecated public ADQLOrder createOrder(final IdentifierItems idItems, final boolean desc) throws Exception { ADQLOrder order = new ADQLOrder(idItems.join("."), desc); - if (order != null) - order.setCaseSensitive(idItems.getColumnCaseSensitivity()); + order.getColumnReference().setCaseSensitive(idItems.getColumnCaseSensitivity()); + order.getColumnReference().setPosition(idItems.getPosition()); return order; } - public ColumnReference createColRef(final IdentifierItem idItem) throws Exception { - ColumnReference colRef = new ColumnReference(idItem.identifier); - if (colRef != null) { - colRef.setPosition(idItem.position); - colRef.setCaseSensitive(idItem.caseSensitivity); - } - return colRef; - } - - public ColumnReference createColRef(final IdentifierItems idItems) throws Exception { - ColumnReference colRef = new ColumnReference(idItems.join(".")); - if (colRef != null) { - colRef.setPosition(idItems.getPosition()); - colRef.setCaseSensitive(idItems.getColumnCaseSensitivity()); - } - return colRef; - } - public ColumnReference createColRef(final int index, final TextPosition position) throws Exception { ColumnReference colRef = new ColumnReference(index); - if (colRef != null) - colRef.setPosition(position); + colRef.setPosition(position); return colRef; } diff --git a/src/adql/parser/grammar/adqlGrammar200.jj b/src/adql/parser/grammar/adqlGrammar200.jj index c622b8a35901499ed45ef3b1297e71b075cf1733..d1e6b4a78806e6e59c7355ceeb42749b5340c335 100644 --- a/src/adql/parser/grammar/adqlGrammar200.jj +++ b/src/adql/parser/grammar/adqlGrammar200.jj @@ -556,10 +556,10 @@ void Where(): {ClauseConstraints where = query.getWhere(); ADQLConstraint condit } } -void GroupBy(): {ClauseADQL<ADQLColumn> groupBy = query.getGroupBy(); ADQLColumn colRef = null; Token start;} { +void GroupBy(): {ClauseADQL<ADQLOperand> groupBy = query.getGroupBy(); ADQLColumn colRef = null; Token start;} { start=<GROUP> <BY> colRef=Column() { groupBy.add(colRef); } ( <COMMA> colRef=Column() { groupBy.add(colRef); } )* - { groupBy.setPosition(new TextPosition(start.beginLine, start.beginColumn, colRef.getPosition().endLine, colRef.getPosition().endColumn)); } + { groupBy.setPosition(new TextPosition(new TextPosition(start), colRef.getPosition())); } } void Having(): {ClauseConstraints having = query.getHaving(); Token start;} { @@ -641,10 +641,17 @@ ADQLOrder OrderItem(): {IdentifierItem identifier = null; Token ind = null, desc ADQLOrder order = null; if (identifier != null){ order = queryFactory.createOrder(identifier, desc!=null); - order.setPosition(identifier.position); + if (desc == null) + order.setPosition(identifier.position); + else + order.setPosition(new TextPosition(identifier.position, new TextPosition(desc))); }else{ order = queryFactory.createOrder(Integer.parseInt(ind.image), desc!=null); - order.setPosition(new TextPosition(ind)); + order.getColumnReference().setPosition(new TextPosition(ind)); + if (desc == null) + order.setPosition(new TextPosition(ind)); + else + order.setPosition(new TextPosition(ind, desc)); } return order; }catch(Exception ex){ @@ -853,14 +860,16 @@ ADQLOperand ValueExpression(): {ADQLOperand valueExpr = null; Token left, right; | LOOKAHEAD(3) valueExpr=Factor() /* At this position in this switch, all possibilities (including - * Column()) have already been tested and failed. + * Column() and NumericFunction) have already been tested and failed. * - * So, this final choice actually aims to throw an error set with the + * So, these final choices actually aim to throw an error set with the * current token and with an error message implying that a column name - * was expected (which is generally the case in an ADQL query). - * - * Note: This choice will generally be reached if an unexpected ADQL/SQL - * word is ending the query. */ + * was expected (which is generally the case in an ADQL query) or that + * the parameters of the numeric function are incorrect. */ + | LOOKAHEAD(2) valueExpr=NumericFunction() + /* + * Note: Besides, this particular choice will generally be reached if an + * unexpected ADQL/SQL word is ending the query. */ | valueExpr=Column() ) {return valueExpr;} }catch(Exception ex){ @@ -1024,8 +1033,7 @@ ClauseConstraints ConditionsList(ClauseConstraints clause): {ADQLConstraint cons } ADQLConstraint Constraint(): {ADQLConstraint constraint = null; Token start, end;} { - (LOOKAHEAD(<EXISTS> | ValueExpression()) constraint=Predicate() - | ( + (LOOKAHEAD(<LEFT_PAR> [<NOT>] Predicate()) start=<LEFT_PAR> { try{ @@ -1037,7 +1045,9 @@ ADQLConstraint Constraint(): {ADQLConstraint constraint = null; Token start, en ConditionsList((ConstraintsGroup)constraint) end=<RIGHT_PAR> { ((ConstraintsGroup)constraint).setPosition(new TextPosition(start, end)); } - )) + | + constraint=Predicate() + ) {return constraint;} } diff --git a/src/adql/parser/grammar/adqlGrammar201.jj b/src/adql/parser/grammar/adqlGrammar201.jj index 53bb5f81709aec53a6807f83201f9c97259d292b..7ee135a2f5a80d3c0ad37017e8bbae4b48f494f1 100644 --- a/src/adql/parser/grammar/adqlGrammar201.jj +++ b/src/adql/parser/grammar/adqlGrammar201.jj @@ -577,10 +577,10 @@ void Where(): {ClauseConstraints where = query.getWhere(); ADQLConstraint condit } } -void GroupBy(): {ClauseADQL<ADQLColumn> groupBy = query.getGroupBy(); ADQLColumn colRef = null; Token start;} { - start=<GROUP> <BY> colRef=Column() { groupBy.add(colRef); } - ( <COMMA> colRef=Column() { groupBy.add(colRef); } )* - { groupBy.setPosition(new TextPosition(start.beginLine, start.beginColumn, colRef.getPosition().endLine, colRef.getPosition().endColumn)); } +void GroupBy(): {ClauseADQL<ADQLOperand> groupBy = query.getGroupBy(); ADQLOperand item = null; Token start;} { + start=<GROUP> <BY> item=ValueExpression() { groupBy.add(item); } + ( <COMMA> item=ValueExpression() { groupBy.add(item); } )* + { groupBy.setPosition(new TextPosition(new TextPosition(start), item.getPosition())); } } void Having(): {ClauseConstraints having = query.getHaving(); Token start;} { @@ -670,18 +670,30 @@ ADQLColumn Column(): {IdentifierItems identifiers;} { } } -ADQLOrder OrderItem(): {IdentifierItem identifier = null; Token ind = null, desc = null;}{ - (identifier=Identifier() | ind=<UNSIGNED_INTEGER>) (<ASC> | desc=<DESC>)? +ADQLOrder OrderItem(): {ADQLOperand expr = null; Token ind = null, desc = null;}{ + expr=ValueExpression() + { + if (expr instanceof NumericConstant && token.kind == UNSIGNED_INTEGER) + ind = token; + } + (<ASC> | desc=<DESC>)? { try{ ADQLOrder order = null; - if (identifier != null){ - order = queryFactory.createOrder(identifier, desc!=null); - order.setPosition(identifier.position); - }else{ + if (ind != null){ order = queryFactory.createOrder(Integer.parseInt(ind.image), desc!=null); - order.setPosition(new TextPosition(ind)); - } + order.getColumnReference().setPosition(new TextPosition(ind)); + if (desc == null) + order.setPosition(new TextPosition(ind)); + else + order.setPosition(new TextPosition(ind, desc)); + }else{ + order = queryFactory.createOrder(expr, desc!=null); + if (desc == null) + order.setPosition(expr.getPosition()); + else + order.setPosition(new TextPosition(expr.getPosition(), new TextPosition(desc))); + } return order; }catch(Exception ex){ throw generateParseException(ex); @@ -1083,8 +1095,7 @@ ClauseConstraints ConditionsList(ClauseConstraints clause): {ADQLConstraint cons } ADQLConstraint Constraint(): {ADQLConstraint constraint = null; Token start, end;} { - (LOOKAHEAD(<EXISTS> | ValueExpression()) constraint=Predicate() - | ( + (LOOKAHEAD(<LEFT_PAR> [<NOT>] Predicate()) start=<LEFT_PAR> { try{ @@ -1096,7 +1107,9 @@ ADQLConstraint Constraint(): {ADQLConstraint constraint = null; Token start, en ConditionsList((ConstraintsGroup)constraint) end=<RIGHT_PAR> { ((ConstraintsGroup)constraint).setPosition(new TextPosition(start, end)); } - )) + | + constraint=Predicate() + ) {return constraint;} } diff --git a/src/adql/query/ADQLOrder.java b/src/adql/query/ADQLOrder.java index 744a0e876a54c48c83eeb77cae5fd29f949c91bb..b43c95fd3b81938b43b3022e86d5702127cac1ca 100644 --- a/src/adql/query/ADQLOrder.java +++ b/src/adql/query/ADQLOrder.java @@ -16,17 +16,47 @@ package adql.query; * 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 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS) + * Copyright 2012-2019 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS) */ +import java.util.NoSuchElementException; + +import adql.parser.feature.LanguageFeature; +import adql.query.operand.ADQLColumn; +import adql.query.operand.ADQLOperand; + /** * Represents an item of the ORDER BY list: that's to say a column reference - * plus a sorting indication (ASC, DESC). + * or a value expression, and an optional sorting indication (ASC, DESC). * * @author Grégory Mantelet (CDS) - * @version 06/2011 + * @version 2.0 (08/2019) */ -public class ADQLOrder extends ColumnReference { +public class ADQLOrder implements ADQLObject { + + /** Description of this ADQL Feature. + * @since 2.0 */ + public static final LanguageFeature FEATURE = new LanguageFeature(null, "ORDER_BY_ITEM", false, "Column reference or expression on which the query result must be ordered."); + + /** Position in the original ADQL query string. + * @since 2.0 */ + private TextPosition position = null; + + /** Reference to the column on which the query result must be ordered. + * <p><i><b>Important note:</b> + * If NULL, this ORDER BY is done on a value expression. + * In such case, see {@link #expression}. + * </i></p> + * @since 2.0 */ + protected ColumnReference colRef = null; + + /** Value on which the query result must be ordered. + * <p><i><b>Important note:</b> + * If NULL, this ORDER BY is done on a column reference. + * In such case, see {@link #colRef}. + * </i></p> + * @since 2.0 */ + protected ADQLOperand expression = null; /** Gives an indication about how to order the results of a query. * (<code>true</code> for DESCending, <code>false</code> for ASCending) */ @@ -40,9 +70,9 @@ public class ADQLOrder extends ColumnReference { * * @throws ArrayIndexOutOfBoundsException If the index is less or equal 0. * - * @see ADQLOrder#ADQLOrder(int, boolean) + * @see #ADQLOrder(int, boolean) */ - public ADQLOrder(int colIndex) throws ArrayIndexOutOfBoundsException { + public ADQLOrder(final int colIndex) throws ArrayIndexOutOfBoundsException { this(colIndex, false); } @@ -55,10 +85,11 @@ public class ADQLOrder extends ColumnReference { * <code>false</code> means ASCending order. * * @throws ArrayIndexOutOfBoundsException If the index is less or equal 0. + * + * @see ColumnReference#ColumnReference(int) ColumnReference(int) */ - public ADQLOrder(int colIndex, boolean desc) throws ArrayIndexOutOfBoundsException { - super(colIndex); - descSorting = desc; + public ADQLOrder(final int colIndex, final boolean desc) throws ArrayIndexOutOfBoundsException { + setOrder(colIndex, desc); } /** @@ -70,9 +101,9 @@ public class ADQLOrder extends ColumnReference { * @throws NullPointerException If the given name is NULL * or is an empty string. * - * @see ADQLOrder#ADQLOrder(String, boolean) + * @see #ADQLOrder(String, boolean) */ - public ADQLOrder(String colName) throws NullPointerException { + public ADQLOrder(final String colName) throws NullPointerException { this(colName, false); } @@ -86,19 +117,62 @@ public class ADQLOrder extends ColumnReference { * * @throws NullPointerException If the given name is NULL * or is an empty string. + * + * @see ColumnReference#ColumnReference(String) ColumnReference(String) + * + * @deprecated Since ADQL-2.1, a column reference can be a qualified + * column (i.e. an {@link ADQLColumn}). You should use + * {@link #ADQLOrder(ADQLOperand)} instead. */ - public ADQLOrder(String colName, boolean desc) throws NullPointerException { - super(colName); - descSorting = desc; + @Deprecated + public ADQLOrder(final String colName, final boolean desc) throws NullPointerException { + this(new ADQLColumn(null, colName), desc); + } + + /** + * Builds an order indication with the expression on which an ASCending + * ordering will be done. + * + * @param expr The expression to order on. + * + * @throws NullPointerException If the given expression is NULL. + * + * @see #ADQLOrder(ADQLOperand) + * + * @since 2.0 + */ + public ADQLOrder(final ADQLOperand expr) throws NullPointerException { + this(expr, false); + } + + /** + * Builds an order indication with the expression on which the specified + * ordering will be done. + * + * @param expr The expression to order on. + * @param desc <code>true</code> means DESCending order, + * <code>false</code> means ASCending order. + * + * @throws NullPointerException If the given expression is NULL. + * + * @since 2.0 + */ + public ADQLOrder(final ADQLOperand expr, final boolean desc) throws NullPointerException { + setOrder(expr, desc); } /** * Builds an ORDER BY item by copying the given one. * * @param toCopy The ORDER BY item to copy. + * + * @throws Exception If the copy failed. */ - public ADQLOrder(ADQLOrder toCopy) { - super(toCopy); + public ADQLOrder(ADQLOrder toCopy) throws Exception { + if (toCopy.colRef != null) + colRef = (ColumnReference)toCopy.colRef.getCopy(); + if (toCopy.expression != null) + expression = (ADQLOperand)toCopy.expression.getCopy(); descSorting = toCopy.descSorting; } @@ -112,6 +186,28 @@ public class ADQLOrder extends ColumnReference { return descSorting; } + /** + * Get the reference of column on which the query result will be ordered. + * + * @return The set column reference. <i>Might be NULL.</i> + * + * @since 2.0 + */ + public final ColumnReference getColumnReference() { + return colRef; + } + + /** + * Get the expression on which the query result will be ordered. + * + * @return The set expression. <i>Might be NULL.</i> + * + * @since 2.0 + */ + public final ADQLOperand getExpression() { + return expression; + } + /** * Updates the current order indication. * @@ -122,11 +218,12 @@ public class ADQLOrder extends ColumnReference { * @throws IndexOutOfBoundsException If the given index is less * or equal 0. */ - public void setOrder(int colIndex, boolean desc) throws ArrayIndexOutOfBoundsException { + public void setOrder(final int colIndex, final boolean desc) throws ArrayIndexOutOfBoundsException { if (colIndex <= 0) throw new ArrayIndexOutOfBoundsException("Impossible to make a reference to the " + colIndex + "th column: a column index must be greater or equal 1!"); - setColumnIndex(colIndex); + colRef = new ColumnReference(colIndex); + expression = null; descSorting = desc; } @@ -139,13 +236,41 @@ public class ADQLOrder extends ColumnReference { * * @throws NullPointerException If the given name is NULL * or is an empty string. + * + * @deprecated Since ADQL-2.1, a column reference can be a qualified + * column (i.e. an {@link ADQLColumn}). You should use + * {@link #setOrder(ADQLOperand, boolean)} instead. */ - public void setOrder(String colName, boolean desc) throws NullPointerException { + @Deprecated + public void setOrder(final String colName, final boolean desc) throws NullPointerException { if (colName == null) throw new NullPointerException("Impossible to make a reference: the given name is null or is an empty string!"); - setColumnName(colName); + colRef = null; + expression = new ADQLColumn(null, colName); + descSorting = desc; + position = null; + } + + /** + * Updates the current order indication. + * + * @param expr The expression to order on. + * @param desc <code>true</code> means DESCending order, + * <code>false</code> means ASCending order. + * + * @throws NullPointerException If the given expression is NULL. + * + * @since 2.0 + */ + public void setOrder(final ADQLOperand expr, boolean desc) throws NullPointerException { + if (expr == null) + throw new NullPointerException("Impossible to make a reference: the given expression is null!"); + + colRef = null; + expression = expr; descSorting = desc; + position = null; } @Override @@ -155,12 +280,77 @@ public class ADQLOrder extends ColumnReference { @Override public String getName() { - return super.getName() + (descSorting ? " DESC" : " ASC"); + return (colRef != null ? colRef.getName() : expression.getName()) + (descSorting ? "_DESC" : "_ASC"); } @Override public String toADQL() { - return super.toADQL() + (descSorting ? " DESC" : " ASC"); + return (colRef != null ? colRef.toADQL() : expression.toADQL()) + (descSorting ? " DESC" : " ASC"); + } + + /** + * Gets the position in the original ADQL query string. + * + * @return The position of this {@link ColumnReference}. + */ + @Override + public final TextPosition getPosition() { + return position; + } + + /** + * Sets the position at which this {@link ColumnReference} has been found in the original ADQL query string. + * + * @param pos Position of this {@link ColumnReference}. + */ + public void setPosition(final TextPosition pos) { + position = pos; + } + + @Override + public final LanguageFeature getFeatureDescription() { + return FEATURE; + } + + @Override + public ADQLIterator adqlIterator() { + return new ADQLIterator() { + + private boolean itemDone = false; + + @Override + public ADQLObject next() { + if (itemDone) + throw new NoSuchElementException(); + else + itemDone = true; + return (colRef != null ? colRef : expression); + } + + @Override + public boolean hasNext() { + return !itemDone; + } + + @Override + public void replace(final ADQLObject replacer) throws UnsupportedOperationException, IllegalStateException { + if (!itemDone) + throw new IllegalStateException("No iteration done yet!"); + else if (replacer == null) + throw new UnsupportedOperationException("Impossible to delete a column reference or an expression from an ORDER BY item! You must delete the complete ORDER BY item."); + + if (replacer instanceof ColumnReference) { + colRef = (ColumnReference)replacer; + expression = null; + } else if (replacer instanceof ADQLOperand) { + colRef = null; + expression = (ADQLOperand)replacer; + } else + throw new UnsupportedOperationException("Impossible to replace a column reference or a value expression by a " + replacer.getClass().getName() + "!"); + + position = null; + } + }; } } diff --git a/src/adql/query/ADQLQuery.java b/src/adql/query/ADQLQuery.java index 45dbbc9b5735cee8da81934dd733fbf479848e3a..303b75f5714858dec2c04a6f0049444b764640be 100644 --- a/src/adql/query/ADQLQuery.java +++ b/src/adql/query/ADQLQuery.java @@ -74,7 +74,7 @@ public class ADQLQuery implements ADQLObject { private ClauseConstraints where; /** The ADQL clause GROUP BY. */ - private ClauseADQL<ADQLColumn> groupBy; + private ClauseADQL<ADQLOperand> groupBy; /** The ADQL clause HAVING. */ private ClauseConstraints having; @@ -113,7 +113,7 @@ public class ADQLQuery implements ADQLObject { select = new ClauseSelect(); from = null; where = new ClauseConstraints("WHERE"); - groupBy = new ClauseADQL<ADQLColumn>("GROUP BY"); + groupBy = new ClauseADQL<ADQLOperand>("GROUP BY"); having = new ClauseConstraints("HAVING"); orderBy = new ClauseADQL<ADQLOrder>("ORDER BY"); offset = null; @@ -132,7 +132,7 @@ public class ADQLQuery implements ADQLObject { select = (ClauseSelect)toCopy.select.getCopy(); from = (FromContent)toCopy.from.getCopy(); where = (ClauseConstraints)toCopy.where.getCopy(); - groupBy = (ClauseADQL<ADQLColumn>)toCopy.groupBy.getCopy(); + groupBy = (ClauseADQL<ADQLOperand>)toCopy.groupBy.getCopy(); having = (ClauseConstraints)toCopy.having.getCopy(); orderBy = (ClauseADQL<ADQLOrder>)toCopy.orderBy.getCopy(); offset = (ClauseOffset)toCopy.offset.getCopy(); @@ -259,7 +259,7 @@ public class ADQLQuery implements ADQLObject { * * @return Its GROUP BY clause. */ - public final ClauseADQL<ADQLColumn> getGroupBy() { + public final ClauseADQL<ADQLOperand> getGroupBy() { return groupBy; } @@ -274,7 +274,7 @@ public class ADQLQuery implements ADQLObject { * * @throws NullPointerException If the given GROUP BY clause is NULL. */ - public void setGroupBy(ClauseADQL<ADQLColumn> newGroupBy) throws NullPointerException { + public void setGroupBy(ClauseADQL<ADQLOperand> newGroupBy) throws NullPointerException { if (newGroupBy == null) groupBy.clear(); else @@ -566,7 +566,7 @@ public class ADQLQuery implements ADQLObject { break; case 3: if (replacer instanceof ClauseADQL) - groupBy = (ClauseADQL<ADQLColumn>)replacer; + groupBy = (ClauseADQL<ADQLOperand>)replacer; else throw new UnsupportedOperationException("Impossible to replace a ClauseADQL (" + groupBy.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!"); break; diff --git a/src/adql/query/ColumnReference.java b/src/adql/query/ColumnReference.java index 68b3cb4671f6bf2a8d6b40ae6be63ecf99841251..07b21edcfbdacfb61e5d5d3abfec956d259d8b9c 100644 --- a/src/adql/query/ColumnReference.java +++ b/src/adql/query/ColumnReference.java @@ -25,10 +25,10 @@ import adql.query.from.ADQLTable; import adql.query.operand.ADQLColumn; /** - * Represents a reference to a selected column either by an index or by a non-qualified column name/alias. + * Represents a reference to a selected column by an index. * * @author Grégory Mantelet (CDS) - * @version 2.0 (07/2019) + * @version 2.0 (08/2019) * * @see ADQLOrder */ @@ -42,15 +42,16 @@ public class ColumnReference implements ADQLObject { private TextPosition position = null; /** Index of a selected column. */ - private int columnIndex = -1; + private int columnIndex; - /** Name or alias of a selected column. */ - private String columnName = null; - - /** The corresponding column in the "database". By default, this field is automatically filled by {@link adql.db.DBChecker}. */ + /** The corresponding column in the "database". + * <p><i>By default, this field is automatically filled by + * {@link adql.db.DBChecker}.</i></p> */ private DBColumn dbLink = null; - /** The {@link ADQLTable} which is supposed to contain this column. By default, this field is automatically filled by {@link adql.db.DBChecker}. */ + /** The {@link ADQLTable} which is supposed to contain this column. + * <p><i>By default, this field is automatically filled by + * {@link adql.db.DBChecker}.</i></p> */ private ADQLTable adqlTable = null; /** Indicates whether the column name/alias is case sensitive. */ @@ -59,28 +60,16 @@ public class ColumnReference implements ADQLObject { /** * Builds a column reference with an index of a selected column. * - * @param index Index of a selected column (from 1). + * @param index Index of a selected column (from 1). * - * @throws ArrayIndexOutOfBoundsException If the given index is less or equal 0. + * @throws ArrayIndexOutOfBoundsException If the given index is less or + * equal 0. */ public ColumnReference(int index) throws ArrayIndexOutOfBoundsException { if (index <= 0) throw new IndexOutOfBoundsException("Impossible to make a reference to the " + index + "th column: a column index must be greater or equal 1 !"); columnIndex = index; - columnName = null; - } - - /** - * Builds a column reference with a name/alias of a selected column. - * - * @param colName A column name/alias. - * - * @throws NullPointerException If the given name is <i>null</i> or is an empty string. - */ - public ColumnReference(String colName) throws NullPointerException { - if (!setColumnName(colName)) - throw new NullPointerException("Impossible to make a reference: the given column name is null or is an empty string !"); } /** @@ -89,7 +78,6 @@ public class ColumnReference implements ADQLObject { * @param toCopy The column reference to copy. */ public ColumnReference(ColumnReference toCopy) { - columnName = toCopy.columnName; caseSensitive = toCopy.caseSensitive; columnIndex = toCopy.columnIndex; } @@ -110,7 +98,8 @@ public class ColumnReference implements ADQLObject { } /** - * Sets the position at which this {@link ColumnReference} has been found in the original ADQL query string. + * Sets the position at which this {@link ColumnReference} has been found in + * the original ADQL query string. * * @param pos Position of this {@link ColumnReference}. */ @@ -121,7 +110,7 @@ public class ColumnReference implements ADQLObject { /** * Gets the index of the referenced column. * - * @return The index of the referenced column or <i>-1</i> if this column reference has been made with a column name/alias. + * @return The index of the referenced column. */ public final int getColumnIndex() { return columnIndex; @@ -131,11 +120,12 @@ public class ColumnReference implements ADQLObject { * Sets the index of the referenced column. * * @param index The index of the referenced column (must be > 0). - * @return <i>true</i> if the column referenced has been updated, <i>false</i> otherwise (if index <= 0). + * + * @return <code>true</code> if the column referenced has been updated, + * <code>false</code> otherwise (if index <= 0). */ public final boolean setColumnIndex(int index) { if (index > 0) { - columnName = null; columnIndex = index; return true; } @@ -143,55 +133,11 @@ public class ColumnReference implements ADQLObject { } /** - * Tells whether the column is referenced by its index or by its name/alias. - * - * @return <i>true</i> if by index, <i>false</i> if by name/alias. - */ - public final boolean isIndex() { - return columnName == null; - } - - /** - * Gets the name/alias of the referenced column. - * - * @return The referenced column's name/alias or <i>null</i> if this column reference has been made with a column index. - */ - public final String getColumnName() { - return columnName; - } - - /** - * Sets the name/alias of the referenced column. - * - * @param name The referenced column's name/alias (must be different from <i>null</i> and from an empty string). - * @return <i>true</i> if the column reference has been updated, <i>false</i> otherwise (if name is <i>null</i> or is an empty string). - */ - public final boolean setColumnName(String name) { - if (name == null) - return false; - - StringBuffer n = new StringBuffer(name); - n.trimToSize(); - if (n.length() > 1 && n.charAt(0) == '\"' && n.charAt(name.length() - 1) == '\"') { - n.deleteCharAt(0); - n.deleteCharAt(n.length() - 1); - n.trimToSize(); - if (n.length() > 0) - caseSensitive = true; - } - if (n.length() == 0) - return false; - else { - columnIndex = -1; - columnName = n.toString(); - return true; - } - } - - /** - * Tells whether the column reference on a column name/alias is case sensitive. + * Tells whether the column reference on a column name/alias is case + * sensitive. * - * @return <i>true</i> if the column name/alias is case sensitive, <i>false</i> otherwise. + * @return <code>true</code> if the column name/alias is case sensitive, + * <code>false</code> otherwise. */ public final boolean isCaseSensitive() { return caseSensitive; @@ -200,7 +146,9 @@ public class ColumnReference implements ADQLObject { /** * Sets the case sensitivity on the column name/alias. * - * @param sensitive <i>true</i> to make case sensitive the column name/alias, <i>false</i> otherwise. + * @param sensitive <code>true</code> to make case sensitive the column + * name/alias, + * <code>false</code> otherwise. */ public final void setCaseSensitive(boolean sensitive) { caseSensitive = sensitive; @@ -209,18 +157,25 @@ public class ColumnReference implements ADQLObject { /** * Gets the corresponding {@link DBColumn}. * - * @return The corresponding {@link DBColumn} if {@link #getColumnName()} is a column name (not an alias), <i>null</i> otherwise. + * @return The corresponding {@link DBColumn} if {@link #getColumnName()} + * is a column name (not an alias), + * or NULL otherwise. */ public final DBColumn getDBLink() { return dbLink; } /** - * <p>Sets the {@link DBColumn} corresponding to this {@link ADQLColumn}.</p> + * Sets the {@link DBColumn} corresponding to this {@link ADQLColumn}. * - * <p>By default, this field is automatically filled by {@link adql.db.DBChecker}.</p> + * <p><i> + * By default, this field is automatically filled by + * {@link adql.db.DBChecker}. + * </i></p> * - * @param dbLink Its corresponding {@link DBColumn} if {@link #getColumnName()} is a column name (not an alias), <i>null</i> otherwise. + * @param dbLink Its corresponding {@link DBColumn} if + * {@link #getColumnName()} is a column name (not an alias), + * or NULL otherwise. */ public final void setDBLink(DBColumn dbLink) { this.dbLink = dbLink; @@ -229,18 +184,26 @@ public class ColumnReference implements ADQLObject { /** * Gets the {@link ADQLTable} from which this column is supposed to come. * - * @return Its source table if {@link #getColumnName()} is a column name (not an alias), otherwise <i>null</i>. + * @return Its source table if {@link #getColumnName()} is a column name + * (not an alias), + * or NULL otherwise. */ public final ADQLTable getAdqlTable() { return adqlTable; } /** - * <p>Sets the {@link ADQLTable} from which this column is supposed to come.</p> + * Sets the {@link ADQLTable} from which this column is supposed to come. * - * <p>By default, this field is automatically filled by {@link adql.db.DBChecker} when {@link adql.db.DBChecker#check(adql.query.ADQLQuery)} is called.</p> + * <p><i> + * By default, this field is automatically filled by + * {@link adql.db.DBChecker} when + * {@link adql.db.DBChecker#check(adql.query.ADQLQuery)} is called. + * </i></p> * - * @param adqlTable Its source table if {@link #getColumnName()} is a column name (not an alias), <i>null</i> otherwise. + * @param adqlTable Its source table if {@link #getColumnName()} is a column + * name (not an alias), + * or NULL otherwise. */ public final void setAdqlTable(ADQLTable adqlTable) { this.adqlTable = adqlTable; @@ -253,7 +216,7 @@ public class ColumnReference implements ADQLObject { @Override public String getName() { - return isIndex() ? (columnIndex + "") : columnName; + return columnIndex + ""; } @Override @@ -263,7 +226,7 @@ public class ColumnReference implements ADQLObject { @Override public String toADQL() { - return isIndex() ? ("" + columnIndex) : (isCaseSensitive() ? ("\"" + columnName + "\"") : columnName); + return "" + columnIndex; } } diff --git a/src/adql/translator/JDBCTranslator.java b/src/adql/translator/JDBCTranslator.java index 7042931cbc3f23a49e533446c08fc3a542ca0191..bc482437f23a4b11a5a176f32a2a73fedf73eaca 100644 --- a/src/adql/translator/JDBCTranslator.java +++ b/src/adql/translator/JDBCTranslator.java @@ -60,10 +60,10 @@ import adql.query.operand.Operation; import adql.query.operand.StringConstant; import adql.query.operand.WrappedOperand; import adql.query.operand.function.ADQLFunction; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.MathFunction; import adql.query.operand.function.SQLFunction; import adql.query.operand.function.SQLFunctionType; -import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.UserDefinedFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; @@ -526,10 +526,7 @@ public abstract class JDBCTranslator implements ADQLTranslator { @Override public String translate(ColumnReference ref) throws TranslationException { - if (ref instanceof ADQLOrder) - return translate((ADQLOrder)ref); - else - return getDefaultColumnReference(ref); + return getDefaultColumnReference(ref); } /** @@ -542,35 +539,15 @@ public abstract class JDBCTranslator implements ADQLTranslator { * @throws TranslationException If there is an error during the translation. */ protected String getDefaultColumnReference(ColumnReference ref) throws TranslationException { - if (ref.isIndex()) { - return "" + ref.getColumnIndex(); - } else { - if (ref.getDBLink() == null) { - return (ref.isCaseSensitive() ? ("\"" + ref.getColumnName() + "\"") : ref.getColumnName()); - } else { - DBColumn dbCol = ref.getDBLink(); - StringBuffer colName = new StringBuffer(); - // Use the table alias if any: - if (ref.getAdqlTable() != null && ref.getAdqlTable().hasAlias()) { - if (ref.getAdqlTable().isCaseSensitive(IdentifierField.ALIAS)) - appendIdentifier(colName, ref.getAdqlTable().getAlias(), true).append('.'); - else - appendIdentifier(colName, ref.getAdqlTable().getAlias().toLowerCase(), true).append('.'); - } - // Use the DBTable if any: - else if (dbCol.getTable() != null) - colName.append(getQualifiedTableName(dbCol.getTable())).append('.'); - - appendIdentifier(colName, dbCol.getDBName(), IdentifierField.COLUMN); - - return colName.toString(); - } - } + return "" + ref.getColumnIndex(); } @Override public String translate(ADQLOrder order) throws TranslationException { - return getDefaultColumnReference(order) + (order.isDescSorting() ? " DESC" : " ASC"); + if (order.getColumnReference() != null) + return translate(order.getColumnReference()) + (order.isDescSorting() ? " DESC" : " ASC"); + else + return translate(order.getExpression()) + (order.isDescSorting() ? " DESC" : " ASC"); } /* ************************** */ diff --git a/test/adql/db/TestDBChecker.java b/test/adql/db/TestDBChecker.java index 04806f49135d147cb4aee07e4cb57eccd310d8f5..391d2320da034bf4a5cbc9f76784b192b3893e1c 100644 --- a/test/adql/db/TestDBChecker.java +++ b/test/adql/db/TestDBChecker.java @@ -22,6 +22,7 @@ import adql.db.DBType.DBDatatype; import adql.db.FunctionDef.FunctionParam; import adql.db.exception.UnresolvedIdentifiersException; import adql.parser.ADQLParser; +import adql.parser.ADQLParser.ADQLVersion; import adql.parser.feature.LanguageFeature; import adql.parser.grammar.ParseException; import adql.query.ADQLObject; @@ -108,642 +109,656 @@ public class TestDBChecker { * * This issue can be tested by creating a ConstraintsGroup (i.e. in a constraints location like WHERE or JOIN...ON, * a constraint (or more) between parenthesis). */ - ADQLParser parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0))); - try { - parser.parseQuery("SELECT * FROM foo WHERE (colI BETWEEN 1 AND 10)"); - } catch(ParseException pe) { - pe.printStackTrace(); - fail(); + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0))); + try { + parser.parseQuery("SELECT * FROM foo WHERE (colI BETWEEN 1 AND 10)"); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed with ADQL-" + version); + } } } @Test public void testGroupByWithQualifiedColName() { - ADQLParser parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0))); - try { - // Not qualified column name: - parser.parseQuery("SELECT colI, COUNT(*) AS cnt FROM foo GROUP BY colI"); - // Qualified with the table name: - parser.parseQuery("SELECT foo.colI, COUNT(*) AS cnt FROM foo GROUP BY foo.colI"); - // Qualified with the table alias: - parser.parseQuery("SELECT f.colI, COUNT(*) AS cnt FROM foo AS f GROUP BY f.colI"); - // With the SELECT item alias: - parser.parseQuery("SELECT colI AS mycol, COUNT(*) AS cnt FROM foo GROUP BY mycol"); - } catch(ParseException pe) { - pe.printStackTrace(); - fail(); + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0))); + try { + // Not qualified column name: + parser.parseQuery("SELECT colI, COUNT(*) AS cnt FROM foo GROUP BY colI"); + // Qualified with the table name: + parser.parseQuery("SELECT foo.colI, COUNT(*) AS cnt FROM foo GROUP BY foo.colI"); + // Qualified with the table alias: + parser.parseQuery("SELECT f.colI, COUNT(*) AS cnt FROM foo AS f GROUP BY f.colI"); + // With the SELECT item alias: + parser.parseQuery("SELECT colI AS mycol, COUNT(*) AS cnt FROM foo GROUP BY mycol"); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed with ADQL-" + version); + } } } @Test public void testQualifiedName() { - ADQLParser parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0))); - try { - // Tests with a table whose the schema is specified: - parser.parseQuery("SELECT * FROM foo;"); - parser.parseQuery("SELECT * FROM aschema.foo;"); - parser.parseQuery("SELECT foo.* FROM foo;"); - parser.parseQuery("SELECT aschema.foo.* FROM foo;"); - parser.parseQuery("SELECT aschema.foo.* FROM aschema.foo;"); - parser.parseQuery("SELECT \"colS\" FROM foo;"); - parser.parseQuery("SELECT foo.\"colS\" FROM foo;"); - parser.parseQuery("SELECT foo.\"colS\" FROM aschema.\"foo\";"); - parser.parseQuery("SELECT \"aschema\".\"foo\".\"colS\" FROM foo;"); - - // Tests with a table without schema: - parser.parseQuery("SELECT * FROM foo2;"); - parser.parseQuery("SELECT foo2.* FROM foo2;"); - parser.parseQuery("SELECT foo2.* FROM \"foo2\";"); - parser.parseQuery("SELECT \"foo2\".* FROM \"foo2\";"); - parser.parseQuery("SELECT oid FROM foo2;"); - parser.parseQuery("SELECT \"oid\" FROM \"foo2\";"); - parser.parseQuery("SELECT foo2.oid FROM foo2;"); - parser.parseQuery("SELECT \"foo2\".\"oid\" FROM \"foo2\";"); - } catch(ParseException pe) { - pe.printStackTrace(); - fail(); - } - - // If no schema is specified, then the table is not part of a schema and so, there is no reason a table with a fake schema prefix should work: - try { - parser.parseQuery("SELECT * FROM noschema.foo2;"); - fail("The table \"foo2\" has no schema specified and so, is not part of a schema. A fake schema prefix should then be forbidden!"); - } catch(ParseException pe) { - } - try { - parser.parseQuery("SELECT noschema.foo2.* FROM foo2;"); - fail("The table \"foo2\" has no schema specified and so, is not part of a schema. A fake schema prefix should then be forbidden!"); - } catch(ParseException pe) { + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0))); + try { + // Tests with a table whose the schema is specified: + parser.parseQuery("SELECT * FROM foo;"); + parser.parseQuery("SELECT * FROM aschema.foo;"); + parser.parseQuery("SELECT foo.* FROM foo;"); + parser.parseQuery("SELECT aschema.foo.* FROM foo;"); + parser.parseQuery("SELECT aschema.foo.* FROM aschema.foo;"); + parser.parseQuery("SELECT \"colS\" FROM foo;"); + parser.parseQuery("SELECT foo.\"colS\" FROM foo;"); + parser.parseQuery("SELECT foo.\"colS\" FROM aschema.\"foo\";"); + parser.parseQuery("SELECT \"aschema\".\"foo\".\"colS\" FROM foo;"); + + // Tests with a table without schema: + parser.parseQuery("SELECT * FROM foo2;"); + parser.parseQuery("SELECT foo2.* FROM foo2;"); + parser.parseQuery("SELECT foo2.* FROM \"foo2\";"); + parser.parseQuery("SELECT \"foo2\".* FROM \"foo2\";"); + parser.parseQuery("SELECT oid FROM foo2;"); + parser.parseQuery("SELECT \"oid\" FROM \"foo2\";"); + parser.parseQuery("SELECT foo2.oid FROM foo2;"); + parser.parseQuery("SELECT \"foo2\".\"oid\" FROM \"foo2\";"); + } catch(ParseException pe) { + pe.printStackTrace(); + fail(); + } + + // If no schema is specified, then the table is not part of a schema and so, there is no reason a table with a fake schema prefix should work: + try { + parser.parseQuery("SELECT * FROM noschema.foo2;"); + fail("[ADQL-" + version + "] The table \"foo2\" has no schema specified and so, is not part of a schema. A fake schema prefix should then be forbidden!"); + } catch(ParseException pe) { + } + try { + parser.parseQuery("SELECT noschema.foo2.* FROM foo2;"); + fail("[ADQL-" + version + "] The table \"foo2\" has no schema specified and so, is not part of a schema. A fake schema prefix should then be forbidden!"); + } catch(ParseException pe) { + } } } @Test public void testColRefWithDottedAlias() { - ADQLParser parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables)); - try { - // ORDER BY - ADQLQuery adql = parser.parseQuery("SELECT colI AS \"col.I\" FROM aschema.foo ORDER BY \"col.I\""); - assertNotNull(adql); - assertEquals("SELECT \"aschema\".\"foo\".\"colI\" AS \"col.I\"\nFROM \"aschema\".\"foo\"\nORDER BY \"col.I\" ASC", (new PostgreSQLTranslator()).translate(adql)); - - // GROUP BY - adql = parser.parseQuery("SELECT colI AS \"col.I\" FROM aschema.foo GROUP BY \"col.I\""); - assertNotNull(adql); - assertEquals("SELECT \"aschema\".\"foo\".\"colI\" AS \"col.I\"\nFROM \"aschema\".\"foo\"\nGROUP BY \"col.I\"", (new PostgreSQLTranslator()).translate(adql)); - } catch(ParseException pe) { - pe.printStackTrace(); - fail(); - } catch(TranslationException te) { - te.printStackTrace(); - fail(); + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables)); + try { + // ORDER BY + ADQLQuery adql = parser.parseQuery("SELECT colI AS \"col.I\" FROM aschema.foo ORDER BY \"col.I\""); + assertNotNull(adql); + assertEquals("SELECT \"aschema\".\"foo\".\"colI\" AS \"col.I\"\nFROM \"aschema\".\"foo\"\nORDER BY \"col.I\" ASC", (new PostgreSQLTranslator()).translate(adql)); + + // GROUP BY + adql = parser.parseQuery("SELECT colI AS \"col.I\" FROM aschema.foo GROUP BY \"col.I\""); + assertNotNull(adql); + assertEquals("SELECT \"aschema\".\"foo\".\"colI\" AS \"col.I\"\nFROM \"aschema\".\"foo\"\nGROUP BY \"col.I\"", (new PostgreSQLTranslator()).translate(adql)); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed with ADQL-" + version); + } catch(TranslationException te) { + te.printStackTrace(); + fail("Failed with ADQL-" + version); + } } } @Test public void testNumericOrStringValueExpressionPrimary() { - ADQLParser parser = new ADQLParser(); - try { - assertNotNull(parser.parseQuery("SELECT 'toto' FROM foo;")); - assertNotNull(parser.parseQuery("SELECT ('toto') FROM foo;")); - assertNotNull(parser.parseQuery("SELECT (('toto')) FROM foo;")); - assertNotNull(parser.parseQuery("SELECT 'toto' || 'blabla' FROM foo;")); - assertNotNull(parser.parseQuery("SELECT ('toto' || 'blabla') FROM foo;")); - assertNotNull(parser.parseQuery("SELECT (('toto' || 'blabla')) FROM foo;")); - assertNotNull(parser.parseQuery("SELECT (('toto') || (('blabla'))) FROM foo;")); - assertNotNull(parser.parseQuery("SELECT 3 FROM foo;")); - assertNotNull(parser.parseQuery("SELECT ((2+3)*5) FROM foo;")); - assertNotNull(parser.parseQuery("SELECT ABS(-123) FROM foo;")); - assertNotNull(parser.parseQuery("SELECT ABS(2*-1+5) FROM foo;")); - assertNotNull(parser.parseQuery("SELECT ABS(COUNT(*)) FROM foo;")); - assertNotNull(parser.parseQuery("SELECT toto FROM foo;")); - assertNotNull(parser.parseQuery("SELECT toto * 3 FROM foo;")); - assertNotNull(parser.parseQuery("SELECT toto || 'blabla' FROM foo;")); - assertNotNull(parser.parseQuery("SELECT 'toto' || 1 FROM foo;")); - assertNotNull(parser.parseQuery("SELECT 1 || 'toto' FROM foo;")); - assertNotNull(parser.parseQuery("SELECT 'toto' || (-1) FROM foo;")); - } catch(ParseException pe) { - pe.printStackTrace(); - fail(); - } - try { - parser.parseQuery("SELECT ABS('toto') FROM foo;"); - fail(); - } catch(ParseException pe) { - } - try { - parser.parseQuery("SELECT 'toto' || -1 FROM foo;"); - fail(); - } catch(ParseException pe) { - } - try { - parser.parseQuery("SELECT -1 || 'toto' FROM foo;"); - fail(); - } catch(ParseException pe) { - } - try { - parser.parseQuery("SELECT ABS(('toto' || 'blabla')) FROM foo;"); - fail(); - } catch(ParseException pe) { - } - try { - parser.parseQuery("SELECT 'toto' * 3 FROM foo;"); - fail(); - } catch(ParseException pe) { + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + try { + assertNotNull(parser.parseQuery("SELECT 'toto' FROM foo;")); + assertNotNull(parser.parseQuery("SELECT ('toto') FROM foo;")); + assertNotNull(parser.parseQuery("SELECT (('toto')) FROM foo;")); + assertNotNull(parser.parseQuery("SELECT 'toto' || 'blabla' FROM foo;")); + assertNotNull(parser.parseQuery("SELECT ('toto' || 'blabla') FROM foo;")); + assertNotNull(parser.parseQuery("SELECT (('toto' || 'blabla')) FROM foo;")); + assertNotNull(parser.parseQuery("SELECT (('toto') || (('blabla'))) FROM foo;")); + assertNotNull(parser.parseQuery("SELECT 3 FROM foo;")); + assertNotNull(parser.parseQuery("SELECT ((2+3)*5) FROM foo;")); + assertNotNull(parser.parseQuery("SELECT ABS(-123) FROM foo;")); + assertNotNull(parser.parseQuery("SELECT ABS(2*-1+5) FROM foo;")); + assertNotNull(parser.parseQuery("SELECT ABS(COUNT(*)) FROM foo;")); + assertNotNull(parser.parseQuery("SELECT toto FROM foo;")); + assertNotNull(parser.parseQuery("SELECT toto * 3 FROM foo;")); + assertNotNull(parser.parseQuery("SELECT toto || 'blabla' FROM foo;")); + assertNotNull(parser.parseQuery("SELECT 'toto' || 1 FROM foo;")); + assertNotNull(parser.parseQuery("SELECT 1 || 'toto' FROM foo;")); + assertNotNull(parser.parseQuery("SELECT 'toto' || (-1) FROM foo;")); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed with ADQL-" + version); + } + try { + parser.parseQuery("SELECT ABS('toto') FROM foo;"); + fail("Failed with ADQL-" + version); + } catch(ParseException pe) { + } + try { + parser.parseQuery("SELECT 'toto' || -1 FROM foo;"); + fail("Failed with ADQL-" + version); + } catch(ParseException pe) { + } + try { + parser.parseQuery("SELECT -1 || 'toto' FROM foo;"); + fail("Failed with ADQL-" + version); + } catch(ParseException pe) { + } + try { + parser.parseQuery("SELECT ABS(('toto' || 'blabla')) FROM foo;"); + fail("Failed with ADQL-" + version); + } catch(ParseException pe) { + } + try { + parser.parseQuery("SELECT 'toto' * 3 FROM foo;"); + fail("Failed with ADQL-" + version); + } catch(ParseException pe) { + } } } @Test public void testUDFManagement() { - // UNKNOWN FUNCTIONS ARE NOT ALLOWED: - ADQLParser parser = new ADQLParser(); - parser.getSupportedFeatures().allowAnyUdf(true); - parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0))); - - // Test with a simple ADQL query without unknown or user defined function: - try { - assertNotNull(parser.parseQuery("SELECT * FROM foo;")); - } catch(ParseException e) { - e.printStackTrace(); - fail("A simple and basic query should not be a problem for the parser!"); - } - - // Test with an ADQL query containing one not declared UDF: - try { - parser.parseQuery("SELECT toto() FROM foo;"); - fail("This query contains a UDF while it's not allowed: this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Unresolved function: \"toto()\"! No UDF has been defined or found with the signature: toto().", ex.getErrors().next().getMessage()); - } - - // DECLARE THE UDFs: - FunctionDef[] udfs; - try { - udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)) }; - parser = new ADQLParser(); - parser.getSupportedFeatures().allowAnyUdf(true); - parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); - } - - // Test again: - try { - assertNotNull(parser.parseQuery("SELECT toto() FROM foo;")); - assertNotNull(parser.parseQuery("SELECT tata() FROM foo;")); - } catch(ParseException e) { - e.printStackTrace(); - fail("This query contains a DECLARED UDF: this test should have succeeded!"); - } - - // Test but with at least one parameter: - try { - parser.parseQuery("SELECT toto('blabla') FROM foo;"); - fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Unresolved function: \"toto('blabla')\"! No UDF has been defined or found with the signature: toto(STRING).", ex.getErrors().next().getMessage()); - } - - // Test but with at least one column parameter: - try { - parser.parseQuery("SELECT toto(colS) FROM foo;"); - fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Unresolved function: \"toto(colS)\"! No UDF has been defined or found with the signature: toto(STRING).", ex.getErrors().next().getMessage()); - } - - // Test but with at least one unknown column parameter: - try { - parser.parseQuery("SELECT toto(whatami) FROM foo;"); - fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(2, ex.getNbErrors()); - Iterator<ParseException> errors = ex.getErrors(); - assertEquals("Unknown column \"whatami\" !", errors.next().getMessage()); - assertEquals("Unresolved function: \"toto(whatami)\"! No UDF has been defined or found with the signature: toto(param1).", errors.next().getMessage()); - } - - // Test with a UDF whose the class is specified ; the corresponding object in the ADQL tree must be replace by an instance of this class: - try { - udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) }; - udfs[0].setUDFClass(UDFToto.class); - parser = new ADQLParser(); + for(ADQLVersion version : ADQLVersion.values()) { + // UNKNOWN FUNCTIONS ARE NOT ALLOWED: + ADQLParser parser = new ADQLParser(version); parser.getSupportedFeatures().allowAnyUdf(true); - parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); - } - - try { - ADQLQuery query = parser.parseQuery("SELECT toto('blabla') FROM foo;"); - assertNotNull(query); - Iterator<ADQLObject> it = query.search(new SimpleSearchHandler() { - @Override - protected boolean match(ADQLObject obj) { - return (obj instanceof UserDefinedFunction) && ((UserDefinedFunction)obj).getName().equals("toto"); - } - }); - assertTrue(it.hasNext()); - assertEquals(UDFToto.class.getName(), it.next().getClass().getName()); - assertFalse(it.hasNext()); - } catch(Exception e) { - e.printStackTrace(); - fail("This query contains a DECLARED UDF with a valid UserDefinedFunction class: this test should have succeeded!"); - } - - // Test with a wrong parameter type: - try { - parser.parseQuery("SELECT toto(123) FROM foo;"); - fail("This query contains an unknown UDF signature (the fct toto is declared with one parameter of type STRING...here it is a numeric): this test should have failed!"); - } catch(Exception e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Unresolved function: \"toto(123)\"! No UDF has been defined or found with the signature: toto(NUMERIC).", ex.getErrors().next().getMessage()); - } - - // Test with UDF class constructor throwing an exception: - try { - udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) }; - udfs[0].setUDFClass(WrongUDFToto.class); - parser = new ADQLParser(); - parser.getSupportedFeatures().allowAnyUdf(true); - parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); - } - - try { - parser.parseQuery("SELECT toto('blabla') FROM foo;"); - fail("The set UDF class constructor has throw an error: this test should have failed!"); - } catch(Exception e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Impossible to represent the function \"toto\": the following error occured while creating this representation: \"[Exception] Systematic error!\"", ex.getErrors().next().getMessage()); + parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0))); + + // Test with a simple ADQL query without unknown or user defined function: + try { + assertNotNull(parser.parseQuery("SELECT * FROM foo;")); + } catch(ParseException e) { + e.printStackTrace(); + fail("A simple and basic query should not be a problem for the parser!"); + } + + // Test with an ADQL query containing one not declared UDF: + try { + parser.parseQuery("SELECT toto() FROM foo;"); + fail("This query contains a UDF while it's not allowed: this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Unresolved function: \"toto()\"! No UDF has been defined or found with the signature: toto().", ex.getErrors().next().getMessage()); + } + + // DECLARE THE UDFs: + FunctionDef[] udfs; + try { + udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)) }; + parser = new ADQLParser(version); + parser.getSupportedFeatures().allowAnyUdf(true); + parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); + } + + // Test again: + try { + assertNotNull(parser.parseQuery("SELECT toto() FROM foo;")); + assertNotNull(parser.parseQuery("SELECT tata() FROM foo;")); + } catch(ParseException e) { + e.printStackTrace(); + fail("This query contains a DECLARED UDF: this test should have succeeded!"); + } + + // Test but with at least one parameter: + try { + parser.parseQuery("SELECT toto('blabla') FROM foo;"); + fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Unresolved function: \"toto('blabla')\"! No UDF has been defined or found with the signature: toto(STRING).", ex.getErrors().next().getMessage()); + } + + // Test but with at least one column parameter: + try { + parser.parseQuery("SELECT toto(colS) FROM foo;"); + fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Unresolved function: \"toto(colS)\"! No UDF has been defined or found with the signature: toto(STRING).", ex.getErrors().next().getMessage()); + } + + // Test but with at least one unknown column parameter: + try { + parser.parseQuery("SELECT toto(whatami) FROM foo;"); + fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(2, ex.getNbErrors()); + Iterator<ParseException> errors = ex.getErrors(); + assertEquals("Unknown column \"whatami\" !", errors.next().getMessage()); + assertEquals("Unresolved function: \"toto(whatami)\"! No UDF has been defined or found with the signature: toto(param1).", errors.next().getMessage()); + } + + // Test with a UDF whose the class is specified ; the corresponding object in the ADQL tree must be replace by an instance of this class: + try { + udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) }; + udfs[0].setUDFClass(UDFToto.class); + parser = new ADQLParser(version); + parser.getSupportedFeatures().allowAnyUdf(true); + parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); + } + + try { + ADQLQuery query = parser.parseQuery("SELECT toto('blabla') FROM foo;"); + assertNotNull(query); + Iterator<ADQLObject> it = query.search(new SimpleSearchHandler() { + @Override + protected boolean match(ADQLObject obj) { + return (obj instanceof UserDefinedFunction) && ((UserDefinedFunction)obj).getName().equals("toto"); + } + }); + assertTrue(it.hasNext()); + assertEquals(UDFToto.class.getName(), it.next().getClass().getName()); + assertFalse(it.hasNext()); + } catch(Exception e) { + e.printStackTrace(); + fail("This query contains a DECLARED UDF with a valid UserDefinedFunction class: this test should have succeeded!"); + } + + // Test with a wrong parameter type: + try { + parser.parseQuery("SELECT toto(123) FROM foo;"); + fail("This query contains an unknown UDF signature (the fct toto is declared with one parameter of type STRING...here it is a numeric): this test should have failed!"); + } catch(Exception e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Unresolved function: \"toto(123)\"! No UDF has been defined or found with the signature: toto(NUMERIC).", ex.getErrors().next().getMessage()); + } + + // Test with UDF class constructor throwing an exception: + try { + udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) }; + udfs[0].setUDFClass(WrongUDFToto.class); + parser = new ADQLParser(version); + parser.getSupportedFeatures().allowAnyUdf(true); + parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); + } + + try { + parser.parseQuery("SELECT toto('blabla') FROM foo;"); + fail("The set UDF class constructor has throw an error: this test should have failed!"); + } catch(Exception e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Impossible to represent the function \"toto\": the following error occured while creating this representation: \"[Exception] Systematic error!\"", ex.getErrors().next().getMessage()); + } } } @Test public void testTypesChecking() { - // DECLARE A SIMPLE PARSER: - ADQLParser parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables)); - - // Test the type of columns generated by the parser: - try { - ADQLQuery query = parser.parseQuery("SELECT colS, colI, colG FROM foo;"); - ADQLOperand colS = query.getSelect().get(0).getOperand(); - ADQLOperand colI = query.getSelect().get(1).getOperand(); - ADQLOperand colG = query.getSelect().get(2).getOperand(); - // test string column: - assertTrue(colS instanceof ADQLColumn); - assertTrue(colS.isString()); - assertFalse(colS.isNumeric()); - assertFalse(colS.isGeometry()); - // test integer column: - assertTrue(colI instanceof ADQLColumn); - assertFalse(colI.isString()); - assertTrue(colI.isNumeric()); - assertFalse(colI.isGeometry()); - // test geometry column: - assertTrue(colG instanceof ADQLColumn); - assertFalse(colG.isString()); - assertFalse(colG.isNumeric()); - assertTrue(colG.isGeometry()); - } catch(ParseException e1) { - if (e1 instanceof UnresolvedIdentifiersException) - ((UnresolvedIdentifiersException)e1).getErrors().next().printStackTrace(); - else + for(ADQLVersion version : ADQLVersion.values()) { + // DECLARE A SIMPLE PARSER: + ADQLParser parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables)); + + // Test the type of columns generated by the parser: + try { + ADQLQuery query = parser.parseQuery("SELECT colS, colI, colG FROM foo;"); + ADQLOperand colS = query.getSelect().get(0).getOperand(); + ADQLOperand colI = query.getSelect().get(1).getOperand(); + ADQLOperand colG = query.getSelect().get(2).getOperand(); + // test string column: + assertTrue(colS instanceof ADQLColumn); + assertTrue(colS.isString()); + assertFalse(colS.isNumeric()); + assertFalse(colS.isGeometry()); + // test integer column: + assertTrue(colI instanceof ADQLColumn); + assertFalse(colI.isString()); + assertTrue(colI.isNumeric()); + assertFalse(colI.isGeometry()); + // test geometry column: + assertTrue(colG instanceof ADQLColumn); + assertFalse(colG.isString()); + assertFalse(colG.isNumeric()); + assertTrue(colG.isGeometry()); + } catch(ParseException e1) { + if (e1 instanceof UnresolvedIdentifiersException) + ((UnresolvedIdentifiersException)e1).getErrors().next().printStackTrace(); + else + e1.printStackTrace(); + fail("This query contains known columns: this test should have succeeded!"); + } + + // Test the expected type - NUMERIC - generated by the parser: + try { + assertNotNull(parser.parseQuery("SELECT colI * 3 FROM foo;")); + } catch(ParseException e) { + e.printStackTrace(); + fail("This query contains a product between 2 numerics: this test should have succeeded!"); + } + try { + parser.parseQuery("SELECT colS * 3 FROM foo;"); + fail("This query contains a product between a string and an integer: this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A numeric value was expected instead of \"colS\".", ex.getErrors().next().getMessage()); + } + try { + parser.parseQuery("SELECT colG * 3 FROM foo;"); + fail("This query contains a product between a geometry and an integer: this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A numeric value was expected instead of \"colG\".", ex.getErrors().next().getMessage()); + } + + // Test the expected type - STRING - generated by the parser: + try { + assertNotNull(parser.parseQuery("SELECT colS || 'blabla' FROM foo;")); + } catch(ParseException e) { + e.printStackTrace(); + fail("This query contains a concatenation between 2 strings: this test should have succeeded!"); + } + try { + assertNotNull(parser.parseQuery("SELECT colI || 'blabla' FROM foo;")); + } catch(ParseException e) { + e.printStackTrace(); + fail("This query contains a concatenation between a column (whatever its type) and a string: this test should have succeeded!"); + } + try { + assertNotNull(parser.parseQuery("SELECT colG || 'blabla' FROM foo;")); + } catch(ParseException e) { + e.printStackTrace(); + fail("This query contains a concatenation between a column (whatever its type) and a string: this test should have succeeded!"); + } + + // Test the expected type - GEOMETRY - generated by the parser: + try { + assertNotNull(parser.parseQuery("SELECT CONTAINS(colG, CIRCLE('', 1, 2, 5)) FROM foo;")); + } catch(ParseException e) { + e.printStackTrace(); + fail("This query contains a geometrical predicate between 2 geometries: this test should have succeeded!"); + } + try { + parser.parseQuery("SELECT CONTAINS(colI, CIRCLE('', 1, 2, 5)) FROM foo;"); + fail("This query contains a geometrical predicate between an integer and a geometry: this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A geometry was expected instead of \"colI\".", ex.getErrors().next().getMessage()); + } + try { + parser.parseQuery("SELECT CONTAINS(colS, CIRCLE('', 1, 2, 5)) FROM foo;"); + fail("This query contains a geometrical predicate between a string and a geometry: this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A geometry was expected instead of \"colS\".", ex.getErrors().next().getMessage()); + } + + // DECLARE SOME UDFs: + FunctionDef[] udfs; + try { + udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)), new FunctionDef("titi", new DBType(DBDatatype.REGION)) }; + parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); + } + + // Test the return type of the function TOTO generated by the parser: + try { + ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;"); + ADQLOperand fct = query.getSelect().get(0).getOperand(); + assertTrue(fct instanceof DefaultUDF); + assertNotNull(((DefaultUDF)fct).getDefinition()); + assertTrue(fct.isString()); + assertFalse(fct.isNumeric()); + assertFalse(fct.isGeometry()); + } catch(ParseException e1) { e1.printStackTrace(); - fail("This query contains known columns: this test should have succeeded!"); - } - - // Test the expected type - NUMERIC - generated by the parser: - try { - assertNotNull(parser.parseQuery("SELECT colI * 3 FROM foo;")); - } catch(ParseException e) { - e.printStackTrace(); - fail("This query contains a product between 2 numerics: this test should have succeeded!"); - } - try { - parser.parseQuery("SELECT colS * 3 FROM foo;"); - fail("This query contains a product between a string and an integer: this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A numeric value was expected instead of \"colS\".", ex.getErrors().next().getMessage()); - } - try { - parser.parseQuery("SELECT colG * 3 FROM foo;"); - fail("This query contains a product between a geometry and an integer: this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A numeric value was expected instead of \"colG\".", ex.getErrors().next().getMessage()); - } - - // Test the expected type - STRING - generated by the parser: - try { - assertNotNull(parser.parseQuery("SELECT colS || 'blabla' FROM foo;")); - } catch(ParseException e) { - e.printStackTrace(); - fail("This query contains a concatenation between 2 strings: this test should have succeeded!"); - } - try { - assertNotNull(parser.parseQuery("SELECT colI || 'blabla' FROM foo;")); - } catch(ParseException e) { - e.printStackTrace(); - fail("This query contains a concatenation between a column (whatever its type) and a string: this test should have succeeded!"); - } - try { - assertNotNull(parser.parseQuery("SELECT colG || 'blabla' FROM foo;")); - } catch(ParseException e) { - e.printStackTrace(); - fail("This query contains a concatenation between a column (whatever its type) and a string: this test should have succeeded!"); - } - - // Test the expected type - GEOMETRY - generated by the parser: - try { - assertNotNull(parser.parseQuery("SELECT CONTAINS(colG, CIRCLE('', 1, 2, 5)) FROM foo;")); - } catch(ParseException e) { - e.printStackTrace(); - fail("This query contains a geometrical predicate between 2 geometries: this test should have succeeded!"); - } - try { - parser.parseQuery("SELECT CONTAINS(colI, CIRCLE('', 1, 2, 5)) FROM foo;"); - fail("This query contains a geometrical predicate between an integer and a geometry: this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A geometry was expected instead of \"colI\".", ex.getErrors().next().getMessage()); - } - try { - parser.parseQuery("SELECT CONTAINS(colS, CIRCLE('', 1, 2, 5)) FROM foo;"); - fail("This query contains a geometrical predicate between a string and a geometry: this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A geometry was expected instead of \"colS\".", ex.getErrors().next().getMessage()); - } - - // DECLARE SOME UDFs: - FunctionDef[] udfs; - try { - udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)), new FunctionDef("titi", new DBType(DBDatatype.REGION)) }; - parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); - } - - // Test the return type of the function TOTO generated by the parser: - try { - ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;"); - ADQLOperand fct = query.getSelect().get(0).getOperand(); - assertTrue(fct instanceof DefaultUDF); - assertNotNull(((DefaultUDF)fct).getDefinition()); - assertTrue(fct.isString()); - assertFalse(fct.isNumeric()); - assertFalse(fct.isGeometry()); - } catch(ParseException e1) { - e1.printStackTrace(); - fail("This query contains a DECLARED UDF: this test should have succeeded!"); - } - - // Test the return type checking inside a whole query: - try { - assertNotNull(parser.parseQuery("SELECT toto() || 'Blabla ' AS \"SuperText\" FROM foo;")); - } catch(ParseException e1) { - e1.printStackTrace(); - fail("This query contains a DECLARED UDF concatenated to a String: this test should have succeeded!"); - } - try { - parser.parseQuery("SELECT toto()*3 AS \"SuperError\" FROM foo;"); - fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!"); - } catch(ParseException e1) { - assertTrue(e1 instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A numeric value was expected instead of \"toto()\".", ex.getErrors().next().getMessage()); - } - - // Test the return type of the function TATA generated by the parser: - try { - ADQLQuery query = parser.parseQuery("SELECT tata() FROM foo;"); - ADQLOperand fct = query.getSelect().get(0).getOperand(); - assertTrue(fct instanceof DefaultUDF); - assertNotNull(((DefaultUDF)fct).getDefinition()); - assertFalse(fct.isString()); - assertTrue(fct.isNumeric()); - assertFalse(fct.isGeometry()); - } catch(ParseException e1) { - e1.printStackTrace(); - fail("This query contains a DECLARED UDF: this test should have succeeded!"); - } - - // Test the return type checking inside a whole query: - try { - assertNotNull(parser.parseQuery("SELECT tata()*3 AS \"aNumeric\" FROM foo;")); - } catch(ParseException e1) { - e1.printStackTrace(); - fail("This query contains a DECLARED UDF multiplicated by 3: this test should have succeeded!"); - } - try { - parser.parseQuery("SELECT 'Blabla ' || tata() AS \"SuperError\" FROM foo;"); - fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!"); - } catch(ParseException e1) { - assertTrue(e1 instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A string value was expected instead of \"tata()\".", ex.getErrors().next().getMessage()); - } - try { - parser.parseQuery("SELECT tata() || 'Blabla ' AS \"SuperError\" FROM foo;"); - fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!"); - } catch(ParseException e1) { - assertTrue(e1 instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A string value was expected instead of \"tata()\".", ex.getErrors().next().getMessage()); - } - - // Test the return type of the function TITI generated by the parser: - try { - ADQLQuery query = parser.parseQuery("SELECT titi() FROM foo;"); - ADQLOperand fct = query.getSelect().get(0).getOperand(); - assertTrue(fct instanceof DefaultUDF); - assertNotNull(((DefaultUDF)fct).getDefinition()); - assertFalse(fct.isString()); - assertFalse(fct.isNumeric()); - assertTrue(fct.isGeometry()); - } catch(ParseException e1) { - e1.printStackTrace(); - fail("This query contains a DECLARED UDF: this test should have succeeded!"); - } - - // Test the return type checking inside a whole query: - try { - parser.parseQuery("SELECT CONTAINS(colG, titi()) ' AS \"Super\" FROM foo;"); - fail("Geometrical UDFs are not allowed for the moment in the ADQL language: this test should have failed!"); - } catch(ParseException e1) { - assertTrue(e1 instanceof ParseException); - assertEquals(" Encountered \"(\". Was expecting one of: \")\" \".\" \".\" \")\" ", e1.getMessage()); - } - try { - parser.parseQuery("SELECT titi()*3 AS \"SuperError\" FROM foo;"); - fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!"); - } catch(ParseException e1) { - assertTrue(e1 instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A numeric value was expected instead of \"titi()\".", ex.getErrors().next().getMessage()); - } - - // Try with functions wrapped on 2 levels: - // i.e. fct1('blabla', fct2(fct3('blabla'))) - try { - FunctionDef[] complexFcts = new FunctionDef[3]; - complexFcts[0] = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)), new FunctionParam("num", new DBType(DBDatatype.INTEGER)) }); - complexFcts[1] = new FunctionDef("fct2", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) }); - complexFcts[2] = new FunctionDef("fct3", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) }); - parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables, Arrays.asList(complexFcts))); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); - } - - // With parameters of the good type: - try { - assertNotNull(parser.parseQuery("SELECT fct1('blabla', fct2(fct3('blabla'))) FROM foo")); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("Types are matching: this test should have succeeded!"); - } - // With parameters of the bad type: - try { - parser.parseQuery("SELECT fct2(fct1('blabla', fct3('blabla'))) FROM foo"); - fail("Parameters types are not matching: the parsing should have failed!"); - } catch(ParseException pe) { - assertEquals(UnresolvedIdentifiersException.class, pe.getClass()); - assertEquals(1, ((UnresolvedIdentifiersException)pe).getNbErrors()); - ParseException innerPe = ((UnresolvedIdentifiersException)pe).getErrors().next(); - assertEquals("Unresolved function: \"fct1('blabla', fct3('blabla'))\"! No UDF has been defined or found with the signature: fct1(STRING, STRING).", innerPe.getMessage()); - } - - // CLEAR ALL UDFs AND ALLOW UNKNOWN FUNCTION: - parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables, null)); - - // Test again: - try { - assertNotNull(parser.parseQuery("SELECT toto() FROM foo;")); - } catch(ParseException e) { - e.printStackTrace(); - fail("The parser allow ANY unknown function: this test should have succeeded!"); - } - - // Test the return type of the function generated by the parser: - try { - ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;"); - ADQLOperand fct = query.getSelect().get(0).getOperand(); - assertTrue(fct instanceof DefaultUDF); - assertNull(((DefaultUDF)fct).getDefinition()); - assertTrue(fct.isString()); - assertTrue(fct.isNumeric()); - } catch(ParseException e1) { - e1.printStackTrace(); - fail("The parser allow ANY unknown function: this test should have succeeded!"); - } - - // DECLARE THE UDF (while unknown functions are allowed): - try { - parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables, Arrays.asList(new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)) }))); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); - } - - // Test the return type of the function generated by the parser: - try { - ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;"); - ADQLOperand fct = query.getSelect().get(0).getOperand(); - assertTrue(fct instanceof DefaultUDF); - assertNotNull(((DefaultUDF)fct).getDefinition()); - assertTrue(fct.isString()); - assertFalse(fct.isNumeric()); - } catch(ParseException e1) { - e1.printStackTrace(); - fail("The parser allow ANY unknown function: this test should have succeeded!"); - } + fail("This query contains a DECLARED UDF: this test should have succeeded!"); + } - // DECLARE UDFs WITH SAME NAMES BUT DIFFERENT TYPE OF ARGUMENT: - try { - udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.VARCHAR)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.INTEGER)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.POINT)) }) }; - parser = new ADQLParser(); - parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); - } + // Test the return type checking inside a whole query: + try { + assertNotNull(parser.parseQuery("SELECT toto() || 'Blabla ' AS \"SuperText\" FROM foo;")); + } catch(ParseException e1) { + e1.printStackTrace(); + fail("This query contains a DECLARED UDF concatenated to a String: this test should have succeeded!"); + } + try { + parser.parseQuery("SELECT toto()*3 AS \"SuperError\" FROM foo;"); + fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!"); + } catch(ParseException e1) { + assertTrue(e1 instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A numeric value was expected instead of \"toto()\".", ex.getErrors().next().getMessage()); + } + + // Test the return type of the function TATA generated by the parser: + try { + ADQLQuery query = parser.parseQuery("SELECT tata() FROM foo;"); + ADQLOperand fct = query.getSelect().get(0).getOperand(); + assertTrue(fct instanceof DefaultUDF); + assertNotNull(((DefaultUDF)fct).getDefinition()); + assertFalse(fct.isString()); + assertTrue(fct.isNumeric()); + assertFalse(fct.isGeometry()); + } catch(ParseException e1) { + e1.printStackTrace(); + fail("This query contains a DECLARED UDF: this test should have succeeded!"); + } - // Test the return type in function of the parameter: - try { - assertNotNull(parser.parseQuery("SELECT toto('blabla') AS toto1, toto(123) AS toto2, toto(POINT('', 1, 2)) AS toto3 FROM foo;")); - } catch(ParseException e1) { - e1.printStackTrace(); - fail("This query contains two DECLARED UDFs used here: this test should have succeeded!"); - } - try { - parser.parseQuery("SELECT toto('blabla') * 123 AS \"SuperError\" FROM foo;"); - fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A numeric value was expected instead of \"toto('blabla')\".", ex.getErrors().next().getMessage()); - } - try { - parser.parseQuery("SELECT toto(123) || 'blabla' AS \"SuperError\" FROM foo;"); - fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A string value was expected instead of \"toto(123)\".", ex.getErrors().next().getMessage()); - } - try { - parser.parseQuery("SELECT toto(POINT('', 1, 2)) || 'blabla' AS \"SuperError\" FROM foo;"); - fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!"); - } catch(ParseException e) { - assertTrue(e instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; - assertEquals(1, ex.getNbErrors()); - assertEquals("Type mismatch! A string value was expected instead of \"toto(POINT('', 1, 2))\".", ex.getErrors().next().getMessage()); + // Test the return type checking inside a whole query: + try { + assertNotNull(parser.parseQuery("SELECT tata()*3 AS \"aNumeric\" FROM foo;")); + } catch(ParseException e1) { + e1.printStackTrace(); + fail("This query contains a DECLARED UDF multiplicated by 3: this test should have succeeded!"); + } + try { + parser.parseQuery("SELECT 'Blabla ' || tata() AS \"SuperError\" FROM foo;"); + fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!"); + } catch(ParseException e1) { + assertTrue(e1 instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A string value was expected instead of \"tata()\".", ex.getErrors().next().getMessage()); + } + try { + parser.parseQuery("SELECT tata() || 'Blabla ' AS \"SuperError\" FROM foo;"); + fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!"); + } catch(ParseException e1) { + assertTrue(e1 instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A string value was expected instead of \"tata()\".", ex.getErrors().next().getMessage()); + } + + // Test the return type of the function TITI generated by the parser: + try { + ADQLQuery query = parser.parseQuery("SELECT titi() FROM foo;"); + ADQLOperand fct = query.getSelect().get(0).getOperand(); + assertTrue(fct instanceof DefaultUDF); + assertNotNull(((DefaultUDF)fct).getDefinition()); + assertFalse(fct.isString()); + assertFalse(fct.isNumeric()); + assertTrue(fct.isGeometry()); + } catch(ParseException e1) { + e1.printStackTrace(); + fail("This query contains a DECLARED UDF: this test should have succeeded!"); + } + + // Test the return type checking inside a whole query: + try { + parser.parseQuery("SELECT CONTAINS(colG, titi()) ' AS \"Super\" FROM foo;"); + fail("Geometrical UDFs are not allowed for the moment in the ADQL language: this test should have failed!"); + } catch(ParseException e1) { + assertTrue(e1 instanceof ParseException); + assertEquals(" Encountered \"(\". Was expecting one of: \")\" \".\" \".\" \")\" ", e1.getMessage()); + } + try { + parser.parseQuery("SELECT titi()*3 AS \"SuperError\" FROM foo;"); + fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!"); + } catch(ParseException e1) { + assertTrue(e1 instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A numeric value was expected instead of \"titi()\".", ex.getErrors().next().getMessage()); + } + + // Try with functions wrapped on 2 levels: + // i.e. fct1('blabla', fct2(fct3('blabla'))) + try { + FunctionDef[] complexFcts = new FunctionDef[3]; + complexFcts[0] = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)), new FunctionParam("num", new DBType(DBDatatype.INTEGER)) }); + complexFcts[1] = new FunctionDef("fct2", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) }); + complexFcts[2] = new FunctionDef("fct3", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) }); + parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables, Arrays.asList(complexFcts))); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); + } + + // With parameters of the good type: + try { + assertNotNull(parser.parseQuery("SELECT fct1('blabla', fct2(fct3('blabla'))) FROM foo")); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Types are matching: this test should have succeeded!"); + } + // With parameters of the bad type: + try { + parser.parseQuery("SELECT fct2(fct1('blabla', fct3('blabla'))) FROM foo"); + fail("Parameters types are not matching: the parsing should have failed!"); + } catch(ParseException pe) { + assertEquals(UnresolvedIdentifiersException.class, pe.getClass()); + assertEquals(1, ((UnresolvedIdentifiersException)pe).getNbErrors()); + ParseException innerPe = ((UnresolvedIdentifiersException)pe).getErrors().next(); + assertEquals("Unresolved function: \"fct1('blabla', fct3('blabla'))\"! No UDF has been defined or found with the signature: fct1(STRING, STRING).", innerPe.getMessage()); + } + + // CLEAR ALL UDFs AND ALLOW UNKNOWN FUNCTION: + parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables, null)); + + // Test again: + try { + assertNotNull(parser.parseQuery("SELECT toto() FROM foo;")); + } catch(ParseException e) { + e.printStackTrace(); + fail("The parser allow ANY unknown function: this test should have succeeded!"); + } + + // Test the return type of the function generated by the parser: + try { + ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;"); + ADQLOperand fct = query.getSelect().get(0).getOperand(); + assertTrue(fct instanceof DefaultUDF); + assertNull(((DefaultUDF)fct).getDefinition()); + assertTrue(fct.isString()); + assertTrue(fct.isNumeric()); + } catch(ParseException e1) { + e1.printStackTrace(); + fail("The parser allow ANY unknown function: this test should have succeeded!"); + } + + // DECLARE THE UDF (while unknown functions are allowed): + try { + parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables, Arrays.asList(new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)) }))); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); + } + + // Test the return type of the function generated by the parser: + try { + ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;"); + ADQLOperand fct = query.getSelect().get(0).getOperand(); + assertTrue(fct instanceof DefaultUDF); + assertNotNull(((DefaultUDF)fct).getDefinition()); + assertTrue(fct.isString()); + assertFalse(fct.isNumeric()); + } catch(ParseException e1) { + e1.printStackTrace(); + fail("The parser allow ANY unknown function: this test should have succeeded!"); + } + + // DECLARE UDFs WITH SAME NAMES BUT DIFFERENT TYPE OF ARGUMENT: + try { + udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.VARCHAR)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.INTEGER)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.POINT)) }) }; + parser = new ADQLParser(version); + parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs))); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)"); + } + + // Test the return type in function of the parameter: + try { + assertNotNull(parser.parseQuery("SELECT toto('blabla') AS toto1, toto(123) AS toto2, toto(POINT('', 1, 2)) AS toto3 FROM foo;")); + } catch(ParseException e1) { + e1.printStackTrace(); + fail("This query contains two DECLARED UDFs used here: this test should have succeeded!"); + } + try { + parser.parseQuery("SELECT toto('blabla') * 123 AS \"SuperError\" FROM foo;"); + fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A numeric value was expected instead of \"toto('blabla')\".", ex.getErrors().next().getMessage()); + } + try { + parser.parseQuery("SELECT toto(123) || 'blabla' AS \"SuperError\" FROM foo;"); + fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A string value was expected instead of \"toto(123)\".", ex.getErrors().next().getMessage()); + } + try { + parser.parseQuery("SELECT toto(POINT('', 1, 2)) || 'blabla' AS \"SuperError\" FROM foo;"); + fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!"); + } catch(ParseException e) { + assertTrue(e instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e; + assertEquals(1, ex.getNbErrors()); + assertEquals("Type mismatch! A string value was expected instead of \"toto(POINT('', 1, 2))\".", ex.getErrors().next().getMessage()); + } } } diff --git a/test/adql/parser/TestADQLParser.java b/test/adql/parser/TestADQLParser.java index 6e3a76c537efbbc71441603746fac215439dddf8..6743b8ec66d6eb2d2323e680d7c46f1eeb7d3813 100644 --- a/test/adql/parser/TestADQLParser.java +++ b/test/adql/parser/TestADQLParser.java @@ -55,6 +55,36 @@ public class TestADQLParser { public void tearDown() throws Exception { } + @Test + public void testConstraintList() { + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + + /* TEST: in a constraint (i.e. Constraint() in the grammar), + * avoid ambiguity between (OPERAND) and (CONSTRAINT) */ + try { + parser.parseWhere("WHERE (mag + 2) < 5"); // CONSTRAINT = (OPERAND) COMP_OPERATOR OPERAND + parser.parseWhere("WHERE (mag < 2)"); // CONSTRAINT = (CONSTRAINT) + } catch(Exception ex) { + ex.printStackTrace(); + fail("[ADQL-" + version + "] Unexpected error while parsing WHERE valid conditions! (see console for more details)"); + } + + // CASE: same test but this time with an incorrect function argument + /* + * NOTE: If this expression is not correctly parsed, the raised + * error will be about CONTAINS instead of being about PI. + */ + try { + parser.parseWhere("WHERE CONTAINS(PI(), CIRCLE('', ra, dec)) = 1"); + fail("PI() is not a valid argument of CONTAINS(...)!"); + } catch(Exception ex) { + assertEquals(ParseException.class, ex.getClass()); + assertTrue(ex.getMessage().startsWith(" Encountered \"PI\". Was expecting one of: \"BOX\" \"CENTROID\" \"CIRCLE\" \"POINT\" \"POLYGON\"")); + } + } + } + @Test public void testNumericFunctionParams() { @@ -228,345 +258,497 @@ public class TestADQLParser { } @Test - public void testColumnReference() { - ADQLParser parser = new ADQLParser(); - try { - // ORDER BY - parser.parseQuery("SELECT * FROM cat ORDER BY oid;"); - parser.parseQuery("SELECT * FROM cat ORDER BY oid ASC;"); - parser.parseQuery("SELECT * FROM cat ORDER BY oid DESC;"); - parser.parseQuery("SELECT * FROM cat ORDER BY 1;"); - parser.parseQuery("SELECT * FROM cat ORDER BY 1 ASC;"); - parser.parseQuery("SELECT * FROM cat ORDER BY 1 DESC;"); - // GROUP BY - parser.parseQuery("SELECT * FROM cat GROUP BY oid;"); - parser.parseQuery("SELECT * FROM cat GROUP BY cat.oid;"); - // JOIN ... USING(...) - parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(oid);"); - } catch(Exception e) { - e.printStackTrace(System.err); - fail("These ADQL queries are strictly correct! No error should have occured. (see stdout for more details)"); - } + public void testGroupBy() { + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); - try { - // ORDER BY - parser.parseQuery("SELECT * FROM cat ORDER BY cat.oid;"); - fail("A qualified column name is forbidden in ORDER BY! This test should have failed."); - } catch(Exception e) { - assertEquals(ParseException.class, e.getClass()); - assertEquals(" Encountered \".\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage()); - } + // CASE: simple or qualified column name => OK! + try { + parser.parseQuery("SELECT * FROM cat GROUP BY oid;"); + parser.parseQuery("SELECT * FROM cat GROUP BY cat.oid;"); + } catch(Exception e) { + e.printStackTrace(System.err); + fail("These ADQL queries are strictly correct! No error should have occured. (see stdout for more details)"); + } - // Query reported as in error before the bug correction: - try { - parser.parseQuery("SELECT TOP 10 browndwarfs.cat.jmag FROM browndwarfs.cat ORDER BY browndwarfs.cat.jmag"); - fail("A qualified column name is forbidden in ORDER BY! This test should have failed."); - } catch(Exception e) { - assertEquals(ParseException.class, e.getClass()); - assertEquals(" Encountered \".\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage()); - } + /* CASE: in ADQL-2.0, only a column is allowed (on the contrary to + * ORDER BY which allows also an index of a selected column) + * => ERROR! */ + final String Q_INDEX = "SELECT * FROM cat GROUP BY 1;"; + if (version == ADQLVersion.V2_0) { + try { + // GROUP BY with a SELECT item index + parser.parseQuery(Q_INDEX); + fail("A SELECT item index is forbidden in GROUP BY! This test should have failed."); + } catch(Exception e) { + assertEquals(ParseException.class, e.getClass()); + assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage()); + } + } else { + try { + parser.parseQuery(Q_INDEX); + } catch(Exception e) { + e.printStackTrace(System.err); + fail("These ADQL queries are strictly correct! No error should have occured. (see stdout for more details)"); + } + } - try { - // GROUP BY with a SELECT item index - parser.parseQuery("SELECT * FROM cat GROUP BY 1;"); - fail("A SELECT item index is forbidden in GROUP BY! This test should have failed."); - } catch(Exception e) { - assertEquals(ParseException.class, e.getClass()); - assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage()); + // CASE: only from ADQL-2.1, any ADQL expression/operand => OK! + final String Q1 = "SELECT * FROM cat GROUP BY 1+2;", Q2 = "SELECT * FROM cat GROUP BY sqrt(foo);"; + if (version == ADQLVersion.V2_0) { + try { + parser.parseQuery(Q1); + fail("In ADQL-v2.0, GROUP BY with an expression is forbidden!"); + } catch(Exception e) { + assertEquals(ParseException.class, e.getClass()); + assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage()); + } + try { + parser.parseQuery(Q2); + fail("In ADQL-v2.0, GROUP BY with an expression is forbidden!"); + } catch(Exception e) { + assertEquals(ParseException.class, e.getClass()); + assertEquals(" Encountered \"sqrt\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> \n" + "(HINT: \"sqrt\" is a reserved ADQL word in v2.0. To use it as a column/table/schema name/alias, write it between double quotes.)", e.getMessage()); + } + } else { + try { + parser.parseQuery(Q1); + parser.parseQuery(Q2); + } catch(Exception e) { + e.printStackTrace(System.err); + fail("In ADQL-" + version + ", GROUP BY with expression SHOULD be allowed. (see console for more details)"); + } + } } + } - try { - // JOIN ... USING(...) - parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(cat.oid);"); - fail("A qualified column name is forbidden in USING(...)! This test should have failed."); - } catch(Exception e) { - assertEquals(ParseException.class, e.getClass()); - assertEquals(" Encountered \".\". Was expecting one of: \")\" \",\" ", e.getMessage()); + @Test + public void testOrderBy() { + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + try { + // CASE: Simple column name + ADQLQuery query; + query = parser.parseQuery("SELECT * FROM cat ORDER BY oid;"); + assertNotNull(query.getOrderBy().get(0).getExpression()); + query = parser.parseQuery("SELECT * FROM cat ORDER BY oid ASC;"); + assertNotNull(query.getOrderBy().get(0).getExpression()); + query = parser.parseQuery("SELECT * FROM cat ORDER BY oid DESC;"); + assertNotNull(query.getOrderBy().get(0).getExpression()); + + // CASE: selected column reference + query = parser.parseQuery("SELECT * FROM cat ORDER BY 1;"); + assertNotNull(query.getOrderBy().get(0).getColumnReference()); + query = parser.parseQuery("SELECT * FROM cat ORDER BY 1 ASC;"); + assertNotNull(query.getOrderBy().get(0).getColumnReference()); + query = parser.parseQuery("SELECT * FROM cat ORDER BY 1 DESC;"); + assertNotNull(query.getOrderBy().get(0).getColumnReference()); + } catch(Exception e) { + e.printStackTrace(System.err); + fail("These ADQL queries are strictly correct! No error should have occured. (cf console for more details)"); + } + + // CASE: only in ADQL-2.0, qualified columns are forbidden + String Q_QUAL_COL = "SELECT * FROM cat ORDER BY cat.oid;"; + if (version == ADQLVersion.V2_0) { + try { + parser.parseQuery(Q_QUAL_COL); + fail("In ADQL-v2.0, a qualified column name is forbidden in ORDER BY! This test should have failed."); + } catch(Exception e) { + assertEquals(ParseException.class, e.getClass()); + assertEquals(" Encountered \".\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage()); + } + } else { + try { + parser.parseQuery(Q_QUAL_COL); + } catch(Exception e) { + e.printStackTrace(); + fail("In ADQL-" + version + ", ORDER BY with a qualified column name should be allowed! (see console for more details)"); + } + } + + // CASE: Query reported as in error before a bug correction: + /* + * NOTE: same as above but with a real bug report. + */ + Q_QUAL_COL = "SELECT TOP 10 browndwarfs.cat.jmag FROM browndwarfs.cat ORDER BY browndwarfs.cat.jmag"; + if (version == ADQLVersion.V2_0) { + try { + parser.parseQuery(Q_QUAL_COL); + fail("A qualified column name is forbidden in ORDER BY! This test should have failed."); + } catch(Exception e) { + assertEquals(ParseException.class, e.getClass()); + assertEquals(" Encountered \".\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage()); + } + } else { + try { + parser.parseQuery(Q_QUAL_COL); + } catch(Exception e) { + e.printStackTrace(); + fail("In ADQL-" + version + ", ORDER BY with a qualified column name should be allowed! (see console for more details)"); + } + } + + // CASE: only from ADQL-2.1, any ADQL expression/operand => OK! + final String Q1 = "SELECT * FROM cat ORDER BY 1+2;", Q2 = "SELECT * FROM cat ORDER BY sqrt(foo);"; + if (version == ADQLVersion.V2_0) { + try { + parser.parseQuery(Q1); + fail("In ADQL-v2.0, ORDER BY with an expression is forbidden!"); + } catch(Exception e) { + assertEquals(ParseException.class, e.getClass()); + assertEquals(" Encountered \"+\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage()); + } + try { + parser.parseQuery(Q2); + fail("In ADQL-v2.0, ORDER BY with an expression is forbidden!"); + } catch(Exception e) { + assertEquals(ParseException.class, e.getClass()); + assertEquals(" Encountered \"sqrt\". Was expecting one of: <UNSIGNED_INTEGER> \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> \n" + "(HINT: \"sqrt\" is a reserved ADQL word in v2.0. To use it as a column/table/schema name/alias, write it between double quotes.)", e.getMessage()); + } + } else { + try { + parser.parseQuery(Q1); + parser.parseQuery(Q2); + } catch(Exception e) { + e.printStackTrace(System.err); + fail("In ADQL-" + version + ", ORDER BY with expression SHOULD be allowed. (see console for more details)"); + } + } } + } - try { - // JOIN ... USING(...) - parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(1);"); - fail("A column index is forbidden in USING(...)! This test should have failed."); - } catch(Exception e) { - assertEquals(ParseException.class, e.getClass()); - assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage()); + @Test + public void testJoinUsing() { + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + try { + // JOIN ... USING(...) + parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(oid);"); + } catch(Exception e) { + e.printStackTrace(System.err); + fail("This ADQL query is strictly correct! No error should have occured. (see stdout for more details)"); + } + try { + // JOIN ... USING(...) + parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(cat.oid);"); + fail("A qualified column name is forbidden in USING(...)! This test should have failed."); + } catch(Exception e) { + assertEquals(ParseException.class, e.getClass()); + assertEquals(" Encountered \".\". Was expecting one of: \")\" \",\" ", e.getMessage()); + } + + try { + // JOIN ... USING(...) + parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(1);"); + fail("A column index is forbidden in USING(...)! This test should have failed."); + } catch(Exception e) { + assertEquals(ParseException.class, e.getClass()); + assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage()); + } } } @Test public void testDelimitedIdentifiersWithDot() { - ADQLParser parser = new ADQLParser(); - try { - ADQLQuery query = parser.parseQuery("SELECT * FROM \"B/avo.rad/catalog\";"); - assertEquals("B/avo.rad/catalog", query.getFrom().getTables().get(0).getTableName()); - } catch(Exception e) { - e.printStackTrace(System.err); - fail("The ADQL query is strictly correct! No error should have occured. (see stdout for more details)"); + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + try { + ADQLQuery query = parser.parseQuery("SELECT * FROM \"B/avo.rad/catalog\";"); + assertEquals("B/avo.rad/catalog", query.getFrom().getTables().get(0).getTableName()); + } catch(Exception e) { + e.printStackTrace(System.err); + fail("The ADQL query is strictly correct! No error should have occured. (see stdout for more details)"); + } } } @Test public void testJoinTree() { - ADQLParser parser = new ADQLParser(); - try { - String[] queries = new String[]{ "SELECT * FROM aTable A JOIN aSecondTable B ON A.id = B.id JOIN aThirdTable C ON B.id = C.id;", "SELECT * FROM aTable A NATURAL JOIN aSecondTable B NATURAL JOIN aThirdTable C;" }; - for(String q : queries) { - ADQLQuery query = parser.parseQuery(q); - - assertTrue(query.getFrom() instanceof ADQLJoin); - - ADQLJoin join = ((ADQLJoin)query.getFrom()); - assertTrue(join.getLeftTable() instanceof ADQLJoin); - assertTrue(join.getRightTable() instanceof ADQLTable); - assertEquals("aThirdTable", ((ADQLTable)join.getRightTable()).getTableName()); - - join = (ADQLJoin)join.getLeftTable(); - assertTrue(join.getLeftTable() instanceof ADQLTable); - assertEquals("aTable", ((ADQLTable)join.getLeftTable()).getTableName()); - assertTrue(join.getRightTable() instanceof ADQLTable); - assertEquals("aSecondTable", ((ADQLTable)join.getRightTable()).getTableName()); + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + try { + String[] queries = new String[]{ "SELECT * FROM aTable A JOIN aSecondTable B ON A.id = B.id JOIN aThirdTable C ON B.id = C.id;", "SELECT * FROM aTable A NATURAL JOIN aSecondTable B NATURAL JOIN aThirdTable C;" }; + for(String q : queries) { + ADQLQuery query = parser.parseQuery(q); + + assertTrue(query.getFrom() instanceof ADQLJoin); + + ADQLJoin join = ((ADQLJoin)query.getFrom()); + assertTrue(join.getLeftTable() instanceof ADQLJoin); + assertTrue(join.getRightTable() instanceof ADQLTable); + assertEquals("aThirdTable", ((ADQLTable)join.getRightTable()).getTableName()); + + join = (ADQLJoin)join.getLeftTable(); + assertTrue(join.getLeftTable() instanceof ADQLTable); + assertEquals("aTable", ((ADQLTable)join.getLeftTable()).getTableName()); + assertTrue(join.getRightTable() instanceof ADQLTable); + assertEquals("aSecondTable", ((ADQLTable)join.getRightTable()).getTableName()); + } + } catch(Exception e) { + e.printStackTrace(System.err); + fail("The ADQL query is strictly correct! No error should have occured. (see stdout for more details)"); } - } catch(Exception e) { - e.printStackTrace(System.err); - fail("The ADQL query is strictly correct! No error should have occured. (see stdout for more details)"); } } @Test public void test() { - ADQLParser parser = new ADQLParser(); - try { - ADQLQuery query = parser.parseQuery("SELECT 'truc''machin' 'bidule' --- why not a comment now ^^\n'FIN' FROM foo;"); - assertNotNull(query); - assertEquals("truc'machinbiduleFIN", ((StringConstant)(query.getSelect().get(0).getOperand())).getValue()); - assertEquals("'truc''machinbiduleFIN'", query.getSelect().get(0).getOperand().toADQL()); - } catch(Exception ex) { - fail("String litteral concatenation is perfectly legal according to the ADQL standard."); - } + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); + try { + ADQLQuery query = parser.parseQuery("SELECT 'truc''machin' 'bidule' --- why not a comment now ^^\n'FIN' FROM foo;"); + assertNotNull(query); + assertEquals("truc'machinbiduleFIN", ((StringConstant)(query.getSelect().get(0).getOperand())).getValue()); + assertEquals("'truc''machinbiduleFIN'", query.getSelect().get(0).getOperand().toADQL()); + } catch(Exception ex) { + fail("String litteral concatenation is perfectly legal according to the ADQL standard."); + } - // With a comment ending the query - try { - ADQLQuery query = parser.parseQuery("SELECT TOP 1 * FROM ivoa.ObsCore -- comment"); - assertNotNull(query); - } catch(Exception ex) { - ex.printStackTrace(); - fail("String litteral concatenation is perfectly legal according to the ADQL standard."); + // With a comment ending the query + try { + ADQLQuery query = parser.parseQuery("SELECT TOP 1 * FROM ivoa.ObsCore -- comment"); + assertNotNull(query); + } catch(Exception ex) { + ex.printStackTrace(); + fail("String litteral concatenation is perfectly legal according to the ADQL standard."); + } } } @Test public void testIncorrectCharacter() { - /* An identifier must be written only with digits, an underscore or - * regular latin characters: */ - try { - (new ADQLParser()).parseQuery("select gr\u00e9gory FROM aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().startsWith("Incorrect character encountered at l.1, c.10: ")); - assertTrue(t.getMessage().endsWith("Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it).")); - } + for(ADQLVersion version : ADQLVersion.values()) { + /* An identifier must be written only with digits, an underscore or + * regular latin characters: */ + try { + (new ADQLParser(version)).parseQuery("select gr\u00e9gory FROM aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().startsWith("Incorrect character encountered at l.1, c.10: ")); + assertTrue(t.getMessage().endsWith("Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it).")); + } - /* Un-finished double/single quoted string: */ - try { - (new ADQLParser()).parseQuery("select \"stuff FROM aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().startsWith("Incorrect character encountered at l.1, c.26: <EOF>")); - assertTrue(t.getMessage().endsWith("Possible cause: a string between single or double quotes which is never closed (solution: well...just close it!).")); - } + /* Un-finished double/single quoted string: */ + try { + (new ADQLParser(version)).parseQuery("select \"stuff FROM aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().startsWith("Incorrect character encountered at l.1, c.26: <EOF>")); + assertTrue(t.getMessage().endsWith("Possible cause: a string between single or double quotes which is never closed (solution: well...just close it!).")); + } - // But in a string, delimited identifier or a comment, it is fine: - try { - (new ADQLParser()).parseQuery("select 'gr\u00e9gory' FROM aTable"); - (new ADQLParser()).parseQuery("select \"gr\u00e9gory\" FROM aTable"); - (new ADQLParser()).parseQuery("select * FROM aTable -- a comment by Gr\u00e9gory"); - } catch(Throwable t) { - fail("This error should never occurs because all these queries have an accentuated character but at a correct place."); + // But in a string, delimited identifier or a comment, it is fine: + try { + (new ADQLParser(version)).parseQuery("select 'gr\u00e9gory' FROM aTable"); + (new ADQLParser(version)).parseQuery("select \"gr\u00e9gory\" FROM aTable"); + (new ADQLParser(version)).parseQuery("select * FROM aTable -- a comment by Gr\u00e9gory"); + } catch(Throwable t) { + fail("This error should never occurs because all these queries have an accentuated character but at a correct place."); + } } } @Test public void testMultipleSpacesInOrderAndGroupBy() { - try { - ADQLParser parser = new ADQLParser(); + for(ADQLVersion version : ADQLVersion.values()) { + try { + ADQLParser parser = new ADQLParser(version); - // Single space: - parser.parseQuery("select * from aTable ORDER BY aCol"); - parser.parseQuery("select * from aTable GROUP BY aCol"); + // Single space: + parser.parseQuery("select * from aTable ORDER BY aCol"); + parser.parseQuery("select * from aTable GROUP BY aCol"); - // More than one space: - parser.parseQuery("select * from aTable ORDER BY aCol"); - parser.parseQuery("select * from aTable GROUP BY aCol"); + // More than one space: + parser.parseQuery("select * from aTable ORDER BY aCol"); + parser.parseQuery("select * from aTable GROUP BY aCol"); - // With any other space character: - parser.parseQuery("select * from aTable ORDER\tBY aCol"); - parser.parseQuery("select * from aTable ORDER\nBY aCol"); - parser.parseQuery("select * from aTable ORDER \t\nBY aCol"); + // With any other space character: + parser.parseQuery("select * from aTable ORDER\tBY aCol"); + parser.parseQuery("select * from aTable ORDER\nBY aCol"); + parser.parseQuery("select * from aTable ORDER \t\nBY aCol"); - parser.parseQuery("select * from aTable GROUP\tBY aCol"); - parser.parseQuery("select * from aTable GROUP\nBY aCol"); - parser.parseQuery("select * from aTable GROUP \t\nBY aCol"); - } catch(Throwable t) { - t.printStackTrace(); - fail("Having multiple space characters between the ORDER/GROUP and the BY keywords should not generate any parsing error."); + parser.parseQuery("select * from aTable GROUP\tBY aCol"); + parser.parseQuery("select * from aTable GROUP\nBY aCol"); + parser.parseQuery("select * from aTable GROUP \t\nBY aCol"); + } catch(Throwable t) { + t.printStackTrace(); + fail("Having multiple space characters between the ORDER/GROUP and the BY keywords should not generate any parsing error."); + } } } @Test public void testADQLReservedWord() { - ADQLParser parser = new ADQLParser(); + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); - final String hintAbs = ".*\n\\(HINT: \"abs\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"; - final String hintPoint = ".*\n\\(HINT: \"point\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"; - final String hintExists = ".*\n\\(HINT: \"exists\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"; - final String hintLike = ".*\n\\(HINT: \"LIKE\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"; + final String hintAbs = ".*\n\\(HINT: \"abs\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"; + final String hintPoint = ".*\n\\(HINT: \"point\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"; + final String hintExists = ".*\n\\(HINT: \"exists\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"; + final String hintLike = ".*\n\\(HINT: \"LIKE\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"; - /* TEST AS A COLUMN/TABLE/SCHEMA NAME... */ - // ...with a numeric function name (but no param): - try { - parser.parseQuery("select abs from aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(hintAbs)); - } - // ...with a geometric function name (but no param): - try { - parser.parseQuery("select point from aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(hintPoint)); - } - // ...with an ADQL function name (but no param): - try { - parser.parseQuery("select exists from aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(hintExists)); - } - // ...with an ADQL syntax item: - try { - parser.parseQuery("select LIKE from aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(hintLike)); - } + /* TEST AS A COLUMN/TABLE/SCHEMA NAME... */ + // ...with a numeric function name (but no param): + try { + parser.parseQuery("select abs from aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(hintAbs)); + } + // ...with a geometric function name (but no param): + try { + parser.parseQuery("select point from aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(hintPoint)); + } + // ...with an ADQL function name (but no param): + try { + parser.parseQuery("select exists from aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(hintExists)); + } + // ...with an ADQL syntax item: + try { + parser.parseQuery("select LIKE from aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(hintLike)); + } - /* TEST AS AN ALIAS... */ - // ...with a numeric function name (but no param): - try { - parser.parseQuery("select aCol AS abs from aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(hintAbs)); - } - // ...with a geometric function name (but no param): - try { - parser.parseQuery("select aCol AS point from aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(hintPoint)); - } - // ...with an ADQL function name (but no param): - try { - parser.parseQuery("select aCol AS exists from aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(hintExists)); - } - // ...with an ADQL syntax item: - try { - parser.parseQuery("select aCol AS LIKE from aTable"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(hintLike)); - } + /* TEST AS AN ALIAS... */ + // ...with a numeric function name (but no param): + try { + parser.parseQuery("select aCol AS abs from aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(hintAbs)); + } + // ...with a geometric function name (but no param): + try { + parser.parseQuery("select aCol AS point from aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(hintPoint)); + } + // ...with an ADQL function name (but no param): + try { + parser.parseQuery("select aCol AS exists from aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(hintExists)); + } + // ...with an ADQL syntax item: + try { + parser.parseQuery("select aCol AS LIKE from aTable"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(hintLike)); + } - /* TEST AT THE END OF THE QUERY (AND IN A WHERE) */ - try { - parser.parseQuery("select aCol from aTable WHERE toto = abs"); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(hintAbs)); + /* TEST AT THE END OF THE QUERY (AND IN A WHERE) */ + try { + parser.parseQuery("select aCol from aTable WHERE toto = abs"); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(hintAbs)); + } } } @Test public void testSQLReservedWord() { - ADQLParser parser = new ADQLParser(); + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); - try { - parser.parseQuery("SELECT rows FROM aTable"); - fail("\"ROWS\" is an SQL reserved word. This query should not pass."); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(".*\n\\(HINT: \"rows\" is not supported in ADQL v[0-9]+\\.[0-9]+, but is however a reserved word\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)")); - } + try { + parser.parseQuery("SELECT rows FROM aTable"); + fail("\"ROWS\" is an SQL reserved word. This query should not pass."); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(".*\n\\(HINT: \"rows\" is not supported in ADQL v[0-9]+\\.[0-9]+, but is however a reserved word\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)")); + } - try { - parser.parseQuery("SELECT CASE WHEN aCol = 2 THEN 'two' ELSE 'smth else' END as str FROM aTable"); - fail("ADQL does not support the CASE syntax. This query should not pass."); - } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertTrue(t.getMessage().matches(".*\n\\(HINT: \"CASE\" is not supported in ADQL v[0-9]+\\.[0-9]+, but is however a reserved word\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)")); + try { + parser.parseQuery("SELECT CASE WHEN aCol = 2 THEN 'two' ELSE 'smth else' END as str FROM aTable"); + fail("ADQL does not support the CASE syntax. This query should not pass."); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertTrue(t.getMessage().matches(".*\n\\(HINT: \"CASE\" is not supported in ADQL v[0-9]+\\.[0-9]+, but is however a reserved word\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)")); + } } } @Test public void testUDFName() { - ADQLParser parser = new ADQLParser(); - - // CASE: Valid UDF name => OK - try { - parser.parseQuery("SELECT foo(p1,p2) FROM aTable"); - } catch(Throwable t) { - t.printStackTrace(); - fail("Unexpected parsing error! This query should have passed. (see console for more details)"); - } + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); +<<<<<<< HEAD // CASE: Invalid UDF name => ParseException final String[] functionsToTest = new String[]{ "_foo", "2do", "do!" }; for(String fct : functionsToTest){ try{ parser.parseQuery("SELECT " + fct + "(p1,p2) FROM aTable"); fail("A UDF name like \"" + fct + "\" is not allowed by the ADQL grammar. This query should not pass."); +======= + // CASE: Valid UDF name => OK + try { + parser.parseQuery("SELECT foo(p1,p2) FROM aTable"); +>>>>>>> [ADQL] 4 commits in one: 1/ new syntax of ORDER and GROUP BY, 2/ fix positions } catch(Throwable t) { - assertEquals(ParseException.class, t.getClass()); - assertEquals("Invalid (User Defined) Function name: \"" + fct + "\"!", t.getMessage()); + t.printStackTrace(); + fail("Unexpected parsing error! This query should have passed. (see console for more details)"); + } + + // CASE: Invalid UDF name => ParseException + final String[] functionsToTest = new String[]{ "_foo", "2do", "do!" }; + for(String fct : functionsToTest) { + try { + parser.parseQuery("SELECT " + fct + "(p1,p2) FROM aTable"); + fail("A UDF name like \"" + fct + "\" is not allowed by the ADQL grammar. This query should not pass."); + } catch(Throwable t) { + assertEquals(ParseException.class, t.getClass()); + assertEquals("Invalid (User Defined) Function name: \"" + fct + "\"!", t.getMessage()); + } } } } @Test public void testUDFDeclaration() { - ADQLParser parser = new ADQLParser(); + for(ADQLVersion version : ADQLVersion.values()) { + ADQLParser parser = new ADQLParser(version); - // CASE: Any UDF allowed => OK! - parser.getSupportedFeatures().allowAnyUdf(true); - try { - assertNotNull(parser.parseQuery("SELECT foo(1,2) FROM bar")); - } catch(Throwable t) { - t.printStackTrace(); - fail("Unexpected parsing error! This query should have passed. (see console for more details)"); - } + // CASE: Any UDF allowed => OK! + parser.getSupportedFeatures().allowAnyUdf(true); + try { + assertNotNull(parser.parseQuery("SELECT foo(1,2) FROM bar")); + } catch(Throwable t) { + t.printStackTrace(); + fail("Unexpected parsing error! This query should have passed. (see console for more details)"); + } - // CASE: No UDF allowed => ERROR - parser.getSupportedFeatures().allowAnyUdf(false); - try { - parser.parseQuery("SELECT foo(1,2) FROM bar"); - fail("No UDF is allowed. This query should have failed!"); - } catch(Throwable t) { - assertEquals(UnresolvedIdentifiersException.class, t.getClass()); - assertEquals("1 unsupported expressions!\n - Unsupported ADQL feature: \"foo(param1 ?type?, param2 ?type?) -> ?type?\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-udf')!", t.getMessage()); - } + // CASE: No UDF allowed => ERROR + parser.getSupportedFeatures().allowAnyUdf(false); + try { + parser.parseQuery("SELECT foo(1,2) FROM bar"); + fail("No UDF is allowed. This query should have failed!"); + } catch(Throwable t) { + assertEquals(UnresolvedIdentifiersException.class, t.getClass()); + assertEquals("1 unsupported expressions!\n - Unsupported ADQL feature: \"foo(param1 ?type?, param2 ?type?) -> ?type?\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-udf')!", t.getMessage()); + } - // CASE: a single UDF declared => OK! - try { - parser.getSupportedFeatures().support(FunctionDef.parse("foo(i1 INTEGER, i2 INTEGER) -> INTEGER").toLanguageFeature()); - assertNotNull(parser.parseQuery("SELECT foo(1,2) FROM bar")); - } catch(Throwable t) { - t.printStackTrace(); - fail("Unexpected parsing error! This query should have passed. (see console for more details)"); + // CASE: a single UDF declared => OK! + try { + parser.getSupportedFeatures().support(FunctionDef.parse("foo(i1 INTEGER, i2 INTEGER) -> INTEGER").toLanguageFeature()); + assertNotNull(parser.parseQuery("SELECT foo(1,2) FROM bar")); + } catch(Throwable t) { + t.printStackTrace(); + fail("Unexpected parsing error! This query should have passed. (see console for more details)"); + } } } @@ -650,79 +832,81 @@ public class TestADQLParser { @Test public void testGeometry() { - // DECLARE A SIMPLE PARSER where all geometries are allowed by default: - ADQLParser parser = new ADQLParser(); - - // Test with several geometries while all are allowed: - try { - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!"); - } + for(ADQLVersion version : ADQLVersion.values()) { + // DECLARE A SIMPLE PARSER where all geometries are allowed by default: + ADQLParser parser = new ADQLParser(version); - // Test with several geometries while only the allowed ones: - try { - parser = new ADQLParser(); - parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO); - parser.getSupportedFeatures().support(ContainsFunction.FEATURE); - parser.getSupportedFeatures().support(PointFunction.FEATURE); - parser.getSupportedFeatures().support(CircleFunction.FEATURE); + // Test with several geometries while all are allowed: + try { + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!"); + } - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!"); - } - try { - parser.parseQuery("SELECT * FROM foo WHERE INTERSECTS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"); - fail("This query contains a not-allowed geometry function (INTERSECTS): this test should have failed!"); - } catch(ParseException pe) { - assertTrue(pe instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; - assertEquals(1, ex.getNbErrors()); - assertEquals("Unsupported ADQL feature: \"INTERSECTS\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", ex.getErrors().next().getMessage()); - } + // Test with several geometries while only the allowed ones: + try { + parser = new ADQLParser(version); + parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO); + parser.getSupportedFeatures().support(ContainsFunction.FEATURE); + parser.getSupportedFeatures().support(PointFunction.FEATURE); + parser.getSupportedFeatures().support(CircleFunction.FEATURE); + + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!"); + } + try { + parser.parseQuery("SELECT * FROM foo WHERE INTERSECTS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"); + fail("This query contains a not-allowed geometry function (INTERSECTS): this test should have failed!"); + } catch(ParseException pe) { + assertTrue(pe instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; + assertEquals(1, ex.getNbErrors()); + assertEquals("Unsupported ADQL feature: \"INTERSECTS\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", ex.getErrors().next().getMessage()); + } - // Test by adding REGION: - try { - parser = new ADQLParser(); - parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO); - parser.getSupportedFeatures().support(ContainsFunction.FEATURE); - parser.getSupportedFeatures().support(PointFunction.FEATURE); - parser.getSupportedFeatures().support(CircleFunction.FEATURE); - parser.getSupportedFeatures().support(RegionFunction.FEATURE); - - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('Position 12.3 45.6'), REGION('circle 1.2 2.3 5')) = 1;")); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!"); - } - try { - parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('Position 12.3 45.6'), REGION('BOX 1.2 2.3 5 9')) = 1;"); - fail("This query contains a not-allowed geometry function (BOX): this test should have failed!"); - } catch(ParseException pe) { - assertTrue(pe instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; - assertEquals(1, ex.getNbErrors()); - assertEquals("Unsupported STC-s region type: \"BOX\" (equivalent to the ADQL feature \"BOX\" of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", ex.getErrors().next().getMessage()); - } + // TODO Test by adding REGION: // Only possible with ADQL-2.0 since in ADQL-2.1, REGION has been removed! + try { + parser = new ADQLParser(ADQLVersion.V2_0); + parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO); + parser.getSupportedFeatures().support(ContainsFunction.FEATURE); + parser.getSupportedFeatures().support(PointFunction.FEATURE); + parser.getSupportedFeatures().support(CircleFunction.FEATURE); + parser.getSupportedFeatures().support(RegionFunction.FEATURE); + + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('Position 12.3 45.6'), REGION('circle 1.2 2.3 5')) = 1;")); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("[ADQL-" + parser.getADQLVersion() + "] This query contains several geometries, and all are theoretically allowed: this test should have succeeded!"); + } + try { + parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('Position 12.3 45.6'), REGION('BOX 1.2 2.3 5 9')) = 1;"); + fail("This query contains a not-allowed geometry function (BOX): this test should have failed!"); + } catch(ParseException pe) { + assertTrue(pe instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; + assertEquals(1, ex.getNbErrors()); + assertEquals("Unsupported STC-s region type: \"BOX\" (equivalent to the ADQL feature \"BOX\" of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", ex.getErrors().next().getMessage()); + } - // Test with several geometries while none geometry is allowed: - try { - parser = new ADQLParser(); - parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO); - - parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"); - fail("This query contains geometries while they are all forbidden: this test should have failed!"); - } catch(ParseException pe) { - assertTrue(pe instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; - assertEquals(3, ex.getNbErrors()); - Iterator<ParseException> itErrors = ex.getErrors(); - assertEquals("Unsupported ADQL feature: \"CONTAINS\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage()); - assertEquals("Unsupported ADQL feature: \"POINT\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage()); - assertEquals("Unsupported ADQL feature: \"CIRCLE\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage()); + // Test with several geometries while none geometry is allowed: + try { + parser = new ADQLParser(version); + parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO); + + parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"); + fail("This query contains geometries while they are all forbidden: this test should have failed!"); + } catch(ParseException pe) { + assertTrue(pe instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; + assertEquals(3, ex.getNbErrors()); + Iterator<ParseException> itErrors = ex.getErrors(); + assertEquals("Unsupported ADQL feature: \"CONTAINS\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage()); + assertEquals("Unsupported ADQL feature: \"POINT\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage()); + assertEquals("Unsupported ADQL feature: \"CIRCLE\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage()); + } } } @@ -757,95 +941,107 @@ public class TestADQLParser { @Test public void testCoordSys() { - // DECLARE A SIMPLE PARSER where all coordinate systems are allowed by default: - ADQLParser parser = new ADQLParser(); + for(ADQLVersion version : ADQLVersion.values()) { + // DECLARE A SIMPLE PARSER where all coordinate systems are allowed by default: + ADQLParser parser = new ADQLParser(version); - // Test with several coordinate systems while all are allowed: - try { - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('icrs', 12.3, 45.6), CIRCLE('cartesian2', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('lsr', 12.3, 45.6), CIRCLE('galactic heliocenter', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('unknownframe', 12.3, 45.6), CIRCLE('galactic unknownrefpos spherical2', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position icrs lsr 12.3 45.6'), REGION('circle fk5 1.2 2.3 5')) = 1;")); - assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;")); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("This query contains several valid coordinate systems, and all are theoretically allowed: this test should have succeeded!"); - } - - // Concatenation as coordinate systems not checked: - try { - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('From ' || 'here', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("This query contains a concatenation as coordinate systems (but only string constants are checked): this test should have succeeded!"); - } + // Test with several coordinate systems while all are allowed: + try { + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('icrs', 12.3, 45.6), CIRCLE('cartesian2', 1.2, 2.3, 5)) = 1;")); + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('lsr', 12.3, 45.6), CIRCLE('galactic heliocenter', 1.2, 2.3, 5)) = 1;")); + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('unknownframe', 12.3, 45.6), CIRCLE('galactic unknownrefpos spherical2', 1.2, 2.3, 5)) = 1;")); + if (version == ADQLVersion.V2_0) { + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position icrs lsr 12.3 45.6'), REGION('circle fk5 1.2 2.3 5')) = 1;")); + assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;")); + } + } catch(ParseException pe) { + pe.printStackTrace(); + fail("This query contains several valid coordinate systems, and all are theoretically allowed: this test should have succeeded!"); + } - // Test with several coordinate systems while only some allowed: - try { - parser = new ADQLParser(); - parser.setAllowedCoordSys(Arrays.asList(new String[]{ "icrs * *", "fk4 geocenter *", "galactic * spherical2" })); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('icrs', 12.3, 45.6), CIRCLE('cartesian3', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT POINT('fk4', 12.3, 45.6) FROM foo;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('fk4 geocenter', 12.3, 45.6), CIRCLE('cartesian2', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('galactic', 12.3, 45.6), CIRCLE('galactic spherical2', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('galactic geocenter', 12.3, 45.6), CIRCLE('galactic lsr spherical2', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position galactic lsr 12.3 45.6'), REGION('circle icrs 1.2 2.3 5')) = 1;")); - assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;")); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("This query contains several valid coordinate systems, and all are theoretically allowed: this test should have succeeded!"); - } - try { - parser.parseQuery("SELECT POINT('fk5 geocenter', 12.3, 45.6) FROM foo;"); - fail("This query contains a not-allowed coordinate system ('fk5' is not allowed): this test should have failed!"); - } catch(ParseException pe) { - assertTrue(pe instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; - assertEquals(1, ex.getNbErrors()); - assertEquals("Coordinate system \"fk5 geocenter\" (= \"FK5 GEOCENTER SPHERICAL2\") not allowed in this implementation. Allowed coordinate systems are: fk4 geocenter *, galactic * spherical2, icrs * *", ex.getErrors().next().getMessage()); - } - try { - parser.parseQuery("SELECT Region('not(position fk5 heliocenter 1 2)') FROM foo;"); - fail("This query contains a not-allowed coordinate system ('fk5' is not allowed): this test should have failed!"); - } catch(ParseException pe) { - assertTrue(pe instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; - assertEquals(1, ex.getNbErrors()); - assertEquals("Coordinate system \"FK5 HELIOCENTER\" (= \"FK5 HELIOCENTER SPHERICAL2\") not allowed in this implementation. Allowed coordinate systems are: fk4 geocenter *, galactic * spherical2, icrs * *", ex.getErrors().next().getMessage()); - } + // Concatenation as coordinate systems not checked: + try { + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('From ' || 'here', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("This query contains a concatenation as coordinate systems (but only string constants are checked): this test should have succeeded!"); + } - // Test with a coordinate system while none is allowed: - try { - parser = new ADQLParser(); - parser.setAllowedCoordSys(new ArrayList<String>(0)); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); - assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position 12.3 45.6'), REGION('circle 1.2 2.3 5')) = 1;")); - assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;")); - } catch(ParseException pe) { - pe.printStackTrace(); - fail("This query specifies none coordinate system: this test should have succeeded!"); - } - try { - parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('ICRS SPHERICAL2', 12.3, 45.6), CIRCLE('icrs', 1.2, 2.3, 5)) = 1;"); - fail("This query specifies coordinate systems while they are all forbidden: this test should have failed!"); - } catch(ParseException pe) { - assertTrue(pe instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; - assertEquals(2, ex.getNbErrors()); - Iterator<ParseException> itErrors = ex.getErrors(); - assertEquals("Coordinate system \"ICRS SPHERICAL2\" (= \"ICRS UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", itErrors.next().getMessage()); - assertEquals("Coordinate system \"icrs\" (= \"ICRS UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", itErrors.next().getMessage()); - } - try { - parser.parseQuery("SELECT Region('not(position fk4 1 2)') FROM foo;"); - fail("This query specifies coordinate systems while they are all forbidden: this test should have failed!"); - } catch(ParseException pe) { - assertTrue(pe instanceof UnresolvedIdentifiersException); - UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; - assertEquals(1, ex.getNbErrors()); - assertEquals("Coordinate system \"FK4\" (= \"FK4 UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", ex.getErrors().next().getMessage()); + // Test with several coordinate systems while only some allowed: + try { + parser = new ADQLParser(version); + parser.setAllowedCoordSys(Arrays.asList(new String[]{ "icrs * *", "fk4 geocenter *", "galactic * spherical2" })); + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('icrs', 12.3, 45.6), CIRCLE('cartesian3', 1.2, 2.3, 5)) = 1;")); + assertNotNull(parser.parseQuery("SELECT POINT('fk4', 12.3, 45.6) FROM foo;")); + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('fk4 geocenter', 12.3, 45.6), CIRCLE('cartesian2', 1.2, 2.3, 5)) = 1;")); + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('galactic', 12.3, 45.6), CIRCLE('galactic spherical2', 1.2, 2.3, 5)) = 1;")); + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('galactic geocenter', 12.3, 45.6), CIRCLE('galactic lsr spherical2', 1.2, 2.3, 5)) = 1;")); + if (version == ADQLVersion.V2_0) { + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position galactic lsr 12.3 45.6'), REGION('circle icrs 1.2 2.3 5')) = 1;")); + assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;")); + } + } catch(ParseException pe) { + pe.printStackTrace(); + fail("This query contains several valid coordinate systems, and all are theoretically allowed: this test should have succeeded!"); + } + try { + parser.parseQuery("SELECT POINT('fk5 geocenter', 12.3, 45.6) FROM foo;"); + fail("This query contains a not-allowed coordinate system ('fk5' is not allowed): this test should have failed!"); + } catch(ParseException pe) { + assertTrue(pe instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; + assertEquals(1, ex.getNbErrors()); + assertEquals("Coordinate system \"fk5 geocenter\" (= \"FK5 GEOCENTER SPHERICAL2\") not allowed in this implementation. Allowed coordinate systems are: fk4 geocenter *, galactic * spherical2, icrs * *", ex.getErrors().next().getMessage()); + } + if (version == ADQLVersion.V2_0) { + try { + parser.parseQuery("SELECT Region('not(position fk5 heliocenter 1 2)') FROM foo;"); + fail("This query contains a not-allowed coordinate system ('fk5' is not allowed): this test should have failed!"); + } catch(ParseException pe) { + assertTrue(pe instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; + assertEquals(1, ex.getNbErrors()); + assertEquals("Coordinate system \"FK5 HELIOCENTER\" (= \"FK5 HELIOCENTER SPHERICAL2\") not allowed in this implementation. Allowed coordinate systems are: fk4 geocenter *, galactic * spherical2, icrs * *", ex.getErrors().next().getMessage()); + } + } + + // Test with a coordinate system while none is allowed: + try { + parser = new ADQLParser(version); + parser.setAllowedCoordSys(new ArrayList<String>(0)); + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;")); + if (version == ADQLVersion.V2_0) { + assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position 12.3 45.6'), REGION('circle 1.2 2.3 5')) = 1;")); + assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;")); + } + } catch(ParseException pe) { + pe.printStackTrace(); + fail("This query specifies none coordinate system: this test should have succeeded!"); + } + try { + parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('ICRS SPHERICAL2', 12.3, 45.6), CIRCLE('icrs', 1.2, 2.3, 5)) = 1;"); + fail("This query specifies coordinate systems while they are all forbidden: this test should have failed!"); + } catch(ParseException pe) { + assertTrue(pe instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; + assertEquals(2, ex.getNbErrors()); + Iterator<ParseException> itErrors = ex.getErrors(); + assertEquals("Coordinate system \"ICRS SPHERICAL2\" (= \"ICRS UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", itErrors.next().getMessage()); + assertEquals("Coordinate system \"icrs\" (= \"ICRS UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", itErrors.next().getMessage()); + } + if (version == ADQLVersion.V2_0) { + try { + parser.parseQuery("SELECT Region('not(position fk4 1 2)') FROM foo;"); + fail("This query specifies coordinate systems while they are all forbidden: this test should have failed!"); + } catch(ParseException pe) { + assertTrue(pe instanceof UnresolvedIdentifiersException); + UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe; + assertEquals(1, ex.getNbErrors()); + assertEquals("Coordinate system \"FK4\" (= \"FK4 UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", ex.getErrors().next().getMessage()); + } + } } } diff --git a/test/adql/query/TestADQLObjectPosition.java b/test/adql/query/TestADQLObjectPosition.java index 133caf7cb603d3e460c9c3c2d5bcb2cdc7e38547..772e4072c83f6176ab1abf91fa453d53ad5d039b 100644 --- a/test/adql/query/TestADQLObjectPosition.java +++ b/test/adql/query/TestADQLObjectPosition.java @@ -10,6 +10,7 @@ import org.junit.Before; import org.junit.Test; import adql.parser.ADQLParser; +import adql.parser.ADQLParser.ADQLVersion; import adql.parser.grammar.ParseException; import adql.query.constraint.Comparison; import adql.query.from.ADQLJoin; @@ -28,24 +29,28 @@ public class TestADQLObjectPosition { @Test public void testPositionInAllClauses() { - try { - ADQLQuery query = new ADQLParser().parseQuery("SELECT truc, bidule.machin, toto(truc, chose) AS \"super\" FROM foo JOIN bidule USING(id) WHERE truc > 12.5 AND bidule.machin < 5 GROUP BY chose HAVING try > 0 ORDER BY chouetteAlors"); + for(ADQLVersion version : ADQLVersion.values()) { + try { + ADQLQuery query = new ADQLParser(version).parseQuery("SELECT truc, bidule.machin, toto(truc, chose) AS \"super\" FROM foo JOIN bidule USING(id) WHERE truc > 12.5 AND bidule.machin < 5 GROUP BY chose HAVING try > 0 ORDER BY chouetteAlors, 2 DESC"); - Iterator<ADQLObject> results = query.search(new SimpleSearchHandler(true) { - @Override - protected boolean match(ADQLObject obj) { - return obj.getPosition() == null; + Iterator<ADQLObject> results = query.search(new SimpleSearchHandler(true) { + @Override + protected boolean match(ADQLObject obj) { + return obj.getPosition() == null; + } + }); + if (results.hasNext()) { + System.err.println("OBJECT WITH NO DEFINED POSITION in ADQL-" + version + ":"); + while(results.hasNext()) { + ADQLObject r = results.next(); + System.err.println(" * " + r.toADQL() + " {" + r.getClass().getSimpleName() + "}"); + } + fail("At least one item of the generated ADQL tree does not have a position information! (see System.err for more details)"); } - }); - if (results.hasNext()) { - System.err.println("OBJECT WITH NO DEFINED POSITION:"); - while(results.hasNext()) - System.err.println(" * " + results.next().toADQL()); - fail("At least one item of the generated ADQL tree does not have a position information! (see System.err for more details)"); + } catch(ParseException pe) { + pe.printStackTrace(); + fail("No error should have occured here: the ADQL query is syntactically correct!"); } - } catch(ParseException pe) { - pe.printStackTrace(); - fail("No error should have occured here: the ADQL query is syntactically correct!"); } } @@ -58,80 +63,84 @@ public class TestADQLObjectPosition { @Test public void testPositionAccuracy() { - try { - ADQLQuery query = new ADQLParser().parseQuery("SELECT TOP 1000 oid FROM foo JOIN bar USING(oid)\nWHERE foo || toto = 'truc'\n AND 2 > 1+0 GROUP BY oid HAVING COUNT(oid) > 10\n\tORDER BY 1 DESC"); - // Test SELECT - assertEquality(new TextPosition(1, 1, 1, 20), query.getSelect().getPosition()); - // Test ADQLColumn (here: "oid") - assertEquality(new TextPosition(1, 17, 1, 20), query.getSelect().get(0).getPosition()); - // Test FROM & ADQLJoin - /* NB: The clause FROM is the only one which is not a list but a single item of type FromContent (JOIN or table). - * That's why, it is not possible to get its exact starting position ('FROM') ; the starting position is - * the one of the first table of the clause FROM. */ - assertEquality(new TextPosition(1, 26, 1, 49), query.getFrom().getPosition()); - // Test ADQLTable - List<ADQLTable> tables = query.getFrom().getTables(); - assertEquality(new TextPosition(1, 26, 1, 29), tables.get(0).getPosition()); - assertEquality(new TextPosition(1, 35, 1, 38), tables.get(1).getPosition()); - // Test the join condition: - Iterator<ADQLColumn> itCol = ((ADQLJoin)query.getFrom()).getJoinedColumns(); - assertEquality(new TextPosition(1, 45, 1, 48), itCol.next().getPosition()); - // Test WHERE - assertEquality(new TextPosition(2, 1, 3, 18), query.getWhere().getPosition()); - // Test COMPARISON = CONSTRAINT - Comparison comp = (Comparison)(query.getWhere().get(0)); - assertEquality(new TextPosition(2, 7, 2, 27), comp.getPosition()); - // Test left operand = concatenation: - ADQLOperand operand = comp.getLeftOperand(); - assertEquality(new TextPosition(2, 7, 2, 18), operand.getPosition()); - Iterator<ADQLObject> itObj = operand.adqlIterator(); - // foo - assertEquality(new TextPosition(2, 7, 2, 10), itObj.next().getPosition()); - // toto - assertEquality(new TextPosition(2, 14, 2, 18), itObj.next().getPosition()); - // Test right operand = string: - operand = comp.getRightOperand(); - assertEquality(new TextPosition(2, 21, 2, 27), operand.getPosition()); - // Test COMPARISON > CONSTRAINT: - comp = (Comparison)(query.getWhere().get(1)); - assertEquality(new TextPosition(3, 11, 3, 18), comp.getPosition()); - // Test left operand = numeric: - operand = comp.getLeftOperand(); - assertEquality(new TextPosition(3, 11, 3, 12), operand.getPosition()); - // Test right operand = operation: - operand = comp.getRightOperand(); - assertEquality(new TextPosition(3, 15, 3, 18), operand.getPosition()); - itObj = operand.adqlIterator(); - // 1 - assertEquality(new TextPosition(3, 15, 3, 16), itObj.next().getPosition()); - // 0 - assertEquality(new TextPosition(3, 17, 3, 18), itObj.next().getPosition()); - // Test GROUP BY - assertEquality(new TextPosition(3, 19, 3, 31), query.getGroupBy().getPosition()); - // oid - assertEquality(new TextPosition(3, 28, 3, 31), query.getGroupBy().get(0).getPosition()); - // Test HAVING - assertEquality(new TextPosition(3, 32, 3, 54), query.getHaving().getPosition()); - // Test COMPARISON > CONSTRAINT: - comp = (Comparison)(query.getHaving().get(0)); - assertEquality(new TextPosition(3, 39, 3, 54), comp.getPosition()); - // Test left operand = COUNT function: - operand = comp.getLeftOperand(); - assertEquality(new TextPosition(3, 39, 3, 49), operand.getPosition()); - // Test parameter = ADQLColumn oid: - assertEquality(new TextPosition(3, 45, 3, 48), ((ADQLFunction)operand).getParameter(0).getPosition()); - // Test right operand = operation: - operand = comp.getRightOperand(); - assertEquality(new TextPosition(3, 52, 3, 54), operand.getPosition()); - // Test ORDER BY - assertEquality(new TextPosition(4, 9, 4, 24), query.getOrderBy().getPosition()); - // Test column index: - assertEquality(new TextPosition(4, 18, 4, 19), query.getOrderBy().get(0).getPosition()); + for(ADQLVersion version : ADQLVersion.values()) { + try { + ADQLQuery query = new ADQLParser(version).parseQuery("SELECT TOP 1000 oid FROM foo JOIN bar USING(oid)\nWHERE foo || toto = 'truc'\n AND 2 > 1+0 GROUP BY oid HAVING COUNT(oid) > 10\n\tORDER BY 1 DESC"); + // Test SELECT + assertEquality(new TextPosition(1, 1, 1, 20), query.getSelect().getPosition()); + // Test ADQLColumn (here: "oid") + assertEquality(new TextPosition(1, 17, 1, 20), query.getSelect().get(0).getPosition()); + // Test FROM & ADQLJoin + /* NB: The clause FROM is the only one which is not a list but a single item of type FromContent (JOIN or table). + * That's why, it is not possible to get its exact starting position ('FROM') ; the starting position is + * the one of the first table of the clause FROM. */ + assertEquality(new TextPosition(1, 26, 1, 49), query.getFrom().getPosition()); + // Test ADQLTable + List<ADQLTable> tables = query.getFrom().getTables(); + assertEquality(new TextPosition(1, 26, 1, 29), tables.get(0).getPosition()); + assertEquality(new TextPosition(1, 35, 1, 38), tables.get(1).getPosition()); + // Test the join condition: + Iterator<ADQLColumn> itCol = ((ADQLJoin)query.getFrom()).getJoinedColumns(); + assertEquality(new TextPosition(1, 45, 1, 48), itCol.next().getPosition()); + // Test WHERE + assertEquality(new TextPosition(2, 1, 3, 18), query.getWhere().getPosition()); + // Test COMPARISON = CONSTRAINT + Comparison comp = (Comparison)(query.getWhere().get(0)); + assertEquality(new TextPosition(2, 7, 2, 27), comp.getPosition()); + // Test left operand = concatenation: + ADQLOperand operand = comp.getLeftOperand(); + assertEquality(new TextPosition(2, 7, 2, 18), operand.getPosition()); + Iterator<ADQLObject> itObj = operand.adqlIterator(); + // foo + assertEquality(new TextPosition(2, 7, 2, 10), itObj.next().getPosition()); + // toto + assertEquality(new TextPosition(2, 14, 2, 18), itObj.next().getPosition()); + // Test right operand = string: + operand = comp.getRightOperand(); + assertEquality(new TextPosition(2, 21, 2, 27), operand.getPosition()); + // Test COMPARISON > CONSTRAINT: + comp = (Comparison)(query.getWhere().get(1)); + assertEquality(new TextPosition(3, 11, 3, 18), comp.getPosition()); + // Test left operand = numeric: + operand = comp.getLeftOperand(); + assertEquality(new TextPosition(3, 11, 3, 12), operand.getPosition()); + // Test right operand = operation: + operand = comp.getRightOperand(); + assertEquality(new TextPosition(3, 15, 3, 18), operand.getPosition()); + itObj = operand.adqlIterator(); + // 1 + assertEquality(new TextPosition(3, 15, 3, 16), itObj.next().getPosition()); + // 0 + assertEquality(new TextPosition(3, 17, 3, 18), itObj.next().getPosition()); + // Test GROUP BY + assertEquality(new TextPosition(3, 19, 3, 31), query.getGroupBy().getPosition()); + // oid + assertEquality(new TextPosition(3, 28, 3, 31), query.getGroupBy().get(0).getPosition()); + // Test HAVING + assertEquality(new TextPosition(3, 32, 3, 54), query.getHaving().getPosition()); + // Test COMPARISON > CONSTRAINT: + comp = (Comparison)(query.getHaving().get(0)); + assertEquality(new TextPosition(3, 39, 3, 54), comp.getPosition()); + // Test left operand = COUNT function: + operand = comp.getLeftOperand(); + assertEquality(new TextPosition(3, 39, 3, 49), operand.getPosition()); + // Test parameter = ADQLColumn oid: + assertEquality(new TextPosition(3, 45, 3, 48), ((ADQLFunction)operand).getParameter(0).getPosition()); + // Test right operand = operation: + operand = comp.getRightOperand(); + assertEquality(new TextPosition(3, 52, 3, 54), operand.getPosition()); + // Test ORDER BY + assertEquality(new TextPosition(4, 9, 4, 24), query.getOrderBy().getPosition()); + // Test ORDER BY item: + assertEquality(new TextPosition(4, 18, 4, 24), query.getOrderBy().get(0).getPosition()); + // Test column index: + assertEquality(new TextPosition(4, 18, 4, 19), query.getOrderBy().get(0).getColumnReference().getPosition()); - } catch(ParseException pe) { - System.err.println("ERROR IN THE ADQL QUERY AT " + pe.getPosition()); - pe.printStackTrace(); - fail("No error should have occured here: the ADQL query is syntactically correct!"); + } catch(ParseException pe) { + System.err.println("ERROR IN THE ADQL-" + version + " QUERY AT " + pe.getPosition()); + pe.printStackTrace(); + fail("No error should have occured here: the ADQL query is syntactically correct!"); + } } }