Separate the LAMBDA keyword syntax from the -> lambda syntax - #2458
Separate the LAMBDA keyword syntax from the -> lambda syntax#2458adriangb wants to merge 6 commits into
Conversation
| fn supports_lambda_keyword_syntax(&self) -> bool { | ||
| self.supports_lambda_functions() | ||
| } |
There was a problem hiding this comment.
One could argue for adding fn supports_lambda_arrow_syntax() as well, but I'd hold off until there is a concrete use case for enabling arrow syntax but not lambda syntax.
There was a problem hiding this comment.
Arrow-only is already expressible by overriding supports_lambda_keyword_syntax to false, so I believe there is no need.
| #[test] | ||
| fn test_lambda_keyword_syntax_on_postgres_derivative() { |
There was a problem hiding this comment.
I'm split between this (the real regression test I want) and another test using a MyDialect in sqlparser_custom_dialect.rs that enables the two flags. Open to input.
There was a problem hiding this comment.
Both seem worth keeping.
|
@LucaCappelletti94 could I ask you to take a look at this change? Thanks! |
LucaCappelletti94
left a comment
There was a problem hiding this comment.
Generally ok, just a missing Snowflake syntax test, and the missing documentation to lambda, and the wrong syntax in the example.
| /// lambda functions, for example: | ||
| /// | ||
| /// ```sql | ||
| /// SELECT transform(array(1, 2, 3), LAMBDA x : x + 1); -- returns [2,3,4] |
There was a problem hiding this comment.
I believe transform(array(...), LAMBDA ...) is the wrong spelling, if you meant the DuckDB syntax it would be more like:
| /// SELECT transform(array(1, 2, 3), LAMBDA x : x + 1); -- returns [2,3,4] | |
| /// SELECT list_transform([1, 2, 3], lambda x : x + 1); -- returns [2, 3, 4] |
| fn supports_lambda_keyword_syntax(&self) -> bool { | ||
| self.supports_lambda_functions() | ||
| } |
There was a problem hiding this comment.
Arrow-only is already expressible by overriding supports_lambda_keyword_syntax to false, so I believe there is no need.
| #[test] | ||
| fn test_lambda_keyword_syntax_on_postgres_derivative() { |
There was a problem hiding this comment.
Both seem worth keeping.
2d11f84 to
d994419
Compare
d994419 to
1d89c66
Compare
|
Thanks for the review @LucaCappelletti94! @iffyio or @yoavcloud could one of you take a look at this PR that @LucaCappelletti94 has already reviewed and approved? Thanks! |
iffyio
left a comment
There was a problem hiding this comment.
left a comment, its not clear to me what the PR is looking to solve for
| /// [`Self::supports_lambda_functions`], so dialects supporting the `->` | ||
| /// spelling accept the `LAMBDA` spelling too unless they say otherwise. | ||
| /// | ||
| /// See <https://duckdb.org/docs/stable/sql/functions/lambda> |
There was a problem hiding this comment.
Im not sure I understood the problem being solved for - the description mentions pg as an example, suggesting there is some ambiguous grammar in play but pg doesnt have lambda syntax to my knowledge?
is there an example syntax that issupported by a dialect and the parser doesnt cover?
There was a problem hiding this comment.
This is a fair point. I replied in #2458 (comment) but reflecting a bit more I think there's an even stronger framing. Your hesitation comes from PostgreSQL being a bad example. It has no lambda syntax, so the new flag
would be unreachable there and the motivation is then speculative. I've reframed the
description to focus on DuckDB and custom dialects.
For DuckDB this enables fixing a live bug rather than a hypothetical.
DuckDbDialect already sets supports_lambda_functions() == true, so DuckDB's own
documented JSON example misparses today:
// DuckDbDialect
"SELECT j -> 'field' FROM t" // => Expr::Lambda { params: [j], body: 'field' } ❌
"SELECT t.j -> 'field' FROM t" // => BinaryOp { op: Arrow } ✅A bare column becomes a lambda, a qualified one stays JSON access. Both print back as
the same SQL, which is why no round-trip test catches it.
DuckDB itself resolves -> from the function signature at bind time, which a parser
can't do — and that's exactly why they deprecated the arrow form in v1.3 in favor of
lambda x : x + 1, with v2.0 disabling it by default and SET lambda_syntax to
choose in between. Following that requires the two spellings to be separable, which is
all this PR does.
I've deliberately not flipped DuckDbDialect here: it fixes the case above but breaks
x -> x > 1 in the other direction, so it's worth its own PR. I did add an assertion
pinning the current arrow behavior because I found the whole suite stays green when
you flip it. This way if we do flip it we can verify the change in behavior against tests.
This PR also pplies to custom dialects wanting JSON accessors and lambdas at once, which
is what the derive_dialect! test now covers. This could be a postgres based dialect that wants to add support for lambda functions (our case, using derive_dialect!) or a completely custom dialect.
|
you cannot currently have a grammar that supports both postgres style json operators and lambda transforms because |
Exercises the capability the way a downstream crate would: derive a dialect from PostgreSqlDialect with `supports_lambda_keyword_syntax` overridden, then check that `LAMBDA x : x + 1` parses while `->` and `->>` keep parsing as JSON member access rather than lambda parameters. PostgreSqlDialect is used here only as a convenient base that already gives `->` its JSON meaning; the case being covered is any custom dialect that wants JSON accessors and lambda functions at once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Address review feedback: rather than parsing the `LAMBDA` spelling and the `->` operator as separate statements, parse one expression that uses both — a lambda whose body is a JSON access — which is the shape a PostgreSQL derivative actually cares about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All three changes are applied review suggestions from Luca Cappelletti. Co-Authored-By: Luca Cappelletti <7738570+LucaCappelletti94@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PostgreSQL was a poor example: it has no lambda functions, so the capability was unreachable there and the motivation read as speculative. The real case is DuckDB, which uses `->` for both JSON member access and the arrow lambda form and resolves the ambiguity from the function signature at bind time. A parser cannot, which is why DuckDB deprecated the arrow form in v1.3 (`SET lambda_syntax`) and disables it by default in v2.0. Expressing that requires the two spellings to be separable. Also pin the shape of DuckDB's `x -> x > 1` as a lambda. Both readings of `->` print identically, so the existing round-trip check passes under either one; without this assertion, changing `DuckDbDialect` to the keyword-only spelling leaves the whole suite green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1d89c66 to
7e7bfc7
Compare
-> as an operator to support LAMBDA syntax
Motivation
supports_lambda_functions()gates two different spellings of the same feature:x -> x + 1LAMBDAkeyword form,LAMBDA x : x + 1Because they share one flag, a dialect cannot have one without the other. That is
a problem for any dialect where
->already means something else.DuckDB
DuckDB uses
->for both JSON member access (j -> '$.family') and the arrowlambda form, and resolves the ambiguity from the function signature at bind time —
which a parser cannot do. DuckDB's own answer was to add the
lambdakeyword anddeprecate the arrow:
DuckDB exposes the choice as
SET lambda_syntax = 'ENABLE_SINGLE_ARROW' | 'DISABLE_SINGLE_ARROW'. Today this crate can only express the first; the two flagshere are what it takes to express the second.
The consequence is visible now.
DuckDbDialectsetssupports_lambda_functions() == true, so DuckDB's own documented JSON example misparses:SELECT j -> 'field' FROM tExpr::Lambda { params: [j], body: 'field' }SELECT t.j -> 'field' FROM tBinaryOp { op: Arrow }A bare column becomes a lambda; a qualified column stays JSON access. Both round-trip
to identical SQL, which is why no test catches it.
This PR does not change
DuckDbDialect. Flipping it to the keyword-only spellingfixes the table above but introduces the mirror-image misparse on
x -> x > 1, andthat tradeoff deserves its own discussion. This change makes the correct behaviour
expressible, which is the prerequisite either way.
Custom dialects
The same conflict is unavoidable for anyone building a custom dialect — derived from
PostgreSQL or not — that wants JSON accessors and lambda functions at once. Today,
enabling lambdas silently reinterprets every
->; there is no way to have both.Overriding only
supports_lambda_keyword_syntaxgives them lambdas with->leftalone. Unlike DuckDB there is no tradeoff here, since the keyword form is unambiguous.
Change
Adds
Dialect::supports_lambda_keyword_syntax(), which gates only theLAMBDAkeyword form and defaults to
supports_lambda_functions().No existing dialect changes behaviour. Dialects that support the arrow form keep both
spellings; dialects that support neither still get neither. A dialect that uses
->for something else can now override just the new method to get lambdas without
disturbing its operator.
Tests
tests/sqlparser_custom_dialect.rs:supports_lambda_keyword_syntaxparseslambda x : x + 1while
->stays aBinaryOperator::Arrowsupports_lambda_functionsstill accepts both spellings,pinning the defaulting behaviour
arrow spelling
tests/sqlparser_derive_dialect.rs:supports_lambda_keyword_syntaxparses a lambdawhose body is a JSON access,
lambda x : (x -> 'a')::INT + 1, asserting the outerexpression is a
LambdaSyntax::LambdaKeywordlambda and the inner->is stillBinaryOperator::Arrowtests/sqlparser_duckdb.rs:x -> x > 1as aLambdaSyntax::Arrowlambda. Both readings of->printidentically, so the pre-existing round-trip check passes under either one — without
this assertion, switching
DuckDbDialectto the keyword-only spelling leaves theentire suite green.
Full suite,
cargo fmt --check, andcargo clippy --all-targets --all-featuresallpass.