Skip to content

Fix MySQL lexer edge cases - #460

Closed
JanJakes wants to merge 3 commits into
ansi-quotesfrom
lalr-lex-fixes
Closed

Fix MySQL lexer edge cases#460
JanJakes wants to merge 3 commits into
ansi-quotesfrom
lalr-lex-fixes

Conversation

@JanJakes

Copy link
Copy Markdown
Member

Summary

  • preserve arbitrary bytes when unquoting string literals
  • keep IGNORE_SPACE lookahead whitespace outside function-name token ranges
  • support six-digit MySQL version comments

Why

These cases currently produce incorrect token values or ranges, or fail to recognize version comments supported by current MySQL releases.

This PR is stacked on #452, which already contains the quoted-identifier backslash fix that was previously part of this branch.

Validation

  • composer test in packages/mysql-parser — 142 tests, 1,225,624 assertions
  • git diff --check

JanJakes added 3 commits July 28, 2026 11:26
The final backslash-stripping step used preg_replace() with the "u" (UTF-8)
modifier. That modifier makes PCRE validate the whole subject as UTF-8 and
return null on the first invalid byte; since get_value() is typed ": string",
the null turned into a fatal TypeError. MySQL string literals may legitimately
carry non-UTF-8 bytes (binary or other-charset payloads), and the lexer scans
them at the byte level, so reading the value of such a literal crashed.

Switch the modifier to "s" (DOTALL). A byte-wise strip is binary-safe, yields
identical results for valid UTF-8 (no continuation byte is a backslash), and
additionally handles a backslash preceding a newline byte.
When resolving a function keyword (SYM_FN), the lexer peeks for a following "("
and, under SQL_MODE_IGNORE_SPACE, skips intervening whitespace first. It skipped
by advancing bytes_already_read and never restored it. When no "(" followed, the
keyword was emitted as an IDENTIFIER whose length — derived from
bytes_already_read in produce() — now covered the trailing whitespace, so the
extracted value was e.g. "COUNT " instead of "COUNT". Under this ANSI-style mode
a column or table named after a function would resolve to the wrong identifier.

Peek with a local index instead of mutating bytes_already_read, so the token's
byte range ends at the keyword and the next scan consumes the whitespace.
read_mysql_comment() read at most five version digits, so a six-digit MMmmrr
version comment — added in MySQL 8.4 — was misparsed: /*!100000 ... */ gated as
version 10000 instead of 100000, and the sixth digit of /*!080400 ... */ leaked
into the comment body as SQL.

Mirror MySQL's own lexer rule (sql/sql_lex.cc): the first five characters must be
digits; a sixth digit immediately followed by whitespace extends the version to
six digits; otherwise the version stays five digits and any extra is content.
@JanJakes JanJakes closed this Jul 28, 2026
@JanJakes
JanJakes deleted the lalr-lex-fixes branch July 28, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant