From 0c0989b3be3ccc43a5b2a843de83ea3cbb6d3e4f Mon Sep 17 00:00:00 2001 From: Ellie Eadington Date: Mon, 2 Nov 2026 14:12:18 +0000 Subject: [PATCH] Parse a parenthesed LATERAL FROM item isParenthesedFromItemAhead() declines ParenthesedFromItem when the token after "(" is LATERAL, on the basis that LateralSubSelect handles it. LateralSubSelect begins at , so it can never match input that begins at "(", and no later alternative accepts it either. Every parenthesed LATERAL item therefore fails at the opening parenthesis with "Encountered: ". Remove the guard so ParenthesedFromItem is tried, as it was before 2d2ee4b replaced the syntactic LOOKAHEAD(ParenthesedFromItem()) with this two-token predicate. Co-Authored-By: Claude Opus 5 --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 1 - .../sf/jsqlparser/statement/select/SelectTest.java | 7 +++++++ .../statement/select/TableFunctionTest.java | 13 +++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 087c530b43..e0ee6a94aa 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1202,7 +1202,6 @@ public class CCJSqlParser extends AbstractJSqlParser { if (k2 == K_TABLE && isTableQueryAhead(2)) return false; // ( ( SELECT ) UNION ( SELECT ) ) → isNestedSetOperationAhead handles it // Other (( patterns: ( (SELECT...) JOIN ... ) or ( (table) ) → ParenthesedFromItem - if (k2 == K_LATERAL) return false; // handled by LateralSubSelect if (k2 == CLOSING_BRACKET) return false; // empty "()" is not valid // EXASOL: ( IMPORT ... ) is a SubImport; handled at the outer FromItem level // by LOOKAHEAD(2, Dialect.EXASOL...) SubImport() - must NOT go into ParenthesedFromItem diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index 122b13ba45..37c9c837d2 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -2728,6 +2728,13 @@ public void testLateral1() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(stmt); } + @Test + public void testParenthesedLateral() throws JSQLParserException { + String stmt = + "SELECT O.ORDERID, O.CUSTNAME, OL.LINETOTAL FROM ORDERS AS O, (LATERAL(SELECT SUM(NETAMT) AS LINETOTAL FROM ORDERLINES AS LINES WHERE LINES.ORDERID = O.ORDERID)) AS OL"; + assertSqlCanBeParsedAndDeparsed(stmt); + } + @Test public void testLateralComplex1() throws IOException, JSQLParserException { String stmt = IOUtils.toString( diff --git a/src/test/java/net/sf/jsqlparser/statement/select/TableFunctionTest.java b/src/test/java/net/sf/jsqlparser/statement/select/TableFunctionTest.java index 3187e046c7..c39f2f0c89 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/TableFunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/TableFunctionTest.java @@ -33,6 +33,19 @@ void testLateralFlat() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + /** + * A LATERAL item may be wrapped in parentheses, in which case it is a parenthesed FROM item + * whose content happens to be lateral, not a bare LateralSubSelect. + */ + @Test + void testParenthesedLateralFlat() throws JSQLParserException { + String sqlStr = "SELECT DIM, count(value) AS COUNT_\n" + + "FROM t a,\n" + + "(LATERAL FLATTEN(input => a.user_items)) b\n" + + "group by 1"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + /** * The SQL keyword "OUTER" is a valid parameter name for Snowflake's FLATTEN table function. */