Skip to content

Commit d3ed3c9

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents c4b7e7b + 2ea773a commit d3ed3c9

12 files changed

Lines changed: 240 additions & 30 deletions

src/ast/mod.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,7 +1423,7 @@ impl fmt::Display for AccessExpr {
14231423
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
14241424
pub struct LambdaFunction {
14251425
/// The parameters to the lambda function.
1426-
pub params: OneOrManyWithParens<Ident>,
1426+
pub params: OneOrManyWithParens<LambdaFunctionParameter>,
14271427
/// The body of the lambda function.
14281428
pub body: Box<Expr>,
14291429
/// The syntax style used to write the lambda function.
@@ -1448,6 +1448,27 @@ impl fmt::Display for LambdaFunction {
14481448
}
14491449
}
14501450

1451+
/// A parameter to a lambda function, optionally with a data type.
1452+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1453+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1454+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1455+
pub struct LambdaFunctionParameter {
1456+
/// The name of the parameter
1457+
pub name: Ident,
1458+
/// The optional data type of the parameter
1459+
/// [Snowflake Syntax](https://docs.snowflake.com/en/sql-reference/functions/filter#arguments)
1460+
pub data_type: Option<DataType>,
1461+
}
1462+
1463+
impl fmt::Display for LambdaFunctionParameter {
1464+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1465+
match &self.data_type {
1466+
Some(dt) => write!(f, "{} {}", self.name, dt),
1467+
None => write!(f, "{}", self.name),
1468+
}
1469+
}
1470+
}
1471+
14511472
/// The syntax style for a lambda function.
14521473
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Copy)]
14531474
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -6369,7 +6390,7 @@ impl Display for CascadeOption {
63696390
}
63706391
}
63716392

6372-
/// Transaction started with [ TRANSACTION | WORK ]
6393+
/// Transaction started with [ TRANSACTION | WORK | TRAN ]
63736394
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
63746395
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
63756396
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -6378,13 +6399,17 @@ pub enum BeginTransactionKind {
63786399
Transaction,
63796400
/// Alternate `WORK` keyword.
63806401
Work,
6402+
/// MSSQL shorthand `TRAN` keyword.
6403+
/// See <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/begin-transaction-transact-sql>
6404+
Tran,
63816405
}
63826406

63836407
impl Display for BeginTransactionKind {
63846408
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63856409
match self {
63866410
BeginTransactionKind::Transaction => write!(f, "TRANSACTION"),
63876411
BeginTransactionKind::Work => write!(f, "WORK"),
6412+
BeginTransactionKind::Tran => write!(f, "TRAN"),
63886413
}
63896414
}
63906415
}

src/dialect/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub use self::mysql::MySqlDialect;
4949
pub use self::oracle::OracleDialect;
5050
pub use self::postgresql::PostgreSqlDialect;
5151
pub use self::redshift::RedshiftSqlDialect;
52+
pub use self::snowflake::parse_snowflake_stage_name;
5253
pub use self::snowflake::SnowflakeDialect;
5354
pub use self::sqlite::SQLiteDialect;
5455

