sqlparser/dialect/
mysql.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#[cfg(not(feature = "std"))]
19use alloc::boxed::Box;
20
21use crate::{
22    ast::{BinaryOperator, Expr, LockTable, LockTableType, Statement},
23    dialect::Dialect,
24    keywords::Keyword,
25    parser::{Parser, ParserError},
26};
27
28use super::keywords;
29
30const RESERVED_FOR_TABLE_ALIAS_MYSQL: &[Keyword] = &[
31    Keyword::USE,
32    Keyword::IGNORE,
33    Keyword::FORCE,
34    Keyword::STRAIGHT_JOIN,
35];
36
37/// A [`Dialect`] for [MySQL](https://www.mysql.com/)
38#[derive(Debug)]
39pub struct MySqlDialect {}
40
41impl Dialect for MySqlDialect {
42    fn is_identifier_start(&self, ch: char) -> bool {
43        // See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html.
44        // Identifiers which begin with a digit are recognized while tokenizing numbers,
45        // so they can be distinguished from exponent numeric literals.
46        // MySQL also implements non ascii utf-8 charecters
47        ch.is_alphabetic()
48            || ch == '_'
49            || ch == '$'
50            || ch == '@'
51            || ('\u{0080}'..='\u{ffff}').contains(&ch)
52            || !ch.is_ascii()
53    }
54
55    fn is_identifier_part(&self, ch: char) -> bool {
56        self.is_identifier_start(ch) || ch.is_ascii_digit() ||
57        // MySQL implements Unicode characters in identifiers.
58        !ch.is_ascii()
59    }
60
61    fn is_delimited_identifier_start(&self, ch: char) -> bool {
62        ch == '`'
63    }
64
65    fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
66        Some('`')
67    }
68
69    // See https://dev.mysql.com/doc/refman/8.0/en/string-literals.html#character-escape-sequences
70    fn supports_string_literal_backslash_escape(&self) -> bool {
71        true
72    }
73
74    /// see <https://dev.mysql.com/doc/refman/8.4/en/string-functions.html#function_concat>
75    fn supports_string_literal_concatenation(&self) -> bool {
76        true
77    }
78
79    fn ignores_wildcard_escapes(&self) -> bool {
80        true
81    }
82
83    fn supports_numeric_prefix(&self) -> bool {
84        true
85    }
86
87    fn parse_infix(
88        &self,
89        parser: &mut crate::parser::Parser,
90        expr: &crate::ast::Expr,
91        _precedence: u8,
92    ) -> Option<Result<crate::ast::Expr, ParserError>> {
93        // Parse DIV as an operator
94        if parser.parse_keyword(Keyword::DIV) {
95            Some(Ok(Expr::BinaryOp {
96                left: Box::new(expr.clone()),
97                op: BinaryOperator::MyIntegerDivide,
98                right: Box::new(parser.parse_expr().unwrap()),
99            }))
100        } else {
101            None
102        }
103    }
104
105    fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
106        if parser.parse_keywords(&[Keyword::LOCK, Keyword::TABLES]) {
107            Some(parse_lock_tables(parser))
108        } else if parser.parse_keywords(&[Keyword::UNLOCK, Keyword::TABLES]) {
109            Some(parse_unlock_tables(parser))
110        } else {
111            None
112        }
113    }
114
115    fn require_interval_qualifier(&self) -> bool {
116        true
117    }
118
119    fn supports_limit_comma(&self) -> bool {
120        true
121    }
122
123    /// See: <https://dev.mysql.com/doc/refman/8.4/en/create-table-select.html>
124    fn supports_create_table_select(&self) -> bool {
125        true
126    }
127
128    /// See: <https://dev.mysql.com/doc/refman/8.4/en/insert.html>
129    fn supports_insert_set(&self) -> bool {
130        true
131    }
132
133    fn supports_user_host_grantee(&self) -> bool {
134        true
135    }
136
137    fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
138        explicit
139            || (!keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
140                && !RESERVED_FOR_TABLE_ALIAS_MYSQL.contains(kw))
141    }
142
143    fn supports_table_hints(&self) -> bool {
144        true
145    }
146
147    fn requires_single_line_comment_whitespace(&self) -> bool {
148        true
149    }
150
151    fn supports_match_against(&self) -> bool {
152        true
153    }
154
155    fn supports_set_names(&self) -> bool {
156        true
157    }
158
159    fn supports_comma_separated_set_assignments(&self) -> bool {
160        true
161    }
162
163    fn supports_data_type_signed_suffix(&self) -> bool {
164        true
165    }
166
167    fn supports_cross_join_constraint(&self) -> bool {
168        true
169    }
170}
171
172/// `LOCK TABLES`
173/// <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html>
174fn parse_lock_tables(parser: &mut Parser) -> Result<Statement, ParserError> {
175    let tables = parser.parse_comma_separated(parse_lock_table)?;
176    Ok(Statement::LockTables { tables })
177}
178
179// tbl_name [[AS] alias] lock_type
180fn parse_lock_table(parser: &mut Parser) -> Result<LockTable, ParserError> {
181    let table = parser.parse_identifier()?;
182    let alias =
183        parser.parse_optional_alias(&[Keyword::READ, Keyword::WRITE, Keyword::LOW_PRIORITY])?;
184    let lock_type = parse_lock_tables_type(parser)?;
185
186    Ok(LockTable {
187        table,
188        alias,
189        lock_type,
190    })
191}
192
193// READ [LOCAL] | [LOW_PRIORITY] WRITE
194fn parse_lock_tables_type(parser: &mut Parser) -> Result<LockTableType, ParserError> {
195    if parser.parse_keyword(Keyword::READ) {
196        if parser.parse_keyword(Keyword::LOCAL) {
197            Ok(LockTableType::Read { local: true })
198        } else {
199            Ok(LockTableType::Read { local: false })
200        }
201    } else if parser.parse_keyword(Keyword::WRITE) {
202        Ok(LockTableType::Write {
203            low_priority: false,
204        })
205    } else if parser.parse_keywords(&[Keyword::LOW_PRIORITY, Keyword::WRITE]) {
206        Ok(LockTableType::Write { low_priority: true })
207    } else {
208        parser.expected("an lock type in LOCK TABLES", parser.peek_token())
209    }
210}
211
212/// UNLOCK TABLES
213/// <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html>
214fn parse_unlock_tables(_parser: &mut Parser) -> Result<Statement, ParserError> {
215    Ok(Statement::UnlockTables)
216}