Skip to main content

sqlparser/
tokenizer.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//! SQL Tokenizer
19//!
20//! The tokenizer (a.k.a. lexer) converts a string into a sequence of tokens.
21//!
22//! The tokens then form the input for the parser, which outputs an Abstract Syntax Tree (AST).
23
24#[cfg(not(feature = "std"))]
25use alloc::{
26    borrow::ToOwned,
27    format,
28    string::{String, ToString},
29    vec,
30    vec::Vec,
31};
32use core::num::NonZeroU8;
33use core::str::Chars;
34use core::{cmp, fmt};
35use core::{iter::Peekable, str};
36
37#[cfg(feature = "serde")]
38use serde::{Deserialize, Serialize};
39
40#[cfg(feature = "visitor")]
41use sqlparser_derive::{Visit, VisitMut};
42
43use crate::dialect::Dialect;
44use crate::dialect::{
45    BigQueryDialect, DuckDbDialect, GenericDialect, MySqlDialect, PostgreSqlDialect,
46    SnowflakeDialect,
47};
48use crate::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX};
49use crate::{
50    ast::{DollarQuotedString, QuoteDelimitedString},
51    dialect::HiveDialect,
52};
53
54/// SQL Token enumeration
55#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
56#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
57#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
58pub enum Token {
59    /// An end-of-file marker, not a real token
60    EOF,
61    /// A keyword (like SELECT) or an optionally quoted SQL identifier
62    Word(Word),
63    /// An unsigned numeric literal
64    Number(String, bool),
65    /// A character that could not be tokenized
66    Char(char),
67    /// Single quoted string: i.e: 'string'
68    SingleQuotedString(String),
69    /// Double quoted string: i.e: "string"
70    DoubleQuotedString(String),
71    /// Triple single quoted strings: Example '''abc'''
72    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
73    TripleSingleQuotedString(String),
74    /// Triple double quoted strings: Example """abc"""
75    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
76    TripleDoubleQuotedString(String),
77    /// Dollar quoted string: i.e: $$string$$ or $tag_name$string$tag_name$
78    DollarQuotedString(DollarQuotedString),
79    /// Byte string literal: i.e: b'string' or B'string' (note that some backends, such as
80    /// PostgreSQL, may treat this syntax as a bit string literal instead, i.e: b'10010101')
81    SingleQuotedByteStringLiteral(String),
82    /// Byte string literal: i.e: b"string" or B"string"
83    DoubleQuotedByteStringLiteral(String),
84    /// Triple single quoted literal with byte string prefix. Example `B'''abc'''`
85    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
86    TripleSingleQuotedByteStringLiteral(String),
87    /// Triple double quoted literal with byte string prefix. Example `B"""abc"""`
88    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
89    TripleDoubleQuotedByteStringLiteral(String),
90    /// Single quoted literal with raw string prefix. Example `R'abc'`
91    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
92    SingleQuotedRawStringLiteral(String),
93    /// Double quoted literal with raw string prefix. Example `R"abc"`
94    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
95    DoubleQuotedRawStringLiteral(String),
96    /// Triple single quoted literal with raw string prefix. Example `R'''abc'''`
97    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
98    TripleSingleQuotedRawStringLiteral(String),
99    /// Triple double quoted literal with raw string prefix. Example `R"""abc"""`
100    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)
101    TripleDoubleQuotedRawStringLiteral(String),
102    /// "National" string literal: i.e: N'string'
103    NationalStringLiteral(String),
104    /// Quote delimited literal. Examples `Q'{ab'c}'`, `Q'|ab'c|'`, `Q'|ab|c|'`
105    /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA)
106    QuoteDelimitedStringLiteral(QuoteDelimitedString),
107    /// "Nationa" quote delimited literal. Examples `NQ'{ab'c}'`, `NQ'|ab'c|'`, `NQ'|ab|c|'`
108    /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA)
109    NationalQuoteDelimitedStringLiteral(QuoteDelimitedString),
110    /// "escaped" string literal, which are an extension to the SQL standard: i.e: e'first \n second' or E 'first \n second'
111    EscapedStringLiteral(String),
112    /// Unicode string literal: i.e: U&'first \000A second'
113    UnicodeStringLiteral(String),
114    /// Hexadecimal string literal: i.e.: X'deadbeef'
115    HexStringLiteral(String),
116    /// Comma
117    Comma,
118    /// Whitespace (space, tab, etc)
119    Whitespace(Whitespace),
120    /// Double equals sign `==`
121    DoubleEq,
122    /// Equality operator `=`
123    Eq,
124    /// Not Equals operator `<>` (or `!=` in some dialects)
125    Neq,
126    /// Less Than operator `<`
127    Lt,
128    /// Greater Than operator `>`
129    Gt,
130    /// Less Than Or Equals operator `<=`
131    LtEq,
132    /// Greater Than Or Equals operator `>=`
133    GtEq,
134    /// Spaceship operator <=>
135    Spaceship,
136    /// Plus operator `+`
137    Plus,
138    /// Minus operator `-`
139    Minus,
140    /// Multiplication operator `*`
141    Mul,
142    /// Division operator `/`
143    Div,
144    /// Integer division operator `//` in DuckDB
145    DuckIntDiv,
146    /// Modulo Operator `%`
147    Mod,
148    /// String concatenation `||`
149    StringConcat,
150    /// Left parenthesis `(`
151    LParen,
152    /// Right parenthesis `)`
153    RParen,
154    /// Period (used for compound identifiers or projections into nested types)
155    Period,
156    /// Colon `:`
157    Colon,
158    /// DoubleColon `::` (used for casting in PostgreSQL)
159    DoubleColon,
160    /// Assignment `:=` (used for keyword argument in DuckDB macros and some functions, and for variable declarations in DuckDB and Snowflake)
161    Assignment,
162    /// SemiColon `;` used as separator for COPY and payload
163    SemiColon,
164    /// Backslash `\` used in terminating the COPY payload with `\.`
165    Backslash,
166    /// Left bracket `[`
167    LBracket,
168    /// Right bracket `]`
169    RBracket,
170    /// Ampersand `&`
171    Ampersand,
172    /// Pipe `|`
173    Pipe,
174    /// Caret `^`
175    Caret,
176    /// Left brace `{`
177    LBrace,
178    /// Right brace `}`
179    RBrace,
180    /// Right Arrow `=>`
181    RArrow,
182    /// Sharp `#` used for PostgreSQL Bitwise XOR operator, also PostgreSQL/Redshift geometrical unary/binary operator (Number of points in path or polygon/Intersection)
183    Sharp,
184    /// `##` PostgreSQL/Redshift geometrical binary operator (Point of closest proximity)
185    DoubleSharp,
186    /// Tilde `~` used for PostgreSQL Bitwise NOT operator or case sensitive match regular expression operator
187    Tilde,
188    /// `~*` , a case insensitive match regular expression operator in PostgreSQL
189    TildeAsterisk,
190    /// `!~` , a case sensitive not match regular expression operator in PostgreSQL
191    ExclamationMarkTilde,
192    /// `!~*` , a case insensitive not match regular expression operator in PostgreSQL
193    ExclamationMarkTildeAsterisk,
194    /// `~~`, a case sensitive match pattern operator in PostgreSQL
195    DoubleTilde,
196    /// `~~*`, a case insensitive match pattern operator in PostgreSQL
197    DoubleTildeAsterisk,
198    /// `!~~`, a case sensitive not match pattern operator in PostgreSQL
199    ExclamationMarkDoubleTilde,
200    /// `!~~*`, a case insensitive not match pattern operator in PostgreSQL
201    ExclamationMarkDoubleTildeAsterisk,
202    /// `<<`, a bitwise shift left operator in PostgreSQL
203    ShiftLeft,
204    /// `>>`, a bitwise shift right operator in PostgreSQL
205    ShiftRight,
206    /// `&&`, an overlap operator in PostgreSQL
207    Overlap,
208    /// Exclamation Mark `!` used for PostgreSQL factorial operator
209    ExclamationMark,
210    /// Double Exclamation Mark `!!` used for PostgreSQL prefix factorial operator
211    DoubleExclamationMark,
212    /// AtSign `@` used for PostgreSQL abs operator, also PostgreSQL/Redshift geometrical unary/binary operator (Center, Contained or on)
213    AtSign,
214    /// `^@`, a "starts with" string operator in PostgreSQL
215    CaretAt,
216    /// `|/`, a square root math operator in PostgreSQL
217    PGSquareRoot,
218    /// `||/`, a cube root math operator in PostgreSQL
219    PGCubeRoot,
220    /// `?` or `$` , a prepared statement arg placeholder
221    Placeholder(String),
222    /// `->`, used as a operator to extract json field in PostgreSQL
223    Arrow,
224    /// `->>`, used as a operator to extract json field as text in PostgreSQL
225    LongArrow,
226    /// `#>`, extracts JSON sub-object at the specified path
227    HashArrow,
228    /// `@-@` PostgreSQL/Redshift geometrical unary operator (Length or circumference)
229    AtDashAt,
230    /// `?-` PostgreSQL/Redshift geometrical unary/binary operator (Is horizontal?/Are horizontally aligned?)
231    QuestionMarkDash,
232    /// `&<` PostgreSQL/Redshift geometrical binary operator (Overlaps to left?)
233    AmpersandLeftAngleBracket,
234    /// `&>` PostgreSQL/Redshift geometrical binary operator (Overlaps to right?)`
235    AmpersandRightAngleBracket,
236    /// `&<|` PostgreSQL/Redshift geometrical binary operator (Does not extend above?)`
237    AmpersandLeftAngleBracketVerticalBar,
238    /// `|&>` PostgreSQL/Redshift geometrical binary operator (Does not extend below?)`
239    VerticalBarAmpersandRightAngleBracket,
240    /// `<->` PostgreSQL/Redshift geometrical binary operator (Distance between)
241    TwoWayArrow,
242    /// `<^` PostgreSQL/Redshift geometrical binary operator (Is below?)
243    LeftAngleBracketCaret,
244    /// `>^` PostgreSQL/Redshift geometrical binary operator (Is above?)
245    RightAngleBracketCaret,
246    /// `?#` PostgreSQL/Redshift geometrical binary operator (Intersects or overlaps)
247    QuestionMarkSharp,
248    /// `?-|` PostgreSQL/Redshift geometrical binary operator (Is perpendicular?)
249    QuestionMarkDashVerticalBar,
250    /// `?||` PostgreSQL/Redshift geometrical binary operator (Are parallel?)
251    QuestionMarkDoubleVerticalBar,
252    /// `~=` PostgreSQL/Redshift geometrical binary operator (Same as)
253    TildeEqual,
254    /// `<<| PostgreSQL/Redshift geometrical binary operator (Is strictly below?)
255    ShiftLeftVerticalBar,
256    /// `|>> PostgreSQL/Redshift geometrical binary operator (Is strictly above?)
257    VerticalBarShiftRight,
258    /// `|> BigQuery pipe operator
259    VerticalBarRightAngleBracket,
260    /// `#>>`, extracts JSON sub-object at the specified path as text
261    HashLongArrow,
262    /// jsonb @> jsonb -> boolean: Test whether left json contains the right json
263    AtArrow,
264    /// jsonb <@ jsonb -> boolean: Test whether right json contains the left json
265    ArrowAt,
266    /// jsonb #- text[] -> jsonb: Deletes the field or array element at the specified
267    /// path, where path elements can be either field keys or array indexes.
268    HashMinus,
269    /// jsonb @? jsonpath -> boolean: Does JSON path return any item for the specified
270    /// JSON value?
271    AtQuestion,
272    /// jsonb @@ jsonpath → boolean: Returns the result of a JSON path predicate check
273    /// for the specified JSON value. Only the first item of the result is taken into
274    /// account. If the result is not Boolean, then NULL is returned.
275    AtAt,
276    /// jsonb ? text -> boolean: Checks whether the string exists as a top-level key within the
277    /// jsonb object
278    Question,
279    /// jsonb ?& text[] -> boolean: Check whether all members of the text array exist as top-level
280    /// keys within the jsonb object
281    QuestionAnd,
282    /// jsonb ?| text[] -> boolean: Check whether any member of the text array exists as top-level
283    /// keys within the jsonb object
284    QuestionPipe,
285    /// Custom binary operator
286    /// This is used to represent any custom binary operator that is not part of the SQL standard.
287    /// PostgreSQL allows defining custom binary operators using CREATE OPERATOR.
288    CustomBinaryOperator(String),
289}
290
291impl fmt::Display for Token {
292    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293        match self {
294            Token::EOF => f.write_str("EOF"),
295            Token::Word(ref w) => write!(f, "{w}"),
296            Token::Number(ref n, l) => write!(f, "{}{long}", n, long = if *l { "L" } else { "" }),
297            Token::Char(ref c) => write!(f, "{c}"),
298            Token::SingleQuotedString(ref s) => write!(f, "'{s}'"),
299            Token::TripleSingleQuotedString(ref s) => write!(f, "'''{s}'''"),
300            Token::DoubleQuotedString(ref s) => write!(f, "\"{s}\""),
301            Token::TripleDoubleQuotedString(ref s) => write!(f, "\"\"\"{s}\"\"\""),
302            Token::DollarQuotedString(ref s) => write!(f, "{s}"),
303            Token::NationalStringLiteral(ref s) => write!(f, "N'{s}'"),
304            Token::QuoteDelimitedStringLiteral(ref s) => s.fmt(f),
305            Token::NationalQuoteDelimitedStringLiteral(ref s) => write!(f, "N{s}"),
306            Token::EscapedStringLiteral(ref s) => write!(f, "E'{s}'"),
307            Token::UnicodeStringLiteral(ref s) => write!(f, "U&'{s}'"),
308            Token::HexStringLiteral(ref s) => write!(f, "X'{s}'"),
309            Token::SingleQuotedByteStringLiteral(ref s) => write!(f, "B'{s}'"),
310            Token::TripleSingleQuotedByteStringLiteral(ref s) => write!(f, "B'''{s}'''"),
311            Token::DoubleQuotedByteStringLiteral(ref s) => write!(f, "B\"{s}\""),
312            Token::TripleDoubleQuotedByteStringLiteral(ref s) => write!(f, "B\"\"\"{s}\"\"\""),
313            Token::SingleQuotedRawStringLiteral(ref s) => write!(f, "R'{s}'"),
314            Token::DoubleQuotedRawStringLiteral(ref s) => write!(f, "R\"{s}\""),
315            Token::TripleSingleQuotedRawStringLiteral(ref s) => write!(f, "R'''{s}'''"),
316            Token::TripleDoubleQuotedRawStringLiteral(ref s) => write!(f, "R\"\"\"{s}\"\"\""),
317            Token::Comma => f.write_str(","),
318            Token::Whitespace(ws) => write!(f, "{ws}"),
319            Token::DoubleEq => f.write_str("=="),
320            Token::Spaceship => f.write_str("<=>"),
321            Token::Eq => f.write_str("="),
322            Token::Neq => f.write_str("<>"),
323            Token::Lt => f.write_str("<"),
324            Token::Gt => f.write_str(">"),
325            Token::LtEq => f.write_str("<="),
326            Token::GtEq => f.write_str(">="),
327            Token::Plus => f.write_str("+"),
328            Token::Minus => f.write_str("-"),
329            Token::Mul => f.write_str("*"),
330            Token::Div => f.write_str("/"),
331            Token::DuckIntDiv => f.write_str("//"),
332            Token::StringConcat => f.write_str("||"),
333            Token::Mod => f.write_str("%"),
334            Token::LParen => f.write_str("("),
335            Token::RParen => f.write_str(")"),
336            Token::Period => f.write_str("."),
337            Token::Colon => f.write_str(":"),
338            Token::DoubleColon => f.write_str("::"),
339            Token::Assignment => f.write_str(":="),
340            Token::SemiColon => f.write_str(";"),
341            Token::Backslash => f.write_str("\\"),
342            Token::LBracket => f.write_str("["),
343            Token::RBracket => f.write_str("]"),
344            Token::Ampersand => f.write_str("&"),
345            Token::Caret => f.write_str("^"),
346            Token::Pipe => f.write_str("|"),
347            Token::LBrace => f.write_str("{"),
348            Token::RBrace => f.write_str("}"),
349            Token::RArrow => f.write_str("=>"),
350            Token::Sharp => f.write_str("#"),
351            Token::DoubleSharp => f.write_str("##"),
352            Token::ExclamationMark => f.write_str("!"),
353            Token::DoubleExclamationMark => f.write_str("!!"),
354            Token::Tilde => f.write_str("~"),
355            Token::TildeAsterisk => f.write_str("~*"),
356            Token::ExclamationMarkTilde => f.write_str("!~"),
357            Token::ExclamationMarkTildeAsterisk => f.write_str("!~*"),
358            Token::DoubleTilde => f.write_str("~~"),
359            Token::DoubleTildeAsterisk => f.write_str("~~*"),
360            Token::ExclamationMarkDoubleTilde => f.write_str("!~~"),
361            Token::ExclamationMarkDoubleTildeAsterisk => f.write_str("!~~*"),
362            Token::AtSign => f.write_str("@"),
363            Token::CaretAt => f.write_str("^@"),
364            Token::ShiftLeft => f.write_str("<<"),
365            Token::ShiftRight => f.write_str(">>"),
366            Token::Overlap => f.write_str("&&"),
367            Token::PGSquareRoot => f.write_str("|/"),
368            Token::PGCubeRoot => f.write_str("||/"),
369            Token::AtDashAt => f.write_str("@-@"),
370            Token::QuestionMarkDash => f.write_str("?-"),
371            Token::AmpersandLeftAngleBracket => f.write_str("&<"),
372            Token::AmpersandRightAngleBracket => f.write_str("&>"),
373            Token::AmpersandLeftAngleBracketVerticalBar => f.write_str("&<|"),
374            Token::VerticalBarAmpersandRightAngleBracket => f.write_str("|&>"),
375            Token::VerticalBarRightAngleBracket => f.write_str("|>"),
376            Token::TwoWayArrow => f.write_str("<->"),
377            Token::LeftAngleBracketCaret => f.write_str("<^"),
378            Token::RightAngleBracketCaret => f.write_str(">^"),
379            Token::QuestionMarkSharp => f.write_str("?#"),
380            Token::QuestionMarkDashVerticalBar => f.write_str("?-|"),
381            Token::QuestionMarkDoubleVerticalBar => f.write_str("?||"),
382            Token::TildeEqual => f.write_str("~="),
383            Token::ShiftLeftVerticalBar => f.write_str("<<|"),
384            Token::VerticalBarShiftRight => f.write_str("|>>"),
385            Token::Placeholder(ref s) => write!(f, "{s}"),
386            Token::Arrow => write!(f, "->"),
387            Token::LongArrow => write!(f, "->>"),
388            Token::HashArrow => write!(f, "#>"),
389            Token::HashLongArrow => write!(f, "#>>"),
390            Token::AtArrow => write!(f, "@>"),
391            Token::ArrowAt => write!(f, "<@"),
392            Token::HashMinus => write!(f, "#-"),
393            Token::AtQuestion => write!(f, "@?"),
394            Token::AtAt => write!(f, "@@"),
395            Token::Question => write!(f, "?"),
396            Token::QuestionAnd => write!(f, "?&"),
397            Token::QuestionPipe => write!(f, "?|"),
398            Token::CustomBinaryOperator(s) => f.write_str(s),
399        }
400    }
401}
402
403impl Token {
404    /// Create a `Token::Word` from an unquoted `keyword`.
405    ///
406    /// The lookup is case-insensitive; unknown values become `Keyword::NoKeyword`.
407    pub fn make_keyword(keyword: &str) -> Self {
408        Token::make_word(keyword, None)
409    }
410
411    /// Create a `Token::Word` from `word` with an optional `quote_style`.
412    ///
413    /// When `quote_style` is `None`, the parser attempts a case-insensitive keyword
414    /// lookup and sets the `Word::keyword` accordingly.
415    pub fn make_word(word: &str, quote_style: Option<char>) -> Self {
416        Token::Word(Word {
417            keyword: keyword_lookup(word, quote_style),
418            value: word.to_string(),
419            quote_style,
420        })
421    }
422
423    /// Like [`Self::make_word`] but takes ownership of the word `String`,
424    /// avoiding an extra allocation when the caller already has an owned value.
425    fn make_word_owned(word: String, quote_style: Option<char>) -> Self {
426        Token::Word(Word {
427            keyword: keyword_lookup(&word, quote_style),
428            value: word,
429            quote_style,
430        })
431    }
432}
433
434/// Case-insensitive keyword lookup using binary search over [`ALL_KEYWORDS`].
435fn keyword_lookup(word: &str, quote_style: Option<char>) -> Keyword {
436    if quote_style.is_some() {
437        return Keyword::NoKeyword;
438    }
439    ALL_KEYWORDS
440        .binary_search_by(|probe| {
441            let probe = probe.as_bytes();
442            let word = word.as_bytes();
443            for (p, w) in probe.iter().zip(word.iter()) {
444                let cmp = p.cmp(&w.to_ascii_uppercase());
445                if cmp != core::cmp::Ordering::Equal {
446                    return cmp;
447                }
448            }
449            probe.len().cmp(&word.len())
450        })
451        .map_or(Keyword::NoKeyword, |x| ALL_KEYWORDS_INDEX[x])
452}
453
454/// A keyword (like SELECT) or an optionally quoted SQL identifier
455#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
456#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
457#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
458pub struct Word {
459    /// The value of the token, without the enclosing quotes, and with the
460    /// escape sequences (if any) processed (TODO: escapes are not handled)
461    pub value: String,
462    /// An identifier can be "quoted" (&lt;delimited identifier> in ANSI parlance).
463    /// The standard and most implementations allow using double quotes for this,
464    /// but some implementations support other quoting styles as well (e.g. \[MS SQL])
465    pub quote_style: Option<char>,
466    /// If the word was not quoted and it matched one of the known keywords,
467    /// this will have one of the values from dialect::keywords, otherwise empty
468    pub keyword: Keyword,
469}
470
471impl fmt::Display for Word {
472    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
473        match self.quote_style {
474            Some(s) if s == '"' || s == '[' || s == '`' => {
475                write!(f, "{}{}{}", s, self.value, Word::matching_end_quote(s))
476            }
477            None => f.write_str(&self.value),
478            _ => panic!("Unexpected quote_style!"),
479        }
480    }
481}
482
483impl Word {
484    fn matching_end_quote(ch: char) -> char {
485        match ch {
486            '"' => '"', // ANSI and most dialects
487            '[' => ']', // MS SQL
488            '`' => '`', // MySQL
489            _ => panic!("unexpected quoting style!"),
490        }
491    }
492}
493
494/// Represents whitespace in the input: spaces, newlines, tabs and comments.
495#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
496#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
497#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
498pub enum Whitespace {
499    /// A single space character.
500    Space,
501    /// A newline character.
502    Newline,
503    /// A tab character.
504    Tab,
505    /// A single-line comment (e.g. `-- comment` or `# comment`).
506    /// The `comment` field contains the text, and `prefix` contains the comment prefix.
507    SingleLineComment {
508        /// The content of the comment (without the prefix).
509        comment: String,
510        /// The prefix used for the comment (for example `--` or `#`).
511        prefix: String,
512    },
513
514    /// A multi-line comment (without the `/* ... */` delimiters).
515    MultiLineComment(String),
516}
517
518impl fmt::Display for Whitespace {
519    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
520        match self {
521            Whitespace::Space => f.write_str(" "),
522            Whitespace::Newline => f.write_str("\n"),
523            Whitespace::Tab => f.write_str("\t"),
524            Whitespace::SingleLineComment { prefix, comment } => writeln!(f, "{prefix}{comment}"),
525            Whitespace::MultiLineComment(s) => write!(f, "/*{s}*/"),
526        }
527    }
528}
529
530/// Location in input string
531///
532/// # Create an "empty" (unknown) `Location`
533/// ```
534/// # use sqlparser::tokenizer::Location;
535/// let location = Location::empty();
536/// ```
537///
538/// # Create a `Location` from a line and column
539/// ```
540/// # use sqlparser::tokenizer::Location;
541/// let location = Location::new(1, 1);
542/// ```
543///
544/// # Create a `Location` from a pair
545/// ```
546/// # use sqlparser::tokenizer::Location;
547/// let location = Location::from((1, 1));
548/// ```
549#[derive(Eq, PartialEq, Hash, Clone, Copy, Ord, PartialOrd)]
550#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
551#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
552pub struct Location {
553    /// Line number, starting from 1.
554    ///
555    /// Note: Line 0 is used for empty spans
556    pub line: u64,
557    /// Line column, starting from 1.
558    ///
559    /// Note: Column 0 is used for empty spans
560    pub column: u64,
561}
562
563impl fmt::Display for Location {
564    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
565        if self.line == 0 {
566            return Ok(());
567        }
568        write!(f, " at Line: {}, Column: {}", self.line, self.column)
569    }
570}
571
572impl fmt::Debug for Location {
573    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
574        write!(f, "Location({},{})", self.line, self.column)
575    }
576}
577
578impl Location {
579    /// Return an "empty" / unknown location
580    pub fn empty() -> Self {
581        Self { line: 0, column: 0 }
582    }
583
584    /// Create a new `Location` for a given line and column
585    pub fn new(line: u64, column: u64) -> Self {
586        Self { line, column }
587    }
588
589    /// Create a new location for a given line and column
590    ///
591    /// Alias for [`Self::new`]
592    // TODO: remove / deprecate in favor of` `new` for consistency?
593    pub fn of(line: u64, column: u64) -> Self {
594        Self::new(line, column)
595    }
596
597    /// Combine self and `end` into a new `Span`
598    pub fn span_to(self, end: Self) -> Span {
599        Span { start: self, end }
600    }
601}
602
603impl From<(u64, u64)> for Location {
604    fn from((line, column): (u64, u64)) -> Self {
605        Self { line, column }
606    }
607}
608
609/// A span represents a linear portion of the input string (start, end)
610///
611/// See [Spanned](crate::ast::Spanned) for more information.
612#[derive(Eq, PartialEq, Hash, Clone, PartialOrd, Ord, Copy)]
613#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
614#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
615pub struct Span {
616    /// Start `Location` (inclusive).
617    pub start: Location,
618    /// End `Location` (inclusive).
619    pub end: Location,
620}
621
622impl fmt::Debug for Span {
623    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
624        write!(f, "Span({:?}..{:?})", self.start, self.end)
625    }
626}
627
628impl Span {
629    // An empty span (0, 0) -> (0, 0)
630    // We need a const instance for pattern matching
631    const EMPTY: Span = Self::empty();
632
633    /// Create a new span from a start and end [`Location`]
634    pub fn new(start: Location, end: Location) -> Span {
635        Span { start, end }
636    }
637
638    /// Returns an empty span `(0, 0) -> (0, 0)`
639    ///
640    /// Empty spans represent no knowledge of source location
641    /// See [Spanned](crate::ast::Spanned) for more information.
642    pub const fn empty() -> Span {
643        Span {
644            start: Location { line: 0, column: 0 },
645            end: Location { line: 0, column: 0 },
646        }
647    }
648
649    /// Returns the smallest Span that contains both `self` and `other`
650    /// If either span is [Span::empty], the other span is returned
651    ///
652    /// # Examples
653    /// ```
654    /// # use sqlparser::tokenizer::{Span, Location};
655    /// // line 1, column1 -> line 2, column 5
656    /// let span1 = Span::new(Location::new(1, 1), Location::new(2, 5));
657    /// // line 2, column 3 -> line 3, column 7
658    /// let span2 = Span::new(Location::new(2, 3), Location::new(3, 7));
659    /// // Union of the two is the min/max of the two spans
660    /// // line 1, column 1 -> line 3, column 7
661    /// let union = span1.union(&span2);
662    /// assert_eq!(union, Span::new(Location::new(1, 1), Location::new(3, 7)));
663    /// ```
664    pub fn union(&self, other: &Span) -> Span {
665        // If either span is empty, return the other
666        // this prevents propagating (0, 0) through the tree
667        match (self, other) {
668            (&Span::EMPTY, _) => *other,
669            (_, &Span::EMPTY) => *self,
670            _ => Span {
671                start: cmp::min(self.start, other.start),
672                end: cmp::max(self.end, other.end),
673            },
674        }
675    }
676
677    /// Same as [Span::union] for `Option<Span>`
678    ///
679    /// If `other` is `None`, `self` is returned
680    pub fn union_opt(&self, other: &Option<Span>) -> Span {
681        match other {
682            Some(other) => self.union(other),
683            None => *self,
684        }
685    }
686
687    /// Return the [Span::union] of all spans in the iterator
688    ///
689    /// If the iterator is empty, an empty span is returned
690    ///
691    /// # Example
692    /// ```
693    /// # use sqlparser::tokenizer::{Span, Location};
694    /// let spans = vec![
695    ///     Span::new(Location::new(1, 1), Location::new(2, 5)),
696    ///     Span::new(Location::new(2, 3), Location::new(3, 7)),
697    ///     Span::new(Location::new(3, 1), Location::new(4, 2)),
698    /// ];
699    /// // line 1, column 1 -> line 4, column 2
700    /// assert_eq!(
701    ///   Span::union_iter(spans),
702    ///   Span::new(Location::new(1, 1), Location::new(4, 2))
703    /// );
704    pub fn union_iter<I: IntoIterator<Item = Span>>(iter: I) -> Span {
705        iter.into_iter()
706            .reduce(|acc, item| acc.union(&item))
707            .unwrap_or(Span::empty())
708    }
709}
710
711/// Backwards compatibility struct for [`TokenWithSpan`]
712#[deprecated(since = "0.53.0", note = "please use `TokenWithSpan` instead")]
713pub type TokenWithLocation = TokenWithSpan;
714
715/// A [Token] with [Span] attached to it
716///
717/// This is used to track the location of a token in the input string
718///
719/// # Examples
720/// ```
721/// # use sqlparser::tokenizer::{Location, Span, Token, TokenWithSpan};
722/// // commas @ line 1, column 10
723/// let tok1 = TokenWithSpan::new(
724///   Token::Comma,
725///   Span::new(Location::new(1, 10), Location::new(1, 11)),
726/// );
727/// assert_eq!(tok1, Token::Comma); // can compare the token
728///
729/// // commas @ line 2, column 20
730/// let tok2 = TokenWithSpan::new(
731///   Token::Comma,
732///   Span::new(Location::new(2, 20), Location::new(2, 21)),
733/// );
734/// // same token but different locations are not equal
735/// assert_ne!(tok1, tok2);
736/// ```
737#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
738#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
739#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
740/// A `Token` together with its `Span` (location in the source).
741pub struct TokenWithSpan {
742    /// The token value.
743    pub token: Token,
744    /// The span covering the token in the input.
745    pub span: Span,
746}
747
748impl TokenWithSpan {
749    /// Create a new [`TokenWithSpan`] from a [`Token`] and a [`Span`]
750    pub fn new(token: Token, span: Span) -> Self {
751        Self { token, span }
752    }
753
754    /// Wrap a token with an empty span
755    pub fn wrap(token: Token) -> Self {
756        Self::new(token, Span::empty())
757    }
758
759    /// Wrap a token with a location from `start` to `end`
760    pub fn at(token: Token, start: Location, end: Location) -> Self {
761        Self::new(token, Span::new(start, end))
762    }
763
764    /// Return an EOF token with no location
765    pub fn new_eof() -> Self {
766        Self::wrap(Token::EOF)
767    }
768}
769
770impl PartialEq<Token> for TokenWithSpan {
771    fn eq(&self, other: &Token) -> bool {
772        &self.token == other
773    }
774}
775
776impl PartialEq<TokenWithSpan> for Token {
777    fn eq(&self, other: &TokenWithSpan) -> bool {
778        self == &other.token
779    }
780}
781
782impl fmt::Display for TokenWithSpan {
783    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
784        self.token.fmt(f)
785    }
786}
787
788/// An error reported by the tokenizer, with a human-readable `message` and a `location`.
789#[derive(Debug, PartialEq, Eq)]
790pub struct TokenizerError {
791    /// A descriptive error message.
792    pub message: String,
793    /// The `Location` where the error was detected.
794    pub location: Location,
795}
796
797impl fmt::Display for TokenizerError {
798    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799        write!(f, "{}{}", self.message, self.location,)
800    }
801}
802
803impl core::error::Error for TokenizerError {}
804
805struct State<'a> {
806    peekable: Peekable<Chars<'a>>,
807    line: u64,
808    col: u64,
809}
810
811impl State<'_> {
812    /// return the next character and advance the stream
813    pub fn next(&mut self) -> Option<char> {
814        match self.peekable.next() {
815            None => None,
816            Some(s) => {
817                if s == '\n' {
818                    self.line += 1;
819                    self.col = 1;
820                } else {
821                    self.col += 1;
822                }
823                Some(s)
824            }
825        }
826    }
827
828    /// return the next character but do not advance the stream
829    pub fn peek(&mut self) -> Option<&char> {
830        self.peekable.peek()
831    }
832
833    /// Return the current `Location` (line and column)
834    pub fn location(&self) -> Location {
835        Location {
836            line: self.line,
837            column: self.col,
838        }
839    }
840}
841
842/// Represents how many quote characters enclose a string literal.
843#[derive(Copy, Clone)]
844enum NumStringQuoteChars {
845    /// e.g. `"abc"`, `'abc'`, `r'abc'`
846    One,
847    /// e.g. `"""abc"""`, `'''abc'''`, `r'''abc'''`
848    Many(NonZeroU8),
849}
850
851/// Settings for tokenizing a quoted string literal.
852struct TokenizeQuotedStringSettings {
853    /// The character used to quote the string.
854    quote_style: char,
855    /// Represents how many quotes characters enclose the string literal.
856    num_quote_chars: NumStringQuoteChars,
857    /// The number of opening quotes left to consume, before parsing
858    /// the remaining string literal.
859    /// For example: given initial string `"""abc"""`. If the caller has
860    /// already parsed the first quote for some reason, then this value
861    /// is set to 1, flagging to look to consume only 2 leading quotes.
862    num_opening_quotes_to_consume: u8,
863    /// True if the string uses backslash escaping of special characters
864    /// e.g `'abc\ndef\'ghi'
865    backslash_escape: bool,
866}
867
868/// SQL Tokenizer
869pub struct Tokenizer<'a> {
870    dialect: &'a dyn Dialect,
871    query: &'a str,
872    /// If true (the default), the tokenizer will un-escape literal
873    /// SQL strings See [`Tokenizer::with_unescape`] for more details.
874    unescape: bool,
875}
876
877impl<'a> Tokenizer<'a> {
878    /// Create a new SQL tokenizer for the specified SQL statement
879    ///
880    /// ```
881    /// # use sqlparser::tokenizer::{Token, Whitespace, Tokenizer};
882    /// # use sqlparser::dialect::GenericDialect;
883    /// # let dialect = GenericDialect{};
884    /// let query = r#"SELECT 'foo'"#;
885    ///
886    /// // Parsing the query
887    /// let tokens = Tokenizer::new(&dialect, &query).tokenize().unwrap();
888    ///
889    /// assert_eq!(tokens, vec![
890    ///   Token::make_word("SELECT", None),
891    ///   Token::Whitespace(Whitespace::Space),
892    ///   Token::SingleQuotedString("foo".to_string()),
893    /// ]);
894    pub fn new(dialect: &'a dyn Dialect, query: &'a str) -> Self {
895        Self {
896            dialect,
897            query,
898            unescape: true,
899        }
900    }
901
902    /// Set unescape mode
903    ///
904    /// When true (default) the tokenizer unescapes literal values
905    /// (for example, `""` in SQL is unescaped to the literal `"`).
906    ///
907    /// When false, the tokenizer provides the raw strings as provided
908    /// in the query.  This can be helpful for programs that wish to
909    /// recover the *exact* original query text without normalizing
910    /// the escaping
911    ///
912    /// # Example
913    ///
914    /// ```
915    /// # use sqlparser::tokenizer::{Token, Tokenizer};
916    /// # use sqlparser::dialect::GenericDialect;
917    /// # let dialect = GenericDialect{};
918    /// let query = r#""Foo "" Bar""#;
919    /// let unescaped = Token::make_word(r#"Foo " Bar"#, Some('"'));
920    /// let original  = Token::make_word(r#"Foo "" Bar"#, Some('"'));
921    ///
922    /// // Parsing with unescaping (default)
923    /// let tokens = Tokenizer::new(&dialect, &query).tokenize().unwrap();
924    /// assert_eq!(tokens, vec![unescaped]);
925    ///
926    /// // Parsing with unescape = false
927    /// let tokens = Tokenizer::new(&dialect, &query)
928    ///    .with_unescape(false)
929    ///    .tokenize().unwrap();
930    /// assert_eq!(tokens, vec![original]);
931    /// ```
932    pub fn with_unescape(mut self, unescape: bool) -> Self {
933        self.unescape = unescape;
934        self
935    }
936
937    /// Tokenize the statement and produce a vector of tokens
938    pub fn tokenize(&mut self) -> Result<Vec<Token>, TokenizerError> {
939        let twl = self.tokenize_with_location()?;
940        Ok(twl.into_iter().map(|t| t.token).collect())
941    }
942
943    /// Tokenize the statement and produce a vector of tokens with location information
944    pub fn tokenize_with_location(&mut self) -> Result<Vec<TokenWithSpan>, TokenizerError> {
945        let mut tokens: Vec<TokenWithSpan> = vec![];
946        self.tokenize_with_location_into_buf(&mut tokens)
947            .map(|_| tokens)
948    }
949
950    /// Tokenize the statement and append tokens with location information into the provided buffer.
951    /// If an error is thrown, the buffer will contain all tokens that were successfully parsed before the error.
952    pub fn tokenize_with_location_into_buf(
953        &mut self,
954        buf: &mut Vec<TokenWithSpan>,
955    ) -> Result<(), TokenizerError> {
956        self.tokenize_with_location_into_buf_with_mapper(buf, |token| token)
957    }
958
959    /// Tokenize the statement and produce a vector of tokens, mapping each token
960    /// with provided `mapper`
961    pub fn tokenize_with_location_into_buf_with_mapper(
962        &mut self,
963        buf: &mut Vec<TokenWithSpan>,
964        mut mapper: impl FnMut(TokenWithSpan) -> TokenWithSpan,
965    ) -> Result<(), TokenizerError> {
966        let mut state = State {
967            peekable: self.query.chars().peekable(),
968            line: 1,
969            col: 1,
970        };
971
972        let mut location = state.location();
973        while let Some(token) = self.next_token(&mut state, buf.last().map(|t| &t.token))? {
974            let span = location.span_to(state.location());
975
976            // Check if this is a multiline comment hint that should be expanded
977            match &token {
978                Token::Whitespace(Whitespace::MultiLineComment(comment))
979                    if self.dialect.supports_multiline_comment_hints()
980                        && comment.starts_with('!') =>
981                {
982                    // Re-tokenize the hints and add them to the buffer
983                    self.tokenize_comment_hints(comment, span, buf, &mut mapper)?;
984                }
985                _ => {
986                    buf.push(mapper(TokenWithSpan { token, span }));
987                }
988            }
989
990            location = state.location();
991        }
992        Ok(())
993    }
994
995    /// Re-tokenize optimizer hints from a multiline comment and add them to the buffer.
996    /// For example, `/*!50110 KEY_BLOCK_SIZE = 1024*/` becomes tokens for `KEY_BLOCK_SIZE = 1024`
997    fn tokenize_comment_hints(
998        &self,
999        comment: &str,
1000        span: Span,
1001        buf: &mut Vec<TokenWithSpan>,
1002        mut mapper: impl FnMut(TokenWithSpan) -> TokenWithSpan,
1003    ) -> Result<(), TokenizerError> {
1004        // Strip the leading '!' and any version digits (e.g., "50110")
1005        let hint_content = comment
1006            .strip_prefix('!')
1007            .unwrap_or(comment)
1008            .trim_start_matches(|c: char| c.is_ascii_digit());
1009
1010        // If there's no content after stripping, nothing to tokenize
1011        if hint_content.is_empty() {
1012            return Ok(());
1013        }
1014
1015        // Create a new tokenizer for the hint content
1016        let inner = Tokenizer::new(self.dialect, hint_content).with_unescape(self.unescape);
1017
1018        // Create a state for tracking position within the hint
1019        let mut state = State {
1020            peekable: hint_content.chars().peekable(),
1021            line: span.start.line,
1022            col: span.start.column,
1023        };
1024
1025        // Tokenize the hint content and add tokens to the buffer
1026        let mut location = state.location();
1027        while let Some(token) = inner.next_token(&mut state, buf.last().map(|t| &t.token))? {
1028            let token_span = location.span_to(state.location());
1029            buf.push(mapper(TokenWithSpan {
1030                token,
1031                span: token_span,
1032            }));
1033            location = state.location();
1034        }
1035
1036        Ok(())
1037    }
1038
1039    // Tokenize the identifier or keywords in `ch`
1040    fn tokenize_identifier_or_keyword(
1041        &self,
1042        ch: impl IntoIterator<Item = char>,
1043        chars: &mut State,
1044    ) -> Result<Option<Token>, TokenizerError> {
1045        chars.next(); // consume the first char
1046        let ch: String = ch.into_iter().collect();
1047        let word = self.tokenize_word(ch, chars);
1048
1049        // TODO: implement parsing of exponent here
1050        if word.chars().all(|x| x.is_ascii_digit() || x == '.') {
1051            let mut inner_state = State {
1052                peekable: word.chars().peekable(),
1053                line: 0,
1054                col: 0,
1055            };
1056            let mut s = peeking_take_while(&mut inner_state, |ch| matches!(ch, '0'..='9' | '.'));
1057            let s2 = peeking_take_while(chars, |ch| matches!(ch, '0'..='9' | '.'));
1058            s += s2.as_str();
1059            return Ok(Some(Token::Number(s, false)));
1060        }
1061
1062        Ok(Some(Token::make_word_owned(word, None)))
1063    }
1064
1065    /// Get the next token or return None
1066    fn next_token(
1067        &self,
1068        chars: &mut State,
1069        prev_token: Option<&Token>,
1070    ) -> Result<Option<Token>, TokenizerError> {
1071        match chars.peek() {
1072            Some(&ch) => match ch {
1073                ' ' => self.consume_and_return(chars, Token::Whitespace(Whitespace::Space)),
1074                '\t' => self.consume_and_return(chars, Token::Whitespace(Whitespace::Tab)),
1075                '\n' => self.consume_and_return(chars, Token::Whitespace(Whitespace::Newline)),
1076                '\r' => {
1077                    // Emit a single Whitespace::Newline token for \r and \r\n
1078                    chars.next();
1079                    if let Some('\n') = chars.peek() {
1080                        chars.next();
1081                    }
1082                    Ok(Some(Token::Whitespace(Whitespace::Newline)))
1083                }
1084                // BigQuery and MySQL use b or B for byte string literal, Postgres for bit strings
1085                b @ 'B' | b @ 'b' if dialect_of!(self is BigQueryDialect | PostgreSqlDialect | MySqlDialect | GenericDialect) =>
1086                {
1087                    chars.next(); // consume
1088                    match chars.peek() {
1089                        Some('\'') => {
1090                            if self.dialect.supports_triple_quoted_string() {
1091                                return self
1092                                    .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1093                                        chars,
1094                                        '\'',
1095                                        false,
1096                                        Token::SingleQuotedByteStringLiteral,
1097                                        Token::TripleSingleQuotedByteStringLiteral,
1098                                    );
1099                            }
1100                            let s = self.tokenize_single_quoted_string(chars, '\'', false)?;
1101                            Ok(Some(Token::SingleQuotedByteStringLiteral(s)))
1102                        }
1103                        Some('\"') => {
1104                            if self.dialect.supports_triple_quoted_string() {
1105                                return self
1106                                    .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1107                                        chars,
1108                                        '"',
1109                                        false,
1110                                        Token::DoubleQuotedByteStringLiteral,
1111                                        Token::TripleDoubleQuotedByteStringLiteral,
1112                                    );
1113                            }
1114                            let s = self.tokenize_single_quoted_string(chars, '\"', false)?;
1115                            Ok(Some(Token::DoubleQuotedByteStringLiteral(s)))
1116                        }
1117                        _ => {
1118                            // regular identifier starting with an "b" or "B"
1119                            let s = self.tokenize_word(b, chars);
1120                            Ok(Some(Token::make_word_owned(s, None)))
1121                        }
1122                    }
1123                }
1124                // BigQuery uses r or R for raw string literal
1125                b @ 'R' | b @ 'r' if dialect_of!(self is BigQueryDialect | GenericDialect) => {
1126                    chars.next(); // consume
1127                    match chars.peek() {
1128                        Some('\'') => self
1129                            .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1130                                chars,
1131                                '\'',
1132                                false,
1133                                Token::SingleQuotedRawStringLiteral,
1134                                Token::TripleSingleQuotedRawStringLiteral,
1135                            ),
1136                        Some('\"') => self
1137                            .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1138                                chars,
1139                                '"',
1140                                false,
1141                                Token::DoubleQuotedRawStringLiteral,
1142                                Token::TripleDoubleQuotedRawStringLiteral,
1143                            ),
1144                        _ => {
1145                            // regular identifier starting with an "r" or "R"
1146                            let s = self.tokenize_word(b, chars);
1147                            Ok(Some(Token::make_word_owned(s, None)))
1148                        }
1149                    }
1150                }
1151                // Redshift uses lower case n for national string literal
1152                n @ 'N' | n @ 'n' => {
1153                    chars.next(); // consume, to check the next char
1154                    match chars.peek() {
1155                        Some('\'') => {
1156                            // N'...' - a <national character string literal>
1157                            let backslash_escape =
1158                                self.dialect.supports_string_literal_backslash_escape();
1159                            let s =
1160                                self.tokenize_single_quoted_string(chars, '\'', backslash_escape)?;
1161                            Ok(Some(Token::NationalStringLiteral(s)))
1162                        }
1163                        Some(&q @ 'q') | Some(&q @ 'Q')
1164                            if self.dialect.supports_quote_delimited_string() =>
1165                        {
1166                            chars.next(); // consume and check the next char
1167                            if let Some('\'') = chars.peek() {
1168                                self.tokenize_quote_delimited_string(chars, &[n, q])
1169                                    .map(|s| Some(Token::NationalQuoteDelimitedStringLiteral(s)))
1170                            } else {
1171                                let s = self.tokenize_word(String::from_iter([n, q]), chars);
1172                                Ok(Some(Token::make_word_owned(s, None)))
1173                            }
1174                        }
1175                        _ => {
1176                            // regular identifier starting with an "N"
1177                            let s = self.tokenize_word(n, chars);
1178                            Ok(Some(Token::make_word_owned(s, None)))
1179                        }
1180                    }
1181                }
1182                q @ 'Q' | q @ 'q' if self.dialect.supports_quote_delimited_string() => {
1183                    chars.next(); // consume and check the next char
1184                    if let Some('\'') = chars.peek() {
1185                        self.tokenize_quote_delimited_string(chars, &[q])
1186                            .map(|s| Some(Token::QuoteDelimitedStringLiteral(s)))
1187                    } else {
1188                        let s = self.tokenize_word(q, chars);
1189                        Ok(Some(Token::make_word_owned(s, None)))
1190                    }
1191                }
1192                // PostgreSQL accepts "escape" string constants, which are an extension to the SQL standard.
1193                x @ 'e' | x @ 'E' if self.dialect.supports_string_escape_constant() => {
1194                    let starting_loc = chars.location();
1195                    chars.next(); // consume, to check the next char
1196                    match chars.peek() {
1197                        Some('\'') => {
1198                            let s =
1199                                self.tokenize_escaped_single_quoted_string(starting_loc, chars)?;
1200                            Ok(Some(Token::EscapedStringLiteral(s)))
1201                        }
1202                        _ => {
1203                            // regular identifier starting with an "E" or "e"
1204                            let s = self.tokenize_word(x, chars);
1205                            Ok(Some(Token::make_word_owned(s, None)))
1206                        }
1207                    }
1208                }
1209                // Unicode string literals like U&'first \000A second' are supported in some dialects, including PostgreSQL
1210                x @ 'u' | x @ 'U' if self.dialect.supports_unicode_string_literal() => {
1211                    chars.next(); // consume, to check the next char
1212                    if chars.peek() == Some(&'&') {
1213                        // we cannot advance the iterator here, as we need to consume the '&' later if the 'u' was an identifier
1214                        let mut chars_clone = chars.peekable.clone();
1215                        chars_clone.next(); // consume the '&' in the clone
1216                        if chars_clone.peek() == Some(&'\'') {
1217                            chars.next(); // consume the '&' in the original iterator
1218                            let s = unescape_unicode_single_quoted_string(chars)?;
1219                            return Ok(Some(Token::UnicodeStringLiteral(s)));
1220                        }
1221                    }
1222                    // regular identifier starting with an "U" or "u"
1223                    let s = self.tokenize_word(x, chars);
1224                    Ok(Some(Token::make_word_owned(s, None)))
1225                }
1226                // The spec only allows an uppercase 'X' to introduce a hex
1227                // string, but PostgreSQL, at least, allows a lowercase 'x' too.
1228                x @ 'x' | x @ 'X' => {
1229                    chars.next(); // consume, to check the next char
1230                    match chars.peek() {
1231                        Some('\'') => {
1232                            // X'...' - a <binary string literal>
1233                            let s = self.tokenize_single_quoted_string(chars, '\'', true)?;
1234                            Ok(Some(Token::HexStringLiteral(s)))
1235                        }
1236                        _ => {
1237                            // regular identifier starting with an "X"
1238                            let s = self.tokenize_word(x, chars);
1239                            Ok(Some(Token::make_word_owned(s, None)))
1240                        }
1241                    }
1242                }
1243                // single quoted string
1244                '\'' => {
1245                    if self.dialect.supports_triple_quoted_string() {
1246                        return self
1247                            .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1248                                chars,
1249                                '\'',
1250                                self.dialect.supports_string_literal_backslash_escape(),
1251                                Token::SingleQuotedString,
1252                                Token::TripleSingleQuotedString,
1253                            );
1254                    }
1255                    let s = self.tokenize_single_quoted_string(
1256                        chars,
1257                        '\'',
1258                        self.dialect.supports_string_literal_backslash_escape(),
1259                    )?;
1260
1261                    Ok(Some(Token::SingleQuotedString(s)))
1262                }
1263                // double quoted string
1264                '\"' if !self.dialect.is_delimited_identifier_start(ch)
1265                    && !self.dialect.is_identifier_start(ch) =>
1266                {
1267                    if self.dialect.supports_triple_quoted_string() {
1268                        return self
1269                            .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1270                                chars,
1271                                '"',
1272                                self.dialect.supports_string_literal_backslash_escape(),
1273                                Token::DoubleQuotedString,
1274                                Token::TripleDoubleQuotedString,
1275                            );
1276                    }
1277                    let s = self.tokenize_single_quoted_string(
1278                        chars,
1279                        '"',
1280                        self.dialect.supports_string_literal_backslash_escape(),
1281                    )?;
1282
1283                    Ok(Some(Token::DoubleQuotedString(s)))
1284                }
1285                // delimited (quoted) identifier
1286                quote_start if self.dialect.is_delimited_identifier_start(ch) => {
1287                    let word = self.tokenize_quoted_identifier(quote_start, chars)?;
1288                    Ok(Some(Token::make_word_owned(word, Some(quote_start))))
1289                }
1290                // Potentially nested delimited (quoted) identifier
1291                quote_start
1292                    if self
1293                        .dialect
1294                        .is_nested_delimited_identifier_start(quote_start)
1295                        && self
1296                            .dialect
1297                            .peek_nested_delimited_identifier_quotes(chars.peekable.clone())
1298                            .is_some() =>
1299                {
1300                    let Some((quote_start, nested_quote_start)) = self
1301                        .dialect
1302                        .peek_nested_delimited_identifier_quotes(chars.peekable.clone())
1303                    else {
1304                        return self.tokenizer_error(
1305                            chars.location(),
1306                            format!("Expected nested delimiter '{quote_start}' before EOF."),
1307                        );
1308                    };
1309
1310                    let Some(nested_quote_start) = nested_quote_start else {
1311                        let word = self.tokenize_quoted_identifier(quote_start, chars)?;
1312                        return Ok(Some(Token::make_word_owned(word, Some(quote_start))));
1313                    };
1314
1315                    let mut word = vec![];
1316                    let quote_end = Word::matching_end_quote(quote_start);
1317                    let nested_quote_end = Word::matching_end_quote(nested_quote_start);
1318                    let error_loc = chars.location();
1319
1320                    chars.next(); // skip the first delimiter
1321                    peeking_take_while(chars, |ch| ch.is_whitespace());
1322                    if chars.peek() != Some(&nested_quote_start) {
1323                        return self.tokenizer_error(
1324                            error_loc,
1325                            format!("Expected nested delimiter '{nested_quote_start}' before EOF."),
1326                        );
1327                    }
1328                    word.push(nested_quote_start.into());
1329                    word.push(self.tokenize_quoted_identifier(nested_quote_end, chars)?);
1330                    word.push(nested_quote_end.into());
1331                    peeking_take_while(chars, |ch| ch.is_whitespace());
1332                    if chars.peek() != Some(&quote_end) {
1333                        return self.tokenizer_error(
1334                            error_loc,
1335                            format!("Expected close delimiter '{quote_end}' before EOF."),
1336                        );
1337                    }
1338                    chars.next(); // skip close delimiter
1339
1340                    Ok(Some(Token::make_word_owned(
1341                        word.concat(),
1342                        Some(quote_start),
1343                    )))
1344                }
1345                // numbers and period
1346                '0'..='9' | '.' => {
1347                    // special case where if ._ is encountered after a word then that word
1348                    // is a table and the _ is the start of the col name.
1349                    // if the prev token is not a word, then this is not a valid sql
1350                    // word or number.
1351                    if ch == '.' && chars.peekable.clone().nth(1) == Some('_') {
1352                        if let Some(Token::Word(_)) = prev_token {
1353                            chars.next();
1354                            return Ok(Some(Token::Period));
1355                        }
1356
1357                        return self.tokenizer_error(
1358                            chars.location(),
1359                            "Unexpected character '_'".to_string(),
1360                        );
1361                    }
1362
1363                    // Some dialects support underscore as number separator
1364                    // There can only be one at a time and it must be followed by another digit
1365                    let is_number_separator = |ch: char, next_char: Option<char>| {
1366                        self.dialect.supports_numeric_literal_underscores()
1367                            && ch == '_'
1368                            && next_char.is_some_and(|next_ch| next_ch.is_ascii_hexdigit())
1369                    };
1370
1371                    let mut s = peeking_next_take_while(chars, |ch, next_ch| {
1372                        ch.is_ascii_digit() || is_number_separator(ch, next_ch)
1373                    });
1374
1375                    // match binary literal that starts with 0x
1376                    if s == "0" && chars.peek() == Some(&'x') {
1377                        chars.next();
1378                        let s2 = peeking_next_take_while(chars, |ch, next_ch| {
1379                            ch.is_ascii_hexdigit() || is_number_separator(ch, next_ch)
1380                        });
1381                        return Ok(Some(Token::HexStringLiteral(s2)));
1382                    }
1383
1384                    // match one period
1385                    if let Some('.') = chars.peek() {
1386                        s.push('.');
1387                        chars.next();
1388                    }
1389
1390                    // If the dialect supports identifiers that start with a numeric prefix
1391                    // and we have now consumed a dot, check if the previous token was a Word.
1392                    // If so, what follows is definitely not part of a decimal number and
1393                    // we should yield the dot as a dedicated token so compound identifiers
1394                    // starting with digits can be parsed correctly.
1395                    if s == "." && self.dialect.supports_numeric_prefix() {
1396                        if let Some(Token::Word(_)) = prev_token {
1397                            return Ok(Some(Token::Period));
1398                        }
1399                    }
1400
1401                    // Consume fractional digits.
1402                    s += &peeking_next_take_while(chars, |ch, next_ch| {
1403                        ch.is_ascii_digit() || is_number_separator(ch, next_ch)
1404                    });
1405
1406                    // No fraction -> Token::Period
1407                    if s == "." {
1408                        return Ok(Some(Token::Period));
1409                    }
1410
1411                    // Parse exponent as number
1412                    let mut exponent_part = String::new();
1413                    if chars.peek() == Some(&'e') || chars.peek() == Some(&'E') {
1414                        let mut char_clone = chars.peekable.clone();
1415                        exponent_part.push(char_clone.next().unwrap());
1416
1417                        // Optional sign
1418                        match char_clone.peek() {
1419                            Some(&c) if matches!(c, '+' | '-') => {
1420                                exponent_part.push(c);
1421                                char_clone.next();
1422                            }
1423                            _ => (),
1424                        }
1425
1426                        match char_clone.peek() {
1427                            // Definitely an exponent, get original iterator up to speed and use it
1428                            Some(&c) if c.is_ascii_digit() => {
1429                                for _ in 0..exponent_part.len() {
1430                                    chars.next();
1431                                }
1432                                exponent_part +=
1433                                    &peeking_take_while(chars, |ch| ch.is_ascii_digit());
1434                                s += exponent_part.as_str();
1435                            }
1436                            // Not an exponent, discard the work done
1437                            _ => (),
1438                        }
1439                    }
1440
1441                    // If the dialect supports identifiers that start with a numeric prefix,
1442                    // we need to check if the value is in fact an identifier and must thus
1443                    // be tokenized as a word.
1444                    if self.dialect.supports_numeric_prefix() {
1445                        if exponent_part.is_empty() {
1446                            // If it is not a number with an exponent, it may be
1447                            // an identifier starting with digits.
1448                            let word =
1449                                peeking_take_while(chars, |ch| self.dialect.is_identifier_part(ch));
1450
1451                            if !word.is_empty() {
1452                                s += word.as_str();
1453                                return Ok(Some(Token::make_word_owned(s, None)));
1454                            }
1455                        } else if prev_token == Some(&Token::Period) {
1456                            // If the previous token was a period, thus not belonging to a number,
1457                            // the value we have is part of an identifier.
1458                            return Ok(Some(Token::make_word_owned(s, None)));
1459                        }
1460                    }
1461
1462                    let long = if chars.peek() == Some(&'L') {
1463                        chars.next();
1464                        true
1465                    } else {
1466                        false
1467                    };
1468                    Ok(Some(Token::Number(s, long)))
1469                }
1470                // punctuation
1471                '(' => self.consume_and_return(chars, Token::LParen),
1472                ')' => self.consume_and_return(chars, Token::RParen),
1473                ',' => self.consume_and_return(chars, Token::Comma),
1474                // operators
1475                '-' => {
1476                    chars.next(); // consume the '-'
1477
1478                    match chars.peek() {
1479                        Some('-') => {
1480                            let mut is_comment = true;
1481                            if self.dialect.requires_single_line_comment_whitespace() {
1482                                is_comment = chars
1483                                    .peekable
1484                                    .clone()
1485                                    .nth(1)
1486                                    .is_some_and(char::is_whitespace);
1487                            }
1488
1489                            if is_comment {
1490                                chars.next(); // consume second '-'
1491                                let comment = self.tokenize_single_line_comment(chars);
1492                                return Ok(Some(Token::Whitespace(
1493                                    Whitespace::SingleLineComment {
1494                                        prefix: "--".to_owned(),
1495                                        comment,
1496                                    },
1497                                )));
1498                            }
1499
1500                            self.start_binop(chars, "-", Token::Minus)
1501                        }
1502                        Some('>') => {
1503                            chars.next();
1504                            match chars.peek() {
1505                                Some('>') => self.consume_for_binop(chars, "->>", Token::LongArrow),
1506                                _ => self.start_binop(chars, "->", Token::Arrow),
1507                            }
1508                        }
1509                        // a regular '-' operator
1510                        _ => self.start_binop(chars, "-", Token::Minus),
1511                    }
1512                }
1513                '/' => {
1514                    chars.next(); // consume the '/'
1515                    match chars.peek() {
1516                        Some('*') => {
1517                            chars.next(); // consume the '*', starting a multi-line comment
1518                            self.tokenize_multiline_comment(chars)
1519                        }
1520                        Some('/') if dialect_of!(self is SnowflakeDialect) => {
1521                            chars.next(); // consume the second '/', starting a snowflake single-line comment
1522                            let comment = self.tokenize_single_line_comment(chars);
1523                            Ok(Some(Token::Whitespace(Whitespace::SingleLineComment {
1524                                prefix: "//".to_owned(),
1525                                comment,
1526                            })))
1527                        }
1528                        Some('/') if dialect_of!(self is DuckDbDialect | GenericDialect) => {
1529                            self.consume_and_return(chars, Token::DuckIntDiv)
1530                        }
1531                        // a regular '/' operator
1532                        _ => Ok(Some(Token::Div)),
1533                    }
1534                }
1535                '+' => self.consume_and_return(chars, Token::Plus),
1536                '*' => self.consume_and_return(chars, Token::Mul),
1537                '%' => {
1538                    chars.next(); // advance past '%'
1539                    match chars.peek() {
1540                        Some(s) if s.is_whitespace() => Ok(Some(Token::Mod)),
1541                        Some(sch) if self.dialect.is_identifier_start('%') => {
1542                            self.tokenize_identifier_or_keyword([ch, *sch], chars)
1543                        }
1544                        _ => self.start_binop(chars, "%", Token::Mod),
1545                    }
1546                }
1547                '|' => {
1548                    chars.next(); // consume the '|'
1549                    match chars.peek() {
1550                        Some('/') => self.consume_for_binop(chars, "|/", Token::PGSquareRoot),
1551                        Some('|') => {
1552                            chars.next(); // consume the second '|'
1553                            match chars.peek() {
1554                                Some('/') => {
1555                                    self.consume_for_binop(chars, "||/", Token::PGCubeRoot)
1556                                }
1557                                _ => self.start_binop(chars, "||", Token::StringConcat),
1558                            }
1559                        }
1560                        Some('&') if self.dialect.supports_geometric_types() => {
1561                            chars.next(); // consume
1562                            match chars.peek() {
1563                                Some('>') => self.consume_for_binop(
1564                                    chars,
1565                                    "|&>",
1566                                    Token::VerticalBarAmpersandRightAngleBracket,
1567                                ),
1568                                _ => self.start_binop_opt(chars, "|&", None),
1569                            }
1570                        }
1571                        Some('>') if self.dialect.supports_geometric_types() => {
1572                            chars.next(); // consume
1573                            match chars.peek() {
1574                                Some('>') => self.consume_for_binop(
1575                                    chars,
1576                                    "|>>",
1577                                    Token::VerticalBarShiftRight,
1578                                ),
1579                                _ => self.start_binop_opt(chars, "|>", None),
1580                            }
1581                        }
1582                        Some('>') if self.dialect.supports_pipe_operator() => {
1583                            self.consume_for_binop(chars, "|>", Token::VerticalBarRightAngleBracket)
1584                        }
1585                        // Bitshift '|' operator
1586                        _ => self.start_binop(chars, "|", Token::Pipe),
1587                    }
1588                }
1589                '=' => {
1590                    chars.next(); // consume
1591                    match chars.peek() {
1592                        Some('>') => self.consume_and_return(chars, Token::RArrow),
1593                        Some('=') => self.consume_and_return(chars, Token::DoubleEq),
1594                        _ => Ok(Some(Token::Eq)),
1595                    }
1596                }
1597                '!' => {
1598                    chars.next(); // consume
1599                    match chars.peek() {
1600                        Some('=') => self.consume_and_return(chars, Token::Neq),
1601                        Some('!') => self.consume_and_return(chars, Token::DoubleExclamationMark),
1602                        Some('~') => {
1603                            chars.next();
1604                            match chars.peek() {
1605                                Some('*') => self
1606                                    .consume_and_return(chars, Token::ExclamationMarkTildeAsterisk),
1607                                Some('~') => {
1608                                    chars.next();
1609                                    match chars.peek() {
1610                                        Some('*') => self.consume_and_return(
1611                                            chars,
1612                                            Token::ExclamationMarkDoubleTildeAsterisk,
1613                                        ),
1614                                        _ => Ok(Some(Token::ExclamationMarkDoubleTilde)),
1615                                    }
1616                                }
1617                                _ => Ok(Some(Token::ExclamationMarkTilde)),
1618                            }
1619                        }
1620                        _ => Ok(Some(Token::ExclamationMark)),
1621                    }
1622                }
1623                '<' => {
1624                    chars.next(); // consume
1625                    match chars.peek() {
1626                        Some('=') => {
1627                            chars.next();
1628                            match chars.peek() {
1629                                Some('>') => self.consume_for_binop(chars, "<=>", Token::Spaceship),
1630                                // `<=+` and `<=-` are not valid combined operators; treat `<=` as
1631                                // the operator and leave `+`/`-` to be tokenized separately.
1632                                Some('+') | Some('-') => Ok(Some(Token::LtEq)),
1633                                _ => self.start_binop(chars, "<=", Token::LtEq),
1634                            }
1635                        }
1636                        Some('|') if self.dialect.supports_geometric_types() => {
1637                            self.consume_for_binop(chars, "<<|", Token::ShiftLeftVerticalBar)
1638                        }
1639                        Some('>') => self.consume_for_binop(chars, "<>", Token::Neq),
1640                        Some('<') if self.dialect.supports_geometric_types() => {
1641                            chars.next(); // consume
1642                            match chars.peek() {
1643                                Some('|') => self.consume_for_binop(
1644                                    chars,
1645                                    "<<|",
1646                                    Token::ShiftLeftVerticalBar,
1647                                ),
1648                                _ => self.start_binop(chars, "<<", Token::ShiftLeft),
1649                            }
1650                        }
1651                        Some('<') => self.consume_for_binop(chars, "<<", Token::ShiftLeft),
1652                        // `<+` is not a valid combined operator; treat `<` as the operator
1653                        // and leave `+` to be tokenized separately.
1654                        Some('+') => Ok(Some(Token::Lt)),
1655                        Some('-') if self.dialect.supports_geometric_types() => {
1656                            if chars.peekable.clone().nth(1) == Some('>') {
1657                                chars.next(); // consume `-`
1658                                self.consume_for_binop(chars, "<->", Token::TwoWayArrow)
1659                            } else {
1660                                Ok(Some(Token::Lt))
1661                            }
1662                        }
1663                        Some('^') if self.dialect.supports_geometric_types() => {
1664                            self.consume_for_binop(chars, "<^", Token::LeftAngleBracketCaret)
1665                        }
1666                        Some('@') => self.consume_for_binop(chars, "<@", Token::ArrowAt),
1667                        _ => self.start_binop(chars, "<", Token::Lt),
1668                    }
1669                }
1670                '>' => {
1671                    chars.next(); // consume
1672                    match chars.peek() {
1673                        Some('=') => self.consume_for_binop(chars, ">=", Token::GtEq),
1674                        Some('>') => self.consume_for_binop(chars, ">>", Token::ShiftRight),
1675                        Some('^') if self.dialect.supports_geometric_types() => {
1676                            self.consume_for_binop(chars, ">^", Token::RightAngleBracketCaret)
1677                        }
1678                        _ => self.start_binop(chars, ">", Token::Gt),
1679                    }
1680                }
1681                ':' => {
1682                    chars.next();
1683                    match chars.peek() {
1684                        Some(':') => self.consume_and_return(chars, Token::DoubleColon),
1685                        Some('=') => self.consume_and_return(chars, Token::Assignment),
1686                        _ => Ok(Some(Token::Colon)),
1687                    }
1688                }
1689                ';' => self.consume_and_return(chars, Token::SemiColon),
1690                '\\' => self.consume_and_return(chars, Token::Backslash),
1691                '[' => self.consume_and_return(chars, Token::LBracket),
1692                ']' => self.consume_and_return(chars, Token::RBracket),
1693                '&' => {
1694                    chars.next(); // consume the '&'
1695                    match chars.peek() {
1696                        Some('>') if self.dialect.supports_geometric_types() => {
1697                            chars.next();
1698                            self.consume_and_return(chars, Token::AmpersandRightAngleBracket)
1699                        }
1700                        Some('<') if self.dialect.supports_geometric_types() => {
1701                            chars.next(); // consume
1702                            match chars.peek() {
1703                                Some('|') => self.consume_and_return(
1704                                    chars,
1705                                    Token::AmpersandLeftAngleBracketVerticalBar,
1706                                ),
1707                                _ => {
1708                                    self.start_binop(chars, "&<", Token::AmpersandLeftAngleBracket)
1709                                }
1710                            }
1711                        }
1712                        Some('&') => {
1713                            chars.next(); // consume the second '&'
1714                            self.start_binop(chars, "&&", Token::Overlap)
1715                        }
1716                        // Bitshift '&' operator
1717                        _ => self.start_binop(chars, "&", Token::Ampersand),
1718                    }
1719                }
1720                '^' => {
1721                    chars.next(); // consume the '^'
1722                    match chars.peek() {
1723                        Some('@') => self.consume_and_return(chars, Token::CaretAt),
1724                        _ => Ok(Some(Token::Caret)),
1725                    }
1726                }
1727                '{' => self.consume_and_return(chars, Token::LBrace),
1728                '}' => self.consume_and_return(chars, Token::RBrace),
1729                '#' if dialect_of!(self is SnowflakeDialect | BigQueryDialect | MySqlDialect | HiveDialect) =>
1730                {
1731                    chars.next(); // consume the '#', starting a snowflake single-line comment
1732                    let comment = self.tokenize_single_line_comment(chars);
1733                    Ok(Some(Token::Whitespace(Whitespace::SingleLineComment {
1734                        prefix: "#".to_owned(),
1735                        comment,
1736                    })))
1737                }
1738                '~' => {
1739                    chars.next(); // consume
1740                    match chars.peek() {
1741                        Some('*') => self.consume_for_binop(chars, "~*", Token::TildeAsterisk),
1742                        Some('=') if self.dialect.supports_geometric_types() => {
1743                            self.consume_for_binop(chars, "~=", Token::TildeEqual)
1744                        }
1745                        Some('~') => {
1746                            chars.next();
1747                            match chars.peek() {
1748                                Some('*') => {
1749                                    self.consume_for_binop(chars, "~~*", Token::DoubleTildeAsterisk)
1750                                }
1751                                _ => self.start_binop(chars, "~~", Token::DoubleTilde),
1752                            }
1753                        }
1754                        _ => self.start_binop(chars, "~", Token::Tilde),
1755                    }
1756                }
1757                '#' => {
1758                    chars.next();
1759                    match chars.peek() {
1760                        Some('-') => self.consume_for_binop(chars, "#-", Token::HashMinus),
1761                        Some('>') => {
1762                            chars.next();
1763                            match chars.peek() {
1764                                Some('>') => {
1765                                    self.consume_for_binop(chars, "#>>", Token::HashLongArrow)
1766                                }
1767                                _ => self.start_binop(chars, "#>", Token::HashArrow),
1768                            }
1769                        }
1770                        Some(' ') => Ok(Some(Token::Sharp)),
1771                        Some('#') if self.dialect.supports_geometric_types() => {
1772                            self.consume_for_binop(chars, "##", Token::DoubleSharp)
1773                        }
1774                        Some(sch) if self.dialect.is_identifier_start('#') => {
1775                            self.tokenize_identifier_or_keyword([ch, *sch], chars)
1776                        }
1777                        _ => self.start_binop(chars, "#", Token::Sharp),
1778                    }
1779                }
1780                '@' => {
1781                    chars.next();
1782                    match chars.peek() {
1783                        Some('@') if self.dialect.supports_geometric_types() => {
1784                            self.consume_and_return(chars, Token::AtAt)
1785                        }
1786                        Some('-') if self.dialect.supports_geometric_types() => {
1787                            chars.next();
1788                            match chars.peek() {
1789                                Some('@') => self.consume_and_return(chars, Token::AtDashAt),
1790                                _ => self.start_binop_opt(chars, "@-", None),
1791                            }
1792                        }
1793                        Some('>') => self.consume_and_return(chars, Token::AtArrow),
1794                        Some('?') => self.consume_and_return(chars, Token::AtQuestion),
1795                        Some('@') => {
1796                            chars.next();
1797                            match chars.peek() {
1798                                Some(' ') => Ok(Some(Token::AtAt)),
1799                                Some(tch) if self.dialect.is_identifier_start('@') => {
1800                                    self.tokenize_identifier_or_keyword([ch, '@', *tch], chars)
1801                                }
1802                                _ => Ok(Some(Token::AtAt)),
1803                            }
1804                        }
1805                        Some(' ') => Ok(Some(Token::AtSign)),
1806                        // We break on quotes here, because no dialect allows identifiers starting
1807                        // with @ and containing quotation marks (e.g. `@'foo'`) unless they are
1808                        // quoted, which is tokenized as a quoted string, not here (e.g.
1809                        // `"@'foo'"`). Further, at least two dialects parse `@` followed by a
1810                        // quoted string as two separate tokens, which this allows. For example,
1811                        // Postgres parses `@'1'` as the absolute value of '1' which is implicitly
1812                        // cast to a numeric type. And when parsing MySQL-style grantees (e.g.
1813                        // `GRANT ALL ON *.* to 'root'@'localhost'`), we also want separate tokens
1814                        // for the user, the `@`, and the host.
1815                        Some('\'') => Ok(Some(Token::AtSign)),
1816                        Some('\"') => Ok(Some(Token::AtSign)),
1817                        Some('`') => Ok(Some(Token::AtSign)),
1818                        Some(sch) if self.dialect.is_identifier_start('@') => {
1819                            self.tokenize_identifier_or_keyword([ch, *sch], chars)
1820                        }
1821                        _ => Ok(Some(Token::AtSign)),
1822                    }
1823                }
1824                // Postgres uses ? for jsonb operators, not prepared statements
1825                '?' if self.dialect.supports_geometric_types() => {
1826                    chars.next(); // consume
1827                    match chars.peek() {
1828                        Some('|') => {
1829                            chars.next();
1830                            match chars.peek() {
1831                                Some('|') => self.consume_and_return(
1832                                    chars,
1833                                    Token::QuestionMarkDoubleVerticalBar,
1834                                ),
1835                                _ => Ok(Some(Token::QuestionPipe)),
1836                            }
1837                        }
1838
1839                        Some('&') => self.consume_and_return(chars, Token::QuestionAnd),
1840                        Some('-') => {
1841                            chars.next(); // consume
1842                            match chars.peek() {
1843                                Some('|') => self
1844                                    .consume_and_return(chars, Token::QuestionMarkDashVerticalBar),
1845                                _ => Ok(Some(Token::QuestionMarkDash)),
1846                            }
1847                        }
1848                        Some('#') => self.consume_and_return(chars, Token::QuestionMarkSharp),
1849                        _ => Ok(Some(Token::Question)),
1850                    }
1851                }
1852                '?' => {
1853                    chars.next();
1854                    let s = peeking_take_while(chars, |ch| ch.is_numeric());
1855                    Ok(Some(Token::Placeholder(format!("?{s}"))))
1856                }
1857
1858                // identifier or keyword
1859                ch if self.dialect.is_identifier_start(ch) => {
1860                    self.tokenize_identifier_or_keyword([ch], chars)
1861                }
1862                '$' => Ok(Some(self.tokenize_dollar_preceded_value(chars)?)),
1863
1864                // whitespace check (including unicode chars) should be last as it covers some of the chars above
1865                ch if ch.is_whitespace() => {
1866                    self.consume_and_return(chars, Token::Whitespace(Whitespace::Space))
1867                }
1868                other => self.consume_and_return(chars, Token::Char(other)),
1869            },
1870            None => Ok(None),
1871        }
1872    }
1873
1874    /// Consume the next character, then parse a custom binary operator. The next character should be included in the prefix
1875    fn consume_for_binop(
1876        &self,
1877        chars: &mut State,
1878        prefix: &str,
1879        default: Token,
1880    ) -> Result<Option<Token>, TokenizerError> {
1881        chars.next(); // consume the first char
1882        self.start_binop_opt(chars, prefix, Some(default))
1883    }
1884
1885    /// parse a custom binary operator
1886    fn start_binop(
1887        &self,
1888        chars: &mut State,
1889        prefix: &str,
1890        default: Token,
1891    ) -> Result<Option<Token>, TokenizerError> {
1892        self.start_binop_opt(chars, prefix, Some(default))
1893    }
1894
1895    /// parse a custom binary operator
1896    fn start_binop_opt(
1897        &self,
1898        chars: &mut State,
1899        prefix: &str,
1900        default: Option<Token>,
1901    ) -> Result<Option<Token>, TokenizerError> {
1902        let mut custom = None;
1903        while let Some(&ch) = chars.peek() {
1904            if !self.dialect.is_custom_operator_part(ch) {
1905                break;
1906            }
1907
1908            custom.get_or_insert_with(|| prefix.to_string()).push(ch);
1909            chars.next();
1910        }
1911        match (custom, default) {
1912            (Some(custom), _) => Ok(Token::CustomBinaryOperator(custom).into()),
1913            (None, Some(tok)) => Ok(Some(tok)),
1914            (None, None) => self.tokenizer_error(
1915                chars.location(),
1916                format!("Expected a valid binary operator after '{prefix}'"),
1917            ),
1918        }
1919    }
1920
1921    /// Tokenize dollar preceded value (i.e: a string/placeholder)
1922    fn tokenize_dollar_preceded_value(&self, chars: &mut State) -> Result<Token, TokenizerError> {
1923        let mut s = String::new();
1924        let mut value = String::new();
1925
1926        chars.next();
1927
1928        // If the dialect does not support dollar-quoted strings, then `$$` is rather a placeholder.
1929        if matches!(chars.peek(), Some('$')) && !self.dialect.supports_dollar_placeholder() {
1930            chars.next();
1931
1932            let mut is_terminated = false;
1933            let mut prev: Option<char> = None;
1934
1935            while let Some(&ch) = chars.peek() {
1936                if prev == Some('$') {
1937                    if ch == '$' {
1938                        chars.next();
1939                        is_terminated = true;
1940                        break;
1941                    } else {
1942                        s.push('$');
1943                        s.push(ch);
1944                    }
1945                } else if ch != '$' {
1946                    s.push(ch);
1947                }
1948
1949                prev = Some(ch);
1950                chars.next();
1951            }
1952
1953            return if chars.peek().is_none() && !is_terminated {
1954                self.tokenizer_error(chars.location(), "Unterminated dollar-quoted string")
1955            } else {
1956                Ok(Token::DollarQuotedString(DollarQuotedString {
1957                    value: s,
1958                    tag: None,
1959                }))
1960            };
1961        } else {
1962            value.push_str(&peeking_take_while(chars, |ch| {
1963                ch.is_alphanumeric()
1964                    || ch == '_'
1965                    // Allow $ as a placeholder character if the dialect supports it
1966                    || matches!(ch, '$' if self.dialect.supports_dollar_placeholder())
1967            }));
1968
1969            // If the dialect supports a dollar sign as a money prefix (e.g. SQL Server),
1970            // and the value so far is all digits, check for a decimal part, e.g. `$123.45`
1971            if matches!(chars.peek(), Some('.'))
1972                && self.dialect.supports_dollar_as_money_prefix()
1973                && !value.is_empty()
1974                && value.chars().all(|c| c.is_ascii_digit())
1975            {
1976                value.push('.');
1977                chars.next();
1978                value.push_str(&peeking_take_while(chars, |ch| ch.is_ascii_digit()));
1979                return Ok(Token::Placeholder(format!("${value}")));
1980            }
1981
1982            // If the dialect does not support dollar-quoted strings, don't look for the end delimiter.
1983            if matches!(chars.peek(), Some('$')) && !self.dialect.supports_dollar_placeholder() {
1984                chars.next();
1985
1986                let mut temp = String::new();
1987                let end_delimiter = format!("${value}$");
1988
1989                loop {
1990                    match chars.next() {
1991                        Some(ch) => {
1992                            temp.push(ch);
1993
1994                            if temp.ends_with(&end_delimiter) {
1995                                if let Some(temp) = temp.strip_suffix(&end_delimiter) {
1996                                    s.push_str(temp);
1997                                }
1998                                break;
1999                            }
2000                        }
2001                        None => {
2002                            if temp.ends_with(&end_delimiter) {
2003                                if let Some(temp) = temp.strip_suffix(&end_delimiter) {
2004                                    s.push_str(temp);
2005                                }
2006                                break;
2007                            }
2008
2009                            return self.tokenizer_error(
2010                                chars.location(),
2011                                "Unterminated dollar-quoted, expected $",
2012                            );
2013                        }
2014                    }
2015                }
2016            } else {
2017                return Ok(Token::Placeholder(format!("${value}")));
2018            }
2019        }
2020
2021        Ok(Token::DollarQuotedString(DollarQuotedString {
2022            value: s,
2023            tag: if value.is_empty() { None } else { Some(value) },
2024        }))
2025    }
2026
2027    fn tokenizer_error<R>(
2028        &self,
2029        loc: Location,
2030        message: impl Into<String>,
2031    ) -> Result<R, TokenizerError> {
2032        Err(TokenizerError {
2033            message: message.into(),
2034            location: loc,
2035        })
2036    }
2037
2038    // Consume characters until newline
2039    fn tokenize_single_line_comment(&self, chars: &mut State) -> String {
2040        peeking_take_while(chars, |ch| match ch {
2041            '\n' => false,                                           // Always stop at \n
2042            '\r' if dialect_of!(self is PostgreSqlDialect) => false, // Stop at \r for Postgres
2043            _ => true, // Keep consuming for other characters
2044        })
2045    }
2046
2047    /// Tokenize an identifier or keyword, after the first char is already consumed.
2048    fn tokenize_word(&self, first_chars: impl Into<String>, chars: &mut State) -> String {
2049        let mut s = first_chars.into();
2050        s.push_str(&peeking_take_while(chars, |ch| {
2051            self.dialect.is_identifier_part(ch)
2052        }));
2053        s
2054    }
2055
2056    /// Read a quoted identifier
2057    fn tokenize_quoted_identifier(
2058        &self,
2059        quote_start: char,
2060        chars: &mut State,
2061    ) -> Result<String, TokenizerError> {
2062        let error_loc = chars.location();
2063        chars.next(); // consume the opening quote
2064        let quote_end = Word::matching_end_quote(quote_start);
2065        let (s, last_char) = self.parse_quoted_ident(chars, quote_end);
2066
2067        if last_char == Some(quote_end) {
2068            Ok(s)
2069        } else {
2070            self.tokenizer_error(
2071                error_loc,
2072                format!("Expected close delimiter '{quote_end}' before EOF."),
2073            )
2074        }
2075    }
2076
2077    /// Read a single quoted string, starting with the opening quote.
2078    fn tokenize_escaped_single_quoted_string(
2079        &self,
2080        starting_loc: Location,
2081        chars: &mut State,
2082    ) -> Result<String, TokenizerError> {
2083        if let Some(s) = unescape_single_quoted_string(chars) {
2084            return Ok(s);
2085        }
2086
2087        self.tokenizer_error(starting_loc, "Unterminated encoded string literal")
2088    }
2089
2090    /// Reads a string literal quoted by a single or triple quote characters.
2091    /// Examples: `'abc'`, `'''abc'''`, `"""abc"""`.
2092    fn tokenize_single_or_triple_quoted_string<F>(
2093        &self,
2094        chars: &mut State,
2095        quote_style: char,
2096        backslash_escape: bool,
2097        single_quote_token: F,
2098        triple_quote_token: F,
2099    ) -> Result<Option<Token>, TokenizerError>
2100    where
2101        F: Fn(String) -> Token,
2102    {
2103        let error_loc = chars.location();
2104
2105        let mut num_opening_quotes = 0u8;
2106        for _ in 0..3 {
2107            if Some(&quote_style) == chars.peek() {
2108                chars.next(); // Consume quote.
2109                num_opening_quotes += 1;
2110            } else {
2111                break;
2112            }
2113        }
2114
2115        let (token_fn, num_quote_chars) = match num_opening_quotes {
2116            1 => (single_quote_token, NumStringQuoteChars::One),
2117            2 => {
2118                // If we matched double quotes, then this is an empty string.
2119                return Ok(Some(single_quote_token("".into())));
2120            }
2121            3 => {
2122                let Some(num_quote_chars) = NonZeroU8::new(3) else {
2123                    return self.tokenizer_error(error_loc, "invalid number of opening quotes");
2124                };
2125                (
2126                    triple_quote_token,
2127                    NumStringQuoteChars::Many(num_quote_chars),
2128                )
2129            }
2130            _ => {
2131                return self.tokenizer_error(error_loc, "invalid string literal opening");
2132            }
2133        };
2134
2135        let settings = TokenizeQuotedStringSettings {
2136            quote_style,
2137            num_quote_chars,
2138            num_opening_quotes_to_consume: 0,
2139            backslash_escape,
2140        };
2141
2142        self.tokenize_quoted_string(chars, settings)
2143            .map(token_fn)
2144            .map(Some)
2145    }
2146
2147    /// Reads a string literal quoted by a single quote character.
2148    fn tokenize_single_quoted_string(
2149        &self,
2150        chars: &mut State,
2151        quote_style: char,
2152        backslash_escape: bool,
2153    ) -> Result<String, TokenizerError> {
2154        self.tokenize_quoted_string(
2155            chars,
2156            TokenizeQuotedStringSettings {
2157                quote_style,
2158                num_quote_chars: NumStringQuoteChars::One,
2159                num_opening_quotes_to_consume: 1,
2160                backslash_escape,
2161            },
2162        )
2163    }
2164
2165    /// Reads a quote delimited string expecting `chars.next()` to deliver a quote.
2166    ///
2167    /// See <https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA>
2168    fn tokenize_quote_delimited_string(
2169        &self,
2170        chars: &mut State,
2171        // the prefix that introduced the possible literal or word,
2172        // e.g. "Q" or "nq"
2173        literal_prefix: &[char],
2174    ) -> Result<QuoteDelimitedString, TokenizerError> {
2175        let literal_start_loc = chars.location();
2176        chars.next();
2177
2178        let start_quote_loc = chars.location();
2179        let (start_quote, end_quote) = match chars.next() {
2180            None | Some(' ') | Some('\t') | Some('\r') | Some('\n') => {
2181                return self.tokenizer_error(
2182                    start_quote_loc,
2183                    format!(
2184                        "Invalid space, tab, newline, or EOF after '{}''",
2185                        String::from_iter(literal_prefix)
2186                    ),
2187                );
2188            }
2189            Some(c) => (
2190                c,
2191                match c {
2192                    '[' => ']',
2193                    '{' => '}',
2194                    '<' => '>',
2195                    '(' => ')',
2196                    c => c,
2197                },
2198            ),
2199        };
2200
2201        // read the string literal until the "quote character" following a by literal quote
2202        let mut value = String::new();
2203        while let Some(ch) = chars.next() {
2204            if ch == end_quote {
2205                if let Some('\'') = chars.peek() {
2206                    chars.next(); // ~ consume the quote
2207                    return Ok(QuoteDelimitedString {
2208                        start_quote,
2209                        value,
2210                        end_quote,
2211                    });
2212                }
2213            }
2214            value.push(ch);
2215        }
2216
2217        self.tokenizer_error(literal_start_loc, "Unterminated string literal")
2218    }
2219
2220    /// Read a quoted string.
2221    fn tokenize_quoted_string(
2222        &self,
2223        chars: &mut State,
2224        settings: TokenizeQuotedStringSettings,
2225    ) -> Result<String, TokenizerError> {
2226        let mut s = String::new();
2227        let error_loc = chars.location();
2228
2229        // Consume any opening quotes.
2230        for _ in 0..settings.num_opening_quotes_to_consume {
2231            if Some(settings.quote_style) != chars.next() {
2232                return self.tokenizer_error(error_loc, "invalid string literal opening");
2233            }
2234        }
2235
2236        let mut num_consecutive_quotes = 0;
2237        while let Some(&ch) = chars.peek() {
2238            let pending_final_quote = match settings.num_quote_chars {
2239                NumStringQuoteChars::One => Some(NumStringQuoteChars::One),
2240                n @ NumStringQuoteChars::Many(count)
2241                    if num_consecutive_quotes + 1 == count.get() =>
2242                {
2243                    Some(n)
2244                }
2245                NumStringQuoteChars::Many(_) => None,
2246            };
2247
2248            match ch {
2249                char if char == settings.quote_style && pending_final_quote.is_some() => {
2250                    chars.next(); // consume
2251
2252                    if let Some(NumStringQuoteChars::Many(count)) = pending_final_quote {
2253                        // For an initial string like `"""abc"""`, at this point we have
2254                        // `abc""` in the buffer and have now matched the final `"`.
2255                        // However, the string to return is simply `abc`, so we strip off
2256                        // the trailing quotes before returning.
2257                        let mut buf = s.chars();
2258                        for _ in 1..count.get() {
2259                            buf.next_back();
2260                        }
2261                        return Ok(buf.as_str().to_string());
2262                    } else if chars
2263                        .peek()
2264                        .map(|c| *c == settings.quote_style)
2265                        .unwrap_or(false)
2266                    {
2267                        s.push(ch);
2268                        if !self.unescape {
2269                            // In no-escape mode, the given query has to be saved completely
2270                            s.push(ch);
2271                        }
2272                        chars.next();
2273                    } else {
2274                        return Ok(s);
2275                    }
2276                }
2277                '\\' if settings.backslash_escape => {
2278                    // consume backslash
2279                    chars.next();
2280
2281                    num_consecutive_quotes = 0;
2282
2283                    if let Some(next) = chars.peek() {
2284                        if !self.unescape
2285                            || (self.dialect.ignores_wildcard_escapes()
2286                                && (*next == '%' || *next == '_'))
2287                        {
2288                            // In no-escape mode, the given query has to be saved completely
2289                            // including backslashes. Similarly, with ignore_like_wildcard_escapes,
2290                            // the backslash is not stripped.
2291                            s.push(ch);
2292                            s.push(*next);
2293                            chars.next(); // consume next
2294                        } else {
2295                            let n = match next {
2296                                '0' => '\0',
2297                                'a' => '\u{7}',
2298                                'b' => '\u{8}',
2299                                'f' => '\u{c}',
2300                                'n' => '\n',
2301                                'r' => '\r',
2302                                't' => '\t',
2303                                'Z' => '\u{1a}',
2304                                _ => *next,
2305                            };
2306                            s.push(n);
2307                            chars.next(); // consume next
2308                        }
2309                    }
2310                }
2311                ch => {
2312                    chars.next(); // consume ch
2313
2314                    if ch == settings.quote_style {
2315                        num_consecutive_quotes += 1;
2316                    } else {
2317                        num_consecutive_quotes = 0;
2318                    }
2319
2320                    s.push(ch);
2321                }
2322            }
2323        }
2324        self.tokenizer_error(error_loc, "Unterminated string literal")
2325    }
2326
2327    fn tokenize_multiline_comment(
2328        &self,
2329        chars: &mut State,
2330    ) -> Result<Option<Token>, TokenizerError> {
2331        let mut s = String::new();
2332        let mut nested = 1;
2333        let supports_nested_comments = self.dialect.supports_nested_comments();
2334        loop {
2335            match chars.next() {
2336                Some('/') if matches!(chars.peek(), Some('*')) && supports_nested_comments => {
2337                    chars.next(); // consume the '*'
2338                    s.push('/');
2339                    s.push('*');
2340                    nested += 1;
2341                }
2342                Some('*') if matches!(chars.peek(), Some('/')) => {
2343                    chars.next(); // consume the '/'
2344                    nested -= 1;
2345                    if nested == 0 {
2346                        break Ok(Some(Token::Whitespace(Whitespace::MultiLineComment(s))));
2347                    }
2348                    s.push('*');
2349                    s.push('/');
2350                }
2351                Some(ch) => {
2352                    s.push(ch);
2353                }
2354                None => {
2355                    break self.tokenizer_error(
2356                        chars.location(),
2357                        "Unexpected EOF while in a multi-line comment",
2358                    );
2359                }
2360            }
2361        }
2362    }
2363
2364    fn parse_quoted_ident(&self, chars: &mut State, quote_end: char) -> (String, Option<char>) {
2365        let mut last_char = None;
2366        let mut s = String::new();
2367        while let Some(ch) = chars.next() {
2368            if ch == quote_end {
2369                if chars.peek() == Some(&quote_end) {
2370                    chars.next();
2371                    s.push(ch);
2372                    if !self.unescape {
2373                        // In no-escape mode, the given query has to be saved completely
2374                        s.push(ch);
2375                    }
2376                } else {
2377                    last_char = Some(quote_end);
2378                    break;
2379                }
2380            } else {
2381                s.push(ch);
2382            }
2383        }
2384        (s, last_char)
2385    }
2386
2387    #[allow(clippy::unnecessary_wraps)]
2388    fn consume_and_return(
2389        &self,
2390        chars: &mut State,
2391        t: Token,
2392    ) -> Result<Option<Token>, TokenizerError> {
2393        chars.next();
2394        Ok(Some(t))
2395    }
2396}
2397
2398/// Read from `chars` until `predicate` returns `false` or EOF is hit.
2399/// Return the characters read as String, and keep the first non-matching
2400/// char available as `chars.next()`.
2401fn peeking_take_while(chars: &mut State, mut predicate: impl FnMut(char) -> bool) -> String {
2402    let mut s = String::new();
2403    while let Some(&ch) = chars.peek() {
2404        if predicate(ch) {
2405            chars.next(); // consume
2406            s.push(ch);
2407        } else {
2408            break;
2409        }
2410    }
2411    s
2412}
2413
2414/// Same as peeking_take_while, but also passes the next character to the predicate.
2415fn peeking_next_take_while(
2416    chars: &mut State,
2417    mut predicate: impl FnMut(char, Option<char>) -> bool,
2418) -> String {
2419    let mut s = String::new();
2420    while let Some(&ch) = chars.peek() {
2421        let next_char = chars.peekable.clone().nth(1);
2422        if predicate(ch, next_char) {
2423            chars.next(); // consume
2424            s.push(ch);
2425        } else {
2426            break;
2427        }
2428    }
2429    s
2430}
2431
2432fn unescape_single_quoted_string(chars: &mut State<'_>) -> Option<String> {
2433    Unescape::new(chars).unescape()
2434}
2435
2436struct Unescape<'a: 'b, 'b> {
2437    chars: &'b mut State<'a>,
2438}
2439
2440impl<'a: 'b, 'b> Unescape<'a, 'b> {
2441    fn new(chars: &'b mut State<'a>) -> Self {
2442        Self { chars }
2443    }
2444    fn unescape(mut self) -> Option<String> {
2445        let mut unescaped = String::new();
2446
2447        self.chars.next();
2448
2449        while let Some(c) = self.chars.next() {
2450            if c == '\'' {
2451                // case: ''''
2452                if self.chars.peek().map(|c| *c == '\'').unwrap_or(false) {
2453                    self.chars.next();
2454                    unescaped.push('\'');
2455                    continue;
2456                }
2457                return Some(unescaped);
2458            }
2459
2460            if c != '\\' {
2461                unescaped.push(c);
2462                continue;
2463            }
2464
2465            let c = match self.chars.next()? {
2466                'b' => '\u{0008}',
2467                'f' => '\u{000C}',
2468                'n' => '\n',
2469                'r' => '\r',
2470                't' => '\t',
2471                'u' => self.unescape_unicode_16()?,
2472                'U' => self.unescape_unicode_32()?,
2473                'x' => self.unescape_hex()?,
2474                c if c.is_digit(8) => self.unescape_octal(c)?,
2475                c => c,
2476            };
2477
2478            unescaped.push(Self::check_null(c)?);
2479        }
2480
2481        None
2482    }
2483
2484    #[inline]
2485    fn check_null(c: char) -> Option<char> {
2486        if c == '\0' {
2487            None
2488        } else {
2489            Some(c)
2490        }
2491    }
2492
2493    #[inline]
2494    fn byte_to_char<const RADIX: u32>(s: &str) -> Option<char> {
2495        // u32 is used here because Pg has an overflow operation rather than throwing an exception directly.
2496        match u32::from_str_radix(s, RADIX) {
2497            Err(_) => None,
2498            Ok(n) => {
2499                let n = n & 0xFF;
2500                if n <= 127 {
2501                    char::from_u32(n)
2502                } else {
2503                    None
2504                }
2505            }
2506        }
2507    }
2508
2509    // Hexadecimal byte value. \xh, \xhh (h = 0–9, A–F)
2510    fn unescape_hex(&mut self) -> Option<char> {
2511        let mut s = String::new();
2512
2513        for _ in 0..2 {
2514            match self.next_hex_digit() {
2515                Some(c) => s.push(c),
2516                None => break,
2517            }
2518        }
2519
2520        if s.is_empty() {
2521            return Some('x');
2522        }
2523
2524        Self::byte_to_char::<16>(&s)
2525    }
2526
2527    #[inline]
2528    fn next_hex_digit(&mut self) -> Option<char> {
2529        match self.chars.peek() {
2530            Some(c) if c.is_ascii_hexdigit() => self.chars.next(),
2531            _ => None,
2532        }
2533    }
2534
2535    // Octal byte value. \o, \oo, \ooo (o = 0–7)
2536    fn unescape_octal(&mut self, c: char) -> Option<char> {
2537        let mut s = String::new();
2538
2539        s.push(c);
2540        for _ in 0..2 {
2541            match self.next_octal_digest() {
2542                Some(c) => s.push(c),
2543                None => break,
2544            }
2545        }
2546
2547        Self::byte_to_char::<8>(&s)
2548    }
2549
2550    #[inline]
2551    fn next_octal_digest(&mut self) -> Option<char> {
2552        match self.chars.peek() {
2553            Some(c) if c.is_digit(8) => self.chars.next(),
2554            _ => None,
2555        }
2556    }
2557
2558    // 16-bit hexadecimal Unicode character value. \uxxxx (x = 0–9, A–F)
2559    fn unescape_unicode_16(&mut self) -> Option<char> {
2560        self.unescape_unicode::<4>()
2561    }
2562
2563    // 32-bit hexadecimal Unicode character value. \Uxxxxxxxx (x = 0–9, A–F)
2564    fn unescape_unicode_32(&mut self) -> Option<char> {
2565        self.unescape_unicode::<8>()
2566    }
2567
2568    fn unescape_unicode<const NUM: usize>(&mut self) -> Option<char> {
2569        let mut s = String::new();
2570        for _ in 0..NUM {
2571            s.push(self.chars.next()?);
2572        }
2573        match u32::from_str_radix(&s, 16) {
2574            Err(_) => None,
2575            Ok(n) => char::from_u32(n),
2576        }
2577    }
2578}
2579
2580fn unescape_unicode_single_quoted_string(chars: &mut State<'_>) -> Result<String, TokenizerError> {
2581    let mut unescaped = String::new();
2582    chars.next(); // consume the opening quote
2583    while let Some(c) = chars.next() {
2584        match c {
2585            '\'' => {
2586                if chars.peek() == Some(&'\'') {
2587                    chars.next();
2588                    unescaped.push('\'');
2589                } else {
2590                    return Ok(unescaped);
2591                }
2592            }
2593            '\\' => match chars.peek() {
2594                Some('\\') => {
2595                    chars.next();
2596                    unescaped.push('\\');
2597                }
2598                Some('+') => {
2599                    chars.next();
2600                    unescaped.push(take_char_from_hex_digits(chars, 6)?);
2601                }
2602                _ => unescaped.push(take_char_from_hex_digits(chars, 4)?),
2603            },
2604            _ => {
2605                unescaped.push(c);
2606            }
2607        }
2608    }
2609    Err(TokenizerError {
2610        message: "Unterminated unicode encoded string literal".to_string(),
2611        location: chars.location(),
2612    })
2613}
2614
2615fn take_char_from_hex_digits(
2616    chars: &mut State<'_>,
2617    max_digits: usize,
2618) -> Result<char, TokenizerError> {
2619    let mut result = 0u32;
2620    for _ in 0..max_digits {
2621        let next_char = chars.next().ok_or_else(|| TokenizerError {
2622            message: "Unexpected EOF while parsing hex digit in escaped unicode string."
2623                .to_string(),
2624            location: chars.location(),
2625        })?;
2626        let digit = next_char.to_digit(16).ok_or_else(|| TokenizerError {
2627            message: format!("Invalid hex digit in escaped unicode string: {next_char}"),
2628            location: chars.location(),
2629        })?;
2630        result = result * 16 + digit;
2631    }
2632    char::from_u32(result).ok_or_else(|| TokenizerError {
2633        message: format!("Invalid unicode character: {result:x}"),
2634        location: chars.location(),
2635    })
2636}
2637
2638#[cfg(test)]
2639mod tests {
2640    use super::*;
2641    use crate::dialect::{
2642        BigQueryDialect, ClickHouseDialect, HiveDialect, MsSqlDialect, MySqlDialect,
2643        PostgreSqlDialect, SQLiteDialect,
2644    };
2645    use crate::test_utils::{all_dialects, all_dialects_except, all_dialects_where};
2646    use core::fmt::Debug;
2647
2648    #[test]
2649    fn tokenizer_error_impl() {
2650        let err = TokenizerError {
2651            message: "test".into(),
2652            location: Location { line: 1, column: 1 },
2653        };
2654        {
2655            use core::error::Error;
2656            assert!(err.source().is_none());
2657        }
2658        assert_eq!(err.to_string(), "test at Line: 1, Column: 1");
2659    }
2660
2661    #[test]
2662    fn tokenize_select_1() {
2663        let sql = String::from("SELECT 1");
2664        let dialect = GenericDialect {};
2665        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2666
2667        let expected = vec![
2668            Token::make_keyword("SELECT"),
2669            Token::Whitespace(Whitespace::Space),
2670            Token::Number(String::from("1"), false),
2671        ];
2672
2673        compare(expected, tokens);
2674    }
2675
2676    #[test]
2677    fn tokenize_select_float() {
2678        let sql = String::from("SELECT .1");
2679        let dialect = GenericDialect {};
2680        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2681
2682        let expected = vec![
2683            Token::make_keyword("SELECT"),
2684            Token::Whitespace(Whitespace::Space),
2685            Token::Number(String::from(".1"), false),
2686        ];
2687
2688        compare(expected, tokens);
2689    }
2690
2691    #[test]
2692    fn tokenize_with_mapper() {
2693        let sql = String::from("SELECT ?");
2694        let dialect = GenericDialect {};
2695        let mut param_num = 1;
2696
2697        let mut tokens = vec![];
2698        Tokenizer::new(&dialect, &sql)
2699            .tokenize_with_location_into_buf_with_mapper(&mut tokens, |mut token_span| {
2700                token_span.token = match token_span.token {
2701                    Token::Placeholder(n) => Token::Placeholder(if n == "?" {
2702                        let ret = format!("${}", param_num);
2703                        param_num += 1;
2704                        ret
2705                    } else {
2706                        n
2707                    }),
2708                    token => token,
2709                };
2710                token_span
2711            })
2712            .unwrap();
2713        let actual = tokens.into_iter().map(|t| t.token).collect();
2714        let expected = vec![
2715            Token::make_keyword("SELECT"),
2716            Token::Whitespace(Whitespace::Space),
2717            Token::Placeholder("$1".to_string()),
2718        ];
2719
2720        compare(expected, actual);
2721    }
2722
2723    #[test]
2724    fn tokenize_clickhouse_double_equal() {
2725        let sql = String::from("SELECT foo=='1'");
2726        let dialect = ClickHouseDialect {};
2727        let mut tokenizer = Tokenizer::new(&dialect, &sql);
2728        let tokens = tokenizer.tokenize().unwrap();
2729
2730        let expected = vec![
2731            Token::make_keyword("SELECT"),
2732            Token::Whitespace(Whitespace::Space),
2733            Token::Word(Word {
2734                value: "foo".to_string(),
2735                quote_style: None,
2736                keyword: Keyword::NoKeyword,
2737            }),
2738            Token::DoubleEq,
2739            Token::SingleQuotedString("1".to_string()),
2740        ];
2741
2742        compare(expected, tokens);
2743    }
2744
2745    #[test]
2746    fn tokenize_numeric_literal_underscore() {
2747        let dialect = GenericDialect {};
2748        let sql = String::from("SELECT 10_000");
2749        let mut tokenizer = Tokenizer::new(&dialect, &sql);
2750        let tokens = tokenizer.tokenize().unwrap();
2751        let expected = vec![
2752            Token::make_keyword("SELECT"),
2753            Token::Whitespace(Whitespace::Space),
2754            Token::Number("10".to_string(), false),
2755            Token::make_word("_000", None),
2756        ];
2757        compare(expected, tokens);
2758
2759        all_dialects_where(|dialect| dialect.supports_numeric_literal_underscores()).tokenizes_to(
2760            "SELECT 10_000, _10_000, 10_00_, 10___0",
2761            vec![
2762                Token::make_keyword("SELECT"),
2763                Token::Whitespace(Whitespace::Space),
2764                Token::Number("10_000".to_string(), false),
2765                Token::Comma,
2766                Token::Whitespace(Whitespace::Space),
2767                Token::make_word("_10_000", None), // leading underscore tokenizes as a word (parsed as column identifier)
2768                Token::Comma,
2769                Token::Whitespace(Whitespace::Space),
2770                Token::Number("10_00".to_string(), false),
2771                Token::make_word("_", None), // trailing underscores tokenizes as a word (syntax error in some dialects)
2772                Token::Comma,
2773                Token::Whitespace(Whitespace::Space),
2774                Token::Number("10".to_string(), false),
2775                Token::make_word("___0", None), // multiple underscores tokenizes as a word (syntax error in some dialects)
2776            ],
2777        );
2778    }
2779
2780    #[test]
2781    fn tokenize_select_exponent() {
2782        let sql = String::from("SELECT 1e10, 1e-10, 1e+10, 1ea, 1e-10a, 1e-10-10");
2783        let dialect = GenericDialect {};
2784        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2785
2786        let expected = vec![
2787            Token::make_keyword("SELECT"),
2788            Token::Whitespace(Whitespace::Space),
2789            Token::Number(String::from("1e10"), false),
2790            Token::Comma,
2791            Token::Whitespace(Whitespace::Space),
2792            Token::Number(String::from("1e-10"), false),
2793            Token::Comma,
2794            Token::Whitespace(Whitespace::Space),
2795            Token::Number(String::from("1e+10"), false),
2796            Token::Comma,
2797            Token::Whitespace(Whitespace::Space),
2798            Token::Number(String::from("1"), false),
2799            Token::make_word("ea", None),
2800            Token::Comma,
2801            Token::Whitespace(Whitespace::Space),
2802            Token::Number(String::from("1e-10"), false),
2803            Token::make_word("a", None),
2804            Token::Comma,
2805            Token::Whitespace(Whitespace::Space),
2806            Token::Number(String::from("1e-10"), false),
2807            Token::Minus,
2808            Token::Number(String::from("10"), false),
2809        ];
2810
2811        compare(expected, tokens);
2812    }
2813
2814    #[test]
2815    fn tokenize_scalar_function() {
2816        let sql = String::from("SELECT sqrt(1)");
2817        let dialect = GenericDialect {};
2818        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2819
2820        let expected = vec![
2821            Token::make_keyword("SELECT"),
2822            Token::Whitespace(Whitespace::Space),
2823            Token::make_word("sqrt", None),
2824            Token::LParen,
2825            Token::Number(String::from("1"), false),
2826            Token::RParen,
2827        ];
2828
2829        compare(expected, tokens);
2830    }
2831
2832    #[test]
2833    fn tokenize_string_string_concat() {
2834        let sql = String::from("SELECT 'a' || 'b'");
2835        let dialect = GenericDialect {};
2836        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2837
2838        let expected = vec![
2839            Token::make_keyword("SELECT"),
2840            Token::Whitespace(Whitespace::Space),
2841            Token::SingleQuotedString(String::from("a")),
2842            Token::Whitespace(Whitespace::Space),
2843            Token::StringConcat,
2844            Token::Whitespace(Whitespace::Space),
2845            Token::SingleQuotedString(String::from("b")),
2846        ];
2847
2848        compare(expected, tokens);
2849    }
2850    #[test]
2851    fn tokenize_bitwise_op() {
2852        let sql = String::from("SELECT one | two ^ three");
2853        let dialect = GenericDialect {};
2854        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2855
2856        let expected = vec![
2857            Token::make_keyword("SELECT"),
2858            Token::Whitespace(Whitespace::Space),
2859            Token::make_word("one", None),
2860            Token::Whitespace(Whitespace::Space),
2861            Token::Pipe,
2862            Token::Whitespace(Whitespace::Space),
2863            Token::make_word("two", None),
2864            Token::Whitespace(Whitespace::Space),
2865            Token::Caret,
2866            Token::Whitespace(Whitespace::Space),
2867            Token::make_word("three", None),
2868        ];
2869        compare(expected, tokens);
2870    }
2871
2872    #[test]
2873    fn tokenize_logical_xor() {
2874        let sql =
2875            String::from("SELECT true XOR true, false XOR false, true XOR false, false XOR true");
2876        let dialect = GenericDialect {};
2877        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2878
2879        let expected = vec![
2880            Token::make_keyword("SELECT"),
2881            Token::Whitespace(Whitespace::Space),
2882            Token::make_keyword("true"),
2883            Token::Whitespace(Whitespace::Space),
2884            Token::make_keyword("XOR"),
2885            Token::Whitespace(Whitespace::Space),
2886            Token::make_keyword("true"),
2887            Token::Comma,
2888            Token::Whitespace(Whitespace::Space),
2889            Token::make_keyword("false"),
2890            Token::Whitespace(Whitespace::Space),
2891            Token::make_keyword("XOR"),
2892            Token::Whitespace(Whitespace::Space),
2893            Token::make_keyword("false"),
2894            Token::Comma,
2895            Token::Whitespace(Whitespace::Space),
2896            Token::make_keyword("true"),
2897            Token::Whitespace(Whitespace::Space),
2898            Token::make_keyword("XOR"),
2899            Token::Whitespace(Whitespace::Space),
2900            Token::make_keyword("false"),
2901            Token::Comma,
2902            Token::Whitespace(Whitespace::Space),
2903            Token::make_keyword("false"),
2904            Token::Whitespace(Whitespace::Space),
2905            Token::make_keyword("XOR"),
2906            Token::Whitespace(Whitespace::Space),
2907            Token::make_keyword("true"),
2908        ];
2909        compare(expected, tokens);
2910    }
2911
2912    #[test]
2913    fn tokenize_simple_select() {
2914        let sql = String::from("SELECT * FROM customer WHERE id = 1 LIMIT 5");
2915        let dialect = GenericDialect {};
2916        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2917
2918        let expected = vec![
2919            Token::make_keyword("SELECT"),
2920            Token::Whitespace(Whitespace::Space),
2921            Token::Mul,
2922            Token::Whitespace(Whitespace::Space),
2923            Token::make_keyword("FROM"),
2924            Token::Whitespace(Whitespace::Space),
2925            Token::make_word("customer", None),
2926            Token::Whitespace(Whitespace::Space),
2927            Token::make_keyword("WHERE"),
2928            Token::Whitespace(Whitespace::Space),
2929            Token::make_word("id", None),
2930            Token::Whitespace(Whitespace::Space),
2931            Token::Eq,
2932            Token::Whitespace(Whitespace::Space),
2933            Token::Number(String::from("1"), false),
2934            Token::Whitespace(Whitespace::Space),
2935            Token::make_keyword("LIMIT"),
2936            Token::Whitespace(Whitespace::Space),
2937            Token::Number(String::from("5"), false),
2938        ];
2939
2940        compare(expected, tokens);
2941    }
2942
2943    #[test]
2944    fn tokenize_explain_select() {
2945        let sql = String::from("EXPLAIN SELECT * FROM customer WHERE id = 1");
2946        let dialect = GenericDialect {};
2947        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2948
2949        let expected = vec![
2950            Token::make_keyword("EXPLAIN"),
2951            Token::Whitespace(Whitespace::Space),
2952            Token::make_keyword("SELECT"),
2953            Token::Whitespace(Whitespace::Space),
2954            Token::Mul,
2955            Token::Whitespace(Whitespace::Space),
2956            Token::make_keyword("FROM"),
2957            Token::Whitespace(Whitespace::Space),
2958            Token::make_word("customer", None),
2959            Token::Whitespace(Whitespace::Space),
2960            Token::make_keyword("WHERE"),
2961            Token::Whitespace(Whitespace::Space),
2962            Token::make_word("id", None),
2963            Token::Whitespace(Whitespace::Space),
2964            Token::Eq,
2965            Token::Whitespace(Whitespace::Space),
2966            Token::Number(String::from("1"), false),
2967        ];
2968
2969        compare(expected, tokens);
2970    }
2971
2972    #[test]
2973    fn tokenize_explain_analyze_select() {
2974        let sql = String::from("EXPLAIN ANALYZE SELECT * FROM customer WHERE id = 1");
2975        let dialect = GenericDialect {};
2976        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2977
2978        let expected = vec![
2979            Token::make_keyword("EXPLAIN"),
2980            Token::Whitespace(Whitespace::Space),
2981            Token::make_keyword("ANALYZE"),
2982            Token::Whitespace(Whitespace::Space),
2983            Token::make_keyword("SELECT"),
2984            Token::Whitespace(Whitespace::Space),
2985            Token::Mul,
2986            Token::Whitespace(Whitespace::Space),
2987            Token::make_keyword("FROM"),
2988            Token::Whitespace(Whitespace::Space),
2989            Token::make_word("customer", None),
2990            Token::Whitespace(Whitespace::Space),
2991            Token::make_keyword("WHERE"),
2992            Token::Whitespace(Whitespace::Space),
2993            Token::make_word("id", None),
2994            Token::Whitespace(Whitespace::Space),
2995            Token::Eq,
2996            Token::Whitespace(Whitespace::Space),
2997            Token::Number(String::from("1"), false),
2998        ];
2999
3000        compare(expected, tokens);
3001    }
3002
3003    #[test]
3004    fn tokenize_string_predicate() {
3005        let sql = String::from("SELECT * FROM customer WHERE salary != 'Not Provided'");
3006        let dialect = GenericDialect {};
3007        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3008
3009        let expected = vec![
3010            Token::make_keyword("SELECT"),
3011            Token::Whitespace(Whitespace::Space),
3012            Token::Mul,
3013            Token::Whitespace(Whitespace::Space),
3014            Token::make_keyword("FROM"),
3015            Token::Whitespace(Whitespace::Space),
3016            Token::make_word("customer", None),
3017            Token::Whitespace(Whitespace::Space),
3018            Token::make_keyword("WHERE"),
3019            Token::Whitespace(Whitespace::Space),
3020            Token::make_word("salary", None),
3021            Token::Whitespace(Whitespace::Space),
3022            Token::Neq,
3023            Token::Whitespace(Whitespace::Space),
3024            Token::SingleQuotedString(String::from("Not Provided")),
3025        ];
3026
3027        compare(expected, tokens);
3028    }
3029
3030    #[test]
3031    fn tokenize_invalid_string() {
3032        let sql = String::from("\n💝مصطفىh");
3033
3034        let dialect = GenericDialect {};
3035        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3036        // println!("tokens: {:#?}", tokens);
3037        let expected = vec![
3038            Token::Whitespace(Whitespace::Newline),
3039            Token::Char('💝'),
3040            Token::make_word("مصطفىh", None),
3041        ];
3042        compare(expected, tokens);
3043    }
3044
3045    #[test]
3046    fn tokenize_newline_in_string_literal() {
3047        let sql = String::from("'foo\r\nbar\nbaz'");
3048
3049        let dialect = GenericDialect {};
3050        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3051        let expected = vec![Token::SingleQuotedString("foo\r\nbar\nbaz".to_string())];
3052        compare(expected, tokens);
3053    }
3054
3055    #[test]
3056    fn tokenize_unterminated_string_literal() {
3057        let sql = String::from("select 'foo");
3058
3059        let dialect = GenericDialect {};
3060        let mut tokenizer = Tokenizer::new(&dialect, &sql);
3061        assert_eq!(
3062            tokenizer.tokenize(),
3063            Err(TokenizerError {
3064                message: "Unterminated string literal".to_string(),
3065                location: Location { line: 1, column: 8 },
3066            })
3067        );
3068    }
3069
3070    #[test]
3071    fn tokenize_unterminated_string_literal_utf8() {
3072        let sql = String::from("SELECT \"なにか\" FROM Y WHERE \"なにか\" = 'test;");
3073
3074        let dialect = GenericDialect {};
3075        let mut tokenizer = Tokenizer::new(&dialect, &sql);
3076        assert_eq!(
3077            tokenizer.tokenize(),
3078            Err(TokenizerError {
3079                message: "Unterminated string literal".to_string(),
3080                location: Location {
3081                    line: 1,
3082                    column: 35
3083                }
3084            })
3085        );
3086    }
3087
3088    #[test]
3089    fn tokenize_invalid_string_cols() {
3090        let sql = String::from("\n\nSELECT * FROM table\t💝مصطفىh");
3091
3092        let dialect = GenericDialect {};
3093        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3094        // println!("tokens: {:#?}", tokens);
3095        let expected = vec![
3096            Token::Whitespace(Whitespace::Newline),
3097            Token::Whitespace(Whitespace::Newline),
3098            Token::make_keyword("SELECT"),
3099            Token::Whitespace(Whitespace::Space),
3100            Token::Mul,
3101            Token::Whitespace(Whitespace::Space),
3102            Token::make_keyword("FROM"),
3103            Token::Whitespace(Whitespace::Space),
3104            Token::make_keyword("table"),
3105            Token::Whitespace(Whitespace::Tab),
3106            Token::Char('💝'),
3107            Token::make_word("مصطفىh", None),
3108        ];
3109        compare(expected, tokens);
3110    }
3111
3112    #[test]
3113    fn tokenize_dollar_quoted_string_tagged() {
3114        let test_cases = vec![
3115            (
3116                String::from("SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$tag$"),
3117                vec![
3118                    Token::make_keyword("SELECT"),
3119                    Token::Whitespace(Whitespace::Space),
3120                    Token::DollarQuotedString(DollarQuotedString {
3121                        value: "dollar '$' quoted strings have $tags like this$ or like this $$".into(),
3122                        tag: Some("tag".into()),
3123                    })
3124                ]
3125            ),
3126            (
3127                String::from("SELECT $abc$x$ab$abc$"),
3128                vec![
3129                    Token::make_keyword("SELECT"),
3130                    Token::Whitespace(Whitespace::Space),
3131                    Token::DollarQuotedString(DollarQuotedString {
3132                        value: "x$ab".into(),
3133                        tag: Some("abc".into()),
3134                    })
3135                ]
3136            ),
3137            (
3138                String::from("SELECT $abc$$abc$"),
3139                vec![
3140                    Token::make_keyword("SELECT"),
3141                    Token::Whitespace(Whitespace::Space),
3142                    Token::DollarQuotedString(DollarQuotedString {
3143                        value: "".into(),
3144                        tag: Some("abc".into()),
3145                    })
3146                ]
3147            ),
3148            (
3149                String::from("0$abc$$abc$1"),
3150                vec![
3151                    Token::Number("0".into(), false),
3152                    Token::DollarQuotedString(DollarQuotedString {
3153                        value: "".into(),
3154                        tag: Some("abc".into()),
3155                    }),
3156                    Token::Number("1".into(), false),
3157                ]
3158            ),
3159            (
3160                String::from("$function$abc$q$data$q$$function$"),
3161                vec![
3162                    Token::DollarQuotedString(DollarQuotedString {
3163                        value: "abc$q$data$q$".into(),
3164                        tag: Some("function".into()),
3165                    }),
3166                ]
3167            ),
3168        ];
3169
3170        let dialect = GenericDialect {};
3171        for (sql, expected) in test_cases {
3172            let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3173            compare(expected, tokens);
3174        }
3175    }
3176
3177    #[test]
3178    fn tokenize_dollar_quoted_string_tagged_unterminated() {
3179        let sql = String::from("SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$different tag$");
3180        let dialect = GenericDialect {};
3181        assert_eq!(
3182            Tokenizer::new(&dialect, &sql).tokenize(),
3183            Err(TokenizerError {
3184                message: "Unterminated dollar-quoted, expected $".into(),
3185                location: Location {
3186                    line: 1,
3187                    column: 91
3188                }
3189            })
3190        );
3191    }
3192
3193    #[test]
3194    fn tokenize_dollar_quoted_string_tagged_unterminated_mirror() {
3195        let sql = String::from("SELECT $abc$abc$");
3196        let dialect = GenericDialect {};
3197        assert_eq!(
3198            Tokenizer::new(&dialect, &sql).tokenize(),
3199            Err(TokenizerError {
3200                message: "Unterminated dollar-quoted, expected $".into(),
3201                location: Location {
3202                    line: 1,
3203                    column: 17
3204                }
3205            })
3206        );
3207    }
3208
3209    #[test]
3210    fn tokenize_dollar_placeholder() {
3211        let sql = String::from("SELECT $$, $$ABC$$, $ABC$, $ABC");
3212        let dialect = SQLiteDialect {};
3213        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3214        assert_eq!(
3215            tokens,
3216            vec![
3217                Token::make_keyword("SELECT"),
3218                Token::Whitespace(Whitespace::Space),
3219                Token::Placeholder("$$".into()),
3220                Token::Comma,
3221                Token::Whitespace(Whitespace::Space),
3222                Token::Placeholder("$$ABC$$".into()),
3223                Token::Comma,
3224                Token::Whitespace(Whitespace::Space),
3225                Token::Placeholder("$ABC$".into()),
3226                Token::Comma,
3227                Token::Whitespace(Whitespace::Space),
3228                Token::Placeholder("$ABC".into()),
3229            ]
3230        );
3231    }
3232
3233    #[test]
3234    fn tokenize_nested_dollar_quoted_strings() {
3235        let sql = String::from("SELECT $tag$dollar $nested$ string$tag$");
3236        let dialect = GenericDialect {};
3237        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3238        let expected = vec![
3239            Token::make_keyword("SELECT"),
3240            Token::Whitespace(Whitespace::Space),
3241            Token::DollarQuotedString(DollarQuotedString {
3242                value: "dollar $nested$ string".into(),
3243                tag: Some("tag".into()),
3244            }),
3245        ];
3246        compare(expected, tokens);
3247    }
3248
3249    #[test]
3250    fn tokenize_dollar_quoted_string_untagged_empty() {
3251        let sql = String::from("SELECT $$$$");
3252        let dialect = GenericDialect {};
3253        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3254        let expected = vec![
3255            Token::make_keyword("SELECT"),
3256            Token::Whitespace(Whitespace::Space),
3257            Token::DollarQuotedString(DollarQuotedString {
3258                value: "".into(),
3259                tag: None,
3260            }),
3261        ];
3262        compare(expected, tokens);
3263    }
3264
3265    #[test]
3266    fn tokenize_dollar_quoted_string_untagged() {
3267        let sql =
3268            String::from("SELECT $$within dollar '$' quoted strings have $tags like this$ $$");
3269        let dialect = GenericDialect {};
3270        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3271        let expected = vec![
3272            Token::make_keyword("SELECT"),
3273            Token::Whitespace(Whitespace::Space),
3274            Token::DollarQuotedString(DollarQuotedString {
3275                value: "within dollar '$' quoted strings have $tags like this$ ".into(),
3276                tag: None,
3277            }),
3278        ];
3279        compare(expected, tokens);
3280    }
3281
3282    #[test]
3283    fn tokenize_dollar_quoted_string_untagged_unterminated() {
3284        let sql = String::from(
3285            "SELECT $$dollar '$' quoted strings have $tags like this$ or like this $different tag$",
3286        );
3287        let dialect = GenericDialect {};
3288        assert_eq!(
3289            Tokenizer::new(&dialect, &sql).tokenize(),
3290            Err(TokenizerError {
3291                message: "Unterminated dollar-quoted string".into(),
3292                location: Location {
3293                    line: 1,
3294                    column: 86
3295                }
3296            })
3297        );
3298    }
3299
3300    #[test]
3301    fn tokenize_right_arrow() {
3302        let sql = String::from("FUNCTION(key=>value)");
3303        let dialect = GenericDialect {};
3304        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3305        let expected = vec![
3306            Token::make_word("FUNCTION", None),
3307            Token::LParen,
3308            Token::make_word("key", None),
3309            Token::RArrow,
3310            Token::make_word("value", None),
3311            Token::RParen,
3312        ];
3313        compare(expected, tokens);
3314    }
3315
3316    #[test]
3317    fn tokenize_is_null() {
3318        let sql = String::from("a IS NULL");
3319        let dialect = GenericDialect {};
3320        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3321
3322        let expected = vec![
3323            Token::make_word("a", None),
3324            Token::Whitespace(Whitespace::Space),
3325            Token::make_keyword("IS"),
3326            Token::Whitespace(Whitespace::Space),
3327            Token::make_keyword("NULL"),
3328        ];
3329
3330        compare(expected, tokens);
3331    }
3332
3333    #[test]
3334    fn tokenize_comment() {
3335        let test_cases = vec![
3336            (
3337                String::from("0--this is a comment\n1"),
3338                vec![
3339                    Token::Number("0".to_string(), false),
3340                    Token::Whitespace(Whitespace::SingleLineComment {
3341                        prefix: "--".to_string(),
3342                        comment: "this is a comment".to_string(),
3343                    }),
3344                    Token::Whitespace(Whitespace::Newline),
3345                    Token::Number("1".to_string(), false),
3346                ],
3347            ),
3348            (
3349                String::from("0--this is a comment\r1"),
3350                vec![
3351                    Token::Number("0".to_string(), false),
3352                    Token::Whitespace(Whitespace::SingleLineComment {
3353                        prefix: "--".to_string(),
3354                        comment: "this is a comment\r1".to_string(),
3355                    }),
3356                ],
3357            ),
3358            (
3359                String::from("0--this is a comment\r\n1"),
3360                vec![
3361                    Token::Number("0".to_string(), false),
3362                    Token::Whitespace(Whitespace::SingleLineComment {
3363                        prefix: "--".to_string(),
3364                        comment: "this is a comment\r".to_string(),
3365                    }),
3366                    Token::Whitespace(Whitespace::Newline),
3367                    Token::Number("1".to_string(), false),
3368                ],
3369            ),
3370        ];
3371
3372        let dialect = GenericDialect {};
3373
3374        for (sql, expected) in test_cases {
3375            let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3376            compare(expected, tokens);
3377        }
3378    }
3379
3380    #[test]
3381    fn tokenize_comment_postgres() {
3382        let sql = String::from("1--\r0");
3383
3384        let dialect = PostgreSqlDialect {};
3385        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3386        let expected = vec![
3387            Token::Number("1".to_string(), false),
3388            Token::Whitespace(Whitespace::SingleLineComment {
3389                prefix: "--".to_string(),
3390                comment: "".to_string(),
3391            }),
3392            Token::Whitespace(Whitespace::Newline), // Postgres treats \r as newline in single-line comments
3393            Token::Number("0".to_string(), false),
3394        ];
3395        compare(expected, tokens);
3396    }
3397
3398    #[test]
3399    fn tokenize_comment_at_eof() {
3400        let sql = String::from("--this is a comment");
3401
3402        let dialect = GenericDialect {};
3403        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3404        let expected = vec![Token::Whitespace(Whitespace::SingleLineComment {
3405            prefix: "--".to_string(),
3406            comment: "this is a comment".to_string(),
3407        })];
3408        compare(expected, tokens);
3409    }
3410
3411    #[test]
3412    fn tokenize_multiline_comment() {
3413        let sql = String::from("0/*multi-line\n* /comment*/1");
3414
3415        let dialect = GenericDialect {};
3416        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3417        let expected = vec![
3418            Token::Number("0".to_string(), false),
3419            Token::Whitespace(Whitespace::MultiLineComment(
3420                "multi-line\n* /comment".to_string(),
3421            )),
3422            Token::Number("1".to_string(), false),
3423        ];
3424        compare(expected, tokens);
3425    }
3426
3427    #[test]
3428    fn tokenize_nested_multiline_comment() {
3429        all_dialects_where(|d| d.supports_nested_comments()).tokenizes_to(
3430            "0/*multi-line\n* \n/* comment \n /*comment*/*/ */ /comment*/1",
3431            vec![
3432                Token::Number("0".to_string(), false),
3433                Token::Whitespace(Whitespace::MultiLineComment(
3434                    "multi-line\n* \n/* comment \n /*comment*/*/ ".into(),
3435                )),
3436                Token::Whitespace(Whitespace::Space),
3437                Token::Div,
3438                Token::Word(Word {
3439                    value: "comment".to_string(),
3440                    quote_style: None,
3441                    keyword: Keyword::COMMENT,
3442                }),
3443                Token::Mul,
3444                Token::Div,
3445                Token::Number("1".to_string(), false),
3446            ],
3447        );
3448
3449        all_dialects_where(|d| d.supports_nested_comments()).tokenizes_to(
3450            "0/*multi-line\n* \n/* comment \n /*comment/**/ */ /comment*/*/1",
3451            vec![
3452                Token::Number("0".to_string(), false),
3453                Token::Whitespace(Whitespace::MultiLineComment(
3454                    "multi-line\n* \n/* comment \n /*comment/**/ */ /comment*/".into(),
3455                )),
3456                Token::Number("1".to_string(), false),
3457            ],
3458        );
3459
3460        all_dialects_where(|d| d.supports_nested_comments()).tokenizes_to(
3461            "SELECT 1/* a /* b */ c */0",
3462            vec![
3463                Token::make_keyword("SELECT"),
3464                Token::Whitespace(Whitespace::Space),
3465                Token::Number("1".to_string(), false),
3466                Token::Whitespace(Whitespace::MultiLineComment(" a /* b */ c ".to_string())),
3467                Token::Number("0".to_string(), false),
3468            ],
3469        );
3470    }
3471
3472    #[test]
3473    fn tokenize_nested_multiline_comment_empty() {
3474        all_dialects_where(|d| d.supports_nested_comments()).tokenizes_to(
3475            "select 1/*/**/*/0",
3476            vec![
3477                Token::make_keyword("select"),
3478                Token::Whitespace(Whitespace::Space),
3479                Token::Number("1".to_string(), false),
3480                Token::Whitespace(Whitespace::MultiLineComment("/**/".to_string())),
3481                Token::Number("0".to_string(), false),
3482            ],
3483        );
3484    }
3485
3486    #[test]
3487    fn tokenize_nested_comments_if_not_supported() {
3488        all_dialects_except(|d| d.supports_nested_comments()).tokenizes_to(
3489            "SELECT 1/*/* nested comment */*/0",
3490            vec![
3491                Token::make_keyword("SELECT"),
3492                Token::Whitespace(Whitespace::Space),
3493                Token::Number("1".to_string(), false),
3494                Token::Whitespace(Whitespace::MultiLineComment(
3495                    "/* nested comment ".to_string(),
3496                )),
3497                Token::Mul,
3498                Token::Div,
3499                Token::Number("0".to_string(), false),
3500            ],
3501        );
3502    }
3503
3504    #[test]
3505    fn tokenize_multiline_comment_with_even_asterisks() {
3506        let sql = String::from("\n/** Comment **/\n");
3507
3508        let dialect = GenericDialect {};
3509        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3510        let expected = vec![
3511            Token::Whitespace(Whitespace::Newline),
3512            Token::Whitespace(Whitespace::MultiLineComment("* Comment *".to_string())),
3513            Token::Whitespace(Whitespace::Newline),
3514        ];
3515        compare(expected, tokens);
3516    }
3517
3518    #[test]
3519    fn tokenize_unicode_whitespace() {
3520        let sql = String::from(" \u{2003}\n");
3521
3522        let dialect = GenericDialect {};
3523        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3524        let expected = vec![
3525            Token::Whitespace(Whitespace::Space),
3526            Token::Whitespace(Whitespace::Space),
3527            Token::Whitespace(Whitespace::Newline),
3528        ];
3529        compare(expected, tokens);
3530    }
3531
3532    #[test]
3533    fn tokenize_mismatched_quotes() {
3534        let sql = String::from("\"foo");
3535
3536        let dialect = GenericDialect {};
3537        let mut tokenizer = Tokenizer::new(&dialect, &sql);
3538        assert_eq!(
3539            tokenizer.tokenize(),
3540            Err(TokenizerError {
3541                message: "Expected close delimiter '\"' before EOF.".to_string(),
3542                location: Location { line: 1, column: 1 },
3543            })
3544        );
3545    }
3546
3547    #[test]
3548    fn tokenize_newlines() {
3549        let sql = String::from("line1\nline2\rline3\r\nline4\r");
3550
3551        let dialect = GenericDialect {};
3552        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3553        let expected = vec![
3554            Token::make_word("line1", None),
3555            Token::Whitespace(Whitespace::Newline),
3556            Token::make_word("line2", None),
3557            Token::Whitespace(Whitespace::Newline),
3558            Token::make_word("line3", None),
3559            Token::Whitespace(Whitespace::Newline),
3560            Token::make_word("line4", None),
3561            Token::Whitespace(Whitespace::Newline),
3562        ];
3563        compare(expected, tokens);
3564    }
3565
3566    #[test]
3567    fn tokenize_mssql_top() {
3568        let sql = "SELECT TOP 5 [bar] FROM foo";
3569        let dialect = MsSqlDialect {};
3570        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3571        let expected = vec![
3572            Token::make_keyword("SELECT"),
3573            Token::Whitespace(Whitespace::Space),
3574            Token::make_keyword("TOP"),
3575            Token::Whitespace(Whitespace::Space),
3576            Token::Number(String::from("5"), false),
3577            Token::Whitespace(Whitespace::Space),
3578            Token::make_word("bar", Some('[')),
3579            Token::Whitespace(Whitespace::Space),
3580            Token::make_keyword("FROM"),
3581            Token::Whitespace(Whitespace::Space),
3582            Token::make_word("foo", None),
3583        ];
3584        compare(expected, tokens);
3585    }
3586
3587    #[test]
3588    fn tokenize_pg_regex_match() {
3589        let sql = "SELECT col ~ '^a', col ~* '^a', col !~ '^a', col !~* '^a'";
3590        let dialect = GenericDialect {};
3591        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3592        let expected = vec![
3593            Token::make_keyword("SELECT"),
3594            Token::Whitespace(Whitespace::Space),
3595            Token::make_word("col", None),
3596            Token::Whitespace(Whitespace::Space),
3597            Token::Tilde,
3598            Token::Whitespace(Whitespace::Space),
3599            Token::SingleQuotedString("^a".into()),
3600            Token::Comma,
3601            Token::Whitespace(Whitespace::Space),
3602            Token::make_word("col", None),
3603            Token::Whitespace(Whitespace::Space),
3604            Token::TildeAsterisk,
3605            Token::Whitespace(Whitespace::Space),
3606            Token::SingleQuotedString("^a".into()),
3607            Token::Comma,
3608            Token::Whitespace(Whitespace::Space),
3609            Token::make_word("col", None),
3610            Token::Whitespace(Whitespace::Space),
3611            Token::ExclamationMarkTilde,
3612            Token::Whitespace(Whitespace::Space),
3613            Token::SingleQuotedString("^a".into()),
3614            Token::Comma,
3615            Token::Whitespace(Whitespace::Space),
3616            Token::make_word("col", None),
3617            Token::Whitespace(Whitespace::Space),
3618            Token::ExclamationMarkTildeAsterisk,
3619            Token::Whitespace(Whitespace::Space),
3620            Token::SingleQuotedString("^a".into()),
3621        ];
3622        compare(expected, tokens);
3623    }
3624
3625    #[test]
3626    fn tokenize_pg_like_match() {
3627        let sql = "SELECT col ~~ '_a%', col ~~* '_a%', col !~~ '_a%', col !~~* '_a%'";
3628        let dialect = GenericDialect {};
3629        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3630        let expected = vec![
3631            Token::make_keyword("SELECT"),
3632            Token::Whitespace(Whitespace::Space),
3633            Token::make_word("col", None),
3634            Token::Whitespace(Whitespace::Space),
3635            Token::DoubleTilde,
3636            Token::Whitespace(Whitespace::Space),
3637            Token::SingleQuotedString("_a%".into()),
3638            Token::Comma,
3639            Token::Whitespace(Whitespace::Space),
3640            Token::make_word("col", None),
3641            Token::Whitespace(Whitespace::Space),
3642            Token::DoubleTildeAsterisk,
3643            Token::Whitespace(Whitespace::Space),
3644            Token::SingleQuotedString("_a%".into()),
3645            Token::Comma,
3646            Token::Whitespace(Whitespace::Space),
3647            Token::make_word("col", None),
3648            Token::Whitespace(Whitespace::Space),
3649            Token::ExclamationMarkDoubleTilde,
3650            Token::Whitespace(Whitespace::Space),
3651            Token::SingleQuotedString("_a%".into()),
3652            Token::Comma,
3653            Token::Whitespace(Whitespace::Space),
3654            Token::make_word("col", None),
3655            Token::Whitespace(Whitespace::Space),
3656            Token::ExclamationMarkDoubleTildeAsterisk,
3657            Token::Whitespace(Whitespace::Space),
3658            Token::SingleQuotedString("_a%".into()),
3659        ];
3660        compare(expected, tokens);
3661    }
3662
3663    #[test]
3664    fn tokenize_quoted_identifier() {
3665        let sql = r#" "a "" b" "a """ "c """"" "#;
3666        let dialect = GenericDialect {};
3667        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3668        let expected = vec![
3669            Token::Whitespace(Whitespace::Space),
3670            Token::make_word(r#"a " b"#, Some('"')),
3671            Token::Whitespace(Whitespace::Space),
3672            Token::make_word(r#"a ""#, Some('"')),
3673            Token::Whitespace(Whitespace::Space),
3674            Token::make_word(r#"c """#, Some('"')),
3675            Token::Whitespace(Whitespace::Space),
3676        ];
3677        compare(expected, tokens);
3678    }
3679
3680    #[test]
3681    fn tokenize_snowflake_div() {
3682        let sql = r#"field/1000"#;
3683        let dialect = SnowflakeDialect {};
3684        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3685        let expected = vec![
3686            Token::make_word(r#"field"#, None),
3687            Token::Div,
3688            Token::Number("1000".to_string(), false),
3689        ];
3690        compare(expected, tokens);
3691    }
3692
3693    #[test]
3694    fn tokenize_quoted_identifier_with_no_escape() {
3695        let sql = r#" "a "" b" "a """ "c """"" "#;
3696        let dialect = GenericDialect {};
3697        let tokens = Tokenizer::new(&dialect, sql)
3698            .with_unescape(false)
3699            .tokenize()
3700            .unwrap();
3701        let expected = vec![
3702            Token::Whitespace(Whitespace::Space),
3703            Token::make_word(r#"a "" b"#, Some('"')),
3704            Token::Whitespace(Whitespace::Space),
3705            Token::make_word(r#"a """#, Some('"')),
3706            Token::Whitespace(Whitespace::Space),
3707            Token::make_word(r#"c """""#, Some('"')),
3708            Token::Whitespace(Whitespace::Space),
3709        ];
3710        compare(expected, tokens);
3711    }
3712
3713    #[test]
3714    fn tokenize_with_location() {
3715        let sql = "SELECT a,\n b";
3716        let dialect = GenericDialect {};
3717        let tokens = Tokenizer::new(&dialect, sql)
3718            .tokenize_with_location()
3719            .unwrap();
3720        let expected = vec![
3721            TokenWithSpan::at(Token::make_keyword("SELECT"), (1, 1).into(), (1, 7).into()),
3722            TokenWithSpan::at(
3723                Token::Whitespace(Whitespace::Space),
3724                (1, 7).into(),
3725                (1, 8).into(),
3726            ),
3727            TokenWithSpan::at(Token::make_word("a", None), (1, 8).into(), (1, 9).into()),
3728            TokenWithSpan::at(Token::Comma, (1, 9).into(), (1, 10).into()),
3729            TokenWithSpan::at(
3730                Token::Whitespace(Whitespace::Newline),
3731                (1, 10).into(),
3732                (2, 1).into(),
3733            ),
3734            TokenWithSpan::at(
3735                Token::Whitespace(Whitespace::Space),
3736                (2, 1).into(),
3737                (2, 2).into(),
3738            ),
3739            TokenWithSpan::at(Token::make_word("b", None), (2, 2).into(), (2, 3).into()),
3740        ];
3741        compare(expected, tokens);
3742    }
3743
3744    fn compare<T: PartialEq + fmt::Debug>(expected: Vec<T>, actual: Vec<T>) {
3745        //println!("------------------------------");
3746        //println!("tokens   = {:?}", actual);
3747        //println!("expected = {:?}", expected);
3748        //println!("------------------------------");
3749        assert_eq!(expected, actual);
3750    }
3751
3752    fn check_unescape(s: &str, expected: Option<&str>) {
3753        let s = format!("'{s}'");
3754        let mut state = State {
3755            peekable: s.chars().peekable(),
3756            line: 0,
3757            col: 0,
3758        };
3759
3760        assert_eq!(
3761            unescape_single_quoted_string(&mut state),
3762            expected.map(|s| s.to_string())
3763        );
3764    }
3765
3766    #[test]
3767    fn test_unescape() {
3768        check_unescape(r"\b", Some("\u{0008}"));
3769        check_unescape(r"\f", Some("\u{000C}"));
3770        check_unescape(r"\t", Some("\t"));
3771        check_unescape(r"\r\n", Some("\r\n"));
3772        check_unescape(r"\/", Some("/"));
3773        check_unescape(r"/", Some("/"));
3774        check_unescape(r"\\", Some("\\"));
3775
3776        // 16 and 32-bit hexadecimal Unicode character value
3777        check_unescape(r"\u0001", Some("\u{0001}"));
3778        check_unescape(r"\u4c91", Some("\u{4c91}"));
3779        check_unescape(r"\u4c916", Some("\u{4c91}6"));
3780        check_unescape(r"\u4c", None);
3781        check_unescape(r"\u0000", None);
3782        check_unescape(r"\U0010FFFF", Some("\u{10FFFF}"));
3783        check_unescape(r"\U00110000", None);
3784        check_unescape(r"\U00000000", None);
3785        check_unescape(r"\u", None);
3786        check_unescape(r"\U", None);
3787        check_unescape(r"\U1010FFFF", None);
3788
3789        // hexadecimal byte value
3790        check_unescape(r"\x4B", Some("\u{004b}"));
3791        check_unescape(r"\x4", Some("\u{0004}"));
3792        check_unescape(r"\x4L", Some("\u{0004}L"));
3793        check_unescape(r"\x", Some("x"));
3794        check_unescape(r"\xP", Some("xP"));
3795        check_unescape(r"\x0", None);
3796        check_unescape(r"\xCAD", None);
3797        check_unescape(r"\xA9", None);
3798
3799        // octal byte value
3800        check_unescape(r"\1", Some("\u{0001}"));
3801        check_unescape(r"\12", Some("\u{000a}"));
3802        check_unescape(r"\123", Some("\u{0053}"));
3803        check_unescape(r"\1232", Some("\u{0053}2"));
3804        check_unescape(r"\4", Some("\u{0004}"));
3805        check_unescape(r"\45", Some("\u{0025}"));
3806        check_unescape(r"\450", Some("\u{0028}"));
3807        check_unescape(r"\603", None);
3808        check_unescape(r"\0", None);
3809        check_unescape(r"\080", None);
3810
3811        // others
3812        check_unescape(r"\9", Some("9"));
3813        check_unescape(r"''", Some("'"));
3814        check_unescape(
3815            r"Hello\r\nRust/\u4c91 SQL Parser\U0010ABCD\1232",
3816            Some("Hello\r\nRust/\u{4c91} SQL Parser\u{10abcd}\u{0053}2"),
3817        );
3818        check_unescape(r"Hello\0", None);
3819        check_unescape(r"Hello\xCADRust", None);
3820    }
3821
3822    #[test]
3823    fn tokenize_numeric_prefix_trait() {
3824        #[derive(Debug)]
3825        struct NumericPrefixDialect;
3826
3827        impl Dialect for NumericPrefixDialect {
3828            fn is_identifier_start(&self, ch: char) -> bool {
3829                ch.is_ascii_lowercase()
3830                    || ch.is_ascii_uppercase()
3831                    || ch.is_ascii_digit()
3832                    || ch == '$'
3833            }
3834
3835            fn is_identifier_part(&self, ch: char) -> bool {
3836                ch.is_ascii_lowercase()
3837                    || ch.is_ascii_uppercase()
3838                    || ch.is_ascii_digit()
3839                    || ch == '_'
3840                    || ch == '$'
3841                    || ch == '{'
3842                    || ch == '}'
3843            }
3844
3845            fn supports_numeric_prefix(&self) -> bool {
3846                true
3847            }
3848        }
3849
3850        tokenize_numeric_prefix_inner(&NumericPrefixDialect {});
3851        tokenize_numeric_prefix_inner(&HiveDialect {});
3852        tokenize_numeric_prefix_inner(&MySqlDialect {});
3853    }
3854
3855    fn tokenize_numeric_prefix_inner(dialect: &dyn Dialect) {
3856        let sql = r#"SELECT * FROM 1"#;
3857        let tokens = Tokenizer::new(dialect, sql).tokenize().unwrap();
3858        let expected = vec![
3859            Token::make_keyword("SELECT"),
3860            Token::Whitespace(Whitespace::Space),
3861            Token::Mul,
3862            Token::Whitespace(Whitespace::Space),
3863            Token::make_keyword("FROM"),
3864            Token::Whitespace(Whitespace::Space),
3865            Token::Number(String::from("1"), false),
3866        ];
3867        compare(expected, tokens);
3868    }
3869
3870    #[test]
3871    fn tokenize_quoted_string_escape() {
3872        let dialect = SnowflakeDialect {};
3873        for (sql, expected, expected_unescaped) in [
3874            (r#"'%a\'%b'"#, r#"%a\'%b"#, r#"%a'%b"#),
3875            (r#"'a\'\'b\'c\'d'"#, r#"a\'\'b\'c\'d"#, r#"a''b'c'd"#),
3876            (r#"'\\'"#, r#"\\"#, r#"\"#),
3877            (
3878                r#"'\0\a\b\f\n\r\t\Z'"#,
3879                r#"\0\a\b\f\n\r\t\Z"#,
3880                "\0\u{7}\u{8}\u{c}\n\r\t\u{1a}",
3881            ),
3882            (r#"'\"'"#, r#"\""#, "\""),
3883            (r#"'\\a\\b\'c'"#, r#"\\a\\b\'c"#, r#"\a\b'c"#),
3884            (r#"'\'abcd'"#, r#"\'abcd"#, r#"'abcd"#),
3885            (r#"'''a''b'"#, r#"''a''b"#, r#"'a'b"#),
3886            (r#"'\q'"#, r#"\q"#, r#"q"#),
3887            (r#"'\%\_'"#, r#"\%\_"#, r#"%_"#),
3888            (r#"'\\%\\_'"#, r#"\\%\\_"#, r#"\%\_"#),
3889        ] {
3890            let tokens = Tokenizer::new(&dialect, sql)
3891                .with_unescape(false)
3892                .tokenize()
3893                .unwrap();
3894            let expected = vec![Token::SingleQuotedString(expected.to_string())];
3895            compare(expected, tokens);
3896
3897            let tokens = Tokenizer::new(&dialect, sql)
3898                .with_unescape(true)
3899                .tokenize()
3900                .unwrap();
3901            let expected = vec![Token::SingleQuotedString(expected_unescaped.to_string())];
3902            compare(expected, tokens);
3903        }
3904
3905        for sql in [r#"'\'"#, r#"'ab\'"#] {
3906            let mut tokenizer = Tokenizer::new(&dialect, sql);
3907            assert_eq!(
3908                "Unterminated string literal",
3909                tokenizer.tokenize().unwrap_err().message.as_str(),
3910            );
3911        }
3912
3913        // Non-escape dialect
3914        for (sql, expected) in [(r#"'\'"#, r#"\"#), (r#"'ab\'"#, r#"ab\"#)] {
3915            let dialect = GenericDialect {};
3916            let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3917
3918            let expected = vec![Token::SingleQuotedString(expected.to_string())];
3919
3920            compare(expected, tokens);
3921        }
3922
3923        // MySQL special case for LIKE escapes
3924        for (sql, expected) in [(r#"'\%'"#, r#"\%"#), (r#"'\_'"#, r#"\_"#)] {
3925            let dialect = MySqlDialect {};
3926            let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3927
3928            let expected = vec![Token::SingleQuotedString(expected.to_string())];
3929
3930            compare(expected, tokens);
3931        }
3932    }
3933
3934    #[test]
3935    fn tokenize_triple_quoted_string() {
3936        fn check<F>(
3937            q: char, // The quote character to test
3938            r: char, // An alternate quote character.
3939            quote_token: F,
3940        ) where
3941            F: Fn(String) -> Token,
3942        {
3943            let dialect = BigQueryDialect {};
3944
3945            for (sql, expected, expected_unescaped) in [
3946                // Empty string
3947                (format!(r#"{q}{q}{q}{q}{q}{q}"#), "".into(), "".into()),
3948                // Should not count escaped quote as end of string.
3949                (
3950                    format!(r#"{q}{q}{q}ab{q}{q}\{q}{q}cd{q}{q}{q}"#),
3951                    format!(r#"ab{q}{q}\{q}{q}cd"#),
3952                    format!(r#"ab{q}{q}{q}{q}cd"#),
3953                ),
3954                // Simple string
3955                (
3956                    format!(r#"{q}{q}{q}abc{q}{q}{q}"#),
3957                    "abc".into(),
3958                    "abc".into(),
3959                ),
3960                // Mix single-double quotes unescaped.
3961                (
3962                    format!(r#"{q}{q}{q}ab{r}{r}{r}c{r}def{r}{r}{r}{q}{q}{q}"#),
3963                    format!("ab{r}{r}{r}c{r}def{r}{r}{r}"),
3964                    format!("ab{r}{r}{r}c{r}def{r}{r}{r}"),
3965                ),
3966                // Escaped quote.
3967                (
3968                    format!(r#"{q}{q}{q}ab{q}{q}c{q}{q}\{q}de{q}{q}f{q}{q}{q}"#),
3969                    format!(r#"ab{q}{q}c{q}{q}\{q}de{q}{q}f"#),
3970                    format!(r#"ab{q}{q}c{q}{q}{q}de{q}{q}f"#),
3971                ),
3972                // backslash-escaped quote characters.
3973                (
3974                    format!(r#"{q}{q}{q}a\'\'b\'c\'d{q}{q}{q}"#),
3975                    r#"a\'\'b\'c\'d"#.into(),
3976                    r#"a''b'c'd"#.into(),
3977                ),
3978                // backslash-escaped characters
3979                (
3980                    format!(r#"{q}{q}{q}abc\0\n\rdef{q}{q}{q}"#),
3981                    r#"abc\0\n\rdef"#.into(),
3982                    "abc\0\n\rdef".into(),
3983                ),
3984            ] {
3985                let tokens = Tokenizer::new(&dialect, sql.as_str())
3986                    .with_unescape(false)
3987                    .tokenize()
3988                    .unwrap();
3989                let expected = vec![quote_token(expected.to_string())];
3990                compare(expected, tokens);
3991
3992                let tokens = Tokenizer::new(&dialect, sql.as_str())
3993                    .with_unescape(true)
3994                    .tokenize()
3995                    .unwrap();
3996                let expected = vec![quote_token(expected_unescaped.to_string())];
3997                compare(expected, tokens);
3998            }
3999
4000            for sql in [
4001                format!(r#"{q}{q}{q}{q}{q}\{q}"#),
4002                format!(r#"{q}{q}{q}abc{q}{q}\{q}"#),
4003                format!(r#"{q}{q}{q}{q}"#),
4004                format!(r#"{q}{q}{q}{r}{r}"#),
4005                format!(r#"{q}{q}{q}abc{q}"#),
4006                format!(r#"{q}{q}{q}abc{q}{q}"#),
4007                format!(r#"{q}{q}{q}abc"#),
4008            ] {
4009                let dialect = BigQueryDialect {};
4010                let mut tokenizer = Tokenizer::new(&dialect, sql.as_str());
4011                assert_eq!(
4012                    "Unterminated string literal",
4013                    tokenizer.tokenize().unwrap_err().message.as_str(),
4014                );
4015            }
4016        }
4017
4018        check('"', '\'', Token::TripleDoubleQuotedString);
4019
4020        check('\'', '"', Token::TripleSingleQuotedString);
4021
4022        let dialect = BigQueryDialect {};
4023
4024        let sql = r#"""''"#;
4025        let tokens = Tokenizer::new(&dialect, sql)
4026            .with_unescape(true)
4027            .tokenize()
4028            .unwrap();
4029        let expected = vec![
4030            Token::DoubleQuotedString("".to_string()),
4031            Token::SingleQuotedString("".to_string()),
4032        ];
4033        compare(expected, tokens);
4034
4035        let sql = r#"''"""#;
4036        let tokens = Tokenizer::new(&dialect, sql)
4037            .with_unescape(true)
4038            .tokenize()
4039            .unwrap();
4040        let expected = vec![
4041            Token::SingleQuotedString("".to_string()),
4042            Token::DoubleQuotedString("".to_string()),
4043        ];
4044        compare(expected, tokens);
4045
4046        // Non-triple quoted string dialect
4047        let dialect = SnowflakeDialect {};
4048        let sql = r#"''''''"#;
4049        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4050        let expected = vec![Token::SingleQuotedString("''".to_string())];
4051        compare(expected, tokens);
4052    }
4053
4054    #[test]
4055    fn test_mysql_users_grantees() {
4056        let dialect = MySqlDialect {};
4057
4058        let sql = "CREATE USER `root`@`%`";
4059        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4060        let expected = vec![
4061            Token::make_keyword("CREATE"),
4062            Token::Whitespace(Whitespace::Space),
4063            Token::make_keyword("USER"),
4064            Token::Whitespace(Whitespace::Space),
4065            Token::make_word("root", Some('`')),
4066            Token::AtSign,
4067            Token::make_word("%", Some('`')),
4068        ];
4069        compare(expected, tokens);
4070    }
4071
4072    #[test]
4073    fn test_postgres_abs_without_space_and_string_literal() {
4074        let dialect = MySqlDialect {};
4075
4076        let sql = "SELECT @'1'";
4077        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4078        let expected = vec![
4079            Token::make_keyword("SELECT"),
4080            Token::Whitespace(Whitespace::Space),
4081            Token::AtSign,
4082            Token::SingleQuotedString("1".to_string()),
4083        ];
4084        compare(expected, tokens);
4085    }
4086
4087    #[test]
4088    fn test_postgres_abs_without_space_and_quoted_column() {
4089        let dialect = MySqlDialect {};
4090
4091        let sql = r#"SELECT @"bar" FROM foo"#;
4092        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4093        let expected = vec![
4094            Token::make_keyword("SELECT"),
4095            Token::Whitespace(Whitespace::Space),
4096            Token::AtSign,
4097            Token::DoubleQuotedString("bar".to_string()),
4098            Token::Whitespace(Whitespace::Space),
4099            Token::make_keyword("FROM"),
4100            Token::Whitespace(Whitespace::Space),
4101            Token::make_word("foo", None),
4102        ];
4103        compare(expected, tokens);
4104    }
4105
4106    #[test]
4107    fn test_national_strings_backslash_escape_not_supported() {
4108        all_dialects_where(|dialect| !dialect.supports_string_literal_backslash_escape())
4109            .tokenizes_to(
4110                "select n'''''\\'",
4111                vec![
4112                    Token::make_keyword("select"),
4113                    Token::Whitespace(Whitespace::Space),
4114                    Token::NationalStringLiteral("''\\".to_string()),
4115                ],
4116            );
4117    }
4118
4119    #[test]
4120    fn test_national_strings_backslash_escape_supported() {
4121        all_dialects_where(|dialect| dialect.supports_string_literal_backslash_escape())
4122            .tokenizes_to(
4123                "select n'''''\\''",
4124                vec![
4125                    Token::make_keyword("select"),
4126                    Token::Whitespace(Whitespace::Space),
4127                    Token::NationalStringLiteral("'''".to_string()),
4128                ],
4129            );
4130    }
4131
4132    #[test]
4133    fn test_string_escape_constant_not_supported() {
4134        all_dialects_where(|dialect| !dialect.supports_string_escape_constant()).tokenizes_to(
4135            "select e'...'",
4136            vec![
4137                Token::make_keyword("select"),
4138                Token::Whitespace(Whitespace::Space),
4139                Token::make_word("e", None),
4140                Token::SingleQuotedString("...".to_string()),
4141            ],
4142        );
4143
4144        all_dialects_where(|dialect| !dialect.supports_string_escape_constant()).tokenizes_to(
4145            "select E'...'",
4146            vec![
4147                Token::make_keyword("select"),
4148                Token::Whitespace(Whitespace::Space),
4149                Token::make_word("E", None),
4150                Token::SingleQuotedString("...".to_string()),
4151            ],
4152        );
4153    }
4154
4155    #[test]
4156    fn test_string_escape_constant_supported() {
4157        all_dialects_where(|dialect| dialect.supports_string_escape_constant()).tokenizes_to(
4158            "select e'\\''",
4159            vec![
4160                Token::make_keyword("select"),
4161                Token::Whitespace(Whitespace::Space),
4162                Token::EscapedStringLiteral("'".to_string()),
4163            ],
4164        );
4165
4166        all_dialects_where(|dialect| dialect.supports_string_escape_constant()).tokenizes_to(
4167            "select E'\\''",
4168            vec![
4169                Token::make_keyword("select"),
4170                Token::Whitespace(Whitespace::Space),
4171                Token::EscapedStringLiteral("'".to_string()),
4172            ],
4173        );
4174    }
4175
4176    #[test]
4177    fn test_whitespace_required_after_single_line_comment() {
4178        all_dialects_where(|dialect| dialect.requires_single_line_comment_whitespace())
4179            .tokenizes_to(
4180                "SELECT --'abc'",
4181                vec![
4182                    Token::make_keyword("SELECT"),
4183                    Token::Whitespace(Whitespace::Space),
4184                    Token::Minus,
4185                    Token::Minus,
4186                    Token::SingleQuotedString("abc".to_string()),
4187                ],
4188            );
4189
4190        all_dialects_where(|dialect| dialect.requires_single_line_comment_whitespace())
4191            .tokenizes_to(
4192                "SELECT -- 'abc'",
4193                vec![
4194                    Token::make_keyword("SELECT"),
4195                    Token::Whitespace(Whitespace::Space),
4196                    Token::Whitespace(Whitespace::SingleLineComment {
4197                        prefix: "--".to_string(),
4198                        comment: " 'abc'".to_string(),
4199                    }),
4200                ],
4201            );
4202
4203        all_dialects_where(|dialect| dialect.requires_single_line_comment_whitespace())
4204            .tokenizes_to(
4205                "SELECT --",
4206                vec![
4207                    Token::make_keyword("SELECT"),
4208                    Token::Whitespace(Whitespace::Space),
4209                    Token::Minus,
4210                    Token::Minus,
4211                ],
4212            );
4213
4214        all_dialects_where(|d| d.requires_single_line_comment_whitespace()).tokenizes_to(
4215            "--\n-- Table structure for table...\n--\n",
4216            vec![
4217                Token::Whitespace(Whitespace::SingleLineComment {
4218                    prefix: "--".to_string(),
4219                    comment: "".to_string(),
4220                }),
4221                Token::Whitespace(Whitespace::Newline),
4222                Token::Whitespace(Whitespace::SingleLineComment {
4223                    prefix: "--".to_string(),
4224                    comment: " Table structure for table...".to_string(),
4225                }),
4226                Token::Whitespace(Whitespace::Newline),
4227                Token::Whitespace(Whitespace::SingleLineComment {
4228                    prefix: "--".to_string(),
4229                    comment: "".to_string(),
4230                }),
4231                Token::Whitespace(Whitespace::Newline),
4232            ],
4233        );
4234    }
4235
4236    #[test]
4237    fn test_whitespace_not_required_after_single_line_comment() {
4238        all_dialects_where(|dialect| !dialect.requires_single_line_comment_whitespace())
4239            .tokenizes_to(
4240                "SELECT --'abc'",
4241                vec![
4242                    Token::make_keyword("SELECT"),
4243                    Token::Whitespace(Whitespace::Space),
4244                    Token::Whitespace(Whitespace::SingleLineComment {
4245                        prefix: "--".to_string(),
4246                        comment: "'abc'".to_string(),
4247                    }),
4248                ],
4249            );
4250
4251        all_dialects_where(|dialect| !dialect.requires_single_line_comment_whitespace())
4252            .tokenizes_to(
4253                "SELECT -- 'abc'",
4254                vec![
4255                    Token::make_keyword("SELECT"),
4256                    Token::Whitespace(Whitespace::Space),
4257                    Token::Whitespace(Whitespace::SingleLineComment {
4258                        prefix: "--".to_string(),
4259                        comment: " 'abc'".to_string(),
4260                    }),
4261                ],
4262            );
4263
4264        all_dialects_where(|dialect| !dialect.requires_single_line_comment_whitespace())
4265            .tokenizes_to(
4266                "SELECT --",
4267                vec![
4268                    Token::make_keyword("SELECT"),
4269                    Token::Whitespace(Whitespace::Space),
4270                    Token::Whitespace(Whitespace::SingleLineComment {
4271                        prefix: "--".to_string(),
4272                        comment: "".to_string(),
4273                    }),
4274                ],
4275            );
4276    }
4277
4278    #[test]
4279    fn test_tokenize_identifiers_numeric_prefix() {
4280        all_dialects_where(|dialect| dialect.supports_numeric_prefix())
4281            .tokenizes_to("123abc", vec![Token::make_word("123abc", None)]);
4282
4283        all_dialects_where(|dialect| dialect.supports_numeric_prefix())
4284            .tokenizes_to("12e34", vec![Token::Number("12e34".to_string(), false)]);
4285
4286        all_dialects_where(|dialect| dialect.supports_numeric_prefix()).tokenizes_to(
4287            "t.12e34",
4288            vec![
4289                Token::make_word("t", None),
4290                Token::Period,
4291                Token::make_word("12e34", None),
4292            ],
4293        );
4294
4295        all_dialects_where(|dialect| dialect.supports_numeric_prefix()).tokenizes_to(
4296            "t.1two3",
4297            vec![
4298                Token::make_word("t", None),
4299                Token::Period,
4300                Token::make_word("1two3", None),
4301            ],
4302        );
4303    }
4304
4305    #[test]
4306    fn tokenize_period_underscore() {
4307        let sql = String::from("SELECT table._col");
4308        // a dialect that supports underscores in numeric literals
4309        let dialect = PostgreSqlDialect {};
4310        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
4311
4312        let expected = vec![
4313            Token::make_keyword("SELECT"),
4314            Token::Whitespace(Whitespace::Space),
4315            Token::Word(Word {
4316                value: "table".to_string(),
4317                quote_style: None,
4318                keyword: Keyword::TABLE,
4319            }),
4320            Token::Period,
4321            Token::Word(Word {
4322                value: "_col".to_string(),
4323                quote_style: None,
4324                keyword: Keyword::NoKeyword,
4325            }),
4326        ];
4327
4328        compare(expected, tokens);
4329
4330        let sql = String::from("SELECT ._123");
4331        if let Ok(tokens) = Tokenizer::new(&dialect, &sql).tokenize() {
4332            panic!("Tokenizer should have failed on {sql}, but it succeeded with {tokens:?}");
4333        }
4334
4335        let sql = String::from("SELECT ._abc");
4336        if let Ok(tokens) = Tokenizer::new(&dialect, &sql).tokenize() {
4337            panic!("Tokenizer should have failed on {sql}, but it succeeded with {tokens:?}");
4338        }
4339    }
4340
4341    #[test]
4342    fn tokenize_question_mark() {
4343        let dialect = PostgreSqlDialect {};
4344        let sql = "SELECT x ? y";
4345        let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4346        compare(
4347            tokens,
4348            vec![
4349                Token::make_keyword("SELECT"),
4350                Token::Whitespace(Whitespace::Space),
4351                Token::make_word("x", None),
4352                Token::Whitespace(Whitespace::Space),
4353                Token::Question,
4354                Token::Whitespace(Whitespace::Space),
4355                Token::make_word("y", None),
4356            ],
4357        );
4358    }
4359
4360    #[test]
4361    fn tokenize_multiline_comment_with_comment_hint() {
4362        let sql = String::from("0/*! word */1");
4363
4364        let dialect = MySqlDialect {};
4365        let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
4366        let expected = vec![
4367            Token::Number("0".to_string(), false),
4368            Token::Whitespace(Whitespace::Space),
4369            Token::Word(Word {
4370                value: "word".to_string(),
4371                quote_style: None,
4372                keyword: Keyword::NoKeyword,
4373            }),
4374            Token::Whitespace(Whitespace::Space),
4375            Token::Number("1".to_string(), false),
4376        ];
4377        compare(expected, tokens);
4378    }
4379
4380    #[test]
4381    fn tokenize_multiline_comment_with_comment_hint_and_version() {
4382        let sql_multi = String::from("0 /*!50110 KEY_BLOCK_SIZE = 1024*/ 1");
4383        let dialect = MySqlDialect {};
4384        let tokens = Tokenizer::new(&dialect, &sql_multi).tokenize().unwrap();
4385        let expected = vec![
4386            Token::Number("0".to_string(), false),
4387            Token::Whitespace(Whitespace::Space),
4388            Token::Whitespace(Whitespace::Space),
4389            Token::Word(Word {
4390                value: "KEY_BLOCK_SIZE".to_string(),
4391                quote_style: None,
4392                keyword: Keyword::KEY_BLOCK_SIZE,
4393            }),
4394            Token::Whitespace(Whitespace::Space),
4395            Token::Eq,
4396            Token::Whitespace(Whitespace::Space),
4397            Token::Number("1024".to_string(), false),
4398            Token::Whitespace(Whitespace::Space),
4399            Token::Number("1".to_string(), false),
4400        ];
4401        compare(expected, tokens);
4402
4403        let tokens = Tokenizer::new(&dialect, "0 /*!50110 */ 1")
4404            .tokenize()
4405            .unwrap();
4406        compare(
4407            vec![
4408                Token::Number("0".to_string(), false),
4409                Token::Whitespace(Whitespace::Space),
4410                Token::Whitespace(Whitespace::Space),
4411                Token::Whitespace(Whitespace::Space),
4412                Token::Number("1".to_string(), false),
4413            ],
4414            tokens,
4415        );
4416
4417        let tokens = Tokenizer::new(&dialect, "0 /*!*/ 1").tokenize().unwrap();
4418        compare(
4419            vec![
4420                Token::Number("0".to_string(), false),
4421                Token::Whitespace(Whitespace::Space),
4422                Token::Whitespace(Whitespace::Space),
4423                Token::Number("1".to_string(), false),
4424            ],
4425            tokens,
4426        );
4427        let tokens = Tokenizer::new(&dialect, "0 /*!   */ 1").tokenize().unwrap();
4428        compare(
4429            vec![
4430                Token::Number("0".to_string(), false),
4431                Token::Whitespace(Whitespace::Space),
4432                Token::Whitespace(Whitespace::Space),
4433                Token::Whitespace(Whitespace::Space),
4434                Token::Whitespace(Whitespace::Space),
4435                Token::Whitespace(Whitespace::Space),
4436                Token::Number("1".to_string(), false),
4437            ],
4438            tokens,
4439        );
4440    }
4441
4442    #[test]
4443    fn tokenize_lt() {
4444        all_dialects().tokenizes_to(
4445            "select a <-50",
4446            vec![
4447                Token::make_keyword("select"),
4448                Token::Whitespace(Whitespace::Space),
4449                Token::make_word("a", None),
4450                Token::Whitespace(Whitespace::Space),
4451                Token::Lt,
4452                Token::Minus,
4453                Token::Number("50".to_string(), false),
4454            ],
4455        );
4456        all_dialects().tokenizes_to(
4457            "select a <+50",
4458            vec![
4459                Token::make_keyword("select"),
4460                Token::Whitespace(Whitespace::Space),
4461                Token::make_word("a", None),
4462                Token::Whitespace(Whitespace::Space),
4463                Token::Lt,
4464                Token::Plus,
4465                Token::Number("50".to_string(), false),
4466            ],
4467        );
4468        all_dialects().tokenizes_to(
4469            "select a <=-50",
4470            vec![
4471                Token::make_keyword("select"),
4472                Token::Whitespace(Whitespace::Space),
4473                Token::make_word("a", None),
4474                Token::Whitespace(Whitespace::Space),
4475                Token::LtEq,
4476                Token::Minus,
4477                Token::Number("50".to_string(), false),
4478            ],
4479        );
4480        all_dialects().tokenizes_to(
4481            "select a <=+50",
4482            vec![
4483                Token::make_keyword("select"),
4484                Token::Whitespace(Whitespace::Space),
4485                Token::make_word("a", None),
4486                Token::Whitespace(Whitespace::Space),
4487                Token::LtEq,
4488                Token::Plus,
4489                Token::Number("50".to_string(), false),
4490            ],
4491        );
4492        all_dialects_where(|d| d.supports_geometric_types()).tokenizes_to(
4493            "select a <->b",
4494            vec![
4495                Token::make_keyword("select"),
4496                Token::Whitespace(Whitespace::Space),
4497                Token::make_word("a", None),
4498                Token::Whitespace(Whitespace::Space),
4499                Token::TwoWayArrow,
4500                Token::make_word("b", None),
4501            ],
4502        );
4503
4504        all_dialects().tokenizes_to(
4505            "select a <-b",
4506            vec![
4507                Token::make_keyword("select"),
4508                Token::Whitespace(Whitespace::Space),
4509                Token::make_word("a", None),
4510                Token::Whitespace(Whitespace::Space),
4511                Token::Lt,
4512                Token::Minus,
4513                Token::make_word("b", None),
4514            ],
4515        );
4516        all_dialects().tokenizes_to(
4517            "select a <+b",
4518            vec![
4519                Token::make_keyword("select"),
4520                Token::Whitespace(Whitespace::Space),
4521                Token::make_word("a", None),
4522                Token::Whitespace(Whitespace::Space),
4523                Token::Lt,
4524                Token::Plus,
4525                Token::make_word("b", None),
4526            ],
4527        );
4528    }
4529}