Conversation
Cloud SQL for MySQL is MySQL, so the schema-level work is small. What stalls these migrations is the managed-platform layer, and that is what this module concentrates on: - SET GLOBAL fails on every variable the migration cares about — no user holds SUPER, not even with cloudsqlsuperuser — so each finding carries the gcloud command that actually fixes it, and says so when no fix exists. - Binary logging is an instance setting (--enable-bin-log, requires automatic backups), not a database flag; retention is governed by --retained-transaction-log-days. - binlog_format is not user-configurable at all; Cloud SQL forces ROW. - --database-flags replaces the whole flag list, so every rendered patch command ships with the describe-first warning. - The Cloud SQL Auth Proxy cannot be used by TiDB Cloud DM, and a Cloud SQL read replica cannot be a DM source at all. Both surface in Phase 1 rather than at cutover. - IAM database users and DEFINER clauses naming Cloud SQL principals have no TiDB equivalent; lower_case_table_names is immutable after instance creation, which turns a case-collision warning into an unfixable-on-the-source blocker. Implements scan and convert; check and sync are documented with CLI stubs that exit non-zero, and load is deliberately disabled. Follows the established rules/ (declarative registries) vs core/ (engines) and collectors vs analyzers split, so 95% of the logic is covered by 210 offline tests with no database. Convert is idempotent by construction: rules match a length-preserving mask that blanks out string literals and comments, so re-running over its own output is a no-op. Nothing is deleted — originals survive in TISHIFT-REMOVED comments. Parse validation only reports errors the rewrite introduced. sqlglot's MySQL dialect cannot parse SRID on a spatial column, and blaming the conversion for that was a false positive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the module to all five places: the supported-paths table, the skill slash-command list, the CLI toolkit section, the test list, and the project structure tree. The CLI section flags the Auth Proxy / DM incompatibility inline, since that is the decision that is expensive to revisit later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Executing the module end-to-end against a real Cloud SQL for MySQL 8.4.10
instance surfaced a set of defects and missing rules. Every change here is
backed by an observed failure, not by reading docs.
Correctness fixes:
- utf8/utf8mb3 widening now pins the collation when the source left it
implicit. `CHARSET=utf8mb3` means utf8mb3_general_ci (case-insensitive) on
MySQL; rewritten to a bare `CHARSET=utf8mb4` it inherits utf8mb4_bin
(case-sensitive) on TiDB. That flips equality predicates and changes what a
UNIQUE key rejects. The clause pattern also swallows a following COLLATE so
a charset/collation pair is one match, instead of emitting two collations.
- The valid-index query was instance-wide. On a single-schema migration off a
shared instance it pulled in other databases' tables: 2 in-scope tables
reported as 6, 12 points deducted instead of 4. Now scoped when a schema is
given.
- information_schema returns UPPERCASE column names regardless of how the
SELECT is written; the collector indexed by lowercase and raised KeyError
against a real server.
- TLS handling was wrong in both directions: `tls: false` passed no ssl
argument at all, so pymysql negotiated TLS opportunistically; `tls: true`
with no CA passed `{"ca": ""}`, which demands verification with nothing to
verify against and killed every connection. Cloud SQL signs each instance
with a per-instance CA, so system roots can never validate it — encrypted-
but-unverified is now an explicit, warned-about state.
New rule CSQL-WARNING-14 — foreign keys referencing a non-unique parent index:
A prefix of a unique key is not unique. `PRIMARY KEY (branch_code, debtor_no)`
leaves `branch_code` duplicable, so a child FK on it alone is unbacked. MySQL
8.0.16+ rejects these with ERROR 6125; verified on a 250-table ERP schema that
stops at table 57 on Cloud SQL, while TiDB v8.5.3 creates all 250 and enforces
the FK. Scored at 0 — reported for dump portability, not as a TiDB blocker.
Rescored CSQL-WARNING-11 to 0: binlog retention is governed by the instance's
transactionLogRetentionDays, which is not exposed over the MySQL protocol and
does not track `binlog_expire_logs_seconds` (observed: instance raised 1 -> 7
while the variable stayed at 86400). Penalizing a value we cannot read is
wrong; the check now tells the operator to verify with `gcloud`.
Also: `--format` accepts comma-separated values, convert refuses a DDL file
with no CREATE TABLE (a silently failed mysqldump otherwise reads as a clean
zero-hit report), auto-increment detection reads COLUMNS.EXTRA instead of the
cached TABLES.AUTO_INCREMENT counter, and the binlog-enable guidance names the
Console's "Point-in-time recovery" wording and the instance restart it causes.
Gitignores the module's two internal notes — the Chinese design rationale and
the worked-run runbook — alongside the existing aurora-to-tidb entries. The
runbook quotes live hostnames, a GCP project, and a cluster id verbatim.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011NQYJP23NffEUaATAwcyRV
ayenller
force-pushed
the
cloudsql-to-tidb
branch
from
August 31, 2026 08:55
7c11df9 to
2e74d28
Compare
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.
Adds
cloudsql-to-tidb, a TiShift module for migrating Google Cloud SQL forMySQL to TiDB Cloud, alongside the existing
aurora-to-tidbandheatwave-to-tidbmodules.Why a separate module
Cloud SQL for MySQL is MySQL, so the schema-level work is nearly an identity
mapping — that alone would not justify a module. The value is entirely in the
managed-platform layer, where the standard MySQL migration advice is
actively wrong:
SUPER(not evencloudsqlsuperuser)SET GLOBALfails; all variable fixes must go throughgcloud--database-flagshas replace semantics--enable-bin-log), not a flaglog_bin=ON" is not actionablebinlog_formatis not user-configurablelower_case_table_namesis immutable after creationThe module's rule is: when generic MySQL advice does not hold on Cloud SQL,
emit the correct alternative command, or say plainly that it cannot be
changed. A wrong fix command is worse than none — the operator runs it, then
spends time working out why nothing happened.
What's in it
SKILL.md— the interactive AI runbook, the primary deliverabletishift_cloudsql/— scan / convert CLI, withload,check,syncstubs that exit non-zero so scripts cannot mistake them for completed phases
references/— the canonical rule corpus, mirrored byrules/*.pydocs/— per-phase operator guides and checklisttests/— 232 tests, fully offline, no database requiredVerified against a live instance
The final commit is not speculative hardening — the module was executed
end-to-end against a real Cloud SQL for MySQL 8.4.10 instance and a TiDB Cloud
Dedicated v8.5.x cluster, and every change in it is backed by an observed
failure:
CHARSET=utf8mb3implies
utf8mb3_general_ci(case-insensitive) on MySQL; rewritten to a bareCHARSET=utf8mb4it inheritsutf8mb4_bin(case-sensitive) on TiDB. Thatflips equality predicates and changes what a UNIQUE key rejects. The
collation is now pinned explicitly whenever the source left it implicit.
a shared instance it counted other databases' tables: 2 in-scope tables
reported as 6, 12 points deducted instead of 4.
tls: falsepassed no ssl argument, sopymysql negotiated TLS opportunistically;
tls: truewithout a CA passed{"ca": ""}, which demands verification with nothing to verify against.SELECT's casing; the collector raised
KeyErroragainst a real server.New rule CSQL-WARNING-14 — foreign keys referencing a non-unique parent
index. A prefix of a unique key is not unique:
PRIMARY KEY (branch_code, debtor_no)leavesbranch_codeduplicable, so a child FK on it alone isunbacked. MySQL 8.0.16+ rejects these with ERROR 6125. Confirmed on a
250-table ERP schema — apply stops at table 57 on Cloud SQL, while TiDB
creates all 250 and enforces the FK correctly. Scored at 0: it is a dump-
portability problem (rollback targets, staging refreshes), not a TiDB blocker.
CSQL-WARNING-11 rescored to 0. Binlog retention is governed by the
instance's
transactionLogRetentionDays, which is not exposed over the MySQLprotocol and does not track
binlog_expire_logs_seconds— observed: aninstance raised 1 → 7 days while the variable stayed at 86400 and the setting
never appeared in
databaseFlags. Penalizing a value the scanner cannot readis wrong; the check now directs the operator to verify with
gcloud.Testing
All offline. No credentials or live infrastructure identifiers are committed —
the worked-run notes stay local, and generated configs remain gitignored.
🤖 Generated with Claude Code
https://claude.ai/code/session_011NQYJP23NffEUaATAwcyRV