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