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

An enumeration.

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

Returns a NUMBER token with number as its text.

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

Returns a STRING token with string as its text.

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

Returns an IDENTIFIER token with identifier as its text.

@classmethod
def var(cls, var: str) -> Token:
508    @classmethod
509    def var(cls, var: str) -> Token:
510        """Returns an VAR token with `var` as its text."""
511        return cls(TokenType.VAR, var)

Returns an VAR token with var as its text.

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

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