Edit on GitHub

sqlglot.tokenizer_core

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

An enumeration.

L_PAREN = <TokenType.L_PAREN: 1>
R_PAREN = <TokenType.R_PAREN: 2>
L_BRACKET = <TokenType.L_BRACKET: 3>
R_BRACKET = <TokenType.R_BRACKET: 4>
L_BRACE = <TokenType.L_BRACE: 5>
R_BRACE = <TokenType.R_BRACE: 6>
COMMA = <TokenType.COMMA: 7>
DOT = <TokenType.DOT: 8>
DASH = <TokenType.DASH: 9>
PLUS = <TokenType.PLUS: 10>
COLON = <TokenType.COLON: 11>
DOTCOLON = <TokenType.DOTCOLON: 12>
DOTCARET = <TokenType.DOTCARET: 13>
DCOLON = <TokenType.DCOLON: 14>
DCOLONDOLLAR = <TokenType.DCOLONDOLLAR: 15>
DCOLONPERCENT = <TokenType.DCOLONPERCENT: 16>
DCOLONQMARK = <TokenType.DCOLONQMARK: 17>
DQMARK = <TokenType.DQMARK: 18>
SEMICOLON = <TokenType.SEMICOLON: 19>
STAR = <TokenType.STAR: 20>
BACKSLASH = <TokenType.BACKSLASH: 21>
SLASH = <TokenType.SLASH: 22>
LT = <TokenType.LT: 23>
LTE = <TokenType.LTE: 24>
GT = <TokenType.GT: 25>
GTE = <TokenType.GTE: 26>
NOT = <TokenType.NOT: 27>
EQ = <TokenType.EQ: 28>
NEQ = <TokenType.NEQ: 29>
NULLSAFE_EQ = <TokenType.NULLSAFE_EQ: 30>
COLON_EQ = <TokenType.COLON_EQ: 31>
COLON_GT = <TokenType.COLON_GT: 32>
NCOLON_GT = <TokenType.NCOLON_GT: 33>
AND = <TokenType.AND: 34>
OR = <TokenType.OR: 35>
AMP = <TokenType.AMP: 36>
DPIPE = <TokenType.DPIPE: 37>
PIPE_GT = <TokenType.PIPE_GT: 38>
PIPE = <TokenType.PIPE: 39>
PIPE_SLASH = <TokenType.PIPE_SLASH: 40>
DPIPE_SLASH = <TokenType.DPIPE_SLASH: 41>
CARET = <TokenType.CARET: 42>
CARET_AT = <TokenType.CARET_AT: 43>
TILDE = <TokenType.TILDE: 44>
ARROW = <TokenType.ARROW: 45>
DARROW = <TokenType.DARROW: 46>
FARROW = <TokenType.FARROW: 47>
HASH = <TokenType.HASH: 48>
HASH_ARROW = <TokenType.HASH_ARROW: 49>
DHASH_ARROW = <TokenType.DHASH_ARROW: 50>
LR_ARROW = <TokenType.LR_ARROW: 51>
LLRR_ARROW = <TokenType.LLRR_ARROW: 52>
DAT = <TokenType.DAT: 53>
AT_QMARK = <TokenType.AT_QMARK: 54>
LT_AT = <TokenType.LT_AT: 55>
AT_GT = <TokenType.AT_GT: 56>
DOLLAR = <TokenType.DOLLAR: 57>
PARAMETER = <TokenType.PARAMETER: 58>
SESSION = <TokenType.SESSION: 59>
SESSION_PARAMETER = <TokenType.SESSION_PARAMETER: 60>
SESSION_USER = <TokenType.SESSION_USER: 61>
DAMP = <TokenType.DAMP: 62>
AMP_LT = <TokenType.AMP_LT: 63>
AMP_GT = <TokenType.AMP_GT: 64>
ADJACENT = <TokenType.ADJACENT: 65>
XOR = <TokenType.XOR: 66>
DSTAR = <TokenType.DSTAR: 67>
QMARK_AMP = <TokenType.QMARK_AMP: 68>
QMARK_PIPE = <TokenType.QMARK_PIPE: 69>
HASH_DASH = <TokenType.HASH_DASH: 70>
EXCLAMATION = <TokenType.EXCLAMATION: 71>
URI_START = <TokenType.URI_START: 72>
BLOCK_START = <TokenType.BLOCK_START: 73>
BLOCK_END = <TokenType.BLOCK_END: 74>
SPACE = <TokenType.SPACE: 75>
BREAK = <TokenType.BREAK: 76>
STRING = <TokenType.STRING: 77>
NUMBER = <TokenType.NUMBER: 78>
IDENTIFIER = <TokenType.IDENTIFIER: 79>
DATABASE = <TokenType.DATABASE: 80>
COLUMN = <TokenType.COLUMN: 81>
COLUMN_DEF = <TokenType.COLUMN_DEF: 82>
SCHEMA = <TokenType.SCHEMA: 83>
TABLE = <TokenType.TABLE: 84>
WAREHOUSE = <TokenType.WAREHOUSE: 85>
STAGE = <TokenType.STAGE: 86>
STREAM = <TokenType.STREAM: 87>
STREAMLIT = <TokenType.STREAMLIT: 88>
VAR = <TokenType.VAR: 89>
BIT_STRING = <TokenType.BIT_STRING: 90>
HEX_STRING = <TokenType.HEX_STRING: 91>
BYTE_STRING = <TokenType.BYTE_STRING: 92>
NATIONAL_STRING = <TokenType.NATIONAL_STRING: 93>
RAW_STRING = <TokenType.RAW_STRING: 94>
HEREDOC_STRING = <TokenType.HEREDOC_STRING: 95>
UNICODE_STRING = <TokenType.UNICODE_STRING: 96>
BIT = <TokenType.BIT: 97>
BOOLEAN = <TokenType.BOOLEAN: 98>
TINYINT = <TokenType.TINYINT: 99>
UTINYINT = <TokenType.UTINYINT: 100>
SMALLINT = <TokenType.SMALLINT: 101>
USMALLINT = <TokenType.USMALLINT: 102>
MEDIUMINT = <TokenType.MEDIUMINT: 103>
UMEDIUMINT = <TokenType.UMEDIUMINT: 104>
INT = <TokenType.INT: 105>
UINT = <TokenType.UINT: 106>
BIGINT = <TokenType.BIGINT: 107>
UBIGINT = <TokenType.UBIGINT: 108>
BIGNUM = <TokenType.BIGNUM: 109>
INT128 = <TokenType.INT128: 110>
UINT128 = <TokenType.UINT128: 111>
INT256 = <TokenType.INT256: 112>
UINT256 = <TokenType.UINT256: 113>
FLOAT = <TokenType.FLOAT: 114>
DOUBLE = <TokenType.DOUBLE: 115>
UDOUBLE = <TokenType.UDOUBLE: 116>
DECIMAL = <TokenType.DECIMAL: 117>
DECIMAL32 = <TokenType.DECIMAL32: 118>
DECIMAL64 = <TokenType.DECIMAL64: 119>
DECIMAL128 = <TokenType.DECIMAL128: 120>
DECIMAL256 = <TokenType.DECIMAL256: 121>
DECFLOAT = <TokenType.DECFLOAT: 122>
UDECIMAL = <TokenType.UDECIMAL: 123>
BIGDECIMAL = <TokenType.BIGDECIMAL: 124>
CHAR = <TokenType.CHAR: 125>
NCHAR = <TokenType.NCHAR: 126>
VARCHAR = <TokenType.VARCHAR: 127>
NVARCHAR = <TokenType.NVARCHAR: 128>
BPCHAR = <TokenType.BPCHAR: 129>
TEXT = <TokenType.TEXT: 130>
MEDIUMTEXT = <TokenType.MEDIUMTEXT: 131>
LONGTEXT = <TokenType.LONGTEXT: 132>
BLOB = <TokenType.BLOB: 133>
MEDIUMBLOB = <TokenType.MEDIUMBLOB: 134>
LONGBLOB = <TokenType.LONGBLOB: 135>
TINYBLOB = <TokenType.TINYBLOB: 136>
TINYTEXT = <TokenType.TINYTEXT: 137>
NAME = <TokenType.NAME: 138>
BINARY = <TokenType.BINARY: 139>
VARBINARY = <TokenType.VARBINARY: 140>
JSON = <TokenType.JSON: 141>
JSONB = <TokenType.JSONB: 142>
TIME = <TokenType.TIME: 143>
TIMETZ = <TokenType.TIMETZ: 144>
TIME_NS = <TokenType.TIME_NS: 145>
TIMESTAMP = <TokenType.TIMESTAMP: 146>
TIMESTAMPTZ = <TokenType.TIMESTAMPTZ: 147>
TIMESTAMPLTZ = <TokenType.TIMESTAMPLTZ: 148>
TIMESTAMPNTZ = <TokenType.TIMESTAMPNTZ: 149>
TIMESTAMP_S = <TokenType.TIMESTAMP_S: 150>
TIMESTAMP_MS = <TokenType.TIMESTAMP_MS: 151>
TIMESTAMP_NS = <TokenType.TIMESTAMP_NS: 152>
DATETIME = <TokenType.DATETIME: 153>
DATETIME2 = <TokenType.DATETIME2: 154>
DATETIME64 = <TokenType.DATETIME64: 155>
SMALLDATETIME = <TokenType.SMALLDATETIME: 156>
DATE = <TokenType.DATE: 157>
DATE32 = <TokenType.DATE32: 158>
INT4RANGE = <TokenType.INT4RANGE: 159>
INT4MULTIRANGE = <TokenType.INT4MULTIRANGE: 160>
INT8RANGE = <TokenType.INT8RANGE: 161>
INT8MULTIRANGE = <TokenType.INT8MULTIRANGE: 162>
NUMRANGE = <TokenType.NUMRANGE: 163>
NUMMULTIRANGE = <TokenType.NUMMULTIRANGE: 164>
TSRANGE = <TokenType.TSRANGE: 165>
TSMULTIRANGE = <TokenType.TSMULTIRANGE: 166>
TSTZRANGE = <TokenType.TSTZRANGE: 167>
TSTZMULTIRANGE = <TokenType.TSTZMULTIRANGE: 168>
DATERANGE = <TokenType.DATERANGE: 169>
DATEMULTIRANGE = <TokenType.DATEMULTIRANGE: 170>
UUID = <TokenType.UUID: 171>
GEOGRAPHY = <TokenType.GEOGRAPHY: 172>
GEOGRAPHYPOINT = <TokenType.GEOGRAPHYPOINT: 173>
NULLABLE = <TokenType.NULLABLE: 174>
GEOMETRY = <TokenType.GEOMETRY: 175>
POINT = <TokenType.POINT: 176>
RING = <TokenType.RING: 177>
LINESTRING = <TokenType.LINESTRING: 178>
LOCALTIME = <TokenType.LOCALTIME: 179>
LOCALTIMESTAMP = <TokenType.LOCALTIMESTAMP: 180>
SYSTIMESTAMP = <TokenType.SYSTIMESTAMP: 181>
MULTILINESTRING = <TokenType.MULTILINESTRING: 182>
POLYGON = <TokenType.POLYGON: 183>
MULTIPOLYGON = <TokenType.MULTIPOLYGON: 184>
HLLSKETCH = <TokenType.HLLSKETCH: 185>
HSTORE = <TokenType.HSTORE: 186>
SUPER = <TokenType.SUPER: 187>
SERIAL = <TokenType.SERIAL: 188>
SMALLSERIAL = <TokenType.SMALLSERIAL: 189>
BIGSERIAL = <TokenType.BIGSERIAL: 190>
XML = <TokenType.XML: 191>
YEAR = <TokenType.YEAR: 192>
USERDEFINED = <TokenType.USERDEFINED: 193>
MONEY = <TokenType.MONEY: 194>
SMALLMONEY = <TokenType.SMALLMONEY: 195>
ROWVERSION = <TokenType.ROWVERSION: 196>
IMAGE = <TokenType.IMAGE: 197>
VARIANT = <TokenType.VARIANT: 198>
OBJECT = <TokenType.OBJECT: 199>
INET = <TokenType.INET: 200>
IPADDRESS = <TokenType.IPADDRESS: 201>
IPPREFIX = <TokenType.IPPREFIX: 202>
IPV4 = <TokenType.IPV4: 203>
IPV6 = <TokenType.IPV6: 204>
ENUM = <TokenType.ENUM: 205>
ENUM8 = <TokenType.ENUM8: 206>
ENUM16 = <TokenType.ENUM16: 207>
FIXEDSTRING = <TokenType.FIXEDSTRING: 208>
LOWCARDINALITY = <TokenType.LOWCARDINALITY: 209>
NESTED = <TokenType.NESTED: 210>
AGGREGATEFUNCTION = <TokenType.AGGREGATEFUNCTION: 211>
SIMPLEAGGREGATEFUNCTION = <TokenType.SIMPLEAGGREGATEFUNCTION: 212>
TDIGEST = <TokenType.TDIGEST: 213>
UNKNOWN = <TokenType.UNKNOWN: 214>
VECTOR = <TokenType.VECTOR: 215>
DYNAMIC = <TokenType.DYNAMIC: 216>
VOID = <TokenType.VOID: 217>
ALIAS = <TokenType.ALIAS: 218>
ALTER = <TokenType.ALTER: 219>
ALL = <TokenType.ALL: 220>
ANTI = <TokenType.ANTI: 221>
ANY = <TokenType.ANY: 222>
APPLY = <TokenType.APPLY: 223>
ARRAY = <TokenType.ARRAY: 224>
ASC = <TokenType.ASC: 225>
ASOF = <TokenType.ASOF: 226>
ATTACH = <TokenType.ATTACH: 227>
AUTO_INCREMENT = <TokenType.AUTO_INCREMENT: 228>
BEGIN = <TokenType.BEGIN: 229>
BETWEEN = <TokenType.BETWEEN: 230>
BULK_COLLECT_INTO = <TokenType.BULK_COLLECT_INTO: 231>
CACHE = <TokenType.CACHE: 232>
CASE = <TokenType.CASE: 233>
CHARACTER_SET = <TokenType.CHARACTER_SET: 234>
CLUSTER_BY = <TokenType.CLUSTER_BY: 235>
COLLATE = <TokenType.COLLATE: 236>
COMMAND = <TokenType.COMMAND: 237>
COMMENT = <TokenType.COMMENT: 238>
COMMIT = <TokenType.COMMIT: 239>
CONNECT_BY = <TokenType.CONNECT_BY: 240>
CONSTRAINT = <TokenType.CONSTRAINT: 241>
COPY = <TokenType.COPY: 242>
CREATE = <TokenType.CREATE: 243>
CROSS = <TokenType.CROSS: 244>
CUBE = <TokenType.CUBE: 245>
CURRENT_DATE = <TokenType.CURRENT_DATE: 246>
CURRENT_DATETIME = <TokenType.CURRENT_DATETIME: 247>
CURRENT_SCHEMA = <TokenType.CURRENT_SCHEMA: 248>
CURRENT_TIME = <TokenType.CURRENT_TIME: 249>
CURRENT_TIMESTAMP = <TokenType.CURRENT_TIMESTAMP: 250>
CURRENT_USER = <TokenType.CURRENT_USER: 251>
CURRENT_USER_ID = <TokenType.CURRENT_USER_ID: 252>
CURRENT_ROLE = <TokenType.CURRENT_ROLE: 253>
CURRENT_CATALOG = <TokenType.CURRENT_CATALOG: 254>
DECLARE = <TokenType.DECLARE: 255>
DEFAULT = <TokenType.DEFAULT: 256>
DELETE = <TokenType.DELETE: 257>
DESC = <TokenType.DESC: 258>
DESCRIBE = <TokenType.DESCRIBE: 259>
DETACH = <TokenType.DETACH: 260>
DICTIONARY = <TokenType.DICTIONARY: 261>
DISTINCT = <TokenType.DISTINCT: 262>
DISTRIBUTE_BY = <TokenType.DISTRIBUTE_BY: 263>
DIV = <TokenType.DIV: 264>
DROP = <TokenType.DROP: 265>
ELSE = <TokenType.ELSE: 266>
END = <TokenType.END: 267>
ESCAPE = <TokenType.ESCAPE: 268>
EXCEPT = <TokenType.EXCEPT: 269>
EXECUTE = <TokenType.EXECUTE: 270>
EXISTS = <TokenType.EXISTS: 271>
FALSE = <TokenType.FALSE: 272>
FETCH = <TokenType.FETCH: 273>
FILE = <TokenType.FILE: 274>
FILE_FORMAT = <TokenType.FILE_FORMAT: 275>
FILTER = <TokenType.FILTER: 276>
FINAL = <TokenType.FINAL: 277>
FIRST = <TokenType.FIRST: 278>
FOR = <TokenType.FOR: 279>
FORCE = <TokenType.FORCE: 280>
FOREIGN_KEY = <TokenType.FOREIGN_KEY: 281>
FORMAT = <TokenType.FORMAT: 282>
FROM = <TokenType.FROM: 283>
FULL = <TokenType.FULL: 284>
FUNCTION = <TokenType.FUNCTION: 285>
GET = <TokenType.GET: 286>
GLOB = <TokenType.GLOB: 287>
GLOBAL = <TokenType.GLOBAL: 288>
GRANT = <TokenType.GRANT: 289>
GROUP_BY = <TokenType.GROUP_BY: 290>
GROUPING_SETS = <TokenType.GROUPING_SETS: 291>
HAVING = <TokenType.HAVING: 292>
HINT = <TokenType.HINT: 293>
IGNORE = <TokenType.IGNORE: 294>
ILIKE = <TokenType.ILIKE: 295>
IN = <TokenType.IN: 296>
INDEX = <TokenType.INDEX: 297>
INDEXED_BY = <TokenType.INDEXED_BY: 298>
INNER = <TokenType.INNER: 299>
INSERT = <TokenType.INSERT: 300>
INSTALL = <TokenType.INSTALL: 301>
INTEGRATION = <TokenType.INTEGRATION: 302>
INTERSECT = <TokenType.INTERSECT: 303>
INTERVAL = <TokenType.INTERVAL: 304>
INTO = <TokenType.INTO: 305>
INTRODUCER = <TokenType.INTRODUCER: 306>
IRLIKE = <TokenType.IRLIKE: 307>
IS = <TokenType.IS: 308>
ISNULL = <TokenType.ISNULL: 309>
JOIN = <TokenType.JOIN: 310>
JOIN_MARKER = <TokenType.JOIN_MARKER: 311>
KEEP = <TokenType.KEEP: 312>
KEY = <TokenType.KEY: 313>
KILL = <TokenType.KILL: 314>
LANGUAGE = <TokenType.LANGUAGE: 315>
LATERAL = <TokenType.LATERAL: 316>
LEFT = <TokenType.LEFT: 317>
LIKE = <TokenType.LIKE: 318>
LIMIT = <TokenType.LIMIT: 319>
LIST = <TokenType.LIST: 320>
LOAD = <TokenType.LOAD: 321>
LOCK = <TokenType.LOCK: 322>
MAP = <TokenType.MAP: 323>
MATCH = <TokenType.MATCH: 324>
MATCH_CONDITION = <TokenType.MATCH_CONDITION: 325>
MATCH_RECOGNIZE = <TokenType.MATCH_RECOGNIZE: 326>
MEMBER_OF = <TokenType.MEMBER_OF: 327>
MERGE = <TokenType.MERGE: 328>
MOD = <TokenType.MOD: 329>
MODEL = <TokenType.MODEL: 330>
NATURAL = <TokenType.NATURAL: 331>
NEXT = <TokenType.NEXT: 332>
NOTHING = <TokenType.NOTHING: 333>
NOTNULL = <TokenType.NOTNULL: 334>
NULL = <TokenType.NULL: 335>
OBJECT_IDENTIFIER = <TokenType.OBJECT_IDENTIFIER: 336>
OFFSET = <TokenType.OFFSET: 337>
ON = <TokenType.ON: 338>
ONLY = <TokenType.ONLY: 339>
OPERATOR = <TokenType.OPERATOR: 340>
ORDER_BY = <TokenType.ORDER_BY: 341>
ORDER_SIBLINGS_BY = <TokenType.ORDER_SIBLINGS_BY: 342>
ORDERED = <TokenType.ORDERED: 343>
ORDINALITY = <TokenType.ORDINALITY: 344>
OUT = <TokenType.OUT: 345>
INOUT = <TokenType.INOUT: 346>
OUTER = <TokenType.OUTER: 347>
OVER = <TokenType.OVER: 348>
OVERLAPS = <TokenType.OVERLAPS: 349>
OVERWRITE = <TokenType.OVERWRITE: 350>
PACKAGE = <TokenType.PACKAGE: 351>
PARTITION = <TokenType.PARTITION: 352>
PARTITION_BY = <TokenType.PARTITION_BY: 353>
PERCENT = <TokenType.PERCENT: 354>
PIVOT = <TokenType.PIVOT: 355>
PLACEHOLDER = <TokenType.PLACEHOLDER: 356>
POLICY = <TokenType.POLICY: 357>
POOL = <TokenType.POOL: 358>
POSITIONAL = <TokenType.POSITIONAL: 359>
PRAGMA = <TokenType.PRAGMA: 360>
PREWHERE = <TokenType.PREWHERE: 361>
PRIMARY_KEY = <TokenType.PRIMARY_KEY: 362>
PROCEDURE = <TokenType.PROCEDURE: 363>
PROPERTIES = <TokenType.PROPERTIES: 364>
PROJECTION = <TokenType.PROJECTION: 365>
PSEUDO_TYPE = <TokenType.PSEUDO_TYPE: 366>
PUT = <TokenType.PUT: 367>
QUALIFY = <TokenType.QUALIFY: 368>
QUOTE = <TokenType.QUOTE: 369>
QDCOLON = <TokenType.QDCOLON: 370>
RANGE = <TokenType.RANGE: 371>
RECURSIVE = <TokenType.RECURSIVE: 372>
REFRESH = <TokenType.REFRESH: 373>
RENAME = <TokenType.RENAME: 374>
REPLACE = <TokenType.REPLACE: 375>
RETURNING = <TokenType.RETURNING: 376>
REVOKE = <TokenType.REVOKE: 377>
REFERENCES = <TokenType.REFERENCES: 378>
RIGHT = <TokenType.RIGHT: 379>
RLIKE = <TokenType.RLIKE: 380>
ROLE = <TokenType.ROLE: 381>
ROLLBACK = <TokenType.ROLLBACK: 382>
ROLLUP = <TokenType.ROLLUP: 383>
ROW = <TokenType.ROW: 384>
ROWS = <TokenType.ROWS: 385>
RULE = <TokenType.RULE: 386>
SELECT = <TokenType.SELECT: 387>
SEMI = <TokenType.SEMI: 388>
SEPARATOR = <TokenType.SEPARATOR: 389>
SEQUENCE = <TokenType.SEQUENCE: 390>
SERDE_PROPERTIES = <TokenType.SERDE_PROPERTIES: 391>
SET = <TokenType.SET: 392>
SETTINGS = <TokenType.SETTINGS: 393>
SHOW = <TokenType.SHOW: 394>
SIMILAR_TO = <TokenType.SIMILAR_TO: 395>
SOME = <TokenType.SOME: 396>
SORT_BY = <TokenType.SORT_BY: 397>
SOUNDS_LIKE = <TokenType.SOUNDS_LIKE: 398>
SQL_SECURITY = <TokenType.SQL_SECURITY: 399>
START_WITH = <TokenType.START_WITH: 400>
STORAGE_INTEGRATION = <TokenType.STORAGE_INTEGRATION: 401>
STRAIGHT_JOIN = <TokenType.STRAIGHT_JOIN: 402>
STRUCT = <TokenType.STRUCT: 403>
SUMMARIZE = <TokenType.SUMMARIZE: 404>
TABLE_SAMPLE = <TokenType.TABLE_SAMPLE: 405>
TAG = <TokenType.TAG: 406>
TEMPORARY = <TokenType.TEMPORARY: 407>
TOP = <TokenType.TOP: 408>
THEN = <TokenType.THEN: 409>
TRUE = <TokenType.TRUE: 410>
TRUNCATE = <TokenType.TRUNCATE: 411>
TRIGGER = <TokenType.TRIGGER: 412>
TYPE = <TokenType.TYPE: 413>
UNCACHE = <TokenType.UNCACHE: 414>
UNDROP = <TokenType.UNDROP: 415>
UNION = <TokenType.UNION: 416>
UNNEST = <TokenType.UNNEST: 417>
UNPIVOT = <TokenType.UNPIVOT: 418>
UPDATE = <TokenType.UPDATE: 419>
USE = <TokenType.USE: 420>
USING = <TokenType.USING: 421>
VALUES = <TokenType.VALUES: 422>
VARIADIC = <TokenType.VARIADIC: 423>
VIEW = <TokenType.VIEW: 424>
SEMANTIC_VIEW = <TokenType.SEMANTIC_VIEW: 425>
VOLATILE = <TokenType.VOLATILE: 426>
VOLUME = <TokenType.VOLUME: 427>
WHEN = <TokenType.WHEN: 428>
WHERE = <TokenType.WHERE: 429>
WINDOW = <TokenType.WINDOW: 430>
WITH = <TokenType.WITH: 431>
UNIQUE = <TokenType.UNIQUE: 432>
UTC_DATE = <TokenType.UTC_DATE: 433>
UTC_TIME = <TokenType.UTC_TIME: 434>
UTC_TIMESTAMP = <TokenType.UTC_TIMESTAMP: 435>
OPTION = <TokenType.OPTION: 436>
SINK = <TokenType.SINK: 437>
SOURCE = <TokenType.SOURCE: 438>
ANALYZE = <TokenType.ANALYZE: 439>
NAMESPACE = <TokenType.NAMESPACE: 440>
EXPORT = <TokenType.EXPORT: 441>
HIVE_TOKEN_STREAM = <TokenType.HIVE_TOKEN_STREAM: 442>
SENTINEL = <TokenType.SENTINEL: 443>
class Token:
475class Token:
476    # mypyc doesn't expose slots
477    _attrs: t.ClassVar[tuple[str, ...]] = (
478        "token_type",
479        "text",
480        "line",
481        "col",
482        "start",
483        "end",
484        "comments",
485    )
486    __slots__ = _attrs
487
488    @classmethod
489    def number(cls, number: int) -> Token:
490        """Returns a NUMBER token with `number` as its text."""
491        return cls(TokenType.NUMBER, str(number))
492
493    @classmethod
494    def string(cls, string: str) -> Token:
495        """Returns a STRING token with `string` as its text."""
496        return cls(TokenType.STRING, string)
497
498    @classmethod
499    def identifier(cls, identifier: str) -> Token:
500        """Returns an IDENTIFIER token with `identifier` as its text."""
501        return cls(TokenType.IDENTIFIER, identifier)
502
503    @classmethod
504    def var(cls, var: str) -> Token:
505        """Returns an VAR token with `var` as its text."""
506        return cls(TokenType.VAR, var)
507
508    def __init__(
509        self,
510        token_type: TokenType,
511        text: str,
512        line: int = 1,
513        col: int = 1,
514        start: int = 0,
515        end: int = 0,
516        comments: list[str] | None = None,
517    ) -> None:
518        self.token_type = token_type
519        self.text = text
520        self.line = line
521        self.col = col
522        self.start = start
523        self.end = end
524        self.comments = [] if comments is None else comments
525
526    def __bool__(self) -> bool:
527        return self.token_type != TokenType.SENTINEL
528
529    def __repr__(self) -> str:
530        attributes = ", ".join(
531            f"{k}: TokenType.{self.token_type.name}"
532            if k == "token_type"
533            else f"{k}: {getattr(self, k)}"
534            for k in self._attrs
535        )
536        return f"<Token {attributes}>"
Token( token_type: TokenType, text: str, line: int = 1, col: int = 1, start: int = 0, end: int = 0, comments: list[str] | None = None)
508    def __init__(
509        self,
510        token_type: TokenType,
511        text: str,
512        line: int = 1,
513        col: int = 1,
514        start: int = 0,
515        end: int = 0,
516        comments: list[str] | None = None,
517    ) -> None:
518        self.token_type = token_type
519        self.text = text
520        self.line = line
521        self.col = col
522        self.start = start
523        self.end = end
524        self.comments = [] if comments is None else comments
@classmethod
def number(cls, number: int) -> Token:
488    @classmethod
489    def number(cls, number: int) -> Token:
490        """Returns a NUMBER token with `number` as its text."""
491        return cls(TokenType.NUMBER, str(number))

