Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions src/dialect/mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down Expand Up @@ -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>> {
Expand Down
13 changes: 7 additions & 6 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
Expand Down
39 changes: 38 additions & 1 deletion tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
Loading