Schema columns carry PK/FK on MySQL/MariaDB (#502 cut 2) - #678
Merged
Merged
Conversation
rollroyces
force-pushed
the
feat/schema-keys-mysql
branch
from
September 13, 2026 14:51
0744b76 to
64c7355
Compare
follows deeplethe#671 (now merged with revisions). The struct change lands in deeplethe#671 — this PR adds the MySQL implementation only, against the new shape (is_primary_key: bool, references_table: Option<String>). Drops the two fields (deeplethe#671 cut: is_foreign_key, nullable) that the maintainer explicitly removed for not being used by the explore prompts. keys() helper mirrors postgres.rs's pattern: * single-column PRIMARY KEY from information_schema.statistics with the same composite-PK guard Postgres uses (index column count = 1) * single-column FOREIGN KEY from information_schema.key_column_usage with ORDINAL_POSITION = 1 + composite-FK guard (a column appears in key_column_usage once per FK constraint it's part of; without the composite guard, both members of a composite FK get marked) * UNION ALL on a five-column shape with NULL placeholders on the PK side, because sqlx's query_as wants a uniform tuple. PK and FK rows are distinguished in the merge loop by whether the FK target columns are NULL * Graceful degradation on the same line as deeplethe#671: a failing keys() returns an empty Keys + a tracing::warn, leaving the columns without key marks rather than failing the whole fetch_schema call. The read-only role test exercises this on MySQL — same permission issue as PG (information_schema constraints are empty for SELECT-only users), same fix shape. fetch_schema keeps its existing CAST(... AS CHAR) treatment — MySQL 8.0 binary protocol reports VARCHAR columns in information_schema as VARBINARY, sqlx strict-typed decoding refuses to read them as String. Three live tests gated on UTOPIA_TEST_MYSQL_URL, mirroring the maintainer's three Postgres tests: * a_single_column_key_is_marked_and_a_composite_one_is_not_mysql — single-column PK and FK both marked; both members of a composite PK and a composite FK stay unmarked * a_foreign_key_points_at_its_own_target_mysql — self-referencing FK resolves correctly; cross-database FK resolves correctly; the PK on a table whose FK name collides with another table's PK stays marked as PK (not as FK) * a_read_only_login_still_sees_the_keys_mysql — a SELECT-only role still gets the PK/FK marks. information_schema reads succeed for SELECT roles (per deeplethe#671 followup), but the test gracefully skips if CREATE USER / GRANT fail. Fx test harness mirrors postgres.rs's: per-test schema with random uuid suffix (CREATE DATABASE here — MySQL's 'schema' is 'database'), no Mutex for parallel safety, SET FOREIGN_KEY_CHECKS = 0 around cleanup so cross-database FK cleanup doesn't fight the drop order. Verified locally against mysql:8.0 on 127.0.0.1:13306 with UTOPIA_TEST_MYSQL_URL set: 269 passed, 1 failed (pre-existing a_live_server_answers_with_typed_values needs the sales.orders fixture from deeplethe#316; not a regression). My new tests all pass; no regressions to existing tests. Signed-off-by: rollroyces <rollroyces@users.noreply.github.com>
rollroyces
force-pushed
the
feat/schema-keys-mysql
branch
from
September 13, 2026 15:01
64c7355 to
0da884d
Compare
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Contributor
|
@rollroyces thanks, this is a clean port of the Postgres cut. Before merging I pushed 78b2eef to your branch. On MariaDB the correlated 🤖 Generated with Claude Code |
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
#502 cut 2 — MySQL / MariaDB
fetch_schemareads single-column primary keys and foreign keys frominformation_schema, parallel to the Postgres impl in #671 (now merged with revisions).This is just the MySQL impl — the struct change (
is_primary_key: bool,references_table: Option<String>) lives in #671 which already merged with two fields removed (is_foreign_keyandnullable) per the maintainer's review note that no explore prompt uses them. The diff againstdevis a single file (mysql.rs, +354 / -16).What's in this PR
query_engine::mysql::keys()— single helper, mirrorspostgres::keys():Single-column PRIMARY KEY from
information_schema.statisticswhereindex_name='PRIMARY'and the index has exactly one column. Same composite-PK guard as Schema columns carry primary and foreign keys (#502 cut 1) #671 (cardinality(k.conkey) = 1in PG,count(*) per index = 1in MySQL). The "column itself is PK" semantics is whatexplore_mappingsreads.Single-column FOREIGN KEY from
information_schema.key_column_usagewherereferenced_table_name IS NOT NULL, withordinal_position = 1AND a per-constraint count = 1 guard. MySQLkey_column_usagehas one row per column of each FK — without the composite guard, both members of(order_id, ordinal) REFERENCESFK` get marked as FK, which misleads the explorer into treating the FK column as a navigation path.UNION ALL on a five-column shape with NULL placeholders on the PK side. sqlx's
query_aswants a uniform tuple; PK and FK rows are distinguished in the merge loop by whether the FK target columns are NULL.entry().or_insert()keeps the first target on a rare multi-FK column (stable, ORDER BY clause).Graceful degradation on the same line as Schema columns carry primary and foreign keys (#502 cut 1) #671: a failing
keys()returnsKeys::default()+ atracing::warn!, leaving columns without key marks rather than failing the wholefetch_schema. The read-only-role test exists to exercise this.fetch_schemakeeps its existingCAST(... AS CHAR)treatment — MySQL 8.0 binary protocol reports VARCHAR columns ininformation_schemaas VARBINARY, sqlx strict-typed decoding refuses them asString.Three live tests, gated on
UTOPIA_TEST_MYSQL_URLMirroring the maintainer's three Postgres tests:
a_single_column_key_is_marked_and_a_composite_one_is_not_mysql— single-column PK and FK both marked; both members of a composite PK and a composite FK stay unmarked.a_foreign_key_points_at_its_own_target_mysql— self-referencing FK resolves correctly; cross-database FK resolves correctly (b.orders.item_id → a.p2.id); the PK on a table whose FK name collides with another table's PK stays marked as PK (not as FK). Cleanup usesSET FOREIGN_KEY_CHECKS = 0so cross-database FK cleanup doesn't fight the drop order.a_read_only_login_still_sees_the_keys_mysql— a SELECT-only role still gets the PK/FK marks. The maintainer discovered the same permission issue on Postgres (information_schema.table_constraintsis empty for SELECT-only users); MySQL has the same shape oninformation_schema.statistics/key_column_usagefor non-super users in some configurations. The test gracefully skips ifCREATE USER/GRANTfail (which they will in most reader-only deployments), since the read-only check is best-effort.Test fixture
Fxtest harness mirrors the maintainer'spostgres::tests::Fx:SET FOREIGN_KEY_CHECKS = 0around cleanup to handle cross-database FK drop orderingmysql_keys_base_<uuid>database is created so the pool has somewhere to landCREATE DATABASEcallsWhat I deliberately did NOT add
is_foreign_key: boolandnullable: boolfrom Schema columns carry primary and foreign keys (#502 cut 1) #671's original are gone in the maintainer's revision. I followed suit:keys()reads intoHashSet<ColumnKey>for PK andHashMap<ColumnKey, String>for FK references. No FK-booleans or nullable flags.-- PKmarkers to[PK, FK→schema.table]brackets in Schema columns carry primary and foreign keys (#502 cut 1) #671; already ondev. No further consumer change needed.Verification
All three of my new tests pass; the existing type-table unit tests in
mysql.rs(mariadb_is_the_same_protocol_under_another_name,every_number_shape_lands_in_a_readable_slot,each_time_type_keeps_its_own_shape) still pass; the only failure is the existing live test that needs thesales.ordersfixture from #316.Known limitations in CI
The maintainer's
test_db::url()doc note is honest: "the backend job doesn't have a DB, 24 store integration tests are silently skipped" — meaning CI will run my live MySQL tests as no-ops because no MySQL container exists in thebackendjob, and themigrationsjob only has Postgres. Like Postgres's live tests, mine are designed to run on a developer machine withUTOPIA_TEST_MYSQL_URLset. The unit tests (type tables, mariadb protocol) will still run in CI and act as a sanity gate.Issue
Closes part of #502. After this lands, cuts 3-5 (Trino / Snowflake / Databricks) target the same struct shape.
Co-Authored-By: Claude Opus 4.1 noreply@anthropic.com
Signed-off-by: Royce rollroyces@users.noreply.github.com
Changes before merging (maintainer)
Reviewed and tested against MariaDB 11.4 (MySQL 8 images could not be pulled here; MySQL 8 was reviewed by reading). Four changes on this branch in 78b2eef:
count(*)subqueries made MariaDB rebuildinformation_schemafor every row: on 2,000 tables with one PK and one FK each, the original query took 182 s; the grouped rewrite (GROUP BY … HAVING COUNT(*) = 1,MIN()of the column and target) returns the same 4,000 rows in 0.68 s.fetch_schemaruns inside the mount request with no statement timeout, so slow meant hung. MySQL 5.7 builds these tables the same way.ORDER BY 1, 2, 3, 4, 5, first wins), with a test case.mysql_keys_base_<uuid>database was never dropped, a URL without a trailing slash failed to connect, and the read-only test failed with a URL naming a database (/sales) because the new user can't open it. The base database is gone (CREATE DATABASE needs no current database) and the read-only URL drops the path. All 7 MySQL tests pass with…:13307,…:13307/and…:13307/sales, and no databases or users remain afterwards.statisticsandkey_column_usagedo return rows to a SELECT-only user (verified); it istable_constraints/referential_constraintsthat are empty for such users, which is why the code must not switch to them. FK names are unique per database on InnoDB, so the "same-named FKs on two tables" case cannot occur.cargo clippy -p utopia-server --all-targets -D warningsandcargo fmt --checkclean on the branch merged with currentdev.