Returns a NUMBER token with number as its text.

@classmethod
def string(cls, string: str) -> Token:
493    @classmethod
494    def string(cls, string: str) -> Token:
495        """Returns a STRING token with `string` as its text."""
496        return cls(TokenType.STRING, string)

Returns a STRING token with string as its text.

@classmethod
def identifier(cls, identifier: str) -> Token:
498    @classmethod
499    def identifier(cls, identifier: str) -> Token:
500        """Returns an IDENTIFIER token with `identifier` as its text."""
501        return cls(TokenType.IDENTIFIER, identifier)

Returns an IDENTIFIER token with identifier as its text.

@classmethod
def var(cls, var: str) -> Token:
503    @classmethod
504    def var(cls, var: str) -> Token:
505        """Returns an VAR token with `var` as its text."""
506        return cls(TokenType.VAR, var)

Returns an VAR token with var as its text.

token_type
text
line
col
start
end
comments
class TokenizerCore:
 539class TokenizerCore:
 540    __slots__ = (
 541        "sql",
 542        "size",
 543        "tokens",
 544        "_start",
 545        "_current",
 546        "_line",
 547        "_col",
 548        "_comments",
 549        "_char",
 550        "_end",
 551        "_peek",
 552        "_prev_token_line",
 553        "single_tokens",
 554        "keywords",
 555        "quotes",
 556        "format_strings",
 557        "identifiers",
 558        "comments",
 559        "string_escapes",
 560        "byte_string_escapes",
 561        "identifier_escapes",
 562        "escape_follow_chars",
 563        "commands",
 564        "command_prefix_tokens",
 565        "nested_comments",
 566        "hint_start",
 567        "tokens_preceding_hint",
 568        "has_bit_strings",
 569        "has_hex_strings",
 570        "numeric_literals",
 571        "var_single_tokens",
 572        "string_escapes_allowed_in_raw_strings",
 573        "heredoc_tag_is_identifier",
 574        "heredoc_string_alternative",
 575        "keyword_trie",
 576        "numbers_can_be_underscore_separated",
 577        "numbers_can_have_decimals",
 578        "identifiers_can_start_with_digit",
 579        "unescaped_sequences",
 580        "numeric_escapes",
 581        "drop_unknown_escapes",
 582    )
 583
 584    def __init__(
 585        self,
 586        single_tokens: dict[str, TokenType],
 587        keywords: dict[str, TokenType],
 588        quotes: dict[str, str],
 589        format_strings: dict[str, tuple[str, TokenType]],
 590        identifiers: dict[str, str],
 591        comments: dict[str, str | None],
 592        string_escapes: set[str],
 593        byte_string_escapes: set[str],
 594        identifier_escapes: set[str],
 595        escape_follow_chars: set[str],
 596        commands: set[TokenType],
 597        command_prefix_tokens: set[TokenType],
 598        nested_comments: bool,
 599        hint_start: str,
 600        tokens_preceding_hint: set[TokenType],
 601        has_bit_strings: bool,
 602        has_hex_strings: bool,
 603        numeric_literals: dict[str, str],
 604        var_single_tokens: set[str],
 605        string_escapes_allowed_in_raw_strings: bool,
 606        heredoc_tag_is_identifier: bool,
 607        heredoc_string_alternative: TokenType,
 608        keyword_trie: dict,
 609        numbers_can_be_underscore_separated: bool,
 610        numbers_can_have_decimals: bool,
 611        identifiers_can_start_with_digit: bool,
 612        unescaped_sequences: dict[str, str],
 613        numeric_escapes: dict[str, tuple[int, int, int, int]],
 614        drop_unknown_escapes: bool,
 615    ) -> None:
 616        self.single_tokens = single_tokens
 617        self.keywords = keywords
 618        self.quotes = quotes
 619        self.format_strings = format_strings
 620        self.identifiers = identifiers
 621        self.comments = comments
 622        self.string_escapes = string_escapes
 623        self.byte_string_escapes = byte_string_escapes
 624        self.identifier_escapes = identifier_escapes
 625        self.escape_follow_chars = escape_follow_chars
 626        self.commands = commands
 627        self.command_prefix_tokens = command_prefix_tokens
 628        self.nested_comments = nested_comments
 629        self.hint_start = hint_start
 630        self.tokens_preceding_hint = tokens_preceding_hint
 631        self.has_bit_strings = has_bit_strings
 632        self.has_hex_strings = has_hex_strings
 633        self.numeric_literals = numeric_literals
 634        self.var_single_tokens = var_single_tokens
 635        self.string_escapes_allowed_in_raw_strings = string_escapes_allowed_in_raw_strings
 636        self.heredoc_tag_is_identifier = heredoc_tag_is_identifier
 637        self.heredoc_string_alternative = heredoc_string_alternative
 638        self.keyword_trie = keyword_trie
 639        self.numbers_can_be_underscore_separated = numbers_can_be_underscore_separated
 640        self.numbers_can_have_decimals = numbers_can_have_decimals
 641        self.identifiers_can_start_with_digit = identifiers_can_start_with_digit
 642        self.unescaped_sequences = unescaped_sequences
 643        self.numeric_escapes = numeric_escapes
 644        self.drop_unknown_escapes = drop_unknown_escapes
 645        self.sql = ""
 646        self.size = 0
 647        self.tokens: list[Token] = []
 648        self._start = 0
 649        self._current = 0
 650        self._line = 1
 651        self._col = 0
 652        self._comments: list[str] = []
 653        self._char = ""
 654        self._end = False
 655        self._peek = ""
 656        self._prev_token_line = -1
 657
 658    def reset(self) -> None:
 659        self.sql = ""
 660        self.size = 0
 661        self.tokens = []
 662        self._start = 0
 663        self._current = 0
 664        self._line = 1
 665        self._col = 0
 666        self._comments = []
 667        self._char = ""
 668        self._end = False
 669        self._peek = ""
 670        self._prev_token_line = -1
 671
 672    def tokenize(self, sql: str) -> list[Token]:
 673        """Returns a list of tokens corresponding to the SQL string `sql`."""
 674        self.reset()
 675        self.sql = sql
 676        self.size = len(sql)
 677
 678        try:
 679            self._scan()
 680        except Exception as e:
 681            start = max(self._current - 50, 0)
 682            end = min(self._current + 50, self.size - 1)
 683            context = self.sql[start:end]
 684            raise TokenError(f"Error tokenizing '{context}'", start=start, end=end) from e
 685
 686        return self.tokens
 687
 688    def _scan(self, check_semicolon: bool = False) -> None:
 689        identifiers = self.identifiers
 690        digit_chars = _DIGIT_CHARS
 691
 692        while self.size and not self._end:
 693            current = self._current
 694
 695            # Skip spaces here rather than iteratively calling advance() for performance reasons
 696            while current < self.size:
 697                char = self.sql[current]
 698
 699                if char == " " or char == "\t":
 700                    current += 1
 701                else:
 702                    break
 703
 704            offset = current - self._current if current > self._current else 1
 705
 706            self._start = current
 707            self._advance(offset)
 708
 709            if not self._char.isspace():
 710                if self._char in digit_chars:
 711                    self._scan_number()
 712                elif self._char in identifiers:
 713                    self._scan_identifier(identifiers[self._char])
 714                else:
 715                    self._scan_keywords()
 716
 717            if check_semicolon and self._peek == ";":
 718                break
 719
 720        if self.tokens and self._comments:
 721            self.tokens[-1].comments.extend(self._comments)
 722
 723    def _chars(self, size: int) -> str:
 724        if size == 1:
 725            return self._char
 726
 727        start = self._current - 1
 728        end = start + size
 729
 730        return self.sql[start:end] if end <= self.size else ""
 731
 732    def _advance(self, i: int = 1, alnum: bool = False) -> None:
 733        char = self._char
 734
 735        if char == "\n" or char == "\r":
 736            # Ensures we don't count an extra line if we get a \r\n line break sequence
 737            if not (char == "\r" and self._peek == "\n"):
 738                self._col = i
 739                self._line += 1
 740        else:
 741            self._col += i
 742
 743        self._current += i
 744        sql = self.sql
 745        size = self.size
 746        self._end = self._current >= size
 747        self._char = sql[self._current - 1]
 748        self._peek = "" if self._end else sql[self._current]
 749
 750        if alnum and self._char.isalnum():
 751            # Cache to local variables instead of attributes for better performance
 752            _col = self._col
 753            _current = self._current
 754            _end = self._end
 755            _peek = self._peek
 756
 757            while _peek.isalnum():
 758                _col += 1
 759                _current += 1
 760                _end = _current >= size
 761                _peek = "" if _end else sql[_current]
 762
 763            self._col = _col
 764            self._current = _current
 765            self._end = _end
 766            self._peek = _peek
 767            self._char = sql[_current - 1]
 768
 769    @property
 770    def _text(self) -> str:
 771        return self.sql[self._start : self._current]
 772
 773    def _add(self, token_type: TokenType, text: str | None = None) -> None:
 774        self._prev_token_line = self._line
 775
 776        if self._comments and token_type == TokenType.SEMICOLON and self.tokens:
 777            self.tokens[-1].comments.extend(self._comments)
 778            self._comments = []
 779
 780        if text is None:
 781            text = self.sql[self._start : self._current]
 782
 783        self.tokens.append(
 784            Token(
 785                token_type,
 786                text=text,
 787                line=self._line,
 788                col=self._col,
 789                start=self._start,
 790                end=self._current - 1,
 791                comments=self._comments,
 792            )
 793        )
 794        self._comments = []
 795
 796        # If we have either a semicolon or a begin token before the command's token, we'll parse
 797        # whatever follows the command's token as a string
 798        if (
 799            token_type in self.commands
 800            and self._peek != ";"
 801            and (len(self.tokens) == 1 or self.tokens[-2].token_type in self.command_prefix_tokens)
 802        ):
 803            start = self._current
 804            tokens = len(self.tokens)
 805            self._scan(check_semicolon=True)
 806            self.tokens = self.tokens[:tokens]
 807            text = self.sql[start : self._current].strip()
 808            if text:
 809                self._add(TokenType.STRING, text)
 810
 811    def _scan_keywords(self) -> None:
 812        sql = self.sql
 813        sql_size = self.size
 814        single_tokens = self.single_tokens
 815        char_upper = _CHAR_UPPER
 816        size = 0
 817        word = None
 818        chars = self._char
 819        char = chars
 820        prev_space = False
 821        skip = False
 822        trie = self.keyword_trie
 823        single_token = char in single_tokens
 824
 825        while chars:
 826            if not skip:
 827                sub = trie.get(char_upper.get(char, char))
 828                if sub is None:
 829                    break
 830                trie = sub
 831                if 0 in trie:
 832                    word = chars
 833
 834            end = self._current + size
 835            size += 1
 836
 837            if end < sql_size:
 838                char = sql[end]
 839                single_token = single_token or char in single_tokens
 840                is_space = char.isspace()
 841
 842                if not is_space or not prev_space:
 843                    if is_space:
 844                        char = " "
 845                    chars += char
 846                    prev_space = is_space
 847                    skip = False
 848                else:
 849                    skip = True
 850            else:
 851                char = ""
 852                break
 853
 854        if word:
 855            if self._scan_string(word):
 856                return
 857            if self._scan_comment(word):
 858                return
 859            if prev_space or single_token or not char:
 860                self._advance(size - 1)
 861                word = word.upper()
 862                self._add(self.keywords[word], text=word)
 863                return
 864
 865        if self._char in single_tokens:
 866            self._add(single_tokens[self._char], text=self._char)
 867            return
 868
 869        self._scan_var()
 870
 871    def _scan_comment(self, comment_start: str) -> bool:
 872        if comment_start not in self.comments:
 873            return False
 874
 875        comment_start_line = self._line
 876        comment_start_size = len(comment_start)
 877        comment_end = self.comments[comment_start]
 878
 879        if comment_end:
 880            # Skip the comment's start delimiter
 881            self._advance(comment_start_size)
 882
 883            comment_count = 1
 884            comment_end_size = len(comment_end)
 885            nested_comments = self.nested_comments
 886
 887            while not self._end:
 888                if self._chars(comment_end_size) == comment_end:
 889                    comment_count -= 1
 890                    if not comment_count:
 891                        break
 892
 893                self._advance(alnum=True)
 894
 895                # Nested comments are allowed by some dialects, e.g. databricks, duckdb, postgres
 896                if (
 897                    nested_comments
 898                    and not self._end
 899                    and self._chars(comment_end_size) == comment_start
 900                ):
 901                    self._advance(comment_start_size)
 902                    comment_count += 1
 903
 904            self._comments.append(self._text[comment_start_size : -comment_end_size + 1])
 905            self._advance(comment_end_size - 1)
 906        else:
 907            _peek = self._peek
 908            while not self._end and _peek != "\n" and _peek != "\r":
 909                self._advance(alnum=True)
 910                _peek = self._peek
 911            self._comments.append(self._text[comment_start_size:])
 912
 913        if (
 914            comment_start == self.hint_start
 915            and self.tokens
 916            and self.tokens[-1].token_type in self.tokens_preceding_hint
 917        ):
 918            self._add(TokenType.HINT)
 919
 920        # Leading comment is attached to the succeeding token, whilst trailing comment to the preceding.
 921        # Multiple consecutive comments are preserved by appending them to the current comments list.
 922        if comment_start_line == self._prev_token_line:
 923            self.tokens[-1].comments.extend(self._comments)
 924            self._comments = []
 925            self._prev_token_line = self._line
 926
 927        return True
 928
 929    def _scan_number(self) -> None:
 930        if self._char == "0":
 931            peek = _CHAR_UPPER.get(self._peek, self._peek)
 932            if peek == "B" and self.has_bit_strings:
 933                return self._scan_bits()
 934            elif peek == "X":
 935                return self._scan_hex() if self.has_hex_strings else self._add(TokenType.NUMBER)
 936
 937        decimal = False
 938        scientific = 0
 939        numbers_can_be_underscore_separated = self.numbers_can_be_underscore_separated
 940        single_tokens = self.single_tokens
 941        keywords = self.keywords
 942        numeric_literals = self.numeric_literals
 943        identifiers_can_start_with_digit = self.identifiers_can_start_with_digit
 944
 945        is_underscore_separated: bool = False
 946        number_text: str = ""
 947        numeric_literal: str = ""
 948        numeric_type: TokenType | None = None
 949
 950        while True:
 951            if self._peek in _DIGIT_CHARS:
 952                # Batch consecutive digits: scan ahead to find how many
 953                sql = self.sql
 954                end = self._current + 1
 955                size = self.size
 956                while end < size and sql[end] in _DIGIT_CHARS:
 957                    end += 1
 958                self._advance(end - self._current)
 959            elif self._peek == "." and not decimal:
 960                if (
 961                    self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER
 962                ) or not self.numbers_can_have_decimals:
 963                    break
 964                decimal = True
 965                self._advance()
 966            elif self._peek in ("-", "+") and scientific == 1:
 967                # Only consume +/- if followed by a digit
 968                if self._current + 1 < self.size and self.sql[self._current + 1] in _DIGIT_CHARS:
 969                    scientific += 1
 970                    self._advance()
 971                else:
 972                    break
 973            elif _CHAR_UPPER.get(self._peek, self._peek) == "E" and not scientific:
 974                scientific += 1
 975                self._advance()
 976            elif self._peek == "_" and numbers_can_be_underscore_separated:
 977                is_underscore_separated = True
 978                self._advance()
 979            elif self._peek.isidentifier():
 980                number_text = self._text
 981
 982                while self._peek and not self._peek.isspace() and self._peek not in single_tokens:
 983                    numeric_literal += self._peek
 984                    self._advance()
 985
 986                numeric_type = keywords.get(numeric_literals.get(numeric_literal.upper(), ""))
 987
 988                if numeric_type:
 989                    break
 990                elif identifiers_can_start_with_digit:
 991                    return self._add(TokenType.VAR)
 992
 993                self._advance(-len(numeric_literal))
 994                break
 995            else:
 996                break
 997
 998        number_text = number_text or self.sql[self._start : self._current]
 999
