Skip to content

Commit f663dcc

Browse files
yoavcloudfmguerreiro
authored andcommitted
MSSQL: Parse IF/ELSE without semicolon delimiters (apache#2128)
1 parent 531d0cf commit f663dcc

3 files changed

Lines changed: 62 additions & 12 deletions

File tree

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
@@ -11606,16 +11606,17 @@ impl<'a> Parser<'a> {
1160611606

1160711607
let next_token = self.next_token();
1160811608
match next_token.token {
11609-
// By default, if a word is located after the `AS` keyword we consider it an alias
11610-
// as long as it's not reserved.
11609+
// Accepts a keyword as an alias if the AS keyword explicitly indicate an alias or if the
11610+
// caller provided a list of reserved keywords and the keyword is not on that list.
1161111611
Token::Word(w)
11612-
if after_as || reserved_kwds.is_some_and(|x| !x.contains(&w.keyword)) =>
11612+
if reserved_kwds.is_some()
11613+
&& (after_as || reserved_kwds.is_some_and(|x| !x.contains(&w.keyword))) =>
1161311614
{
1161411615
Ok(Some(w.into_ident(next_token.span)))
1161511616
}
11616-
// This pattern allows for customizing the acceptance of words as aliases based on the caller's
11617-
// context, such as to what SQL element this word is a potential alias of (select item alias, table name
11618-
// alias, etc.) or dialect-specific logic that goes beyond a simple list of reserved keywords.
11617+
// Accepts a keyword as alias based on the caller's context, such as to what SQL element
11618+
// this word is a potential alias of using the validator call-back. This allows for
11619+
// dialect-specific logic.
1161911620
Token::Word(w) if validator(after_as, &w.keyword, self) => {
1162011621
Ok(Some(w.into_ident(next_token.span)))
1162111622
}

tests/sqlparser_mssql.rs

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

0 commit comments

Comments
 (0)