Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 13 additions & 20 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -5383,8 +5383,19 @@ PlainSelect PlainSelect() #PlainSelect:
[ LOOKAHEAD(<K_ORDER> <K_BY>) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ]
[ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ]
[ LOOKAHEAD(2) <K_EMIT> <K_CHANGES> { plainSelect.setEmitChanges(true); } ]
[ LOOKAHEAD(7) limit = LimitBy() { plainSelect.setLimitBy(limit); } ]
[ LOOKAHEAD(<K_LIMIT>) 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(<K_LIMIT>) limit = LimitWithOffset()
[ LOOKAHEAD(<K_BY>) <K_BY> expressionList = ExpressionList() { limit.setByExpressions(expressionList); } ]
{
if (limit.getByExpressions() != null) {
plainSelect.setLimitBy(limit);
} else {
plainSelect.setLimit(limit);
}
} ]
[ LOOKAHEAD(<K_OFFSET>) offset = Offset() { plainSelect.setOffset(offset); } ]
[ LOOKAHEAD(<K_LIMIT>, { limit==null }) limit = LimitWithOffset() { plainSelect.setLimit(limit); } ]
[ LOOKAHEAD(<K_FETCH>) fetch = Fetch() { plainSelect.setFetch(fetch); } ]
Expand Down Expand Up @@ -6773,24 +6784,6 @@ Limit PlainLimit() #PlainLimit:
}
}

/**
* Clickhouse LIMIT BY
* @see <a href='https://clickhouse.com/docs/en/sql-reference/statements/select'>SELECT Query</a>
*/
Limit LimitBy():
{
Limit limit;
ExpressionList byExpressions;
}
{
limit = LimitWithOffset()
<K_BY> byExpressions = ExpressionList()
{
limit.setByExpressions(byExpressions);
return limit;
}
}

Offset Offset():
{
Offset offset = new Offset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand Down
Loading