Skip to content
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
56 changes: 51 additions & 5 deletions src/dialect/mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,64 @@ impl Dialect for MsSqlDialect {

fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
match kw {
// List of keywords that cannot be used as select item aliases in MSSQL
// regardless of whether the alias is explicit or implicit
Keyword::IF | Keyword::ELSE => false,
// List of keywords that cannot be used as select item (column) aliases in MSSQL
// regardless of whether the alias is explicit or implicit.
//
// These are T-SQL statement-starting keywords; allowing them as implicit aliases
// causes the parser to consume the keyword as an alias for the previous expression,
// then fail on the token that follows (e.g. `TABLE`, `@var`, `sp_name`, …).
Keyword::IF
| Keyword::ELSE
| Keyword::DECLARE
| Keyword::EXEC
| Keyword::EXECUTE
| Keyword::INSERT
| Keyword::UPDATE
| Keyword::DELETE
| Keyword::DROP
| Keyword::CREATE
| Keyword::ALTER
| Keyword::TRUNCATE
| Keyword::PRINT
| Keyword::WHILE
| Keyword::RETURN
| Keyword::THROW
| Keyword::RAISERROR
| Keyword::MERGE => false,
_ => explicit || self.is_column_alias(kw, parser),
}
}

fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
match kw {
// List of keywords that cannot be used as table aliases in MSSQL
// regardless of whether the alias is explicit or implicit
Keyword::IF | Keyword::ELSE => false,
// regardless of whether the alias is explicit or implicit.
//
// These are T-SQL statement-starting keywords. Without blocking them here,
// a bare `SELECT * FROM t` followed by a newline and one of these keywords
// would cause the parser to consume the keyword as a table alias for `t`,
// then fail on the token that follows (e.g. `@var`, `sp_name`, `TABLE`, …).
//
// `SET` is already covered by the global `RESERVED_FOR_TABLE_ALIAS` list;
// the keywords below are MSSQL-specific additions.
Keyword::IF
| Keyword::ELSE
| Keyword::DECLARE
| Keyword::EXEC
| Keyword::EXECUTE
| Keyword::INSERT
| Keyword::UPDATE
| Keyword::DELETE
| Keyword::DROP
| Keyword::CREATE
| Keyword::ALTER
| Keyword::TRUNCATE
| Keyword::PRINT
| Keyword::WHILE
| Keyword::RETURN
| Keyword::THROW
| Keyword::RAISERROR
| Keyword::MERGE => false,
_ => explicit || self.is_table_alias(kw, parser),
}
}
Expand Down
53 changes: 53 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2730,3 +2730,56 @@ fn parse_mssql_tran_shorthand() {
// ROLLBACK TRAN normalizes to ROLLBACK (same as ROLLBACK TRANSACTION)
ms().one_statement_parses_to("ROLLBACK TRAN", "ROLLBACK");
}

#[test]
fn test_tsql_statement_keywords_not_implicit_aliases() {
// T-SQL statement-starting keywords must never be consumed as implicit
// aliases for a preceding SELECT item or table reference when using
// newline-delimited multi-statement scripts.

// Without the fix, the parser would consume a statement-starting keyword
// as an implicit alias for the preceding SELECT item or table reference,
// then fail on the next token. Verify parsing succeeds and each input
// produces the expected number of statements.

// Keywords that should not become implicit column aliases
let col_alias_cases: &[(&str, usize)] = &[
("select 1\ndeclare @x as int", 2),
("select 1\nexec sp_who", 2),
("select 1\ninsert into t values (1)", 2),
("select 1\nupdate t set col=1", 2),
("select 1\ndelete from t", 2),
("select 1\ndrop table t", 2),
("select 1\ncreate table t (id int)", 2),
("select 1\nalter table t add col int", 2),
("select 1\nreturn", 2),
];
for (sql, expected) in col_alias_cases {
let stmts = tsql()
.parse_sql_statements(sql)
.unwrap_or_else(|e| panic!("failed to parse {sql:?}: {e}"));
assert_eq!(
stmts.len(),
*expected,
"expected {expected} stmts for: {sql:?}"
);
}

// Keywords that should not become implicit table aliases
let tbl_alias_cases: &[(&str, usize)] = &[
("select * from t\ndeclare @x as int", 2),
("select * from t\ndrop table t", 2),
("select * from t\ncreate table u (id int)", 2),
("select * from t\nexec sp_who", 2),
];
for (sql, expected) in tbl_alias_cases {
let stmts = tsql()
.parse_sql_statements(sql)
.unwrap_or_else(|e| panic!("failed to parse {sql:?}: {e}"));
assert_eq!(
stmts.len(),
*expected,
"expected {expected} stmts for: {sql:?}"
);
}
}
Loading