// 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 { Some('[') } /// SQL Server has `CONVERT(type, value)` instead of `CONVERT(value, type)` /// 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: fn supports_set_stmt_without_operator(&self) -> bool { true } /// See: fn supports_timestamp_versioning(&self) -> bool { true } /// See fn supports_nested_comments(&self) -> bool { true } /// See fn supports_object_name_double_dot_notation(&self) -> bool { true } /// See 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> { 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 { 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 { 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, ) -> Result, 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) } }