1000        # Normalize inputs such as 100_000 to 100000
1001        if is_underscore_separated:
1002            number_text = number_text.replace("_", "")
1003
1004        self._add(TokenType.NUMBER, number_text)
1005
1006        # Normalize inputs such as 123L to 123::BIGINT so that they're parsed as casts
1007        if numeric_type:
1008            self._add(TokenType.DCOLON, "::")
1009            self._add(numeric_type, numeric_literal)
1010
1011    def _scan_bits(self) -> None:
1012        self._advance()
1013        value = self._extract_value()
1014        try:
1015            # If `value` can't be converted to a binary, fallback to tokenizing it as an identifier
1016            int(value, 2)
1017            self._add(TokenType.BIT_STRING, value[2:])  # Drop the 0b
1018        except ValueError:
1019            self._add(TokenType.IDENTIFIER)
1020
1021    def _scan_hex(self) -> None:
1022        self._advance()
1023        value = self._extract_value()
1024        try:
1025            # If `value` can't be converted to a hex, fallback to tokenizing it as an identifier
1026            int(value, 16)
1027            self._add(TokenType.HEX_STRING, value[2:])  # Drop the 0x
1028        except ValueError:
1029            self._add(TokenType.IDENTIFIER)
1030
1031    def _extract_value(self) -> str:
1032        single_tokens = self.single_tokens
1033
1034        while True:
1035            char = self._peek.strip()
1036            if char and char not in single_tokens:
1037                self._advance(alnum=True)
1038            else:
1039                break
1040
1041        return self._text
1042
1043    def _scan_string(self, start: str) -> bool:
1044        base = None
1045        token_type = TokenType.STRING
1046
1047        if start in self.quotes:
1048            end = self.quotes[start]
1049        elif start in self.format_strings:
1050            end, token_type = self.format_strings[start]
1051
1052            if token_type == TokenType.HEX_STRING:
1053                base = 16
1054            elif token_type == TokenType.BIT_STRING:
1055                base = 2
1056            elif token_type == TokenType.HEREDOC_STRING:
1057                self._advance()
1058
1059                if self._char == end:
1060                    tag = ""
1061                else:
1062                    tag = self._extract_string(
1063                        end,
1064                        raw_string=True,
1065                        raise_unmatched=not self.heredoc_tag_is_identifier,
1066                    )
1067
1068                if (
1069                    tag
1070                    and self.heredoc_tag_is_identifier
1071                    and (self._end or tag.isdigit() or any(c.isspace() for c in tag))
1072                ):
1073                    if not self._end:
1074                        self._advance(-1)
1075
1076                    self._advance(-len(tag))
1077                    self._add(self.heredoc_string_alternative)
1078                    return True
1079
1080                end = f"{start}{tag}{end}"
1081        else:
1082            return False
1083
1084        self._advance(len(start))
1085        text = self._extract_string(
1086            end,
1087            escapes=(
1088                self.byte_string_escapes
1089                if token_type == TokenType.BYTE_STRING
1090                else self.string_escapes
1091            ),
1092            raw_string=token_type == TokenType.RAW_STRING,
1093        )
1094
1095        if base and text:
1096            try:
1097                int(text, base)
1098            except Exception:
1099                raise TokenError(
1100                    f"Numeric string contains invalid characters from {self._line}:{self._start}"
1101                )
1102
1103        self._add(token_type, text)
1104        return True
1105
1106    def _scan_identifier(self, identifier_end: str) -> None:
1107        self._advance()
1108        text = self._extract_string(
1109            identifier_end, escapes=self.identifier_escapes | {identifier_end}
1110        )
1111        self._add(TokenType.IDENTIFIER, text)
1112
1113    def _scan_var(self) -> None:
1114        var_single_tokens = self.var_single_tokens
1115        single_tokens = self.single_tokens
1116
1117        while True:
1118            peek = self._peek
1119            if not peek or peek.isspace():
1120                break
1121            if peek not in var_single_tokens and peek in single_tokens:
1122                break
1123            self._advance(alnum=True)
1124
1125        self._add(
1126            TokenType.VAR
1127            if self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER
1128            else self.keywords.get(self.sql[self._start : self._current].upper(), TokenType.VAR)
1129        )
1130
1131    def _read_numeric_escape(
1132        self, start: int, base: int, min_digits: int, max_digits: int, max_value: int
1133    ) -> tuple[int, int]:
1134        """
1135        Reads up to `max_digits` digits from `start`, stopping before the value exceeds `max_value`.
1136        Returns the value and the end index, or -1 as the value if fewer than `min_digits` were read.
1137        """
1138        sql = self.sql
1139        value = 0
1140        end = start
1141        limit = min(start + max_digits, self.size)
1142
1143        while end < limit:
1144            digit = _DIGIT_VALUES.get(sql[end], 16)
1145            if digit >= base or value * base + digit > max_value:
1146                break
1147            value = value * base + digit
1148            end += 1
1149
1150        return (value, end) if end - start >= min_digits else (-1, end)
1151
1152    def _scan_numeric_escape(self) -> str:
1153        """
1154        Decodes the numeric escape sequence starting at the current char, which is a backslash,
1155        according to `numeric_escapes`. Returns an empty string if nothing is decoded.
1156        """
1157        sql = self.sql
1158        start = self._current
1159        peek = self._peek
1160
1161        # Octal escapes start with a digit (e.g. \101), the others with a letter (e.g. \x41)
1162        is_octal = peek in _OCTAL_CHARS
1163        spec = self.numeric_escapes.get("0" if is_octal else peek)
1164
1165        if spec:
1166            base, min_digits, max_digits, max_value = spec
1167            value, end = self._read_numeric_escape(
1168                start if is_octal else start + 1, base, min_digits, max_digits, max_value
1169            )
1170
1171            # A UTF-16 surrogate is only valid as a high half (D800-DBFF) followed by an escaped
1172            # low half (DC00-DFFF), e.g. \uD83D\uDE00 is U+1F600
1173            if 0xD800 <= value <= 0xDFFF:
1174                low, low_end = -1, end
1175                if value <= 0xDBFF and sql[end : end + 2] == "\\" + peek:
1176                    low, low_end = self._read_numeric_escape(
1177                        end + 2, base, min_digits, max_digits, max_value
1178                    )
1179
1180                if 0xDC00 <= low <= 0xDFFF:
1181                    # Combine the two halves into a single code point (standard formula)
1182                    value = 0x10000 + ((value - 0xD800) << 10) + (low - 0xDC00)
1183                    end = low_end
1184                else:
1185                    # A lone surrogate isn't a character, so the escape isn't decoded
1186                    value = -1
1187
1188            # -1 means the escape isn't decoded, e.g. because it has too few digits. An escape at the
1189            # end of the input is left to the caller, which reports the unterminated string
1190            if value >= 0 and end < self.size:
1191                # Move past the whole escape, from the backslash through its last digit
1192                self._advance(end - start + 1)
1193                return chr(value)
1194
1195        return ""
1196
1197    def _extract_string(
1198        self,
1199        delimiter: str,
1200        escapes: set[str] | None = None,
1201        raw_string: bool = False,
1202        raise_unmatched: bool = True,
1203    ) -> str:
1204        text = ""
1205        delim_size = len(delimiter)
1206        escapes = self.string_escapes if escapes is None else escapes
1207        unescaped_sequences = self.unescaped_sequences
1208        escape_follow_chars = self.escape_follow_chars
1209        numeric_escapes = self.numeric_escapes
1210        drop_unknown_escapes = self.drop_unknown_escapes
1211        string_escapes_allowed_in_raw_strings = self.string_escapes_allowed_in_raw_strings
1212        quotes = self.quotes
1213        sql = self.sql
1214
1215        # use str.find() when the string is simple... no \ or other escapes
1216        if delim_size == 1:
1217            pos = self._current - 1
1218            end = sql.find(delimiter, pos)
1219
1220            if (
1221                # the closing delimiter was found
1222                end != -1
1223                # there's no doubled delimiter (e.g. '' escape), or the delimiter isn't an escape char
1224                and (end + 1 >= self.size or sql[end + 1] != delimiter or delimiter not in escapes)
1225                # no backslash in the string that would need escape processing
1226                and (not (unescaped_sequences or "\\" in escapes) or sql.find("\\", pos, end) == -1)
1227            ):
1228                newlines = sql.count("\n", pos, end)
1229                if newlines:
1230                    self._line += newlines
1231                    self._col = end - sql.rfind("\n", pos, end)
1232                else:
1233                    self._col += end - pos
1234
1235                self._current = end + 1
1236                self._end = self._current >= self.size
1237                self._char = sql[end]
1238                self._peek = "" if self._end else sql[self._current]
1239                return sql[pos:end]
1240
1241        while True:
1242            backslash_escape = not raw_string and self._char == "\\" and "\\" in escapes
1243
1244            # Numeric escapes come first, since they may overlap fixed sequences (e.g. \000 vs \0)
1245            if backslash_escape and numeric_escapes:
1246                decoded = self._scan_numeric_escape()
1247                if decoded:
1248                    text += decoded
1249                    continue
1250
1251            # An escape at the end of the input is left to the checks below, which report the
1252            # unterminated string
1253            if (
1254                not raw_string
1255                and unescaped_sequences
1256                and self._char in escapes
1257                and self._current + 1 < self.size
1258            ):
1259                unescaped_sequence = unescaped_sequences.get(self._char + self._peek)
1260                if unescaped_sequence:
1261                    # Advance one char at a time so that line numbers are updated for a newline
1262                    if self._peek in "\n\r":
1263                        self._advance()
1264                        self._advance()
1265                    else:
1266                        self._advance(2)
1267                    text += unescaped_sequence
1268                    continue
1269
1270            # Not decoded: drop the backslash if configured, unless the string is unterminated
1271            if backslash_escape and drop_unknown_escapes and self._current + 1 < self.size:
1272                # Advance one char at a time so that line numbers are updated if `peek` is a newline
1273                peek = self._peek
1274                self._advance()
1275                self._advance()
1276                text += peek
1277                continue
1278
1279            is_valid_custom_escape = (
1280                escape_follow_chars and self._char == "\\" and self._peek not in escape_follow_chars
1281            )
1282
1283            # An escaped quote before the closing delimiter (e.g. \" in """a\"""") must be
1284            # consumed here, otherwise it'd be picked up by the delimiter check below and
1285            # terminate the string early. This is only relevant for multi-char delimiters
1286            # made up of quote chars, e.g. it shouldn't apply to Snowflake's $$ strings
1287            escaped_delimiter = self._peek == delimiter or (
1288                delim_size > 1 and self._peek == delimiter[0] and self._peek in quotes
1289            )
1290
1291            if (
1292                (string_escapes_allowed_in_raw_strings or not raw_string)
1293                and self._char in escapes
1294                and (escaped_delimiter or self._peek in escapes or is_valid_custom_escape)
1295                and (self._char not in quotes or self._char == self._peek)
1296            ):
1297                if escaped_delimiter:
1298                    text += self._peek if not raw_string else self._char + self._peek
1299                elif is_valid_custom_escape and self._char != self._peek:
1300                    text += self._peek
1301                else:
1302                    text += self._char + self._peek
1303
1304                if self._current + 1 < self.size:
1305                    self._advance(2)
1306                else:
1307                    raise TokenError(f"Missing {delimiter} from {self._line}:{self._current}")
1308            else:
1309                if self._chars(delim_size) == delimiter:
1310                    if delim_size > 1:
1311                        self._advance(delim_size - 1)
1312                    break
1313
1314                if self._end:
1315                    if not raise_unmatched:
1316                        return text + self._char
1317
1318                    raise TokenError(f"Missing {delimiter} from {self._line}:{self._start}")
1319
1320                current = self._current - 1
1321                self._advance(alnum=True)
1322                text += sql[current : self._current - 1]
1323
1324        return text
TokenizerCore( single_tokens: dict[str, TokenType], keywords: dict[str, TokenType], quotes: dict[str, str], format_strings: dict[str, tuple[str, TokenType]], identifiers: dict[str, str], comments: dict[str, str | None], string_escapes: set[str], byte_string_escapes: set[str], identifier_escapes: set[str], escape_follow_chars: set[str], commands: set[TokenType], command_prefix_tokens: set[TokenType], nested_comments: bool, hint_start: str, tokens_preceding_hint: set[TokenType], has_bit_strings: bool, has_hex_strings: bool, numeric_literals: dict[str, str], var_single_tokens: set[str], string_escapes_allowed_in_raw_strings: bool, heredoc_tag_is_identifier: bool, heredoc_string_alternative: TokenType, keyword_trie: dict, numbers_can_be_underscore_separated: bool, numbers_can_have_decimals: bool, identifiers_can_start_with_digit: bool, unescaped_sequences: dict[str, str], numeric_escapes: dict[str, tuple[int, int, int, int]], drop_unknown_escapes: bool)
584    def __init__(
585        self,
586        single_tokens: dict[str, TokenType],
587        keywords: dict[str, TokenType],
588        quotes: dict[str, str],
589        format_strings: dict[str, tuple[str, TokenType]],
590        identifiers: dict[str, str],
591        comments: dict[str, str | None],
592        string_escapes: set[str],
593        byte_string_escapes: set[str],
594        identifier_escapes: set[str],
595        escape_follow_chars: set[str],
596        commands: set[TokenType],
597        command_prefix_tokens: set[TokenType],
598        nested_comments: bool,
599        hint_start: str,
600        tokens_preceding_hint: set[TokenType],
601        has_bit_strings: bool,
602        has_hex_strings: bool,
603        numeric_literals: dict[str, str],
604        var_single_tokens: set[str],
605        string_escapes_allowed_in_raw_strings: bool,
606        heredoc_tag_is_identifier: bool,
607        heredoc_string_alternative: TokenType,
608        keyword_trie: dict,
609        numbers_can_be_underscore_separated: bool,
610        numbers_can_have_decimals: bool,
611        identifiers_can_start_with_digit: bool,
612        unescaped_sequences: dict[str, str],
613        numeric_escapes: dict[str, tuple[int, int, int, int]],
614        drop_unknown_escapes: bool,
615    ) -> None:
616        self.single_tokens = single_tokens
617        self.keywords = keywords
618        self.quotes = quotes
619        self.format_strings = format_strings
620        self.identifiers = identifiers
621        self.comments = comments
622        self.string_escapes = string_escapes
623        self.byte_string_escapes = byte_string_escapes
624        self.identifier_escapes = identifier_escapes
625        self.escape_follow_chars = escape_follow_chars
626        self.commands = commands
627        self.command_prefix_tokens = command_prefix_tokens
628        self.nested_comments = nested_comments
629        self.hint_start = hint_start
630        self.tokens_preceding_hint = tokens_preceding_hint
631        self.has_bit_strings = has_bit_strings
632        self.has_hex_strings = has_hex_strings
633        self.numeric_literals = numeric_literals
634        self.var_single_tokens = var_single_tokens
635        self.string_escapes_allowed_in_raw_strings = string_escapes_allowed_in_raw_strings
636        self.heredoc_tag_is_identifier = heredoc_tag_is_identifier
637        self.heredoc_string_alternative = heredoc_string_alternative
638        self.keyword_trie = keyword_trie
639        self.numbers_can_be_underscore_separated = numbers_can_be_underscore_separated
640        self.numbers_can_have_decimals = numbers_can_have_decimals
641        self.identifiers_can_start_with_digit = identifiers_can_start_with_digit
642        self.unescaped_sequences = unescaped_sequences
643        self.numeric_escapes = numeric_escapes
644        self.drop_unknown_escapes = drop_unknown_escapes
645        self.sql = ""
646        self.size = 0
647        self.tokens: list[Token] = []
648        self._start = 0
649        self._current = 0
650        self._line = 1
651        self._col = 0
652        self._comments: list[str] = []
653        self._char = ""
654        self._end = False
655        self._peek = ""
656        self._prev_token_line = -1
single_tokens
keywords
quotes
format_strings
identifiers
comments
string_escapes
byte_string_escapes
identifier_escapes
escape_follow_chars
commands
command_prefix_tokens
nested_comments
hint_start
tokens_preceding_hint
has_bit_strings
has_hex_strings
numeric_literals
var_single_tokens
string_escapes_allowed_in_raw_strings
heredoc_tag_is_identifier
heredoc_string_alternative
keyword_trie
numbers_can_be_underscore_separated
numbers_can_have_decimals
identifiers_can_start_with_digit
unescaped_sequences
numeric_escapes
drop_unknown_escapes
sql
size
tokens: list[Token]
def reset(self) -> None:
658    def reset(self) -> None:
659        self.sql = ""
660        self.size = 0
661        self.tokens = []
662        self._start = 0
663        self._current = 0
664        self._line = 1
665        self._col = 0
666        self._comments = []
667        self._char = ""
668        self._end = False
669        self._peek = ""
670        self._prev_token_line = -1
def tokenize(self, sql: str) -> list[Token]:
672    def tokenize(self, sql: str) -> list[Token]:
673        """Returns a list of tokens corresponding to the SQL string `sql`."""
674        self.reset()
675        self.sql = sql
676        self.size = len(sql)
677
678        try:
679            self._scan()
680        except Exception as e:
681            start = max(self._current - 50, 0)
682            end = min(self._current + 50, self.size - 1)
683            context = self.sql[start:end]
684            raise TokenError(f"Error tokenizing '{context}'", start=start, end=end) from e
685
686        return self.tokens

Returns a list of tokens corresponding to the SQL string sql.