MSSQL: Parse IF/ELSE without semicolon delimiters (#2128)
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled

This commit is contained in:
Yoav Cohen 2025-12-10 07:04:22 -05:00 committed by GitHub
parent 0b1e0c35d9
commit 1b842d3b6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 12 deletions

View file

@ -21,14 +21,12 @@ use crate::ast::{
GranteesType, IfStatement, Statement,
};
use crate::dialect::Dialect;
use crate::keywords::{self, Keyword};
use crate::keywords::Keyword;
use crate::parser::{Parser, ParserError};
use crate::tokenizer::Token;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[Keyword::IF, Keyword::ELSE];
/// A [`Dialect`] for [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/)
#[derive(Debug)]
pub struct MsSqlDialect {}
@ -128,8 +126,22 @@ impl Dialect for MsSqlDialect {
&[GranteesType::Public]
}
fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
!keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw) && !RESERVED_FOR_COLUMN_ALIAS.contains(kw)
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,
_ => 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,
_ => explicit || self.is_table_alias(kw, parser),
}
}
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {

View file

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

View file

@ -2501,8 +2501,45 @@ fn test_tsql_no_semicolon_delimiter() {
DECLARE @X AS NVARCHAR(MAX)='x'
DECLARE @Y AS NVARCHAR(MAX)='y'
"#;
let stmts = tsql().parse_sql_statements(sql).unwrap();
assert_eq!(stmts.len(), 2);
assert!(stmts.iter().all(|s| matches!(s, Statement::Declare { .. })));
let sql = r#"
SELECT col FROM tbl
IF x=1
SELECT 1
ELSE
SELECT 2
"#;
let stmts = tsql().parse_sql_statements(sql).unwrap();
assert_eq!(stmts.len(), 2);
assert!(matches!(&stmts[0], Statement::Query(_)));
assert!(matches!(&stmts[1], Statement::If(_)));
}
#[test]
fn test_sql_keywords_as_table_aliases() {
// Some keywords that should not be parsed as an alias implicitly or explicitly
let reserved_kws = vec!["IF", "ELSE"];
for kw in reserved_kws {
for explicit in &["", "AS "] {
assert!(tsql()
.parse_sql_statements(&format!("SELECT * FROM tbl {explicit}{kw}"))
.is_err());
}
}
}
#[test]
fn test_sql_keywords_as_column_aliases() {
// Some keywords that should not be parsed as an alias implicitly or explicitly
let reserved_kws = vec!["IF", "ELSE"];
for kw in reserved_kws {
for explicit in &["", "AS "] {
assert!(tsql()
.parse_sql_statements(&format!("SELECT col {explicit}{kw} FROM tbl"))
.is_err());
}
}
}