Skip to content

Add MySQL to goldeneye: generate relations.jsonl and check the analyze cases against a live server - #4616

Merged
kyleconroy merged 3 commits into
mainfrom
claude/goldeneye-mysql-support-n0dru2
Sep 10, 2026
Merged

Add MySQL to goldeneye: generate relations.jsonl and check the analyze cases against a live server#4616
kyleconroy merged 3 commits into
mainfrom
claude/goldeneye-mysql-support-n0dru2

Conversation

@kyleconroy

@kyleconroy kyleconroy commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a mysql engine to internal/goldeneye, following the pattern of the other engines: Locate, Version, Generate and Analyze, tests that run the checks, and a job in the gen workflow. The server is named by MYSQL_SERVER_URI (a go-sql-driver DSN) and has to be MySQL 26, the major pinned in mysql.Major.

Everything that names a MySQL release is standardized on 26.7: the gen workflow runs the mysql:26.7 image, docker-compose.yml does too, and sqlc-test-setup (which CI uses) pins the 26.7.0 deb bundle in one place and accepts an installed server only from that major on. Its installed-version check compared the first digit of the major, which would have called 26 older than 9; it compares the number now.

What is generated

MySQL keeps no catalog of its types, functions or operators, so those files stay hand-written, the way ClickHouse's and SQLite's do. What it does describe is its data dictionary: internal/engine/dolphin/dialect/relations.jsonl is now generated from information_schema — every view of it, with columns, NOT NULL and the declared type spelled MySQL's way (bigint unsigned included). Names are written in lower case, since MySQL matches them in any case and the dolphin converter lowercases every identifier. The other schemas mysqld --initialize creates (mysql, performance_schema, sys) are base tables, and every table a dialect seeds is one the analysis core hands codegen as a model, so they are left out for now.

Two hand-written files change alongside:

  • types.jsonl gains <type> unsigned aliases on the numeric types, so the generated columns resolve to their base type instead of a user type.
  • functions.jsonl marks the aggregates that are NULL over no rows as nullable (AVG, MAX, MIN, SUM, STD*, VAR*, JSON_ARRAYAGG, JSON_OBJECTAGG), which is what MySQL reports; BIT_*, COUNT and ANY_VALUE are not. Their return type is any, so no generated Go changes; analyze_select/mysql's golden gains a nullable on max(created).

The analysis check

Analyze loads a case's schema and fixture into a database of its own and asks the server three things about each query:

  • What a driver sees: the query is run with every parameter a user variable set to NULL, and result columns come from the result set's metadata as go-sql-driver reports it.
  • What the resolver made of it: the optimizer trace's expanded_query prints each query block after name resolution and before optimisation, with every column qualified, aliases kept and SELECT * expanded, which gives provenance and each parameter's partner. Views and derived tables are kept unmerged so a column read through one is reported as its column, and an information_schema view is not resolved away into the dictionary tables behind it.
  • For statements the trace does not expand (INSERT ... VALUES, single-table UPDATE/DELETE), the note EXPLAIN FORMAT=TRADITIONAL leaves, which prints the statement the same way (the traditional format is asked for since MySQL 26 defaults to the tree, which leaves no note). A SELECT cannot be read from the note: it is printed after optimisation, and a unique-key lookup against an empty table has folded to NULL = (@x) there.

MySQL reports nothing about a parameter but its position, so a parameter is described by its partner: a column's type and nullability from information_schema, a derived table's column from what its block projects, an expression's from running it over the tables it reads. Two things the driver keeps to itself, and how the check works around them: the table a result column comes from (read from the trace), and the length that distinguishes the sizes of TEXT/BLOB on the wire (a column read from a table is spelled the way the table declares it).

All five MySQL analyze cases match the server byte for byte, including a new analyze_system_catalog/mysql case that exercises the generated relations end to end through sqlc analyze.

Also

  • endtoend.Rewrite now recognises sqlc.slice(...), and hands LIMIT to the second count of LIMIT ?, ?.
  • The engine table in the command gains a dir, since MySQL's dialect lives under dolphin.
  • README and CLAUDE.md describe the new engine and the pinned release.

Test plan

  • go run ./cmd/sqlc-test-setup install upgrades an installed 9.1 to 26.7.0; the server starts and reports 26.7.0
  • go test ./... in internal/goldeneye with MYSQL_SERVER_URI set: dialect and all five analyze cases match MySQL 26.7.0 (goldeneye check mysql says the same)
  • go test ./internal/... ./cmd/... in the main module; every MySQL TestReplay case in every context passes against 26.7
  • The analysis check exercised on a scratch case with BETWEEN, IN lists, NOT LIKE, function partners, LIMIT ?, ?, derived tables, correlated subqueries, multi-row INSERT without a column list, ON DUPLICATE KEY UPDATE, multi-table UPDATE, LEFT JOIN, UNION and a recursive CTE

🤖 Generated with Claude Code

https://claude.ai/code/session_012DTeySVan5NcA3RS6xxdUn

…lyze cases

Add a mysql package to goldeneye that reads a live server named by
MYSQL_SERVER_URI. MySQL keeps no catalog of its types, functions or
operators, so those files stay hand-written; what the server does describe
is its data dictionary, so relations.jsonl is generated from
information_schema, with names in lower case since MySQL matches them in
any case and sqlc's parser lowercases every identifier. The other system
schemas are tables rather than views, which the analysis core would hand
codegen as models, so they are left out for now. The dialect lives under
internal/engine/dolphin, so the command gains a dialect directory
distinct from the engine name.

The package also checks the analyze_*/mysql cases against the server.
Result columns come from the result set's metadata as go-sql-driver
reports it; provenance and parameters come from the optimizer trace's
expanded_query, which prints each block after resolution and before
optimisation, and from the note EXPLAIN leaves for the statements the
trace does not expand. Views and derived tables are kept unmerged so a
column read through one is still its column. A column read from a table
is spelled by its declaration, since the driver hides the length that
tells the sizes of TEXT apart.

Making sqlc agree: MySQL reports its aggregates as nullable, so the
aggregates that are NULL over no rows are marked so in functions.jsonl,
which changes the committed analyze_select output; "bigint unsigned" and
the other unsigned spellings become aliases of their types so the
generated relations resolve; and an analyze_system_catalog case covers
querying information_schema. The shared placeholder rewriter learns
sqlc.slice and the second count of LIMIT ?, ?.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DTeySVan5NcA3RS6xxdUn
The gen workflow runs the mysql:26.7 image, so the pinned major is 26 and
relations.jsonl is regenerated from 26.7.0, which adds the JSON duality
view and library views to information_schema. EXPLAIN defaults to the tree
format since MySQL 26, which leaves no rewritten statement behind, so the
analysis check asks for the traditional format.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DTeySVan5NcA3RS6xxdUn
sqlc-test-setup pins the release it installs in one place, so the deb
bundle it downloads, the version it accepts as already installed, and the
docker-compose service all name MySQL 26.7, the same release goldeneye
generates the dialect from and its gen workflow runs. The installed-version
check compared the first digit of the major release, which would have
called 26 older than 9; it compares the number now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DTeySVan5NcA3RS6xxdUn
@kyleconroy
kyleconroy merged commit 4b22260 into main Sep 10, 2026
12 checks passed
@kyleconroy
kyleconroy deleted the claude/goldeneye-mysql-support-n0dru2 branch September 10, 2026 17:20
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.

2 participants