diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 7e0f1a104..35a62ab76 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1415,14 +1415,47 @@ 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` + /// + /// + /// + /// Supported, but deprecated in DuckDB: + /// + Arrow, + /// Lambda keyword syntax: `lambda param : expr` or `lambda param1, param2 : expr` + /// + /// Recommended in DuckDB: + /// + 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 f3a0903a4..da57253d6 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 77207283c..964e4b388 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 8001611e0..64b653910 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 : ` + /// + /// 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 208a56e23..c7a1981e9 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 9a9a73fe6..9064c8dc3 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 4a2f29e15..80a15eb11 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); +}