This is an automated email from the ASF dual-hosted git repository. github-bot pushed a commit to branch gh-readonly-queue/main/pr-2192-8e36e8ee85e8d24c3bd6948298a4675e341a1db3 in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 4b4a9d7411f7a855999069e6a0d10202cdcab7b9 Author: Yoav Cohen <[email protected]> AuthorDate: Fri Feb 13 09:39:31 2026 +0100 Snowflake: Lambda functions (#2192) --- src/ast/mod.rs | 23 +++++++++++++- src/dialect/snowflake.rs | 5 +++ src/parser/mod.rs | 71 ++++++++++++++++++++++++++++++++++--------- tests/sqlparser_common.rs | 17 ++++++++++- tests/sqlparser_databricks.rs | 16 ++++++++-- 5 files changed, 114 insertions(+), 18 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 601af1bd..4d8c536a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1423,7 +1423,7 @@ impl fmt::Display for AccessExpr { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct LambdaFunction { /// The parameters to the lambda function. - pub params: OneOrManyWithParens<Ident>, + pub params: OneOrManyWithParens<LambdaFunctionParameter>, /// The body of the lambda function. pub body: Box<Expr>, /// The syntax style used to write the lambda function. @@ -1448,6 +1448,27 @@ impl fmt::Display for LambdaFunction { } } +/// A parameter to a lambda function, optionally with a data type. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct LambdaFunctionParameter { + /// The name of the parameter + pub name: Ident, + /// The optional data type of the parameter + /// [Snowflake Syntax](https://docs.snowflake.com/en/sql-reference/functions/filter#arguments) + pub data_type: Option<DataType>, +} + +impl fmt::Display for LambdaFunctionParameter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.data_type { + Some(dt) => write!(f, "{} {}", self.name, dt), + None => write!(f, "{}", self.name), + } + } +} + /// The syntax style for a lambda function. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 14e4ad45..d6470916 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -662,6 +662,11 @@ impl Dialect for SnowflakeDialect { fn supports_select_wildcard_rename(&self) -> bool { true } + + /// See <https://docs.snowflake.com/en/user-guide/querying-semistructured#label-higher-order-functions> + fn supports_lambda_functions(&self) -> bool { + true + } } // Peeks ahead to identify tokens that are expected after diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7a2bda8a..7dc75815 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1606,10 +1606,34 @@ impl<'a> Parser<'a> { value: self.parse_introduced_string_expr()?.into(), }) } + // An unreserved word (likely an identifier) is followed by an arrow, + // which indicates a lambda function with a single, untyped parameter. + // For example: `a -> a * 2`. Token::Arrow if self.dialect.supports_lambda_functions() => { self.expect_token(&Token::Arrow)?; Ok(Expr::Lambda(LambdaFunction { - params: OneOrManyWithParens::One(w.to_ident(w_span)), + params: OneOrManyWithParens::One(LambdaFunctionParameter { + name: w.to_ident(w_span), + data_type: None, + }), + body: Box::new(self.parse_expr()?), + syntax: LambdaSyntax::Arrow, + })) + } + // An unreserved word (likely an identifier) that is followed by another word (likley a data type) + // which is then followed by an arrow, which indicates a lambda function with a single, typed parameter. + // For example: `a INT -> a * 2`. + Token::Word(_) + if self.dialect.supports_lambda_functions() + && self.peek_nth_token_ref(1).token == Token::Arrow => + { + let data_type = self.parse_data_type()?; + self.expect_token(&Token::Arrow)?; + Ok(Expr::Lambda(LambdaFunction { + params: OneOrManyWithParens::One(LambdaFunctionParameter { + name: w.to_ident(w_span), + data_type: Some(data_type), + }), body: Box::new(self.parse_expr()?), syntax: LambdaSyntax::Arrow, })) @@ -2195,7 +2219,7 @@ impl<'a> Parser<'a> { return Ok(None); } self.maybe_parse(|p| { - let params = p.parse_comma_separated(|p| p.parse_identifier())?; + let params = p.parse_comma_separated(|p| p.parse_lambda_function_parameter())?; p.expect_token(&Token::RParen)?; p.expect_token(&Token::Arrow)?; let expr = p.parse_expr()?; @@ -2207,7 +2231,7 @@ impl<'a> Parser<'a> { }) } - /// Parses a lambda expression using the `LAMBDA` keyword syntax. + /// Parses a lambda expression following the `LAMBDA` keyword syntax. /// /// Syntax: `LAMBDA <params> : <expr>` /// @@ -2217,30 +2241,49 @@ impl<'a> Parser<'a> { /// /// 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 = self.parse_lambda_function_parameters()?; + // 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, + })) + } + + /// Parses the parameters of a lambda function with optional typing. + fn parse_lambda_function_parameters( + &mut self, + ) -> Result<OneOrManyWithParens<LambdaFunctionParameter>, 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())?; + let params = self.parse_comma_separated(|p| p.parse_lambda_function_parameter())?; 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())?; + let params = self.parse_comma_separated(|p| p.parse_lambda_function_parameter())?; 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, - })) + Ok(params) + } + + /// Parses a single parameter of a lambda function, with optional typing. + fn parse_lambda_function_parameter(&mut self) -> Result<LambdaFunctionParameter, ParserError> { + let name = self.parse_identifier()?; + let data_type = match self.peek_token().token { + Token::Word(_) => self.maybe_parse(|p| p.parse_data_type())?, + _ => None, + }; + Ok(LambdaFunctionParameter { name, data_type }) } /// Tries to parse the body of an [ODBC escaping sequence] diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 899dba8d..5822153a 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -15925,7 +15925,16 @@ fn test_lambdas() { ] ), Expr::Lambda(LambdaFunction { - params: OneOrManyWithParens::Many(vec![Ident::new("p1"), Ident::new("p2")]), + params: OneOrManyWithParens::Many(vec![ + LambdaFunctionParameter { + name: Ident::new("p1"), + data_type: None + }, + LambdaFunctionParameter { + name: Ident::new("p2"), + data_type: None + } + ]), body: Box::new(Expr::Case { case_token: AttachedToken::empty(), end_token: AttachedToken::empty(), @@ -15970,6 +15979,12 @@ fn test_lambdas() { "map_zip_with(map(1, 'a', 2, 'b'), map(1, 'x', 2, 'y'), (k, v1, v2) -> concat(v1, v2))", ); dialects.verified_expr("transform(array(1, 2, 3), x -> x + 1)"); + + // Ensure all lambda variants are parsed correctly + dialects.verified_expr("a -> a * 2"); // Single parameter without type + dialects.verified_expr("a INT -> a * 2"); // Single parameter with type + dialects.verified_expr("(a, b) -> a * b"); // Multiple parameters without types + dialects.verified_expr("(a INT, b FLOAT) -> a * b"); // Multiple parameters with types } #[test] diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index 6a7534ad..24d06ef2 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -72,7 +72,10 @@ fn test_databricks_exists() { ] ), Expr::Lambda(LambdaFunction { - params: OneOrManyWithParens::One(Ident::new("x")), + params: OneOrManyWithParens::One(LambdaFunctionParameter { + name: Ident::new("x"), + data_type: None + }), body: Box::new(Expr::IsNull(Box::new(Expr::Identifier(Ident::new("x"))))), syntax: LambdaSyntax::Arrow, }) @@ -109,7 +112,16 @@ fn test_databricks_lambdas() { ] ), Expr::Lambda(LambdaFunction { - params: OneOrManyWithParens::Many(vec![Ident::new("p1"), Ident::new("p2")]), + params: OneOrManyWithParens::Many(vec![ + LambdaFunctionParameter { + name: Ident::new("p1"), + data_type: None + }, + LambdaFunctionParameter { + name: Ident::new("p2"), + data_type: None + } + ]), body: Box::new(Expr::Case { case_token: AttachedToken::empty(), end_token: AttachedToken::empty(), --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
