Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for parsing MsSql alias with equals #1467

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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;
/// ```

Something like this to illustrate what the flag implies Im thinking?

fn supports_eq_alias_assigment(&self) -> bool {
false
}
}

/// This represents the operators for which precedence must be defined
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ impl Dialect for MsSqlDialect {
fn supports_connect_by(&self) -> bool {
true
}

fn supports_eq_alias_assigment(&self) -> bool {
true
}
}
36 changes: 30 additions & 6 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11173,12 +11173,36 @@ 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 => {
// 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
Comment on lines +11177 to +11179
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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

We can add the mssql link as part of the trait impl for the dialect instead? Then here I think we would be able to skip the comment altogether since the self.dialect.supports_eq_alias_assignment() along with the documentation on the trait definition for the method would suffice. It'll let us keep the doc for the feature centralized

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 },
None => SelectItem::UnnamedExpr(expr),
})
Comment on lines +11180 to +11204
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 },
None => SelectItem::UnnamedExpr(expr),
})
let (expr, alias) = match expr {
Expr::BinaryOp {
left: Expr::Identifier(alias),
BinaryOperator::Eq,
right,
} if self.dialect.supports_eq_alias_assigment() => {
(expr, Some(alias))
}
expr => {
// Parse the common AS keyword for aliasing a column
self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS)?
}
};
Ok(alias
.map(|alias| SelectItem::ExprWithAlias{ expr, alias })
.unwrap_or_else(|| SelectItem::UnnamedExpr(expr)))

The current inline looks reasonable, I think its mostly rustfmt that making it look a like it has bit more code than it does, but I think we could simplify the logic a bit as above to help with that and readability/intent as well? hopefully?

}
}
}

Expand Down
11 changes: 11 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,17 @@ fn parse_create_table_with_identity_column() {
}
}

#[test]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry I forgot to mention this part earlier, the test looks reasonable but now with the method on the dialect, can we move the test to common.rs and write it in a similar style to this? that would allow any future dialects with the same support to be covered automatically by the test.

Also for this feature, can we include an assertion that for the dialects that don't support the flag, verifying that they indeed end up with an unnamed select item?

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 {})],
Expand Down