Hi Tom,
attached you find an updated version of EJBQLParser.g. I added new
grammar rules for
- multiple expressions in the SELECT clause
- constructor expression (NEW keyword)
- EXISTS
- ALL, ANY, SOME
- CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP
- SIZE
Please note, all of the above features are not fully implemented, so the
parser will throw an exception "Not yet implemented: SIZE function" if
any of these are used in an EJBQL query.
Please let me know if there are any issues with the change. Thanks!
Regards Michael
/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the "License"). You may not use this file except
* in compliance with the License.
*
* You can obtain a copy of the license at
* glassfish/bootstrap/legal/CDDLv1.0.txt or
*
https://glassfish.dev.java.net/public/CDDLv1.0.html.
* See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* HEADER in each file and include the License file at
* glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
* add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your
* own identifying information: Portions Copyright [yyyy]
* [name of copyright owner]
*/
// Added 20/12/2000 JED. Define the package for the class
header {
package oracle.toplink.essentials.internal.parsing.ejbql.antlr273;
import oracle.toplink.essentials.exceptions.EJBQLException;
}
/** */
class EJBQLParser extends Parser("oracle.toplink.essentials.internal.parsing.ejbql.EJBQLParser");
options {
exportVocab=EJBQL;
k = 3; // This is the number of tokens to look ahead to
buildAST = true;
}
tokens {
ABS="abs";
ALL="all";
AND="and";
ANY="any";
AS="as";
ASC="asc";
AVG="avg";
BETWEEN="between";
BOTH="both";
BY="by";
CONCAT="concat";
COUNT="count";
CURRENT_DATE="current_date";
CURRENT_TIME="current_time";
CURRENT_TIMESTAMP="current_timestamp";
DESC="desc";
DELETE="delete";
DISTINCT="distinct";
EMPTY="empty";
ESCAPE="escape";
EXISTS="exists";
FALSE="false";
FETCH="fetch";
FROM="from";
GROUP="group";
HAVING="having";
IN="in";
INNER="inner";
IS="is";
JOIN="join";
LEADING="leading";
LEFT="left";
LENGTH="length";
LIKE="like";
LOCATE="locate";
LOWER="lower";
MAX="max";
MEMBER="member";
MIN="min";
MOD="mod";
NEW="new";
NOT="not";
NULL="null";
OBJECT="object";
OF="of";
OR="or";
ORDER="order";
OUTER="outer";
SELECT="select";
SET="set";
SIZE="size";
SQRT="sqrt";
SOME="some";
SUBSTRING="substring";
SUM="sum";
TRAILING="trailing";
TRIM="trim";
TRUE="true";
UNKNOWN="unknown";
UPDATE="update";
UPPER="upper";
WHERE="where";
}
{
protected void validateAbstractSchemaName(Token token)
throws RecognitionException {
String text = token.getText();
if (!isValidJavaIdentifier(token.getText())) {
throw new NoViableAltException(token, getFilename());
}
}
protected boolean isValidJavaIdentifier(String text) {
if ((text == null) || text.equals(""))
return false;
// check first char
if (!Character.isJavaIdentifierStart(text.charAt(0)))
return false;
// check remaining characters
for (int i = 1; i < text.length(); i++) {
if (!Character.isJavaIdentifierPart(text.charAt(i))) {
return false;
}
}
return true;
}
}
document
: selectStatement
| updateStatement
| deleteStatement
;
selectStatement
: (selectClause {finishedSelect();})
(fromClause {finishedFrom();})
(whereClause {finishedWhere();})?
(groupByClause {finishedGroupBy();})?
(havingClause {finishedHaving();})?
(orderByClause {finishedOrderBy();})?
EOF {finishedAll();}
;
//================================================
updateStatement
: (updateClause {finishedUpdate();})
(setClause {finishedSet();})
(whereClause {finishedWhere();})?
EOF {finishedAll();}
;
updateClause
: UPDATE {matchedUpdate();}
(abstractSchemaName ((AS)? abstractSchemaIdentifier)?)
;
setClause
: (set setAssignmentClause)
(COMMA {startEqualsAssignment();} setAssignmentClause)*
;
set
: SET {matchedSet();}
;
setAssignmentClause
: ((leftMostIdentifier dot)? identifier equalsAssignment newValue {finishedEqualsAssignment();})
;
newValue
: expressionOperand
;
//================================================
deleteStatement
: deleteClause { finishedDelete(); }
(whereClause {finishedWhere();})?
EOF {finishedAll();}
;
deleteClause
: DELETE { matchedDelete(); } FROM
abstractSchemaName ((AS)? abstractSchemaIdentifier)?
;
//================================================
selectClause
: SELECT {matchedSelect();} (distinct)?
selectExpression
( COMMA
{
throw EJBQLException.notYetImplemented(
"multiple expressions in SELECT clause");
}
selectExpression
)*
;
distinct
: DISTINCT {matchedDistinct();}
;
selectExpression
: singleValuedPathExpression
| aggregateFunction {finishedAggregate();}
| identifier
| OBJECT LEFT_ROUND_BRACKET identifier RIGHT_ROUND_BRACKET
| constructorExpression
;
aggregateFunction
: AVG {matchedAvg();} aggregateFunctionArg
| MAX {matchedMax();} aggregateFunctionArg
| MIN {matchedMin();} aggregateFunctionArg
| SUM {matchedSum();} aggregateFunctionArg
| COUNT {matchedCount();} countFunctionArg
;
aggregateFunctionArg
: LEFT_ROUND_BRACKET (distinct)? stateFieldPathExpression RIGHT_ROUND_BRACKET
;
countFunctionArg
: LEFT_ROUND_BRACKET (distinct)? ( identifier | pathExpression ) RIGHT_ROUND_BRACKET
;
constructorExpression
: NEW
{
throw EJBQLException.notYetImplemented(
"constructor expression");
}
constructorName
LEFT_ROUND_BRACKET
( constructorItem ( COMMA constructorItem )* )?
RIGHT_ROUND_BRACKET
;
constructorName
: baseIdentifier ( DOT baseIdentifier )*
;
constructorItem
: singleValuedPathExpression
| aggregateFunction {finishedAggregate();}
;
fromClause
: from identificationVariableDeclaration
(COMMA (identificationVariableDeclaration |
collectionMemberDeclaration) )*
;
from
: FROM {matchedFrom();}
;
identificationVariableDeclaration
: rangeVariableDeclaration (join)*
;
rangeVariableDeclaration
: abstractSchemaName (AS)? abstractSchemaIdentifier
;
// Non-terminal abstractSchemaName first matches any token to allow abstract
// schema names that are keywords (such as order, etc.).
// Method validateAbstractSchemaName throws an exception if the text of the
// token is not a valid Java identifier.
abstractSchemaName
: ident:.
{
validateAbstractSchemaName(ident);
matchedAbstractSchemaName();
}
;
abstractSchemaIdentifier
: baseIdentifier {matchedAbstractSchemaIdentifier();}
;
join
{ boolean outerJoin; }
: outerJoin = joinSpec
( associationPathExpression (AS)? baseIdentifier
{
matchedJoinIdentifier();
finishedJoin(outerJoin);
}
| FETCH associationPathExpression
{ finishedFetchJoin(outerJoin); }
)
;
joinSpec returns [boolean outer]
{ outer = false; }
: (LEFT (OUTER)? { outer = true; } | INNER )? JOIN
;
collectionMemberDeclaration
: IN LEFT_ROUND_BRACKET collectionValuedPathExpression RIGHT_ROUND_BRACKET
(AS)? baseIdentifier
{
matchedCollectionMemberIdentifier();
finishedInClauseInFrom();
}
;
collectionValuedPathExpression
: pathExpression
;
associationPathExpression
: pathExpression
;
singleValuedPathExpression
: pathExpression
;
stateFieldPathExpression
: pathExpression
;
pathExpression
: leftMostIdentifier dot (identifier dot)* identifier
;
dot
: DOT {matchedDot();}
;
identifier
: baseIdentifier {matchedIdentifier();}
;
leftMostIdentifier
: baseIdentifier {matchedLeftMostIdentifier();}
;
baseIdentifier
: TEXTCHAR
;
whereClause
: WHERE {matchedWhere();} conditionalExpression
;
conditionalExpression
: conditionalTerm (OR{matchedOr();} conditionalTerm {finishedOr();})*
;
conditionalTerm
: {conditionalTermFound();} conditionalFactor (AND{matchedAnd();} conditionalFactor {finishedAnd();})*
;
conditionalFactor
: (NOT {matchedNot();})? ( conditionalPrimary | existsExpression )
;
conditionalPrimary
: (LEFT_ROUND_BRACKET conditionalExpression) =>
(LEFT_ROUND_BRACKET {matchedLeftRoundBracket();} conditionalExpression RIGHT_ROUND_BRACKET {matchedRightRoundBracket();})
| simpleConditionalExpression
;
simpleConditionalExpression
: arithmeticExpression simpleConditionalExpressionRemainder
;
simpleConditionalExpressionRemainder
: comparisonExpression
| ((NOT)? BETWEEN) => (betweenExpression)
| ((NOT)? LIKE) => (likeExpression)
| ((NOT)? IN) => (inExpression)
| (isNot NULL) => (nullComparisonExpression)
| emptyCollectionComparisonExpression
| collectionMemberExpression
;
betweenExpression
: (NOT {matchedNot();})? BETWEEN {matchedBetween();} stringExpression AND {matchedAndAfterBetween();} stringExpression {finishedBetweenAnd();}
;
inExpression
: (NOT {matchedNot();})? (IN {matchedIn();})
(LEFT_ROUND_BRACKET
(literalString | literalNumeric | inputParameter | namedInputParameter)
(COMMA (literalString | literalNumeric | inputParameter | namedInputParameter))*
RIGHT_ROUND_BRACKET)
{finishedIn();}
;
likeExpression
: (NOT {matchedNot();})?
(LIKE {matchedLike();}
(literalString | inputParameter | namedInputParameter)
(ESCAPE {matchedEscape();} literalString {finishedEscape();})?)
{finishedLike();}
;
nullComparisonExpression
: isNot NULL {matchedNull();} {finishedNull();}
;
emptyCollectionComparisonExpression
: isNot EMPTY {matchedEmpty();} {finishedEmpty();}
;
collectionMemberExpression
: (NOT {matchedNot();})? (MEMBER (OF)?) {matchedMemberOf();} collectionValuedPathExpression {finishedMemberOf();}
;
existsExpression
: EXISTS
{
throw EJBQLException.notYetImplemented("EXISTS expression");
}
LEFT_ROUND_BRACKET subquery RIGHT_ROUND_BRACKET
;
comparisonExpression
: (equalTo | notEqualTo | greaterThan | greaterThanEqualTo | lessThan | lessThanEqualTo)
( arithmeticExpression | anyOrAllExpression )
{finishedComparisonExpression();}
;
arithmeticExpression
: simpleArithmeticExpression
| LEFT_ROUND_BRACKET subquery RIGHT_ROUND_BRACKET
;
simpleArithmeticExpression : arithmeticTerm ((plus | minus) arithmeticTerm)* ;
arithmeticTerm : arithmeticFactor ((multiply | divide) arithmeticFactor)* {finishedMultiplyOrDivide();} ;
arithmeticFactor : (plus | minus)? arithmeticPrimary ;
arithmeticPrimary
: expressionOperand
| (LEFT_ROUND_BRACKET {matchedLeftRoundBracket();} arithmeticExpression RIGHT_ROUND_BRACKET {matchedRightRoundBracket();})
;
expressionOperand
: leftMostIdentifier
| stateFieldPathExpression
| functionsReturningNumerics
| functionsReturningDatetime
| functionsReturningStrings
| inputParameter
| namedInputParameter
| literalNumeric
| literalString
| literalBoolean
;
anyOrAllExpression
: ( ALL | ANY | SOME )
{
throw EJBQLException.notYetImplemented("ALL, ANY, SOME expression");
}
LEFT_ROUND_BRACKET subquery RIGHT_ROUND_BRACKET
;
isNot
: IS (NOT {matchedNot();})?
;
stringExpression
: stringPrimary
| LEFT_ROUND_BRACKET subquery RIGHT_ROUND_BRACKET
;
stringPrimary
: literalString
| functionsReturningStrings
| inputParameter
| namedInputParameter
| stateFieldPathExpression
;
//Literals and Low level stuff
literal
: literalNumeric
| literalBoolean
| literalString
;
literalNumeric
: NUM_INT {matchedInteger();}
| NUM_FLOAT {matchedFloat();}
;
literalBoolean
: TRUE {matchedTRUE();}
| FALSE {matchedFALSE();}
;
literalString
: STRING_LITERAL_DOUBLE_QUOTED {matchedDoubleQuotedString();}
| STRING_LITERAL_SINGLE_QUOTED {matchedSingleQuotedString();}
;
inputParameter
: (QUESTIONMARK NUM_INT) {matchedInputParameter();}
;
namedInputParameter
: (COLON TEXTCHAR) {matchedNamedInputParameter();}
;
functionsReturningNumerics
: abs
| length
| mod
| sqrt
| locate
| size
;
functionsReturningDatetime
: CURRENT_DATE
{
throw EJBQLException.notYetImplemented("CURRENT_DATE function");
}
| CURRENT_TIME
{
throw EJBQLException.notYetImplemented("CURRENT_TIME function");
}
| CURRENT_TIMESTAMP
{
throw EJBQLException.notYetImplemented("CURRENT_TIMESTAMP function");
}
;
functionsReturningStrings
: concat
| substring
| trim
| upper
| lower
;
plus
: PLUS {matchedPlus();}
;
minus
: MINUS {matchedMinus();}
;
multiply
: MULTIPLY {matchedMultiply();}
;
divide
: DIVIDE {matchedDivide();}
;
equalTo
: EQUALS {matchedEqualsComparison();}
;
equalsAssignment
: EQUALS {matchedEqualsAssignment();}
;
greaterThan
: GREATER_THAN {matchedGreaterThan();}
;
greaterThanEqualTo
: GREATER_THAN_EQUAL_TO {matchedGreaterThanEqualTo();}
;
lessThan
: LESS_THAN {matchedLessThan();}
;
lessThanEqualTo
: LESS_THAN_EQUAL_TO {matchedLessThanEqualTo();}
;
notEqualTo
: (NOT_EQUAL_TO) {matchedNotEqualTo();}
;
// Functions returning strings
concat
: CONCAT {matchedConcat();}
LEFT_ROUND_BRACKET
(singleValuedPathExpression | literalString) COMMA {matchedCommaAfterConcat();}
(singleValuedPathExpression | literalString)
RIGHT_ROUND_BRACKET {finishedConcat();}
;
substring
: SUBSTRING {matchedSubstring();}
LEFT_ROUND_BRACKET
(singleValuedPathExpression | inputParameter | namedInputParameter | literalString) {finishedSubstringVariable();}
COMMA arithmeticExpression
COMMA arithmeticExpression
RIGHT_ROUND_BRACKET
{finishedSubstring();}
;
trim
: TRIM {matchedTrim();}
LEFT_ROUND_BRACKET
( ( trimDef )=> trimDef )? stringPrimary {finishedTrimVariable();}
RIGHT_ROUND_BRACKET
{finishedTrim();}
;
trimDef
: ( trimSpec )? ( trimChar {finishedTrimChar();} )? FROM
;
trimSpec
: LEADING {matchedLeading();}
| TRAILING {matchedTrailing();}
| BOTH {matchedBoth();}
;
trimChar
: literalString
| inputParameter
;
upper
: UPPER { matchedUpper(); }
LEFT_ROUND_BRACKET stringPrimary { finishedUpperVariable(); } RIGHT_ROUND_BRACKET
{ finishedUpper(); }
;
lower
: LOWER { matchedLower(); }
LEFT_ROUND_BRACKET stringPrimary { finishedLowerVariable(); } RIGHT_ROUND_BRACKET
{ finishedLower(); }
;
// Functions returning numerics
abs
: ABS {matchedAbs();}
LEFT_ROUND_BRACKET
(singleValuedPathExpression | inputParameter | namedInputParameter ) {finishedAbsVariable();}
RIGHT_ROUND_BRACKET
{finishedAbs();}
;
length
: LENGTH {matchedLength();}
LEFT_ROUND_BRACKET
singleValuedPathExpression {finishedLengthVariable();}
RIGHT_ROUND_BRACKET
{finishedLength();}
;
locate
: LOCATE {matchedLocate();}
LEFT_ROUND_BRACKET
literalString {finishedLocateLiteral();}
COMMA singleValuedPathExpression {finishedLocateVariable();}
RIGHT_ROUND_BRACKET
{finishedLocate();}
;
size
: SIZE
{
throw EJBQLException.notYetImplemented("SIZE function");
}
LEFT_ROUND_BRACKET collectionValuedPathExpression RIGHT_ROUND_BRACKET
;
mod
: MOD {matchedMod();}
LEFT_ROUND_BRACKET
arithmeticExpression {finishedFirstArithmeticExpressionInMOD(); }
COMMA arithmeticExpression {finishedSecondArithmeticExpressionInMOD();}
RIGHT_ROUND_BRACKET
;
sqrt
: SQRT {matchedSqrt();}
LEFT_ROUND_BRACKET
(singleValuedPathExpression | inputParameter | namedInputParameter) {finishedSqrtVariable();}
RIGHT_ROUND_BRACKET
;
subquery
: simpleSelectClause
subqueryFromClause
(whereClause {finishedWhere();})?
(groupByClause {finishedGroupBy();})?
(havingClause {finishedHaving();})?
;
simpleSelectClause
: SELECT {matchedSelect();} (distinct)? simpleSelectExpression
;
simpleSelectExpression
: singleValuedPathExpression
| aggregateFunction
| baseIdentifier
;
subqueryFromClause
: from subselectIdentificationVariableDeclaration
( COMMA subselectIdentificationVariableDeclaration )*
;
subselectIdentificationVariableDeclaration
: identificationVariableDeclaration
| associationPathExpression (AS)? baseIdentifier
| collectionMemberDeclaration
;
orderByClause
: ORDER BY {matchedOrderBy();}
orderByItem (COMMA orderByItem)*
;
orderByItem
: stateFieldPathExpression {matchedOrderByItem();}
(ASC {matchedAscDirection();} | DESC {matchedDescDirection();})?
{finishedOrderByItem();}
;
groupByClause
: GROUP BY { matchedGroupBy(); }
groupByItem (COMMA groupByItem)*
;
groupByItem
: stateFieldPathExpression
{ finishedGroupByItem(); }
;
havingClause
: HAVING { matchedHaving(); }
conditionalExpression
;
/** */
class EJBQLLexer extends Lexer;
options {
caseSensitive=false;
caseSensitiveLiterals=false;
k = 4;
exportVocab=EJBQL;
charVocabulary = '\3'..'\377';
}
// hexadecimal digit (again, note it's protected!)
protected
HEX_DIGIT
: ('0'..'9'|'a'..'f')
;
WS : (' ' | '\t' | '\n' | '\r')+
{ $setType(Token.SKIP); } ;
LEFT_ROUND_BRACKET
: '('
;
RIGHT_ROUND_BRACKET
: ')'
;
COMMA
: ','
;
TEXTCHAR
: ('a'..'z' | '_' | '$') ('a'..'z' | '_' | '$' | '0'..'9')*
;
// a numeric literal
NUM_INT
{boolean isDecimal=false;}
: '.' {_ttype = DOT;}
(('0'..'9')+ (EXPONENT)? (FLOAT_SUFFIX)? { _ttype = NUM_FLOAT; })?
| ( '0' {isDecimal = true;} // special case for just '0'
( ('x')
( // hex
// the 'e'|'E' and float suffix stuff look
// like hex digits, hence the (...)+ doesn't
// know when to stop: ambig. ANTLR resolves
// it correctly by matching immediately. It
// is therefor ok to hush warning.
options {
warnWhenFollowAmbig=false;
}
: HEX_DIGIT
)+
| ('0'..'7')+ // octal
)?
| ('1'..'9') ('0'..'9')* {isDecimal=true;} // non-zero decimal
)
( ('l')
// only check to see if it's a float if looks like decimal so far
| {isDecimal}?
( '.' ('0'..'9')* (EXPONENT)? (FLOAT_SUFFIX)?
| EXPONENT (FLOAT_SUFFIX)?
| FLOAT_SUFFIX
)
{ _ttype = NUM_FLOAT; }
)?
;
// a couple protected methods to assist in matching floating point numbers
protected
EXPONENT
: ('e') ('+'|'-')? ('0'..'9')+
;
protected
FLOAT_SUFFIX
: 'f'|'d'
;
EQUALS
: '='
;
GREATER_THAN
: '>'
;
GREATER_THAN_EQUAL_TO
: ">="
;
LESS_THAN
: '<'
;
LESS_THAN_EQUAL_TO
: "<="
;
NOT_EQUAL_TO
: "<>"
;
REMOTE_INTERFACE_REFERENCE_OPERATOR
: "=>"
;
MULTIPLY
: "*"
;
DIVIDE
: "/"
;
PLUS
: "+"
;
MINUS
: "-"
;
QUESTIONMARK
: "?"
;
COLON
: ":"
;
// Added Jan 9, 2001 JED
// string literals
STRING_LITERAL_DOUBLE_QUOTED
: '"' (ESC|~('"'|'\\'))* '"'
;
STRING_LITERAL_SINGLE_QUOTED
: ('\'' '\\' '\'') => ('\'' '\\' '\'')
| '\'' (ESC|~('\''|'\\') | ("''"))* '\''
;
// Added Jan 9, 2001 JED
// escape sequence -- note that this is protected; it can only be called
// from another lexer rule -- it will not ever directly return a token to
// the parser
// There are various ambiguities hushed in this rule. The optional
// '0'...'9' digit matches should be matched here rather than letting
// them go back to STRING_LITERAL to be matched. ANTLR does the
// right thing by matching immediately; hence, it's ok to shut off
// the FOLLOW ambig warnings.
protected
ESC
: '\\'
( '_'
| 'n'
| 'r'
| 't'
| 'b'
| 'f'
| '"'
| '\''
| '\\'
| ('u')+ HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
| ('0'..'3')
(
options {
warnWhenFollowAmbig = false;
}
: ('0'..'7')
(
options {
warnWhenFollowAmbig = false;
}
: '0'..'7'
)?
)?
| ('4'..'7')
(
options {
warnWhenFollowAmbig = false;
}
: ('0'..'9')
)?
)
;