| // 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, CreateTrigger, |
| GranteesType, IfStatement, Statement, |
| }; |
| 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(IfStatement { |
| if_block, |
| else_block, |
| elseif_blocks: Vec::new(), |
| end_token: None, |
| } |
| .into()) |
| } |
| |
| /// 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(CreateTrigger { |
| or_alter, |
| temporary: false, |
| or_replace: false, |
| is_constraint: false, |
| name, |
| period: Some(period), |
| period_before_table: false, |
| events, |
| table_name, |
| referenced_table_name: None, |
| referencing: Vec::new(), |
| trigger_object: None, |
| condition: None, |
| exec_body: None, |
| statements_as: true, |
| statements, |
| characteristics: None, |
| } |
| .into()) |
| } |
| |
| /// 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) |
| } |
| } |