Skip to content

Commit 1b842d3

Browse files
authored
MSSQL: Parse IF/ELSE without semicolon delimiters (#2128)
1 parent 0b1e0c3 commit 1b842d3

File tree

3 files changed

+62
-12
lines changed

3 files changed

+62
-12
lines changed

src/dialect/mssql.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@ use crate::ast::{
2121
GranteesType, IfStatement, Statement,
2222
};
2323
use crate::dialect::Dialect;
24-
use crate::keywords::{self, Keyword};
24+
use crate::keywords::Keyword;
2525
use crate::parser::{Parser, ParserError};
2626
use crate::tokenizer::Token;
2727
#[cfg(not(feature = "std"))]
2828
use alloc::{vec, vec::Vec};
2929

30-
const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[Keyword::IF, Keyword::ELSE];
31-
3230
/// A [`Dialect`] for [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/)
3331
#[derive(Debug)]
3432
pub struct MsSqlDialect {}
@@ -128,8 +126,22 @@ impl Dialect for MsSqlDialect {
128126
&[GranteesType::Public]
129127
}
130128

131-
fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
132-
!keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw) && !RESERVED_FOR_COLUMN_ALIAS.contains(kw)
129+
fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
130+
match kw {
131+
// List of keywords that cannot be used as select item aliases in MSSQL
132+
// regardless of whether the alias is explicit or implicit
133+
Keyword::IF | Keyword::ELSE => false,
134+
_ => explicit || self.is_column_alias(kw, parser),
135+
}
136+
}
137+
138+
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
139+
match kw {
140+
// List of keywords that cannot be used as table aliases in MSSQL
141+
// regardless of whether the alias is explicit or implicit
142+
Keyword::IF | Keyword::ELSE => false,
143+
_ => explicit || self.is_table_alias(kw, parser),
144+
}
133145
}
134146

135147
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {

src/parser/mod.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11503,16 +11503,17 @@ impl<'a> Parser<'a> {
1150311503

1150411504
let next_token = self.next_token();
1150511505
match next_token.token {
11506-
// By default, if a word is located after the `AS` keyword we consider it an alias
11507-
// as long as it's not reserved.
11506+
// Accepts a keyword as an alias if the AS keyword explicitly indicate an alias or if the
11507+
// caller provided a list of reserved keywords and the keyword is not on that list.
1150811508
Token::Word(w)
11509-
if after_as || reserved_kwds.is_some_and(|x| !x.contains(&w.keyword)) =>
11509+
if reserved_kwds.is_some()
11510+
&& (after_as || reserved_kwds.is_some_and(|x| !x.contains(&w.keyword))) =>
1151011511
{
1151111512
Ok(Some(w.into_ident(next_token.span)))
1151211513
}
11513-
// This pattern allows for customizing the acceptance of words as aliases based on the caller's
11514-
// context, such as to what SQL element this word is a potential alias of (select item alias, table name
11515-
// alias, etc.) or dialect-specific logic that goes beyond a simple list of reserved keywords.
11514+
// Accepts a keyword as alias based on the caller's context, such as to what SQL element
11515+
// this word is a potential alias of using the validator call-back. This allows for
11516+
// dialect-specific logic.
1151611517
Token::Word(w) if validator(after_as, &w.keyword, self) => {
1151711518
Ok(Some(w.into_ident(next_token.span)))
1151811519
}

tests/sqlparser_mssql.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2501,8 +2501,45 @@ fn test_tsql_no_semicolon_delimiter() {
25012501
DECLARE @X AS NVARCHAR(MAX)='x'
25022502
DECLARE @Y AS NVARCHAR(MAX)='y'
25032503
"#;
2504-
25052504
let stmts = tsql().parse_sql_statements(sql).unwrap();
25062505
assert_eq!(stmts.len(), 2);
25072506
assert!(stmts.iter().all(|s| matches!(s, Statement::Declare { .. })));
2507+
2508+
let sql = r#"
2509+
SELECT col FROM tbl
2510+
IF x=1
2511+
SELECT 1
2512+
ELSE
2513+
SELECT 2
2514+
"#;
2515+
let stmts = tsql().parse_sql_statements(sql).unwrap();
2516+
assert_eq!(stmts.len(), 2);
2517+
assert!(matches!(&stmts[0], Statement::Query(_)));
2518+
assert!(matches!(&stmts[1], Statement::If(_)));
2519+
}
2520+
2521+
#[test]
2522+
fn test_sql_keywords_as_table_aliases() {
2523+
// Some keywords that should not be parsed as an alias implicitly or explicitly
2524+
let reserved_kws = vec!["IF", "ELSE"];
2525+
for kw in reserved_kws {
2526+
for explicit in &["", "AS "] {
2527+
assert!(tsql()
2528+
.parse_sql_statements(&format!("SELECT * FROM tbl {explicit}{kw}"))
2529+
.is_err());
2530+
}
2531+
}
2532+
}
2533+
2534+
#[test]
2535+
fn test_sql_keywords_as_column_aliases() {
2536+
// Some keywords that should not be parsed as an alias implicitly or explicitly
2537+
let reserved_kws = vec!["IF", "ELSE"];
2538+
for kw in reserved_kws {
2539+
for explicit in &["", "AS "] {
2540+
assert!(tsql()
2541+
.parse_sql_statements(&format!("SELECT col {explicit}{kw} FROM tbl"))
2542+
.is_err());
2543+
}
2544+
}
25082545
}

0 commit comments

Comments
 (0)