mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-10 01:08:02 +00:00
298 lines
10 KiB
Rust
298 lines
10 KiB
Rust
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
use crate::ast::helpers::attached_token::AttachedToken;
|
|
use crate::ast::{
|
|
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, GranteesType,
|
|
IfStatement, Statement, TriggerObject,
|
|
};
|
|
use crate::dialect::Dialect;
|
|
use crate::keywords::{self, 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 {}
|
|
|
|
impl Dialect for MsSqlDialect {
|
|
fn is_delimited_identifier_start(&self, ch: char) -> bool {
|
|
ch == '"' || ch == '['
|
|
}
|
|
|
|
fn is_identifier_start(&self, ch: char) -> bool {
|
|
// See https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-2017#rules-for-regular-identifiers
|
|
ch.is_alphabetic() || ch == '_' || ch == '#' || ch == '@'
|
|
}
|
|
|
|
fn is_identifier_part(&self, ch: char) -> bool {
|
|
ch.is_alphabetic()
|
|
|| ch.is_ascii_digit()
|
|
|| ch == '@'
|
|
|| ch == '$'
|
|
|| ch == '#'
|
|
|| ch == '_'
|
|
}
|
|
|
|
fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
|
|
Some('[')
|
|
}
|
|
|
|
/// SQL Server has `CONVERT(type, value)` instead of `CONVERT(value, type)`
|
|
/// <https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16>
|
|
fn convert_type_before_value(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn supports_outer_join_operator(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn supports_connect_by(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn supports_eq_alias_assignment(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn supports_try_convert(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// In MSSQL, there is no boolean type, and `true` and `false` are valid column names
|
|
fn supports_boolean_literals(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn supports_named_fn_args_with_colon_operator(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn supports_named_fn_args_with_expr_name(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn supports_named_fn_args_with_rarrow_operator(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn supports_start_transaction_modifier(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn supports_end_transaction_modifier(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// See: <https://learn.microsoft.com/en-us/sql/t-sql/statements/set-statements-transact-sql>
|
|
fn supports_set_stmt_without_operator(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// See: <https://learn.microsoft.com/en-us/sql/relational-databases/tables/querying-data-in-a-system-versioned-temporal-table>
|
|
fn supports_timestamp_versioning(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// See <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/slash-star-comment-transact-sql?view=sql-server-ver16>
|
|
fn supports_nested_comments(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// See <https://learn.microsoft.com/en-us/sql/t-sql/queries/from-transact-sql>
|
|
fn supports_object_name_double_dot_notation(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// See <https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/server-level-roles>
|
|
fn get_reserved_grantees_types(&self) -> &[GranteesType] {
|
|
&[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 parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
|
if parser.peek_keyword(Keyword::IF) {
|
|
Some(self.parse_if_stmt(parser))
|
|
} else if parser.parse_keywords(&[Keyword::CREATE, Keyword::TRIGGER]) {
|
|
Some(self.parse_create_trigger(parser, false))
|
|
} else if parser.parse_keywords(&[
|
|
Keyword::CREATE,
|
|
Keyword::OR,
|
|
Keyword::ALTER,
|
|
Keyword::TRIGGER,
|
|
]) {
|
|
Some(self.parse_create_trigger(parser, true))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MsSqlDialect {
|
|
/// ```sql
|
|
/// IF boolean_expression
|
|
/// { sql_statement | statement_block }
|
|
/// [ ELSE
|
|
/// { sql_statement | statement_block } ]
|
|
/// ```
|
|
fn parse_if_stmt(&self, parser: &mut Parser) -> Result<Statement, ParserError> {
|
|
let if_token = parser.expect_keyword(Keyword::IF)?;
|
|
|
|
let condition = parser.parse_expr()?;
|
|
|
|
let if_block = if parser.peek_keyword(Keyword::BEGIN) {
|
|
let begin_token = parser.expect_keyword(Keyword::BEGIN)?;
|
|
let statements = self.parse_statement_list(parser, Some(Keyword::END))?;
|
|
let end_token = parser.expect_keyword(Keyword::END)?;
|
|
ConditionalStatementBlock {
|
|
start_token: AttachedToken(if_token),
|
|
condition: Some(condition),
|
|
then_token: None,
|
|
conditional_statements: ConditionalStatements::BeginEnd(BeginEndStatements {
|
|
begin_token: AttachedToken(begin_token),
|
|
statements,
|
|
end_token: AttachedToken(end_token),
|
|
}),
|
|
}
|
|
} else {
|
|
let stmt = parser.parse_statement()?;
|
|
ConditionalStatementBlock {
|
|
start_token: AttachedToken(if_token),
|
|
condition: Some(condition),
|
|
then_token: None,
|
|
conditional_statements: ConditionalStatements::Sequence {
|
|
statements: vec![stmt],
|
|
},
|
|
}
|
|
};
|
|
|
|
let mut prior_statement_ended_with_semi_colon = false;
|
|
while let Token::SemiColon = parser.peek_token_ref().token {
|
|
parser.advance_token();
|
|
prior_statement_ended_with_semi_colon = true;
|
|
}
|
|
|
|
let mut else_block = None;
|
|
if parser.peek_keyword(Keyword::ELSE) {
|
|
let else_token = parser.expect_keyword(Keyword::ELSE)?;
|
|
if parser.peek_keyword(Keyword::BEGIN) {
|
|
let begin_token = parser.expect_keyword(Keyword::BEGIN)?;
|
|
let statements = self.parse_statement_list(parser, Some(Keyword::END))?;
|
|
let end_token = parser.expect_keyword(Keyword::END)?;
|
|
else_block = Some(ConditionalStatementBlock {
|
|
start_token: AttachedToken(else_token),
|
|
condition: None,
|
|
then_token: None,
|
|
conditional_statements: ConditionalStatements::BeginEnd(BeginEndStatements {
|
|
begin_token: AttachedToken(begin_token),
|
|
statements,
|
|
end_token: AttachedToken(end_token),
|
|
}),
|
|
});
|
|
} else {
|
|
let stmt = parser.parse_statement()?;
|
|
else_block = Some(ConditionalStatementBlock {
|
|
start_token: AttachedToken(else_token),
|
|
condition: None,
|
|
then_token: None,
|
|
conditional_statements: ConditionalStatements::Sequence {
|
|
statements: vec![stmt],
|
|
},
|
|
});
|
|
}
|
|
} else if prior_statement_ended_with_semi_colon {
|
|
parser.prev_token();
|
|
}
|
|
|
|
Ok(Statement::If(IfStatement {
|
|
if_block,
|
|
else_block,
|
|
elseif_blocks: Vec::new(),
|
|
end_token: None,
|
|
}))
|
|
}
|
|
|
|
/// Parse `CREATE TRIGGER` for [MsSql]
|
|
///
|
|
/// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql
|
|
fn parse_create_trigger(
|
|
&self,
|
|
parser: &mut Parser,
|
|
or_alter: bool,
|
|
) -> Result<Statement, ParserError> {
|
|
let name = parser.parse_object_name(false)?;
|
|
parser.expect_keyword_is(Keyword::ON)?;
|
|
let table_name = parser.parse_object_name(false)?;
|
|
let period = parser.parse_trigger_period()?;
|
|
let events = parser.parse_comma_separated(Parser::parse_trigger_event)?;
|
|
|
|
parser.expect_keyword_is(Keyword::AS)?;
|
|
let statements = Some(parser.parse_conditional_statements(&[Keyword::END])?);
|
|
|
|
Ok(Statement::CreateTrigger {
|
|
or_alter,
|
|
or_replace: false,
|
|
is_constraint: false,
|
|
name,
|
|
period,
|
|
events,
|
|
table_name,
|
|
referenced_table_name: None,
|
|
referencing: Vec::new(),
|
|
trigger_object: TriggerObject::Statement,
|
|
include_each: false,
|
|
condition: None,
|
|
exec_body: None,
|
|
statements,
|
|
characteristics: None,
|
|
})
|
|
}
|
|
|
|
/// Parse a sequence of statements, optionally separated by semicolon.
|
|
///
|
|
/// Stops parsing when reaching EOF or the given keyword.
|
|
fn parse_statement_list(
|
|
&self,
|
|
parser: &mut Parser,
|
|
terminal_keyword: Option<Keyword>,
|
|
) -> Result<Vec<Statement>, ParserError> {
|
|
let mut stmts = Vec::new();
|
|
loop {
|
|
if let Token::EOF = parser.peek_token_ref().token {
|
|
break;
|
|
}
|
|
if let Some(term) = terminal_keyword {
|
|
if parser.peek_keyword(term) {
|
|
break;
|
|
}
|
|
}
|
|
stmts.push(parser.parse_statement()?);
|
|
while let Token::SemiColon = parser.peek_token_ref().token {
|
|
parser.advance_token();
|
|
}
|
|
}
|
|
Ok(stmts)
|
|
}
|
|
}
|