Skip to content

Commit bd7f70e

Browse files
authored
MSSQL: prevent statement-starting keywords from being consumed as implicit aliases (#2233)
1 parent e87241a commit bd7f70e

File tree

2 files changed

+104
-5
lines changed

2 files changed

+104
-5
lines changed

src/dialect/mssql.rs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,64 @@ impl Dialect for MsSqlDialect {
129129

130130
fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
131131
match kw {
132-
// List of keywords that cannot be used as select item aliases in MSSQL
133-
// regardless of whether the alias is explicit or implicit
134-
Keyword::IF | Keyword::ELSE => false,
132+
// List of keywords that cannot be used as select item (column) aliases in MSSQL
133+
// regardless of whether the alias is explicit or implicit.
134+
//
135+
// These are T-SQL statement-starting keywords; allowing them as implicit aliases
136+
// causes the parser to consume the keyword as an alias for the previous expression,
137+
// then fail on the token that follows (e.g. `TABLE`, `@var`, `sp_name`, …).
138+
Keyword::IF
139+
| Keyword::ELSE
140+
| Keyword::DECLARE
141+
| Keyword::EXEC
142+
| Keyword::EXECUTE
143+
| Keyword::INSERT
144+
| Keyword::UPDATE
145+
| Keyword::DELETE
146+
| Keyword::DROP
147+
| Keyword::CREATE
148+
| Keyword::ALTER
149+
| Keyword::TRUNCATE
150+
| Keyword::PRINT
151+
| Keyword::WHILE
152+
| Keyword::RETURN
153+
| Keyword::THROW
154+
| Keyword::RAISERROR
155+
| Keyword::MERGE => false,
135156
_ => explicit || self.is_column_alias(kw, parser),
136157
}
137158
}
138159

139160
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
140161
match kw {
141162
// List of keywords that cannot be used as table aliases in MSSQL
142-
// regardless of whether the alias is explicit or implicit
143-
Keyword::IF | Keyword::ELSE => false,
163+
// regardless of whether the alias is explicit or implicit.
164+
//
165+
// These are T-SQL statement-starting keywords. Without blocking them here,
166+
// a bare `SELECT * FROM t` followed by a newline and one of these keywords
167+
// would cause the parser to consume the keyword as a table alias for `t`,
168+
// then fail on the token that follows (e.g. `@var`, `sp_name`, `TABLE`, …).
169+
//
170+
// `SET` is already covered by the global `RESERVED_FOR_TABLE_ALIAS` list;
171+
// the keywords below are MSSQL-specific additions.
172+
Keyword::IF
173+
| Keyword::ELSE
174+
| Keyword::DECLARE
175+
| Keyword::EXEC
176+
| Keyword::EXECUTE
177+
| Keyword::INSERT
178+
| Keyword::UPDATE
179+
| Keyword::DELETE
180+
| Keyword::DROP
181+
| Keyword::CREATE
182+
| Keyword::ALTER
183+
| Keyword::TRUNCATE
184+
| Keyword::PRINT
185+
| Keyword::WHILE
186+
| Keyword::RETURN
187+
| Keyword::THROW
188+
| Keyword::RAISERROR
189+
| Keyword::MERGE => false,
144190
_ => explicit || self.is_table_alias(kw, parser),
145191
}
146192
}

tests/sqlparser_mssql.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2730,3 +2730,56 @@ fn parse_mssql_tran_shorthand() {
27302730
// ROLLBACK TRAN normalizes to ROLLBACK (same as ROLLBACK TRANSACTION)
27312731
ms().one_statement_parses_to("ROLLBACK TRAN", "ROLLBACK");
27322732
}
2733+
2734+
#[test]
2735+
fn test_tsql_statement_keywords_not_implicit_aliases() {
2736+
// T-SQL statement-starting keywords must never be consumed as implicit
2737+
// aliases for a preceding SELECT item or table reference when using
2738+
// newline-delimited multi-statement scripts.
2739+
2740+
// Without the fix, the parser would consume a statement-starting keyword
2741+
// as an implicit alias for the preceding SELECT item or table reference,
2742+
// then fail on the next token. Verify parsing succeeds and each input
2743+
// produces the expected number of statements.
2744+
2745+
// Keywords that should not become implicit column aliases
2746+
let col_alias_cases: &[(&str, usize)] = &[
2747+
("select 1\ndeclare @x as int", 2),
2748+
("select 1\nexec sp_who", 2),
2749+
("select 1\ninsert into t values (1)", 2),
2750+
("select 1\nupdate t set col=1", 2),
2751+
("select 1\ndelete from t", 2),
2752+
("select 1\ndrop table t", 2),
2753+
("select 1\ncreate table t (id int)", 2),
2754+
("select 1\nalter table t add col int", 2),
2755+
("select 1\nreturn", 2),
2756+
];
2757+
for (sql, expected) in col_alias_cases {
2758+
let stmts = tsql()
2759+
.parse_sql_statements(sql)
2760+
.unwrap_or_else(|e| panic!("failed to parse {sql:?}: {e}"));
2761+
assert_eq!(
2762+
stmts.len(),
2763+
*expected,
2764+
"expected {expected} stmts for: {sql:?}"
2765+
);
2766+
}
2767+
2768+
// Keywords that should not become implicit table aliases
2769+
let tbl_alias_cases: &[(&str, usize)] = &[
2770+
("select * from t\ndeclare @x as int", 2),
2771+
("select * from t\ndrop table t", 2),
2772+
("select * from t\ncreate table u (id int)", 2),
2773+
("select * from t\nexec sp_who", 2),
2774+
];
2775+
for (sql, expected) in tbl_alias_cases {
2776+
let stmts = tsql()
2777+
.parse_sql_statements(sql)
2778+
.unwrap_or_else(|e| panic!("failed to parse {sql:?}: {e}"));
2779+
assert_eq!(
2780+
stmts.len(),
2781+
*expected,
2782+
"expected {expected} stmts for: {sql:?}"
2783+
);
2784+
}
2785+
}

0 commit comments

Comments
 (0)