* 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.
*
*
*
* 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(...)).
*
*
*
Here are the key functions to use:
*
*
{@link #parseQuery(java.lang.String)} (or any of its alternatives)
* to parse an input ADQL query String and get its corresponding ADQL tree
*
*
{@link #tryQuickFix(java.lang.String)} to try fixing the most common
* issues with ADQL queries (e.g. Unicode confusable characters,
* unescaped ADQL identifiers, SQL reserved keywords, ...)
*
*
*
WARNING:
* To modify this class it's strongly encouraged to modify the .jj file in the
* section between PARSER_BEGIN and PARSER_END and to re-compile
* it with JavaCC.
*
*
* @see QueryChecker
* @see ADQLQueryFactory
*
* @author Grégory Mantelet (CDS;ARI)
* @version 1.5-2 (10/2020)
*/
public class ADQLParser {
/** Tools to build the object representation of the ADQL query. */
private ADQLQueryFactory queryFactory = new ADQLQueryFactory();
/** The stack of queries (because there may be some sub-queries). */
private Stack stackQuery = new Stack();
/** The object representation of the ADQL query to parse.
* (ONLY USED DURING THE PARSING, else it is always null). */
private ADQLQuery query = null;
/** 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()}. */
private Token currentIdentifierToken = null;
/**
* Builds an ADQL parser without a query to parse.
*/
public ADQLParser(){
this(new java.io.ByteArrayInputStream("".getBytes()));
setDebug(false);
}
/**
* 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 {@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();
queryChecker = checker;
if (factory != null)
queryFactory = factory;
}
/**
* Builds an ADQL parser without a query to parse but with a
* {@link QueryChecker}.
*
* @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
* {@link ADQLQueryFactory}.
*
* @param factory The object to use to build an object representation of
* the given ADQL query.
*/
public ADQLParser(ADQLQueryFactory factory) {
this((QueryChecker)null, factory);
}
/**
* 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 {@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);
setDebug(false);
setDebug(false);
queryChecker = checker;
if (factory != null)
queryFactory = factory;
}
/**
* 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 {@link ADQLQuery}.
*/
public ADQLParser(java.io.InputStream stream, QueryChecker checker) {
this(stream, checker, null);
}
/**
* 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.
*/
public ADQLParser(java.io.InputStream stream, ADQLQueryFactory factory) {
this(stream, (QueryChecker)null, factory);
}
/**
* 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 encoding The supplied encoding.
* @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);
setDebug(false);
queryChecker = checker;
if (factory != null)
queryFactory = factory;
}
/**
* 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 encoding The supplied encoding.
* @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);
}
/**
* 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 encoding The supplied encoding.
* @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);
}
/**
* 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 {@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);
setDebug(false);
setDebug(false);
queryChecker = checker;
if (factory != null)
queryFactory = factory;
}
/**
* 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 {@link ADQLQuery}.
*/
public ADQLParser(java.io.Reader reader, QueryChecker checker) {
this(reader, checker, null);
}
/**
* 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.
*/
public ADQLParser(java.io.Reader reader, ADQLQueryFactory factory) {
this(reader, null, factory);
}
/**
* 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 {@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);
setDebug(false);
setDebug(false);
queryChecker = checker;
if (factory != null)
queryFactory = factory;
}
/**
* 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 {@link ADQLQuery}.
*/
public ADQLParser(ADQLParserTokenManager tm, QueryChecker checker) {
this(tm, checker, null);
}
/**
* 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.
*/
public ADQLParser(ADQLParserTokenManager tm, ADQLQueryFactory factory) {
this(tm, null, factory);
}
/* ADDITIONAL GETTERS & SETTERS */
public final void setDebug(boolean debug){
if (debug) enable_tracing();
else disable_tracing();
}
public final QueryChecker getQueryChecker(){
return queryChecker;
}
public final void setQueryChecker(QueryChecker checker){
queryChecker = checker;
}
public final ADQLQueryFactory getQueryFactory(){
return queryFactory;
}
public final void setQueryFactory(ADQLQueryFactory factory){
queryFactory = (factory!=null)?factory:(new ADQLQueryFactory());
}
/* EXCEPTION HELPER FUNCTION */
private final ParseException generateParseException(Exception ex){
if (!(ex instanceof ParseException)){
ParseException pex = new ParseException("["+ex.getClass().getName()+"] "+ex.getMessage());
pex.setStackTrace(ex.getStackTrace());
return pex;
}else
return (ParseException)ex;
}
/* QUERY PARSING FUNCTIONS */
/**
* Tell whether the given string is a valid ADQL regular identifier.
*
*
* According to the ADQL-2.0's BNF, a regular identifier (i.e. not delimited
* ; not between double quotes) must be a letter followed by a letter, digit
* or underscore. So, the following regular expression:
*
*
[a-zA-Z]+[a-zA-Z0-9_]*
*
*
This is what this function tests on the given string.
*
* @param idCandidate The string to test.
*
* @return true if the given string is a valid regular
* identifier,
* false otherwise.
*
* @see #testRegularIdentifier(adql.parser.Token)
*
* @since 1.5
*/
public final boolean isRegularIdentifier(final String idCandidate) {
return idCandidate.matches("[a-zA-Z]+[a-zA-Z0-9_]*");
}
/**
* Test the given token as an ADQL's regular identifier.
*
*
* This function uses {@link #isRegularIdentifier(java.lang.String)} to
* test the given token's image. If the test fails, a
* {@link adql.parser.ParseException} is thrown.
*
*
* @param token The token to test.
*
* @throws ParseException If the given token is not a valid ADQL regular
* identifier.
*
* @see #isRegularIdentifier(java.lang.String)
*
* @since 1.5
*/
public final void testRegularIdentifier(final Token token) throws ParseException {
if (!isRegularIdentifier(token.image))
throw new ParseException("Invalid ADQL regular identifier: \""+token.image+"\"! If it aims to be a column/table name/alias, you should write it between double quotes.", new TextPosition(token));
}
/**
* Parses the query given at the creation of this parser or in the
* ReInit functions.
*
* @return The object representation of the given ADQL query.
*
* @throws ParseException If there is at least one syntactic error.
*
* @see ADQLParser#Query()
*/
public final ADQLQuery parseQuery() throws ParseException {
stackQuery.clear();
query = null;
try {
return Query();
}catch(TokenMgrError tme) {
throw new ParseException(tme);
}
}
/**
* 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)
* @see ADQLParser#setDebug(boolean)
* @see ADQLParser#Query()
*/
public final ADQLQuery parseQuery(String q) throws ParseException {
stackQuery.clear();
query = null;
ReInit(new java.io.ByteArrayInputStream(q.getBytes()));
try {
return Query();
}catch(TokenMgrError tme) {
throw new ParseException(tme);
}
}
/**
* 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)
* @see ADQLParser#setDebug(boolean)
* @see ADQLParser#Query()
*/
public final ADQLQuery parseQuery(java.io.InputStream stream) throws ParseException {
stackQuery.clear();
query = null;
ReInit(stream);
try {
return Query();
}catch(TokenMgrError tme) {
throw new ParseException(tme);
}
}
/* CORRECTION SUGGESTION */
/**
* Try fixing tokens/terms of the input ADQL query.
*
*
* This function does not try to fix syntactical or semantical errors.
* It just try to fix the most common issues in ADQL queries, such as:
*
*
*
some Unicode characters confusable with ASCII characters (like a
* space, a dash, ...) ; this function replace them by their ASCII
* alternative,
*
any of the following are double quoted:
*
*
non regular ADQL identifiers
* (e.g. _RAJ2000),
*
ADQL function names used as identifiers
* (e.g. distance)
*
and SQL reserved keywords
* (e.g. public).
*
*
*
*
*
Note 1:
* The given stream is NOT closed by this function even if the EOF is
* reached. It is the responsibility of the caller to close it.
*
*
*
Note 2:
* This function does not use any instance variable of this parser
* (especially the InputStream or Reader provided at initialisation or
* ReInit).
*
*
* @param input Stream containing the input ADQL query to fix.
*
* @return The suggested correction of the input ADQL query.
*
* @throws java.io.IOException If there is any error while reading from the
* given input stream.
* @throws ParseException If any unrecognised character is encountered,
* or if anything else prevented the tokenization
* of some characters/words/terms.
*
* @see #tryQuickFix(java.lang.String)
*
* @since 1.5
*/
public final String tryQuickFix(final java.io.InputStream input) throws java.io.IOException, ParseException {
// Fetch everything into a single string:
StringBuffer buf = new StringBuffer();
byte[] cBuf = new byte[1024];
int nbChar;
while((nbChar = input.read(cBuf)) > -1){
buf.append(new String(cBuf, 0, nbChar));
}
// Convert the buffer into a String and now try to fix it:
return tryQuickFix(buf.toString());
}
/**
* Try fixing tokens/terms of the given ADQL query.
*
*
* This function does not try to fix syntactical or semantical errors.
* It just try to fix the most common issues in ADQL queries, such as:
*
*
*
some Unicode characters confusable with ASCII characters (like a
* space, a dash, ...) ; this function replace them by their ASCII
* alternative,
*
any of the following are double quoted:
*
*
non regular ADQL identifiers
* (e.g. _RAJ2000),
*
ADQL function names used as identifiers
* (e.g. distance)
*
and SQL reserved keywords
* (e.g. public).
*
*
*
*
*
Note:
* This function does not use any instance variable of this parser
* (especially the InputStream or Reader provided at initialisation or
* ReInit).
*
*
* @param adqlQuery The input ADQL query to fix.
*
* @return The suggested correction of the given ADQL query.
*
* @throws ParseException If any unrecognised character is encountered,
* or if anything else prevented the tokenization
* of some characters/words/terms.
*
* @since 1.5
*/
public String tryQuickFix(String adqlQuery) throws ParseException {
StringBuffer suggestedQuery = new StringBuffer();
// 1. Replace all Unicode confusable characters:
adqlQuery = replaceUnicodeConfusables(adqlQuery);
/* 1.bis. Normalise new lines and tabulations
* (to simplify the column counting): */
adqlQuery = adqlQuery.replaceAll("(\r\n|\r|\n)", System.getProperty("line.separator")).replaceAll("\t", " ");
// 2. Analyse the query token by token:
ADQLParserTokenManager parser = new ADQLParserTokenManager(new SimpleCharStream(new java.io.ByteArrayInputStream(adqlQuery.getBytes())));
final String[] lines = adqlQuery.split(System.getProperty("line.separator"));
try{
String suggestedToken;
int lastLine = 1, lastCol = 1;
Token token = null, nextToken = parser.getNextToken();
// for all tokens until the EOF or EOQ:
do{
// get the next token:
token = nextToken;
nextToken = (isEnd(token) ? null : parser.getNextToken());
// 3. Double quote any suspect token:
if (mustEscape(token, nextToken)){
suggestedToken = "\"" + token.image + "\"";
}else
suggestedToken = token.image;
/* 4. Append all space characters (and comments) before the
* token: */
/* same line, just get the space characters between the last
* token and the one to append: */
if (lastLine == token.beginLine){
if (token.kind == ADQLParserConstants.EOF)
suggestedQuery.append(lines[lastLine - 1].substring(lastCol - 1));
else
suggestedQuery.append(lines[lastLine - 1].substring(lastCol - 1, token.beginColumn - (isEnd(token) ? 0 : 1)));
lastCol = token.endColumn + 1;
}
// not the same line...
else{
/* append all remaining space characters until the position
* of the token to append: */
do{
suggestedQuery.append(lines[lastLine - 1].substring(lastCol - 1)).append('\n');
lastLine++;
lastCol = 1;
}while(lastLine < token.beginLine);
/* if there are still space characters before the token,
* append them as well: */
if (lastCol < token.beginColumn)
suggestedQuery.append(lines[lastLine - 1].substring(lastCol - 1, token.beginColumn - 1));
// finally, set the correct column position:
lastCol = token.endColumn + 1;
}
// 5. Append the suggested token:
suggestedQuery.append(suggestedToken);
}while(!isEnd(token));
}catch(TokenMgrError err){
// wrap such errors and propagate them:
throw new ParseException(err);
}
return suggestedQuery.toString();
}
/**
* All of the most common Unicode confusable characters and their
* ASCII/UTF-8 alternative.
*
*
* Keys of this map represent the ASCII character while the values are the
* regular expression for all possible Unicode alternatives.
*
*
* @since 1.5
*/
protected final static java.util.Map REGEX_UNICODE_CONFUSABLES = new java.util.HashMap(10);
/** Regular expression matching all Unicode alternatives for -.
* @since 1.5 */
protected final static String REGEX_DASH = "[\u002D\u02D7\u06D4\u2010\u2011\u2012\u2013\u2043\u2212\u2796\u2CBA\uFE58\u2014\u2015\u207B\u208B\u0096\u058A\uFE63\uFF0D]";
/** Regular expression matching all Unicode alternatives for _.
* @since 1.5 */
protected final static String REGEX_UNDERSCORE = "[\u005F\u07FA\uFE4D\uFE4E\uFE4F]";
/** Regular expression matching all Unicode alternatives for '.
* @since 1.5 */
protected final static String REGEX_QUOTE = "[\u0027\u0060\u00B4\u02B9\u02BB\u02BC\u02BD\u02BE\u02C8\u02CA\u02CB\u02F4\u0374\u0384\u055A\u055D\u05D9\u05F3\u07F4\u07F5\u144A\u16CC\u1FBD\u1FBF\u1FEF\u1FFD\u1FFE\u2018\u2019\u201B\u2032\u2035\uA78C\uFF07\uFF40]";
/** Regular expression matching all Unicode alternatives for ".
* @since 1.5 */
protected final static String REGEX_DOUBLE_QUOTE = "[\u02BA\u02DD\u02EE\u02F6\u05F2\u05F4\u1CD3\u201C\u201D\u201F\u2033\u2036\u3003\uFF02]";
/** Regular expression matching all Unicode alternatives for ..
* @since 1.5 */
protected final static String REGEX_STOP = "[\u002E\u0660\u06F0\u0701\u0702\u2024\uA4F8\uA60E]";
/** Regular expression matching all Unicode alternatives for +.
* @since 1.5 */
protected final static String REGEX_PLUS = "[\u002B\u16ED\u2795]";
/** Regular expression matching all Unicode alternatives for .
* @since 1.5 */
protected final static String REGEX_SPACE = "[\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F]";
/** Regular expression matching all Unicode alternatives for <.
* @since 1.5 */
protected final static String REGEX_LESS_THAN = "[\u003C\u02C2\u1438\u16B2\u2039\u276E]";
/** Regular expression matching all Unicode alternatives for >.
* @since 1.5 */
protected final static String REGEX_GREATER_THAN = "[\u003E\u02C3\u1433\u203A\u276F]";
/** Regular expression matching all Unicode alternatives for =.
* @since 1.5 */
protected final static String REGEX_EQUAL = "[\u003D\u1400\u2E40\u30A0\uA4FF]";
static {
REGEX_UNICODE_CONFUSABLES.put("-", REGEX_DASH);
REGEX_UNICODE_CONFUSABLES.put("_", REGEX_UNDERSCORE);
REGEX_UNICODE_CONFUSABLES.put("'", REGEX_QUOTE);
REGEX_UNICODE_CONFUSABLES.put("\"", REGEX_DOUBLE_QUOTE);
REGEX_UNICODE_CONFUSABLES.put(".", REGEX_STOP);
REGEX_UNICODE_CONFUSABLES.put("+", REGEX_PLUS);
REGEX_UNICODE_CONFUSABLES.put(" ", REGEX_SPACE);
REGEX_UNICODE_CONFUSABLES.put("<", REGEX_LESS_THAN);
REGEX_UNICODE_CONFUSABLES.put(">", REGEX_GREATER_THAN);
REGEX_UNICODE_CONFUSABLES.put("=", REGEX_EQUAL);
}
/**
* Replace all Unicode characters that can be confused with other ASCI/UTF-8
* characters (e.g. different spaces, dashes, ...) in their ASCII version.
*
* @param adqlQuery The ADQL query string in which Unicode confusable
* characters must be replaced.
*
* @return The same query without the most common Unicode confusable
* characters.
*
* @since 1.5
*/
protected String replaceUnicodeConfusables(final String adqlQuery){
String newAdqlQuery = adqlQuery;
for(java.util.Map.Entry confusable : REGEX_UNICODE_CONFUSABLES.entrySet())
newAdqlQuery = newAdqlQuery.replaceAll(confusable.getValue(), confusable.getKey());
return newAdqlQuery;
}
/**
* Tell whether the given token represents the end of an ADQL query.
*
* @param token Token to analyze.
*
* @return true if the given token represents a query end,
* false otherwise.
*
* @since 1.5
*/
protected boolean isEnd(final Token token){
return token.kind == ADQLParserConstants.EOF || token.kind == ADQLParserConstants.EOQ;
}
/**
* Tell whether the given token must be double quoted.
*
*
* This function considers all the following as terms to double quote:
*
*
*
SQL reserved keywords
,
*
unrecognised regular identifiers (e.g. neither a delimited nor a
* valid ADQL regular identifier)
*
and ADQL function name without a parameters list.
*
*
* @param token The token to analyze.
* @param nextToken The following token. (useful to detect the start of a
* function's parameters list)
*
* @return true if the given token must be double quoted,
* false to keep it as provided.
*
* @since 1.5
*/
protected boolean mustEscape(final Token token, final Token nextToken){
switch(token.kind){
case ADQLParserConstants.SQL_RESERVED_WORD:
return true;
case ADQLParserConstants.REGULAR_IDENTIFIER_CANDIDATE:
return !isRegularIdentifier(token.image);
default:
return isFunctionName(token) && (nextToken == null || nextToken.kind != ADQLParserConstants.LEFT_PAR);
}
}
/**
* Tell whether the given token matches to an ADQL function name.
*
* @param token The token to analyze.
*
* @return true if the given token is an ADQL function name,
* false otherwise.
*
* @since 1.5
*/
protected boolean isFunctionName(final Token token){
switch(token.kind){
case ADQLParserConstants.COUNT:
case ADQLParserConstants.EXISTS:
case ADQLParserConstants.AVG:
case ADQLParserConstants.MAX:
case ADQLParserConstants.MIN:
case ADQLParserConstants.SUM:
case ADQLParserConstants.BOX:
case ADQLParserConstants.CENTROID:
case ADQLParserConstants.CIRCLE:
case ADQLParserConstants.POINT:
case ADQLParserConstants.POLYGON:
case ADQLParserConstants.REGION:
case ADQLParserConstants.CONTAINS:
case ADQLParserConstants.INTERSECTS:
case ADQLParserConstants.AREA:
case ADQLParserConstants.COORD1:
case ADQLParserConstants.COORD2:
case ADQLParserConstants.COORDSYS:
case ADQLParserConstants.DISTANCE:
case ADQLParserConstants.ABS:
case ADQLParserConstants.CEILING:
case ADQLParserConstants.DEGREES:
case ADQLParserConstants.EXP:
case ADQLParserConstants.FLOOR:
case ADQLParserConstants.LOG:
case ADQLParserConstants.LOG10:
case ADQLParserConstants.MOD:
case ADQLParserConstants.PI:
case ADQLParserConstants.POWER:
case ADQLParserConstants.RADIANS:
case ADQLParserConstants.RAND:
case ADQLParserConstants.ROUND:
case ADQLParserConstants.SQRT:
case ADQLParserConstants.TRUNCATE:
case ADQLParserConstants.ACOS:
case ADQLParserConstants.ASIN:
case ADQLParserConstants.ATAN:
case ADQLParserConstants.ATAN2:
case ADQLParserConstants.COS:
case ADQLParserConstants.COT:
case ADQLParserConstants.SIN:
case ADQLParserConstants.TAN:
case ADQLParserConstants.USING:
return true;
default:
return false;
}
}
/* MAIN PROGRAM */
/**
* Gets the specified ADQL query and parses the given ADQL query. The SQL
* translation is then printed if the syntax is correct.
*
*
* ONLY the syntax is checked: the query is NOT EXECUTED !
*
*
* @param args
* @throws Exception
*/
public static final void main(String[] args) throws Exception {
final String USAGE = "Usage:\n adqlParser.jar [-d] [-v] [-e] [-a|-s] [-f] [|]\n\nNOTE: If no file or URL is given, the ADQL query is expected in the standard\n input. This query must end with a ';' or !\n\nParameters:\n -v or --verbose : Print the main steps of the parsing\n -d or --debug : Print stack traces when a grave error occurs\n -e or --explain : Explain the ADQL parsing (or Expand the parsing tree)\n -a or --adql : Display the understood ADQL query\n -s or --sql : Ask the SQL translation of the given ADQL query\n (SQL compatible with PostgreSQL)\n -f or --try-fix : Try fixing the most common ADQL query issues before\n attempting to parse the query.\n\nReturn:\n By default: nothing if the query is correct. Otherwise a message explaining\n why the query is not correct is displayed.\n With the -s option, the SQL translation of the given ADQL query will be\n returned.\n With the -a option, the ADQL query is returned as it has been understood.\n\nExit status:\n 0 OK !\n 1 Parameter error (missing or incorrect parameter)\n 2 File error (incorrect file/url, reading error, ...)\n 3 Parsing error (syntactic or semantic error)\n 4 Translation error (a problem has occurred during the translation of the\n given ADQL query in SQL).";
ADQLParser parser;
final String urlRegex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
String file = null, metaFile = null;
short mode = -1;
boolean verbose=false, debug=false, explain=false, tryFix=false;
// Parameters reading:
for(int i=0; i }
/* ************************************************************************** */
/* Reserved SQL words */
/* */
/* NOTE: */
/* This list is the one provided by the ADQL-2.0 standard after removal of */
/* all ADQL used words (e.g. SELECT, AS, LIKE, AVG, ABS, COS, POINT). */
/* (see ParseException.initialise(Token, int[][], String[]) for more */
/* details) */
/* ************************************************************************** */
TOKEN : {
< SQL_RESERVED_WORD: ("ABSOLUTE"|"ACTION"|"ADD"|"ALLOCATE"|"ALTER"|"ANY"|"ARE"|"ASSERTION"|"AT"|"AUTHORIZATION"|"BEGIN"|"BIT"|"BIT_LENGTH"|"BOTH"|"CASCADE"|"CASCADED"|"CASE"|"CAST"|"CATALOG"|"CHAR"|"CHARACTER"|"CHAR_LENGTH"|"CHARACTER_LENGTH"|"CHECK"|"CLOSE"|"COALESCE"|"COLLATE"|"COLLATION"|"COLUMN"|"COMMIT"|"CONNECT"|"CONNECTION"|"CONSTRAINT"|"CONSTRAINTS"|"CONTINUE"|"CONVERT"|"CORRESPONDING"|"CREATE"|"CURRENT"|"CURRENT_DATE"|"CURRENT_TIME"|"CURRENT_TIMESTAMP"|"CURRENT_USER"|"CURSOR"|"DATE"|"DAY"|"DEALLOCATE"|"DECIMAL"|"DECLARE"|"DEFAULT"|"DEFERRABLE"|"DEFERRED"|"DELETE"|"DESCRIBE"|"DESCRIPTOR"|"DIAGNOSTICS"|"DISCONNECT"|"DOMAIN"|"DOUBLE"|"DROP"|"ELSE"|"END"|"END-EXEC"|"ESCAPE"|"EXCEPT"|"EXCEPTION"|"EXEC"|"EXECUTE"|"EXTERNAL"|"EXTRACT"|"FALSE"|"FETCH"|"FIRST"|"FLOAT"|"FOR"|"FOREIGN"|"FOUND"|"GET"|"GLOBAL"|"GO"|"GOTO"|"GRANT"|"HOUR"|"IDENTITY"|"IMMEDIATE"|"INDICATOR"|"INITIALLY"|"INPUT"|"INSENSITIVE"|"INSERT"|"INT"|"INTEGER"|"INTERSECT"|"INTERVAL"|"INTO"|"ISOLATION"|"KEY"|"LANGUAGE"|"LAST"|"LEADING"|"LEVEL"|"LOCAL"|"LOWER"|"MATCH"|"MINUTE"|"MODULE"|"MONTH"|"NAMES"|"NATIONAL"|"NCHAR"|"NEXT"|"NO"|"NULLIF"|"NUMERIC"|"OCTET_LENGTH"|"OF"|"ONLY"|"OPEN"|"OPTION"|"OUTPUT"|"OVERLAPS"|"PAD"|"PARTIAL"|"POSITION"|"PRECISION"|"PREPARE"|"PRESERVE"|"PRIMARY"|"PRIOR"|"PRIVILEGES"|"PROCEDURE"|"PUBLIC"|"READ"|"REAL"|"REFERENCES"|"RELATIVE"|"RESTRICT"|"REVOKE"|"ROLLBACK"|"ROWS"|"SCHEMA"|"SCROLL"|"SECOND"|"SECTION"|"SESSION"|"SESSION_USER"|"SET"|"SIZE"|"SMALLINT"|"SOME"|"SPACE"|"SQL"|"SQLCODE"|"SQLERROR"|"SQLSTATE"|"SUBSTRING"|"SYSTEM_USER"|"TABLE"|"TEMPORARY"|"THEN"|"TIME"|"TIMESTAMP"|"TIMEZONE_HOUR"|"TIMEZONE_MINUTE"|"TO"|"TRAILING"|"TRANSACTION"|"TRANSLATE"|"TRANSLATION"|"TRIM"|"TRUE"|"UNION"|"UNIQUE"|"UNKNOWN"|"UPDATE"|"UPPER"|"USAGE"|"USER"|"VALUE"|"VALUES"|"VARCHAR"|"VARYING"|"VIEW"|"WHEN"|"WHENEVER"|"WITH"|"WORK"|"WRITE"|"YEAR"|"ZONE") >
}
/* *********** */
/* Punctuation */
/* *********** */
TOKEN : {
< LEFT_PAR: "(" >
| < RIGHT_PAR: ")" >
| < DOT: "." >
| < COMMA: "," >
| < EOQ: ";">
| < CONCAT: "||" >
}
/* ******************** */
/* Arithmetic operators */
/* ******************** */
TOKEN : {
< PLUS: "+" >
| < MINUS: "-" >
| < ASTERISK: "*" >
| < DIVIDE: "/" >
}
/* ******************** */
/* Comparison operators */
/* ******************** */
TOKEN : {
< EQUAL: "=" >
| < NOT_EQUAL: "<>" | "!=" >
| < LESS_THAN: "<" >
| < LESS_EQUAL_THAN: "<=" >
| < GREATER_THAN: ">" >
| < GREATER_EQUAL_THAN: ">=" >
}
/* *************** */
/* SELECT's tokens */
/* *************** */
TOKEN : {
< SELECT: "SELECT" >
| < QUANTIFIER: "DISTINCT" | "ALL" >
| < TOP: "TOP" >
}
/* ************* */
/* FROM's tokens */
/* ************* */
TOKEN : {
< FROM: "FROM" >
| < AS: "AS" >
| < NATURAL: "NATURAL" >
| < INNER: "INNER" >
| < OUTER: "OUTER" >
| < RIGHT: "RIGHT" >
| < LEFT: "LEFT" >
| < FULL: "FULL" >
| < JOIN: "JOIN" >
| < ON: "ON" >
| < USING: "USING" >
}
/* ************** */
/* WHERE's tokens */
/* ************** */
TOKEN : {
< WHERE: "WHERE" >
| < AND: "AND" >
| < OR: "OR" >
| < NOT: "NOT" >
| < IS: "IS" >
| < NULL: "NULL" >
| < BETWEEN: "BETWEEN" >
| < LIKE: "LIKE" >
| < IN: "IN" >
| < EXISTS: "EXISTS" >
}
/* ********************* */
/* Other clauses' tokens */
/* ********************* */
TOKEN : {
< BY: "BY" >
| < GROUP: "GROUP" >
| < HAVING: "HAVING" >
| < ORDER: "ORDER" >
| < ASC: "ASC" >
| < DESC: "DESC" >
}
/* ************* */
/* SQL functions */
/* ************* */
TOKEN : {
< AVG: "AVG" >
| < MAX: "MAX" >
| < MIN: "MIN" >
| < SUM: "SUM" >
| < COUNT: "COUNT" >
}
/* ************** */
/* ADQL functions */
/* ************** */
TOKEN : {
< BOX: "BOX" >
| < CENTROID: "CENTROID" >
| < CIRCLE: "CIRCLE" >
| < POINT: "POINT" >
| < POLYGON: "POLYGON" >
| < REGION: "REGION" >
| < CONTAINS: "CONTAINS" >
| < INTERSECTS: "INTERSECTS" >
| < AREA: "AREA" >
| < COORD1: "COORD1" >
| < COORD2: "COORD2" >
| < COORDSYS: "COORDSYS" >
| < DISTANCE: "DISTANCE" >
}
/* ********************** */
/* Mathematical functions */
/* ********************** */
TOKEN : {
< ABS: "ABS" >
| < CEILING: "CEILING" >
| < DEGREES: "DEGREES" >
| < EXP: "EXP" >
| < FLOOR: "FLOOR" >
| < LOG: "LOG" >
| < LOG10: "LOG10" >
| < MOD: "MOD" >
| < PI: "PI" >
| < POWER: "POWER" >
| < RADIANS: "RADIANS" >
| < RAND: "RAND" >
| < ROUND: "ROUND" >
| < SQRT: "SQRT" >
| < TRUNCATE: "TRUNCATE" >
}
/* ************************* */
/* Trigonometrical functions */
/* ************************* */
TOKEN : {
< ACOS: "ACOS" >
| < ASIN: "ASIN" >
| < ATAN: "ATAN" >
| < ATAN2: "ATAN2" >
| < COS: "COS" >
| < COT: "COT" >
| < SIN: "SIN" >
| < TAN: "TAN" >
}
/* ******* */
/* Comment */
/* ******* */
SKIP : { < (~["\n","\r"])* ("\n"|"\r"|"\r\n")? > }
/* ****** */
/* String */
/* ****** */
MORE : { "'" : WithinString }
MORE : { < ~["'"] | ("''") > }
TOKEN : { < STRING_LITERAL: "'" >: DEFAULT }
/* *************** */
/* Primary numbers */
/* *************** */
TOKEN : {
< SCIENTIFIC_NUMBER: (|) "E" (|)? >
| < UNSIGNED_FLOAT: ( ()?) | () >
| < UNSIGNED_INTEGER: ()+ >
| < #DIGIT: ["0"-"9"] >
}
/* ************************************************* */
/* Identifier (column, tables, ...) */
/* ************************************************* */
MORE : { "\"" : WithinDelimitedId }
MORE : { < ~["\""] | ("\"\"") > }
TOKEN : { < DELIMITED_IDENTIFIER: "\"" >: DEFAULT }
TOKEN : {
< REGULAR_IDENTIFIER_CANDIDATE: (()+ ( | )* | ()+ ( | )*) >
| < #Letter: ["a"-"z","A"-"Z","_","?","$","@","^","#","`","~","[","]","{","}"] >
}
/* ########## */
/* # SYNTAX # */
/* ########## */
/* ******************* */
/* GENERAL ADQL SYNTAX */
/* ******************* */
/**
* Parses the ADQL query given at the parser creation or in the {@link ADQLParser#ReInit(java.io.InputStream)}
* or in the parseQuery functions.
*
* @return The object representation of the query.
* @throws ParseException If the query syntax is incorrect.
*/
ADQLQuery Query(): {ADQLQuery q = null;}{
q=QueryExpression() ( | )
{
// check the query:
if (queryChecker != null)
queryChecker.check(q);
return q;
}
}
ADQLQuery QueryExpression(): {TextPosition endPos = null;} {
{
try{
// create the query:
query = queryFactory.createQuery();
stackQuery.push(query);
}catch(Exception ex){
throw generateParseException(ex);
}
}
Select()
From() {endPos = query.getFrom().getPosition();}
[Where() {endPos = query.getWhere().getPosition();}]
[GroupBy() {endPos = query.getGroupBy().getPosition();}]
[Having() {endPos = query.getHaving().getPosition();}]
[OrderBy() {endPos = query.getOrderBy().getPosition();}]
{
// set the position of the query:
query.setPosition(new TextPosition(query.getSelect().getPosition(), endPos));
// get the previous query (!= null if the current query is a sub-query):
ADQLQuery previousQuery = stackQuery.pop();
if (stackQuery.isEmpty())
query = null;
else
query = stackQuery.peek();
return previousQuery;
}
}
ADQLQuery SubQueryExpression(): {ADQLQuery q = null; Token start, end;} {
start= q=QueryExpression() end=
{
q.setPosition(new TextPosition(start, end));
return q;
}
}
void Select(): {ClauseSelect select = query.getSelect(); SelectItem item=null; Token start,t = null;} {
start=