From 4126a9b2e05cfd8833b122be1ce9f4a59b90f6d5 Mon Sep 17 00:00:00 2001 From: Yoa Bot Date: Mon, 23 Feb 2026 13:10:37 +0100 Subject: [PATCH] MSSQL: prevent statement-starting keywords from being consumed as implicit aliases In T-SQL multi-statement scripts delimited by newlines, the parser was incorrectly consuming statement-starting keywords (DECLARE, EXEC/EXECUTE, INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE, PRINT, WHILE, RETURN, THROW, RAISERROR, MERGE) as implicit aliases for the preceding SELECT item or table reference, then failing on the next token. Extended is_select_item_alias() and is_table_factor_alias() in MsSqlDialect to return false for all of these keywords, matching the existing treatment of IF and ELSE. Adds test_tsql_statement_keywords_not_implicit_aliases covering both column and table alias cases across the affected keyword set. --- src/dialect/mssql.rs | 56 ++++++++++++++++++++++++++++++++++++---- tests/sqlparser_mssql.rs | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index 42e05858f..8ad765dd3 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -129,9 +129,30 @@ 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), } } @@ -139,8 +160,33 @@ impl Dialect for MsSqlDialect { 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), } } diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index b5fd1e77e..6c8412a4a 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -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:?}" + ); + } +}