src/dialect/mssql.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,12 @@ impl Dialect for MsSqlDialect {
151151
let is_block = parser
152152
.maybe_parse(|p| {
153153
if p.parse_transaction_modifier().is_some()
154-
|| p.parse_one_of_keywords(&[Keyword::TRANSACTION, Keyword::WORK])
155-
.is_some()
154+
|| p.parse_one_of_keywords(&[
155+
Keyword::TRANSACTION,
156+
Keyword::WORK,
157+
Keyword::TRAN,
158+
])
159+
.is_some()
156160
|| matches!(p.peek_token_ref().token, Token::SemiColon | Token::EOF)
157161
{
158162
p.expected("statement", p.peek_token())

src/dialect/mysql.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,15 @@ impl Dialect for MySqlDialect {
102102
) -> Option<Result<crate::ast::Expr, ParserError>> {
103103
// Parse DIV as an operator
104104
if parser.parse_keyword(Keyword::DIV) {
105+
let left = Box::new(expr.clone());
106+
let right = Box::new(match parser.parse_expr() {
107+
Ok(expr) => expr,
108+
Err(e) => return Some(Err(e)),
109+
});
105110
Some(Ok(Expr::BinaryOp {
106-
left: Box::new(expr.clone()),
111+
left,
107112
op: BinaryOperator::MyIntegerDivide,
108-
right: Box::new(parser.parse_expr().unwrap()),
113+
right,
109114
}))
110115
} else {
111116
None

src/dialect/snowflake.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,11 @@ impl Dialect for SnowflakeDialect {
662662
fn supports_select_wildcard_rename(&self) -> bool {
663663
true
664664
}
665+
666+
/// See <https://docs.snowflake.com/en/user-guide/querying-semistructured#label-higher-order-functions>
667+
fn supports_lambda_functions(&self) -> bool {
668+
true
669+
}
665670
}
666671

667672
// Peeks ahead to identify tokens that are expected after
@@ -1225,7 +1230,7 @@ pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result<Ident, ParserE
12251230
parser.prev_token();
12261231
break;
12271232
}
1228-
Token::RParen => {
1233+
Token::LParen | Token::RParen => {
12291234
parser.prev_token();
12301235
break;
12311236
}
@@ -1243,6 +1248,8 @@ pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result<Ident, ParserE
12431248
Ok(Ident::new(ident))
12441249
}
12451250

1251+
/// Parses a Snowflake stage name, which may start with `@` for internal stages.
1252+
/// Examples: `@mystage`, `@namespace.stage`, `schema.table`
12461253
pub fn parse_snowflake_stage_name(parser: &mut Parser) -> Result<ObjectName, ParserError> {
12471254
match parser.next_token().token {
12481255
Token::AtSign => {

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,7 @@ define_keywords!(
10511051
TOTP,
10521052
TRACE,
10531053
TRAILING,
1054+
TRAN,
10541055
TRANSACTION,
10551056
TRANSIENT,
10561057
TRANSLATE,

src/parser/mod.rs

Lines changed: 112 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,11 @@ impl<'a> Parser<'a> {
12841284
// SQLite has single-quoted identifiers
12851285
id_parts.push(Ident::with_quote('\'', s))
12861286
}
1287+
Token::Placeholder(s) => {
1288+
// Snowflake uses $1, $2, etc. for positional column references
1289+
// in staged data queries like: SELECT t.$1 FROM @stage t
1290+
id_parts.push(Ident::new(s))
1291+
}
12871292
Token::Mul => {
12881293
return Ok(Expr::QualifiedWildcard(
12891294
ObjectName::from(id_parts),
@@ -1606,10 +1611,34 @@ impl<'a> Parser<'a> {
16061611
value: self.parse_introduced_string_expr()?.into(),
16071612
})
16081613
}
1614+
// An unreserved word (likely an identifier) is followed by an arrow,
1615+
// which indicates a lambda function with a single, untyped parameter.
1616+
// For example: `a -> a * 2`.
16091617
Token::Arrow if self.dialect.supports_lambda_functions() => {
16101618
self.expect_token(&Token::Arrow)?;
16111619
Ok(Expr::Lambda(LambdaFunction {
1612-
params: OneOrManyWithParens::One(w.to_ident(w_span)),
1620+
params: OneOrManyWithParens::One(LambdaFunctionParameter {
1621+
name: w.to_ident(w_span),
1622+
data_type: None,
1623+
}),
1624+
body: Box::new(self.parse_expr()?),
1625+
syntax: LambdaSyntax::Arrow,
1626+
}))
1627+
}
1628+
// An unreserved word (likely an identifier) that is followed by another word (likley a data type)
1629+
// which is then followed by an arrow, which indicates a lambda function with a single, typed parameter.
1630+
// For example: `a INT -> a * 2`.
1631+
Token::Word(_)
1632+
if self.dialect.supports_lambda_functions()
1633+
&& self.peek_nth_token_ref(1).token == Token::Arrow =>
1634+
{
1635+
let data_type = self.parse_data_type()?;
1636+
self.expect_token(&Token::Arrow)?;
1637+
Ok(Expr::Lambda(LambdaFunction {
1638+
params: OneOrManyWithParens::One(LambdaFunctionParameter {
1639+
name: w.to_ident(w_span),
1640+
data_type: Some(data_type),
1641+
}),
16131642
body: Box::new(self.parse_expr()?),
16141643
syntax: LambdaSyntax::Arrow,
16151644
}))
@@ -1922,6 +1951,13 @@ impl<'a> Parser<'a> {
19221951
chain.push(AccessExpr::Dot(expr));
19231952
self.advance_token(); // The consumed string
19241953
}
1954+
Token::Placeholder(s) => {
1955+
// Snowflake uses $1, $2, etc. for positional column references
1956+
// in staged data queries like: SELECT t.$1 FROM @stage t
1957+
let expr = Expr::Identifier(Ident::with_span(next_token.span, s));
1958+
chain.push(AccessExpr::Dot(expr));
1959+
self.advance_token(); // The consumed placeholder
1960+
}
19251961
// Fallback to parsing an arbitrary expression, but restrict to expression
19261962
// types that are valid after the dot operator. This ensures that e.g.
19271963
// `T.interval` is parsed as a compound identifier, not as an interval
@@ -2195,7 +2231,7 @@ impl<'a> Parser<'a> {
21952231
return Ok(None);
21962232
}
21972233
self.maybe_parse(|p| {
2198-
let params = p.parse_comma_separated(|p| p.parse_identifier())?;
2234+
let params = p.parse_comma_separated(|p| p.parse_lambda_function_parameter())?;
21992235
p.expect_token(&Token::RParen)?;
22002236
p.expect_token(&Token::Arrow)?;
22012237
let expr = p.parse_expr()?;
@@ -2207,7 +2243,7 @@ impl<'a> Parser<'a> {
22072243
})
22082244
}
22092245

2210-
/// Parses a lambda expression using the `LAMBDA` keyword syntax.
2246+
/// Parses a lambda expression following the `LAMBDA` keyword syntax.
22112247
///
22122248
/// Syntax: `LAMBDA <params> : <expr>`
22132249
///
@@ -2217,30 +2253,49 @@ impl<'a> Parser<'a> {
22172253
///
22182254
/// See <https://duckdb.org/docs/stable/sql/functions/lambda>
22192255
fn parse_lambda_expr(&mut self) -> Result<Expr, ParserError> {
2256+
// Parse the parameters: either a single identifier or comma-separated identifiers
2257+
let params = self.parse_lambda_function_parameters()?;
2258+
// Expect the colon separator
2259+
self.expect_token(&Token::Colon)?;
2260+
// Parse the body expression
2261+
let body = self.parse_expr()?;
2262+
Ok(Expr::Lambda(LambdaFunction {
2263+
params,
2264+
body: Box::new(body),
2265+
syntax: LambdaSyntax::LambdaKeyword,
2266+
}))
2267+
}
2268+
2269+
/// Parses the parameters of a lambda function with optional typing.
2270+
fn parse_lambda_function_parameters(
2271+
&mut self,
2272+
) -> Result<OneOrManyWithParens<LambdaFunctionParameter>, ParserError> {
22202273
// Parse the parameters: either a single identifier or comma-separated identifiers
22212274
let params = if self.consume_token(&Token::LParen) {
22222275
// Parenthesized parameters: (x, y)
2223-
let params = self.parse_comma_separated(|p| p.parse_identifier())?;
2276+
let params = self.parse_comma_separated(|p| p.parse_lambda_function_parameter())?;
22242277
self.expect_token(&Token::RParen)?;
22252278
OneOrManyWithParens::Many(params)
22262279
} else {
22272280
// Unparenthesized parameters: x or x, y
2228-
let params = self.parse_comma_separated(|p| p.parse_identifier())?;
2281+
let params = self.parse_comma_separated(|p| p.parse_lambda_function_parameter())?;
22292282
if params.len() == 1 {
22302283
OneOrManyWithParens::One(params.into_iter().next().unwrap())
22312284
} else {
22322285
OneOrManyWithParens::Many(params)
22332286
}
22342287
};
2235-
// Expect the colon separator
2236-
self.expect_token(&Token::Colon)?;
2237-
// Parse the body expression
2238-
let body = self.parse_expr()?;
2239-
Ok(Expr::Lambda(LambdaFunction {
2240-
params,
2241-
body: Box::new(body),
2242-
syntax: LambdaSyntax::LambdaKeyword,
2243-
}))
2288+
Ok(params)
2289+
}
2290+
2291+
/// Parses a single parameter of a lambda function, with optional typing.
2292+
fn parse_lambda_function_parameter(&mut self) -> Result<LambdaFunctionParameter, ParserError> {
2293+
let name = self.parse_identifier()?;
2294+
let data_type = match self.peek_token().token {
2295+
Token::Word(_) => self.maybe_parse(|p| p.parse_data_type())?,
2296+
_ => None,
2297+
};
2298+
Ok(LambdaFunctionParameter { name, data_type })
22442299
}
22452300

22462301
/// Tries to parse the body of an [ODBC escaping sequence]
@@ -15392,6 +15447,9 @@ impl<'a> Parser<'a> {
1539215447
&& self.peek_keyword_with_tokens(Keyword::SEMANTIC_VIEW, &[Token::LParen])
1539315448
{
1539415449
self.parse_semantic_view_table_factor()
15450+
} else if self.peek_token_ref().token == Token::AtSign {
15451+
// Stage reference: @mystage or @namespace.stage (e.g. Snowflake)
15452+
self.parse_snowflake_stage_table_factor()
1539515453
} else {
1539615454
let name = self.parse_object_name(true)?;
1539715455

@@ -15488,6 +15546,37 @@ impl<'a> Parser<'a> {
1548815546
}
1548915547
}
1549015548

15549+
/// Parse a Snowflake stage reference as a table factor.
15550+
/// Handles syntax like: `@mystage1 (file_format => 'myformat', pattern => '...')`
15551+
///
15552+
/// See: <https://docs.snowflake.com/en/user-guide/querying-stage>
15553+
fn parse_snowflake_stage_table_factor(&mut self) -> Result<TableFactor, ParserError> {
15554+
// Parse the stage name starting with @
15555+
let name = crate::dialect::parse_snowflake_stage_name(self)?;
15556+
15557+
// Parse optional stage options like (file_format => 'myformat', pattern => '...')
15558+
let args = if self.consume_token(&Token::LParen) {
15559+
Some(self.parse_table_function_args()?)
15560+
} else {
15561+
None
15562+
};
15563+
15564+
let alias = self.maybe_parse_table_alias()?;
15565+
15566+
Ok(TableFactor::Table {
15567+
name,
15568+
alias,
15569+
args,
15570+
with_hints: vec![],
15571+
version: None,
15572+
partitions: vec![],
15573+
with_ordinality: false,
15574+
json_path: None,
15575+
sample: None,
15576+
index_hints: vec![],
15577+
})
15578+
}
15579+
1549115580
fn maybe_parse_table_sample(&mut self) -> Result<Option<Box<TableSample>>, ParserError> {
1549215581
let modifier = if self.parse_keyword(Keyword::TABLESAMPLE) {
1549315582
TableSampleModifier::TableSample
@@ -18114,11 +18203,14 @@ impl<'a> Parser<'a> {
1811418203
/// Parse a 'BEGIN' statement
1811518204
pub fn parse_begin(&mut self) -> Result<Statement, ParserError> {
1811618205
let modifier = self.parse_transaction_modifier();
18117-
let transaction = match self.parse_one_of_keywords(&[Keyword::TRANSACTION, Keyword::WORK]) {
18118-
Some(Keyword::TRANSACTION) => Some(BeginTransactionKind::Transaction),
18119-
Some(Keyword::WORK) => Some(BeginTransactionKind::Work),
18120-
_ => None,
18121-
};
18206+
let transaction =
18207+
match self.parse_one_of_keywords(&[Keyword::TRANSACTION, Keyword::WORK, Keyword::TRAN])
18208+
{
18209+
Some(Keyword::TRANSACTION) => Some(BeginTransactionKind::Transaction),
18210+
Some(Keyword::WORK) => Some(BeginTransactionKind::Work),
18211+
Some(Keyword::TRAN) => Some(BeginTransactionKind::Tran),
18212+
_ => None,
18213+
};
1812218214
Ok(Statement::StartTransaction {
1812318215
modes: self.parse_transaction_modes()?,
1812418216
begin: true,
@@ -18252,7 +18344,7 @@ impl<'a> Parser<'a> {
1825218344

1825318345
/// Parse an optional `AND [NO] CHAIN` clause for `COMMIT` and `ROLLBACK` statements
1825418346
pub fn parse_commit_rollback_chain(&mut self) -> Result<bool, ParserError> {
18255-
let _ = self.parse_one_of_keywords(&[Keyword::TRANSACTION, Keyword::WORK]);
18347+
let _ = self.parse_one_of_keywords(&[Keyword::TRANSACTION, Keyword::WORK, Keyword::TRAN]);
1825618348
if self.parse_keyword(Keyword::AND) {
1825718349
let chain = !self.parse_keyword(Keyword::NO);
1825818350
self.expect_keyword_is(Keyword::CHAIN)?;

tests/sqlparser_common.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15937,7 +15937,16 @@ fn test_lambdas() {
1593715937
]
1593815938
),
1593915939
Expr::Lambda(LambdaFunction {
15940-
params: OneOrManyWithParens::Many(vec![Ident::new("p1"), Ident::new("p2")]),
15940+
params: OneOrManyWithParens::Many(vec![
15941+
LambdaFunctionParameter {
15942+
name: Ident::new("p1"),
15943+
data_type: None
15944+
},
15945+
LambdaFunctionParameter {
15946+
name: Ident::new("p2"),
15947+
data_type: None
15948+
}
15949+
]),
1594115950
body: Box::new(Expr::Case {
1594215951
case_token: AttachedToken::empty(),
1594315952
end_token: AttachedToken::empty(),
@@ -15982,6 +15991,12 @@ fn test_lambdas() {
1598215991
"map_zip_with(map(1, 'a', 2, 'b'), map(1, 'x', 2, 'y'), (k, v1, v2) -> concat(v1, v2))",
1598315992
);
1598415993
dialects.verified_expr("transform(array(1, 2, 3), x -> x + 1)");
15994+
15995+
// Ensure all lambda variants are parsed correctly
15996+
dialects.verified_expr("a -> a * 2"); // Single parameter without type
15997+
dialects.verified_expr("a INT -> a * 2"); // Single parameter with type
15998+
dialects.verified_expr("(a, b) -> a * b"); // Multiple parameters without types
15999+
dialects.verified_expr("(a INT, b FLOAT) -> a * b"); // Multiple parameters with types
1598516000
}
1598616001

1598716002
#[test]

0 commit comments

Comments
 (0)