From 8ab9694c11c056ee78dcf3dfb801efdf5d6e1acf Mon Sep 17 00:00:00 2001 From: Ophir Lojkine Date: Mon, 5 Jan 2026 16:16:34 +0100 Subject: [PATCH 1/6] Add support for DuckDB lambda keyword syntax (`lambda x : expr`) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/ast/mod.rs | 27 ++++++++++++++++++++++- src/keywords.rs | 1 + src/parser/mod.rs | 41 +++++++++++++++++++++++++++++++++++ tests/sqlparser_common.rs | 3 ++- tests/sqlparser_databricks.rs | 6 +++-- tests/sqlparser_duckdb.rs | 19 ++++++++++++++++ 6 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 467678602..6be911fa4 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1293,14 +1293,39 @@ pub struct LambdaFunction { pub params: OneOrManyWithParens, /// The body of the lambda function. pub body: Box, + /// The syntax style used to write the lambda function. + pub syntax: LambdaSyntax, } impl fmt::Display for LambdaFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} -> {}", self.params, self.body) + match self.syntax { + LambdaSyntax::Arrow => write!(f, "{} -> {}", self.params, self.body), + LambdaSyntax::LambdaKeyword => { + // For lambda keyword syntax, display params without parentheses + // e.g., `lambda x, y : expr` not `lambda (x, y) : expr` + write!(f, "lambda ")?; + match &self.params { + OneOrManyWithParens::One(p) => write!(f, "{p}")?, + OneOrManyWithParens::Many(ps) => write!(f, "{}", display_comma_separated(ps))?, + }; + write!(f, " : {}", self.body) + } + } } } +/// The syntax style for a lambda function. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum LambdaSyntax { + /// Arrow syntax: `param -> expr` or `(param1, param2) -> expr` + Arrow, + /// Lambda keyword syntax: `lambda param : expr` or `lambda param1, param2 : expr` + LambdaKeyword, +} + /// Encapsulates the common pattern in SQL where either one unparenthesized item /// such as an identifier or expression is permitted, or multiple of the same /// item in a parenthesized list. For accessing items regardless of the form, diff --git a/src/keywords.rs b/src/keywords.rs index f06842ec6..58724ca06 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -547,6 +547,7 @@ define_keywords!( KEY_BLOCK_SIZE, KILL, LAG, + LAMBDA, LANGUAGE, LARGE, LAST, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f07e8919a..fd30c7901 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1516,6 +1516,9 @@ impl<'a> Parser<'a> { Keyword::MAP if *self.peek_token_ref() == Token::LBrace && self.dialect.support_map_literal_syntax() => { Ok(Some(self.parse_duckdb_map_literal()?)) } + Keyword::LAMBDA if self.dialect.supports_lambda_functions() => { + Ok(Some(self.parse_lambda_expr()?)) + } _ if self.dialect.supports_geometric_types() => match w.keyword { Keyword::CIRCLE => Ok(Some(self.parse_geometric_type(GeometricTypeKind::Circle)?)), Keyword::BOX => Ok(Some(self.parse_geometric_type(GeometricTypeKind::GeometricBox)?)), @@ -1568,6 +1571,7 @@ impl<'a> Parser<'a> { Ok(Expr::Lambda(LambdaFunction { params: OneOrManyWithParens::One(w.clone().into_ident(w_span)), body: Box::new(self.parse_expr()?), + syntax: LambdaSyntax::Arrow, })) } _ => Ok(Expr::Identifier(w.clone().into_ident(w_span))), @@ -2108,10 +2112,47 @@ impl<'a> Parser<'a> { Ok(Expr::Lambda(LambdaFunction { params: OneOrManyWithParens::Many(params), body: Box::new(expr), + syntax: LambdaSyntax::Arrow, })) }) } + /// Parses a lambda expression using the `LAMBDA` keyword syntax. + /// + /// Syntax: `LAMBDA : ` + /// + /// Examples: + /// - `LAMBDA x : x + 1` + /// - `LAMBDA x, i : x > i` + /// + /// See + fn parse_lambda_expr(&mut self) -> Result { + // Parse the parameters: either a single identifier or comma-separated identifiers + let params = if self.consume_token(&Token::LParen) { + // Parenthesized parameters: (x, y) + let params = self.parse_comma_separated(|p| p.parse_identifier())?; + self.expect_token(&Token::RParen)?; + OneOrManyWithParens::Many(params) + } else { + // Unparenthesized parameters: x or x, y + let params = self.parse_comma_separated(|p| p.parse_identifier())?; + if params.len() == 1 { + OneOrManyWithParens::One(params.into_iter().next().unwrap()) + } else { + OneOrManyWithParens::Many(params) + } + }; + // Expect the colon separator + self.expect_token(&Token::Colon)?; + // Parse the body expression + let body = self.parse_expr()?; + Ok(Expr::Lambda(LambdaFunction { + params, + body: Box::new(body), + syntax: LambdaSyntax::LambdaKeyword, + })) + } + /// Tries to parse the body of an [ODBC escaping sequence] /// i.e. without the enclosing braces /// Currently implemented: diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 9f549e4d0..b9ba860b1 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -15619,7 +15619,8 @@ fn test_lambdas() { }, ], else_result: Some(Box::new(Expr::value(number("1")))), - }) + }), + syntax: LambdaSyntax::Arrow, }) ] )), diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index 065e8f9e7..885040fd4 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -73,7 +73,8 @@ fn test_databricks_exists() { ), Expr::Lambda(LambdaFunction { params: OneOrManyWithParens::One(Ident::new("x")), - body: Box::new(Expr::IsNull(Box::new(Expr::Identifier(Ident::new("x"))))) + body: Box::new(Expr::IsNull(Box::new(Expr::Identifier(Ident::new("x"))))), + syntax: LambdaSyntax::Arrow, }) ] ), @@ -141,7 +142,8 @@ fn test_databricks_lambdas() { }, ], else_result: Some(Box::new(Expr::value(number("1")))) - }) + }), + syntax: LambdaSyntax::Arrow, }) ] )), diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 73a1afe26..289bc0af9 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -870,3 +870,22 @@ fn parse_extract_single_quotes() { let sql = "SELECT EXTRACT('month' FROM my_timestamp) FROM my_table"; duckdb().verified_stmt(sql); } + +#[test] +fn test_duckdb_lambda_function() { + // Test basic lambda with list_filter + let sql = "SELECT [3, 4, 5, 6].list_filter(lambda x : x > 4)"; + duckdb().verified_stmt(sql); + + // Test lambda with arrow syntax (also supported by DuckDB) + let sql_arrow = "SELECT list_filter([1, 2, 3], x -> x > 1)"; + duckdb().verified_stmt(sql_arrow); + + // Test lambda with multiple parameters (with index) + let sql_multi = "SELECT list_filter([1, 3, 1, 5], lambda x, i : x > i)"; + duckdb().verified_stmt(sql_multi); + + // Test lambda in list_transform + let sql_transform = "SELECT list_transform([1, 2, 3], lambda x : x * 2)"; + duckdb().verified_stmt(sql_transform); +} From c2b9b27a2b82b81db8d677d8640973e3a399596a Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Tue, 13 Jan 2026 14:02:29 +0100 Subject: [PATCH 2/6] Update tests/sqlparser_duckdb.rs Co-authored-by: Ifeanyi Ubah --- tests/sqlparser_duckdb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 289bc0af9..6030c252d 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -875,7 +875,7 @@ fn parse_extract_single_quotes() { fn test_duckdb_lambda_function() { // Test basic lambda with list_filter let sql = "SELECT [3, 4, 5, 6].list_filter(lambda x : x > 4)"; - duckdb().verified_stmt(sql); + duckdb_and_generic().verified_stmt(sql); // Test lambda with arrow syntax (also supported by DuckDB) let sql_arrow = "SELECT list_filter([1, 2, 3], x -> x > 1)"; From 974a77049f754e780352297044afb1df3bb14a2a Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 13 Jan 2026 14:08:21 +0100 Subject: [PATCH 3/6] add links to docs fixes https://github.com/apache/datafusion-sqlparser-rs/pull/2149#discussion_r2685990664 --- src/ast/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 6be911fa4..336d9165c 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1321,8 +1321,16 @@ impl fmt::Display for LambdaFunction { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum LambdaSyntax { /// Arrow syntax: `param -> expr` or `(param1, param2) -> expr` + /// + /// https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-lambda-functions + /// + /// Supported, but deprecated in DuckDB: + /// https://duckdb.org/docs/stable/sql/functions/lambda Arrow, /// Lambda keyword syntax: `lambda param : expr` or `lambda param1, param2 : expr` + /// + /// Recommended in DuckDB: + /// https://duckdb.org/docs/stable/sql/functions/lambda LambdaKeyword, } From 6588e646e6f69bbceb317619e02cedad4f11cc0d Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 13 Jan 2026 14:16:20 +0100 Subject: [PATCH 4/6] test: Restrict DuckDB lambda test to DuckDbDialect GenericDialect does not support lambda functions due to syntax conflicts with PostgreSQL JSON operators (specifically the '->' operator). This commit updates test_duckdb_lambda_function to verify lambda syntax only against DuckDbDialect, resolving a test failure where GenericDialect misparsed the lambda expression. --- tests/sqlparser_duckdb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 6030c252d..289bc0af9 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -875,7 +875,7 @@ fn parse_extract_single_quotes() { fn test_duckdb_lambda_function() { // Test basic lambda with list_filter let sql = "SELECT [3, 4, 5, 6].list_filter(lambda x : x > 4)"; - duckdb_and_generic().verified_stmt(sql); + duckdb().verified_stmt(sql); // Test lambda with arrow syntax (also supported by DuckDB) let sql_arrow = "SELECT list_filter([1, 2, 3], x -> x > 1)"; From 9e06d8921c5556814ea541b4ead39cfd08ef61d3 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 13 Jan 2026 14:21:14 +0100 Subject: [PATCH 5/6] add links to docs --- src/ast/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 336d9165c..7dda81821 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1322,15 +1322,15 @@ impl fmt::Display for LambdaFunction { pub enum LambdaSyntax { /// Arrow syntax: `param -> expr` or `(param1, param2) -> expr` /// - /// https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-lambda-functions + /// /// /// Supported, but deprecated in DuckDB: - /// https://duckdb.org/docs/stable/sql/functions/lambda + /// Arrow, /// Lambda keyword syntax: `lambda param : expr` or `lambda param1, param2 : expr` /// /// Recommended in DuckDB: - /// https://duckdb.org/docs/stable/sql/functions/lambda + /// LambdaKeyword, } From 7c27ea1ea5ea77194fb10bf55f6d11b36cff1d36 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 13 Jan 2026 14:30:11 +0100 Subject: [PATCH 6/6] add support for lambda functions to the generic dialect --- src/dialect/generic.rs | 4 ++++ tests/sqlparser_duckdb.rs | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index bbedbc059..880baf7b6 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -199,4 +199,8 @@ impl Dialect for GenericDialect { fn supports_quote_delimited_string(&self) -> bool { true } + + fn supports_lambda_functions(&self) -> bool { + true + } } diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 289bc0af9..a636e8f60 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -875,17 +875,17 @@ fn parse_extract_single_quotes() { fn test_duckdb_lambda_function() { // Test basic lambda with list_filter let sql = "SELECT [3, 4, 5, 6].list_filter(lambda x : x > 4)"; - duckdb().verified_stmt(sql); + duckdb_and_generic().verified_stmt(sql); // Test lambda with arrow syntax (also supported by DuckDB) let sql_arrow = "SELECT list_filter([1, 2, 3], x -> x > 1)"; - duckdb().verified_stmt(sql_arrow); + duckdb_and_generic().verified_stmt(sql_arrow); // Test lambda with multiple parameters (with index) let sql_multi = "SELECT list_filter([1, 3, 1, 5], lambda x, i : x > i)"; - duckdb().verified_stmt(sql_multi); + duckdb_and_generic().verified_stmt(sql_multi); // Test lambda in list_transform let sql_transform = "SELECT list_transform([1, 2, 3], lambda x : x * 2)"; - duckdb().verified_stmt(sql_transform); + duckdb_and_generic().verified_stmt(sql_transform); }