Commit 8a2ce121 authored by Grégory Mantelet's avatar Grégory Mantelet
Browse files

[ADQL] Support OFFSET.

parent 9f845f0b
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -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; }
}

/* ************* */
@@ -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));
@@ -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 */
/* *************************** */
+67 −1
Original line number Diff line number Diff line
@@ -52,7 +52,7 @@ import adql.search.ISearchHandler;
 * </p>
 *
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 2.0 (07/2019)
 * @version 2.0 (08/2019)
 */
public class ADQLQuery implements ADQLObject {

@@ -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 */
@@ -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;
	}

	/**
@@ -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);
	}

@@ -160,6 +166,7 @@ public class ADQLQuery implements ADQLObject {
		groupBy.clear();
		having.clear();
		orderBy.clear();
		offset = -1;
		position = null;
	}

@@ -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;
@@ -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();
	}

+4 −1
Original line number Diff line number Diff line
@@ -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();
	}
+19 −6
Original line number Diff line number Diff line
@@ -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 {
@@ -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();
	}

@@ -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));
+39 −0
Original line number Diff line number Diff line
@@ -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