diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 0cf4530ed..088492931 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -5383,8 +5383,19 @@ PlainSelect PlainSelect() #PlainSelect: [ LOOKAHEAD( ) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ] [ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ] [ LOOKAHEAD(2) { plainSelect.setEmitChanges(true); } ] - [ LOOKAHEAD(7) limit = LimitBy() { plainSelect.setLimitBy(limit); } ] - [ LOOKAHEAD() limit = LimitWithOffset() { plainSelect.setLimit(limit); } ] + // Parse the LIMIT row count once (this accepts a parenthesized subquery too), then + // optionally attach ClickHouse's `LIMIT ... BY ...`. Checking BY right after the limit + // expression avoids a numeric LOOKAHEAD, which cannot see past a long parenthesized + // subquery and would wrongly commit to LIMIT BY (issue #2359). + [ LOOKAHEAD() limit = LimitWithOffset() + [ LOOKAHEAD() expressionList = ExpressionList() { limit.setByExpressions(expressionList); } ] + { + if (limit.getByExpressions() != null) { + plainSelect.setLimitBy(limit); + } else { + plainSelect.setLimit(limit); + } + } ] [ LOOKAHEAD() offset = Offset() { plainSelect.setOffset(offset); } ] [ LOOKAHEAD(, { limit==null }) limit = LimitWithOffset() { plainSelect.setLimit(limit); } ] [ LOOKAHEAD() fetch = Fetch() { plainSelect.setFetch(fetch); } ] @@ -6773,24 +6784,6 @@ Limit PlainLimit() #PlainLimit: } } -/** - * Clickhouse LIMIT BY - * @see SELECT Query - */ -Limit LimitBy(): -{ - Limit limit; - ExpressionList byExpressions; -} -{ - limit = LimitWithOffset() - byExpressions = ExpressionList() - { - limit.setByExpressions(byExpressions); - return limit; - } -} - Offset Offset(): { Offset offset = new Offset(); diff --git a/src/test/java/net/sf/jsqlparser/expression/LimitExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/LimitExpressionTest.java index 70dbd2103..3139530c6 100644 --- a/src/test/java/net/sf/jsqlparser/expression/LimitExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/LimitExpressionTest.java @@ -11,6 +11,7 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.select.ParenthesedSelect; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; @@ -31,6 +32,38 @@ public void testIssue933() throws JSQLParserException { "SELECT * FROM tmp3 LIMIT (SELECT 2)", true); } + @Test + public void testIssue2359() throws JSQLParserException { + // PostgreSQL allows any expression, including a scalar subquery, as the LIMIT row + // count. A long parenthesized subquery used to fail because the ClickHouse + // "LIMIT ... BY ..." branch was chosen by a numeric LOOKAHEAD that cannot see past + // the subquery. + String sql = "WITH some_table AS (SELECT 1 AS some_column), " + + "another_table AS (SELECT 'some_value' AS condition_column) " + + "SELECT some_column FROM some_table ORDER BY some_column " + + "LIMIT (SELECT COUNT(*) FROM another_table WHERE condition_column = 'some_value')"; + + PlainSelect plainSelect = (PlainSelect) CCJSqlParserUtil.parse(sql); + Assertions.assertTrue( + plainSelect.getLimit().getRowCount() instanceof ParenthesedSelect); + Assertions.assertNull(plainSelect.getLimitBy()); + + TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + + // A function wrapping a scalar subquery must work as the row count too. + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a FROM t LIMIT GREATEST(0, (SELECT COUNT(*) FROM u WHERE c = 'x'))", + true); + } + + @Test + public void testLimitByClickHouseUnchanged() throws JSQLParserException { + // ClickHouse "LIMIT ... BY ..." must keep parsing and round-tripping after the LIMIT + // row-count disambiguation was rewritten (issue #2359). + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT id FROM t LIMIT 5 BY id", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT id FROM t LIMIT 2, 5 BY id", true); + } + @Test public void testIssue1373() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed(