| // 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::Keyword; |
| use crate::parser::{Parser, ParserError}; |
| use crate::tokenizer::Token; |
| #[cfg(not(feature = "std"))] |
| use alloc::{vec, vec::Vec}; |
| |
| /// A [`Dialect`] for [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/) |
| #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
| 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 |
| } |
| |
| /// SQL Server supports `$` as a prefix for money literals |
| /// <https://learn.microsoft.com/en-us/sql/t-sql/data-types/constants-transact-sql?view=sql-server-ver17#money-constants> |
| fn supports_dollar_as_money_prefix(&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_table_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_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool { |
| match kw { |
| // List of keywords that cannot be used as select item (column) aliases in MSSQL |
| // regardless of whether the alias is explicit or implicit. |
| // |
| // These are T-SQL statement-starting keywords; allowing them as implicit aliases |
| // causes the parser to consume the keyword as an alias for the previous expression, |
| // then fail on the token that follows (e.g. `TABLE`, `@var`, `sp_name`, …). |
| Keyword::IF |
| | Keyword::ELSE |
| | Keyword::DECLARE |
| | Keyword::EXEC |
| | Keyword::EXECUTE |
| | Keyword::INSERT |
| | Keyword::UPDATE |
| | Keyword::DELETE |
| | Keyword::DROP |
| | Keyword::CREATE |
| | Keyword::ALTER |
| | Keyword::TRUNCATE |
| | Keyword::PRINT |
| | Keyword::WHILE |
| | Keyword::RETURN |
| | Keyword::THROW |
| | Keyword::RAISERROR |
| | Keyword::MERGE => 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. |
| // |
| // These are T-SQL statement-starting keywords. Without blocking them here, |
| // a bare `SELECT * FROM t` followed by a newline and one of these keywords |
| // would cause the parser to consume the keyword as a table alias for `t`, |
| // then fail on the token that follows (e.g. `@var`, `sp_name`, `TABLE`, …). |
| // |
| // `SET` is already covered by the global `RESERVED_FOR_TABLE_ALIAS` list; |
| // the keywords below are MSSQL-specific additions. |
| Keyword::IF |
| | Keyword::ELSE |
| | Keyword::DECLARE |
| | Keyword::EXEC |
| | Keyword::EXECUTE |
| | Keyword::INSERT |
| | Keyword::UPDATE |
| | Keyword::DELETE |
| | Keyword::DROP |
| | Keyword::CREATE |
| | Keyword::ALTER |
| | Keyword::TRUNCATE |
| | Keyword::PRINT |
| | Keyword::WHILE |
| | Keyword::RETURN |
| | Keyword::THROW |
| | Keyword::RAISERROR |
| | Keyword::MERGE => false, |
| _ => explicit || self.is_table_alias(kw, parser), |
| } |
| } |
| |
| fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> { |
| if parser.parse_keyword(Keyword::BEGIN) { |
| // Check if this is a BEGIN...END block rather than BEGIN TRANSACTION |
| let is_block = parser |
| .maybe_parse(|p| { |
| if p.parse_transaction_modifier().is_some() |
| || p.parse_one_of_keywords(&[ |
| Keyword::TRANSACTION, |
| Keyword::WORK, |
| Keyword::TRAN, |
| ]) |
| .is_some() |
| || matches!(p.peek_token_ref().token, Token::SemiColon | Token::EOF) |
| { |
| p.expected_ref("statement", p.peek_token_ref()) |
| } else { |
| Ok(()) |
| } |
| }) |
| .unwrap_or(None) |
| .is_some(); |
| if is_block { |
| Some(parser.parse_begin_exception_end()) |
| } else { |
| parser.prev_token(); |
| None |
| } |
| } else 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 |
| } |
| } |
| |
| fn get_next_precedence(&self, parser: &Parser) -> Option<Result<u8, ParserError>> { |
| let token = parser.peek_token_ref(); |
| match &token.token { |
| // lowest prec to prevent it from turning into a binary op |
| Token::Colon => Some(Ok(self.prec_unknown())), |
| _ => 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) |
| } |
| } |