1#[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#[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 EOF,
61 Word(Word),
63 Number(String, bool),
65 Char(char),
67 SingleQuotedString(String),
69 DoubleQuotedString(String),
71 TripleSingleQuotedString(String),
74 TripleDoubleQuotedString(String),
77 DollarQuotedString(DollarQuotedString),
79 SingleQuotedByteStringLiteral(String),
82 DoubleQuotedByteStringLiteral(String),
84 TripleSingleQuotedByteStringLiteral(String),
87 TripleDoubleQuotedByteStringLiteral(String),
90 SingleQuotedRawStringLiteral(String),
93 DoubleQuotedRawStringLiteral(String),
96 TripleSingleQuotedRawStringLiteral(String),
99 TripleDoubleQuotedRawStringLiteral(String),
102 NationalStringLiteral(String),
104 QuoteDelimitedStringLiteral(QuoteDelimitedString),
107 NationalQuoteDelimitedStringLiteral(QuoteDelimitedString),
110 EscapedStringLiteral(String),
112 UnicodeStringLiteral(String),
114 HexStringLiteral(String),
116 Comma,
118 Whitespace(Whitespace),
120 DoubleEq,
122 Eq,
124 Neq,
126 Lt,
128 Gt,
130 LtEq,
132 GtEq,
134 Spaceship,
136 Plus,
138 Minus,
140 Mul,
142 Div,
144 DuckIntDiv,
146 Mod,
148 StringConcat,
150 LParen,
152 RParen,
154 Period,
156 Colon,
158 DoubleColon,
160 Assignment,
162 SemiColon,
164 Backslash,
166 LBracket,
168 RBracket,
170 Ampersand,
172 Pipe,
174 Caret,
176 LBrace,
178 RBrace,
180 RArrow,
182 Sharp,
184 DoubleSharp,
186 Tilde,
188 TildeAsterisk,
190 ExclamationMarkTilde,
192 ExclamationMarkTildeAsterisk,
194 DoubleTilde,
196 DoubleTildeAsterisk,
198 ExclamationMarkDoubleTilde,
200 ExclamationMarkDoubleTildeAsterisk,
202 ShiftLeft,
204 ShiftRight,
206 Overlap,
208 ExclamationMark,
210 DoubleExclamationMark,
212 AtSign,
214 CaretAt,
216 PGSquareRoot,
218 PGCubeRoot,
220 Placeholder(String),
222 Arrow,
224 LongArrow,
226 HashArrow,
228 AtDashAt,
230 QuestionMarkDash,
232 AmpersandLeftAngleBracket,
234 AmpersandRightAngleBracket,
236 AmpersandLeftAngleBracketVerticalBar,
238 VerticalBarAmpersandRightAngleBracket,
240 TwoWayArrow,
242 LeftAngleBracketCaret,
244 RightAngleBracketCaret,
246 QuestionMarkSharp,
248 QuestionMarkDashVerticalBar,
250 QuestionMarkDoubleVerticalBar,
252 TildeEqual,
254 ShiftLeftVerticalBar,
256 VerticalBarShiftRight,
258 VerticalBarRightAngleBracket,
260 HashLongArrow,
262 AtArrow,
264 ArrowAt,
266 HashMinus,
269 AtQuestion,
272 AtAt,
276 Question,
279 QuestionAnd,
282 QuestionPipe,
285 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 pub fn make_keyword(keyword: &str) -> Self {
408 Token::make_word(keyword, None)
409 }
410
411 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 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
434fn 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#[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 pub value: String,
462 pub quote_style: Option<char>,
466 pub keyword: Keyword,
469}
470
471impl fmt::Display for Word {
472 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
473 crate::ast::fmt_ident(f, &self.value, self.quote_style)
474 }
475}
476
477impl Word {
478 fn matching_end_quote(ch: char) -> char {
479 match ch {
480 '"' => '"', '[' => ']', '`' => '`', _ => panic!("unexpected quoting style!"),
484 }
485 }
486}
487
488#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
490#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
491#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
492pub enum Whitespace {
493 Space,
495 Newline,
497 Tab,
499 SingleLineComment {
502 comment: String,
504 prefix: String,
506 },
507
508 MultiLineComment(String),
510}
511
512impl fmt::Display for Whitespace {
513 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
514 match self {
515 Whitespace::Space => f.write_str(" "),
516 Whitespace::Newline => f.write_str("\n"),
517 Whitespace::Tab => f.write_str("\t"),
518 Whitespace::SingleLineComment { prefix, comment } => writeln!(f, "{prefix}{comment}"),
519 Whitespace::MultiLineComment(s) => write!(f, "/*{s}*/"),
520 }
521 }
522}
523
524#[derive(Eq, PartialEq, Hash, Clone, Copy, Ord, PartialOrd)]
544#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
545#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
546pub struct Location {
547 pub line: u64,
551 pub column: u64,
555}
556
557impl fmt::Display for Location {
558 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
559 if self.line == 0 {
560 return Ok(());
561 }
562 write!(f, " at Line: {}, Column: {}", self.line, self.column)
563 }
564}
565
566impl fmt::Debug for Location {
567 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
568 write!(f, "Location({},{})", self.line, self.column)
569 }
570}
571
572impl Location {
573 pub fn empty() -> Self {
575 Self { line: 0, column: 0 }
576 }
577
578 pub fn new(line: u64, column: u64) -> Self {
580 Self { line, column }
581 }
582
583 pub fn of(line: u64, column: u64) -> Self {
588 Self::new(line, column)
589 }
590
591 pub fn span_to(self, end: Self) -> Span {
593 Span { start: self, end }
594 }
595}
596
597impl From<(u64, u64)> for Location {
598 fn from((line, column): (u64, u64)) -> Self {
599 Self { line, column }
600 }
601}
602
603#[derive(Eq, PartialEq, Hash, Clone, PartialOrd, Ord, Copy)]
607#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
608#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
609pub struct Span {
610 pub start: Location,
612 pub end: Location,
614}
615
616impl fmt::Debug for Span {
617 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
618 write!(f, "Span({:?}..{:?})", self.start, self.end)
619 }
620}
621
622impl Span {
623 const EMPTY: Span = Self::empty();
626
627 pub fn new(start: Location, end: Location) -> Span {
629 Span { start, end }
630 }
631
632 pub const fn empty() -> Span {
637 Span {
638 start: Location { line: 0, column: 0 },
639 end: Location { line: 0, column: 0 },
640 }
641 }
642
643 pub fn union(&self, other: &Span) -> Span {
659 match (self, other) {
662 (&Span::EMPTY, _) => *other,
663 (_, &Span::EMPTY) => *self,
664 _ => Span {
665 start: cmp::min(self.start, other.start),
666 end: cmp::max(self.end, other.end),
667 },
668 }
669 }
670
671 pub fn union_opt(&self, other: &Option<Span>) -> Span {
675 match other {
676 Some(other) => self.union(other),
677 None => *self,
678 }
679 }
680
681 pub fn union_iter<I: IntoIterator<Item = Span>>(iter: I) -> Span {
699 iter.into_iter()
700 .reduce(|acc, item| acc.union(&item))
701 .unwrap_or(Span::empty())
702 }
703}
704
705#[deprecated(since = "0.53.0", note = "please use `TokenWithSpan` instead")]
707pub type TokenWithLocation = TokenWithSpan;
708
709#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
732#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
733#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
734pub struct TokenWithSpan {
736 pub token: Token,
738 pub span: Span,
740}
741
742impl TokenWithSpan {
743 pub fn new(token: Token, span: Span) -> Self {
745 Self { token, span }
746 }
747
748 pub fn wrap(token: Token) -> Self {
750 Self::new(token, Span::empty())
751 }
752
753 pub fn at(token: Token, start: Location, end: Location) -> Self {
755 Self::new(token, Span::new(start, end))
756 }
757
758 pub fn new_eof() -> Self {
760 Self::wrap(Token::EOF)
761 }
762}
763
764impl PartialEq<Token> for TokenWithSpan {
765 fn eq(&self, other: &Token) -> bool {
766 &self.token == other
767 }
768}
769
770impl PartialEq<TokenWithSpan> for Token {
771 fn eq(&self, other: &TokenWithSpan) -> bool {
772 self == &other.token
773 }
774}
775
776impl fmt::Display for TokenWithSpan {
777 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
778 self.token.fmt(f)
779 }
780}
781
782#[derive(Debug, PartialEq, Eq)]
784pub struct TokenizerError {
785 pub message: String,
787 pub location: Location,
789}
790
791impl fmt::Display for TokenizerError {
792 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
793 write!(f, "{}{}", self.message, self.location,)
794 }
795}
796
797impl core::error::Error for TokenizerError {}
798
799struct State<'a> {
800 peekable: Peekable<Chars<'a>>,
801 line: u64,
802 col: u64,
803}
804
805impl State<'_> {
806 pub fn next(&mut self) -> Option<char> {
808 match self.peekable.next() {
809 None => None,
810 Some(s) => {
811 if s == '\n' {
812 self.line += 1;
813 self.col = 1;
814 } else {
815 self.col += 1;
816 }
817 Some(s)
818 }
819 }
820 }
821
822 pub fn peek(&mut self) -> Option<&char> {
824 self.peekable.peek()
825 }
826
827 pub fn location(&self) -> Location {
829 Location {
830 line: self.line,
831 column: self.col,
832 }
833 }
834}
835
836#[derive(Copy, Clone)]
838enum NumStringQuoteChars {
839 One,
841 Many(NonZeroU8),
843}
844
845struct TokenizeQuotedStringSettings {
847 quote_style: char,
849 num_quote_chars: NumStringQuoteChars,
851 num_opening_quotes_to_consume: u8,
857 backslash_escape: bool,
860}
861
862pub struct Tokenizer<'a> {
864 dialect: &'a dyn Dialect,
865 query: &'a str,
866 unescape: bool,
869}
870
871impl<'a> Tokenizer<'a> {
872 pub fn new(dialect: &'a dyn Dialect, query: &'a str) -> Self {
889 Self {
890 dialect,
891 query,
892 unescape: true,
893 }
894 }
895
896 pub fn with_unescape(mut self, unescape: bool) -> Self {
927 self.unescape = unescape;
928 self
929 }
930
931 pub fn tokenize(&mut self) -> Result<Vec<Token>, TokenizerError> {
933 let twl = self.tokenize_with_location()?;
934 Ok(twl.into_iter().map(|t| t.token).collect())
935 }
936
937 pub fn tokenize_with_location(&mut self) -> Result<Vec<TokenWithSpan>, TokenizerError> {
939 let mut tokens: Vec<TokenWithSpan> = vec![];
940 self.tokenize_with_location_into_buf(&mut tokens)
941 .map(|_| tokens)
942 }
943
944 pub fn tokenize_with_location_into_buf(
947 &mut self,
948 buf: &mut Vec<TokenWithSpan>,
949 ) -> Result<(), TokenizerError> {
950 self.tokenize_with_location_into_buf_with_mapper(buf, |token| token)
951 }
952
953 pub fn tokenize_with_location_into_buf_with_mapper(
956 &mut self,
957 buf: &mut Vec<TokenWithSpan>,
958 mut mapper: impl FnMut(TokenWithSpan) -> TokenWithSpan,
959 ) -> Result<(), TokenizerError> {
960 let mut state = State {
961 peekable: self.query.chars().peekable(),
962 line: 1,
963 col: 1,
964 };
965
966 let mut location = state.location();
967 while let Some(token) = self.next_token(&mut state, buf.last().map(|t| &t.token))? {
968 let span = location.span_to(state.location());
969
970 match &token {
972 Token::Whitespace(Whitespace::MultiLineComment(comment))
973 if self.dialect.supports_multiline_comment_hints()
974 && comment.starts_with('!') =>
975 {
976 self.tokenize_comment_hints(comment, span, buf, &mut mapper)?;
978 }
979 _ => {
980 buf.push(mapper(TokenWithSpan { token, span }));
981 }
982 }
983
984 location = state.location();
985 }
986 Ok(())
987 }
988
989 fn tokenize_comment_hints(
992 &self,
993 comment: &str,
994 span: Span,
995 buf: &mut Vec<TokenWithSpan>,
996 mut mapper: impl FnMut(TokenWithSpan) -> TokenWithSpan,
997 ) -> Result<(), TokenizerError> {
998 let hint_content = comment
1000 .strip_prefix('!')
1001 .unwrap_or(comment)
1002 .trim_start_matches(|c: char| c.is_ascii_digit());
1003
1004 if hint_content.is_empty() {
1006 return Ok(());
1007 }
1008
1009 let inner = Tokenizer::new(self.dialect, hint_content).with_unescape(self.unescape);
1011
1012 let mut state = State {
1014 peekable: hint_content.chars().peekable(),
1015 line: span.start.line,
1016 col: span.start.column,
1017 };
1018
1019 let mut location = state.location();
1021 while let Some(token) = inner.next_token(&mut state, buf.last().map(|t| &t.token))? {
1022 let token_span = location.span_to(state.location());
1023 buf.push(mapper(TokenWithSpan {
1024 token,
1025 span: token_span,
1026 }));
1027 location = state.location();
1028 }
1029
1030 Ok(())
1031 }
1032
1033 fn tokenize_identifier_or_keyword(
1035 &self,
1036 ch: impl IntoIterator<Item = char>,
1037 chars: &mut State,
1038 ) -> Result<Option<Token>, TokenizerError> {
1039 chars.next(); let ch: String = ch.into_iter().collect();
1041 let word = self.tokenize_word(ch, chars);
1042
1043 if word.chars().all(|x| x.is_ascii_digit() || x == '.') {
1045 let mut inner_state = State {
1046 peekable: word.chars().peekable(),
1047 line: 0,
1048 col: 0,
1049 };
1050 let mut s = peeking_take_while(&mut inner_state, |ch| matches!(ch, '0'..='9' | '.'));
1051 let s2 = peeking_take_while(chars, |ch| matches!(ch, '0'..='9' | '.'));
1052 s += s2.as_str();
1053 return Ok(Some(Token::Number(s, false)));
1054 }
1055
1056 Ok(Some(Token::make_word_owned(word, None)))
1057 }
1058
1059 fn next_token(
1061 &self,
1062 chars: &mut State,
1063 prev_token: Option<&Token>,
1064 ) -> Result<Option<Token>, TokenizerError> {
1065 match chars.peek() {
1066 Some(&ch) => match ch {
1067 ' ' => self.consume_and_return(chars, Token::Whitespace(Whitespace::Space)),
1068 '\t' => self.consume_and_return(chars, Token::Whitespace(Whitespace::Tab)),
1069 '\n' => self.consume_and_return(chars, Token::Whitespace(Whitespace::Newline)),
1070 '\r' => {
1071 chars.next();
1073 if let Some('\n') = chars.peek() {
1074 chars.next();
1075 }
1076 Ok(Some(Token::Whitespace(Whitespace::Newline)))
1077 }
1078 b @ 'B' | b @ 'b' if dialect_of!(self is BigQueryDialect | PostgreSqlDialect | MySqlDialect | GenericDialect) =>
1080 {
1081 chars.next(); match chars.peek() {
1083 Some('\'') => {
1084 if self.dialect.supports_triple_quoted_string() {
1085 return self
1086 .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1087 chars,
1088 '\'',
1089 false,
1090 Token::SingleQuotedByteStringLiteral,
1091 Token::TripleSingleQuotedByteStringLiteral,
1092 );
1093 }
1094 let s = self.tokenize_single_quoted_string(chars, '\'', false)?;
1095 Ok(Some(Token::SingleQuotedByteStringLiteral(s)))
1096 }
1097 Some('\"') => {
1098 if self.dialect.supports_triple_quoted_string() {
1099 return self
1100 .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1101 chars,
1102 '"',
1103 false,
1104 Token::DoubleQuotedByteStringLiteral,
1105 Token::TripleDoubleQuotedByteStringLiteral,
1106 );
1107 }
1108 let s = self.tokenize_single_quoted_string(chars, '\"', false)?;
1109 Ok(Some(Token::DoubleQuotedByteStringLiteral(s)))
1110 }
1111 _ => {
1112 let s = self.tokenize_word(b, chars);
1114 Ok(Some(Token::make_word_owned(s, None)))
1115 }
1116 }
1117 }
1118 b @ 'R' | b @ 'r' if dialect_of!(self is BigQueryDialect | GenericDialect) => {
1120 chars.next(); match chars.peek() {
1122 Some('\'') => self
1123 .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1124 chars,
1125 '\'',
1126 false,
1127 Token::SingleQuotedRawStringLiteral,
1128 Token::TripleSingleQuotedRawStringLiteral,
1129 ),
1130 Some('\"') => self
1131 .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1132 chars,
1133 '"',
1134 false,
1135 Token::DoubleQuotedRawStringLiteral,
1136 Token::TripleDoubleQuotedRawStringLiteral,
1137 ),
1138 _ => {
1139 let s = self.tokenize_word(b, chars);
1141 Ok(Some(Token::make_word_owned(s, None)))
1142 }
1143 }
1144 }
1145 n @ 'N' | n @ 'n' => {
1147 chars.next(); match chars.peek() {
1149 Some('\'') if self.dialect.supports_national_string_literal() => {
1150 let backslash_escape =
1152 self.dialect.supports_string_literal_backslash_escape();
1153 let s =
1154 self.tokenize_single_quoted_string(chars, '\'', backslash_escape)?;
1155 Ok(Some(Token::NationalStringLiteral(s)))
1156 }
1157 Some(&q @ 'q') | Some(&q @ 'Q')
1158 if self.dialect.supports_quote_delimited_string() =>
1159 {
1160 chars.next(); if let Some('\'') = chars.peek() {
1162 self.tokenize_quote_delimited_string(chars, &[n, q])
1163 .map(|s| Some(Token::NationalQuoteDelimitedStringLiteral(s)))
1164 } else {
1165 let s = self.tokenize_word(String::from_iter([n, q]), chars);
1166 Ok(Some(Token::make_word_owned(s, None)))
1167 }
1168 }
1169 _ => {
1170 let s = self.tokenize_word(n, chars);
1172 Ok(Some(Token::make_word_owned(s, None)))
1173 }
1174 }
1175 }
1176 q @ 'Q' | q @ 'q' if self.dialect.supports_quote_delimited_string() => {
1177 chars.next(); if let Some('\'') = chars.peek() {
1179 self.tokenize_quote_delimited_string(chars, &[q])
1180 .map(|s| Some(Token::QuoteDelimitedStringLiteral(s)))
1181 } else {
1182 let s = self.tokenize_word(q, chars);
1183 Ok(Some(Token::make_word_owned(s, None)))
1184 }
1185 }
1186 x @ 'e' | x @ 'E' if self.dialect.supports_string_escape_constant() => {
1188 let starting_loc = chars.location();
1189 chars.next(); match chars.peek() {
1191 Some('\'') => {
1192 let s =
1193 self.tokenize_escaped_single_quoted_string(starting_loc, chars)?;
1194 Ok(Some(Token::EscapedStringLiteral(s)))
1195 }
1196 _ => {
1197 let s = self.tokenize_word(x, chars);
1199 Ok(Some(Token::make_word_owned(s, None)))
1200 }
1201 }
1202 }
1203 x @ 'u' | x @ 'U' if self.dialect.supports_unicode_string_literal() => {
1205 chars.next(); if chars.peek() == Some(&'&') {
1207 let mut chars_clone = chars.peekable.clone();
1209 chars_clone.next(); if chars_clone.peek() == Some(&'\'') {
1211 chars.next(); let s = unescape_unicode_single_quoted_string(chars)?;
1213 return Ok(Some(Token::UnicodeStringLiteral(s)));
1214 }
1215 }
1216 let s = self.tokenize_word(x, chars);
1218 Ok(Some(Token::make_word_owned(s, None)))
1219 }
1220 x @ 'x' | x @ 'X' => {
1223 chars.next(); match chars.peek() {
1225 Some('\'') => {
1226 let s = self.tokenize_single_quoted_string(chars, '\'', true)?;
1228 Ok(Some(Token::HexStringLiteral(s)))
1229 }
1230 _ => {
1231 let s = self.tokenize_word(x, chars);
1233 Ok(Some(Token::make_word_owned(s, None)))
1234 }
1235 }
1236 }
1237 '\'' => {
1239 if self.dialect.supports_triple_quoted_string() {
1240 return self
1241 .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1242 chars,
1243 '\'',
1244 self.dialect.supports_string_literal_backslash_escape(),
1245 Token::SingleQuotedString,
1246 Token::TripleSingleQuotedString,
1247 );
1248 }
1249 let s = self.tokenize_single_quoted_string(
1250 chars,
1251 '\'',
1252 self.dialect.supports_string_literal_backslash_escape(),
1253 )?;
1254
1255 Ok(Some(Token::SingleQuotedString(s)))
1256 }
1257 '\"' if !self.dialect.is_delimited_identifier_start(ch)
1259 && !self.dialect.is_identifier_start(ch) =>
1260 {
1261 if self.dialect.supports_triple_quoted_string() {
1262 return self
1263 .tokenize_single_or_triple_quoted_string::<fn(String) -> Token>(
1264 chars,
1265 '"',
1266 self.dialect.supports_string_literal_backslash_escape(),
1267 Token::DoubleQuotedString,
1268 Token::TripleDoubleQuotedString,
1269 );
1270 }
1271 let s = self.tokenize_single_quoted_string(
1272 chars,
1273 '"',
1274 self.dialect.supports_string_literal_backslash_escape(),
1275 )?;
1276
1277 Ok(Some(Token::DoubleQuotedString(s)))
1278 }
1279 quote_start if self.dialect.is_delimited_identifier_start(ch) => {
1281 let word = self.tokenize_quoted_identifier(quote_start, chars)?;
1282 Ok(Some(Token::make_word_owned(word, Some(quote_start))))
1283 }
1284 quote_start
1286 if self
1287 .dialect
1288 .is_nested_delimited_identifier_start(quote_start)
1289 && self
1290 .dialect
1291 .peek_nested_delimited_identifier_quotes(chars.peekable.clone())
1292 .is_some() =>
1293 {
1294 let Some((quote_start, nested_quote_start)) = self
1295 .dialect
1296 .peek_nested_delimited_identifier_quotes(chars.peekable.clone())
1297 else {
1298 return self.tokenizer_error(
1299 chars.location(),
1300 format!("Expected nested delimiter '{quote_start}' before EOF."),
1301 );
1302 };
1303
1304 let Some(nested_quote_start) = nested_quote_start else {
1305 let word = self.tokenize_quoted_identifier(quote_start, chars)?;
1306 return Ok(Some(Token::make_word_owned(word, Some(quote_start))));
1307 };
1308
1309 let mut word = vec![];
1310 let quote_end = Word::matching_end_quote(quote_start);
1311 let nested_quote_end = Word::matching_end_quote(nested_quote_start);
1312 let error_loc = chars.location();
1313
1314 chars.next(); peeking_take_while(chars, |ch| ch.is_whitespace());
1316 if chars.peek() != Some(&nested_quote_start) {
1317 return self.tokenizer_error(
1318 error_loc,
1319 format!("Expected nested delimiter '{nested_quote_start}' before EOF."),
1320 );
1321 }
1322 word.push(nested_quote_start.into());
1323 word.push(self.tokenize_quoted_identifier(nested_quote_end, chars)?);
1324 word.push(nested_quote_end.into());
1325 peeking_take_while(chars, |ch| ch.is_whitespace());
1326 if chars.peek() != Some("e_end) {
1327 return self.tokenizer_error(
1328 error_loc,
1329 format!("Expected close delimiter '{quote_end}' before EOF."),
1330 );
1331 }
1332 chars.next(); Ok(Some(Token::make_word_owned(
1335 word.concat(),
1336 Some(quote_start),
1337 )))
1338 }
1339 '0'..='9' | '.' => {
1341 if ch == '.' && chars.peekable.clone().nth(1) == Some('_') {
1346 if let Some(Token::Word(_)) = prev_token {
1347 chars.next();
1348 return Ok(Some(Token::Period));
1349 }
1350
1351 return self.tokenizer_error(
1352 chars.location(),
1353 "Unexpected character '_'".to_string(),
1354 );
1355 }
1356
1357 if ch == '.'
1360 && self.dialect.supports_tuple_element_access()
1361 && matches!(
1362 prev_token,
1363 Some(
1364 Token::Word(_)
1365 | Token::RParen
1366 | Token::RBracket
1367 | Token::Number(..)
1368 )
1369 )
1370 {
1371 chars.next();
1372 return Ok(Some(Token::Period));
1373 }
1374
1375 let mut s = self.tokenize_number_part(chars, |ch| ch.is_ascii_digit())?;
1376
1377 if !s.is_empty()
1379 && self.dialect.supports_tuple_element_access()
1380 && prev_token == Some(&Token::Period)
1381 {
1382 return Ok(Some(Token::Number(s, false)));
1383 }
1384
1385 if s == "0" && chars.peek() == Some(&'x') {
1387 chars.next();
1388 let s2 = self.tokenize_number_part(chars, |ch| ch.is_ascii_hexdigit())?;
1389 return Ok(Some(Token::HexStringLiteral(s2)));
1390 }
1391
1392 if let Some('.') = chars.peek() {
1394 s.push('.');
1395 chars.next();
1396 }
1397
1398 if s == "." && self.dialect.supports_numeric_prefix() {
1404 if let Some(Token::Word(_)) = prev_token {
1405 return Ok(Some(Token::Period));
1406 }
1407 }
1408
1409 s += &self.tokenize_number_part(chars, |ch| ch.is_ascii_digit())?;
1411
1412 if s == "." {
1414 return Ok(Some(Token::Period));
1415 }
1416
1417 let mut exponent_part = String::new();
1419 if chars.peek() == Some(&'e') || chars.peek() == Some(&'E') {
1420 let mut char_clone = chars.peekable.clone();
1421 exponent_part.push(char_clone.next().unwrap());
1422
1423 match char_clone.peek() {
1425 Some(&c) if matches!(c, '+' | '-') => {
1426 exponent_part.push(c);
1427 char_clone.next();
1428 }
1429 _ => (),
1430 }
1431
1432 match char_clone.peek() {
1433 Some(&c)
1435 if c.is_ascii_digit()
1436 || (c == '_'
1437 && self.dialect.supports_numeric_literal_underscores()) =>
1438 {
1439 for _ in 0..exponent_part.len() {
1440 chars.next();
1441 }
1442 exponent_part +=
1443 &self.tokenize_number_part(chars, |ch| ch.is_ascii_digit())?;
1444 s += exponent_part.as_str();
1445 }
1446 _ => (),
1448 }
1449 }
1450
1451 if self.dialect.supports_numeric_prefix() {
1455 if exponent_part.is_empty() {
1456 let word =
1459 peeking_take_while(chars, |ch| self.dialect.is_identifier_part(ch));
1460
1461 if !word.is_empty() {
1462 s += word.as_str();
1463 return Ok(Some(Token::make_word_owned(s, None)));
1464 }
1465 } else if prev_token == Some(&Token::Period) {
1466 return Ok(Some(Token::make_word_owned(s, None)));
1469 }
1470 }
1471
1472 let long = if chars.peek() == Some(&'L') {
1473 chars.next();
1474 true
1475 } else {
1476 false
1477 };
1478 Ok(Some(Token::Number(s, long)))
1479 }
1480 '(' => self.consume_and_return(chars, Token::LParen),
1482 ')' => self.consume_and_return(chars, Token::RParen),
1483 ',' => self.consume_and_return(chars, Token::Comma),
1484 '-' => {
1486 chars.next(); match chars.peek() {
1489 Some('-') => {
1490 let mut is_comment = true;
1491 if self.dialect.requires_single_line_comment_whitespace() {
1492 is_comment = chars
1493 .peekable
1494 .clone()
1495 .nth(1)
1496 .is_some_and(char::is_whitespace);
1497 }
1498
1499 if is_comment {
1500 chars.next(); let comment = self.tokenize_single_line_comment(chars);
1502 return Ok(Some(Token::Whitespace(
1503 Whitespace::SingleLineComment {
1504 prefix: "--".to_owned(),
1505 comment,
1506 },
1507 )));
1508 }
1509
1510 self.start_binop(chars, "-", Token::Minus)
1511 }
1512 Some('>') => {
1513 chars.next();
1514 match chars.peek() {
1515 Some('>') => self.consume_for_binop(chars, "->>", Token::LongArrow),
1516 _ => self.start_binop(chars, "->", Token::Arrow),
1517 }
1518 }
1519 _ => self.start_binop(chars, "-", Token::Minus),
1521 }
1522 }
1523 '/' => {
1524 chars.next(); match chars.peek() {
1526 Some('*') => {
1527 chars.next(); self.tokenize_multiline_comment(chars)
1529 }
1530 Some('/') if dialect_of!(self is SnowflakeDialect) => {
1531 chars.next(); let comment = self.tokenize_single_line_comment(chars);
1533 Ok(Some(Token::Whitespace(Whitespace::SingleLineComment {
1534 prefix: "//".to_owned(),
1535 comment,
1536 })))
1537 }
1538 Some('/') if dialect_of!(self is DuckDbDialect | GenericDialect) => {
1539 self.consume_and_return(chars, Token::DuckIntDiv)
1540 }
1541 _ => Ok(Some(Token::Div)),
1543 }
1544 }
1545 '+' => self.consume_and_return(chars, Token::Plus),
1546 '*' => self.consume_and_return(chars, Token::Mul),
1547 '%' => {
1548 chars.next(); match chars.peek() {
1550 Some(s) if s.is_whitespace() => Ok(Some(Token::Mod)),
1551 Some(sch) if self.dialect.is_identifier_start('%') => {
1552 self.tokenize_identifier_or_keyword([ch, *sch], chars)
1553 }
1554 _ => self.start_binop(chars, "%", Token::Mod),
1555 }
1556 }
1557 '|' => {
1558 chars.next(); match chars.peek() {
1560 Some('/') => self.consume_for_binop(chars, "|/", Token::PGSquareRoot),
1561 Some('|') => {
1562 chars.next(); match chars.peek() {
1564 Some('/') => {
1565 self.consume_for_binop(chars, "||/", Token::PGCubeRoot)
1566 }
1567 _ => self.start_binop(chars, "||", Token::StringConcat),
1568 }
1569 }
1570 Some('&') if self.dialect.supports_geometric_types() => {
1571 chars.next(); match chars.peek() {
1573 Some('>') => self.consume_for_binop(
1574 chars,
1575 "|&>",
1576 Token::VerticalBarAmpersandRightAngleBracket,
1577 ),
1578 _ => self.start_binop_opt(chars, "|&", None),
1579 }
1580 }
1581 Some('>') if self.dialect.supports_geometric_types() => {
1582 chars.next(); match chars.peek() {
1584 Some('>') => self.consume_for_binop(
1585 chars,
1586 "|>>",
1587 Token::VerticalBarShiftRight,
1588 ),
1589 _ => self.start_binop_opt(chars, "|>", None),
1590 }
1591 }
1592 Some('>') if self.dialect.supports_pipe_operator() => {
1593 self.consume_for_binop(chars, "|>", Token::VerticalBarRightAngleBracket)
1594 }
1595 _ => self.start_binop(chars, "|", Token::Pipe),
1597 }
1598 }
1599 '=' => {
1600 chars.next(); match chars.peek() {
1602 Some('>') => self.consume_and_return(chars, Token::RArrow),
1603 Some('=') => self.consume_and_return(chars, Token::DoubleEq),
1604 _ => Ok(Some(Token::Eq)),
1605 }
1606 }
1607 '!' => {
1608 chars.next(); match chars.peek() {
1610 Some('=') => self.consume_and_return(chars, Token::Neq),
1611 Some('!') => self.consume_and_return(chars, Token::DoubleExclamationMark),
1612 Some('~') => {
1613 chars.next();
1614 match chars.peek() {
1615 Some('*') => self
1616 .consume_and_return(chars, Token::ExclamationMarkTildeAsterisk),
1617 Some('~') => {
1618 chars.next();
1619 match chars.peek() {
1620 Some('*') => self.consume_and_return(
1621 chars,
1622 Token::ExclamationMarkDoubleTildeAsterisk,
1623 ),
1624 _ => Ok(Some(Token::ExclamationMarkDoubleTilde)),
1625 }
1626 }
1627 _ => Ok(Some(Token::ExclamationMarkTilde)),
1628 }
1629 }
1630 _ => Ok(Some(Token::ExclamationMark)),
1631 }
1632 }
1633 '<' => {
1634 chars.next(); match chars.peek() {
1636 Some('=') => {
1637 chars.next();
1638 match chars.peek() {
1639 Some('>') => self.consume_for_binop(chars, "<=>", Token::Spaceship),
1640 Some('+') | Some('-') => Ok(Some(Token::LtEq)),
1643 _ => self.start_binop(chars, "<=", Token::LtEq),
1644 }
1645 }
1646 Some('|') if self.dialect.supports_geometric_types() => {
1647 self.consume_for_binop(chars, "<<|", Token::ShiftLeftVerticalBar)
1648 }
1649 Some('>') => self.consume_for_binop(chars, "<>", Token::Neq),
1650 Some('<') if self.dialect.supports_geometric_types() => {
1651 chars.next(); match chars.peek() {
1653 Some('|') => self.consume_for_binop(
1654 chars,
1655 "<<|",
1656 Token::ShiftLeftVerticalBar,
1657 ),
1658 _ => self.start_binop(chars, "<<", Token::ShiftLeft),
1659 }
1660 }
1661 Some('<') => self.consume_for_binop(chars, "<<", Token::ShiftLeft),
1662 Some('+') => Ok(Some(Token::Lt)),
1665 Some('-') if self.dialect.supports_geometric_types() => {
1666 if chars.peekable.clone().nth(1) == Some('>') {
1667 chars.next(); self.consume_for_binop(chars, "<->", Token::TwoWayArrow)
1669 } else {
1670 Ok(Some(Token::Lt))
1671 }
1672 }
1673 Some('^') if self.dialect.supports_geometric_types() => {
1674 self.consume_for_binop(chars, "<^", Token::LeftAngleBracketCaret)
1675 }
1676 Some('@') => self.consume_for_binop(chars, "<@", Token::ArrowAt),
1677 _ => self.start_binop(chars, "<", Token::Lt),
1678 }
1679 }
1680 '>' => {
1681 chars.next(); match chars.peek() {
1683 Some('=') => self.consume_for_binop(chars, ">=", Token::GtEq),
1684 Some('>') => self.consume_for_binop(chars, ">>", Token::ShiftRight),
1685 Some('^') if self.dialect.supports_geometric_types() => {
1686 self.consume_for_binop(chars, ">^", Token::RightAngleBracketCaret)
1687 }
1688 _ => self.start_binop(chars, ">", Token::Gt),
1689 }
1690 }
1691 ':' => {
1692 chars.next();
1693 match chars.peek() {
1694 Some(':') => self.consume_and_return(chars, Token::DoubleColon),
1695 Some('=') => self.consume_and_return(chars, Token::Assignment),
1696 _ => Ok(Some(Token::Colon)),
1697 }
1698 }
1699 ';' => self.consume_and_return(chars, Token::SemiColon),
1700 '\\' => self.consume_and_return(chars, Token::Backslash),
1701 '[' => self.consume_and_return(chars, Token::LBracket),
1702 ']' => self.consume_and_return(chars, Token::RBracket),
1703 '&' => {
1704 chars.next(); match chars.peek() {
1706 Some('>') if self.dialect.supports_geometric_types() => {
1707 chars.next();
1708 self.consume_and_return(chars, Token::AmpersandRightAngleBracket)
1709 }
1710 Some('<') if self.dialect.supports_geometric_types() => {
1711 chars.next(); match chars.peek() {
1713 Some('|') => self.consume_and_return(
1714 chars,
1715 Token::AmpersandLeftAngleBracketVerticalBar,
1716 ),
1717 _ => {
1718 self.start_binop(chars, "&<", Token::AmpersandLeftAngleBracket)
1719 }
1720 }
1721 }
1722 Some('&') => {
1723 chars.next(); self.start_binop(chars, "&&", Token::Overlap)
1725 }
1726 _ => self.start_binop(chars, "&", Token::Ampersand),
1728 }
1729 }
1730 '^' => {
1731 chars.next(); match chars.peek() {
1733 Some('@') => self.consume_and_return(chars, Token::CaretAt),
1734 _ => Ok(Some(Token::Caret)),
1735 }
1736 }
1737 '{' => self.consume_and_return(chars, Token::LBrace),
1738 '}' => self.consume_and_return(chars, Token::RBrace),
1739 '#' if dialect_of!(self is SnowflakeDialect | BigQueryDialect | MySqlDialect | HiveDialect) =>
1740 {
1741 chars.next(); let comment = self.tokenize_single_line_comment(chars);
1743 Ok(Some(Token::Whitespace(Whitespace::SingleLineComment {
1744 prefix: "#".to_owned(),
1745 comment,
1746 })))
1747 }
1748 '~' => {
1749 chars.next(); match chars.peek() {
1751 Some('*') => self.consume_for_binop(chars, "~*", Token::TildeAsterisk),
1752 Some('=') if self.dialect.supports_geometric_types() => {
1753 self.consume_for_binop(chars, "~=", Token::TildeEqual)
1754 }
1755 Some('~') => {
1756 chars.next();
1757 match chars.peek() {
1758 Some('*') => {
1759 self.consume_for_binop(chars, "~~*", Token::DoubleTildeAsterisk)
1760 }
1761 _ => self.start_binop(chars, "~~", Token::DoubleTilde),
1762 }
1763 }
1764 _ => self.start_binop(chars, "~", Token::Tilde),
1765 }
1766 }
1767 '#' => {
1768 chars.next();
1769 match chars.peek() {
1770 Some('-') => self.consume_for_binop(chars, "#-", Token::HashMinus),
1771 Some('>') => {
1772 chars.next();
1773 match chars.peek() {
1774 Some('>') => {
1775 self.consume_for_binop(chars, "#>>", Token::HashLongArrow)
1776 }
1777 _ => self.start_binop(chars, "#>", Token::HashArrow),
1778 }
1779 }
1780 Some(' ') => Ok(Some(Token::Sharp)),
1781 Some('#') if self.dialect.supports_geometric_types() => {
1782 self.consume_for_binop(chars, "##", Token::DoubleSharp)
1783 }
1784 Some(sch) if self.dialect.is_identifier_start('#') => {
1785 self.tokenize_identifier_or_keyword([ch, *sch], chars)
1786 }
1787 _ => self.start_binop(chars, "#", Token::Sharp),
1788 }
1789 }
1790 '@' => {
1791 chars.next();
1792 match chars.peek() {
1793 Some('@') if self.dialect.supports_geometric_types() => {
1794 self.consume_and_return(chars, Token::AtAt)
1795 }
1796 Some('-') if self.dialect.supports_geometric_types() => {
1797 chars.next();
1798 match chars.peek() {
1799 Some('@') => self.consume_and_return(chars, Token::AtDashAt),
1800 _ => self.start_binop_opt(chars, "@-", None),
1801 }
1802 }
1803 Some('>') => self.consume_and_return(chars, Token::AtArrow),
1804 Some('?') => self.consume_and_return(chars, Token::AtQuestion),
1805 Some('@') => {
1806 chars.next();
1807 match chars.peek() {
1808 Some(' ') => Ok(Some(Token::AtAt)),
1809 Some(tch) if self.dialect.is_identifier_start('@') => {
1810 self.tokenize_identifier_or_keyword([ch, '@', *tch], chars)
1811 }
1812 _ => Ok(Some(Token::AtAt)),
1813 }
1814 }
1815 Some(' ') => Ok(Some(Token::AtSign)),
1816 Some('\'') => Ok(Some(Token::AtSign)),
1826 Some('\"') => Ok(Some(Token::AtSign)),
1827 Some('`') => Ok(Some(Token::AtSign)),
1828 Some(sch) if self.dialect.is_identifier_start('@') => {
1829 self.tokenize_identifier_or_keyword([ch, *sch], chars)
1830 }
1831 _ => Ok(Some(Token::AtSign)),
1832 }
1833 }
1834 '?' if self.dialect.supports_geometric_types() => {
1836 chars.next(); match chars.peek() {
1838 Some('|') => {
1839 chars.next();
1840 match chars.peek() {
1841 Some('|') => self.consume_and_return(
1842 chars,
1843 Token::QuestionMarkDoubleVerticalBar,
1844 ),
1845 _ => Ok(Some(Token::QuestionPipe)),
1846 }
1847 }
1848
1849 Some('&') => self.consume_and_return(chars, Token::QuestionAnd),
1850 Some('-') => {
1851 chars.next(); match chars.peek() {
1853 Some('|') => self
1854 .consume_and_return(chars, Token::QuestionMarkDashVerticalBar),
1855 _ => Ok(Some(Token::QuestionMarkDash)),
1856 }
1857 }
1858 Some('#') => self.consume_and_return(chars, Token::QuestionMarkSharp),
1859 _ => Ok(Some(Token::Question)),
1860 }
1861 }
1862 '?' => {
1863 chars.next();
1864 let s = peeking_take_while(chars, |ch| ch.is_numeric());
1865 Ok(Some(Token::Placeholder(format!("?{s}"))))
1866 }
1867
1868 ch if self.dialect.is_identifier_start(ch) => {
1870 self.tokenize_identifier_or_keyword([ch], chars)
1871 }
1872 '$' => Ok(Some(self.tokenize_dollar_preceded_value(chars)?)),
1873
1874 ch if ch.is_whitespace() => {
1876 self.consume_and_return(chars, Token::Whitespace(Whitespace::Space))
1877 }
1878 other => self.consume_and_return(chars, Token::Char(other)),
1879 },
1880 None => Ok(None),
1881 }
1882 }
1883
1884 fn consume_for_binop(
1886 &self,
1887 chars: &mut State,
1888 prefix: &str,
1889 default: Token,
1890 ) -> Result<Option<Token>, TokenizerError> {
1891 chars.next(); self.start_binop_opt(chars, prefix, Some(default))
1893 }
1894
1895 fn start_binop(
1897 &self,
1898 chars: &mut State,
1899 prefix: &str,
1900 default: Token,
1901 ) -> Result<Option<Token>, TokenizerError> {
1902 self.start_binop_opt(chars, prefix, Some(default))
1903 }
1904
1905 fn start_binop_opt(
1907 &self,
1908 chars: &mut State,
1909 prefix: &str,
1910 default: Option<Token>,
1911 ) -> Result<Option<Token>, TokenizerError> {
1912 let mut custom = None;
1913 while let Some(&ch) = chars.peek() {
1914 if !self.dialect.is_custom_operator_part(ch) {
1915 break;
1916 }
1917
1918 custom.get_or_insert_with(|| prefix.to_string()).push(ch);
1919 chars.next();
1920 }
1921 match (custom, default) {
1922 (Some(custom), _) => Ok(Token::CustomBinaryOperator(custom).into()),
1923 (None, Some(tok)) => Ok(Some(tok)),
1924 (None, None) => self.tokenizer_error(
1925 chars.location(),
1926 format!("Expected a valid binary operator after '{prefix}'"),
1927 ),
1928 }
1929 }
1930
1931 fn tokenize_dollar_preceded_value(&self, chars: &mut State) -> Result<Token, TokenizerError> {
1933 let mut s = String::new();
1934 let mut value = String::new();
1935
1936 chars.next();
1937
1938 if matches!(chars.peek(), Some('$')) && !self.dialect.supports_dollar_placeholder() {
1940 chars.next();
1941
1942 let mut is_terminated = false;
1943 let mut prev: Option<char> = None;
1944
1945 while let Some(&ch) = chars.peek() {
1946 if prev == Some('$') {
1947 if ch == '$' {
1948 chars.next();
1949 is_terminated = true;
1950 break;
1951 } else {
1952 s.push('$');
1953 s.push(ch);
1954 }
1955 } else if ch != '$' {
1956 s.push(ch);
1957 }
1958
1959 prev = Some(ch);
1960 chars.next();
1961 }
1962
1963 return if chars.peek().is_none() && !is_terminated {
1964 self.tokenizer_error(chars.location(), "Unterminated dollar-quoted string")
1965 } else {
1966 Ok(Token::DollarQuotedString(DollarQuotedString {
1967 value: s,
1968 tag: None,
1969 }))
1970 };
1971 } else {
1972 value.push_str(&peeking_take_while(chars, |ch| {
1973 ch.is_alphanumeric()
1974 || ch == '_'
1975 || matches!(ch, '$' if self.dialect.supports_dollar_placeholder())
1977 }));
1978
1979 if matches!(chars.peek(), Some('.'))
1982 && self.dialect.supports_dollar_as_money_prefix()
1983 && !value.is_empty()
1984 && value.chars().all(|c| c.is_ascii_digit())
1985 {
1986 value.push('.');
1987 chars.next();
1988 value.push_str(&peeking_take_while(chars, |ch| ch.is_ascii_digit()));
1989 return Ok(Token::Placeholder(format!("${value}")));
1990 }
1991
1992 if matches!(chars.peek(), Some('$')) && !self.dialect.supports_dollar_placeholder() {
1994 chars.next();
1995
1996 let mut temp = String::new();
1997 let end_delimiter = format!("${value}$");
1998
1999 loop {
2000 match chars.next() {
2001 Some(ch) => {
2002 temp.push(ch);
2003
2004 if temp.ends_with(&end_delimiter) {
2005 if let Some(temp) = temp.strip_suffix(&end_delimiter) {
2006 s.push_str(temp);
2007 }
2008 break;
2009 }
2010 }
2011 None => {
2012 if temp.ends_with(&end_delimiter) {
2013 if let Some(temp) = temp.strip_suffix(&end_delimiter) {
2014 s.push_str(temp);
2015 }
2016 break;
2017 }
2018
2019 return self.tokenizer_error(
2020 chars.location(),
2021 "Unterminated dollar-quoted, expected $",
2022 );
2023 }
2024 }
2025 }
2026 } else {
2027 return Ok(Token::Placeholder(format!("${value}")));
2028 }
2029 }
2030
2031 Ok(Token::DollarQuotedString(DollarQuotedString {
2032 value: s,
2033 tag: if value.is_empty() { None } else { Some(value) },
2034 }))
2035 }
2036
2037 fn tokenizer_error<R>(
2038 &self,
2039 loc: Location,
2040 message: impl Into<String>,
2041 ) -> Result<R, TokenizerError> {
2042 Err(TokenizerError {
2043 message: message.into(),
2044 location: loc,
2045 })
2046 }
2047
2048 fn tokenize_number_part(
2049 &self,
2050 chars: &mut State,
2051 is_digit: impl Fn(char) -> bool,
2052 ) -> Result<String, TokenizerError> {
2053 let supports_separator = self.dialect.supports_numeric_literal_underscores();
2054 let mut s = String::new();
2055
2056 while let Some(&ch) = chars.peek() {
2057 if is_digit(ch) {
2058 chars.next();
2059 s.push(ch);
2060 } else if supports_separator && ch == '_' {
2061 let next_char = chars.peekable.clone().nth(1);
2062 if s.is_empty() || !next_char.is_some_and(&is_digit) {
2063 return self.tokenizer_error(chars.location(), "Unexpected character '_'");
2064 }
2065 chars.next();
2066 s.push(ch);
2067 } else {
2068 break;
2069 }
2070 }
2071
2072 Ok(s)
2073 }
2074
2075 fn tokenize_single_line_comment(&self, chars: &mut State) -> String {
2077 peeking_take_while(chars, |ch| match ch {
2078 '\n' => false, '\r' if dialect_of!(self is PostgreSqlDialect) => false, _ => true, })
2082 }
2083
2084 fn tokenize_word(&self, first_chars: impl Into<String>, chars: &mut State) -> String {
2086 let mut s = first_chars.into();
2087 s.push_str(&peeking_take_while(chars, |ch| {
2088 self.dialect.is_identifier_part(ch)
2089 }));
2090 s
2091 }
2092
2093 fn tokenize_quoted_identifier(
2095 &self,
2096 quote_start: char,
2097 chars: &mut State,
2098 ) -> Result<String, TokenizerError> {
2099 let error_loc = chars.location();
2100 chars.next(); let quote_end = Word::matching_end_quote(quote_start);
2102 let (s, last_char) = self.parse_quoted_ident(chars, quote_end);
2103
2104 if last_char == Some(quote_end) {
2105 Ok(s)
2106 } else {
2107 self.tokenizer_error(
2108 error_loc,
2109 format!("Expected close delimiter '{quote_end}' before EOF."),
2110 )
2111 }
2112 }
2113
2114 fn tokenize_escaped_single_quoted_string(
2116 &self,
2117 starting_loc: Location,
2118 chars: &mut State,
2119 ) -> Result<String, TokenizerError> {
2120 if let Some(s) = unescape_single_quoted_string(chars) {
2121 return Ok(s);
2122 }
2123
2124 self.tokenizer_error(starting_loc, "Unterminated encoded string literal")
2125 }
2126
2127 fn tokenize_single_or_triple_quoted_string<F>(
2130 &self,
2131 chars: &mut State,
2132 quote_style: char,
2133 backslash_escape: bool,
2134 single_quote_token: F,
2135 triple_quote_token: F,
2136 ) -> Result<Option<Token>, TokenizerError>
2137 where
2138 F: Fn(String) -> Token,
2139 {
2140 let error_loc = chars.location();
2141
2142 let mut num_opening_quotes = 0u8;
2143 for _ in 0..3 {
2144 if Some("e_style) == chars.peek() {
2145 chars.next(); num_opening_quotes += 1;
2147 } else {
2148 break;
2149 }
2150 }
2151
2152 let (token_fn, num_quote_chars) = match num_opening_quotes {
2153 1 => (single_quote_token, NumStringQuoteChars::One),
2154 2 => {
2155 return Ok(Some(single_quote_token("".into())));
2157 }
2158 3 => {
2159 let Some(num_quote_chars) = NonZeroU8::new(3) else {
2160 return self.tokenizer_error(error_loc, "invalid number of opening quotes");
2161 };
2162 (
2163 triple_quote_token,
2164 NumStringQuoteChars::Many(num_quote_chars),
2165 )
2166 }
2167 _ => {
2168 return self.tokenizer_error(error_loc, "invalid string literal opening");
2169 }
2170 };
2171
2172 let settings = TokenizeQuotedStringSettings {
2173 quote_style,
2174 num_quote_chars,
2175 num_opening_quotes_to_consume: 0,
2176 backslash_escape,
2177 };
2178
2179 self.tokenize_quoted_string(chars, settings)
2180 .map(token_fn)
2181 .map(Some)
2182 }
2183
2184 fn tokenize_single_quoted_string(
2186 &self,
2187 chars: &mut State,
2188 quote_style: char,
2189 backslash_escape: bool,
2190 ) -> Result<String, TokenizerError> {
2191 self.tokenize_quoted_string(
2192 chars,
2193 TokenizeQuotedStringSettings {
2194 quote_style,
2195 num_quote_chars: NumStringQuoteChars::One,
2196 num_opening_quotes_to_consume: 1,
2197 backslash_escape,
2198 },
2199 )
2200 }
2201
2202 fn tokenize_quote_delimited_string(
2206 &self,
2207 chars: &mut State,
2208 literal_prefix: &[char],
2211 ) -> Result<QuoteDelimitedString, TokenizerError> {
2212 let literal_start_loc = chars.location();
2213 chars.next();
2214
2215 let start_quote_loc = chars.location();
2216 let (start_quote, end_quote) = match chars.next() {
2217 None | Some(' ') | Some('\t') | Some('\r') | Some('\n') => {
2218 return self.tokenizer_error(
2219 start_quote_loc,
2220 format!(
2221 "Invalid space, tab, newline, or EOF after '{}''",
2222 String::from_iter(literal_prefix)
2223 ),
2224 );
2225 }
2226 Some(c) => (
2227 c,
2228 match c {
2229 '[' => ']',
2230 '{' => '}',
2231 '<' => '>',
2232 '(' => ')',
2233 c => c,
2234 },
2235 ),
2236 };
2237
2238 let mut value = String::new();
2240 while let Some(ch) = chars.next() {
2241 if ch == end_quote {
2242 if let Some('\'') = chars.peek() {
2243 chars.next(); return Ok(QuoteDelimitedString {
2245 start_quote,
2246 value,
2247 end_quote,
2248 });
2249 }
2250 }
2251 value.push(ch);
2252 }
2253
2254 self.tokenizer_error(literal_start_loc, "Unterminated string literal")
2255 }
2256
2257 fn tokenize_quoted_string(
2259 &self,
2260 chars: &mut State,
2261 settings: TokenizeQuotedStringSettings,
2262 ) -> Result<String, TokenizerError> {
2263 let mut s = String::new();
2264 let error_loc = chars.location();
2265
2266 for _ in 0..settings.num_opening_quotes_to_consume {
2268 if Some(settings.quote_style) != chars.next() {
2269 return self.tokenizer_error(error_loc, "invalid string literal opening");
2270 }
2271 }
2272
2273 let mut num_consecutive_quotes = 0;
2274 while let Some(&ch) = chars.peek() {
2275 let pending_final_quote = match settings.num_quote_chars {
2276 NumStringQuoteChars::One => Some(NumStringQuoteChars::One),
2277 n @ NumStringQuoteChars::Many(count)
2278 if num_consecutive_quotes + 1 == count.get() =>
2279 {
2280 Some(n)
2281 }
2282 NumStringQuoteChars::Many(_) => None,
2283 };
2284
2285 match ch {
2286 char if char == settings.quote_style && pending_final_quote.is_some() => {
2287 chars.next(); if let Some(NumStringQuoteChars::Many(count)) = pending_final_quote {
2290 let mut buf = s.chars();
2295 for _ in 1..count.get() {
2296 buf.next_back();
2297 }
2298 return Ok(buf.as_str().to_string());
2299 } else if chars
2300 .peek()
2301 .map(|c| *c == settings.quote_style)
2302 .unwrap_or(false)
2303 {
2304 s.push(ch);
2305 if !self.unescape {
2306 s.push(ch);
2308 }
2309 chars.next();
2310 } else {
2311 return Ok(s);
2312 }
2313 }
2314 '\\' if settings.backslash_escape => {
2315 chars.next();
2317
2318 num_consecutive_quotes = 0;
2319
2320 if let Some(next) = chars.peek() {
2321 if !self.unescape
2322 || (self.dialect.ignores_wildcard_escapes()
2323 && (*next == '%' || *next == '_'))
2324 {
2325 s.push(ch);
2329 s.push(*next);
2330 chars.next(); } else {
2332 let n = match next {
2333 '0' => '\0',
2334 'a' => '\u{7}',
2335 'b' => '\u{8}',
2336 'f' => '\u{c}',
2337 'n' => '\n',
2338 'r' => '\r',
2339 't' => '\t',
2340 'Z' => '\u{1a}',
2341 _ => *next,
2342 };
2343 s.push(n);
2344 chars.next(); }
2346 }
2347 }
2348 ch => {
2349 chars.next(); if ch == settings.quote_style {
2352 num_consecutive_quotes += 1;
2353 } else {
2354 num_consecutive_quotes = 0;
2355 }
2356
2357 s.push(ch);
2358 }
2359 }
2360 }
2361 self.tokenizer_error(error_loc, "Unterminated string literal")
2362 }
2363
2364 fn tokenize_multiline_comment(
2365 &self,
2366 chars: &mut State,
2367 ) -> Result<Option<Token>, TokenizerError> {
2368 let mut s = String::new();
2369 let mut nested = 1;
2370 let supports_nested_comments = self.dialect.supports_nested_comments();
2371 loop {
2372 match chars.next() {
2373 Some('/') if matches!(chars.peek(), Some('*')) && supports_nested_comments => {
2374 chars.next(); s.push('/');
2376 s.push('*');
2377 nested += 1;
2378 }
2379 Some('*') if matches!(chars.peek(), Some('/')) => {
2380 chars.next(); nested -= 1;
2382 if nested == 0 {
2383 break Ok(Some(Token::Whitespace(Whitespace::MultiLineComment(s))));
2384 }
2385 s.push('*');
2386 s.push('/');
2387 }
2388 Some(ch) => {
2389 s.push(ch);
2390 }
2391 None => {
2392 break self.tokenizer_error(
2393 chars.location(),
2394 "Unexpected EOF while in a multi-line comment",
2395 );
2396 }
2397 }
2398 }
2399 }
2400
2401 fn parse_quoted_ident(&self, chars: &mut State, quote_end: char) -> (String, Option<char>) {
2402 let mut last_char = None;
2403 let mut s = String::new();
2404 while let Some(ch) = chars.next() {
2405 if ch == quote_end {
2406 if chars.peek() == Some("e_end) {
2407 chars.next();
2408 s.push(ch);
2409 if !self.unescape {
2410 s.push(ch);
2412 }
2413 } else {
2414 last_char = Some(quote_end);
2415 break;
2416 }
2417 } else {
2418 s.push(ch);
2419 }
2420 }
2421 (s, last_char)
2422 }
2423
2424 #[allow(clippy::unnecessary_wraps)]
2425 fn consume_and_return(
2426 &self,
2427 chars: &mut State,
2428 t: Token,
2429 ) -> Result<Option<Token>, TokenizerError> {
2430 chars.next();
2431 Ok(Some(t))
2432 }
2433}
2434
2435fn peeking_take_while(chars: &mut State, mut predicate: impl FnMut(char) -> bool) -> String {
2439 let mut s = String::new();
2440 while let Some(&ch) = chars.peek() {
2441 if predicate(ch) {
2442 chars.next(); s.push(ch);
2444 } else {
2445 break;
2446 }
2447 }
2448 s
2449}
2450
2451fn unescape_single_quoted_string(chars: &mut State<'_>) -> Option<String> {
2452 Unescape::new(chars).unescape()
2453}
2454
2455struct Unescape<'a: 'b, 'b> {
2456 chars: &'b mut State<'a>,
2457}
2458
2459impl<'a: 'b, 'b> Unescape<'a, 'b> {
2460 fn new(chars: &'b mut State<'a>) -> Self {
2461 Self { chars }
2462 }
2463 fn unescape(mut self) -> Option<String> {
2464 let mut unescaped = String::new();
2465
2466 self.chars.next();
2467
2468 while let Some(c) = self.chars.next() {
2469 if c == '\'' {
2470 if self.chars.peek().map(|c| *c == '\'').unwrap_or(false) {
2472 self.chars.next();
2473 unescaped.push('\'');
2474 continue;
2475 }
2476 return Some(unescaped);
2477 }
2478
2479 if c != '\\' {
2480 unescaped.push(c);
2481 continue;
2482 }
2483
2484 let c = match self.chars.next()? {
2485 'b' => '\u{0008}',
2486 'f' => '\u{000C}',
2487 'n' => '\n',
2488 'r' => '\r',
2489 't' => '\t',
2490 'u' => self.unescape_unicode_16()?,
2491 'U' => self.unescape_unicode_32()?,
2492 'x' => self.unescape_hex()?,
2493 c if c.is_digit(8) => self.unescape_octal(c)?,
2494 c => c,
2495 };
2496
2497 unescaped.push(Self::check_null(c)?);
2498 }
2499
2500 None
2501 }
2502
2503 #[inline]
2504 fn check_null(c: char) -> Option<char> {
2505 if c == '\0' {
2506 None
2507 } else {
2508 Some(c)
2509 }
2510 }
2511
2512 #[inline]
2513 fn byte_to_char<const RADIX: u32>(s: &str) -> Option<char> {
2514 match u32::from_str_radix(s, RADIX) {
2516 Err(_) => None,
2517 Ok(n) => {
2518 let n = n & 0xFF;
2519 if n <= 127 {
2520 char::from_u32(n)
2521 } else {
2522 None
2523 }
2524 }
2525 }
2526 }
2527
2528 fn unescape_hex(&mut self) -> Option<char> {
2530 let mut s = String::new();
2531
2532 for _ in 0..2 {
2533 match self.next_hex_digit() {
2534 Some(c) => s.push(c),
2535 None => break,
2536 }
2537 }
2538
2539 if s.is_empty() {
2540 return Some('x');
2541 }
2542
2543 Self::byte_to_char::<16>(&s)
2544 }
2545
2546 #[inline]
2547 fn next_hex_digit(&mut self) -> Option<char> {
2548 match self.chars.peek() {
2549 Some(c) if c.is_ascii_hexdigit() => self.chars.next(),
2550 _ => None,
2551 }
2552 }
2553
2554 fn unescape_octal(&mut self, c: char) -> Option<char> {
2556 let mut s = String::new();
2557
2558 s.push(c);
2559 for _ in 0..2 {
2560 match self.next_octal_digest() {
2561 Some(c) => s.push(c),
2562 None => break,
2563 }
2564 }
2565
2566 Self::byte_to_char::<8>(&s)
2567 }
2568
2569 #[inline]
2570 fn next_octal_digest(&mut self) -> Option<char> {
2571 match self.chars.peek() {
2572 Some(c) if c.is_digit(8) => self.chars.next(),
2573 _ => None,
2574 }
2575 }
2576
2577 fn unescape_unicode_16(&mut self) -> Option<char> {
2579 self.unescape_unicode::<4>()
2580 }
2581
2582 fn unescape_unicode_32(&mut self) -> Option<char> {
2584 self.unescape_unicode::<8>()
2585 }
2586
2587 fn unescape_unicode<const NUM: usize>(&mut self) -> Option<char> {
2588 let mut s = String::new();
2589 for _ in 0..NUM {
2590 s.push(self.chars.next()?);
2591 }
2592 match u32::from_str_radix(&s, 16) {
2593 Err(_) => None,
2594 Ok(n) => char::from_u32(n),
2595 }
2596 }
2597}
2598
2599fn unescape_unicode_single_quoted_string(chars: &mut State<'_>) -> Result<String, TokenizerError> {
2600 let mut unescaped = String::new();
2601 chars.next(); while let Some(c) = chars.next() {
2603 match c {
2604 '\'' => {
2605 if chars.peek() == Some(&'\'') {
2606 chars.next();
2607 unescaped.push('\'');
2608 } else {
2609 return Ok(unescaped);
2610 }
2611 }
2612 '\\' => match chars.peek() {
2613 Some('\\') => {
2614 chars.next();
2615 unescaped.push('\\');
2616 }
2617 Some('+') => {
2618 chars.next();
2619 unescaped.push(take_char_from_hex_digits(chars, 6)?);
2620 }
2621 _ => unescaped.push(take_char_from_hex_digits(chars, 4)?),
2622 },
2623 _ => {
2624 unescaped.push(c);
2625 }
2626 }
2627 }
2628 Err(TokenizerError {
2629 message: "Unterminated unicode encoded string literal".to_string(),
2630 location: chars.location(),
2631 })
2632}
2633
2634fn take_char_from_hex_digits(
2635 chars: &mut State<'_>,
2636 max_digits: usize,
2637) -> Result<char, TokenizerError> {
2638 let mut result = 0u32;
2639 for _ in 0..max_digits {
2640 let next_char = chars.next().ok_or_else(|| TokenizerError {
2641 message: "Unexpected EOF while parsing hex digit in escaped unicode string."
2642 .to_string(),
2643 location: chars.location(),
2644 })?;
2645 let digit = next_char.to_digit(16).ok_or_else(|| TokenizerError {
2646 message: format!("Invalid hex digit in escaped unicode string: {next_char}"),
2647 location: chars.location(),
2648 })?;
2649 result = result * 16 + digit;
2650 }
2651 char::from_u32(result).ok_or_else(|| TokenizerError {
2652 message: format!("Invalid unicode character: {result:x}"),
2653 location: chars.location(),
2654 })
2655}
2656
2657#[cfg(test)]
2658mod tests {
2659 use super::*;
2660 use crate::dialect::{
2661 BigQueryDialect, ClickHouseDialect, HiveDialect, MsSqlDialect, MySqlDialect,
2662 PostgreSqlDialect, SQLiteDialect,
2663 };
2664 use crate::test_utils::{all_dialects, all_dialects_except, all_dialects_where};
2665 use core::fmt::Debug;
2666
2667 #[test]
2668 fn tokenizer_error_impl() {
2669 let err = TokenizerError {
2670 message: "test".into(),
2671 location: Location { line: 1, column: 1 },
2672 };
2673 {
2674 use core::error::Error;
2675 assert!(err.source().is_none());
2676 }
2677 assert_eq!(err.to_string(), "test at Line: 1, Column: 1");
2678 }
2679
2680 #[test]
2681 fn tokenize_select_1() {
2682 let sql = String::from("SELECT 1");
2683 let dialect = GenericDialect {};
2684 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2685
2686 let expected = vec![
2687 Token::make_keyword("SELECT"),
2688 Token::Whitespace(Whitespace::Space),
2689 Token::Number(String::from("1"), false),
2690 ];
2691
2692 compare(expected, tokens);
2693 }
2694
2695 #[test]
2696 fn tokenize_select_float() {
2697 let sql = String::from("SELECT .1");
2698 let dialect = GenericDialect {};
2699 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2700
2701 let expected = vec![
2702 Token::make_keyword("SELECT"),
2703 Token::Whitespace(Whitespace::Space),
2704 Token::Number(String::from(".1"), false),
2705 ];
2706
2707 compare(expected, tokens);
2708 }
2709
2710 #[test]
2711 fn tokenize_with_mapper() {
2712 let sql = String::from("SELECT ?");
2713 let dialect = GenericDialect {};
2714 let mut param_num = 1;
2715
2716 let mut tokens = vec![];
2717 Tokenizer::new(&dialect, &sql)
2718 .tokenize_with_location_into_buf_with_mapper(&mut tokens, |mut token_span| {
2719 token_span.token = match token_span.token {
2720 Token::Placeholder(n) => Token::Placeholder(if n == "?" {
2721 let ret = format!("${}", param_num);
2722 param_num += 1;
2723 ret
2724 } else {
2725 n
2726 }),
2727 token => token,
2728 };
2729 token_span
2730 })
2731 .unwrap();
2732 let actual = tokens.into_iter().map(|t| t.token).collect();
2733 let expected = vec![
2734 Token::make_keyword("SELECT"),
2735 Token::Whitespace(Whitespace::Space),
2736 Token::Placeholder("$1".to_string()),
2737 ];
2738
2739 compare(expected, actual);
2740 }
2741
2742 #[test]
2743 fn tokenize_clickhouse_double_equal() {
2744 let sql = String::from("SELECT foo=='1'");
2745 let dialect = ClickHouseDialect {};
2746 let mut tokenizer = Tokenizer::new(&dialect, &sql);
2747 let tokens = tokenizer.tokenize().unwrap();
2748
2749 let expected = vec![
2750 Token::make_keyword("SELECT"),
2751 Token::Whitespace(Whitespace::Space),
2752 Token::Word(Word {
2753 value: "foo".to_string(),
2754 quote_style: None,
2755 keyword: Keyword::NoKeyword,
2756 }),
2757 Token::DoubleEq,
2758 Token::SingleQuotedString("1".to_string()),
2759 ];
2760
2761 compare(expected, tokens);
2762 }
2763
2764 #[test]
2765 fn tokenize_numeric_literal_underscore() {
2766 let dialect = GenericDialect {};
2767 let sql = String::from("SELECT 10_000");
2768 let mut tokenizer = Tokenizer::new(&dialect, &sql);
2769 let tokens = tokenizer.tokenize().unwrap();
2770 let expected = vec![
2771 Token::make_keyword("SELECT"),
2772 Token::Whitespace(Whitespace::Space),
2773 Token::Number("10".to_string(), false),
2774 Token::make_word("_000", None),
2775 ];
2776 compare(expected, tokens);
2777
2778 let numeric_underscore_dialects =
2779 all_dialects_where(|dialect| dialect.supports_numeric_literal_underscores());
2780
2781 numeric_underscore_dialects.tokenizes_to(
2782 "SELECT 10_000, _10_000, 1_000.123, 1_000.123_456, 1e1_0",
2783 vec![
2784 Token::make_keyword("SELECT"),
2785 Token::Whitespace(Whitespace::Space),
2786 Token::Number("10_000".to_string(), false),
2787 Token::Comma,
2788 Token::Whitespace(Whitespace::Space),
2789 Token::make_word("_10_000", None), Token::Comma,
2791 Token::Whitespace(Whitespace::Space),
2792 Token::Number("1_000.123".to_string(), false), Token::Comma,
2794 Token::Whitespace(Whitespace::Space),
2795 Token::Number("1_000.123_456".to_string(), false), Token::Comma,
2797 Token::Whitespace(Whitespace::Space),
2798 Token::Number("1e1_0".to_string(), false), ],
2800 );
2801
2802 numeric_underscore_dialects.tokenizes_to(
2803 "0xFF_FF",
2804 vec![Token::HexStringLiteral("FF_FF".to_string())],
2805 );
2806
2807 for dialect in &numeric_underscore_dialects.dialects {
2808 for sql in [
2809 "SELECT 10_00_",
2810 "SELECT 10___0",
2811 "SELECT 1_000.123_",
2812 "SELECT 1._000",
2813 "SELECT 1_a",
2814 "SELECT 1e_1",
2815 "SELECT 1e1_",
2816 "SELECT 1e1__0",
2817 "SELECT 0x_1",
2818 "SELECT 0x1_",
2819 ] {
2820 let err = Tokenizer::new(&**dialect, sql).tokenize().unwrap_err();
2821 assert_eq!("Unexpected character '_'", err.message);
2822 }
2823 }
2824 }
2825
2826 #[test]
2827 fn tokenize_select_exponent() {
2828 let sql = String::from("SELECT 1e10, 1e-10, 1e+10, 1ea, 1e-10a, 1e-10-10");
2829 let dialect = GenericDialect {};
2830 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2831
2832 let expected = vec![
2833 Token::make_keyword("SELECT"),
2834 Token::Whitespace(Whitespace::Space),
2835 Token::Number(String::from("1e10"), false),
2836 Token::Comma,
2837 Token::Whitespace(Whitespace::Space),
2838 Token::Number(String::from("1e-10"), false),
2839 Token::Comma,
2840 Token::Whitespace(Whitespace::Space),
2841 Token::Number(String::from("1e+10"), false),
2842 Token::Comma,
2843 Token::Whitespace(Whitespace::Space),
2844 Token::Number(String::from("1"), false),
2845 Token::make_word("ea", None),
2846 Token::Comma,
2847 Token::Whitespace(Whitespace::Space),
2848 Token::Number(String::from("1e-10"), false),
2849 Token::make_word("a", None),
2850 Token::Comma,
2851 Token::Whitespace(Whitespace::Space),
2852 Token::Number(String::from("1e-10"), false),
2853 Token::Minus,
2854 Token::Number(String::from("10"), false),
2855 ];
2856
2857 compare(expected, tokens);
2858 }
2859
2860 #[test]
2861 fn tokenize_scalar_function() {
2862 let sql = String::from("SELECT sqrt(1)");
2863 let dialect = GenericDialect {};
2864 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2865
2866 let expected = vec![
2867 Token::make_keyword("SELECT"),
2868 Token::Whitespace(Whitespace::Space),
2869 Token::make_word("sqrt", None),
2870 Token::LParen,
2871 Token::Number(String::from("1"), false),
2872 Token::RParen,
2873 ];
2874
2875 compare(expected, tokens);
2876 }
2877
2878 #[test]
2879 fn tokenize_string_string_concat() {
2880 let sql = String::from("SELECT 'a' || 'b'");
2881 let dialect = GenericDialect {};
2882 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2883
2884 let expected = vec![
2885 Token::make_keyword("SELECT"),
2886 Token::Whitespace(Whitespace::Space),
2887 Token::SingleQuotedString(String::from("a")),
2888 Token::Whitespace(Whitespace::Space),
2889 Token::StringConcat,
2890 Token::Whitespace(Whitespace::Space),
2891 Token::SingleQuotedString(String::from("b")),
2892 ];
2893
2894 compare(expected, tokens);
2895 }
2896 #[test]
2897 fn tokenize_bitwise_op() {
2898 let sql = String::from("SELECT one | two ^ three");
2899 let dialect = GenericDialect {};
2900 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2901
2902 let expected = vec![
2903 Token::make_keyword("SELECT"),
2904 Token::Whitespace(Whitespace::Space),
2905 Token::make_word("one", None),
2906 Token::Whitespace(Whitespace::Space),
2907 Token::Pipe,
2908 Token::Whitespace(Whitespace::Space),
2909 Token::make_word("two", None),
2910 Token::Whitespace(Whitespace::Space),
2911 Token::Caret,
2912 Token::Whitespace(Whitespace::Space),
2913 Token::make_word("three", None),
2914 ];
2915 compare(expected, tokens);
2916 }
2917
2918 #[test]
2919 fn tokenize_logical_xor() {
2920 let sql =
2921 String::from("SELECT true XOR true, false XOR false, true XOR false, false XOR true");
2922 let dialect = GenericDialect {};
2923 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2924
2925 let expected = vec![
2926 Token::make_keyword("SELECT"),
2927 Token::Whitespace(Whitespace::Space),
2928 Token::make_keyword("true"),
2929 Token::Whitespace(Whitespace::Space),
2930 Token::make_keyword("XOR"),
2931 Token::Whitespace(Whitespace::Space),
2932 Token::make_keyword("true"),
2933 Token::Comma,
2934 Token::Whitespace(Whitespace::Space),
2935 Token::make_keyword("false"),
2936 Token::Whitespace(Whitespace::Space),
2937 Token::make_keyword("XOR"),
2938 Token::Whitespace(Whitespace::Space),
2939 Token::make_keyword("false"),
2940 Token::Comma,
2941 Token::Whitespace(Whitespace::Space),
2942 Token::make_keyword("true"),
2943 Token::Whitespace(Whitespace::Space),
2944 Token::make_keyword("XOR"),
2945 Token::Whitespace(Whitespace::Space),
2946 Token::make_keyword("false"),
2947 Token::Comma,
2948 Token::Whitespace(Whitespace::Space),
2949 Token::make_keyword("false"),
2950 Token::Whitespace(Whitespace::Space),
2951 Token::make_keyword("XOR"),
2952 Token::Whitespace(Whitespace::Space),
2953 Token::make_keyword("true"),
2954 ];
2955 compare(expected, tokens);
2956 }
2957
2958 #[test]
2959 fn tokenize_simple_select() {
2960 let sql = String::from("SELECT * FROM customer WHERE id = 1 LIMIT 5");
2961 let dialect = GenericDialect {};
2962 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2963
2964 let expected = vec![
2965 Token::make_keyword("SELECT"),
2966 Token::Whitespace(Whitespace::Space),
2967 Token::Mul,
2968 Token::Whitespace(Whitespace::Space),
2969 Token::make_keyword("FROM"),
2970 Token::Whitespace(Whitespace::Space),
2971 Token::make_word("customer", None),
2972 Token::Whitespace(Whitespace::Space),
2973 Token::make_keyword("WHERE"),
2974 Token::Whitespace(Whitespace::Space),
2975 Token::make_word("id", None),
2976 Token::Whitespace(Whitespace::Space),
2977 Token::Eq,
2978 Token::Whitespace(Whitespace::Space),
2979 Token::Number(String::from("1"), false),
2980 Token::Whitespace(Whitespace::Space),
2981 Token::make_keyword("LIMIT"),
2982 Token::Whitespace(Whitespace::Space),
2983 Token::Number(String::from("5"), false),
2984 ];
2985
2986 compare(expected, tokens);
2987 }
2988
2989 #[test]
2990 fn tokenize_explain_select() {
2991 let sql = String::from("EXPLAIN SELECT * FROM customer WHERE id = 1");
2992 let dialect = GenericDialect {};
2993 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2994
2995 let expected = vec![
2996 Token::make_keyword("EXPLAIN"),
2997 Token::Whitespace(Whitespace::Space),
2998 Token::make_keyword("SELECT"),
2999 Token::Whitespace(Whitespace::Space),
3000 Token::Mul,
3001 Token::Whitespace(Whitespace::Space),
3002 Token::make_keyword("FROM"),
3003 Token::Whitespace(Whitespace::Space),
3004 Token::make_word("customer", None),
3005 Token::Whitespace(Whitespace::Space),
3006 Token::make_keyword("WHERE"),
3007 Token::Whitespace(Whitespace::Space),
3008 Token::make_word("id", None),
3009 Token::Whitespace(Whitespace::Space),
3010 Token::Eq,
3011 Token::Whitespace(Whitespace::Space),
3012 Token::Number(String::from("1"), false),
3013 ];
3014
3015 compare(expected, tokens);
3016 }
3017
3018 #[test]
3019 fn tokenize_explain_analyze_select() {
3020 let sql = String::from("EXPLAIN ANALYZE SELECT * FROM customer WHERE id = 1");
3021 let dialect = GenericDialect {};
3022 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3023
3024 let expected = vec![
3025 Token::make_keyword("EXPLAIN"),
3026 Token::Whitespace(Whitespace::Space),
3027 Token::make_keyword("ANALYZE"),
3028 Token::Whitespace(Whitespace::Space),
3029 Token::make_keyword("SELECT"),
3030 Token::Whitespace(Whitespace::Space),
3031 Token::Mul,
3032 Token::Whitespace(Whitespace::Space),
3033 Token::make_keyword("FROM"),
3034 Token::Whitespace(Whitespace::Space),
3035 Token::make_word("customer", None),
3036 Token::Whitespace(Whitespace::Space),
3037 Token::make_keyword("WHERE"),
3038 Token::Whitespace(Whitespace::Space),
3039 Token::make_word("id", None),
3040 Token::Whitespace(Whitespace::Space),
3041 Token::Eq,
3042 Token::Whitespace(Whitespace::Space),
3043 Token::Number(String::from("1"), false),
3044 ];
3045
3046 compare(expected, tokens);
3047 }
3048
3049 #[test]
3050 fn tokenize_string_predicate() {
3051 let sql = String::from("SELECT * FROM customer WHERE salary != 'Not Provided'");
3052 let dialect = GenericDialect {};
3053 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3054
3055 let expected = vec![
3056 Token::make_keyword("SELECT"),
3057 Token::Whitespace(Whitespace::Space),
3058 Token::Mul,
3059 Token::Whitespace(Whitespace::Space),
3060 Token::make_keyword("FROM"),
3061 Token::Whitespace(Whitespace::Space),
3062 Token::make_word("customer", None),
3063 Token::Whitespace(Whitespace::Space),
3064 Token::make_keyword("WHERE"),
3065 Token::Whitespace(Whitespace::Space),
3066 Token::make_word("salary", None),
3067 Token::Whitespace(Whitespace::Space),
3068 Token::Neq,
3069 Token::Whitespace(Whitespace::Space),
3070 Token::SingleQuotedString(String::from("Not Provided")),
3071 ];
3072
3073 compare(expected, tokens);
3074 }
3075
3076 #[test]
3077 fn tokenize_invalid_string() {
3078 let sql = String::from("\n💝مصطفىh");
3079
3080 let dialect = GenericDialect {};
3081 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3082 let expected = vec![
3084 Token::Whitespace(Whitespace::Newline),
3085 Token::Char('💝'),
3086 Token::make_word("مصطفىh", None),
3087 ];
3088 compare(expected, tokens);
3089 }
3090
3091 #[test]
3092 fn tokenize_newline_in_string_literal() {
3093 let sql = String::from("'foo\r\nbar\nbaz'");
3094
3095 let dialect = GenericDialect {};
3096 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3097 let expected = vec![Token::SingleQuotedString("foo\r\nbar\nbaz".to_string())];
3098 compare(expected, tokens);
3099 }
3100
3101 #[test]
3102 fn tokenize_unterminated_string_literal() {
3103 let sql = String::from("select 'foo");
3104
3105 let dialect = GenericDialect {};
3106 let mut tokenizer = Tokenizer::new(&dialect, &sql);
3107 assert_eq!(
3108 tokenizer.tokenize(),
3109 Err(TokenizerError {
3110 message: "Unterminated string literal".to_string(),
3111 location: Location { line: 1, column: 8 },
3112 })
3113 );
3114 }
3115
3116 #[test]
3117 fn tokenize_unterminated_string_literal_utf8() {
3118 let sql = String::from("SELECT \"なにか\" FROM Y WHERE \"なにか\" = 'test;");
3119
3120 let dialect = GenericDialect {};
3121 let mut tokenizer = Tokenizer::new(&dialect, &sql);
3122 assert_eq!(
3123 tokenizer.tokenize(),
3124 Err(TokenizerError {
3125 message: "Unterminated string literal".to_string(),
3126 location: Location {
3127 line: 1,
3128 column: 35
3129 }
3130 })
3131 );
3132 }
3133
3134 #[test]
3135 fn tokenize_invalid_string_cols() {
3136 let sql = String::from("\n\nSELECT * FROM table\t💝مصطفىh");
3137
3138 let dialect = GenericDialect {};
3139 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3140 let expected = vec![
3142 Token::Whitespace(Whitespace::Newline),
3143 Token::Whitespace(Whitespace::Newline),
3144 Token::make_keyword("SELECT"),
3145 Token::Whitespace(Whitespace::Space),
3146 Token::Mul,
3147 Token::Whitespace(Whitespace::Space),
3148 Token::make_keyword("FROM"),
3149 Token::Whitespace(Whitespace::Space),
3150 Token::make_keyword("table"),
3151 Token::Whitespace(Whitespace::Tab),
3152 Token::Char('💝'),
3153 Token::make_word("مصطفىh", None),
3154 ];
3155 compare(expected, tokens);
3156 }
3157
3158 #[test]
3159 fn tokenize_dollar_quoted_string_tagged() {
3160 let test_cases = vec![
3161 (
3162 String::from("SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$tag$"),
3163 vec![
3164 Token::make_keyword("SELECT"),
3165 Token::Whitespace(Whitespace::Space),
3166 Token::DollarQuotedString(DollarQuotedString {
3167 value: "dollar '$' quoted strings have $tags like this$ or like this $$".into(),
3168 tag: Some("tag".into()),
3169 })
3170 ]
3171 ),
3172 (
3173 String::from("SELECT $abc$x$ab$abc$"),
3174 vec![
3175 Token::make_keyword("SELECT"),
3176 Token::Whitespace(Whitespace::Space),
3177 Token::DollarQuotedString(DollarQuotedString {
3178 value: "x$ab".into(),
3179 tag: Some("abc".into()),
3180 })
3181 ]
3182 ),
3183 (
3184 String::from("SELECT $abc$$abc$"),
3185 vec![
3186 Token::make_keyword("SELECT"),
3187 Token::Whitespace(Whitespace::Space),
3188 Token::DollarQuotedString(DollarQuotedString {
3189 value: "".into(),
3190 tag: Some("abc".into()),
3191 })
3192 ]
3193 ),
3194 (
3195 String::from("0$abc$$abc$1"),
3196 vec![
3197 Token::Number("0".into(), false),
3198 Token::DollarQuotedString(DollarQuotedString {
3199 value: "".into(),
3200 tag: Some("abc".into()),
3201 }),
3202 Token::Number("1".into(), false),
3203 ]
3204 ),
3205 (
3206 String::from("$function$abc$q$data$q$$function$"),
3207 vec![
3208 Token::DollarQuotedString(DollarQuotedString {
3209 value: "abc$q$data$q$".into(),
3210 tag: Some("function".into()),
3211 }),
3212 ]
3213 ),
3214 ];
3215
3216 let dialect = GenericDialect {};
3217 for (sql, expected) in test_cases {
3218 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3219 compare(expected, tokens);
3220 }
3221 }
3222
3223 #[test]
3224 fn tokenize_dollar_quoted_string_tagged_unterminated() {
3225 let sql = String::from("SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$different tag$");
3226 let dialect = GenericDialect {};
3227 assert_eq!(
3228 Tokenizer::new(&dialect, &sql).tokenize(),
3229 Err(TokenizerError {
3230 message: "Unterminated dollar-quoted, expected $".into(),
3231 location: Location {
3232 line: 1,
3233 column: 91
3234 }
3235 })
3236 );
3237 }
3238
3239 #[test]
3240 fn tokenize_dollar_quoted_string_tagged_unterminated_mirror() {
3241 let sql = String::from("SELECT $abc$abc$");
3242 let dialect = GenericDialect {};
3243 assert_eq!(
3244 Tokenizer::new(&dialect, &sql).tokenize(),
3245 Err(TokenizerError {
3246 message: "Unterminated dollar-quoted, expected $".into(),
3247 location: Location {
3248 line: 1,
3249 column: 17
3250 }
3251 })
3252 );
3253 }
3254
3255 #[test]
3256 fn tokenize_dollar_placeholder() {
3257 let sql = String::from("SELECT $$, $$ABC$$, $ABC$, $ABC");
3258 let dialect = SQLiteDialect {};
3259 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3260 assert_eq!(
3261 tokens,
3262 vec![
3263 Token::make_keyword("SELECT"),
3264 Token::Whitespace(Whitespace::Space),
3265 Token::Placeholder("$$".into()),
3266 Token::Comma,
3267 Token::Whitespace(Whitespace::Space),
3268 Token::Placeholder("$$ABC$$".into()),
3269 Token::Comma,
3270 Token::Whitespace(Whitespace::Space),
3271 Token::Placeholder("$ABC$".into()),
3272 Token::Comma,
3273 Token::Whitespace(Whitespace::Space),
3274 Token::Placeholder("$ABC".into()),
3275 ]
3276 );
3277 }
3278
3279 #[test]
3280 fn tokenize_nested_dollar_quoted_strings() {
3281 let sql = String::from("SELECT $tag$dollar $nested$ string$tag$");
3282 let dialect = GenericDialect {};
3283 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3284 let expected = vec![
3285 Token::make_keyword("SELECT"),
3286 Token::Whitespace(Whitespace::Space),
3287 Token::DollarQuotedString(DollarQuotedString {
3288 value: "dollar $nested$ string".into(),
3289 tag: Some("tag".into()),
3290 }),
3291 ];
3292 compare(expected, tokens);
3293 }
3294
3295 #[test]
3296 fn tokenize_dollar_quoted_string_untagged_empty() {
3297 let sql = String::from("SELECT $$$$");
3298 let dialect = GenericDialect {};
3299 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3300 let expected = vec![
3301 Token::make_keyword("SELECT"),
3302 Token::Whitespace(Whitespace::Space),
3303 Token::DollarQuotedString(DollarQuotedString {
3304 value: "".into(),
3305 tag: None,
3306 }),
3307 ];
3308 compare(expected, tokens);
3309 }
3310
3311 #[test]
3312 fn tokenize_dollar_quoted_string_untagged() {
3313 let sql =
3314 String::from("SELECT $$within dollar '$' quoted strings have $tags like this$ $$");
3315 let dialect = GenericDialect {};
3316 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3317 let expected = vec![
3318 Token::make_keyword("SELECT"),
3319 Token::Whitespace(Whitespace::Space),
3320 Token::DollarQuotedString(DollarQuotedString {
3321 value: "within dollar '$' quoted strings have $tags like this$ ".into(),
3322 tag: None,
3323 }),
3324 ];
3325 compare(expected, tokens);
3326 }
3327
3328 #[test]
3329 fn tokenize_dollar_quoted_string_untagged_unterminated() {
3330 let sql = String::from(
3331 "SELECT $$dollar '$' quoted strings have $tags like this$ or like this $different tag$",
3332 );
3333 let dialect = GenericDialect {};
3334 assert_eq!(
3335 Tokenizer::new(&dialect, &sql).tokenize(),
3336 Err(TokenizerError {
3337 message: "Unterminated dollar-quoted string".into(),
3338 location: Location {
3339 line: 1,
3340 column: 86
3341 }
3342 })
3343 );
3344 }
3345
3346 #[test]
3347 fn tokenize_right_arrow() {
3348 let sql = String::from("FUNCTION(key=>value)");
3349 let dialect = GenericDialect {};
3350 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3351 let expected = vec![
3352 Token::make_word("FUNCTION", None),
3353 Token::LParen,
3354 Token::make_word("key", None),
3355 Token::RArrow,
3356 Token::make_word("value", None),
3357 Token::RParen,
3358 ];
3359 compare(expected, tokens);
3360 }
3361
3362 #[test]
3363 fn tokenize_is_null() {
3364 let sql = String::from("a IS NULL");
3365 let dialect = GenericDialect {};
3366 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3367
3368 let expected = vec![
3369 Token::make_word("a", None),
3370 Token::Whitespace(Whitespace::Space),
3371 Token::make_keyword("IS"),
3372 Token::Whitespace(Whitespace::Space),
3373 Token::make_keyword("NULL"),
3374 ];
3375
3376 compare(expected, tokens);
3377 }
3378
3379 #[test]
3380 fn tokenize_comment() {
3381 let test_cases = vec![
3382 (
3383 String::from("0--this is a comment\n1"),
3384 vec![
3385 Token::Number("0".to_string(), false),
3386 Token::Whitespace(Whitespace::SingleLineComment {
3387 prefix: "--".to_string(),
3388 comment: "this is a comment".to_string(),
3389 }),
3390 Token::Whitespace(Whitespace::Newline),
3391 Token::Number("1".to_string(), false),
3392 ],
3393 ),
3394 (
3395 String::from("0--this is a comment\r1"),
3396 vec![
3397 Token::Number("0".to_string(), false),
3398 Token::Whitespace(Whitespace::SingleLineComment {
3399 prefix: "--".to_string(),
3400 comment: "this is a comment\r1".to_string(),
3401 }),
3402 ],
3403 ),
3404 (
3405 String::from("0--this is a comment\r\n1"),
3406 vec![
3407 Token::Number("0".to_string(), false),
3408 Token::Whitespace(Whitespace::SingleLineComment {
3409 prefix: "--".to_string(),
3410 comment: "this is a comment\r".to_string(),
3411 }),
3412 Token::Whitespace(Whitespace::Newline),
3413 Token::Number("1".to_string(), false),
3414 ],
3415 ),
3416 ];
3417
3418 let dialect = GenericDialect {};
3419
3420 for (sql, expected) in test_cases {
3421 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3422 compare(expected, tokens);
3423 }
3424 }
3425
3426 #[test]
3427 fn tokenize_comment_postgres() {
3428 let sql = String::from("1--\r0");
3429
3430 let dialect = PostgreSqlDialect {};
3431 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3432 let expected = vec![
3433 Token::Number("1".to_string(), false),
3434 Token::Whitespace(Whitespace::SingleLineComment {
3435 prefix: "--".to_string(),
3436 comment: "".to_string(),
3437 }),
3438 Token::Whitespace(Whitespace::Newline), Token::Number("0".to_string(), false),
3440 ];
3441 compare(expected, tokens);
3442 }
3443
3444 #[test]
3445 fn tokenize_comment_at_eof() {
3446 let sql = String::from("--this is a comment");
3447
3448 let dialect = GenericDialect {};
3449 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3450 let expected = vec![Token::Whitespace(Whitespace::SingleLineComment {
3451 prefix: "--".to_string(),
3452 comment: "this is a comment".to_string(),
3453 })];
3454 compare(expected, tokens);
3455 }
3456
3457 #[test]
3458 fn tokenize_multiline_comment() {
3459 let sql = String::from("0/*multi-line\n* /comment*/1");
3460
3461 let dialect = GenericDialect {};
3462 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3463 let expected = vec![
3464 Token::Number("0".to_string(), false),
3465 Token::Whitespace(Whitespace::MultiLineComment(
3466 "multi-line\n* /comment".to_string(),
3467 )),
3468 Token::Number("1".to_string(), false),
3469 ];
3470 compare(expected, tokens);
3471 }
3472
3473 #[test]
3474 fn tokenize_nested_multiline_comment() {
3475 all_dialects_where(|d| d.supports_nested_comments()).tokenizes_to(
3476 "0/*multi-line\n* \n/* comment \n /*comment*/*/ */ /comment*/1",
3477 vec![
3478 Token::Number("0".to_string(), false),
3479 Token::Whitespace(Whitespace::MultiLineComment(
3480 "multi-line\n* \n/* comment \n /*comment*/*/ ".into(),
3481 )),
3482 Token::Whitespace(Whitespace::Space),
3483 Token::Div,
3484 Token::Word(Word {
3485 value: "comment".to_string(),
3486 quote_style: None,
3487 keyword: Keyword::COMMENT,
3488 }),
3489 Token::Mul,
3490 Token::Div,
3491 Token::Number("1".to_string(), false),
3492 ],
3493 );
3494
3495 all_dialects_where(|d| d.supports_nested_comments()).tokenizes_to(
3496 "0/*multi-line\n* \n/* comment \n /*comment/**/ */ /comment*/*/1",
3497 vec![
3498 Token::Number("0".to_string(), false),
3499 Token::Whitespace(Whitespace::MultiLineComment(
3500 "multi-line\n* \n/* comment \n /*comment/**/ */ /comment*/".into(),
3501 )),
3502 Token::Number("1".to_string(), false),
3503 ],
3504 );
3505
3506 all_dialects_where(|d| d.supports_nested_comments()).tokenizes_to(
3507 "SELECT 1/* a /* b */ c */0",
3508 vec![
3509 Token::make_keyword("SELECT"),
3510 Token::Whitespace(Whitespace::Space),
3511 Token::Number("1".to_string(), false),
3512 Token::Whitespace(Whitespace::MultiLineComment(" a /* b */ c ".to_string())),
3513 Token::Number("0".to_string(), false),
3514 ],
3515 );
3516 }
3517
3518 #[test]
3519 fn tokenize_nested_multiline_comment_empty() {
3520 all_dialects_where(|d| d.supports_nested_comments()).tokenizes_to(
3521 "select 1/*/**/*/0",
3522 vec![
3523 Token::make_keyword("select"),
3524 Token::Whitespace(Whitespace::Space),
3525 Token::Number("1".to_string(), false),
3526 Token::Whitespace(Whitespace::MultiLineComment("/**/".to_string())),
3527 Token::Number("0".to_string(), false),
3528 ],
3529 );
3530 }
3531
3532 #[test]
3533 fn tokenize_nested_comments_if_not_supported() {
3534 all_dialects_except(|d| d.supports_nested_comments()).tokenizes_to(
3535 "SELECT 1/*/* nested comment */*/0",
3536 vec![
3537 Token::make_keyword("SELECT"),
3538 Token::Whitespace(Whitespace::Space),
3539 Token::Number("1".to_string(), false),
3540 Token::Whitespace(Whitespace::MultiLineComment(
3541 "/* nested comment ".to_string(),
3542 )),
3543 Token::Mul,
3544 Token::Div,
3545 Token::Number("0".to_string(), false),
3546 ],
3547 );
3548 }
3549
3550 #[test]
3551 fn tokenize_multiline_comment_with_even_asterisks() {
3552 let sql = String::from("\n/** Comment **/\n");
3553
3554 let dialect = GenericDialect {};
3555 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3556 let expected = vec![
3557 Token::Whitespace(Whitespace::Newline),
3558 Token::Whitespace(Whitespace::MultiLineComment("* Comment *".to_string())),
3559 Token::Whitespace(Whitespace::Newline),
3560 ];
3561 compare(expected, tokens);
3562 }
3563
3564 #[test]
3565 fn tokenize_unicode_whitespace() {
3566 let sql = String::from(" \u{2003}\n");
3567
3568 let dialect = GenericDialect {};
3569 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3570 let expected = vec![
3571 Token::Whitespace(Whitespace::Space),
3572 Token::Whitespace(Whitespace::Space),
3573 Token::Whitespace(Whitespace::Newline),
3574 ];
3575 compare(expected, tokens);
3576 }
3577
3578 #[test]
3579 fn tokenize_mismatched_quotes() {
3580 let sql = String::from("\"foo");
3581
3582 let dialect = GenericDialect {};
3583 let mut tokenizer = Tokenizer::new(&dialect, &sql);
3584 assert_eq!(
3585 tokenizer.tokenize(),
3586 Err(TokenizerError {
3587 message: "Expected close delimiter '\"' before EOF.".to_string(),
3588 location: Location { line: 1, column: 1 },
3589 })
3590 );
3591 }
3592
3593 #[test]
3594 fn tokenize_newlines() {
3595 let sql = String::from("line1\nline2\rline3\r\nline4\r");
3596
3597 let dialect = GenericDialect {};
3598 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
3599 let expected = vec![
3600 Token::make_word("line1", None),
3601 Token::Whitespace(Whitespace::Newline),
3602 Token::make_word("line2", None),
3603 Token::Whitespace(Whitespace::Newline),
3604 Token::make_word("line3", None),
3605 Token::Whitespace(Whitespace::Newline),
3606 Token::make_word("line4", None),
3607 Token::Whitespace(Whitespace::Newline),
3608 ];
3609 compare(expected, tokens);
3610 }
3611
3612 #[test]
3613 fn tokenize_mssql_top() {
3614 let sql = "SELECT TOP 5 [bar] FROM foo";
3615 let dialect = MsSqlDialect {};
3616 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3617 let expected = vec![
3618 Token::make_keyword("SELECT"),
3619 Token::Whitespace(Whitespace::Space),
3620 Token::make_keyword("TOP"),
3621 Token::Whitespace(Whitespace::Space),
3622 Token::Number(String::from("5"), false),
3623 Token::Whitespace(Whitespace::Space),
3624 Token::make_word("bar", Some('[')),
3625 Token::Whitespace(Whitespace::Space),
3626 Token::make_keyword("FROM"),
3627 Token::Whitespace(Whitespace::Space),
3628 Token::make_word("foo", None),
3629 ];
3630 compare(expected, tokens);
3631 }
3632
3633 #[test]
3634 fn tokenize_pg_regex_match() {
3635 let sql = "SELECT col ~ '^a', col ~* '^a', col !~ '^a', col !~* '^a'";
3636 let dialect = GenericDialect {};
3637 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3638 let expected = vec![
3639 Token::make_keyword("SELECT"),
3640 Token::Whitespace(Whitespace::Space),
3641 Token::make_word("col", None),
3642 Token::Whitespace(Whitespace::Space),
3643 Token::Tilde,
3644 Token::Whitespace(Whitespace::Space),
3645 Token::SingleQuotedString("^a".into()),
3646 Token::Comma,
3647 Token::Whitespace(Whitespace::Space),
3648 Token::make_word("col", None),
3649 Token::Whitespace(Whitespace::Space),
3650 Token::TildeAsterisk,
3651 Token::Whitespace(Whitespace::Space),
3652 Token::SingleQuotedString("^a".into()),
3653 Token::Comma,
3654 Token::Whitespace(Whitespace::Space),
3655 Token::make_word("col", None),
3656 Token::Whitespace(Whitespace::Space),
3657 Token::ExclamationMarkTilde,
3658 Token::Whitespace(Whitespace::Space),
3659 Token::SingleQuotedString("^a".into()),
3660 Token::Comma,
3661 Token::Whitespace(Whitespace::Space),
3662 Token::make_word("col", None),
3663 Token::Whitespace(Whitespace::Space),
3664 Token::ExclamationMarkTildeAsterisk,
3665 Token::Whitespace(Whitespace::Space),
3666 Token::SingleQuotedString("^a".into()),
3667 ];
3668 compare(expected, tokens);
3669 }
3670
3671 #[test]
3672 fn tokenize_pg_like_match() {
3673 let sql = "SELECT col ~~ '_a%', col ~~* '_a%', col !~~ '_a%', col !~~* '_a%'";
3674 let dialect = GenericDialect {};
3675 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3676 let expected = vec![
3677 Token::make_keyword("SELECT"),
3678 Token::Whitespace(Whitespace::Space),
3679 Token::make_word("col", None),
3680 Token::Whitespace(Whitespace::Space),
3681 Token::DoubleTilde,
3682 Token::Whitespace(Whitespace::Space),
3683 Token::SingleQuotedString("_a%".into()),
3684 Token::Comma,
3685 Token::Whitespace(Whitespace::Space),
3686 Token::make_word("col", None),
3687 Token::Whitespace(Whitespace::Space),
3688 Token::DoubleTildeAsterisk,
3689 Token::Whitespace(Whitespace::Space),
3690 Token::SingleQuotedString("_a%".into()),
3691 Token::Comma,
3692 Token::Whitespace(Whitespace::Space),
3693 Token::make_word("col", None),
3694 Token::Whitespace(Whitespace::Space),
3695 Token::ExclamationMarkDoubleTilde,
3696 Token::Whitespace(Whitespace::Space),
3697 Token::SingleQuotedString("_a%".into()),
3698 Token::Comma,
3699 Token::Whitespace(Whitespace::Space),
3700 Token::make_word("col", None),
3701 Token::Whitespace(Whitespace::Space),
3702 Token::ExclamationMarkDoubleTildeAsterisk,
3703 Token::Whitespace(Whitespace::Space),
3704 Token::SingleQuotedString("_a%".into()),
3705 ];
3706 compare(expected, tokens);
3707 }
3708
3709 #[test]
3710 fn tokenize_quoted_identifier() {
3711 let sql = r#" "a "" b" "a """ "c """"" "#;
3712 let dialect = GenericDialect {};
3713 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3714 let expected = vec![
3715 Token::Whitespace(Whitespace::Space),
3716 Token::make_word(r#"a " b"#, Some('"')),
3717 Token::Whitespace(Whitespace::Space),
3718 Token::make_word(r#"a ""#, Some('"')),
3719 Token::Whitespace(Whitespace::Space),
3720 Token::make_word(r#"c """#, Some('"')),
3721 Token::Whitespace(Whitespace::Space),
3722 ];
3723 compare(expected, tokens);
3724 }
3725
3726 #[test]
3727 fn tokenize_snowflake_div() {
3728 let sql = r#"field/1000"#;
3729 let dialect = SnowflakeDialect {};
3730 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3731 let expected = vec![
3732 Token::make_word(r#"field"#, None),
3733 Token::Div,
3734 Token::Number("1000".to_string(), false),
3735 ];
3736 compare(expected, tokens);
3737 }
3738
3739 #[test]
3740 fn tokenize_quoted_identifier_with_no_escape() {
3741 let sql = r#" "a "" b" "a """ "c """"" "#;
3742 let dialect = GenericDialect {};
3743 let tokens = Tokenizer::new(&dialect, sql)
3744 .with_unescape(false)
3745 .tokenize()
3746 .unwrap();
3747 let expected = vec![
3748 Token::Whitespace(Whitespace::Space),
3749 Token::make_word(r#"a "" b"#, Some('"')),
3750 Token::Whitespace(Whitespace::Space),
3751 Token::make_word(r#"a """#, Some('"')),
3752 Token::Whitespace(Whitespace::Space),
3753 Token::make_word(r#"c """""#, Some('"')),
3754 Token::Whitespace(Whitespace::Space),
3755 ];
3756 compare(expected, tokens);
3757 }
3758
3759 #[test]
3760 fn tokenize_with_location() {
3761 let sql = "SELECT a,\n b";
3762 let dialect = GenericDialect {};
3763 let tokens = Tokenizer::new(&dialect, sql)
3764 .tokenize_with_location()
3765 .unwrap();
3766 let expected = vec![
3767 TokenWithSpan::at(Token::make_keyword("SELECT"), (1, 1).into(), (1, 7).into()),
3768 TokenWithSpan::at(
3769 Token::Whitespace(Whitespace::Space),
3770 (1, 7).into(),
3771 (1, 8).into(),
3772 ),
3773 TokenWithSpan::at(Token::make_word("a", None), (1, 8).into(), (1, 9).into()),
3774 TokenWithSpan::at(Token::Comma, (1, 9).into(), (1, 10).into()),
3775 TokenWithSpan::at(
3776 Token::Whitespace(Whitespace::Newline),
3777 (1, 10).into(),
3778 (2, 1).into(),
3779 ),
3780 TokenWithSpan::at(
3781 Token::Whitespace(Whitespace::Space),
3782 (2, 1).into(),
3783 (2, 2).into(),
3784 ),
3785 TokenWithSpan::at(Token::make_word("b", None), (2, 2).into(), (2, 3).into()),
3786 ];
3787 compare(expected, tokens);
3788 }
3789
3790 fn compare<T: PartialEq + fmt::Debug>(expected: Vec<T>, actual: Vec<T>) {
3791 assert_eq!(expected, actual);
3796 }
3797
3798 fn check_unescape(s: &str, expected: Option<&str>) {
3799 let s = format!("'{s}'");
3800 let mut state = State {
3801 peekable: s.chars().peekable(),
3802 line: 0,
3803 col: 0,
3804 };
3805
3806 assert_eq!(
3807 unescape_single_quoted_string(&mut state),
3808 expected.map(|s| s.to_string())
3809 );
3810 }
3811
3812 #[test]
3813 fn test_unescape() {
3814 check_unescape(r"\b", Some("\u{0008}"));
3815 check_unescape(r"\f", Some("\u{000C}"));
3816 check_unescape(r"\t", Some("\t"));
3817 check_unescape(r"\r\n", Some("\r\n"));
3818 check_unescape(r"\/", Some("/"));
3819 check_unescape(r"/", Some("/"));
3820 check_unescape(r"\\", Some("\\"));
3821
3822 check_unescape(r"\u0001", Some("\u{0001}"));
3824 check_unescape(r"\u4c91", Some("\u{4c91}"));
3825 check_unescape(r"\u4c916", Some("\u{4c91}6"));
3826 check_unescape(r"\u4c", None);
3827 check_unescape(r"\u0000", None);
3828 check_unescape(r"\U0010FFFF", Some("\u{10FFFF}"));
3829 check_unescape(r"\U00110000", None);
3830 check_unescape(r"\U00000000", None);
3831 check_unescape(r"\u", None);
3832 check_unescape(r"\U", None);
3833 check_unescape(r"\U1010FFFF", None);
3834
3835 check_unescape(r"\x4B", Some("\u{004b}"));
3837 check_unescape(r"\x4", Some("\u{0004}"));
3838 check_unescape(r"\x4L", Some("\u{0004}L"));
3839 check_unescape(r"\x", Some("x"));
3840 check_unescape(r"\xP", Some("xP"));
3841 check_unescape(r"\x0", None);
3842 check_unescape(r"\xCAD", None);
3843 check_unescape(r"\xA9", None);
3844
3845 check_unescape(r"\1", Some("\u{0001}"));
3847 check_unescape(r"\12", Some("\u{000a}"));
3848 check_unescape(r"\123", Some("\u{0053}"));
3849 check_unescape(r"\1232", Some("\u{0053}2"));
3850 check_unescape(r"\4", Some("\u{0004}"));
3851 check_unescape(r"\45", Some("\u{0025}"));
3852 check_unescape(r"\450", Some("\u{0028}"));
3853 check_unescape(r"\603", None);
3854 check_unescape(r"\0", None);
3855 check_unescape(r"\080", None);
3856
3857 check_unescape(r"\9", Some("9"));
3859 check_unescape(r"''", Some("'"));
3860 check_unescape(
3861 r"Hello\r\nRust/\u4c91 SQL Parser\U0010ABCD\1232",
3862 Some("Hello\r\nRust/\u{4c91} SQL Parser\u{10abcd}\u{0053}2"),
3863 );
3864 check_unescape(r"Hello\0", None);
3865 check_unescape(r"Hello\xCADRust", None);
3866 }
3867
3868 #[test]
3869 fn tokenize_numeric_prefix_trait() {
3870 #[derive(Debug)]
3871 struct NumericPrefixDialect;
3872
3873 impl Dialect for NumericPrefixDialect {
3874 fn is_identifier_start(&self, ch: char) -> bool {
3875 ch.is_ascii_lowercase()
3876 || ch.is_ascii_uppercase()
3877 || ch.is_ascii_digit()
3878 || ch == '$'
3879 }
3880
3881 fn is_identifier_part(&self, ch: char) -> bool {
3882 ch.is_ascii_lowercase()
3883 || ch.is_ascii_uppercase()
3884 || ch.is_ascii_digit()
3885 || ch == '_'
3886 || ch == '$'
3887 || ch == '{'
3888 || ch == '}'
3889 }
3890
3891 fn supports_numeric_prefix(&self) -> bool {
3892 true
3893 }
3894 }
3895
3896 tokenize_numeric_prefix_inner(&NumericPrefixDialect {});
3897 tokenize_numeric_prefix_inner(&HiveDialect {});
3898 tokenize_numeric_prefix_inner(&MySqlDialect {});
3899 }
3900
3901 fn tokenize_numeric_prefix_inner(dialect: &dyn Dialect) {
3902 let sql = r#"SELECT * FROM 1"#;
3903 let tokens = Tokenizer::new(dialect, sql).tokenize().unwrap();
3904 let expected = vec![
3905 Token::make_keyword("SELECT"),
3906 Token::Whitespace(Whitespace::Space),
3907 Token::Mul,
3908 Token::Whitespace(Whitespace::Space),
3909 Token::make_keyword("FROM"),
3910 Token::Whitespace(Whitespace::Space),
3911 Token::Number(String::from("1"), false),
3912 ];
3913 compare(expected, tokens);
3914 }
3915
3916 #[test]
3917 fn tokenize_quoted_string_escape() {
3918 let dialect = SnowflakeDialect {};
3919 for (sql, expected, expected_unescaped) in [
3920 (r#"'%a\'%b'"#, r#"%a\'%b"#, r#"%a'%b"#),
3921 (r#"'a\'\'b\'c\'d'"#, r#"a\'\'b\'c\'d"#, r#"a''b'c'd"#),
3922 (r#"'\\'"#, r#"\\"#, r#"\"#),
3923 (
3924 r#"'\0\a\b\f\n\r\t\Z'"#,
3925 r#"\0\a\b\f\n\r\t\Z"#,
3926 "\0\u{7}\u{8}\u{c}\n\r\t\u{1a}",
3927 ),
3928 (r#"'\"'"#, r#"\""#, "\""),
3929 (r#"'\\a\\b\'c'"#, r#"\\a\\b\'c"#, r#"\a\b'c"#),
3930 (r#"'\'abcd'"#, r#"\'abcd"#, r#"'abcd"#),
3931 (r#"'''a''b'"#, r#"''a''b"#, r#"'a'b"#),
3932 (r#"'\q'"#, r#"\q"#, r#"q"#),
3933 (r#"'\%\_'"#, r#"\%\_"#, r#"%_"#),
3934 (r#"'\\%\\_'"#, r#"\\%\\_"#, r#"\%\_"#),
3935 ] {
3936 let tokens = Tokenizer::new(&dialect, sql)
3937 .with_unescape(false)
3938 .tokenize()
3939 .unwrap();
3940 let expected = vec![Token::SingleQuotedString(expected.to_string())];
3941 compare(expected, tokens);
3942
3943 let tokens = Tokenizer::new(&dialect, sql)
3944 .with_unescape(true)
3945 .tokenize()
3946 .unwrap();
3947 let expected = vec![Token::SingleQuotedString(expected_unescaped.to_string())];
3948 compare(expected, tokens);
3949 }
3950
3951 for sql in [r#"'\'"#, r#"'ab\'"#] {
3952 let mut tokenizer = Tokenizer::new(&dialect, sql);
3953 assert_eq!(
3954 "Unterminated string literal",
3955 tokenizer.tokenize().unwrap_err().message.as_str(),
3956 );
3957 }
3958
3959 for (sql, expected) in [(r#"'\'"#, r#"\"#), (r#"'ab\'"#, r#"ab\"#)] {
3961 let dialect = GenericDialect {};
3962 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3963
3964 let expected = vec![Token::SingleQuotedString(expected.to_string())];
3965
3966 compare(expected, tokens);
3967 }
3968
3969 for (sql, expected) in [(r#"'\%'"#, r#"\%"#), (r#"'\_'"#, r#"\_"#)] {
3971 let dialect = MySqlDialect {};
3972 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
3973
3974 let expected = vec![Token::SingleQuotedString(expected.to_string())];
3975
3976 compare(expected, tokens);
3977 }
3978 }
3979
3980 #[test]
3981 fn tokenize_triple_quoted_string() {
3982 fn check<F>(
3983 q: char, r: char, quote_token: F,
3986 ) where
3987 F: Fn(String) -> Token,
3988 {
3989 let dialect = BigQueryDialect {};
3990
3991 for (sql, expected, expected_unescaped) in [
3992 (format!(r#"{q}{q}{q}{q}{q}{q}"#), "".into(), "".into()),
3994 (
3996 format!(r#"{q}{q}{q}ab{q}{q}\{q}{q}cd{q}{q}{q}"#),
3997 format!(r#"ab{q}{q}\{q}{q}cd"#),
3998 format!(r#"ab{q}{q}{q}{q}cd"#),
3999 ),
4000 (
4002 format!(r#"{q}{q}{q}abc{q}{q}{q}"#),
4003 "abc".into(),
4004 "abc".into(),
4005 ),
4006 (
4008 format!(r#"{q}{q}{q}ab{r}{r}{r}c{r}def{r}{r}{r}{q}{q}{q}"#),
4009 format!("ab{r}{r}{r}c{r}def{r}{r}{r}"),
4010 format!("ab{r}{r}{r}c{r}def{r}{r}{r}"),
4011 ),
4012 (
4014 format!(r#"{q}{q}{q}ab{q}{q}c{q}{q}\{q}de{q}{q}f{q}{q}{q}"#),
4015 format!(r#"ab{q}{q}c{q}{q}\{q}de{q}{q}f"#),
4016 format!(r#"ab{q}{q}c{q}{q}{q}de{q}{q}f"#),
4017 ),
4018 (
4020 format!(r#"{q}{q}{q}a\'\'b\'c\'d{q}{q}{q}"#),
4021 r#"a\'\'b\'c\'d"#.into(),
4022 r#"a''b'c'd"#.into(),
4023 ),
4024 (
4026 format!(r#"{q}{q}{q}abc\0\n\rdef{q}{q}{q}"#),
4027 r#"abc\0\n\rdef"#.into(),
4028 "abc\0\n\rdef".into(),
4029 ),
4030 ] {
4031 let tokens = Tokenizer::new(&dialect, sql.as_str())
4032 .with_unescape(false)
4033 .tokenize()
4034 .unwrap();
4035 let expected = vec![quote_token(expected.to_string())];
4036 compare(expected, tokens);
4037
4038 let tokens = Tokenizer::new(&dialect, sql.as_str())
4039 .with_unescape(true)
4040 .tokenize()
4041 .unwrap();
4042 let expected = vec![quote_token(expected_unescaped.to_string())];
4043 compare(expected, tokens);
4044 }
4045
4046 for sql in [
4047 format!(r#"{q}{q}{q}{q}{q}\{q}"#),
4048 format!(r#"{q}{q}{q}abc{q}{q}\{q}"#),
4049 format!(r#"{q}{q}{q}{q}"#),
4050 format!(r#"{q}{q}{q}{r}{r}"#),
4051 format!(r#"{q}{q}{q}abc{q}"#),
4052 format!(r#"{q}{q}{q}abc{q}{q}"#),
4053 format!(r#"{q}{q}{q}abc"#),
4054 ] {
4055 let dialect = BigQueryDialect {};
4056 let mut tokenizer = Tokenizer::new(&dialect, sql.as_str());
4057 assert_eq!(
4058 "Unterminated string literal",
4059 tokenizer.tokenize().unwrap_err().message.as_str(),
4060 );
4061 }
4062 }
4063
4064 check('"', '\'', Token::TripleDoubleQuotedString);
4065
4066 check('\'', '"', Token::TripleSingleQuotedString);
4067
4068 let dialect = BigQueryDialect {};
4069
4070 let sql = r#"""''"#;
4071 let tokens = Tokenizer::new(&dialect, sql)
4072 .with_unescape(true)
4073 .tokenize()
4074 .unwrap();
4075 let expected = vec![
4076 Token::DoubleQuotedString("".to_string()),
4077 Token::SingleQuotedString("".to_string()),
4078 ];
4079 compare(expected, tokens);
4080
4081 let sql = r#"''"""#;
4082 let tokens = Tokenizer::new(&dialect, sql)
4083 .with_unescape(true)
4084 .tokenize()
4085 .unwrap();
4086 let expected = vec![
4087 Token::SingleQuotedString("".to_string()),
4088 Token::DoubleQuotedString("".to_string()),
4089 ];
4090 compare(expected, tokens);
4091
4092 let dialect = SnowflakeDialect {};
4094 let sql = r#"''''''"#;
4095 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4096 let expected = vec![Token::SingleQuotedString("''".to_string())];
4097 compare(expected, tokens);
4098 }
4099
4100 #[test]
4101 fn test_mysql_users_grantees() {
4102 let dialect = MySqlDialect {};
4103
4104 let sql = "CREATE USER `root`@`%`";
4105 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4106 let expected = vec![
4107 Token::make_keyword("CREATE"),
4108 Token::Whitespace(Whitespace::Space),
4109 Token::make_keyword("USER"),
4110 Token::Whitespace(Whitespace::Space),
4111 Token::make_word("root", Some('`')),
4112 Token::AtSign,
4113 Token::make_word("%", Some('`')),
4114 ];
4115 compare(expected, tokens);
4116 }
4117
4118 #[test]
4119 fn test_postgres_abs_without_space_and_string_literal() {
4120 let dialect = MySqlDialect {};
4121
4122 let sql = "SELECT @'1'";
4123 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4124 let expected = vec![
4125 Token::make_keyword("SELECT"),
4126 Token::Whitespace(Whitespace::Space),
4127 Token::AtSign,
4128 Token::SingleQuotedString("1".to_string()),
4129 ];
4130 compare(expected, tokens);
4131 }
4132
4133 #[test]
4134 fn test_postgres_abs_without_space_and_quoted_column() {
4135 let dialect = MySqlDialect {};
4136
4137 let sql = r#"SELECT @"bar" FROM foo"#;
4138 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4139 let expected = vec![
4140 Token::make_keyword("SELECT"),
4141 Token::Whitespace(Whitespace::Space),
4142 Token::AtSign,
4143 Token::DoubleQuotedString("bar".to_string()),
4144 Token::Whitespace(Whitespace::Space),
4145 Token::make_keyword("FROM"),
4146 Token::Whitespace(Whitespace::Space),
4147 Token::make_word("foo", None),
4148 ];
4149 compare(expected, tokens);
4150 }
4151
4152 #[test]
4153 fn test_national_strings_backslash_escape_not_supported() {
4154 all_dialects_where(|dialect| {
4155 !dialect.supports_string_literal_backslash_escape()
4156 && dialect.supports_national_string_literal()
4157 })
4158 .tokenizes_to(
4159 "select n'''''\\'",
4160 vec![
4161 Token::make_keyword("select"),
4162 Token::Whitespace(Whitespace::Space),
4163 Token::NationalStringLiteral("''\\".to_string()),
4164 ],
4165 );
4166 }
4167
4168 #[test]
4169 fn test_national_strings_backslash_escape_supported() {
4170 all_dialects_where(|dialect| dialect.supports_string_literal_backslash_escape())
4171 .tokenizes_to(
4172 "select n'''''\\''",
4173 vec![
4174 Token::make_keyword("select"),
4175 Token::Whitespace(Whitespace::Space),
4176 Token::NationalStringLiteral("'''".to_string()),
4177 ],
4178 );
4179 }
4180
4181 #[test]
4182 fn test_string_escape_constant_not_supported() {
4183 all_dialects_where(|dialect| !dialect.supports_string_escape_constant()).tokenizes_to(
4184 "select e'...'",
4185 vec![
4186 Token::make_keyword("select"),
4187 Token::Whitespace(Whitespace::Space),
4188 Token::make_word("e", None),
4189 Token::SingleQuotedString("...".to_string()),
4190 ],
4191 );
4192
4193 all_dialects_where(|dialect| !dialect.supports_string_escape_constant()).tokenizes_to(
4194 "select E'...'",
4195 vec![
4196 Token::make_keyword("select"),
4197 Token::Whitespace(Whitespace::Space),
4198 Token::make_word("E", None),
4199 Token::SingleQuotedString("...".to_string()),
4200 ],
4201 );
4202 }
4203
4204 #[test]
4205 fn test_string_escape_constant_supported() {
4206 all_dialects_where(|dialect| dialect.supports_string_escape_constant()).tokenizes_to(
4207 "select e'\\''",
4208 vec![
4209 Token::make_keyword("select"),
4210 Token::Whitespace(Whitespace::Space),
4211 Token::EscapedStringLiteral("'".to_string()),
4212 ],
4213 );
4214
4215 all_dialects_where(|dialect| dialect.supports_string_escape_constant()).tokenizes_to(
4216 "select E'\\''",
4217 vec![
4218 Token::make_keyword("select"),
4219 Token::Whitespace(Whitespace::Space),
4220 Token::EscapedStringLiteral("'".to_string()),
4221 ],
4222 );
4223 }
4224
4225 #[test]
4226 fn test_whitespace_required_after_single_line_comment() {
4227 all_dialects_where(|dialect| dialect.requires_single_line_comment_whitespace())
4228 .tokenizes_to(
4229 "SELECT --'abc'",
4230 vec![
4231 Token::make_keyword("SELECT"),
4232 Token::Whitespace(Whitespace::Space),
4233 Token::Minus,
4234 Token::Minus,
4235 Token::SingleQuotedString("abc".to_string()),
4236 ],
4237 );
4238
4239 all_dialects_where(|dialect| dialect.requires_single_line_comment_whitespace())
4240 .tokenizes_to(
4241 "SELECT -- 'abc'",
4242 vec![
4243 Token::make_keyword("SELECT"),
4244 Token::Whitespace(Whitespace::Space),
4245 Token::Whitespace(Whitespace::SingleLineComment {
4246 prefix: "--".to_string(),
4247 comment: " 'abc'".to_string(),
4248 }),
4249 ],
4250 );
4251
4252 all_dialects_where(|dialect| dialect.requires_single_line_comment_whitespace())
4253 .tokenizes_to(
4254 "SELECT --",
4255 vec![
4256 Token::make_keyword("SELECT"),
4257 Token::Whitespace(Whitespace::Space),
4258 Token::Minus,
4259 Token::Minus,
4260 ],
4261 );
4262
4263 all_dialects_where(|d| d.requires_single_line_comment_whitespace()).tokenizes_to(
4264 "--\n-- Table structure for table...\n--\n",
4265 vec![
4266 Token::Whitespace(Whitespace::SingleLineComment {
4267 prefix: "--".to_string(),
4268 comment: "".to_string(),
4269 }),
4270 Token::Whitespace(Whitespace::Newline),
4271 Token::Whitespace(Whitespace::SingleLineComment {
4272 prefix: "--".to_string(),
4273 comment: " Table structure for table...".to_string(),
4274 }),
4275 Token::Whitespace(Whitespace::Newline),
4276 Token::Whitespace(Whitespace::SingleLineComment {
4277 prefix: "--".to_string(),
4278 comment: "".to_string(),
4279 }),
4280 Token::Whitespace(Whitespace::Newline),
4281 ],
4282 );
4283 }
4284
4285 #[test]
4286 fn test_whitespace_not_required_after_single_line_comment() {
4287 all_dialects_where(|dialect| !dialect.requires_single_line_comment_whitespace())
4288 .tokenizes_to(
4289 "SELECT --'abc'",
4290 vec![
4291 Token::make_keyword("SELECT"),
4292 Token::Whitespace(Whitespace::Space),
4293 Token::Whitespace(Whitespace::SingleLineComment {
4294 prefix: "--".to_string(),
4295 comment: "'abc'".to_string(),
4296 }),
4297 ],
4298 );
4299
4300 all_dialects_where(|dialect| !dialect.requires_single_line_comment_whitespace())
4301 .tokenizes_to(
4302 "SELECT -- 'abc'",
4303 vec![
4304 Token::make_keyword("SELECT"),
4305 Token::Whitespace(Whitespace::Space),
4306 Token::Whitespace(Whitespace::SingleLineComment {
4307 prefix: "--".to_string(),
4308 comment: " 'abc'".to_string(),
4309 }),
4310 ],
4311 );
4312
4313 all_dialects_where(|dialect| !dialect.requires_single_line_comment_whitespace())
4314 .tokenizes_to(
4315 "SELECT --",
4316 vec![
4317 Token::make_keyword("SELECT"),
4318 Token::Whitespace(Whitespace::Space),
4319 Token::Whitespace(Whitespace::SingleLineComment {
4320 prefix: "--".to_string(),
4321 comment: "".to_string(),
4322 }),
4323 ],
4324 );
4325 }
4326
4327 #[test]
4328 fn test_tokenize_identifiers_numeric_prefix() {
4329 all_dialects_where(|dialect| dialect.supports_numeric_prefix())
4330 .tokenizes_to("123abc", vec![Token::make_word("123abc", None)]);
4331
4332 all_dialects_where(|dialect| dialect.supports_numeric_prefix())
4333 .tokenizes_to("12e34", vec![Token::Number("12e34".to_string(), false)]);
4334
4335 all_dialects_where(|dialect| dialect.supports_numeric_prefix()).tokenizes_to(
4336 "t.12e34",
4337 vec![
4338 Token::make_word("t", None),
4339 Token::Period,
4340 Token::make_word("12e34", None),
4341 ],
4342 );
4343
4344 all_dialects_where(|dialect| dialect.supports_numeric_prefix()).tokenizes_to(
4345 "t.1two3",
4346 vec![
4347 Token::make_word("t", None),
4348 Token::Period,
4349 Token::make_word("1two3", None),
4350 ],
4351 );
4352 }
4353
4354 #[test]
4355 fn tokenize_period_underscore() {
4356 let sql = String::from("SELECT table._col");
4357 let dialect = PostgreSqlDialect {};
4359 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
4360
4361 let expected = vec![
4362 Token::make_keyword("SELECT"),
4363 Token::Whitespace(Whitespace::Space),
4364 Token::Word(Word {
4365 value: "table".to_string(),
4366 quote_style: None,
4367 keyword: Keyword::TABLE,
4368 }),
4369 Token::Period,
4370 Token::Word(Word {
4371 value: "_col".to_string(),
4372 quote_style: None,
4373 keyword: Keyword::NoKeyword,
4374 }),
4375 ];
4376
4377 compare(expected, tokens);
4378
4379 let sql = String::from("SELECT ._123");
4380 if let Ok(tokens) = Tokenizer::new(&dialect, &sql).tokenize() {
4381 panic!("Tokenizer should have failed on {sql}, but it succeeded with {tokens:?}");
4382 }
4383
4384 let sql = String::from("SELECT ._abc");
4385 if let Ok(tokens) = Tokenizer::new(&dialect, &sql).tokenize() {
4386 panic!("Tokenizer should have failed on {sql}, but it succeeded with {tokens:?}");
4387 }
4388 }
4389
4390 #[test]
4391 fn tokenize_question_mark() {
4392 let dialect = PostgreSqlDialect {};
4393 let sql = "SELECT x ? y";
4394 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4395 compare(
4396 tokens,
4397 vec![
4398 Token::make_keyword("SELECT"),
4399 Token::Whitespace(Whitespace::Space),
4400 Token::make_word("x", None),
4401 Token::Whitespace(Whitespace::Space),
4402 Token::Question,
4403 Token::Whitespace(Whitespace::Space),
4404 Token::make_word("y", None),
4405 ],
4406 );
4407 }
4408
4409 #[test]
4410 fn tokenize_multiline_comment_with_comment_hint() {
4411 let sql = String::from("0/*! word */1");
4412
4413 let dialect = MySqlDialect {};
4414 let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
4415 let expected = vec![
4416 Token::Number("0".to_string(), false),
4417 Token::Whitespace(Whitespace::Space),
4418 Token::Word(Word {
4419 value: "word".to_string(),
4420 quote_style: None,
4421 keyword: Keyword::NoKeyword,
4422 }),
4423 Token::Whitespace(Whitespace::Space),
4424 Token::Number("1".to_string(), false),
4425 ];
4426 compare(expected, tokens);
4427 }
4428
4429 #[test]
4430 fn tokenize_multiline_comment_with_comment_hint_and_version() {
4431 let sql_multi = String::from("0 /*!50110 KEY_BLOCK_SIZE = 1024*/ 1");
4432 let dialect = MySqlDialect {};
4433 let tokens = Tokenizer::new(&dialect, &sql_multi).tokenize().unwrap();
4434 let expected = vec![
4435 Token::Number("0".to_string(), false),
4436 Token::Whitespace(Whitespace::Space),
4437 Token::Whitespace(Whitespace::Space),
4438 Token::Word(Word {
4439 value: "KEY_BLOCK_SIZE".to_string(),
4440 quote_style: None,
4441 keyword: Keyword::KEY_BLOCK_SIZE,
4442 }),
4443 Token::Whitespace(Whitespace::Space),
4444 Token::Eq,
4445 Token::Whitespace(Whitespace::Space),
4446 Token::Number("1024".to_string(), false),
4447 Token::Whitespace(Whitespace::Space),
4448 Token::Number("1".to_string(), false),
4449 ];
4450 compare(expected, tokens);
4451
4452 let tokens = Tokenizer::new(&dialect, "0 /*!50110 */ 1")
4453 .tokenize()
4454 .unwrap();
4455 compare(
4456 vec![
4457 Token::Number("0".to_string(), false),
4458 Token::Whitespace(Whitespace::Space),
4459 Token::Whitespace(Whitespace::Space),
4460 Token::Whitespace(Whitespace::Space),
4461 Token::Number("1".to_string(), false),
4462 ],
4463 tokens,
4464 );
4465
4466 let tokens = Tokenizer::new(&dialect, "0 /*!*/ 1").tokenize().unwrap();
4467 compare(
4468 vec![
4469 Token::Number("0".to_string(), false),
4470 Token::Whitespace(Whitespace::Space),
4471 Token::Whitespace(Whitespace::Space),
4472 Token::Number("1".to_string(), false),
4473 ],
4474 tokens,
4475 );
4476 let tokens = Tokenizer::new(&dialect, "0 /*! */ 1").tokenize().unwrap();
4477 compare(
4478 vec![
4479 Token::Number("0".to_string(), false),
4480 Token::Whitespace(Whitespace::Space),
4481 Token::Whitespace(Whitespace::Space),
4482 Token::Whitespace(Whitespace::Space),
4483 Token::Whitespace(Whitespace::Space),
4484 Token::Whitespace(Whitespace::Space),
4485 Token::Number("1".to_string(), false),
4486 ],
4487 tokens,
4488 );
4489 }
4490
4491 #[test]
4492 fn tokenize_lt() {
4493 all_dialects().tokenizes_to(
4494 "select a <-50",
4495 vec![
4496 Token::make_keyword("select"),
4497 Token::Whitespace(Whitespace::Space),
4498 Token::make_word("a", None),
4499 Token::Whitespace(Whitespace::Space),
4500 Token::Lt,
4501 Token::Minus,
4502 Token::Number("50".to_string(), false),
4503 ],
4504 );
4505 all_dialects().tokenizes_to(
4506 "select a <+50",
4507 vec![
4508 Token::make_keyword("select"),
4509 Token::Whitespace(Whitespace::Space),
4510 Token::make_word("a", None),
4511 Token::Whitespace(Whitespace::Space),
4512 Token::Lt,
4513 Token::Plus,
4514 Token::Number("50".to_string(), false),
4515 ],
4516 );
4517 all_dialects().tokenizes_to(
4518 "select a <=-50",
4519 vec![
4520 Token::make_keyword("select"),
4521 Token::Whitespace(Whitespace::Space),
4522 Token::make_word("a", None),
4523 Token::Whitespace(Whitespace::Space),
4524 Token::LtEq,
4525 Token::Minus,
4526 Token::Number("50".to_string(), false),
4527 ],
4528 );
4529 all_dialects().tokenizes_to(
4530 "select a <=+50",
4531 vec![
4532 Token::make_keyword("select"),
4533 Token::Whitespace(Whitespace::Space),
4534 Token::make_word("a", None),
4535 Token::Whitespace(Whitespace::Space),
4536 Token::LtEq,
4537 Token::Plus,
4538 Token::Number("50".to_string(), false),
4539 ],
4540 );
4541 all_dialects_where(|d| d.supports_geometric_types()).tokenizes_to(
4542 "select a <->b",
4543 vec![
4544 Token::make_keyword("select"),
4545 Token::Whitespace(Whitespace::Space),
4546 Token::make_word("a", None),
4547 Token::Whitespace(Whitespace::Space),
4548 Token::TwoWayArrow,
4549 Token::make_word("b", None),
4550 ],
4551 );
4552
4553 all_dialects().tokenizes_to(
4554 "select a <-b",
4555 vec![
4556 Token::make_keyword("select"),
4557 Token::Whitespace(Whitespace::Space),
4558 Token::make_word("a", None),
4559 Token::Whitespace(Whitespace::Space),
4560 Token::Lt,
4561 Token::Minus,
4562 Token::make_word("b", None),
4563 ],
4564 );
4565 all_dialects().tokenizes_to(
4566 "select a <+b",
4567 vec![
4568 Token::make_keyword("select"),
4569 Token::Whitespace(Whitespace::Space),
4570 Token::make_word("a", None),
4571 Token::Whitespace(Whitespace::Space),
4572 Token::Lt,
4573 Token::Plus,
4574 Token::make_word("b", None),
4575 ],
4576 );
4577 }
4578
4579 #[test]
4580 fn test_word_display_quote_escaping() {
4581 assert_eq!(
4582 Word {
4583 value: "a\"b".to_string(),
4584 quote_style: Some('"'),
4585 keyword: Keyword::NoKeyword,
4586 }
4587 .to_string(),
4588 "\"a\"\"b\""
4589 );
4590 assert_eq!(
4591 Word {
4592 value: "a`b".to_string(),
4593 quote_style: Some('`'),
4594 keyword: Keyword::NoKeyword,
4595 }
4596 .to_string(),
4597 "`a``b`"
4598 );
4599 assert_eq!(
4600 Word {
4601 value: "a b".to_string(),
4602 quote_style: Some('['),
4603 keyword: Keyword::NoKeyword,
4604 }
4605 .to_string(),
4606 "[a b]"
4607 );
4608 }
4609
4610 #[test]
4611 fn tokenize_period_before_digits_after_identifier_or_bracket() {
4612 let dialect = ClickHouseDialect {};
4613 for (sql, expected) in [
4614 (
4615 "t.1",
4616 vec![
4617 Token::make_word("t", None),
4618 Token::Period,
4619 Token::Number("1".to_string(), false),
4620 ],
4621 ),
4622 (
4623 "(1).1",
4624 vec![
4625 Token::LParen,
4626 Token::Number("1".to_string(), false),
4627 Token::RParen,
4628 Token::Period,
4629 Token::Number("1".to_string(), false),
4630 ],
4631 ),
4632 (
4633 "a[1].1",
4634 vec![
4635 Token::make_word("a", None),
4636 Token::LBracket,
4637 Token::Number("1".to_string(), false),
4638 Token::RBracket,
4639 Token::Period,
4640 Token::Number("1".to_string(), false),
4641 ],
4642 ),
4643 (
4644 "t.1.2",
4645 vec![
4646 Token::make_word("t", None),
4647 Token::Period,
4648 Token::Number("1".to_string(), false),
4649 Token::Period,
4650 Token::Number("2".to_string(), false),
4651 ],
4652 ),
4653 (
4654 "t .1",
4655 vec![
4656 Token::make_word("t", None),
4657 Token::Whitespace(Whitespace::Space),
4658 Token::Number(".1".to_string(), false),
4659 ],
4660 ),
4661 ("1.5", vec![Token::Number("1.5".to_string(), false)]),
4662 (".5", vec![Token::Number(".5".to_string(), false)]),
4663 ] {
4664 let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
4665 compare(expected, tokens);
4666 }
4667
4668 let tokens = Tokenizer::new(&GenericDialect {}, "t.1")
4669 .tokenize()
4670 .unwrap();
4671 compare(
4672 vec![
4673 Token::make_word("t", None),
4674 Token::Number(".1".to_string(), false),
4675 ],
4676 tokens,
4677 );
4678 }
4679}