Add MySQL to goldeneye: generate relations.jsonl and check the analyze cases against a live server - #4616
Open
kyleconroy wants to merge 2 commits into
Open
Add MySQL to goldeneye: generate relations.jsonl and check the analyze cases against a live server#4616kyleconroy wants to merge 2 commits into
kyleconroy wants to merge 2 commits into
Conversation
…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
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.
Summary
Adds a
mysqlengine tointernal/goldeneye, following the pattern of the other engines:Locate,Version,GenerateandAnalyze, tests that run the checks, and a job in thegenworkflow. The server is named byMYSQL_SERVER_URI(a go-sql-driver DSN) and has to be MySQL 26, the major pinned inmysql.Major; the workflow runs themysql:26.7image.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.jsonlis now generated frominformation_schema— every view of it, with columns,NOT NULLand the declared type spelled MySQL's way (bigint unsignedincluded). Names are written in lower case, since MySQL matches them in any case and the dolphin converter lowercases every identifier. The other schemasmysqld --initializecreates (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.jsonlgains<type> unsignedaliases on the numeric types, so the generated columns resolve to their base type instead of a user type.functions.jsonlmarks 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_*,COUNTandANY_VALUEare not. Their return type isany, so no generated Go changes;analyze_select/mysql's golden gains anullableonmax(created).The analysis check
Analyzeloads a case's schema and fixture into a database of its own and asks the server three things about each query:expanded_queryprints each query block after name resolution and before optimisation, with every column qualified, aliases kept andSELECT *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 aninformation_schemaview is not resolved away into the dictionary tables behind it.INSERT ... VALUES, single-tableUPDATE/DELETE), the noteEXPLAIN FORMAT=TRADITIONALleaves, which prints the statement the same way. ASELECTcannot be read from the note: it is printed after optimisation, and a unique-key lookup against an empty table has folded toNULL = (@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 ofTEXT/BLOBon 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/mysqlcase that exercises the generated relations end to end throughsqlc analyze.Also
endtoend.Rewritenow recognisessqlc.slice(...), and handsLIMITto the second count ofLIMIT ?, ?.enginetable in the command gains adir, since MySQL's dialect lives underdolphin.Test plan
go test ./...ininternal/goldeneyewithMYSQL_SERVER_URIset: dialect and all five analyze cases match MySQL 26.7.0 (goldeneye check mysqlsays the same)go test ./internal/... ./cmd/...in the main module, includingTestReplay/basefor every MySQL caseBETWEEN,INlists,NOT LIKE, function partners,LIMIT ?, ?, derived tables, correlated subqueries, multi-rowINSERTwithout a column list,ON DUPLICATE KEY UPDATE, multi-tableUPDATE,LEFT JOIN,UNIONand a recursive CTE🤖 Generated with Claude Code
https://claude.ai/code/session_012DTeySVan5NcA3RS6xxdUn
Generated by Claude Code