From 85690dcbc28473f7e77d74a3237ca0d0d68f7677 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 29 Oct 2026 09:54:53 +0100 Subject: [PATCH] perf: drop the list-form probes in ExpressionList `ExpressionList()` ran three 3-token lookaheads to choose between the complex, simple and parenthesed list forms, and `ParenthesedExpressionList()` scanned into the list on the parse path to tell an empty body from a non-empty one. Together they took about 5% of parser CPU on `performance.sql`. The probes decided nothing: `ComplexExpressionList()` accepts every list the other two forms do, including `()` and bracketed operands such as `( 1 + a ) / ( 1 + b )`, so it won wherever they could have. It is now called directly, and the parenthesed body is guarded by the token kind of the closing bracket. Parser CPU per parse drops from 29.0 ms to 27.7 ms (-4.5%, three interleaved pairs, on top of #2876), and `./gradlew jmh` from 32.67 to 28.54 ms/op. --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 28 ++++--------------- .../jsqlparser/expression/FunctionTest.java | 10 +++++++ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index a2122b90a9..18ccaa5d4e 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -12715,23 +12715,9 @@ ExpressionList ExpressionList() #ExpressionList: ExpressionList expressionList; } { - ( - LOOKAHEAD(3, { !interrupted }) expressionList = ComplexExpressionList() - | - LOOKAHEAD(3) expressionList = SimpleExpressionList() - | - LOOKAHEAD(3) expressionList = ParenthesedExpressionList() - | - // Fallback, see isUnparenthesizedSelectAhead(): the three probes above are - // syntactic and cannot see through the Pratt operator loop, which lives in a - // Java action. They all fail on a bracketed operand of a larger expression, - // e.g. "( 1 + a ) / ( 1 + b )" or "( 1 + a )" - the third token is an operator - // where the probe expects "," or ")". Such input is still a valid expression - // list, so parse it as one instead of raising a ParseException. - LOOKAHEAD({ !interrupted }) expressionList = ComplexExpressionList() - | - expressionList = SimpleExpressionList() - ) + // ComplexExpressionList() parses every list form, "()" and bracketed operands such as + // "( 1 + a ) / ( 1 + b )" included. + expressionList = ComplexExpressionList() { // Avoid redundant ExpressionLists containing only one ParenthesedExpressionList // return the Parenthesed Sub ExpressionList instead @@ -12763,11 +12749,7 @@ ParenthesedExpressionList ParenthesedExpressionList(): } { "(" - ( - LOOKAHEAD({ !interrupted }) expressions = ComplexExpressionList() - | - expressions = SimpleExpressionList() - )? + [ LOOKAHEAD({ getToken(1).kind != CLOSING_BRACKET }) expressions = ComplexExpressionList() ] ")" { return new ParenthesedExpressionList(expressions); @@ -12835,7 +12817,7 @@ ExpressionList ComplexExpressionList(): | LOOKAHEAD(2, { isNamedParameterAssignmentAhead() }) expr=PostgresNamedFunctionParameter() | - // JavaCC scans an unguarded alternative's first token; a semantic-only lookahead enters directly + // The semantic lookahead makes JavaCC enter Expression() with no scan of its first token. LOOKAHEAD({ !interrupted }) expr=Expression() ) { diff --git a/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java b/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java index deb508c199..6456918b15 100644 --- a/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java @@ -157,4 +157,14 @@ void testAesDecryptWithKeyExpressionInSelect() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @Test + void testBracketedOperandAndEmptyListArguments() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT f((1 + a) / (1 + b), (c + d) * 2) FROM t", true); + + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT f(()) FROM t", true); + + TestUtils.assertSqlCanBeParsedAndDeparsed("INSERT INTO t VALUES ()", true); + } }