Loading src/adql/parser/grammar/adqlGrammar201.jj +13 −0 Original line number Diff line number Diff line Loading @@ -301,6 +301,7 @@ TOKEN : { | < ORDER: "ORDER" > { matchedToken.adqlReserved = true; } | < ASC: "ASC" > { matchedToken.adqlReserved = true; } | < DESC: "DESC" > { matchedToken.adqlReserved = true; } | < OFFSET: "OFFSET" > { matchedToken.adqlReserved = true; } } /* ************* */ Loading Loading @@ -439,6 +440,7 @@ ADQLQuery QueryExpression(): {TextPosition endPos = null;} { [GroupBy() {endPos = query.getGroupBy().getPosition();}] [Having() {endPos = query.getHaving().getPosition();}] [OrderBy() {endPos = query.getOrderBy().getPosition();}] [Offset() {endPos = new TextPosition(token);}] { // set the position of the query: query.setPosition(new TextPosition(query.getSelect().getPosition(), endPos)); Loading Loading @@ -577,6 +579,17 @@ void OrderBy(): {ClauseADQL<ADQLOrder> orderBy = query.getOrderBy(); ADQLOrder o { orderBy.setPosition(new TextPosition(start, token)); } } void Offset(): { Token t; } { <OFFSET> t=<UNSIGNED_INTEGER> { try{ query.setOffset(Integer.parseInt(t.image)); }catch(NumberFormatException nfe){ throw new ParseException("The OFFSET limit (\""+t.image+"\") isn't a regular unsigned integer!", new TextPosition(t)); } } } /* *************************** */ /* COLUMN AND TABLE REFERENCES */ /* *************************** */ Loading src/adql/query/ADQLQuery.java +67 −1 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ import adql.search.ISearchHandler; * </p> * * @author Grégory Mantelet (CDS;ARI) * @version 2.0 (07/2019) * @version 2.0 (08/2019) */ public class ADQLQuery implements ADQLObject { Loading Loading @@ -82,6 +82,10 @@ public class ADQLQuery implements ADQLObject { /** The ADQL clause ORDER BY. */ private ClauseADQL<ADQLOrder> orderBy; /** The ADQL clause OFFSET. * @since 2.0 */ private int offset; /** Position of this Query (or sub-query) inside the whole given ADQL query * string. * @since 1.4 */ Loading Loading @@ -112,6 +116,7 @@ public class ADQLQuery implements ADQLObject { groupBy = new ClauseADQL<ADQLColumn>("GROUP BY"); having = new ClauseConstraints("HAVING"); orderBy = new ClauseADQL<ADQLOrder>("ORDER BY"); offset = -1; } /** Loading @@ -130,6 +135,7 @@ public class ADQLQuery implements ADQLObject { groupBy = (ClauseADQL<ADQLColumn>)toCopy.groupBy.getCopy(); having = (ClauseConstraints)toCopy.having.getCopy(); orderBy = (ClauseADQL<ADQLOrder>)toCopy.orderBy.getCopy(); offset = toCopy.offset; position = (toCopy.position == null) ? null : new TextPosition(toCopy.position); } Loading Loading @@ -160,6 +166,7 @@ public class ADQLQuery implements ADQLObject { groupBy.clear(); having.clear(); orderBy.clear(); offset = -1; position = null; } Loading Loading @@ -331,6 +338,62 @@ public class ADQLQuery implements ADQLObject { position = null; } /** * Gets the OFFSET value of this query. * * @return Its OFFSET value, * or a negative value if no OFFSET is set. * * @since 2.0 */ public final int getOffset() { return offset; } /** * Tell whether an OFFSET is set in this query. * * @return <code>true</code> if an OFFSET is set, * <code>false</code> otherwise. * * @since 2.0 */ public final boolean hasOffset() { return (offset > -1); } /** * Remove the OFFSET value of this query. * * <p><i><b>Note:</b> * The position of the query is erased. * </i></p>. * * @since 2.0 */ public void setNoOffset() { offset = -1; position = null; } /** * Replaces its OFFSET value by the given one. * * <p><i><b>Note:</b> * The position of the query is erased. * </i></p> * * @param newOffset The new OFFSET value. * <i><b>Note:</b> a negative value removes the OFFSET from * this query.</i> * * @since 2.0 */ public void setOffset(final int newOffset) { offset = newOffset; position = null; } @Override public final TextPosition getPosition() { return position; Loading Loading @@ -580,6 +643,9 @@ public class ADQLQuery implements ADQLObject { if (!orderBy.isEmpty()) adql.append('\n').append(orderBy.toADQL()); if (hasOffset()) adql.append("\nOFFSET ").append(offset); return adql.toString(); } Loading src/adql/translator/JDBCTranslator.java +4 −1 Original line number Diff line number Diff line Loading @@ -375,7 +375,10 @@ public abstract class JDBCTranslator implements ADQLTranslator { sql.append('\n').append(translate(query.getOrderBy())); if (query.getSelect().hasLimit()) sql.append("\nLimit ").append(query.getSelect().getLimit()); sql.append("\nLIMIT ").append(query.getSelect().getLimit()); if (query.hasOffset()) sql.append("\nOFFSET ").append(query.getOffset()); return sql.toString(); } Loading src/adql/translator/SQLServerTranslator.java +19 −6 Original line number Diff line number Diff line Loading @@ -134,10 +134,13 @@ public class SQLServerTranslator extends JDBCTranslator { /** * For SQL Server, {@link #translate(ClauseSelect)} must be overridden for * TOP/LIMIT handling. We must not add the LIMIT at the end of the query, it * must go in the SELECT. * LIMIT and OFFSET handling. * * @see #translate(ClauseSelect) * <p><i><b>Implementation note:</b> * LIMIT is replaced by FETCH NEXT instead of TOP because of the addition * of OFFSET support in ADQL-2.1 grammar. With MS-SQLServer, TOP can not be * used with OFFSET...it must be OFFSET...LIMIT.... * </i></p> */ @Override public String translate(ADQLQuery query) throws TranslationException { Loading @@ -157,6 +160,16 @@ public class SQLServerTranslator extends JDBCTranslator { if (!query.getOrderBy().isEmpty()) sql.append('\n').append(translate(query.getOrderBy())); if (query.getSelect().hasLimit()) { if (query.hasOffset()) sql.append('\n').append("OFFSET ").append(query.getOffset()).append(" ROWS"); else sql.append('\n').append("OFFSET 0 ROWS"); sql.append(" FETCH NEXT ").append(query.getSelect().getLimit()).append(" ROWS ONLY"); } else if (query.hasOffset()) { sql.append('\n').append("OFFSET ").append(query.getOffset()).append(" ROWS"); } return sql.toString(); } Loading @@ -165,9 +178,9 @@ public class SQLServerTranslator extends JDBCTranslator { String sql = null; for(int i = 0; i < clause.size(); i++) { if (i == 0) { sql = clause.getName() + (clause.distinctColumns() ? " DISTINCT" : "") + (clause.hasLimit() ? " TOP " + clause.getLimit() + " " : ""); } else if (i == 0) sql = clause.getName() + (clause.distinctColumns() ? " DISTINCT" : ""); else sql += " " + clause.getSeparator(i); sql += " " + translate(clause.get(i)); Loading test/adql/parser/TestADQLParser.java +39 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,45 @@ public class TestADQLParser { public void tearDown() throws Exception { } @Test public void testOffset() { // CASE: No OFFSET in ADQL-2.0 ADQLParser parser = new ADQLParser(ADQLVersion.V2_0); try { parser.parseQuery("SELECT * FROM foo ORDER BY id OFFSET 10"); fail("OFFSET should not be allowed with ADQL-2.0!"); } catch(Exception ex) { assertEquals(ParseException.class, ex.getClass()); assertEquals(" Encountered \"OFFSET\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", ex.getMessage()); } // CASE: OFFSET allowed in ADQL-2.1 parser = new ADQLParser(ADQLVersion.V2_1); try { assertEquals("SELECT *\nFROM foo\nOFFSET 10", parser.parseQuery("SELECT * FROM foo OFFSET 10").toADQL()); assertEquals("SELECT *\nFROM foo\nORDER BY id ASC\nOFFSET 10", parser.parseQuery("SELECT * FROM foo ORDER BY id OFFSET 10").toADQL()); assertEquals("SELECT *\nFROM foo\nORDER BY id ASC\nOFFSET 0", parser.parseQuery("SELECT * FROM foo ORDER BY id OFFSET 0").toADQL()); assertEquals("SELECT TOP 5 *\nFROM foo\nORDER BY id ASC\nOFFSET 10", parser.parseQuery("SELECT TOP 5 * FROM foo ORDER BY id OFFSET 10").toADQL()); } catch(Exception ex) { ex.printStackTrace(); fail("Unexpected error with a valid OFFSET syntax! (see console for more details)"); } // CASE: Only an unsigned integer constant is allowed String[] offsets = new String[]{ "-1", "colOffset", "2*5" }; String[] expectedErrors = new String[]{ " Encountered \"-\". Was expecting: <UNSIGNED_INTEGER> ", " Encountered \"colOffset\". Was expecting: <UNSIGNED_INTEGER> ", " Encountered \"*\". Was expecting one of: <EOF> \";\" " }; for(int i = 0; i < offsets.length; i++) { try { parser.parseQuery("SELECT * FROM foo OFFSET " + offsets[i]); fail("Incorrect offset expression (\"" + offsets[i] + "\"). This test should have failed."); } catch(Exception ex) { assertEquals(ParseException.class, ex.getClass()); assertEquals(expectedErrors[i], ex.getMessage()); } } } @Test public void testColumnReference() { ADQLParser parser = new ADQLParser(); Loading Loading
src/adql/parser/grammar/adqlGrammar201.jj +13 −0 Original line number Diff line number Diff line Loading @@ -301,6 +301,7 @@ TOKEN : { | < ORDER: "ORDER" > { matchedToken.adqlReserved = true; } | < ASC: "ASC" > { matchedToken.adqlReserved = true; } | < DESC: "DESC" > { matchedToken.adqlReserved = true; } | < OFFSET: "OFFSET" > { matchedToken.adqlReserved = true; } } /* ************* */ Loading Loading @@ -439,6 +440,7 @@ ADQLQuery QueryExpression(): {TextPosition endPos = null;} { [GroupBy() {endPos = query.getGroupBy().getPosition();}] [Having() {endPos = query.getHaving().getPosition();}] [OrderBy() {endPos = query.getOrderBy().getPosition();}] [Offset() {endPos = new TextPosition(token);}] { // set the position of the query: query.setPosition(new TextPosition(query.getSelect().getPosition(), endPos)); Loading Loading @@ -577,6 +579,17 @@ void OrderBy(): {ClauseADQL<ADQLOrder> orderBy = query.getOrderBy(); ADQLOrder o { orderBy.setPosition(new TextPosition(start, token)); } } void Offset(): { Token t; } { <OFFSET> t=<UNSIGNED_INTEGER> { try{ query.setOffset(Integer.parseInt(t.image)); }catch(NumberFormatException nfe){ throw new ParseException("The OFFSET limit (\""+t.image+"\") isn't a regular unsigned integer!", new TextPosition(t)); } } } /* *************************** */ /* COLUMN AND TABLE REFERENCES */ /* *************************** */ Loading
src/adql/query/ADQLQuery.java +67 −1 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ import adql.search.ISearchHandler; * </p> * * @author Grégory Mantelet (CDS;ARI) * @version 2.0 (07/2019) * @version 2.0 (08/2019) */ public class ADQLQuery implements ADQLObject { Loading Loading @@ -82,6 +82,10 @@ public class ADQLQuery implements ADQLObject { /** The ADQL clause ORDER BY. */ private ClauseADQL<ADQLOrder> orderBy; /** The ADQL clause OFFSET. * @since 2.0 */ private int offset; /** Position of this Query (or sub-query) inside the whole given ADQL query * string. * @since 1.4 */ Loading Loading @@ -112,6 +116,7 @@ public class ADQLQuery implements ADQLObject { groupBy = new ClauseADQL<ADQLColumn>("GROUP BY"); having = new ClauseConstraints("HAVING"); orderBy = new ClauseADQL<ADQLOrder>("ORDER BY"); offset = -1; } /** Loading @@ -130,6 +135,7 @@ public class ADQLQuery implements ADQLObject { groupBy = (ClauseADQL<ADQLColumn>)toCopy.groupBy.getCopy(); having = (ClauseConstraints)toCopy.having.getCopy(); orderBy = (ClauseADQL<ADQLOrder>)toCopy.orderBy.getCopy(); offset = toCopy.offset; position = (toCopy.position == null) ? null : new TextPosition(toCopy.position); } Loading Loading @@ -160,6 +166,7 @@ public class ADQLQuery implements ADQLObject { groupBy.clear(); having.clear(); orderBy.clear(); offset = -1; position = null; } Loading Loading @@ -331,6 +338,62 @@ public class ADQLQuery implements ADQLObject { position = null; } /** * Gets the OFFSET value of this query. * * @return Its OFFSET value, * or a negative value if no OFFSET is set. * * @since 2.0 */ public final int getOffset() { return offset; } /** * Tell whether an OFFSET is set in this query. * * @return <code>true</code> if an OFFSET is set, * <code>false</code> otherwise. * * @since 2.0 */ public final boolean hasOffset() { return (offset > -1); } /** * Remove the OFFSET value of this query. * * <p><i><b>Note:</b> * The position of the query is erased. * </i></p>. * * @since 2.0 */ public void setNoOffset() { offset = -1; position = null; } /** * Replaces its OFFSET value by the given one. * * <p><i><b>Note:</b> * The position of the query is erased. * </i></p> * * @param newOffset The new OFFSET value. * <i><b>Note:</b> a negative value removes the OFFSET from * this query.</i> * * @since 2.0 */ public void setOffset(final int newOffset) { offset = newOffset; position = null; } @Override public final TextPosition getPosition() { return position; Loading Loading @@ -580,6 +643,9 @@ public class ADQLQuery implements ADQLObject { if (!orderBy.isEmpty()) adql.append('\n').append(orderBy.toADQL()); if (hasOffset()) adql.append("\nOFFSET ").append(offset); return adql.toString(); } Loading
src/adql/translator/JDBCTranslator.java +4 −1 Original line number Diff line number Diff line Loading @@ -375,7 +375,10 @@ public abstract class JDBCTranslator implements ADQLTranslator { sql.append('\n').append(translate(query.getOrderBy())); if (query.getSelect().hasLimit()) sql.append("\nLimit ").append(query.getSelect().getLimit()); sql.append("\nLIMIT ").append(query.getSelect().getLimit()); if (query.hasOffset()) sql.append("\nOFFSET ").append(query.getOffset()); return sql.toString(); } Loading
src/adql/translator/SQLServerTranslator.java +19 −6 Original line number Diff line number Diff line Loading @@ -134,10 +134,13 @@ public class SQLServerTranslator extends JDBCTranslator { /** * For SQL Server, {@link #translate(ClauseSelect)} must be overridden for * TOP/LIMIT handling. We must not add the LIMIT at the end of the query, it * must go in the SELECT. * LIMIT and OFFSET handling. * * @see #translate(ClauseSelect) * <p><i><b>Implementation note:</b> * LIMIT is replaced by FETCH NEXT instead of TOP because of the addition * of OFFSET support in ADQL-2.1 grammar. With MS-SQLServer, TOP can not be * used with OFFSET...it must be OFFSET...LIMIT.... * </i></p> */ @Override public String translate(ADQLQuery query) throws TranslationException { Loading @@ -157,6 +160,16 @@ public class SQLServerTranslator extends JDBCTranslator { if (!query.getOrderBy().isEmpty()) sql.append('\n').append(translate(query.getOrderBy())); if (query.getSelect().hasLimit()) { if (query.hasOffset()) sql.append('\n').append("OFFSET ").append(query.getOffset()).append(" ROWS"); else sql.append('\n').append("OFFSET 0 ROWS"); sql.append(" FETCH NEXT ").append(query.getSelect().getLimit()).append(" ROWS ONLY"); } else if (query.hasOffset()) { sql.append('\n').append("OFFSET ").append(query.getOffset()).append(" ROWS"); } return sql.toString(); } Loading @@ -165,9 +178,9 @@ public class SQLServerTranslator extends JDBCTranslator { String sql = null; for(int i = 0; i < clause.size(); i++) { if (i == 0) { sql = clause.getName() + (clause.distinctColumns() ? " DISTINCT" : "") + (clause.hasLimit() ? " TOP " + clause.getLimit() + " " : ""); } else if (i == 0) sql = clause.getName() + (clause.distinctColumns() ? " DISTINCT" : ""); else sql += " " + clause.getSeparator(i); sql += " " + translate(clause.get(i)); Loading
test/adql/parser/TestADQLParser.java +39 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,45 @@ public class TestADQLParser { public void tearDown() throws Exception { } @Test public void testOffset() { // CASE: No OFFSET in ADQL-2.0 ADQLParser parser = new ADQLParser(ADQLVersion.V2_0); try { parser.parseQuery("SELECT * FROM foo ORDER BY id OFFSET 10"); fail("OFFSET should not be allowed with ADQL-2.0!"); } catch(Exception ex) { assertEquals(ParseException.class, ex.getClass()); assertEquals(" Encountered \"OFFSET\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", ex.getMessage()); } // CASE: OFFSET allowed in ADQL-2.1 parser = new ADQLParser(ADQLVersion.V2_1); try { assertEquals("SELECT *\nFROM foo\nOFFSET 10", parser.parseQuery("SELECT * FROM foo OFFSET 10").toADQL()); assertEquals("SELECT *\nFROM foo\nORDER BY id ASC\nOFFSET 10", parser.parseQuery("SELECT * FROM foo ORDER BY id OFFSET 10").toADQL()); assertEquals("SELECT *\nFROM foo\nORDER BY id ASC\nOFFSET 0", parser.parseQuery("SELECT * FROM foo ORDER BY id OFFSET 0").toADQL()); assertEquals("SELECT TOP 5 *\nFROM foo\nORDER BY id ASC\nOFFSET 10", parser.parseQuery("SELECT TOP 5 * FROM foo ORDER BY id OFFSET 10").toADQL()); } catch(Exception ex) { ex.printStackTrace(); fail("Unexpected error with a valid OFFSET syntax! (see console for more details)"); } // CASE: Only an unsigned integer constant is allowed String[] offsets = new String[]{ "-1", "colOffset", "2*5" }; String[] expectedErrors = new String[]{ " Encountered \"-\". Was expecting: <UNSIGNED_INTEGER> ", " Encountered \"colOffset\". Was expecting: <UNSIGNED_INTEGER> ", " Encountered \"*\". Was expecting one of: <EOF> \";\" " }; for(int i = 0; i < offsets.length; i++) { try { parser.parseQuery("SELECT * FROM foo OFFSET " + offsets[i]); fail("Incorrect offset expression (\"" + offsets[i] + "\"). This test should have failed."); } catch(Exception ex) { assertEquals(ParseException.class, ex.getClass()); assertEquals(expectedErrors[i], ex.getMessage()); } } } @Test public void testColumnReference() { ADQLParser parser = new ADQLParser(); Loading