Commit 993ee846 authored by gmantele's avatar gmantele
Browse files

[ADQL] Allow multiple space characters between ORDER/GROUP and BY keywords.

parent caa7f8be
Loading
Loading
Loading
Loading
+659 −608

File changed.

Preview size limit exceeded, changes collapsed.

+62 −59
Original line number Diff line number Diff line
@@ -91,113 +91,115 @@ public interface ADQLParserConstants {
  /** RegularExpression Id. */
  int EXISTS = 41;
  /** RegularExpression Id. */
  int GROUP_BY = 42;
  int BY = 42;
  /** RegularExpression Id. */
  int HAVING = 43;
  int GROUP = 43;
  /** RegularExpression Id. */
  int ORDER_BY = 44;
  int HAVING = 44;
  /** RegularExpression Id. */
  int ASC = 45;
  int ORDER = 45;
  /** RegularExpression Id. */
  int DESC = 46;
  int ASC = 46;
  /** RegularExpression Id. */
  int AVG = 47;
  int DESC = 47;
  /** RegularExpression Id. */
  int MAX = 48;
  int AVG = 48;
  /** RegularExpression Id. */
  int MIN = 49;
  int MAX = 49;
  /** RegularExpression Id. */
  int SUM = 50;
  int MIN = 50;
  /** RegularExpression Id. */
  int COUNT = 51;
  int SUM = 51;
  /** RegularExpression Id. */
  int BOX = 52;
  int COUNT = 52;
  /** RegularExpression Id. */
  int CENTROID = 53;
  int BOX = 53;
  /** RegularExpression Id. */
  int CIRCLE = 54;
  int CENTROID = 54;
  /** RegularExpression Id. */
  int POINT = 55;
  int CIRCLE = 55;
  /** RegularExpression Id. */
  int POLYGON = 56;
  int POINT = 56;
  /** RegularExpression Id. */
  int REGION = 57;
  int POLYGON = 57;
  /** RegularExpression Id. */
  int CONTAINS = 58;
  int REGION = 58;
  /** RegularExpression Id. */
  int INTERSECTS = 59;
  int CONTAINS = 59;
  /** RegularExpression Id. */
  int AREA = 60;
  int INTERSECTS = 60;
  /** RegularExpression Id. */
  int COORD1 = 61;
  int AREA = 61;
  /** RegularExpression Id. */
  int COORD2 = 62;
  int COORD1 = 62;
  /** RegularExpression Id. */
  int COORDSYS = 63;
  int COORD2 = 63;
  /** RegularExpression Id. */
  int DISTANCE = 64;
  int COORDSYS = 64;
  /** RegularExpression Id. */
  int ABS = 65;
  int DISTANCE = 65;
  /** RegularExpression Id. */
  int CEILING = 66;
  int ABS = 66;
  /** RegularExpression Id. */
  int DEGREES = 67;
  int CEILING = 67;
  /** RegularExpression Id. */
  int EXP = 68;
  int DEGREES = 68;
  /** RegularExpression Id. */
  int FLOOR = 69;
  int EXP = 69;
  /** RegularExpression Id. */
  int LOG = 70;
  int FLOOR = 70;
  /** RegularExpression Id. */
  int LOG10 = 71;
  int LOG = 71;
  /** RegularExpression Id. */
  int MOD = 72;
  int LOG10 = 72;
  /** RegularExpression Id. */
  int PI = 73;
  int MOD = 73;
  /** RegularExpression Id. */
  int POWER = 74;
  int PI = 74;
  /** RegularExpression Id. */
  int RADIANS = 75;
  int POWER = 75;
  /** RegularExpression Id. */
  int RAND = 76;
  int RADIANS = 76;
  /** RegularExpression Id. */
  int ROUND = 77;
  int RAND = 77;
  /** RegularExpression Id. */
  int SQRT = 78;
  int ROUND = 78;
  /** RegularExpression Id. */
  int TRUNCATE = 79;
  int SQRT = 79;
  /** RegularExpression Id. */
  int ACOS = 80;
  int TRUNCATE = 80;
  /** RegularExpression Id. */
  int ASIN = 81;
  int ACOS = 81;
  /** RegularExpression Id. */
  int ATAN = 82;
  int ASIN = 82;
  /** RegularExpression Id. */
  int ATAN2 = 83;
  int ATAN = 83;
  /** RegularExpression Id. */
  int COS = 84;
  int ATAN2 = 84;
  /** RegularExpression Id. */
  int COT = 85;
  int COS = 85;
  /** RegularExpression Id. */
  int SIN = 86;
  int COT = 86;
  /** RegularExpression Id. */
  int TAN = 87;
  int SIN = 87;
  /** RegularExpression Id. */
  int STRING_LITERAL = 91;
  int TAN = 88;
  /** RegularExpression Id. */
  int DELIMITED_IDENTIFIER = 94;
  int STRING_LITERAL = 92;
  /** RegularExpression Id. */
  int REGULAR_IDENTIFIER = 95;
  int DELIMITED_IDENTIFIER = 95;
  /** RegularExpression Id. */
  int Letter = 96;
  int REGULAR_IDENTIFIER = 96;
  /** RegularExpression Id. */
  int SCIENTIFIC_NUMBER = 97;
  int Letter = 97;
  /** RegularExpression Id. */
  int UNSIGNED_FLOAT = 98;
  int SCIENTIFIC_NUMBER = 98;
  /** RegularExpression Id. */
  int UNSIGNED_INTEGER = 99;
  int UNSIGNED_FLOAT = 99;
  /** RegularExpression Id. */
  int DIGIT = 100;
  int UNSIGNED_INTEGER = 100;
  /** RegularExpression Id. */
  int DIGIT = 101;

  /** Lexical state. */
  int DEFAULT = 0;
@@ -250,9 +252,10 @@ public interface ADQLParserConstants {
    "\"LIKE\"",
    "\"IN\"",
    "\"EXISTS\"",
    "\"GROUP BY\"",
    "\"BY\"",
    "\"GROUP\"",
    "\"HAVING\"",
    "\"ORDER BY\"",
    "\"ORDER\"",
    "\"ASC\"",
    "\"DESC\"",
    "\"AVG\"",
@@ -296,12 +299,12 @@ public interface ADQLParserConstants {
    "\"COT\"",
    "\"SIN\"",
    "\"TAN\"",
    "<token of kind 88>",
    "<token of kind 89>",
    "\"\\\'\"",
    "<token of kind 90>",
    "<token of kind 91>",
    "\"\\\'\"",
    "\"\\\"\"",
    "<token of kind 93>",
    "<token of kind 94>",
    "\"\\\"\"",
    "<REGULAR_IDENTIFIER>",
    "<Letter>",
+355 −385

File changed.

Preview size limit exceeded, changes collapsed.

+117 −62
Original line number Diff line number Diff line
@@ -19,11 +19,16 @@
 */

/*
*  This JavaCC file implements the BNF definition of ADQL v2.0 (IVOA Recommendation 30 Oct 2008 - http://www.ivoa.net/Documents/cover/ADQL-20081030.html).
*  To generate the parser with this file use JavaCC. This .jj file has been successfully tested with JavaCC 6.0.
* This JavaCC file implements the BNF definition of ADQL v2.0
* (IVOA Recommendation 30 Oct 2008 - http://www.ivoa.net/Documents/cover/ADQL-20081030.html).
* 
*  The generated parser checks the syntax of the given ADQL query and generates an object representation but no coherence with any database is done.
*  If the syntax is not conform to the ADQL definition an error message is printed else it will be the message "Correct syntax".
* To generate the parser with this file use JavaCC. This .jj file has been
* successfully tested with JavaCC 6.0.
* 
* The generated parser checks the syntax of the given ADQL query and generates
* an object representation but no coherence with any database is done.
* If the syntax is not conform to the ADQL definition an error message is
* printed else it will be the message "Correct syntax".
*
*  Author:  Gr&eacute;gory Mantelet (CDS;ARI) - gmantele@ari.uni-heidelberg.de
*  Version: 1.4 (09/2017)
@@ -74,16 +79,31 @@ import adql.translator.PostgreSQLTranslator;
import adql.translator.TranslationException;

/**
* <p>Parses an ADQL query thanks to the {@link ADQLParser#Query()} function. </p>
* Parses an ADQL query thanks to the {@link ADQLParser#Query()} function.
* 
* <p>This parser is able, thanks to a {@link QueryChecker} object, to check each ADQLQuery just after its generation.
* It could be used to check the consistency between the ADQL query to parse and the "database" on which the query must be executed.
* By default, there is no {@link QueryChecker}. Thus you must extend {@link QueryChecker} to check semantically all generated ADQLQuery objects.</p>
* <p>
*   This parser is able, thanks to a {@link QueryChecker} object, to check each
*   {@link ADQLQuery} just after its generation. It could be used to check the
*   consistency between the ADQL query to parse and the "database" on which the
*   query must be executed. By default, there is no {@link QueryChecker}. Thus
*   you must extend {@link QueryChecker} to check semantically all generated
*   ADQLQuery objects.
* </p>
* 
* <p>To create an object representation of the given ADQL query, this parser uses a {@link ADQLQueryFactory} object. So if you want customize some object (ie. CONTAINS) of this representation
* you just have to extend the corresponding default object (ie. ContainsFunction) and to extend the corresponding function of {@link ADQLQueryFactory} (ie. createContains(...)).</p>
* <p>
*   To create an object representation of the given ADQL query, this parser uses
*   a {@link ADQLQueryFactory} object. So if you want customize some object
*   (ie. CONTAINS) of this representation you just have to extend the
*   corresponding default object (ie. ContainsFunction) and to extend the
*   corresponding function of {@link ADQLQueryFactory}
*   (ie. createContains(...)).
* </p>
* 
* <p><b><u>WARNING:</u> To modify this class it's strongly encouraged to modify the .jj file in the section between <i>PARSER_BEGIN</i> and <i>PARSER_END</i> and to re-compile it with JavaCC.</b></p>
* <p><b><u>WARNING:</u>
*   To modify this class it's strongly encouraged to modify the .jj file in the
*   section between <i>PARSER_BEGIN</i> and <i>PARSER_END</i> and to re-compile
*   it with JavaCC.
* </b></p>
*
* @see QueryChecker
* @see ADQLQueryFactory
@@ -99,13 +119,16 @@ public class ADQLParser {
	/** The stack of queries (because there may be some sub-queries). */
	private Stack<ADQLQuery> stackQuery = new Stack<ADQLQuery>();
	
	/** The object representation of the ADQL query to parse. (ONLY USED DURING THE PARSING, else it is always <i>null</i>). */
	/** The object representation of the ADQL query to parse.
	 * (ONLY USED DURING THE PARSING, else it is always <i>null</i>). */
	private ADQLQuery query = null;
	
	/** Checks each ADQLQuery (sub-query or not) just after their generation. */
	/** Checks each {@link ADQLQuery} (sub-query or not) just after their
	 * generation. */
	private QueryChecker queryChecker = null;
	
	/** The first token of a table/column name. This token is extracted by {@link #Identifier()}. */
	/** The first token of a table/column name. This token is extracted by
	 * {@link #Identifier()}. */
	private Token currentIdentifierToken = null;
	
	/**
@@ -117,10 +140,12 @@ public class ADQLParser {
	}
	
	/**
	* Builds an ADQL parser without a query to parse but with a QueryChecker and a ADQLQueryFactory.
	* Builds an ADQL parser without a query to parse but with a
	* {@link QueryChecker} and a {@link ADQLQueryFactory}.
	*
	* @param checker	The object to use to check each ADQLQuery.
	* @param factory	The object to use to build an object representation of the given ADQL query.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(QueryChecker checker, ADQLQueryFactory factory) {
		this();
@@ -132,18 +157,21 @@ public class ADQLParser {
	}
	
	/**
	* Builds an ADQL parser without a query to parse but with a QueryChecker.
	* Builds an ADQL parser without a query to parse but with a
	* {@link QueryChecker}.
	*
	* @param checker	The object to use to check each ADQLQuery.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(QueryChecker checker) {
		this(checker, null);
	}
	
	/**
	* Builds an ADQL parser without a query to parse but with a ADQLQueryFactory.
	* Builds an ADQL parser without a query to parse but with a
	* {@link ADQLQueryFactory}.
	*
	* @param factory	The object to use to build an object representation of the given ADQL query.
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(ADQLQueryFactory factory) {
		this((QueryChecker)null, factory);
@@ -153,8 +181,9 @@ public class ADQLParser {
	* Builds a parser with a stream containing the query to parse.
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param checker	The object to use to check each ADQLQuery.
	* @param factory	The object to use to build an object representation of the given ADQL query.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(java.io.InputStream stream, QueryChecker checker, ADQLQueryFactory factory) {
		this(stream);
@@ -172,7 +201,7 @@ public class ADQLParser {
	* Builds a parser with a stream containing the query to parse.
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param checker	The object to use to check each ADQLQuery.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(java.io.InputStream stream, QueryChecker checker) {
		this(stream, checker, null);
@@ -182,7 +211,8 @@ public class ADQLParser {
	* Builds a parser with a stream containing the query to parse.
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param factory	The object to use to build an object representation of the given ADQL query.
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(java.io.InputStream stream, ADQLQueryFactory factory) {
		this(stream, (QueryChecker)null, factory);
@@ -193,8 +223,9 @@ public class ADQLParser {
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param encoding	The supplied encoding.
	* @param checker		The object to use to check each ADQLQuery.
	* @param factory		The object to use to build an object representation of the given ADQL query.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(java.io.InputStream stream, String encoding, QueryChecker checker, ADQLQueryFactory factory) {
		this(stream, encoding);
@@ -211,7 +242,7 @@ public class ADQLParser {
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param encoding	The supplied encoding.
	* @param checker		The object to use to check each ADQLQuery.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(java.io.InputStream stream, String encoding, QueryChecker checker) {
		this(stream, encoding, checker, null);
@@ -222,7 +253,8 @@ public class ADQLParser {
	*
	* @param stream		The stream in which the ADQL query to parse is given.
	* @param encoding	The supplied encoding.
	* @param factory		The object to use to build an object representation of the given ADQL query.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(java.io.InputStream stream, String encoding, ADQLQueryFactory factory) {
		this(stream, encoding, null, factory);
@@ -232,8 +264,9 @@ public class ADQLParser {
	* Builds a parser with a reader containing the query to parse.
	*
	* @param reader		The reader in which the ADQL query to parse is given.
	* @param checker		The object to use to check each ADQLQuery.
	* @param factory		The object to use to build an object representation of the given ADQL query.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(java.io.Reader reader, QueryChecker checker, ADQLQueryFactory factory) {
		this(reader);
@@ -251,7 +284,7 @@ public class ADQLParser {
	* Builds a parser with a reader containing the query to parse.
	*
	* @param reader		The reader in which the ADQL query to parse is given.
	* @param checker		The object to use to check each ADQLQuery.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(java.io.Reader reader, QueryChecker checker) {
		this(reader, checker, null);
@@ -261,7 +294,8 @@ public class ADQLParser {
	* Builds a parser with a reader containing the query to parse.
	*
	* @param reader		The reader in which the ADQL query to parse is given.
	* @param factory		The object to use to build an object representation of the given ADQL query.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(java.io.Reader reader, ADQLQueryFactory factory) {
		this(reader, null, factory);
@@ -271,8 +305,9 @@ public class ADQLParser {
	* Builds a parser with another token manager.
	*
	* @param tm			The manager which associates a token to a numeric code.
	* @param checker		The object to use to check each ADQLQuery.
	* @param factory		The object to use to build an object representation of the given ADQL query.
	* @param checker	The object to use to check each {@link ADQLQuery }.
	* @param factory	The object to use to build an object representation
	*               	of the given ADQL query.
	*/
	public ADQLParser(ADQLParserTokenManager tm, QueryChecker checker, ADQLQueryFactory factory) {
		this(tm);
@@ -290,7 +325,7 @@ public class ADQLParser {
	* Builds a parser with another token manager.
	*
	* @param tm			The manager which associates a token to a numeric code.
	* @param checker		The object to use to check each ADQLQuery.
	* @param checker	The object to use to check each {@link ADQLQuery}.
	*/
	public ADQLParser(ADQLParserTokenManager tm, QueryChecker checker) {
		this(tm, checker, null);
@@ -300,16 +335,19 @@ public class ADQLParser {
	* Builds a parser with another token manager.
	*
	* @param tm			The manager which associates a token to a numeric code.
	* @param factory		The object to use to build an object representation of the given ADQL query.
	* @param factory	The object to use to build an object representation of
	*               	the given ADQL query.
	*/
	public ADQLParser(ADQLParserTokenManager tm, ADQLQueryFactory factory) {
		this(tm, null, factory);
	}
	
	/**
	* Parses the query given at the creation of this parser or in the <i>ReInit</i> functions.
	* Parses the query given at the creation of this parser or in the
	* <i>ReInit</i> functions.
	*
	* @return 	The object representation of the given ADQL query.
	* 
	* @throws ParseException	If there is at least one syntactic error.
	*
	* @see ADQLParser#Query()
@@ -328,7 +366,9 @@ public class ADQLParser {
	* Parses the query given in parameter.
	*
	* @param q	The ADQL query to parse.
	* 
	* @return	The object representation of the given ADQL query.
	* 
	* @throws ParseException	If there is at least one syntactic error.
	*
	* @see ADQLParser#ReInit(java.io.InputStream)
@@ -350,7 +390,9 @@ public class ADQLParser {
	* Parses the query contained in the stream given in parameter.
	*
	* @param stream		The stream which contains the ADQL query to parse.
	* 
	* @return	The object representation of the given ADQL query.
	* 
	* @throws ParseException	If there is at least one syntactic error.
	*
	* @see ADQLParser#ReInit(java.io.InputStream)
@@ -399,11 +441,23 @@ public class ADQLParser {
	}

	/**
	* <p>Gets the specified ADQL query and parses the given ADQL query. The SQL translation is then printed if the syntax is correct.</p>
	* <p><b>ONLY the syntax is checked: the query is NOT EXECUTED !</b></p>
	* <p>Supplied parameters are: <ul><li>[-debug] -url http://...</li><li>[-debug] -file ...</li><li>[-debug] -query SELECT...</li></ul></p>
	* Gets the specified ADQL query and parses the given ADQL query. The SQL
	* translation is then printed if the syntax is correct.
	* 
	* <p>
	*     <b>ONLY the syntax is checked: the query is NOT EXECUTED !</b>
	* </p>
	* 
	* <p>Supplied parameters are:
	*     <ul>
	*       <li>[-debug] -url http://...</li>
	*       <li>[-debug] -file ...</li>
	*       <li>[-debug] -query SELECT...</li>
	*     </ul>
	* </p>
	*
	* @param args
	
	* @throws Exception
	*/
	public static final void main(String[] args) throws Exception {
@@ -589,9 +643,10 @@ TOKEN : {
/* Other clauses' tokens */
/* ********************* */
TOKEN : {
	< GROUP_BY: "GROUP BY" >
	< BY: "BY" >
|	< GROUP: "GROUP" >
|	< HAVING: "HAVING" >
|	< ORDER_BY: "ORDER BY" >
|	< ORDER: "ORDER" >
|	< ASC: "ASC" >
|	< DESC: "DESC" >
}
@@ -854,7 +909,7 @@ 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); }
	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)); }
}
@@ -868,7 +923,7 @@ void Having(): {ClauseConstraints having = query.getHaving(); Token start;} {
}

void OrderBy(): {ClauseADQL<ADQLOrder> orderBy = query.getOrderBy(); ADQLOrder order = null; Token start;} {
	start=<ORDER_BY> order=OrderItem() {orderBy.add(order);}
	start=<ORDER> <BY> order=OrderItem() {orderBy.add(order);}
	( <COMMA> order=OrderItem() {orderBy.add(order);} )*
	{ orderBy.setPosition(new TextPosition(start, token)); }
}
+27 −0
Original line number Diff line number Diff line
@@ -179,4 +179,31 @@ public class TestADQLParser {
		}
	}

	@Test
	public void testMultipleSpacesInOrderAndGroupBy(){
		try{
			ADQLParser parser = new ADQLParser();

			// 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");

			// 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.");
		}
	}

}