diff --git a/src/adql/db/DBChecker.java b/src/adql/db/DBChecker.java index 8e6b8ae03c53b2b16b8fa766677f8eabb5cf17d9..2f476e51548f0ad76d5b2b7b983ab90979dbb399 100644 --- a/src/adql/db/DBChecker.java +++ b/src/adql/db/DBChecker.java @@ -42,6 +42,7 @@ import adql.parser.QueryChecker; import adql.query.ADQLIterator; import adql.query.ADQLObject; import adql.query.ADQLQuery; +import adql.query.ClauseADQL; import adql.query.ClauseSelect; import adql.query.ColumnReference; import adql.query.IdentifierField; @@ -615,7 +616,7 @@ public class DBChecker implements QueryChecker { ISearchHandler sHandler; // Check the existence of all columns: - sHandler = new SearchColumnHandler(); + sHandler = new SearchColumnOutsideGroupByHandler(); sHandler.search(query); for(ADQLObject result : sHandler){ try{ @@ -630,12 +631,30 @@ 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); + } + } + // 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); - ClauseSelect select = query.getSelect(); for(ADQLObject result : sHandler){ try{ ColumnReference colRef = (ColumnReference)result; @@ -695,6 +714,38 @@ public class DBChecker implements QueryChecker { } } + /** + * Check whether the given column corresponds to a selected item's alias or to an existing column. + * + * @param col The column to check. + * @param select The SELECT clause of the ADQL query. + * @param dbColumns The list of all available columns. + * + * @return The corresponding {@link DBColumn} if this column corresponds to an existing column, + * <i>NULL</i> otherwise. + * + * @throws ParseException An {@link UnresolvedColumnException} if the given column can't be resolved + * or an {@link UnresolvedTableException} if its table reference can't be resolved. + * + * @see ClauseSelect#searchByAlias(String) + * @see #resolveColumn(ADQLColumn, SearchColumnList, Stack) + * + * @since 1.4 + */ + protected DBColumn checkGroupByItem(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException{ + /* 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.*/ + if (col.getTableName() == null){ + ArrayList<SelectItem> founds = select.searchByAlias(col.getColumnName(), col.isCaseSensitive(IdentifierField.COLUMN)); + if (founds.size() == 1) + return null; + else if (founds.size() > 1) + throw new UnresolvedColumnException(col, founds.get(0).getAlias(), founds.get(1).getAlias()); + } + return resolveColumn(col, dbColumns, null); + } + /** * Check whether the given column reference corresponds to a selected item (column or an expression with an alias) * or to an existing column. @@ -1245,6 +1296,26 @@ public class DBChecker implements QueryChecker { /* *************** */ /* SEARCH HANDLERS */ /* *************** */ + + /** + * Lets searching all {@link ADQLColumn} in the given object, EXCEPT in the GROUP BY clause. + * + * <p> + * {@link ADQLColumn}s of the GROUP BY may be aliases and so, they can not be checked + * exactly as a normal column. + * </p> + * + * @author Grégory Mantelet (ARI) + * @version 1.4 (04/2017) + * @since 1.4 + */ + private static class SearchColumnOutsideGroupByHandler extends SearchColumnHandler { + @Override + protected boolean goInto(final ADQLObject obj){ + return !(obj instanceof ClauseADQL<?> && ((ClauseADQL<?>)obj).getName().equalsIgnoreCase("GROUP BY")) && super.goInto(obj); + } + } + /** * Lets searching all tables. * diff --git a/test/adql/db/TestDBChecker.java b/test/adql/db/TestDBChecker.java index aec904dec03b4a1cffa1bb5371f5756eda8862c5..c2b978a67f2b4e1cd33035e3f9216ba042cb5c8d 100644 --- a/test/adql/db/TestDBChecker.java +++ b/test/adql/db/TestDBChecker.java @@ -106,6 +106,8 @@ public class TestDBChecker { 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(); @@ -156,9 +158,15 @@ public class TestDBChecker { public void testColRefWithDottedAlias(){ ADQLParser parser = new ADQLParser(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();