GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix: allow scalar subquery as LIMIT row count (#2359) by fudianchn · Pull Request #2464 · JSQLParser/JSqlParser

Conversation

@fudianchn

@fudianchn fudianchn commented Aug 13, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

What

Allow any expression, including a scalar subquery, as the LIMIT row count, e.g.:

LIMIT (SELECT COUNT(*) FROM t WHERE ...)
LIMIT GREATEST(0, (SELECT COUNT(*) FROM t WHERE ...))

JSQLParser currently rejects these with Encountered: <EOF> ... Was expecting: "BY".

Why / Root cause

As you noted in the issue, a LOOKAHEAD is too tight. PlainSelect disambiguated the ClickHouse LIMIT ... BY ... branch from a plain limit with a numeric LOOKAHEAD(7) on LimitBy(). A numeric lookahead cannot see past a long parenthesized subquery, so for LIMIT (SELECT COUNT(*) FROM ... WHERE ...) it wrongly committed to the LIMIT BY branch and then failed at the missing BY. Short subqueries such as LIMIT (SELECT 1) happened to stay under the 7-token window and worked, which is why only longer ones failed.

A naive fix (scanning ahead for a BY token) would not work, because a subquery may legitimately contain ORDER BY, whose BY is the same K_BY token.

How

The disambiguation is moved to where it belongs: parse the LIMIT row count once via LimitWithOffset() (which already accepts a parenthesized subquery through its LOOKAHEAD(3) ParenthesedSelect() branch), then check the immediately following token for BY:

[ 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);
        }
    } ]

LimitWithOffset already carries byExpressions, so there is no AST and no public API change. The now-unused LimitBy() production is removed (it was only ever called from this one site).

Scope

All existing LIMIT shapes keep working: LIMIT n, LIMIT n, m, LIMIT n OFFSET m, OFFSET m LIMIT n, LIMIT ALL, and ClickHouse LIMIT n BY ... / LIMIT n, m BY .... The previously-unsupported OFFSET m LIMIT n BY x ordering stays unsupported (out of scope, unchanged).

Testing

LimitExpressionTest.testIssue2359 reproduces the issue (fails on master, passes with this change) and asserts the row count is a ParenthesedSelect stored on limit (not limitBy). testLimitByClickHouseUnchanged guards the rewritten LIMIT ... BY ... path.

Local: spotlessApply, checkstyleMain/Test, spotbugsMain, pmdMain clean; SelectTest (725), ClickHouseTest (15), LimitExpressionTest (6) and the full test suite green apart from pre-existing failures unrelated to this change (ParserKeywordsUtilsTest writing a temp file to C:\WINDOWS, and Mockito MockMaker init on JDK 21).

Performance

gradle jmh, JSQLParserBenchmark.parseSQLStatements on performance.sql, version=latest, 10 forks × 10 iterations (100 samples) on a 32-core host:

build ms/op
master (LOOKAHEAD(7) LimitBy()) 3.598 ± 0.017
this change 3.603 ± 0.017

The +0.14% delta lies within the 99.9% confidence intervals (the CIs overlap almost entirely): no regression.

Verification of the original issue

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')

fails on master and parses + round-trips with this change.

Fixes #2359

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@fudianchn
fix: allow scalar subquery as LIMIT row count (JSQLParser#2359)
c58efe9
PostgreSQL allows any expression, including a scalar subquery, as the LIMIT
row count, e.g. LIMIT (SELECT COUNT(*) FROM t WHERE ...) or
LIMIT GREATEST(0, (SELECT ...)). JSQLParser rejected these.

PlainSelect disambiguated the ClickHouse "LIMIT ... BY ..." branch from a
plain limit with a numeric LOOKAHEAD(7) on LimitBy(). A numeric lookahead
cannot see past a long parenthesized subquery, so for LIMIT (subquery) it
wrongly committed to the LIMIT BY branch and then failed at the missing BY
keyword. (Short subqueries such as LIMIT (SELECT 1) happened to stay under
the lookahead window and worked, which is why the bug only surfaced for
longer ones.)

The disambiguation is moved to where it belongs: parse the LIMIT row count
once via LimitWithOffset() (which already accepts a parenthesized
subquery), then check the immediately following token for BY. A token scan
for BY would not work, because a subquery may contain ORDER BY (a K_BY).
The now-unused LimitBy() production is removed; LimitWithOffset already
carries byExpressions, so no AST or public API change.

All LIMIT shapes keep working: LIMIT n, LIMIT n, m, LIMIT n OFFSET m,
OFFSET m LIMIT n, LIMIT ALL, and ClickHouse LIMIT n BY ... / LIMIT n, m BY ...

Fixes JSQLParser#2359

Signed-off-by: 付典 <[email protected]>
@fudianchn
fudianchn marked this pull request as draft August 13, 2026 18:28
@fudianchn
fudianchn marked this pull request as ready for review August 13, 2026 19:23
@manticore-projects

manticore-projects commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

This is the much better solution of course, thank you for fixing this!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@manticore-projects
manticore-projects merged commit 2989cb1 into JSQLParser:master Aug 14, 2026
7 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@fudianchn
fudianchn deleted the fix/limit-subquery-row-count-2359 branch August 21, 2026 00:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

[BUG] JSQLParser Version 5.3: LIMIT with subquery fails: Was expecting: "BY"

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@fudianchn @manticore-projects
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.