This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 3880a933 Add support for DuckDB `LAMBDA` keyword syntax (#2149)
3880a933 is described below
commit 3880a933e4c2d1e639c1b8bcb48bea34c37801fd
Author: Ophir LOJKINE <[email protected]>
AuthorDate: Tue Jan 13 21:50:08 2026 +0100
Add support for DuckDB `LAMBDA` keyword syntax (#2149)
Co-authored-by: Claude Opus 4.5 <[email protected]>
Co-authored-by: Ifeanyi Ubah <[email protected]>
---
src/ast/mod.rs | 35 ++++++++++++++++++++++++++++++++++-
src/dialect/generic.rs | 4 ++++
src/keywords.rs | 1 +
src/parser/mod.rs | 41 +++++++++++++++++++++++++++++++++++++++++
tests/sqlparser_common.rs | 3 ++-
tests/sqlparser_databricks.rs | 6 ++++--
tests/sqlparser_duckdb.rs | 19 +++++++++++++++++++
7 files changed, 105 insertions(+), 4 deletions(-)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 7e0f1a10..35a62ab7 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -1415,14 +1415,47 @@ pub struct LambdaFunction {
pub params: OneOrManyWithParens<Ident>,
/// The body of the lambda function.
pub body: Box<Expr>,
+ /// 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`
+ ///
+ ///
<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,
+}
+
/// 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/dialect/generic.rs b/src/dialect/generic.rs
index f3a0903a..da57253d 100644
--- a/src/dialect/generic.rs
+++ b/src/dialect/generic.rs
@@ -203,4 +203,8 @@ impl Dialect for GenericDialect {
fn supports_quote_delimited_string(&self) -> bool {
true
}
+
+ fn supports_lambda_functions(&self) -> bool {
+ true
+ }
}
diff --git a/src/keywords.rs b/src/keywords.rs
index 77207283..964e4b38 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -555,6 +555,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 8001611e..64b65391 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -1548,6 +1548,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)?)),
@@ -1600,6 +1603,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))),
@@ -2141,10 +2145,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 <params> : <expr>`
+ ///
+ /// Examples:
+ /// - `LAMBDA x : x + 1`
+ /// - `LAMBDA x, i : x > i`
+ ///
+ /// See <https://duckdb.org/docs/stable/sql/functions/lambda>
+ fn parse_lambda_expr(&mut self) -> Result<Expr, ParserError> {
+ // 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 208a56e2..c7a1981e 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -15678,7 +15678,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 9a9a73fe..9064c8dc 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 4a2f29e1..80a15eb1 100644
--- a/tests/sqlparser_duckdb.rs
+++ b/tests/sqlparser_duckdb.rs
@@ -872,3 +872,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_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_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_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_and_generic().verified_stmt(sql_transform);
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]