feat: support ClickHouse WITH expression aliases including lambdas - #2644
Merged
manticore-projects merged 3 commits intoSep 17, 2026
Conversation
…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]>
manticore-projects
requested changes
Sep 17, 2026
| private int skipBalancedBracketGroup(int i) { | ||
| int depth = 0; | ||
| int guard = 0; | ||
| while (guard++ < 4096) { |
Contributor
There was a problem hiding this comment.
Where does this 4096 boundary come from? Looks rather arbitrary to me. What if there are more characters inside that bracket?
Contributor
Author
There was a problem hiding this comment.
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.
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]>
Signed-off-by: 付典 <[email protected]>
fudianchn
marked this pull request as draft
September 17, 2026 03:40
fudianchn
marked this pull request as ready for review
September 17, 2026 03:42
Contributor
|
Thank you! Although we are reaching the point where even I am not certain anymore, what JSQLParser supports (or not). :-D |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
AI disclosure: this change was prepared with AI coding agents.
What
WITHitems accept ClickHouse'sWITH <expression> AS <identifier>next to classic CTEs andWITH 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 existingLambdaExpressiongrammar; the whole-lambda-in-parentheses form is modeled as aParenthesedExpressionList, matching how(x -> e)already parses in select-item position.WithItemexposesgetExpression().Why
The query from #2632 fails at the first WITH item on master, and with it every documented
WITH <expr> AS nameform, including the lambda examples in the ClickHouse documentation.How
WithItem()gains a third alternativeWithItemExpression(), following theWithFunctionDeclarationprecedent (Fix[2306] - Adds support for Trino UDF #2307). Lambda bodies reuse the existingLambdaExpressionproduction.isWithExpressionAliasAhead()distinguishes expression aliases from the CTE shapename [(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 everyDATA_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.SelectVisitorAdaptertraversal (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 wholeWITH <expr> AS <ident>family was missing (verified against docker ClickHouse server 26.8.2.7: 16 shapes legal, including the undocumented barex -> x*2and chainedtotal * 2 AS doubled). Second,isImplicitCastAhead()classified everyDATA_TYPEhead as a typed-literal prefix, sendingSELECT double(5),SELECT number FROM t, andsum(number)into a branch that requires a following literal.Testing
dec8f5d: the issue query andSELECT 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.06f1556and pass with the follow-up. A mutation that always chooses the expression branch is rejected by the CTE guard.mvn clean verify(7312 tests, 25 skipped) andgradlew 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.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.06f1556and 12.3 ± 2.8 ms/op with the fix. The parsed output matches. This improvement is specific to that long-expression input.Behavior notes
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.data -> 'x'reads as a lambda; master rejected both readings there.->>/#>are unaffected.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 aliasdouble, the select item parses as the function calldouble(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