sqlglot.tokenizer_core
1from __future__ import annotations 2 3import typing as t 4from enum import IntEnum, auto 5 6from sqlglot.errors import TokenError 7 8# dict lookup is faster than .upper() and .isdigit() 9_CHAR_UPPER: dict[str, str] = {chr(i): chr(i).upper() for i in range(97, 123)} 10_DIGIT_CHARS: frozenset[str] = frozenset("0123456789") 11_DIGIT_VALUES: dict[str, int] = {c: int(c, 16) for c in "0123456789abcdefABCDEF"} 12_OCTAL_CHARS: frozenset[str] = frozenset("01234567") 13 14 15class TokenType(IntEnum): 16 L_PAREN = auto() 17 R_PAREN = auto() 18 L_BRACKET = auto() 19 R_BRACKET = auto() 20 L_BRACE = auto() 21 R_BRACE = auto() 22 COMMA = auto() 23 DOT = auto() 24 DASH = auto() 25 PLUS = auto() 26 COLON = auto() 27 DOTCOLON = auto() 28 DOTCARET = auto() 29 DCOLON = auto() 30 DCOLONDOLLAR = auto() 31 DCOLONPERCENT = auto() 32 DCOLONQMARK = auto() 33 DQMARK = auto() 34 SEMICOLON = auto() 35 STAR = auto() 36 BACKSLASH = auto() 37 SLASH = auto() 38 LT = auto() 39 LTE = auto() 40 GT = auto() 41 GTE = auto() 42 NOT = auto() 43 EQ = auto() 44 NEQ = auto() 45 NULLSAFE_EQ = auto() 46 COLON_EQ = auto() 47 COLON_GT = auto() 48 NCOLON_GT = auto() 49 AND = auto() 50 OR = auto() 51 AMP = auto() 52 DPIPE = auto() 53 PIPE_GT = auto() 54 PIPE = auto() 55 PIPE_SLASH = auto() 56 DPIPE_SLASH = auto() 57 CARET = auto() 58 CARET_AT = auto() 59 TILDE = auto() 60 ARROW = auto() 61 DARROW = auto() 62 FARROW = auto() 63 HASH = auto() 64 HASH_ARROW = auto() 65 DHASH_ARROW = auto() 66 LR_ARROW = auto() 67 LLRR_ARROW = auto() 68 DAT = auto() 69 AT_QMARK = auto() 70 LT_AT = auto() 71 AT_GT = auto() 72 DOLLAR = auto() 73 PARAMETER = auto() 74 SESSION = auto() 75 SESSION_PARAMETER = auto() 76 SESSION_USER = auto() 77 DAMP = auto() 78 AMP_LT = auto() 79 AMP_GT = auto() 80 ADJACENT = auto() 81 XOR = auto() 82 DSTAR = auto() 83 QMARK_AMP = auto() 84 QMARK_PIPE = auto() 85 HASH_DASH = auto() 86 EXCLAMATION = auto() 87 88 URI_START = auto() 89 90 BLOCK_START = auto() 91 BLOCK_END = auto() 92 93 SPACE = auto() 94 BREAK = auto() 95 96 STRING = auto() 97 NUMBER = auto() 98 IDENTIFIER = auto() 99 DATABASE = auto() 100 COLUMN = auto() 101 COLUMN_DEF = auto() 102 SCHEMA = auto() 103 TABLE = auto() 104 WAREHOUSE = auto() 105 STAGE = auto() 106 STREAM = auto() 107 STREAMLIT = auto() 108 VAR = auto() 109 BIT_STRING = auto() 110 HEX_STRING = auto() 111 BYTE_STRING = auto() 112 NATIONAL_STRING = auto() 113 RAW_STRING = auto() 114 HEREDOC_STRING = auto() 115 UNICODE_STRING = auto() 116 117 # types 118 BIT = auto() 119 BOOLEAN = auto() 120 TINYINT = auto() 121 UTINYINT = auto() 122 SMALLINT = auto() 123 USMALLINT = auto() 124 MEDIUMINT = auto() 125 UMEDIUMINT = auto() 126 INT = auto() 127 UINT = auto() 128 BIGINT = auto() 129 UBIGINT = auto() 130 BIGNUM = auto() 131 INT128 = auto() 132 UINT128 = auto() 133 INT256 = auto() 134 UINT256 = auto() 135 FLOAT = auto() 136 DOUBLE = auto() 137 UDOUBLE = auto() 138 DECIMAL = auto() 139 DECIMAL32 = auto() 140 DECIMAL64 = auto() 141 DECIMAL128 = auto() 142 DECIMAL256 = auto() 143 DECFLOAT = auto() 144 UDECIMAL = auto() 145 BIGDECIMAL = auto() 146 CHAR = auto() 147 NCHAR = auto() 148 VARCHAR = auto() 149 NVARCHAR = auto() 150 BPCHAR = auto() 151 TEXT = auto() 152 MEDIUMTEXT = auto() 153 LONGTEXT = auto() 154 BLOB = auto() 155 MEDIUMBLOB = auto() 156 LONGBLOB = auto() 157 TINYBLOB = auto() 158 TINYTEXT = auto() 159 NAME = auto() 160 BINARY = auto() 161 VARBINARY = auto() 162 JSON = auto() 163 JSONB = auto() 164 TIME = auto() 165 TIMETZ = auto() 166 TIME_NS = auto() 167 TIMESTAMP = auto() 168 TIMESTAMPTZ = auto() 169 TIMESTAMPLTZ = auto() 170 TIMESTAMPNTZ = auto() 171 TIMESTAMP_S = auto() 172 TIMESTAMP_MS = auto() 173 TIMESTAMP_NS = auto() 174 DATETIME = auto() 175 DATETIME2 = auto() 176 DATETIME64 = auto() 177 SMALLDATETIME = auto() 178 DATE = auto() 179 DATE32 = auto() 180 INT4RANGE = auto() 181 INT4MULTIRANGE = auto() 182 INT8RANGE = auto() 183 INT8MULTIRANGE = auto() 184 NUMRANGE = auto() 185 NUMMULTIRANGE = auto() 186 TSRANGE = auto() 187 TSMULTIRANGE = auto() 188 TSTZRANGE = auto() 189 TSTZMULTIRANGE = auto() 190 DATERANGE = auto() 191 DATEMULTIRANGE = auto() 192 UUID = auto() 193 GEOGRAPHY = auto() 194 GEOGRAPHYPOINT = auto() 195 NULLABLE = auto() 196 GEOMETRY = auto() 197 POINT = auto() 198 RING = auto() 199 LINESTRING = auto() 200 LOCALTIME = auto() 201 LOCALTIMESTAMP = auto() 202 SYSTIMESTAMP = auto() 203 MULTILINESTRING = auto() 204 POLYGON = auto() 205 MULTIPOLYGON = auto() 206 HLLSKETCH = auto() 207 HSTORE = auto() 208 SUPER = auto() 209 SERIAL = auto() 210 SMALLSERIAL = auto() 211 BIGSERIAL = auto() 212 XML = auto() 213 YEAR = auto() 214 USERDEFINED = auto() 215 MONEY = auto() 216 SMALLMONEY = auto() 217 ROWVERSION = auto() 218 IMAGE = auto() 219 VARIANT = auto() 220 OBJECT = auto() 221 INET = auto() 222 IPADDRESS = auto() 223 IPPREFIX = auto() 224 IPV4 = auto() 225 IPV6 = auto() 226 ENUM = auto() 227 ENUM8 = auto() 228 ENUM16 = auto() 229 FIXEDSTRING = auto() 230 LOWCARDINALITY = auto() 231 NESTED = auto() 232 AGGREGATEFUNCTION = auto() 233 SIMPLEAGGREGATEFUNCTION = auto() 234 TDIGEST = auto() 235 UNKNOWN = auto() 236 VECTOR = auto() 237 DYNAMIC = auto() 238 VOID = auto() 239 240 # keywords 241 ALIAS = auto() 242 ALTER = auto() 243 ALL = auto() 244 ANTI = auto() 245 ANY = auto() 246 APPLY = auto() 247 ARRAY = auto() 248 ASC = auto() 249 ASOF = auto() 250 ATTACH = auto() 251 AUTO_INCREMENT = auto() 252 BEGIN = auto() 253 BETWEEN = auto() 254 BULK_COLLECT_INTO = auto() 255 CACHE = auto() 256 CASE = auto() 257 CHARACTER_SET = auto() 258 CLUSTER_BY = auto() 259 COLLATE = auto() 260 COMMAND = auto() 261 COMMENT = auto() 262 COMMIT = auto() 263 CONNECT_BY = auto() 264 CONSTRAINT = auto() 265 COPY = auto() 266 CREATE = auto() 267 CROSS = auto() 268 CUBE = auto() 269 CURRENT_DATE = auto() 270 CURRENT_DATETIME = auto() 271 CURRENT_SCHEMA = auto() 272 CURRENT_TIME = auto() 273 CURRENT_TIMESTAMP = auto() 274 CURRENT_USER = auto() 275 CURRENT_USER_ID = auto() 276 CURRENT_ROLE = auto() 277 CURRENT_CATALOG = auto() 278 DECLARE = auto() 279 DEFAULT = auto() 280 DELETE = auto() 281 DESC = auto() 282 DESCRIBE = auto() 283 DETACH = auto() 284 DICTIONARY = auto() 285 DISTINCT = auto() 286 DISTRIBUTE_BY = auto() 287 DIV = auto() 288 DROP = auto() 289 ELSE = auto() 290 END = auto() 291 ESCAPE = auto() 292 EXCEPT = auto() 293 EXECUTE = auto() 294 EXISTS = auto() 295 FALSE = auto() 296 FETCH = auto() 297 FILE = auto() 298 FILE_FORMAT = auto() 299 FILTER = auto() 300 FINAL = auto() 301 FIRST = auto() 302 FOR = auto() 303 FORCE = auto() 304 FOREIGN_KEY = auto() 305 FORMAT = auto() 306 FROM = auto() 307 FULL = auto() 308 FUNCTION = auto() 309 GET = auto() 310 GLOB = auto() 311 GLOBAL = auto() 312 GRANT = auto() 313 GROUP_BY = auto() 314 GROUPING_SETS = auto() 315 HAVING = auto() 316 HINT = auto() 317 IGNORE = auto() 318 ILIKE = auto() 319 IN = auto() 320 INDEX = auto() 321 INDEXED_BY = auto() 322 INNER = auto() 323 INSERT = auto() 324 INSTALL = auto() 325 INTEGRATION = auto() 326 INTERSECT = auto() 327 INTERVAL = auto() 328 INTO = auto() 329 INTRODUCER = auto() 330 IRLIKE = auto() 331 IS = auto() 332 ISNULL = auto() 333 JOIN = auto() 334 JOIN_MARKER = auto() 335 KEEP = auto() 336 KEY = auto() 337 KILL = auto() 338 LANGUAGE = auto() 339 LATERAL = auto() 340 LEFT = auto() 341 LIKE = auto() 342 LIMIT = auto() 343 LIST = auto() 344 LOAD = auto() 345 LOCK = auto() 346 MAP = auto() 347 MATCH = auto() 348 MATCH_CONDITION = auto() 349 MATCH_RECOGNIZE = auto() 350 MEMBER_OF = auto() 351 MERGE = auto() 352 MOD = auto() 353 MODEL = auto() 354 NATURAL = auto() 355 NEXT = auto() 356 NOTHING = auto() 357 NOTNULL = auto() 358 NULL = auto() 359 OBJECT_IDENTIFIER = auto() 360 OFFSET = auto() 361 ON = auto() 362 ONLY = auto() 363 OPERATOR = auto() 364 ORDER_BY = auto() 365 ORDER_SIBLINGS_BY = auto() 366 ORDERED = auto() 367 ORDINALITY = auto() 368 OUT = auto() 369 INOUT = auto() 370 OUTER = auto() 371 OVER = auto() 372 OVERLAPS = auto() 373 OVERWRITE = auto() 374 PACKAGE = auto() 375 PARTITION = auto() 376 PARTITION_BY = auto() 377 PERCENT = auto() 378 PIVOT = auto() 379 PLACEHOLDER = auto() 380 POLICY = auto() 381 POOL = auto() 382 POSITIONAL = auto() 383 PRAGMA = auto() 384 PREWHERE = auto() 385 PRIMARY_KEY = auto() 386 PROCEDURE = auto() 387 PROPERTIES = auto() 388 PROJECTION = auto() 389 PSEUDO_TYPE = auto() 390 PUT = auto() 391 QUALIFY = auto() 392 QUOTE = auto() 393 QDCOLON = auto() 394 RANGE = auto() 395 RECURSIVE = auto() 396 REFRESH = auto() 397 RENAME = auto() 398 REPLACE = auto() 399 RETURNING = auto() 400 REVOKE = auto() 401 REFERENCES = auto() 402 RIGHT = auto() 403 RLIKE = auto() 404 ROLE = auto() 405 ROLLBACK = auto() 406 ROLLUP = auto() 407 ROW = auto() 408 ROWS = auto() 409 RULE = auto() 410 SELECT = auto() 411 SEMI = auto() 412 SEPARATOR = auto() 413 SEQUENCE = auto() 414 SERDE_PROPERTIES = auto() 415 SET = auto() 416 SETTINGS = auto() 417 SHOW = auto() 418 SIMILAR_TO = auto() 419 SOME = auto() 420 SORT_BY = auto() 421 SOUNDS_LIKE = auto() 422 SQL_SECURITY = auto() 423 START_WITH = auto() 424 STORAGE_INTEGRATION = auto() 425 STRAIGHT_JOIN = auto() 426 STRUCT = auto() 427 SUMMARIZE = auto() 428 TABLE_SAMPLE = auto() 429 TAG = auto() 430 TEMPORARY = auto() 431 TOP = auto() 432 THEN = auto() 433 TRUE = auto() 434 TRUNCATE = auto() 435 TRIGGER = auto() 436 TYPE = auto() 437 UNCACHE = auto() 438 UNDROP = auto() 439 UNION = auto() 440 UNNEST = auto() 441 UNPIVOT = auto() 442 UPDATE = auto() 443 USE = auto() 444 USING = auto() 445 VALUES = auto() 446 VARIADIC = auto() 447 VIEW = auto() 448 SEMANTIC_VIEW = auto() 449 VOLATILE = auto() 450 VOLUME = auto() 451 WHEN = auto() 452 WHERE = auto() 453 WINDOW = auto() 454 WITH = auto() 455 UNIQUE = auto() 456 UTC_DATE = auto() 457 UTC_TIME = auto() 458 UTC_TIMESTAMP = auto() 459 OPTION = auto() 460 SINK = auto() 461 SOURCE = auto() 462 ANALYZE = auto() 463 NAMESPACE = auto() 464 EXPORT = auto() 465 466 # sentinels 467 HIVE_TOKEN_STREAM = auto() 468 SENTINEL = auto() 469 470 def __str__(self) -> str: 471 return f"TokenType.{self.name}" 472 473 474class Token: 475 # mypyc doesn't expose slots 476 _attrs: t.ClassVar[tuple[str, ...]] = ( 477 "token_type", 478 "text", 479 "line", 480 "col", 481 "start", 482 "end", 483 "comments", 484 ) 485 __slots__ = _attrs 486 487 @classmethod 488 def number(cls, number: int) -> Token: 489 """Returns a NUMBER token with `number` as its text.""" 490 return cls(TokenType.NUMBER, str(number)) 491 492 @classmethod 493 def string(cls, string: str) -> Token: 494 """Returns a STRING token with `string` as its text.""" 495 return cls(TokenType.STRING, string) 496 497 @classmethod 498 def identifier(cls, identifier: str) -> Token: 499 """Returns an IDENTIFIER token with `identifier` as its text.""" 500 return cls(TokenType.IDENTIFIER, identifier) 501 502 @classmethod 503 def var(cls, var: str) -> Token: 504 """Returns an VAR token with `var` as its text.""" 505 return cls(TokenType.VAR, var) 506 507 def __init__( 508 self, 509 token_type: TokenType, 510 text: str, 511 line: int = 1, 512 col: int = 1, 513 start: int = 0, 514 end: int = 0, 515 comments: list[str] | None = None, 516 ) -> None: 517 self.token_type = token_type 518 self.text = text 519 self.line = line 520 self.col = col 521 self.start = start 522 self.end = end 523 self.comments = [] if comments is None else comments 524 525 def __bool__(self) -> bool: 526 return self.token_type != TokenType.SENTINEL 527 528 def __repr__(self) -> str: 529 attributes = ", ".join( 530 f"{k}: TokenType.{self.token_type.name}" 531 if k == "token_type" 532 else f"{k}: {getattr(self, k)}" 533 for k in self._attrs 534 ) 535 return f"<Token {attributes}>" 536 537 538class TokenizerCore: 539 __slots__ = ( 540 "sql", 541 "size", 542 "tokens", 543 "_start", 544 "_current", 545 "_line", 546 "_col", 547 "_comments", 548 "_char", 549 "_end", 550 "_peek", 551 "_prev_token_line", 552 "single_tokens", 553 "keywords", 554 "quotes", 555 "format_strings", 556 "identifiers", 557 "comments", 558 "string_escapes", 559 "byte_string_escapes", 560 "identifier_escapes", 561 "escape_follow_chars", 562 "commands", 563 "command_prefix_tokens", 564 "nested_comments", 565 "hint_start", 566 "tokens_preceding_hint", 567 "has_bit_strings", 568 "has_hex_strings", 569 "numeric_literals", 570 "var_single_tokens", 571 "string_escapes_allowed_in_raw_strings", 572 "heredoc_tag_is_identifier", 573 "heredoc_string_alternative", 574 "keyword_trie", 575 "numbers_can_be_underscore_separated", 576 "numbers_can_have_decimals", 577 "identifiers_can_start_with_digit", 578 "unescaped_sequences", 579 "numeric_escapes", 580 "drop_unknown_escapes", 581 ) 582 583 def __init__( 584 self, 585 single_tokens: dict[str, TokenType], 586 keywords: dict[str, TokenType], 587 quotes: dict[str, str], 588 format_strings: dict[str, tuple[str, TokenType]], 589 identifiers: dict[str, str], 590 comments: dict[str, str | None], 591 string_escapes: set[str], 592 byte_string_escapes: set[str], 593 identifier_escapes: set[str], 594 escape_follow_chars: set[str], 595 commands: set[TokenType], 596 command_prefix_tokens: set[TokenType], 597 nested_comments: bool, 598 hint_start: str, 599 tokens_preceding_hint: set[TokenType], 600 has_bit_strings: bool, 601 has_hex_strings: bool, 602 numeric_literals: dict[str, str], 603 var_single_tokens: set[str], 604 string_escapes_allowed_in_raw_strings: bool, 605 heredoc_tag_is_identifier: bool, 606 heredoc_string_alternative: TokenType, 607 keyword_trie: dict, 608 numbers_can_be_underscore_separated: bool, 609 numbers_can_have_decimals: bool, 610 identifiers_can_start_with_digit: bool, 611 unescaped_sequences: dict[str, str], 612 numeric_escapes: dict[str, tuple[int, int, int, int]], 613 drop_unknown_escapes: bool, 614 ) -> None: 615 self.single_tokens = single_tokens 616 self.keywords = keywords 617 self.quotes = quotes 618 self.format_strings = format_strings 619 self.identifiers = identifiers 620 self.comments = comments 621 self.string_escapes = string_escapes 622 self.byte_string_escapes = byte_string_escapes 623 self.identifier_escapes = identifier_escapes 624 self.escape_follow_chars = escape_follow_chars 625 self.commands = commands 626 self.command_prefix_tokens = command_prefix_tokens 627 self.nested_comments = nested_comments 628 self.hint_start = hint_start 629 self.tokens_preceding_hint = tokens_preceding_hint 630 self.has_bit_strings = has_bit_strings 631 self.has_hex_strings = has_hex_strings 632 self.numeric_literals = numeric_literals 633 self.var_single_tokens = var_single_tokens 634 self.string_escapes_allowed_in_raw_strings = string_escapes_allowed_in_raw_strings 635 self.heredoc_tag_is_identifier = heredoc_tag_is_identifier 636 self.heredoc_string_alternative = heredoc_string_alternative 637 self.keyword_trie = keyword_trie 638 self.numbers_can_be_underscore_separated = numbers_can_be_underscore_separated 639 self.numbers_can_have_decimals = numbers_can_have_decimals 640 self.identifiers_can_start_with_digit = identifiers_can_start_with_digit 641 self.unescaped_sequences = unescaped_sequences 642 self.numeric_escapes = numeric_escapes 643 self.drop_unknown_escapes = drop_unknown_escapes 644 self.sql = "" 645 self.size = 0 646 self.tokens: list[Token] = [] 647 self._start = 0 648 self._current = 0 649 self._line = 1 650 self._col = 0 651 self._comments: list[str] = [] 652 self._char = "" 653 self._end = False 654 self._peek = "" 655 self._prev_token_line = -1 656 657 def reset(self) -> None: 658 self.sql = "" 659 self.size = 0 660 self.tokens = [] 661 self._start = 0 662 self._current = 0 663 self._line = 1 664 self._col = 0 665 self._comments = [] 666 self._char = "" 667 self._end = False 668 self._peek = "" 669 self._prev_token_line = -1 670 671 def tokenize(self, sql: str) -> list[Token]: 672 """Returns a list of tokens corresponding to the SQL string `sql`.""" 673 self.reset() 674 self.sql = sql 675 self.size = len(sql) 676 677 try: 678 self._scan() 679 except Exception as e: 680 start = max(self._current - 50, 0) 681 end = min(self._current + 50, self.size - 1) 682 context = self.sql[start:end] 683 raise TokenError(f"Error tokenizing '{context}'", start=start, end=end) from e 684 685 return self.tokens 686 687 def _scan(self, check_semicolon: bool = False) -> None: 688 identifiers = self.identifiers 689 digit_chars = _DIGIT_CHARS 690 691 while self.size and not self._end: 692 current = self._current 693 694 # Skip spaces here rather than iteratively calling advance() for performance reasons 695 while current < self.size: 696 char = self.sql[current] 697 698 if char == " " or char == "\t": 699 current += 1 700 else: 701 break 702 703 offset = current - self._current if current > self._current else 1 704 705 self._start = current 706 self._advance(offset) 707 708 if not self._char.isspace(): 709 if self._char in digit_chars: 710 self._scan_number() 711 elif self._char in identifiers: 712 self._scan_identifier(identifiers[self._char]) 713 else: 714 self._scan_keywords() 715 716 if check_semicolon and self._peek == ";": 717 break 718 719 if self.tokens and self._comments: 720 self.tokens[-1].comments.extend(self._comments) 721 722 def _chars(self, size: int) -> str: 723 if size == 1: 724 return self._char 725 726 start = self._current - 1 727 end = start + size 728 729 return self.sql[start:end] if end <= self.size else "" 730 731 def _advance(self, i: int = 1, alnum: bool = False) -> None: 732 char = self._char 733 734 if char == "\n" or char == "\r": 735 # Ensures we don't count an extra line if we get a \r\n line break sequence 736 if not (char == "\r" and self._peek == "\n"): 737 self._col = i 738 self._line += 1 739 else: 740 self._col += i 741 742 self._current += i 743 sql = self.sql 744 size = self.size 745 self._end = self._current >= size 746 self._char = sql[self._current - 1] 747 self._peek = "" if self._end else sql[self._current] 748 749 if alnum and self._char.isalnum(): 750 # Cache to local variables instead of attributes for better performance 751 _col = self._col 752 _current = self._current 753 _end = self._end 754 _peek = self._peek 755 756 while _peek.isalnum(): 757 _col += 1 758 _current += 1 759 _end = _current >= size 760 _peek = "" if _end else sql[_current] 761 762 self._col = _col 763 self._current = _current 764 self._end = _end 765 self._peek = _peek 766 self._char = sql[_current - 1] 767 768 @property 769 def _text(self) -> str: 770 return self.sql[self._start : self._current] 771 772 def _add(self, token_type: TokenType, text: str | None = None) -> None: 773 self._prev_token_line = self._line 774 775 if self._comments and token_type == TokenType.SEMICOLON and self.tokens: 776 self.tokens[-1].comments.extend(self._comments) 777 self._comments = [] 778 779 if text is None: 780 text = self.sql[self._start : self._current] 781 782 self.tokens.append( 783 Token( 784 token_type, 785 text=text, 786 line=self._line, 787 col=self._col, 788 start=self._start, 789 end=self._current - 1, 790 comments=self._comments, 791 ) 792 ) 793 self._comments = [] 794 795 # If we have either a semicolon or a begin token before the command's token, we'll parse 796 # whatever follows the command's token as a string 797 if ( 798 token_type in self.commands 799 and self._peek != ";" 800 and (len(self.tokens) == 1 or self.tokens[-2].token_type in self.command_prefix_tokens) 801 ): 802 start = self._current 803 tokens = len(self.tokens) 804 self._scan(check_semicolon=True) 805 self.tokens = self.tokens[:tokens] 806 text = self.sql[start : self._current].strip() 807 if text: 808 self._add(TokenType.STRING, text) 809 810 def _scan_keywords(self) -> None: 811 sql = self.sql 812 sql_size = self.size 813 single_tokens = self.single_tokens 814 char_upper = _CHAR_UPPER 815 size = 0 816 word = None 817 chars = self._char 818 char = chars 819 prev_space = False 820 skip = False 821 trie = self.keyword_trie 822 single_token = char in single_tokens 823 824 while chars: 825 if not skip: 826 sub = trie.get(char_upper.get(char, char)) 827 if sub is None: 828 break 829 trie = sub 830 if 0 in trie: 831 word = chars 832 833 end = self._current + size 834 size += 1 835 836 if end < sql_size: 837 char = sql[end] 838 single_token = single_token or char in single_tokens 839 is_space = char.isspace() 840 841 if not is_space or not prev_space: 842 if is_space: 843 char = " " 844 chars += char 845 prev_space = is_space 846 skip = False 847 else: 848 skip = True 849 else: 850 char = "" 851 break 852 853 if word: 854 if self._scan_string(word): 855 return 856 if self._scan_comment(word): 857 return 858 if prev_space or single_token or not char: 859 self._advance(size - 1) 860 word = word.upper() 861 self._add(self.keywords[word], text=word) 862 return 863 864 if self._char in single_tokens: 865 self._add(single_tokens[self._char], text=self._char) 866 return 867 868 self._scan_var() 869 870 def _scan_comment(self, comment_start: str) -> bool: 871 if comment_start not in self.comments: 872 return False 873 874 comment_start_line = self._line 875 comment_start_size = len(comment_start) 876 comment_end = self.comments[comment_start] 877 878 if comment_end: 879 # Skip the comment's start delimiter 880 self._advance(comment_start_size) 881 882 comment_count = 1 883 comment_end_size = len(comment_end) 884 nested_comments = self.nested_comments 885 886 while not self._end: 887 if self._chars(comment_end_size) == comment_end: 888 comment_count -= 1 889 if not comment_count: 890 break 891 892 self._advance(alnum=True) 893 894 # Nested comments are allowed by some dialects, e.g. databricks, duckdb, postgres 895 if ( 896 nested_comments 897 and not self._end 898 and self._chars(comment_end_size) == comment_start 899 ): 900 self._advance(comment_start_size) 901 comment_count += 1 902 903 self._comments.append(self._text[comment_start_size : -comment_end_size + 1]) 904 self._advance(comment_end_size - 1) 905 else: 906 _peek = self._peek 907 while not self._end and _peek != "\n" and _peek != "\r": 908 self._advance(alnum=True) 909 _peek = self._peek 910 self._comments.append(self._text[comment_start_size:]) 911 912 if ( 913 comment_start == self.hint_start 914 and self.tokens 915 and self.tokens[-1].token_type in self.tokens_preceding_hint 916 ): 917 self._add(TokenType.HINT) 918 919 # Leading comment is attached to the succeeding token, whilst trailing comment to the preceding. 920 # Multiple consecutive comments are preserved by appending them to the current comments list. 921 if comment_start_line == self._prev_token_line: 922 self.tokens[-1].comments.extend(self._comments) 923 self._comments = [] 924 self._prev_token_line = self._line 925 926 return True 927 928 def _scan_number(self) -> None: 929 if self._char == "0": 930 peek = _CHAR_UPPER.get(self._peek, self._peek) 931 if peek == "B" and self.has_bit_strings: 932 return self._scan_bits() 933 elif peek == "X": 934 return self._scan_hex() if self.has_hex_strings else self._add(TokenType.NUMBER) 935 936 decimal = False 937 scientific = 0 938 numbers_can_be_underscore_separated = self.numbers_can_be_underscore_separated 939 single_tokens = self.single_tokens 940 keywords = self.keywords 941 numeric_literals = self.numeric_literals 942 identifiers_can_start_with_digit = self.identifiers_can_start_with_digit 943 944 is_underscore_separated: bool = False 945 number_text: str = "" 946 numeric_literal: str = "" 947 numeric_type: TokenType | None = None 948 949 while True: 950 if self._peek in _DIGIT_CHARS: 951 # Batch consecutive digits: scan ahead to find how many 952 sql = self.sql 953 end = self._current + 1 954 size = self.size 955 while end < size and sql[end] in _DIGIT_CHARS: 956 end += 1 957 self._advance(end - self._current) 958 elif self._peek == "." and not decimal: 959 if ( 960 self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER 961 ) or not self.numbers_can_have_decimals: 962 break 963 decimal = True 964 self._advance() 965 elif self._peek in ("-", "+") and scientific == 1: 966 # Only consume +/- if followed by a digit 967 if self._current + 1 < self.size and self.sql[self._current + 1] in _DIGIT_CHARS: 968 scientific += 1 969 self._advance() 970 else: 971 break 972 elif _CHAR_UPPER.get(self._peek, self._peek) == "E" and not scientific: 973 scientific += 1 974 self._advance() 975 elif self._peek == "_" and numbers_can_be_underscore_separated: 976 is_underscore_separated = True 977 self._advance() 978 elif self._peek.isidentifier(): 979 number_text = self._text 980 981 while self._peek and not self._peek.isspace() and self._peek not in single_tokens: 982 numeric_literal += self._peek 983 self._advance() 984 985 numeric_type = keywords.get(numeric_literals.get(numeric_literal.upper(), "")) 986 987 if numeric_type: 988 break 989 elif identifiers_can_start_with_digit: 990 return self._add(TokenType.VAR) 991 992 self._advance(-len(numeric_literal)) 993 break 994 else: 995 break 996 997 number_text = number_text or self.sql[self._start : self._current] 998 999 # Normalize inputs such as 100_000 to 100000 1000 if is_underscore_separated: 1001 number_text = number_text.replace("_", "") 1002 1003 self._add(TokenType.NUMBER, number_text) 1004 1005 # Normalize inputs such as 123L to 123::BIGINT so that they're parsed as casts 1006 if numeric_type: 1007 self._add(TokenType.DCOLON, "::") 1008 self._add(numeric_type, numeric_literal) 1009 1010 def _scan_bits(self) -> None: 1011 self._advance() 1012 value = self._extract_value() 1013 try: 1014 # If `value` can't be converted to a binary, fallback to tokenizing it as an identifier 1015 int(value, 2) 1016 self._add(TokenType.BIT_STRING, value[2:]) # Drop the 0b 1017 except ValueError: 1018 self._add(TokenType.IDENTIFIER) 1019 1020 def _scan_hex(self) -> None: 1021 self._advance() 1022 value = self._extract_value() 1023 try: 1024 # If `value` can't be converted to a hex, fallback to tokenizing it as an identifier 1025 int(value, 16) 1026 self._add(TokenType.HEX_STRING, value[2:]) # Drop the 0x 1027 except ValueError: 1028 self._add(TokenType.IDENTIFIER) 1029 1030 def _extract_value(self) -> str: 1031 single_tokens = self.single_tokens 1032 1033 while True: 1034 char = self._peek.strip() 1035 if char and char not in single_tokens: 1036 self._advance(alnum=True) 1037 else: 1038 break 1039 1040 return self._text 1041 1042 def _scan_string(self, start: str) -> bool: 1043 base = None 1044 token_type = TokenType.STRING 1045 1046 if start in self.quotes: 1047 end = self.quotes[start] 1048 elif start in self.format_strings: 1049 end, token_type = self.format_strings[start] 1050 1051 if token_type == TokenType.HEX_STRING: 1052 base = 16 1053 elif token_type == TokenType.BIT_STRING: 1054 base = 2 1055 elif token_type == TokenType.HEREDOC_STRING: 1056 self._advance() 1057 1058 if self._char == end: 1059 tag = "" 1060 else: 1061 tag = self._extract_string( 1062 end, 1063 raw_string=True, 1064 raise_unmatched=not self.heredoc_tag_is_identifier, 1065 ) 1066 1067 if ( 1068 tag 1069 and self.heredoc_tag_is_identifier 1070 and (self._end or tag.isdigit() or any(c.isspace() for c in tag)) 1071 ): 1072 if not self._end: 1073 self._advance(-1) 1074 1075 self._advance(-len(tag)) 1076 self._add(self.heredoc_string_alternative) 1077 return True 1078 1079 end = f"{start}{tag}{end}" 1080 else: 1081 return False 1082 1083 self._advance(len(start)) 1084 text = self._extract_string( 1085 end, 1086 escapes=( 1087 self.byte_string_escapes 1088 if token_type == TokenType.BYTE_STRING 1089 else self.string_escapes 1090 ), 1091 raw_string=token_type == TokenType.RAW_STRING, 1092 ) 1093 1094 if base and text: 1095 try: 1096 int(text, base) 1097 except Exception: 1098 raise TokenError( 1099 f"Numeric string contains invalid characters from {self._line}:{self._start}" 1100 ) 1101 1102 self._add(token_type, text) 1103 return True 1104 1105 def _scan_identifier(self, identifier_end: str) -> None: 1106 self._advance() 1107 text = self._extract_string( 1108 identifier_end, escapes=self.identifier_escapes | {identifier_end} 1109 ) 1110 self._add(TokenType.IDENTIFIER, text) 1111 1112 def _scan_var(self) -> None: 1113 var_single_tokens = self.var_single_tokens 1114 single_tokens = self.single_tokens 1115 1116 while True: 1117 peek = self._peek 1118 if not peek or peek.isspace(): 1119 break 1120 if peek not in var_single_tokens and peek in single_tokens: 1121 break 1122 self._advance(alnum=True) 1123 1124 self._add( 1125 TokenType.VAR 1126 if self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER 1127 else self.keywords.get(self.sql[self._start : self._current].upper(), TokenType.VAR) 1128 ) 1129 1130 def _read_numeric_escape( 1131 self, start: int, base: int, min_digits: int, max_digits: int, max_value: int 1132 ) -> tuple[int, int]: 1133 """ 1134 Reads up to `max_digits` digits from `start`, stopping before the value exceeds `max_value`. 1135 Returns the value and the end index, or -1 as the value if fewer than `min_digits` were read. 1136 """ 1137 sql = self.sql 1138 value = 0 1139 end = start 1140 limit = min(start + max_digits, self.size) 1141 1142 while end < limit: 1143 digit = _DIGIT_VALUES.get(sql[end], 16) 1144 if digit >= base or value * base + digit > max_value: 1145 break 1146 value = value * base + digit 1147 end += 1 1148 1149 return (value, end) if end - start >= min_digits else (-1, end) 1150 1151 def _scan_numeric_escape(self) -> str: 1152 """ 1153 Decodes the numeric escape sequence starting at the current char, which is a backslash, 1154 according to `numeric_escapes`. Returns an empty string if nothing is decoded. 1155 """ 1156 sql = self.sql 1157 start = self._current 1158 peek = self._peek 1159 1160 # Octal escapes start with a digit (e.g. \101), the others with a letter (e.g. \x41) 1161 is_octal = peek in _OCTAL_CHARS 1162 spec = self.numeric_escapes.get("0" if is_octal else peek) 1163 1164 if spec: 1165 base, min_digits, max_digits, max_value = spec 1166 value, end = self._read_numeric_escape( 1167 start if is_octal else start + 1, base, min_digits, max_digits, max_value 1168 ) 1169 1170 # A UTF-16 surrogate is only valid as a high half (D800-DBFF) followed by an escaped 1171 # low half (DC00-DFFF), e.g. \uD83D\uDE00 is U+1F600 1172 if 0xD800 <= value <= 0xDFFF: 1173 low, low_end = -1, end 1174 if value <= 0xDBFF and sql[end : end + 2] == "\\" + peek: 1175 low, low_end = self._read_numeric_escape( 1176 end + 2, base, min_digits, max_digits, max_value 1177 ) 1178 1179 if 0xDC00 <= low <= 0xDFFF: 1180 # Combine the two halves into a single code point (standard formula) 1181 value = 0x10000 + ((value - 0xD800) << 10) + (low - 0xDC00) 1182 end = low_end 1183 else: 1184 # A lone surrogate isn't a character, so the escape isn't decoded 1185 value = -1 1186 1187 # -1 means the escape isn't decoded, e.g. because it has too few digits. An escape at the 1188 # end of the input is left to the caller, which reports the unterminated string 1189 if value >= 0 and end < self.size: 1190 # Move past the whole escape, from the backslash through its last digit 1191 self._advance(end - start + 1) 1192 return chr(value) 1193 1194 return "" 1195 1196 def _extract_string( 1197 self, 1198 delimiter: str, 1199 escapes: set[str] | None = None, 1200 raw_string: bool = False, 1201 raise_unmatched: bool = True, 1202 ) -> str: 1203 text = "" 1204 delim_size = len(delimiter) 1205 escapes = self.string_escapes if escapes is None else escapes 1206 unescaped_sequences = self.unescaped_sequences 1207 escape_follow_chars = self.escape_follow_chars 1208 numeric_escapes = self.numeric_escapes 1209 drop_unknown_escapes = self.drop_unknown_escapes 1210 string_escapes_allowed_in_raw_strings = self.string_escapes_allowed_in_raw_strings 1211 quotes = self.quotes 1212 sql = self.sql 1213 1214 # use str.find() when the string is simple... no \ or other escapes 1215 if delim_size == 1: 1216 pos = self._current - 1 1217 end = sql.find(delimiter, pos) 1218 1219 if ( 1220 # the closing delimiter was found 1221 end != -1 1222 # there's no doubled delimiter (e.g. '' escape), or the delimiter isn't an escape char 1223 and (end + 1 >= self.size or sql[end + 1] != delimiter or delimiter not in escapes) 1224 # no backslash in the string that would need escape processing 1225 and (not (unescaped_sequences or "\\" in escapes) or sql.find("\\", pos, end) == -1) 1226 ): 1227 newlines = sql.count("\n", pos, end) 1228 if newlines: 1229 self._line += newlines 1230 self._col = end - sql.rfind("\n", pos, end) 1231 else: 1232 self._col += end - pos 1233 1234 self._current = end + 1 1235 self._end = self._current >= self.size 1236 self._char = sql[end] 1237 self._peek = "" if self._end else sql[self._current] 1238 return sql[pos:end] 1239 1240 while True: 1241 backslash_escape = not raw_string and self._char == "\\" and "\\" in escapes 1242 1243 # Numeric escapes come first, since they may overlap fixed sequences (e.g. \000 vs \0) 1244 if backslash_escape and numeric_escapes: 1245 decoded = self._scan_numeric_escape() 1246 if decoded: 1247 text += decoded 1248 continue 1249 1250 # An escape at the end of the input is left to the checks below, which report the 1251 # unterminated string 1252 if ( 1253 not raw_string 1254 and unescaped_sequences 1255 and self._char in escapes 1256 and self._current + 1 < self.size 1257 ): 1258 unescaped_sequence = unescaped_sequences.get(self._char + self._peek) 1259 if unescaped_sequence: 1260 # Advance one char at a time so that line numbers are updated for a newline 1261 if self._peek in "\n\r": 1262 self._advance() 1263 self._advance() 1264 else: 1265 self._advance(2) 1266 text += unescaped_sequence 1267 continue 1268 1269 # Not decoded: drop the backslash if configured, unless the string is unterminated 1270 if backslash_escape and drop_unknown_escapes and self._current + 1 < self.size: 1271 # Advance one char at a time so that line numbers are updated if `peek` is a newline 1272 peek = self._peek 1273 self._advance() 1274 self._advance() 1275 text += peek 1276 continue 1277 1278 is_valid_custom_escape = ( 1279 escape_follow_chars and self._char == "\\" and self._peek not in escape_follow_chars 1280 ) 1281 1282 # An escaped quote before the closing delimiter (e.g. \" in """a\"""") must be 1283 # consumed here, otherwise it'd be picked up by the delimiter check below and 1284 # terminate the string early. This is only relevant for multi-char delimiters 1285 # made up of quote chars, e.g. it shouldn't apply to Snowflake's $$ strings 1286 escaped_delimiter = self._peek == delimiter or ( 1287 delim_size > 1 and self._peek == delimiter[0] and self._peek in quotes 1288 ) 1289 1290 if ( 1291 (string_escapes_allowed_in_raw_strings or not raw_string) 1292 and self._char in escapes 1293 and (escaped_delimiter or self._peek in escapes or is_valid_custom_escape) 1294 and (self._char not in quotes or self._char == self._peek) 1295 ): 1296 if escaped_delimiter: 1297 text += self._peek if not raw_string else self._char + self._peek 1298 elif is_valid_custom_escape and self._char != self._peek: 1299 text += self._peek 1300 else: 1301 text += self._char + self._peek 1302 1303 if self._current + 1 < self.size: 1304 self._advance(2) 1305 else: 1306 raise TokenError(f"Missing {delimiter} from {self._line}:{self._current}") 1307 else: 1308 if self._chars(delim_size) == delimiter: 1309 if delim_size > 1: 1310 self._advance(delim_size - 1) 1311 break 1312 1313 if self._end: 1314 if not raise_unmatched: 1315 return text + self._char 1316 1317 raise TokenError(f"Missing {delimiter} from {self._line}:{self._start}") 1318 1319 current = self._current - 1 1320 self._advance(alnum=True) 1321 text += sql[current : self._current - 1] 1322 1323 return text
class
TokenType(enum.IntEnum):
16class TokenType(IntEnum): 17 L_PAREN = auto() 18 R_PAREN = auto() 19 L_BRACKET = auto() 20 R_BRACKET = auto() 21 L_BRACE = auto() 22 R_BRACE = auto() 23 COMMA = auto() 24 DOT = auto() 25 DASH = auto() 26 PLUS = auto() 27 COLON = auto() 28 DOTCOLON = auto() 29 DOTCARET = auto() 30 DCOLON = auto() 31 DCOLONDOLLAR = auto() 32 DCOLONPERCENT = auto() 33 DCOLONQMARK = auto() 34 DQMARK = auto() 35 SEMICOLON = auto() 36 STAR = auto() 37 BACKSLASH = auto() 38 SLASH = auto() 39 LT = auto() 40 LTE = auto() 41 GT = auto() 42 GTE = auto() 43 NOT = auto() 44 EQ = auto() 45 NEQ = auto() 46 NULLSAFE_EQ = auto() 47 COLON_EQ = auto() 48 COLON_GT = auto() 49 NCOLON_GT = auto() 50 AND = auto() 51 OR = auto() 52 AMP = auto() 53 DPIPE = auto() 54 PIPE_GT = auto() 55 PIPE = auto() 56 PIPE_SLASH = auto() 57 DPIPE_SLASH = auto() 58 CARET = auto() 59 CARET_AT = auto() 60 TILDE = auto() 61 ARROW = auto() 62 DARROW = auto() 63 FARROW = auto() 64 HASH = auto() 65 HASH_ARROW = auto() 66 DHASH_ARROW = auto() 67 LR_ARROW = auto() 68 LLRR_ARROW = auto() 69 DAT = auto() 70 AT_QMARK = auto() 71 LT_AT = auto() 72 AT_GT = auto() 73 DOLLAR = auto() 74 PARAMETER = auto() 75 SESSION = auto() 76 SESSION_PARAMETER = auto() 77 SESSION_USER = auto() 78 DAMP = auto() 79 AMP_LT = auto() 80 AMP_GT = auto() 81 ADJACENT = auto() 82 XOR = auto() 83 DSTAR = auto() 84 QMARK_AMP = auto() 85 QMARK_PIPE = auto() 86 HASH_DASH = auto() 87 EXCLAMATION = auto() 88 89 URI_START = auto() 90 91 BLOCK_START = auto() 92 BLOCK_END = auto() 93 94 SPACE = auto() 95 BREAK = auto() 96 97 STRING = auto() 98 NUMBER = auto() 99 IDENTIFIER = auto() 100 DATABASE = auto() 101 COLUMN = auto() 102 COLUMN_DEF = auto() 103 SCHEMA = auto() 104 TABLE = auto() 105 WAREHOUSE = auto() 106 STAGE = auto() 107 STREAM = auto() 108 STREAMLIT = auto() 109 VAR = auto() 110 BIT_STRING = auto() 111 HEX_STRING = auto() 112 BYTE_STRING = auto() 113 NATIONAL_STRING = auto() 114 RAW_STRING = auto() 115 HEREDOC_STRING = auto() 116 UNICODE_STRING = auto() 117 118 # types 119 BIT = auto() 120 BOOLEAN = auto() 121 TINYINT = auto() 122 UTINYINT = auto() 123 SMALLINT = auto() 124 USMALLINT = auto() 125 MEDIUMINT = auto() 126 UMEDIUMINT = auto() 127 INT = auto() 128 UINT = auto() 129 BIGINT = auto() 130 UBIGINT = auto() 131 BIGNUM = auto() 132 INT128 = auto() 133 UINT128 = auto() 134 INT256 = auto() 135 UINT256 = auto() 136 FLOAT = auto() 137 DOUBLE = auto() 138 UDOUBLE = auto() 139 DECIMAL = auto() 140 DECIMAL32 = auto() 141 DECIMAL64 = auto() 142 DECIMAL128 = auto() 143 DECIMAL256 = auto() 144 DECFLOAT = auto() 145 UDECIMAL = auto() 146 BIGDECIMAL = auto() 147 CHAR = auto() 148 NCHAR = auto() 149 VARCHAR = auto() 150 NVARCHAR = auto() 151 BPCHAR = auto() 152 TEXT = auto() 153 MEDIUMTEXT = auto() 154 LONGTEXT = auto() 155 BLOB = auto() 156 MEDIUMBLOB = auto() 157 LONGBLOB = auto() 158 TINYBLOB = auto() 159 TINYTEXT = auto() 160 NAME = auto() 161 BINARY = auto() 162 VARBINARY = auto() 163 JSON = auto() 164 JSONB = auto() 165 TIME = auto() 166 TIMETZ = auto() 167 TIME_NS = auto() 168 TIMESTAMP = auto() 169 TIMESTAMPTZ = auto() 170 TIMESTAMPLTZ = auto() 171 TIMESTAMPNTZ = auto() 172 TIMESTAMP_S = auto() 173 TIMESTAMP_MS = auto() 174 TIMESTAMP_NS = auto() 175 DATETIME = auto() 176 DATETIME2 = auto() 177 DATETIME64 = auto() 178 SMALLDATETIME = auto() 179 DATE = auto() 180 DATE32 = auto() 181 INT4RANGE = auto() 182 INT4MULTIRANGE = auto() 183 INT8RANGE = auto() 184 INT8MULTIRANGE = auto() 185 NUMRANGE = auto() 186 NUMMULTIRANGE = auto() 187 TSRANGE = auto() 188 TSMULTIRANGE = auto() 189 TSTZRANGE = auto() 190 TSTZMULTIRANGE = auto() 191 DATERANGE = auto() 192 DATEMULTIRANGE = auto() 193 UUID = auto() 194 GEOGRAPHY = auto() 195 GEOGRAPHYPOINT = auto() 196 NULLABLE = auto() 197 GEOMETRY = auto() 198 POINT = auto() 199 RING = auto() 200 LINESTRING = auto() 201 LOCALTIME = auto() 202 LOCALTIMESTAMP = auto() 203 SYSTIMESTAMP = auto() 204 MULTILINESTRING = auto() 205 POLYGON = auto() 206 MULTIPOLYGON = auto() 207 HLLSKETCH = auto() 208 HSTORE = auto() 209 SUPER = auto() 210 SERIAL = auto() 211 SMALLSERIAL = auto() 212 BIGSERIAL = auto() 213 XML = auto() 214 YEAR = auto() 215 USERDEFINED = auto() 216 MONEY = auto() 217 SMALLMONEY = auto() 218 ROWVERSION = auto() 219 IMAGE = auto() 220 VARIANT = auto() 221 OBJECT = auto() 222 INET = auto() 223 IPADDRESS = auto() 224 IPPREFIX = auto() 225 IPV4 = auto() 226 IPV6 = auto() 227 ENUM = auto() 228 ENUM8 = auto() 229 ENUM16 = auto() 230 FIXEDSTRING = auto() 231 LOWCARDINALITY = auto() 232 NESTED = auto() 233 AGGREGATEFUNCTION = auto() 234 SIMPLEAGGREGATEFUNCTION = auto() 235 TDIGEST = auto() 236 UNKNOWN = auto() 237 VECTOR = auto() 238 DYNAMIC = auto() 239 VOID = auto() 240 241 # keywords 242 ALIAS = auto() 243 ALTER = auto() 244 ALL = auto() 245 ANTI = auto() 246 ANY = auto() 247 APPLY = auto() 248 ARRAY = auto() 249 ASC = auto() 250 ASOF = auto() 251 ATTACH = auto() 252 AUTO_INCREMENT = auto() 253 BEGIN = auto() 254 BETWEEN = auto() 255 BULK_COLLECT_INTO = auto() 256 CACHE = auto() 257 CASE = auto() 258 CHARACTER_SET = auto() 259 CLUSTER_BY = auto() 260 COLLATE = auto() 261 COMMAND = auto() 262 COMMENT = auto() 263 COMMIT = auto() 264 CONNECT_BY = auto() 265 CONSTRAINT = auto() 266 COPY = auto() 267 CREATE = auto() 268 CROSS = auto() 269 CUBE = auto() 270 CURRENT_DATE = auto() 271 CURRENT_DATETIME = auto() 272 CURRENT_SCHEMA = auto() 273 CURRENT_TIME = auto() 274 CURRENT_TIMESTAMP = auto() 275 CURRENT_USER = auto() 276 CURRENT_USER_ID = auto() 277 CURRENT_ROLE = auto() 278 CURRENT_CATALOG = auto() 279 DECLARE = auto() 280 DEFAULT = auto() 281 DELETE = auto() 282 DESC = auto() 283 DESCRIBE = auto() 284 DETACH = auto() 285 DICTIONARY = auto() 286 DISTINCT = auto() 287 DISTRIBUTE_BY = auto() 288 DIV = auto() 289 DROP = auto() 290 ELSE = auto() 291 END = auto() 292 ESCAPE = auto() 293 EXCEPT = auto() 294 EXECUTE = auto() 295 EXISTS = auto() 296 FALSE = auto() 297 FETCH = auto() 298 FILE = auto() 299 FILE_FORMAT = auto() 300 FILTER = auto() 301 FINAL = auto() 302 FIRST = auto() 303 FOR = auto() 304 FORCE = auto() 305 FOREIGN_KEY = auto() 306 FORMAT = auto() 307 FROM = auto() 308 FULL = auto() 309 FUNCTION = auto() 310 GET = auto() 311 GLOB = auto() 312 GLOBAL = auto() 313 GRANT = auto() 314 GROUP_BY = auto() 315 GROUPING_SETS = auto() 316 HAVING = auto() 317 HINT = auto() 318 IGNORE = auto() 319 ILIKE = auto() 320 IN = auto() 321 INDEX = auto() 322 INDEXED_BY = auto() 323 INNER = auto() 324 INSERT = auto() 325 INSTALL = auto() 326 INTEGRATION = auto() 327 INTERSECT = auto() 328 INTERVAL = auto() 329 INTO = auto() 330 INTRODUCER = auto() 331 IRLIKE = auto() 332 IS = auto() 333 ISNULL = auto() 334 JOIN = auto() 335 JOIN_MARKER = auto() 336 KEEP = auto() 337 KEY = auto() 338 KILL = auto() 339 LANGUAGE = auto() 340 LATERAL = auto() 341 LEFT = auto() 342 LIKE = auto() 343 LIMIT = auto() 344 LIST = auto() 345 LOAD = auto() 346 LOCK = auto() 347 MAP = auto() 348 MATCH = auto() 349 MATCH_CONDITION = auto() 350 MATCH_RECOGNIZE = auto() 351 MEMBER_OF = auto() 352 MERGE = auto() 353 MOD = auto() 354 MODEL = auto() 355 NATURAL = auto() 356 NEXT = auto() 357 NOTHING = auto() 358 NOTNULL = auto() 359 NULL = auto() 360 OBJECT_IDENTIFIER = auto() 361 OFFSET = auto() 362 ON = auto() 363 ONLY = auto() 364 OPERATOR = auto() 365 ORDER_BY = auto() 366 ORDER_SIBLINGS_BY = auto() 367 ORDERED = auto() 368 ORDINALITY = auto() 369 OUT = auto() 370 INOUT = auto() 371 OUTER = auto() 372 OVER = auto() 373 OVERLAPS = auto() 374 OVERWRITE = auto() 375 PACKAGE = auto() 376 PARTITION = auto() 377 PARTITION_BY = auto() 378 PERCENT = auto() 379 PIVOT = auto() 380 PLACEHOLDER = auto() 381 POLICY = auto() 382 POOL = auto() 383 POSITIONAL = auto() 384 PRAGMA = auto() 385 PREWHERE = auto() 386 PRIMARY_KEY = auto() 387 PROCEDURE = auto() 388 PROPERTIES = auto() 389 PROJECTION = auto() 390 PSEUDO_TYPE = auto() 391 PUT = auto() 392 QUALIFY = auto() 393 QUOTE = auto() 394 QDCOLON = auto() 395 RANGE = auto() 396 RECURSIVE = auto() 397 REFRESH = auto() 398 RENAME = auto() 399 REPLACE = auto() 400 RETURNING = auto() 401 REVOKE = auto() 402 REFERENCES = auto() 403 RIGHT = auto() 404 RLIKE = auto() 405 ROLE = auto() 406 ROLLBACK = auto() 407 ROLLUP = auto() 408 ROW = auto() 409 ROWS = auto() 410 RULE = auto() 411 SELECT = auto() 412 SEMI = auto() 413 SEPARATOR = auto() 414 SEQUENCE = auto() 415 SERDE_PROPERTIES = auto() 416 SET = auto() 417 SETTINGS = auto() 418 SHOW = auto() 419 SIMILAR_TO = auto() 420 SOME = auto() 421 SORT_BY = auto() 422 SOUNDS_LIKE = auto() 423 SQL_SECURITY = auto() 424 START_WITH = auto() 425 STORAGE_INTEGRATION = auto() 426 STRAIGHT_JOIN = auto() 427 STRUCT = auto() 428 SUMMARIZE = auto() 429 TABLE_SAMPLE = auto() 430 TAG = auto() 431 TEMPORARY = auto() 432 TOP = auto() 433 THEN = auto() 434 TRUE = auto() 435 TRUNCATE = auto() 436 TRIGGER = auto() 437 TYPE = auto() 438 UNCACHE = auto() 439 UNDROP = auto() 440 UNION = auto() 441 UNNEST = auto() 442 UNPIVOT = auto() 443 UPDATE = auto() 444 USE = auto() 445 USING = auto() 446 VALUES = auto() 447 VARIADIC = auto() 448 VIEW = auto() 449 SEMANTIC_VIEW = auto() 450 VOLATILE = auto() 451 VOLUME = auto() 452 WHEN = auto() 453 WHERE = auto() 454 WINDOW = auto() 455 WITH = auto() 456 UNIQUE = auto() 457 UTC_DATE = auto() 458 UTC_TIME = auto() 459 UTC_TIMESTAMP = auto() 460 OPTION = auto() 461 SINK = auto() 462 SOURCE = auto() 463 ANALYZE = auto() 464 NAMESPACE = auto() 465 EXPORT = auto() 466 467 # sentinels 468 HIVE_TOKEN_STREAM = auto() 469 SENTINEL = auto() 470 471 def __str__(self) -> str: 472 return f"TokenType.{self.name}"
An enumeration.
L_PAREN =
<TokenType.L_PAREN: 1>
R_PAREN =
<TokenType.R_PAREN: 2>
L_BRACKET =
<TokenType.L_BRACKET: 3>
R_BRACKET =
<TokenType.R_BRACKET: 4>
L_BRACE =
<TokenType.L_BRACE: 5>
R_BRACE =
<TokenType.R_BRACE: 6>
COMMA =
<TokenType.COMMA: 7>
DOT =
<TokenType.DOT: 8>
DASH =
<TokenType.DASH: 9>
PLUS =
<TokenType.PLUS: 10>
COLON =
<TokenType.COLON: 11>
DOTCOLON =
<TokenType.DOTCOLON: 12>
DOTCARET =
<TokenType.DOTCARET: 13>
DCOLON =
<TokenType.DCOLON: 14>
DCOLONDOLLAR =
<TokenType.DCOLONDOLLAR: 15>
DCOLONPERCENT =
<TokenType.DCOLONPERCENT: 16>
DCOLONQMARK =
<TokenType.DCOLONQMARK: 17>
DQMARK =
<TokenType.DQMARK: 18>
SEMICOLON =
<TokenType.SEMICOLON: 19>
STAR =
<TokenType.STAR: 20>
BACKSLASH =
<TokenType.BACKSLASH: 21>
SLASH =
<TokenType.SLASH: 22>
LT =
<TokenType.LT: 23>
LTE =
<TokenType.LTE: 24>
GT =
<TokenType.GT: 25>
GTE =
<TokenType.GTE: 26>
NOT =
<TokenType.NOT: 27>
EQ =
<TokenType.EQ: 28>
NEQ =
<TokenType.NEQ: 29>
NULLSAFE_EQ =
<TokenType.NULLSAFE_EQ: 30>
COLON_EQ =
<TokenType.COLON_EQ: 31>
COLON_GT =
<TokenType.COLON_GT: 32>
NCOLON_GT =
<TokenType.NCOLON_GT: 33>
AND =
<TokenType.AND: 34>
OR =
<TokenType.OR: 35>
AMP =
<TokenType.AMP: 36>
DPIPE =
<TokenType.DPIPE: 37>
PIPE_GT =
<TokenType.PIPE_GT: 38>
PIPE =
<TokenType.PIPE: 39>
PIPE_SLASH =
<TokenType.PIPE_SLASH: 40>
DPIPE_SLASH =
<TokenType.DPIPE_SLASH: 41>
CARET =
<TokenType.CARET: 42>
CARET_AT =
<TokenType.CARET_AT: 43>
TILDE =
<TokenType.TILDE: 44>
ARROW =
<TokenType.ARROW: 45>
DARROW =
<TokenType.DARROW: 46>
FARROW =
<TokenType.FARROW: 47>
HASH =
<TokenType.HASH: 48>
HASH_ARROW =
<TokenType.HASH_ARROW: 49>
DHASH_ARROW =
<TokenType.DHASH_ARROW: 50>
LR_ARROW =
<TokenType.LR_ARROW: 51>
LLRR_ARROW =
<TokenType.LLRR_ARROW: 52>
DAT =
<TokenType.DAT: 53>
AT_QMARK =
<TokenType.AT_QMARK: 54>
LT_AT =
<TokenType.LT_AT: 55>
AT_GT =
<TokenType.AT_GT: 56>
DOLLAR =
<TokenType.DOLLAR: 57>
PARAMETER =
<TokenType.PARAMETER: 58>
SESSION =
<TokenType.SESSION: 59>
SESSION_PARAMETER =
<TokenType.SESSION_PARAMETER: 60>
SESSION_USER =
<TokenType.SESSION_USER: 61>
DAMP =
<TokenType.DAMP: 62>
AMP_LT =
<TokenType.AMP_LT: 63>
AMP_GT =
<TokenType.AMP_GT: 64>
ADJACENT =
<TokenType.ADJACENT: 65>
XOR =
<TokenType.XOR: 66>
DSTAR =
<TokenType.DSTAR: 67>
QMARK_AMP =
<TokenType.QMARK_AMP: 68>
QMARK_PIPE =
<TokenType.QMARK_PIPE: 69>
HASH_DASH =
<TokenType.HASH_DASH: 70>
EXCLAMATION =
<TokenType.EXCLAMATION: 71>
URI_START =
<TokenType.URI_START: 72>
BLOCK_START =
<TokenType.BLOCK_START: 73>
BLOCK_END =
<TokenType.BLOCK_END: 74>
SPACE =
<TokenType.SPACE: 75>
BREAK =
<TokenType.BREAK: 76>
STRING =
<TokenType.STRING: 77>
NUMBER =
<TokenType.NUMBER: 78>
IDENTIFIER =
<TokenType.IDENTIFIER: 79>
DATABASE =
<TokenType.DATABASE: 80>
COLUMN =
<TokenType.COLUMN: 81>
COLUMN_DEF =
<TokenType.COLUMN_DEF: 82>
SCHEMA =
<TokenType.SCHEMA: 83>
TABLE =
<TokenType.TABLE: 84>
WAREHOUSE =
<TokenType.WAREHOUSE: 85>
STAGE =
<TokenType.STAGE: 86>
STREAM =
<TokenType.STREAM: 87>
STREAMLIT =
<TokenType.STREAMLIT: 88>
VAR =
<TokenType.VAR: 89>
BIT_STRING =
<TokenType.BIT_STRING: 90>
HEX_STRING =
<TokenType.HEX_STRING: 91>
BYTE_STRING =
<TokenType.BYTE_STRING: 92>
NATIONAL_STRING =
<TokenType.NATIONAL_STRING: 93>
RAW_STRING =
<TokenType.RAW_STRING: 94>
HEREDOC_STRING =
<TokenType.HEREDOC_STRING: 95>
UNICODE_STRING =
<TokenType.UNICODE_STRING: 96>
BIT =
<TokenType.BIT: 97>
BOOLEAN =
<TokenType.BOOLEAN: 98>
TINYINT =
<TokenType.TINYINT: 99>
UTINYINT =
<TokenType.UTINYINT: 100>
SMALLINT =
<TokenType.SMALLINT: 101>
USMALLINT =
<TokenType.USMALLINT: 102>
MEDIUMINT =
<TokenType.MEDIUMINT: 103>
UMEDIUMINT =
<TokenType.UMEDIUMINT: 104>
INT =
<TokenType.INT: 105>
UINT =
<TokenType.UINT: 106>
BIGINT =
<TokenType.BIGINT: 107>
UBIGINT =
<TokenType.UBIGINT: 108>
BIGNUM =
<TokenType.BIGNUM: 109>
INT128 =
<TokenType.INT128: 110>
UINT128 =
<TokenType.UINT128: 111>
INT256 =
<TokenType.INT256: 112>
UINT256 =
<TokenType.UINT256: 113>
FLOAT =
<TokenType.FLOAT: 114>
DOUBLE =
<TokenType.DOUBLE: 115>
UDOUBLE =
<TokenType.UDOUBLE: 116>
DECIMAL =
<TokenType.DECIMAL: 117>
DECIMAL32 =
<TokenType.DECIMAL32: 118>
DECIMAL64 =
<TokenType.DECIMAL64: 119>
DECIMAL128 =
<TokenType.DECIMAL128: 120>
DECIMAL256 =
<TokenType.DECIMAL256: 121>
DECFLOAT =
<TokenType.DECFLOAT: 122>
UDECIMAL =
<TokenType.UDECIMAL: 123>
BIGDECIMAL =
<TokenType.BIGDECIMAL: 124>
CHAR =
<TokenType.CHAR: 125>
NCHAR =
<TokenType.NCHAR: 126>
VARCHAR =
<TokenType.VARCHAR: 127>
NVARCHAR =
<TokenType.NVARCHAR: 128>
BPCHAR =
<TokenType.BPCHAR: 129>
TEXT =
<TokenType.TEXT: 130>
MEDIUMTEXT =
<TokenType.MEDIUMTEXT: 131>
LONGTEXT =
<TokenType.LONGTEXT: 132>
BLOB =
<TokenType.BLOB: 133>
MEDIUMBLOB =
<TokenType.MEDIUMBLOB: 134>
LONGBLOB =
<TokenType.LONGBLOB: 135>
TINYBLOB =
<TokenType.TINYBLOB: 136>
TINYTEXT =
<TokenType.TINYTEXT: 137>
NAME =
<TokenType.NAME: 138>
BINARY =
<TokenType.BINARY: 139>
VARBINARY =
<TokenType.VARBINARY: 140>
JSON =
<TokenType.JSON: 141>
JSONB =
<TokenType.JSONB: 142>
TIME =
<TokenType.TIME: 143>
TIMETZ =
<TokenType.TIMETZ: 144>
TIME_NS =
<TokenType.TIME_NS: 145>
TIMESTAMP =
<TokenType.TIMESTAMP: 146>
TIMESTAMPTZ =
<TokenType.TIMESTAMPTZ: 147>
TIMESTAMPLTZ =
<TokenType.TIMESTAMPLTZ: 148>
TIMESTAMPNTZ =
<TokenType.TIMESTAMPNTZ: 149>
TIMESTAMP_S =
<TokenType.TIMESTAMP_S: 150>
TIMESTAMP_MS =
<TokenType.TIMESTAMP_MS: 151>
TIMESTAMP_NS =
<TokenType.TIMESTAMP_NS: 152>
DATETIME =
<TokenType.DATETIME: 153>
DATETIME2 =
<TokenType.DATETIME2: 154>
DATETIME64 =
<TokenType.DATETIME64: 155>
SMALLDATETIME =
<TokenType.SMALLDATETIME: 156>
DATE =
<TokenType.DATE: 157>
DATE32 =
<TokenType.DATE32: 158>
INT4RANGE =
<TokenType.INT4RANGE: 159>
INT4MULTIRANGE =
<TokenType.INT4MULTIRANGE: 160>
INT8RANGE =
<TokenType.INT8RANGE: 161>
INT8MULTIRANGE =
<TokenType.INT8MULTIRANGE: 162>
NUMRANGE =
<TokenType.NUMRANGE: 163>
NUMMULTIRANGE =
<TokenType.NUMMULTIRANGE: 164>
TSRANGE =
<TokenType.TSRANGE: 165>
TSMULTIRANGE =
<TokenType.TSMULTIRANGE: 166>
TSTZRANGE =
<TokenType.TSTZRANGE: 167>
TSTZMULTIRANGE =
<TokenType.TSTZMULTIRANGE: 168>
DATERANGE =
<TokenType.DATERANGE: 169>
DATEMULTIRANGE =
<TokenType.DATEMULTIRANGE: 170>
UUID =
<TokenType.UUID: 171>
GEOGRAPHY =
<TokenType.GEOGRAPHY: 172>
GEOGRAPHYPOINT =
<TokenType.GEOGRAPHYPOINT: 173>
NULLABLE =
<TokenType.NULLABLE: 174>
GEOMETRY =
<TokenType.GEOMETRY: 175>
POINT =
<TokenType.POINT: 176>
RING =
<TokenType.RING: 177>
LINESTRING =
<TokenType.LINESTRING: 178>
LOCALTIME =
<TokenType.LOCALTIME: 179>
LOCALTIMESTAMP =
<TokenType.LOCALTIMESTAMP: 180>
SYSTIMESTAMP =
<TokenType.SYSTIMESTAMP: 181>
MULTILINESTRING =
<TokenType.MULTILINESTRING: 182>
POLYGON =
<TokenType.POLYGON: 183>
MULTIPOLYGON =
<TokenType.MULTIPOLYGON: 184>
HLLSKETCH =
<TokenType.HLLSKETCH: 185>
HSTORE =
<TokenType.HSTORE: 186>
SUPER =
<TokenType.SUPER: 187>
SERIAL =
<TokenType.SERIAL: 188>
SMALLSERIAL =
<TokenType.SMALLSERIAL: 189>
BIGSERIAL =
<TokenType.BIGSERIAL: 190>
XML =
<TokenType.XML: 191>
YEAR =
<TokenType.YEAR: 192>
USERDEFINED =
<TokenType.USERDEFINED: 193>
MONEY =
<TokenType.MONEY: 194>
SMALLMONEY =
<TokenType.SMALLMONEY: 195>
ROWVERSION =
<TokenType.ROWVERSION: 196>
IMAGE =
<TokenType.IMAGE: 197>
VARIANT =
<TokenType.VARIANT: 198>
OBJECT =
<TokenType.OBJECT: 199>
INET =
<TokenType.INET: 200>
IPADDRESS =
<TokenType.IPADDRESS: 201>
IPPREFIX =
<TokenType.IPPREFIX: 202>
IPV4 =
<TokenType.IPV4: 203>
IPV6 =
<TokenType.IPV6: 204>
ENUM =
<TokenType.ENUM: 205>
ENUM8 =
<TokenType.ENUM8: 206>
ENUM16 =
<TokenType.ENUM16: 207>
FIXEDSTRING =
<TokenType.FIXEDSTRING: 208>
LOWCARDINALITY =
<TokenType.LOWCARDINALITY: 209>
NESTED =
<TokenType.NESTED: 210>
AGGREGATEFUNCTION =
<TokenType.AGGREGATEFUNCTION: 211>
SIMPLEAGGREGATEFUNCTION =
<TokenType.SIMPLEAGGREGATEFUNCTION: 212>
TDIGEST =
<TokenType.TDIGEST: 213>
UNKNOWN =
<TokenType.UNKNOWN: 214>
VECTOR =
<TokenType.VECTOR: 215>
DYNAMIC =
<TokenType.DYNAMIC: 216>
VOID =
<TokenType.VOID: 217>
ALIAS =
<TokenType.ALIAS: 218>
ALTER =
<TokenType.ALTER: 219>
ALL =
<TokenType.ALL: 220>
ANTI =
<TokenType.ANTI: 221>
ANY =
<TokenType.ANY: 222>
APPLY =
<TokenType.APPLY: 223>
ARRAY =
<TokenType.ARRAY: 224>
ASC =
<TokenType.ASC: 225>
ASOF =
<TokenType.ASOF: 226>
ATTACH =
<TokenType.ATTACH: 227>
AUTO_INCREMENT =
<TokenType.AUTO_INCREMENT: 228>
BEGIN =
<TokenType.BEGIN: 229>
BETWEEN =
<TokenType.BETWEEN: 230>
BULK_COLLECT_INTO =
<TokenType.BULK_COLLECT_INTO: 231>
CACHE =
<TokenType.CACHE: 232>
CASE =
<TokenType.CASE: 233>
CHARACTER_SET =
<TokenType.CHARACTER_SET: 234>
CLUSTER_BY =
<TokenType.CLUSTER_BY: 235>
COLLATE =
<TokenType.COLLATE: 236>
COMMAND =
<TokenType.COMMAND: 237>
COMMENT =
<TokenType.COMMENT: 238>
COMMIT =
<TokenType.COMMIT: 239>
CONNECT_BY =
<TokenType.CONNECT_BY: 240>
CONSTRAINT =
<TokenType.CONSTRAINT: 241>
COPY =
<TokenType.COPY: 242>
CREATE =
<TokenType.CREATE: 243>
CROSS =
<TokenType.CROSS: 244>
CUBE =
<TokenType.CUBE: 245>
CURRENT_DATE =
<TokenType.CURRENT_DATE: 246>
CURRENT_DATETIME =
<TokenType.CURRENT_DATETIME: 247>
CURRENT_SCHEMA =
<TokenType.CURRENT_SCHEMA: 248>
CURRENT_TIME =
<TokenType.CURRENT_TIME: 249>
CURRENT_TIMESTAMP =
<TokenType.CURRENT_TIMESTAMP: 250>
CURRENT_USER =
<TokenType.CURRENT_USER: 251>
CURRENT_USER_ID =
<TokenType.CURRENT_USER_ID: 252>
CURRENT_ROLE =
<TokenType.CURRENT_ROLE: 253>
CURRENT_CATALOG =
<TokenType.CURRENT_CATALOG: 254>
DECLARE =
<TokenType.DECLARE: 255>
DEFAULT =
<TokenType.DEFAULT: 256>
DELETE =
<TokenType.DELETE: 257>
DESC =
<TokenType.DESC: 258>
DESCRIBE =
<TokenType.DESCRIBE: 259>
DETACH =
<TokenType.DETACH: 260>
DICTIONARY =
<TokenType.DICTIONARY: 261>
DISTINCT =
<TokenType.DISTINCT: 262>
DISTRIBUTE_BY =
<TokenType.DISTRIBUTE_BY: 263>
DIV =
<TokenType.DIV: 264>
DROP =
<TokenType.DROP: 265>
ELSE =
<TokenType.ELSE: 266>
END =
<TokenType.END: 267>
ESCAPE =
<TokenType.ESCAPE: 268>
EXCEPT =
<TokenType.EXCEPT: 269>
EXECUTE =
<TokenType.EXECUTE: 270>
EXISTS =
<TokenType.EXISTS: 271>
FALSE =
<TokenType.FALSE: 272>
FETCH =
<TokenType.FETCH: 273>
FILE =
<TokenType.FILE: 274>
FILE_FORMAT =
<TokenType.FILE_FORMAT: 275>
FILTER =
<TokenType.FILTER: 276>
FINAL =
<TokenType.FINAL: 277>
FIRST =
<TokenType.FIRST: 278>
FOR =
<TokenType.FOR: 279>
FORCE =
<TokenType.FORCE: 280>
FOREIGN_KEY =
<TokenType.FOREIGN_KEY: 281>
FORMAT =
<TokenType.FORMAT: 282>
FROM =
<TokenType.FROM: 283>
FULL =
<TokenType.FULL: 284>
FUNCTION =
<TokenType.FUNCTION: 285>
GET =
<TokenType.GET: 286>
GLOB =
<TokenType.GLOB: 287>
GLOBAL =
<TokenType.GLOBAL: 288>
GRANT =
<TokenType.GRANT: 289>
GROUP_BY =
<TokenType.GROUP_BY: 290>
GROUPING_SETS =
<TokenType.GROUPING_SETS: 291>
HAVING =
<TokenType.HAVING: 292>
HINT =
<TokenType.HINT: 293>
IGNORE =
<TokenType.IGNORE: 294>
ILIKE =
<TokenType.ILIKE: 295>
IN =
<TokenType.IN: 296>
INDEX =
<TokenType.INDEX: 297>
INDEXED_BY =
<TokenType.INDEXED_BY: 298>
INNER =
<TokenType.INNER: 299>
INSERT =
<TokenType.INSERT: 300>
INSTALL =
<TokenType.INSTALL: 301>
INTEGRATION =
<TokenType.INTEGRATION: 302>
INTERSECT =
<TokenType.INTERSECT: 303>
INTERVAL =
<TokenType.INTERVAL: 304>
INTO =
<TokenType.INTO: 305>
INTRODUCER =
<TokenType.INTRODUCER: 306>
IRLIKE =
<TokenType.IRLIKE: 307>
IS =
<TokenType.IS: 308>
ISNULL =
<TokenType.ISNULL: 309>
JOIN =
<TokenType.JOIN: 310>
JOIN_MARKER =
<TokenType.JOIN_MARKER: 311>
KEEP =
<TokenType.KEEP: 312>
KEY =
<TokenType.KEY: 313>
KILL =
<TokenType.KILL: 314>
LANGUAGE =
<TokenType.LANGUAGE: 315>
LATERAL =
<TokenType.LATERAL: 316>
LEFT =
<TokenType.LEFT: 317>
LIKE =
<TokenType.LIKE: 318>
LIMIT =
<TokenType.LIMIT: 319>
LIST =
<TokenType.LIST: 320>
LOAD =
<TokenType.LOAD: 321>
LOCK =
<TokenType.LOCK: 322>
MAP =
<TokenType.MAP: 323>
MATCH =
<TokenType.MATCH: 324>
MATCH_CONDITION =
<TokenType.MATCH_CONDITION: 325>
MATCH_RECOGNIZE =
<TokenType.MATCH_RECOGNIZE: 326>
MEMBER_OF =
<TokenType.MEMBER_OF: 327>
MERGE =
<TokenType.MERGE: 328>
MOD =
<TokenType.MOD: 329>
MODEL =
<TokenType.MODEL: 330>
NATURAL =
<TokenType.NATURAL: 331>
NEXT =
<TokenType.NEXT: 332>
NOTHING =
<TokenType.NOTHING: 333>
NOTNULL =
<TokenType.NOTNULL: 334>
NULL =
<TokenType.NULL: 335>
OBJECT_IDENTIFIER =
<TokenType.OBJECT_IDENTIFIER: 336>
OFFSET =
<TokenType.OFFSET: 337>
ON =
<TokenType.ON: 338>
ONLY =
<TokenType.ONLY: 339>
OPERATOR =
<TokenType.OPERATOR: 340>
ORDER_BY =
<TokenType.ORDER_BY: 341>
ORDER_SIBLINGS_BY =
<TokenType.ORDER_SIBLINGS_BY: 342>
ORDERED =
<TokenType.ORDERED: 343>
ORDINALITY =
<TokenType.ORDINALITY: 344>
OUT =
<TokenType.OUT: 345>
INOUT =
<TokenType.INOUT: 346>
OUTER =
<TokenType.OUTER: 347>
OVER =
<TokenType.OVER: 348>
OVERLAPS =
<TokenType.OVERLAPS: 349>
OVERWRITE =
<TokenType.OVERWRITE: 350>
PACKAGE =
<TokenType.PACKAGE: 351>
PARTITION =
<TokenType.PARTITION: 352>
PARTITION_BY =
<TokenType.PARTITION_BY: 353>
PERCENT =
<TokenType.PERCENT: 354>
PIVOT =
<TokenType.PIVOT: 355>
PLACEHOLDER =
<TokenType.PLACEHOLDER: 356>
POLICY =
<TokenType.POLICY: 357>
POOL =
<TokenType.POOL: 358>
POSITIONAL =
<TokenType.POSITIONAL: 359>
PRAGMA =
<TokenType.PRAGMA: 360>
PREWHERE =
<TokenType.PREWHERE: 361>
PRIMARY_KEY =
<TokenType.PRIMARY_KEY: 362>
PROCEDURE =
<TokenType.PROCEDURE: 363>
PROPERTIES =
<TokenType.PROPERTIES: 364>
PROJECTION =
<TokenType.PROJECTION: 365>
PSEUDO_TYPE =
<TokenType.PSEUDO_TYPE: 366>
PUT =
<TokenType.PUT: 367>
QUALIFY =
<TokenType.QUALIFY: 368>
QUOTE =
<TokenType.QUOTE: 369>
QDCOLON =
<TokenType.QDCOLON: 370>
RANGE =
<TokenType.RANGE: 371>
RECURSIVE =
<TokenType.RECURSIVE: 372>
REFRESH =
<TokenType.REFRESH: 373>
RENAME =
<TokenType.RENAME: 374>
REPLACE =
<TokenType.REPLACE: 375>
RETURNING =
<TokenType.RETURNING: 376>
REVOKE =
<TokenType.REVOKE: 377>
REFERENCES =
<TokenType.REFERENCES: 378>
RIGHT =
<TokenType.RIGHT: 379>
RLIKE =
<TokenType.RLIKE: 380>
ROLE =
<TokenType.ROLE: 381>
ROLLBACK =
<TokenType.ROLLBACK: 382>
ROLLUP =
<TokenType.ROLLUP: 383>
ROW =
<TokenType.ROW: 384>
ROWS =
<TokenType.ROWS: 385>
RULE =
<TokenType.RULE: 386>
SELECT =
<TokenType.SELECT: 387>
SEMI =
<TokenType.SEMI: 388>
SEPARATOR =
<TokenType.SEPARATOR: 389>
SEQUENCE =
<TokenType.SEQUENCE: 390>
SERDE_PROPERTIES =
<TokenType.SERDE_PROPERTIES: 391>
SET =
<TokenType.SET: 392>
SETTINGS =
<TokenType.SETTINGS: 393>
SHOW =
<TokenType.SHOW: 394>
SIMILAR_TO =
<TokenType.SIMILAR_TO: 395>
SOME =
<TokenType.SOME: 396>
SORT_BY =
<TokenType.SORT_BY: 397>
SOUNDS_LIKE =
<TokenType.SOUNDS_LIKE: 398>
SQL_SECURITY =
<TokenType.SQL_SECURITY: 399>
START_WITH =
<TokenType.START_WITH: 400>
STORAGE_INTEGRATION =
<TokenType.STORAGE_INTEGRATION: 401>
STRAIGHT_JOIN =
<TokenType.STRAIGHT_JOIN: 402>
STRUCT =
<TokenType.STRUCT: 403>
SUMMARIZE =
<TokenType.SUMMARIZE: 404>
TABLE_SAMPLE =
<TokenType.TABLE_SAMPLE: 405>
TAG =
<TokenType.TAG: 406>
TEMPORARY =
<TokenType.TEMPORARY: 407>
TOP =
<TokenType.TOP: 408>
THEN =
<TokenType.THEN: 409>
TRUE =
<TokenType.TRUE: 410>
TRUNCATE =
<TokenType.TRUNCATE: 411>
TRIGGER =
<TokenType.TRIGGER: 412>
TYPE =
<TokenType.TYPE: 413>
UNCACHE =
<TokenType.UNCACHE: 414>
UNDROP =
<TokenType.UNDROP: 415>
UNION =
<TokenType.UNION: 416>
UNNEST =
<TokenType.UNNEST: 417>
UNPIVOT =
<TokenType.UNPIVOT: 418>
UPDATE =
<TokenType.UPDATE: 419>
USE =
<TokenType.USE: 420>
USING =
<TokenType.USING: 421>
VALUES =
<TokenType.VALUES: 422>
VARIADIC =
<TokenType.VARIADIC: 423>
VIEW =
<TokenType.VIEW: 424>
SEMANTIC_VIEW =
<TokenType.SEMANTIC_VIEW: 425>
VOLATILE =
<TokenType.VOLATILE: 426>
VOLUME =
<TokenType.VOLUME: 427>
WHEN =
<TokenType.WHEN: 428>
WHERE =
<TokenType.WHERE: 429>
WINDOW =
<TokenType.WINDOW: 430>
WITH =
<TokenType.WITH: 431>
UNIQUE =
<TokenType.UNIQUE: 432>
UTC_DATE =
<TokenType.UTC_DATE: 433>
UTC_TIME =
<TokenType.UTC_TIME: 434>
UTC_TIMESTAMP =
<TokenType.UTC_TIMESTAMP: 435>
OPTION =
<TokenType.OPTION: 436>
SINK =
<TokenType.SINK: 437>
SOURCE =
<TokenType.SOURCE: 438>
ANALYZE =
<TokenType.ANALYZE: 439>
NAMESPACE =
<TokenType.NAMESPACE: 440>
EXPORT =
<TokenType.EXPORT: 441>
HIVE_TOKEN_STREAM =
<TokenType.HIVE_TOKEN_STREAM: 442>
SENTINEL =
<TokenType.SENTINEL: 443>
class
Token:
475class Token: 476 # mypyc doesn't expose slots 477 _attrs: t.ClassVar[tuple[str, ...]] = ( 478 "token_type", 479 "text", 480 "line", 481 "col", 482 "start", 483 "end", 484 "comments", 485 ) 486 __slots__ = _attrs 487 488 @classmethod 489 def number(cls, number: int) -> Token: 490 """Returns a NUMBER token with `number` as its text.""" 491 return cls(TokenType.NUMBER, str(number)) 492 493 @classmethod 494 def string(cls, string: str) -> Token: 495 """Returns a STRING token with `string` as its text.""" 496 return cls(TokenType.STRING, string) 497 498 @classmethod 499 def identifier(cls, identifier: str) -> Token: 500 """Returns an IDENTIFIER token with `identifier` as its text.""" 501 return cls(TokenType.IDENTIFIER, identifier) 502 503 @classmethod 504 def var(cls, var: str) -> Token: 505 """Returns an VAR token with `var` as its text.""" 506 return cls(TokenType.VAR, var) 507 508 def __init__( 509 self, 510 token_type: TokenType, 511 text: str, 512 line: int = 1, 513 col: int = 1, 514 start: int = 0, 515 end: int = 0, 516 comments: list[str] | None = None, 517 ) -> None: 518 self.token_type = token_type 519 self.text = text 520 self.line = line 521 self.col = col 522 self.start = start 523 self.end = end 524 self.comments = [] if comments is None else comments 525 526 def __bool__(self) -> bool: 527 return self.token_type != TokenType.SENTINEL 528 529 def __repr__(self) -> str: 530 attributes = ", ".join( 531 f"{k}: TokenType.{self.token_type.name}" 532 if k == "token_type" 533 else f"{k}: {getattr(self, k)}" 534 for k in self._attrs 535 ) 536 return f"<Token {attributes}>"
Token( token_type: TokenType, text: str, line: int = 1, col: int = 1, start: int = 0, end: int = 0, comments: list[str] | None = None)
508 def __init__( 509 self, 510 token_type: TokenType, 511 text: str, 512 line: int = 1, 513 col: int = 1, 514 start: int = 0, 515 end: int = 0, 516 comments: list[str] | None = None, 517 ) -> None: 518 self.token_type = token_type 519 self.text = text 520 self.line = line 521 self.col = col 522 self.start = start 523 self.end = end 524 self.comments = [] if comments is None else comments
488 @classmethod 489 def number(cls, number: int) -> Token: 490 """Returns a NUMBER token with `number` as its text.""" 491 return cls(TokenType.NUMBER, str(number))
Returns a NUMBER token with number as its text.
493 @classmethod 494 def string(cls, string: str) -> Token: 495 """Returns a STRING token with `string` as its text.""" 496 return cls(TokenType.STRING, string)
Returns a STRING token with string as its text.
498 @classmethod 499 def identifier(cls, identifier: str) -> Token: 500 """Returns an IDENTIFIER token with `identifier` as its text.""" 501 return cls(TokenType.IDENTIFIER, identifier)
Returns an IDENTIFIER token with identifier as its text.
class
TokenizerCore:
539class TokenizerCore: 540 __slots__ = ( 541 "sql", 542 "size", 543 "tokens", 544 "_start", 545 "_current", 546 "_line", 547 "_col", 548 "_comments", 549 "_char", 550 "_end", 551 "_peek", 552 "_prev_token_line", 553 "single_tokens", 554 "keywords", 555 "quotes", 556 "format_strings", 557 "identifiers", 558 "comments", 559 "string_escapes", 560 "byte_string_escapes", 561 "identifier_escapes", 562 "escape_follow_chars", 563 "commands", 564 "command_prefix_tokens", 565 "nested_comments", 566 "hint_start", 567 "tokens_preceding_hint", 568 "has_bit_strings", 569 "has_hex_strings", 570 "numeric_literals", 571 "var_single_tokens", 572 "string_escapes_allowed_in_raw_strings", 573 "heredoc_tag_is_identifier", 574 "heredoc_string_alternative", 575 "keyword_trie", 576 "numbers_can_be_underscore_separated", 577 "numbers_can_have_decimals", 578 "identifiers_can_start_with_digit", 579 "unescaped_sequences", 580 "numeric_escapes", 581 "drop_unknown_escapes", 582 ) 583 584 def __init__( 585 self, 586 single_tokens: dict[str, TokenType], 587 keywords: dict[str, TokenType], 588 quotes: dict[str, str], 589 format_strings: dict[str, tuple[str, TokenType]], 590 identifiers: dict[str, str], 591 comments: dict[str, str | None], 592 string_escapes: set[str], 593 byte_string_escapes: set[str], 594 identifier_escapes: set[str], 595 escape_follow_chars: set[str], 596 commands: set[TokenType], 597 command_prefix_tokens: set[TokenType], 598 nested_comments: bool, 599 hint_start: str, 600 tokens_preceding_hint: set[TokenType], 601 has_bit_strings: bool, 602 has_hex_strings: bool, 603 numeric_literals: dict[str, str], 604 var_single_tokens: set[str], 605 string_escapes_allowed_in_raw_strings: bool, 606 heredoc_tag_is_identifier: bool, 607 heredoc_string_alternative: TokenType, 608 keyword_trie: dict, 609 numbers_can_be_underscore_separated: bool, 610 numbers_can_have_decimals: bool, 611 identifiers_can_start_with_digit: bool, 612 unescaped_sequences: dict[str, str], 613 numeric_escapes: dict[str, tuple[int, int, int, int]], 614 drop_unknown_escapes: bool, 615 ) -> None: 616 self.single_tokens = single_tokens 617 self.keywords = keywords 618 self.quotes = quotes 619 self.format_strings = format_strings 620 self.identifiers = identifiers 621 self.comments = comments 622 self.string_escapes = string_escapes 623 self.byte_string_escapes = byte_string_escapes 624 self.identifier_escapes = identifier_escapes 625 self.escape_follow_chars = escape_follow_chars 626 self.commands = commands 627 self.command_prefix_tokens = command_prefix_tokens 628 self.nested_comments = nested_comments 629 self.hint_start = hint_start 630 self.tokens_preceding_hint = tokens_preceding_hint 631 self.has_bit_strings = has_bit_strings 632 self.has_hex_strings = has_hex_strings 633 self.numeric_literals = numeric_literals 634 self.var_single_tokens = var_single_tokens 635 self.string_escapes_allowed_in_raw_strings = string_escapes_allowed_in_raw_strings 636 self.heredoc_tag_is_identifier = heredoc_tag_is_identifier 637 self.heredoc_string_alternative = heredoc_string_alternative 638 self.keyword_trie = keyword_trie 639 self.numbers_can_be_underscore_separated = numbers_can_be_underscore_separated 640 self.numbers_can_have_decimals = numbers_can_have_decimals 641 self.identifiers_can_start_with_digit = identifiers_can_start_with_digit 642 self.unescaped_sequences = unescaped_sequences 643 self.numeric_escapes = numeric_escapes 644 self.drop_unknown_escapes = drop_unknown_escapes 645 self.sql = "" 646 self.size = 0 647 self.tokens: list[Token] = [] 648 self._start = 0 649 self._current = 0 650 self._line = 1 651 self._col = 0 652 self._comments: list[str] = [] 653 self._char = "" 654 self._end = False 655 self._peek = "" 656 self._prev_token_line = -1 657 658 def reset(self) -> None: 659 self.sql = "" 660 self.size = 0 661 self.tokens = [] 662 self._start = 0 663 self._current = 0 664 self._line = 1 665 self._col = 0 666 self._comments = [] 667 self._char = "" 668 self._end = False 669 self._peek = "" 670 self._prev_token_line = -1 671 672 def tokenize(self, sql: str) -> list[Token]: 673 """Returns a list of tokens corresponding to the SQL string `sql`.""" 674 self.reset() 675 self.sql = sql 676 self.size = len(sql) 677 678 try: 679 self._scan() 680 except Exception as e: 681 start = max(self._current - 50, 0) 682 end = min(self._current + 50, self.size - 1) 683 context = self.sql[start:end] 684 raise TokenError(f"Error tokenizing '{context}'", start=start, end=end) from e 685 686 return self.tokens 687 688 def _scan(self, check_semicolon: bool = False) -> None: 689 identifiers = self.identifiers 690 digit_chars = _DIGIT_CHARS 691 692 while self.size and not self._end: 693 current = self._current 694 695 # Skip spaces here rather than iteratively calling advance() for performance reasons 696 while current < self.size: 697 char = self.sql[current] 698 699 if char == " " or char == "\t": 700 current += 1 701 else: 702 break 703 704 offset = current - self._current if current > self._current else 1 705 706 self._start = current 707 self._advance(offset) 708 709 if not self._char.isspace(): 710 if self._char in digit_chars: 711 self._scan_number() 712 elif self._char in identifiers: 713 self._scan_identifier(identifiers[self._char]) 714 else: 715 self._scan_keywords() 716 717 if check_semicolon and self._peek == ";": 718 break 719 720 if self.tokens and self._comments: 721 self.tokens[-1].comments.extend(self._comments) 722 723 def _chars(self, size: int) -> str: 724 if size == 1: 725 return self._char 726 727 start = self._current - 1 728 end = start + size 729 730 return self.sql[start:end] if end <= self.size else "" 731 732 def _advance(self, i: int = 1, alnum: bool = False) -> None: 733 char = self._char 734 735 if char == "\n" or char == "\r": 736 # Ensures we don't count an extra line if we get a \r\n line break sequence 737 if not (char == "\r" and self._peek == "\n"): 738 self._col = i 739 self._line += 1 740 else: 741 self._col += i 742 743 self._current += i 744 sql = self.sql 745 size = self.size 746 self._end = self._current >= size 747 self._char = sql[self._current - 1] 748 self._peek = "" if self._end else sql[self._current] 749 750 if alnum and self._char.isalnum(): 751 # Cache to local variables instead of attributes for better performance 752 _col = self._col 753 _current = self._current 754 _end = self._end 755 _peek = self._peek 756 757 while _peek.isalnum(): 758 _col += 1 759 _current += 1 760 _end = _current >= size 761 _peek = "" if _end else sql[_current] 762 763 self._col = _col 764 self._current = _current 765 self._end = _end 766 self._peek = _peek 767 self._char = sql[_current - 1] 768 769 @property 770 def _text(self) -> str: 771 return self.sql[self._start : self._current] 772 773 def _add(self, token_type: TokenType, text: str | None = None) -> None: 774 self._prev_token_line = self._line 775 776 if self._comments and token_type == TokenType.SEMICOLON and self.tokens: 777 self.tokens[-1].comments.extend(self._comments) 778 self._comments = [] 779 780 if text is None: 781 text = self.sql[self._start : self._current] 782 783 self.tokens.append( 784 Token( 785 token_type, 786 text=text, 787 line=self._line, 788 col=self._col, 789 start=self._start, 790 end=self._current - 1, 791 comments=self._comments, 792 ) 793 ) 794 self._comments = [] 795 796 # If we have either a semicolon or a begin token before the command's token, we'll parse 797 # whatever follows the command's token as a string 798 if ( 799 token_type in self.commands 800 and self._peek != ";" 801 and (len(self.tokens) == 1 or self.tokens[-2].token_type in self.command_prefix_tokens) 802 ): 803 start = self._current 804 tokens = len(self.tokens) 805 self._scan(check_semicolon=True) 806 self.tokens = self.tokens[:tokens] 807 text = self.sql[start : self._current].strip() 808 if text: 809 self._add(TokenType.STRING, text) 810 811 def _scan_keywords(self) -> None: 812 sql = self.sql 813 sql_size = self.size 814 single_tokens = self.single_tokens 815 char_upper = _CHAR_UPPER 816 size = 0 817 word = None 818 chars = self._char 819 char = chars 820 prev_space = False 821 skip = False 822 trie = self.keyword_trie 823 single_token = char in single_tokens 824 825 while chars: 826 if not skip: 827 sub = trie.get(char_upper.get(char, char)) 828 if sub is None: 829 break 830 trie = sub 831 if 0 in trie: 832 word = chars 833 834 end = self._current + size 835 size += 1 836 837 if end < sql_size: 838 char = sql[end] 839 single_token = single_token or char in single_tokens 840 is_space = char.isspace() 841 842 if not is_space or not prev_space: 843 if is_space: 844 char = " " 845 chars += char 846 prev_space = is_space 847 skip = False 848 else: 849 skip = True 850 else: 851 char = "" 852 break 853 854 if word: 855 if self._scan_string(word): 856 return 857 if self._scan_comment(word): 858 return 859 if prev_space or single_token or not char: 860 self._advance(size - 1) 861 word = word.upper() 862 self._add(self.keywords[word], text=word) 863 return 864 865 if self._char in single_tokens: 866 self._add(single_tokens[self._char], text=self._char) 867 return 868 869 self._scan_var() 870 871 def _scan_comment(self, comment_start: str) -> bool: 872 if comment_start not in self.comments: 873 return False 874 875 comment_start_line = self._line 876 comment_start_size = len(comment_start) 877 comment_end = self.comments[comment_start] 878 879 if comment_end: 880 # Skip the comment's start delimiter 881 self._advance(comment_start_size) 882 883 comment_count = 1 884 comment_end_size = len(comment_end) 885 nested_comments = self.nested_comments 886 887 while not self._end: 888 if self._chars(comment_end_size) == comment_end: 889 comment_count -= 1 890 if not comment_count: 891 break 892 893 self._advance(alnum=True) 894 895 # Nested comments are allowed by some dialects, e.g. databricks, duckdb, postgres 896 if ( 897 nested_comments 898 and not self._end 899 and self._chars(comment_end_size) == comment_start 900 ): 901 self._advance(comment_start_size) 902 comment_count += 1 903 904 self._comments.append(self._text[comment_start_size : -comment_end_size + 1]) 905 self._advance(comment_end_size - 1) 906 else: 907 _peek = self._peek 908 while not self._end and _peek != "\n" and _peek != "\r": 909 self._advance(alnum=True) 910 _peek = self._peek 911 self._comments.append(self._text[comment_start_size:]) 912 913 if ( 914 comment_start == self.hint_start 915 and self.tokens 916 and self.tokens[-1].token_type in self.tokens_preceding_hint 917 ): 918 self._add(TokenType.HINT) 919 920 # Leading comment is attached to the succeeding token, whilst trailing comment to the preceding. 921 # Multiple consecutive comments are preserved by appending them to the current comments list. 922 if comment_start_line == self._prev_token_line: 923 self.tokens[-1].comments.extend(self._comments) 924 self._comments = [] 925 self._prev_token_line = self._line 926 927 return True 928 929 def _scan_number(self) -> None: 930 if self._char == "0": 931 peek = _CHAR_UPPER.get(self._peek, self._peek) 932 if peek == "B" and self.has_bit_strings: 933 return self._scan_bits() 934 elif peek == "X": 935 return self._scan_hex() if self.has_hex_strings else self._add(TokenType.NUMBER) 936 937 decimal = False 938 scientific = 0 939 numbers_can_be_underscore_separated = self.numbers_can_be_underscore_separated 940 single_tokens = self.single_tokens 941 keywords = self.keywords 942 numeric_literals = self.numeric_literals 943 identifiers_can_start_with_digit = self.identifiers_can_start_with_digit 944 945 is_underscore_separated: bool = False 946 number_text: str = "" 947 numeric_literal: str = "" 948 numeric_type: TokenType | None = None 949 950 while True: 951 if self._peek in _DIGIT_CHARS: 952 # Batch consecutive digits: scan ahead to find how many 953 sql = self.sql 954 end = self._current + 1 955 size = self.size 956 while end < size and sql[end] in _DIGIT_CHARS: 957 end += 1 958 self._advance(end - self._current) 959 elif self._peek == "." and not decimal: 960 if ( 961 self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER 962 ) or not self.numbers_can_have_decimals: 963 break 964 decimal = True 965 self._advance() 966 elif self._peek in ("-", "+") and scientific == 1: 967 # Only consume +/- if followed by a digit 968 if self._current + 1 < self.size and self.sql[self._current + 1] in _DIGIT_CHARS: 969 scientific += 1 970 self._advance() 971 else: 972 break 973 elif _CHAR_UPPER.get(self._peek, self._peek) == "E" and not scientific: 974 scientific += 1 975 self._advance() 976 elif self._peek == "_" and numbers_can_be_underscore_separated: 977 is_underscore_separated = True 978 self._advance() 979 elif self._peek.isidentifier(): 980 number_text = self._text 981 982 while self._peek and not self._peek.isspace() and self._peek not in single_tokens: 983 numeric_literal += self._peek 984 self._advance() 985 986 numeric_type = keywords.get(numeric_literals.get(numeric_literal.upper(), "")) 987 988 if numeric_type: 989 break 990 elif identifiers_can_start_with_digit: 991 return self._add(TokenType.VAR) 992 993 self._advance(-len(numeric_literal)) 994 break 995 else: 996 break 997 998 number_text = number_text or self.sql[self._start : self._current] 999 1000 # Normalize inputs such as 100_000 to 100000 1001 if is_underscore_separated: 1002 number_text = number_text.replace("_", "") 1003 1004 self._add(TokenType.NUMBER, number_text) 1005 1006 # Normalize inputs such as 123L to 123::BIGINT so that they're parsed as casts 1007 if numeric_type: 1008 self._add(TokenType.DCOLON, "::") 1009 self._add(numeric_type, numeric_literal) 1010 1011 def _scan_bits(self) -> None: 1012 self._advance() 1013 value = self._extract_value() 1014 try: 1015 # If `value` can't be converted to a binary, fallback to tokenizing it as an identifier 1016 int(value, 2) 1017 self._add(TokenType.BIT_STRING, value[2:]) # Drop the 0b 1018 except ValueError: 1019 self._add(TokenType.IDENTIFIER) 1020 1021 def _scan_hex(self) -> None: 1022 self._advance() 1023 value = self._extract_value() 1024 try: 1025 # If `value` can't be converted to a hex, fallback to tokenizing it as an identifier 1026 int(value, 16) 1027 self._add(TokenType.HEX_STRING, value[2:]) # Drop the 0x 1028 except ValueError: 1029 self._add(TokenType.IDENTIFIER) 1030 1031 def _extract_value(self) -> str: 1032 single_tokens = self.single_tokens 1033 1034 while True: 1035 char = self._peek.strip() 1036 if char and char not in single_tokens: 1037 self._advance(alnum=True) 1038 else: 1039 break 1040 1041 return self._text 1042 1043 def _scan_string(self, start: str) -> bool: 1044 base = None 1045 token_type = TokenType.STRING 1046 1047 if start in self.quotes: 1048 end = self.quotes[start] 1049 elif start in self.format_strings: 1050 end, token_type = self.format_strings[start] 1051 1052 if token_type == TokenType.HEX_STRING: 1053 base = 16 1054 elif token_type == TokenType.BIT_STRING: 1055 base = 2 1056 elif token_type == TokenType.HEREDOC_STRING: 1057 self._advance() 1058 1059 if self._char == end: 1060 tag = "" 1061 else: 1062 tag = self._extract_string( 1063 end, 1064 raw_string=True, 1065 raise_unmatched=not self.heredoc_tag_is_identifier, 1066 ) 1067 1068 if ( 1069 tag 1070 and self.heredoc_tag_is_identifier 1071 and (self._end or tag.isdigit() or any(c.isspace() for c in tag)) 1072 ): 1073 if not self._end: 1074 self._advance(-1) 1075 1076 self._advance(-len(tag)) 1077 self._add(self.heredoc_string_alternative) 1078 return True 1079 1080 end = f"{start}{tag}{end}" 1081 else: 1082 return False 1083 1084 self._advance(len(start)) 1085 text = self._extract_string( 1086 end, 1087 escapes=( 1088 self.byte_string_escapes 1089 if token_type == TokenType.BYTE_STRING 1090 else self.string_escapes 1091 ), 1092 raw_string=token_type == TokenType.RAW_STRING, 1093 ) 1094 1095 if base and text: 1096 try: 1097 int(text, base) 1098 except Exception: 1099 raise TokenError( 1100 f"Numeric string contains invalid characters from {self._line}:{self._start}" 1101 ) 1102 1103 self._add(token_type, text) 1104 return True 1105 1106 def _scan_identifier(self, identifier_end: str) -> None: 1107 self._advance() 1108 text = self._extract_string( 1109 identifier_end, escapes=self.identifier_escapes | {identifier_end} 1110 ) 1111 self._add(TokenType.IDENTIFIER, text) 1112 1113 def _scan_var(self) -> None: 1114 var_single_tokens = self.var_single_tokens 1115 single_tokens = self.single_tokens 1116 1117 while True: 1118 peek = self._peek 1119 if not peek or peek.isspace(): 1120 break 1121 if peek not in var_single_tokens and peek in single_tokens: 1122 break 1123 self._advance(alnum=True) 1124 1125 self._add( 1126 TokenType.VAR 1127 if self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER 1128 else self.keywords.get(self.sql[self._start : self._current].upper(), TokenType.VAR) 1129 ) 1130 1131 def _read_numeric_escape( 1132 self, start: int, base: int, min_digits: int, max_digits: int, max_value: int 1133 ) -> tuple[int, int]: 1134 """ 1135 Reads up to `max_digits` digits from `start`, stopping before the value exceeds `max_value`. 1136 Returns the value and the end index, or -1 as the value if fewer than `min_digits` were read. 1137 """ 1138 sql = self.sql 1139 value = 0 1140 end = start 1141 limit = min(start + max_digits, self.size) 1142 1143 while end < limit: 1144 digit = _DIGIT_VALUES.get(sql[end], 16) 1145 if digit >= base or value * base + digit > max_value: 1146 break 1147 value = value * base + digit 1148 end += 1 1149 1150 return (value, end) if end - start >= min_digits else (-1, end) 1151 1152 def _scan_numeric_escape(self) -> str: 1153 """ 1154 Decodes the numeric escape sequence starting at the current char, which is a backslash, 1155 according to `numeric_escapes`. Returns an empty string if nothing is decoded. 1156 """ 1157 sql = self.sql 1158 start = self._current 1159 peek = self._peek 1160 1161 # Octal escapes start with a digit (e.g. \101), the others with a letter (e.g. \x41) 1162 is_octal = peek in _OCTAL_CHARS 1163 spec = self.numeric_escapes.get("0" if is_octal else peek) 1164 1165 if spec: 1166 base, min_digits, max_digits, max_value = spec 1167 value, end = self._read_numeric_escape( 1168 start if is_octal else start + 1, base, min_digits, max_digits, max_value 1169 ) 1170 1171 # A UTF-16 surrogate is only valid as a high half (D800-DBFF) followed by an escaped 1172 # low half (DC00-DFFF), e.g. \uD83D\uDE00 is U+1F600 1173 if 0xD800 <= value <= 0xDFFF: 1174 low, low_end = -1, end 1175 if value <= 0xDBFF and sql[end : end + 2] == "\\" + peek: 1176 low, low_end = self._read_numeric_escape( 1177 end + 2, base, min_digits, max_digits, max_value 1178 ) 1179 1180 if 0xDC00 <= low <= 0xDFFF: 1181 # Combine the two halves into a single code point (standard formula) 1182 value = 0x10000 + ((value - 0xD800) << 10) + (low - 0xDC00) 1183 end = low_end 1184 else: 1185 # A lone surrogate isn't a character, so the escape isn't decoded 1186 value = -1 1187 1188 # -1 means the escape isn't decoded, e.g. because it has too few digits. An escape at the 1189 # end of the input is left to the caller, which reports the unterminated string 1190 if value >= 0 and end < self.size: 1191 # Move past the whole escape, from the backslash through its last digit 1192 self._advance(end - start + 1) 1193 return chr(value) 1194 1195 return "" 1196 1197 def _extract_string( 1198 self, 1199 delimiter: str, 1200 escapes: set[str] | None = None, 1201 raw_string: bool = False, 1202 raise_unmatched: bool = True, 1203 ) -> str: 1204 text = "" 1205 delim_size = len(delimiter) 1206 escapes = self.string_escapes if escapes is None else escapes 1207 unescaped_sequences = self.unescaped_sequences 1208 escape_follow_chars = self.escape_follow_chars 1209 numeric_escapes = self.numeric_escapes 1210 drop_unknown_escapes = self.drop_unknown_escapes 1211 string_escapes_allowed_in_raw_strings = self.string_escapes_allowed_in_raw_strings 1212 quotes = self.quotes 1213 sql = self.sql 1214 1215 # use str.find() when the string is simple... no \ or other escapes 1216 if delim_size == 1: 1217 pos = self._current - 1 1218 end = sql.find(delimiter, pos) 1219 1220 if ( 1221 # the closing delimiter was found 1222 end != -1 1223 # there's no doubled delimiter (e.g. '' escape), or the delimiter isn't an escape char 1224 and (end + 1 >= self.size or sql[end + 1] != delimiter or delimiter not in escapes) 1225 # no backslash in the string that would need escape processing 1226 and (not (unescaped_sequences or "\\" in escapes) or sql.find("\\", pos, end) == -1) 1227 ): 1228 newlines = sql.count("\n", pos, end) 1229 if newlines: 1230 self._line += newlines 1231 self._col = end - sql.rfind("\n", pos, end) 1232 else: 1233 self._col += end - pos 1234 1235 self._current = end + 1 1236 self._end = self._current >= self.size 1237 self._char = sql[end] 1238 self._peek = "" if self._end else sql[self._current] 1239 return sql[pos:end] 1240 1241 while True: 1242 backslash_escape = not raw_string and self._char == "\\" and "\\" in escapes 1243 1244 # Numeric escapes come first, since they may overlap fixed sequences (e.g. \000 vs \0) 1245 if backslash_escape and numeric_escapes: 1246 decoded = self._scan_numeric_escape() 1247 if decoded: 1248 text += decoded 1249 continue 1250 1251 # An escape at the end of the input is left to the checks below, which report the 1252 # unterminated string 1253 if ( 1254 not raw_string 1255 and unescaped_sequences 1256 and self._char in escapes 1257 and self._current + 1 < self.size 1258 ): 1259 unescaped_sequence = unescaped_sequences.get(self._char + self._peek) 1260 if unescaped_sequence: 1261 # Advance one char at a time so that line numbers are updated for a newline 1262 if self._peek in "\n\r": 1263 self._advance() 1264 self._advance() 1265 else: 1266 self._advance(2) 1267 text += unescaped_sequence 1268 continue 1269 1270 # Not decoded: drop the backslash if configured, unless the string is unterminated 1271 if backslash_escape and drop_unknown_escapes and self._current + 1 < self.size: 1272 # Advance one char at a time so that line numbers are updated if `peek` is a newline 1273 peek = self._peek 1274 self._advance() 1275 self._advance() 1276 text += peek 1277 continue 1278 1279 is_valid_custom_escape = ( 1280 escape_follow_chars and self._char == "\\" and self._peek not in escape_follow_chars 1281 ) 1282 1283 # An escaped quote before the closing delimiter (e.g. \" in """a\"""") must be 1284 # consumed here, otherwise it'd be picked up by the delimiter check below and 1285 # terminate the string early. This is only relevant for multi-char delimiters 1286 # made up of quote chars, e.g. it shouldn't apply to Snowflake's $$ strings 1287 escaped_delimiter = self._peek == delimiter or ( 1288 delim_size > 1 and self._peek == delimiter[0] and self._peek in quotes 1289 ) 1290 1291 if ( 1292 (string_escapes_allowed_in_raw_strings or not raw_string) 1293 and self._char in escapes 1294 and (escaped_delimiter or self._peek in escapes or is_valid_custom_escape) 1295 and (self._char not in quotes or self._char == self._peek) 1296 ): 1297 if escaped_delimiter: 1298 text += self._peek if not raw_string else self._char + self._peek 1299 elif is_valid_custom_escape and self._char != self._peek: 1300 text += self._peek 1301 else: 1302 text += self._char + self._peek 1303 1304 if self._current + 1 < self.size: 1305 self._advance(2) 1306 else: 1307 raise TokenError(f"Missing {delimiter} from {self._line}:{self._current}") 1308 else: 1309 if self._chars(delim_size) == delimiter: 1310 if delim_size > 1: 1311 self._advance(delim_size - 1) 1312 break 1313 1314 if self._end: 1315 if not raise_unmatched: 1316 return text + self._char 1317 1318 raise TokenError(f"Missing {delimiter} from {self._line}:{self._start}") 1319 1320 current = self._current - 1 1321 self._advance(alnum=True) 1322 text += sql[current : self._current - 1] 1323 1324 return text
TokenizerCore( single_tokens: dict[str, TokenType], keywords: dict[str, TokenType], quotes: dict[str, str], format_strings: dict[str, tuple[str, TokenType]], identifiers: dict[str, str], comments: dict[str, str | None], string_escapes: set[str], byte_string_escapes: set[str], identifier_escapes: set[str], escape_follow_chars: set[str], commands: set[TokenType], command_prefix_tokens: set[TokenType], nested_comments: bool, hint_start: str, tokens_preceding_hint: set[TokenType], has_bit_strings: bool, has_hex_strings: bool, numeric_literals: dict[str, str], var_single_tokens: set[str], string_escapes_allowed_in_raw_strings: bool, heredoc_tag_is_identifier: bool, heredoc_string_alternative: TokenType, keyword_trie: dict, numbers_can_be_underscore_separated: bool, numbers_can_have_decimals: bool, identifiers_can_start_with_digit: bool, unescaped_sequences: dict[str, str], numeric_escapes: dict[str, tuple[int, int, int, int]], drop_unknown_escapes: bool)
584 def __init__( 585 self, 586 single_tokens: dict[str, TokenType], 587 keywords: dict[str, TokenType], 588 quotes: dict[str, str], 589 format_strings: dict[str, tuple[str, TokenType]], 590 identifiers: dict[str, str], 591 comments: dict[str, str | None], 592 string_escapes: set[str], 593 byte_string_escapes: set[str], 594 identifier_escapes: set[str], 595 escape_follow_chars: set[str], 596 commands: set[TokenType], 597 command_prefix_tokens: set[TokenType], 598 nested_comments: bool, 599 hint_start: str, 600 tokens_preceding_hint: set[TokenType], 601 has_bit_strings: bool, 602 has_hex_strings: bool, 603 numeric_literals: dict[str, str], 604 var_single_tokens: set[str], 605 string_escapes_allowed_in_raw_strings: bool, 606 heredoc_tag_is_identifier: bool, 607 heredoc_string_alternative: TokenType, 608 keyword_trie: dict, 609 numbers_can_be_underscore_separated: bool, 610 numbers_can_have_decimals: bool, 611 identifiers_can_start_with_digit: bool, 612 unescaped_sequences: dict[str, str], 613 numeric_escapes: dict[str, tuple[int, int, int, int]], 614 drop_unknown_escapes: bool, 615 ) -> None: 616 self.single_tokens = single_tokens 617 self.keywords = keywords 618 self.quotes = quotes 619 self.format_strings = format_strings 620 self.identifiers = identifiers 621 self.comments = comments 622 self.string_escapes = string_escapes 623 self.byte_string_escapes = byte_string_escapes 624 self.identifier_escapes = identifier_escapes 625 self.escape_follow_chars = escape_follow_chars 626 self.commands = commands 627 self.command_prefix_tokens = command_prefix_tokens 628 self.nested_comments = nested_comments 629 self.hint_start = hint_start 630 self.tokens_preceding_hint = tokens_preceding_hint 631 self.has_bit_strings = has_bit_strings 632 self.has_hex_strings = has_hex_strings 633 self.numeric_literals = numeric_literals 634 self.var_single_tokens = var_single_tokens 635 self.string_escapes_allowed_in_raw_strings = string_escapes_allowed_in_raw_strings 636 self.heredoc_tag_is_identifier = heredoc_tag_is_identifier 637 self.heredoc_string_alternative = heredoc_string_alternative 638 self.keyword_trie = keyword_trie 639 self.numbers_can_be_underscore_separated = numbers_can_be_underscore_separated 640 self.numbers_can_have_decimals = numbers_can_have_decimals 641 self.identifiers_can_start_with_digit = identifiers_can_start_with_digit 642 self.unescaped_sequences = unescaped_sequences 643 self.numeric_escapes = numeric_escapes 644 self.drop_unknown_escapes = drop_unknown_escapes 645 self.sql = "" 646 self.size = 0 647 self.tokens: list[Token] = [] 648 self._start = 0 649 self._current = 0 650 self._line = 1 651 self._col = 0 652 self._comments: list[str] = [] 653 self._char = "" 654 self._end = False 655 self._peek = "" 656 self._prev_token_line = -1
tokens: list[Token]
672 def tokenize(self, sql: str) -> list[Token]: 673 """Returns a list of tokens corresponding to the SQL string `sql`.""" 674 self.reset() 675 self.sql = sql 676 self.size = len(sql) 677 678 try: 679 self._scan() 680 except Exception as e: 681 start = max(self._current - 50, 0) 682 end = min(self._current + 50, self.size - 1) 683 context = self.sql[start:end] 684 raise TokenError(f"Error tokenizing '{context}'", start=start, end=end) from e 685 686 return self.tokens
Returns a list of tokens corresponding to the SQL string sql.