Skip to content

Commit ca2b3d7

Browse files
fix(grammar): parse bracketed operands in function arguments and expression lists
The Pratt operator loops live in Java actions, which JavaCC does not execute during syntactic lookahead. LOOKAHEAD(3) therefore cannot see past an infix operator and fails on arguments such as "( 1 + a ) / ( 1 + b )", falling through to Select() and reporting "was expecting <K_WITH>". Add fallback alternatives to ExpressionList(), extract FunctionArgumentList() from InternalFunction() and guard it with the O(1) predicate isUnparenthesizedSelectAhead() instead of a syntactic probe. Signed-off-by: Andreas Reichel <andreas@manticore-projects.com>
1 parent 65c30f0 commit ca2b3d7

1 file changed

Lines changed: 70 additions & 16 deletions

File tree

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,26 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
294294
}
295295
}
296296

297+
/**
298+
* True when the next token can only start a bare (unparenthesized) sub-select.
299+
*
300+
* Used where the alternative to Select() is an ExpressionList: the Pratt
301+
* arithmetic loop runs inside a Java action (prattArithRest) and JavaCC does
302+
* not execute actions while evaluating syntactic lookahead, so a jj_3R probe
303+
* cannot see through any infix operator. A syntactic LOOKAHEAD(n) over an
304+
* expression therefore fails as soon as an operator appears within the first
305+
* n tokens - e.g. "( 1 + a ) / ( 1 + b )" - and control wrongly falls through
306+
* to the Select() branch. This O(1) token check replaces that probe.
307+
*/
308+
protected boolean isUnparenthesizedSelectAhead() {
309+
try {
310+
int kind = getToken(1).kind;
311+
return kind == K_SELECT || kind == K_WITH || kind == K_VALUES;
312+
} catch (TokenMgrError e) {
313+
return false;
314+
}
315+
}
316+
297317
/**
298318
* Pratt arithmetic operator precedence loop.
299319
* Handles: *, /, %, ^, DIV (prec=6) and +, -, ||, |, &, <<, >> (prec=5)
@@ -385,7 +405,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
385405
}
386406

387407
// True when "(" opens a parenthesised FROM item (table/function/lateral/join)
388-
// rather than a SELECT subquery. Cheap 2-token check replaces expensive
408+
// rather than a SELECT subquery. Cheap 2-token check replaces expensive
389409
// syntactic LOOKAHEAD(ParenthesedFromItem()).
390410
protected boolean isParenthesedFromItemAhead() {
391411
try {
@@ -7376,6 +7396,16 @@ ExpressionList ExpressionList() #ExpressionList:
73767396
LOOKAHEAD(3) expressionList = SimpleExpressionList()
73777397
|
73787398
LOOKAHEAD(3) expressionList = ParenthesedExpressionList()
7399+
|
7400+
// Fallback, see isUnparenthesizedSelectAhead(): the three probes above are
7401+
// syntactic and cannot see through the Pratt operator loop, which lives in a
7402+
// Java action. They all fail on a bracketed operand of a larger expression,
7403+
// e.g. "( 1 + a ) / ( 1 + b )" or "( 1 + a )" - the third token is an operator
7404+
// where the probe expects "," or ")". Such input is still a valid expression
7405+
// list, so parse it as one instead of raising a ParseException.
7406+
LOOKAHEAD({ !interrupted }) expressionList = ComplexExpressionList()
7407+
|
7408+
expressionList = SimpleExpressionList()
73797409
)
73807410
{
73817411
// Avoid redundant ExpressionLists containing only one ParenthesedExpressionList
@@ -9490,11 +9520,7 @@ Function InternalFunction(boolean escaped):
94909520
Expression expr = null;
94919521
Expression attributeExpression = null;
94929522
Column attributeColumn = null;
9493-
List<OrderByElement> orderByList;
9494-
String onOverflowTruncate = null;
9495-
Token overflowToken = null;
94969523
Limit limit;
9497-
Token extraKeywordToken;
94989524
List<Function.KeywordArgument> keywordArgs = null;
94999525
}
95009526
{
@@ -9511,18 +9537,15 @@ Function InternalFunction(boolean escaped):
95119537
)
95129538
]
95139539
(
9514-
LOOKAHEAD(3) [ LOOKAHEAD(2) extraKeywordToken = <K_TABLE> { retval.setExtraKeyword(extraKeywordToken.image); } ]
9515-
expressionList=ExpressionList()
9516-
[ orderByList = OrderByElements() { retval.setOrderByElements(orderByList); } ]
9540+
LOOKAHEAD(3) expressionList = FunctionArgumentList(retval)
95179541

9518-
// https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/LISTAGG.html
9519-
[
9520-
<K_ON> <K_OVERFLOW> ( overflowToken=<K_TRUNCATE> | overflowToken=<K_ERROR> ) { onOverflowTruncate=overflowToken.image; }
9521-
[
9522-
overflowToken = <S_CHAR_LITERAL> { onOverflowTruncate+= " " + overflowToken.image; }
9523-
[ ( overflowToken=<K_WITH> | overflowToken=<K_WITHOUT> ) <K_COUNT> { onOverflowTruncate+=" " + overflowToken.image + " COUNT"; }]
9524-
]
9525-
] { retval.setOnOverflowTruncate(onOverflowTruncate); }
9542+
|
9543+
// The probe above is syntactic and cannot see through the Pratt operator
9544+
// loop, so it fails on arguments such as "( 1 + a ) / ( 1 + b )" and used
9545+
// to fall through to Select(), reporting "Encountered <OPENING_BRACKET> ...
9546+
// was expecting <K_WITH>". Select() is only reachable here for a bare
9547+
// sub-select, so decide on that instead.
9548+
LOOKAHEAD({ !isUnparenthesizedSelectAhead() }) expressionList = FunctionArgumentList(retval)
95269549

95279550
|
95289551
LOOKAHEAD({ !getAsBoolean(Feature.allowUnparenthesizedSubSelects) }) expr = Select() { expressionList = new ExpressionList(expr); }
@@ -9604,6 +9627,37 @@ Function InternalFunction(boolean escaped):
96049627
}
96059628
}
96069629

9630+
/**
9631+
* Argument list of a Function, extracted from InternalFunction() so that it can be
9632+
* reached from more than one lookahead guard without duplicating the expansion.
9633+
*/
9634+
ExpressionList FunctionArgumentList(Function retval):
9635+
{
9636+
ExpressionList expressionList = null;
9637+
List<OrderByElement> orderByList;
9638+
String onOverflowTruncate = null;
9639+
Token overflowToken = null;
9640+
Token extraKeywordToken;
9641+
}
9642+
{
9643+
[ LOOKAHEAD(2) extraKeywordToken = <K_TABLE> { retval.setExtraKeyword(extraKeywordToken.image); } ]
9644+
expressionList=ExpressionList()
9645+
[ orderByList = OrderByElements() { retval.setOrderByElements(orderByList); } ]
9646+
9647+
// https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/LISTAGG.html
9648+
[
9649+
<K_ON> <K_OVERFLOW> ( overflowToken=<K_TRUNCATE> | overflowToken=<K_ERROR> ) { onOverflowTruncate=overflowToken.image; }
9650+
[
9651+
overflowToken = <S_CHAR_LITERAL> { onOverflowTruncate+= " " + overflowToken.image; }
9652+
[ ( overflowToken=<K_WITH> | overflowToken=<K_WITHOUT> ) <K_COUNT> { onOverflowTruncate+=" " + overflowToken.image + " COUNT"; }]
9653+
]
9654+
] { retval.setOnOverflowTruncate(onOverflowTruncate); }
9655+
9656+
{
9657+
return expressionList;
9658+
}
9659+
}
9660+
96079661
XMLSerializeExpr XMLSerializeExpr(): {
96089662
XMLSerializeExpr result;
96099663
Expression expression;

0 commit comments

Comments
 (0)