From 2ee78ef60a4c3fac548bb13d0edf80e619f4948e Mon Sep 17 00:00:00 2001 From: Yoav Cohen Date: Fri, 11 Oct 2024 08:47:11 +0200 Subject: [PATCH 1/6] Add support for parsing MsSql alias with equals --- src/parser/mod.rs | 39 +++++++++++++++++++++++++++++++++------ tests/sqlparser_mssql.rs | 11 +++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b4c0487b4..7aceb5e72 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11181,15 +11181,42 @@ impl<'a> Parser<'a> { self.peek_token().location ) } - expr => self - .parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS) - .map(|alias| match alias { - Some(alias) => SelectItem::ExprWithAlias { expr, alias }, - None => SelectItem::UnnamedExpr(expr), - }), + expr => { + if dialect_of!(self is MsSqlDialect) { + if let Some(select_item) = self.parse_mssql_alias_with_equal(&expr) { + return Ok(select_item); + } + } + self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS) + .map(|alias| match alias { + Some(alias) => SelectItem::ExprWithAlias { expr, alias }, + None => SelectItem::UnnamedExpr(expr), + }) + } } } + /// Parse a [`SelectItem`] based on an MsSql syntax that uses the equal sign + /// to denote an alias, for example: SELECT col_alias = col FROM tbl + /// + fn parse_mssql_alias_with_equal(&mut self, expr: &Expr) -> Option { + if let Expr::BinaryOp { + left, op, right, .. + } = expr + { + if op == &BinaryOperator::Eq { + if let Expr::Identifier(ref alias) = **left { + return Some(SelectItem::ExprWithAlias { + expr: *right.clone(), + alias: alias.clone(), + }); + } + } + } + + None + } + /// Parse an [`WildcardAdditionalOptions`] information for wildcard select items. /// /// If it is not possible to parse it, will return an option. diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 5a2ef9e87..7ebd6026a 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1024,6 +1024,17 @@ fn parse_create_table_with_identity_column() { } } +#[test] +fn test_alias_equal_expr() { + let sql = r#"SELECT some_alias = some_column FROM some_table"#; + let expected = r#"SELECT some_column AS some_alias FROM some_table"#; + let _ = ms().one_statement_parses_to(sql, expected); + + let sql = r#"SELECT some_alias = (a*b) FROM some_table"#; + let expected = r#"SELECT (a * b) AS some_alias FROM some_table"#; + let _ = ms().one_statement_parses_to(sql, expected); +} + fn ms() -> TestedDialects { TestedDialects { dialects: vec![Box::new(MsSqlDialect {})], From 1fedea7da55822243efaebebf2491d2f322adb43 Mon Sep 17 00:00:00 2001 From: Yoav Cohen Date: Mon, 14 Oct 2024 08:02:32 +0200 Subject: [PATCH 2/6] Try to inline the function based on code review feedback --- src/dialect/mod.rs | 5 +++++ src/dialect/mssql.rs | 4 ++++ src/parser/mod.rs | 47 +++++++++++++++++++++----------------------- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 744f5a8c8..01cceac36 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -561,6 +561,11 @@ pub trait Dialect: Debug + Any { fn supports_asc_desc_in_column_definition(&self) -> bool { false } + + /// For example: SELECT col_alias = col FROM tbl + fn supports_eq_alias_assigment(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index a9d296be3..cace372c0 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -49,4 +49,8 @@ impl Dialect for MsSqlDialect { fn supports_connect_by(&self) -> bool { true } + + fn supports_eq_alias_assigment(&self) -> bool { + true + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7aceb5e72..9e609e2d8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11182,11 +11182,29 @@ impl<'a> Parser<'a> { ) } expr => { - if dialect_of!(self is MsSqlDialect) { - if let Some(select_item) = self.parse_mssql_alias_with_equal(&expr) { - return Ok(select_item); + // Parse a [`SelectItem`] based on an [MsSql] syntax that uses the equal sign + // to denote an alias, for example: SELECT col_alias = col FROM tbl + // [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/queries/select-examples-transact-sql?view=sql-server-ver16#b-use-select-with-column-headings-and-calculations + let expr = if self.dialect.supports_eq_alias_assigment() { + if let Expr::BinaryOp { + ref left, + op: BinaryOperator::Eq, + ref right, + } = expr + { + if let Expr::Identifier(alias) = left.as_ref() { + return Ok(SelectItem::ExprWithAlias { + expr: *right.clone(), + alias: alias.clone(), + }); + } } - } + expr + } else { + expr + }; + + // Parse the common AS keyword for aliasing a column self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS) .map(|alias| match alias { Some(alias) => SelectItem::ExprWithAlias { expr, alias }, @@ -11196,27 +11214,6 @@ impl<'a> Parser<'a> { } } - /// Parse a [`SelectItem`] based on an MsSql syntax that uses the equal sign - /// to denote an alias, for example: SELECT col_alias = col FROM tbl - /// - fn parse_mssql_alias_with_equal(&mut self, expr: &Expr) -> Option { - if let Expr::BinaryOp { - left, op, right, .. - } = expr - { - if op == &BinaryOperator::Eq { - if let Expr::Identifier(ref alias) = **left { - return Some(SelectItem::ExprWithAlias { - expr: *right.clone(), - alias: alias.clone(), - }); - } - } - } - - None - } - /// Parse an [`WildcardAdditionalOptions`] information for wildcard select items. /// /// If it is not possible to parse it, will return an option. From 79a91d89b5b8358a88819e9d28b349f2f36aa9eb Mon Sep 17 00:00:00 2001 From: Yoav Cohen Date: Mon, 14 Oct 2024 22:19:15 +0200 Subject: [PATCH 3/6] Go back to helper function --- src/dialect/mod.rs | 8 ++++++- src/parser/mod.rs | 48 +++++++++++++++++++++------------------ tests/sqlparser_common.rs | 12 ++++++++++ tests/sqlparser_mssql.rs | 11 --------- 4 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 01cceac36..33e180877 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -562,7 +562,13 @@ pub trait Dialect: Debug + Any { false } - /// For example: SELECT col_alias = col FROM tbl + /// Returns true if this dialect supports treating the equals operator `=` within a [`SelectItem`] + /// as an alias assignment operator, rather than a boolean expression. + /// For example: the following statements are equivalent for such a dialect: + /// ```sql + /// SELECT col_alias = col FROM tbl; + /// SELECT col_alias AS col FROM tbl; + /// ``` fn supports_eq_alias_assigment(&self) -> bool { false } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9e609e2d8..1c18b8707 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11182,29 +11182,11 @@ impl<'a> Parser<'a> { ) } expr => { - // Parse a [`SelectItem`] based on an [MsSql] syntax that uses the equal sign - // to denote an alias, for example: SELECT col_alias = col FROM tbl - // [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/queries/select-examples-transact-sql?view=sql-server-ver16#b-use-select-with-column-headings-and-calculations - let expr = if self.dialect.supports_eq_alias_assigment() { - if let Expr::BinaryOp { - ref left, - op: BinaryOperator::Eq, - ref right, - } = expr - { - if let Expr::Identifier(alias) = left.as_ref() { - return Ok(SelectItem::ExprWithAlias { - expr: *right.clone(), - alias: alias.clone(), - }); - } + if self.dialect.supports_eq_alias_assigment() { + if let Some(select_item) = Self::maybe_unpack_alias_assignment(&expr) { + return Ok(select_item); } - expr - } else { - expr - }; - - // Parse the common AS keyword for aliasing a column + } self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS) .map(|alias| match alias { Some(alias) => SelectItem::ExprWithAlias { expr, alias }, @@ -12220,6 +12202,28 @@ impl<'a> Parser<'a> { } false } + + /// Parse a [`SelectItem`] based on an [MsSql] syntax that uses the equal sign + /// to denote an alias, for example: SELECT col_alias = col FROM tbl + /// [MsSql]: + fn maybe_unpack_alias_assignment(expr: &Expr) -> Option { + if let Expr::BinaryOp { + left, + op: BinaryOperator::Eq, + right, + .. + } = expr + { + if let Expr::Identifier(ref alias) = **left { + return Some(SelectItem::ExprWithAlias { + expr: *right.clone(), + alias: alias.clone(), + }); + } + } + + None + } } impl Word { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 55ab3ddc3..ff6612588 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -11432,3 +11432,15 @@ fn test_any_some_all_comparison() { verified_stmt("SELECT c1 FROM tbl WHERE c1 <> SOME(SELECT c2 FROM tbl)"); verified_stmt("SELECT 1 = ANY(WITH x AS (SELECT 1) SELECT * FROM x)"); } + +#[test] +fn test_alias_equal_expr() { + let dialects = all_dialects_where(|d| d.supports_eq_alias_assigment()); + let sql = r#"SELECT some_alias = some_column FROM some_table"#; + let expected = r#"SELECT some_column AS some_alias FROM some_table"#; + let _ = dialects.one_statement_parses_to(sql, expected); + + let sql = r#"SELECT some_alias = (a*b) FROM some_table"#; + let expected = r#"SELECT (a * b) AS some_alias FROM some_table"#; + let _ = dialects.one_statement_parses_to(sql, expected); +} diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 7ebd6026a..5a2ef9e87 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1024,17 +1024,6 @@ fn parse_create_table_with_identity_column() { } } -#[test] -fn test_alias_equal_expr() { - let sql = r#"SELECT some_alias = some_column FROM some_table"#; - let expected = r#"SELECT some_column AS some_alias FROM some_table"#; - let _ = ms().one_statement_parses_to(sql, expected); - - let sql = r#"SELECT some_alias = (a*b) FROM some_table"#; - let expected = r#"SELECT (a * b) AS some_alias FROM some_table"#; - let _ = ms().one_statement_parses_to(sql, expected); -} - fn ms() -> TestedDialects { TestedDialects { dialects: vec![Box::new(MsSqlDialect {})], From e9de85d426216d416843691c1c0930c22daef023 Mon Sep 17 00:00:00 2001 From: Yoav Cohen Date: Wed, 16 Oct 2024 22:12:17 +0200 Subject: [PATCH 4/6] Inline the function wihtout cloning --- src/parser/mod.rs | 56 +++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1c18b8707..842e85c12 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11181,18 +11181,30 @@ impl<'a> Parser<'a> { self.peek_token().location ) } - expr => { - if self.dialect.supports_eq_alias_assigment() { - if let Some(select_item) = Self::maybe_unpack_alias_assignment(&expr) { - return Ok(select_item); - } - } - self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS) - .map(|alias| match alias { - Some(alias) => SelectItem::ExprWithAlias { expr, alias }, - None => SelectItem::UnnamedExpr(expr), - }) + Expr::BinaryOp { + left, + op: BinaryOperator::Eq, + right, + } if self.dialect.supports_eq_alias_assigment() + && matches!(left.as_ref(), Expr::Identifier(_)) => + { + let Expr::Identifier(alias) = *left else { + return parser_err!( + "BUG: expected identifier expression as alias", + self.peek_token().location + ); + }; + Ok(SelectItem::ExprWithAlias { + expr: *right, + alias, + }) } + expr => self + .parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS) + .map(|alias| match alias { + Some(alias) => SelectItem::ExprWithAlias { expr, alias }, + None => SelectItem::UnnamedExpr(expr), + }), } } @@ -12202,28 +12214,6 @@ impl<'a> Parser<'a> { } false } - - /// Parse a [`SelectItem`] based on an [MsSql] syntax that uses the equal sign - /// to denote an alias, for example: SELECT col_alias = col FROM tbl - /// [MsSql]: - fn maybe_unpack_alias_assignment(expr: &Expr) -> Option { - if let Expr::BinaryOp { - left, - op: BinaryOperator::Eq, - right, - .. - } = expr - { - if let Expr::Identifier(ref alias) = **left { - return Some(SelectItem::ExprWithAlias { - expr: *right.clone(), - alias: alias.clone(), - }); - } - } - - None - } } impl Word { From 90960e95dd57dba7b3830233c14f57dbcab46d07 Mon Sep 17 00:00:00 2001 From: Yoav Cohen Date: Thu, 17 Oct 2024 18:49:41 +0200 Subject: [PATCH 5/6] Add test case for dialects that do not support the alias assignment by equals sign --- tests/sqlparser_common.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index ff6612588..55725eeca 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -11443,4 +11443,9 @@ fn test_alias_equal_expr() { let sql = r#"SELECT some_alias = (a*b) FROM some_table"#; let expected = r#"SELECT (a * b) AS some_alias FROM some_table"#; let _ = dialects.one_statement_parses_to(sql, expected); + + let dialects = all_dialects_where(|d| !d.supports_eq_alias_assigment()); + let sql = r#"SELECT x = (a * b) FROM some_table"#; + let expected = r#"SELECT x = (a * b) FROM some_table"#; + let _ = dialects.one_statement_parses_to(sql, expected); } From 5afda20c1ba67d01e1ed673d3e1ea1a4d0895f38 Mon Sep 17 00:00:00 2001 From: Yoav Cohen Date: Sun, 20 Oct 2024 18:37:57 +0200 Subject: [PATCH 6/6] Fix docs --- src/dialect/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 33e180877..871055685 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -562,7 +562,7 @@ pub trait Dialect: Debug + Any { false } - /// Returns true if this dialect supports treating the equals operator `=` within a [`SelectItem`] + /// Returns true if this dialect supports treating the equals operator `=` within a `SelectItem` /// as an alias assignment operator, rather than a boolean expression. /// For example: the following statements are equivalent for such a dialect: /// ```sql