mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-12-23 11:12:51 +00:00
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
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:
parent
0b1e0c35d9
commit
1b842d3b6a
3 changed files with 62 additions and 12 deletions
|
|
@ -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>> {
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue