GeezerVison is showing github.com — feat: support ClickHouse WITH expression aliases including lambdas by fudianchn · Pull Request #2644 · JSQLParser/JSqlParser
Conversation
fudianchn
commented
Sep 17, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
AI disclosure: this change was prepared with AI coding agents.
What
WITH items accept ClickHouse's WITH <expression> AS <identifier> next to classic CTEs and WITH FUNCTION: scalar, column, and compound expressions, the three lambda shapes ClickHouse accepts (x -> e, (x, y) -> e, (x -> e)), and the reversed-CTE reading (SELECT ...) AS name. Parsing reuses the existing LambdaExpression grammar; the whole-lambda-in-parentheses form is modeled as a ParenthesedExpressionList, matching how (x -> e) already parses in select-item position. WithItem exposes getExpression().
Why
The query from #2632 fails at the first WITH item on master, and with it every documented WITH <expr> AS name form, including the lambda examples in the ClickHouse documentation.
How
WithItem() gains a third alternative WithItemExpression(), following the WithFunctionDeclaration precedent (Fix[2306] - Adds support for Trino UDF #2307). Lambda bodies reuse the existing LambdaExpression production.
isWithExpressionAliasAhead() distinguishes expression aliases from the CTE shape name [(cols)] AS [NOT] MATERIALIZED (statement). It walks the optional bracket group through the token chain until the matching closing bracket or EOF, and aborts on parser interruption. There is no token-count limit. Qualified names go directly to the expression branch because a CTE name is a single identifier token.
isImplicitCastAhead() no longer treats every DATA_TYPE / DATA_TYPE(N) as a typed-literal prefix: it starts one only when a trailing literal completes it, otherwise the tokens parse as an identifier or function call. INT '5', DECIMAL(10,2) '1.5', DOUBLE PRECISION '1' keep their cast reading. This second layer is needed for the issue's own SELECT.
- The expression branch is wired through deparser (DmlDeParserSupport), TablesNamesFinder, SelectValidator, StatementFeatureVisitor, and the generic
SelectVisitorAdapter traversal (subquery aliases stay on the select path, other expressions go to the expression visitor). SEARCH/CYCLE suffixes accepted by the tolerant grammar serialize on the expression branch too.
Root cause
Two layers. First, WithItem() only had the CTE and WITH FUNCTION forms; the whole WITH <expr> AS <ident> family was missing (verified against docker ClickHouse server 26.8.2.7: 16 shapes legal, including the undocumented bare x -> x*2 and chained total * 2 AS doubled). Second, isImplicitCastAhead() classified every DATA_TYPE head as a typed-literal prefix, sending SELECT double(5), SELECT number FROM t, and sum(number) into a branch that requires a following literal.
Testing
- Red to green on master
dec8f5d: the issue query and SELECT double(5) fail there and pass with this change; classic CTE and typed-literal guards remain unchanged. Further guards cover nested WITH, INSERT, mixed expression aliases and CTEs, MATERIALIZED and invalid input.
- The review follow-up adds 13 regression cases for bracket groups at 4095, 4096 and 4097 tokens and beyond, large CTE column lists, nested calls, qualified function names, incomplete input and cancellation. Over-limit expressions and cancellation checks fail on the reviewed revision
06f1556 and pass with the follow-up. A mutation that always chooses the expression branch is rejected by the CTE guard.
- JDK 17: full
mvn clean verify (7312 tests, 25 skipped) and gradlew check (7330 tests, 25 skipped) passed with the scanner fix. After incorporating the separate visitor-assertion follow-up, all 64 ClickHouse tests passed again with both build tools.
- Project JMH
JSQLParserBenchmark.parseSQLStatements, standard 54-statement corpus, version=latest, one thread, 3 forks, 2 x 5-second warmups and 5 x 1-second measurements per fork: master/fixed/master measured 78.1 ± 25.6 / 57.1 ± 3.4 / 55.6 ± 2.2 ms/op (99.9% confidence intervals, 15 samples each). The intervals overlap; this benchmark shows no clear regression. Both versions produce the same serialized statements.
- The same benchmark and settings on a single 2047-argument WITH function (4095 tokens in its bracket group, accepted by both versions) measured 111.3 ± 25.6 ms/op at
06f1556 and 12.3 ± 2.8 ms/op with the fix. The parsed output matches. This improvement is specific to that long-expression input.
Behavior notes
- Tolerated shapes that now parse where master errored, each deparsing and re-parsing stably:
WITH (a, b) AS c, WITH t(a, b) AS u, WITH ? AS p, WITH TRUE AS flag, WITH int(5) AS f, WITH number AS n, WITH (x, y) -> x + y AS m.
- In an alias position, PG's
data -> 'x' reads as a lambda; master rejected both readings there. ->>/#> are unaffected.
- Not implemented: ClickHouse's trailing comma after the last WITH item. Single-parameter lambdas deparse without their parameter parentheses; the existing
LambdaExpression.toString() convention, shared with function-argument lambdas.
Verification of the original issue
On master dec8f5d, WITH (x -> x * 2) AS double SELECT double(5) fails to parse. With this change the WITH item carries the lambda (ParenthesedExpressionList wrapping a LambdaExpression) with alias double, the select item parses as the function call double(5), and the statement round-trips. All ClickHouse syntax evidence comes from docker clickhouse-server 26.8.2.7; no claim is made about running the reporter's JSqlParser version.
Fixes #2632
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
…mbdas
WITH items gained ClickHouse's WITH <expression> AS <identifier> form next
to CTEs and WITH FUNCTION, reusing the existing LambdaExpression grammar for
the x -> e, (params) -> e and (x -> e) shapes, ParenthesedSelect for
(SELECT ...) AS name, and a bounded token scan to tell expression aliases
from the CTE shape name [(cols)] AS [NOT] MATERIALIZED (statement).
The generic SelectVisitorAdapter traversal reaches the expression body of
such items (subquery aliases stay on the select path, other expressions go
to the expression visitor), and the SEARCH/CYCLE suffix accepted by the
tolerant grammar serializes on the expression branch instead of being
dropped.
DATA_TYPE and DATA_TYPE(N) now start an implicit typed literal only when a
trailing literal completes it, so keyword-named columns and function calls
(SELECT number, double(5)) parse instead of failing.
Fixes JSQLParser#2632
Signed-off-by: 付典 <[email protected]>
|
|
private int skipBalancedBracketGroup(int i) {
|
|
|
int depth = 0;
|
|
|
int guard = 0;
|
|
|
while (guard++ < 4096) {
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does this 4096 boundary come from? Looks rather arbitrary to me. What if there are more characters inside that bracket?
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 4096 limit was arbitrary and could reject a valid WITH expression. 5fd7241 removes it and walks the token chain until the matching closing bracket or EOF, stopping on cancellation. Qualified names go directly to the expression branch, so the separate 64 limit is gone too.
Regression tests cover 4095/4096/4097 tokens and beyond, nested calls, large CTE column lists, incomplete input and cancellation.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
manticore-projects reacted with thumbs up emoji
The ClickHouse visitor-adapter test declared a list that was never
filled or asserted, and its WithItem override only called super, so the
claimed expression-visitor hand-off was not actually verified (Codacy:
unused local variable). Pass a collecting ExpressionVisitorAdapter to
the SelectVisitorAdapter and assert the lambda text it receives.
Signed-off-by: 付典 <[email protected]>
Uh oh!
There was an error while loading. Please reload this page.
|
Thank you! Although we are reaching the point where even I am not certain anymore, what JSQLParser supports (or not). :-D
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
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.
|