This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch 
gh-readonly-queue/main/pr-2149-ee3b6223e673b9c0f737d649a7f934de0186bc64
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

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]

Reply via email to