Repository navigation
feat: the MySQL 8.4 dialect - #32
Merged
Merged
Conversation
DialectMySQL is MySQL 8.4's grammar with its default sql_mode: PostgreSQL 18's without what MySQL does not run (ILIKE, the ~~ and ~ operators, SIMILAR TO, IS DISTINCT FROM, BETWEEN SYMMETRIC and ASYMMETRIC, ISNULL and NOTNULL), keeping != and an empty ESCAPE, which it runs. Every row was run against MySQL 8.4.13. And it reads strings as MySQL does, with backslash escapes. A parser reading them as PostgreSQL does, in front of MySQL, accepted name = '\'' OR id > 0 #' with the allow-list [name] as one comparison of name with a string; MySQL 8.4 runs it, with a newline after it as in a SQL template, as a comparison of name with a quote, then id > 0, then a comment: every row, on a field the allow-list does not have. In MySQL's dialect it is refused. NewLexer reads strings as before; the parser builds its lexer for the dialect. An ESCAPE string is measured in characters, a doubled quote counted once in every dialect and a backslash pair once in MySQL's: MySQL's ESCAPE '\\' is one backslash, and four single quotes are one quote, where they were counted as empty. docs/dialects.md has MySQL in every table and diagram, a section on how a string is read, and its refusals, held to Parse by the docs test, which now reads one table per dialect. Every other page names MySQL 8.4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
qfv read every filter's strings as PostgreSQL does, with a backslash just a character. MySQL 8.4 reads a backslash inside a string as an escape. So a parser in front of MySQL could accept a filter whose predicates it never saw.
With the allow-list
["name"], qfv v1.1.1 accepts this as one comparison ofnamewith a string:MySQL 8.4.13 runs it, followed by a newline as in a SQL template, as a comparison of
namewith a quote, thenid > 0, then a comment. It returned 3 rows where qfv's reading returns 0: a predicate onid, a field the allow-list does not have.No consumer of ours runs on MySQL; core and the template run on PostgreSQL 18. But anyone using qfv in front of MySQL had this hole, and there was no MySQL dialect to choose.
What changes
DialectMySQL: MySQL 8.4's grammar with its defaultsql_mode.idis not allowed.NewLexerkeeps PostgreSQL's reading for every existing caller; the parser builds its lexer for the dialect.ILIKE, the~~and~operators,SIMILAR TO,IS [NOT] DISTINCT FROM,BETWEEN SYMMETRIC/ASYMMETRIC, andISNULL/NOTNULL. Each spelling-level refusal names what to write instead.!=and an emptyESCAPE, which MySQL runs.NO_BACKSLASH_ESCAPESreads strings the other way.ESCAPElength in characters: a doubled quote counts once in every dialect, and a backslash pair once in MySQL's.ESCAPE '\\'is one backslash.<=>andREGEXPare not parsed yet. Each would be a new operator and node, changing what every dialect sees, so they are left for a later minor. The docs say so.How it was checked
sql_moderead from the server. Each statement was sent as a hex literal throughPREPARE, so that only the server parsed it: themysqlclient parses backslashes itself and would have misled. 47 inputs, each answer in the test's comment.TestTheMySQLDialectReadsStringsAsMySQLDoes:id;\',\\,''and\%are each one token;NewLexeris unchanged.TestTheMySQLDialect: 24 accepted inputs and 16 refused, each with its message.NewLexerreading as MySQL.IS DISTINCT FROM.BETWEEN SYMMETRIC.ESCAPEin MySQL.SYMMETRICnot checked.go test -racepasses, coverage is 98.2%, andgolangci-lintreports 0 issues.Docs, every page, each changed diagram rendered:
docs/dialects.md: MySQL in every table and diagram, a section with a diagram on how a string is read, and MySQL's refusals;migration.md: v1.1.1 → v1.2.0, with "if your filters run on MySQL, passWithDialect(qfv.DialectMySQL)";README.md,docs/README.md,configuration.md,getting-started.md,filtering.md,error-handling.md, and the package doc;ExampleWithDialect_mySQL.v1.2.0. An independent review that tries to break the string claim against the same server is running; its findings will go in a follow-up commit.
🤖 Generated with Claude Code