PATTERN Cited by 2 sources
Cross-engine database migration audit¶
Cross-engine database migration audit is a pre-migration pattern: before moving a schema between two SQL engines (e.g. PostgreSQL → MySQL), enumerate the differences along three orthogonal axes and confirm every source construct maps to a supported target construct.
The axes — from Adnan Kukic's PlanetScale audit (sources/2026-04-21-planetscale-migrating-from-postgres-to-mysql) — are:
- Column-type axis — does every source column have a target equivalent? Does the equivalent behave the same way at the representation level, not just the declaration level?
- DDL/DML dialect axis — do
CREATE,DROP,TRUNCATE,INSERT,ON CONFLICT / ON DUPLICATE KEY, stored procedures, triggers, views, and partitioning translate cleanly? - Operational-semantics axis — identifier case sensitivity, transaction safety of DDL statements, extension ecosystems, sequence / auto-increment semantics.
Worked example: Postgres → MySQL audit¶
The canonical wiki instance is the 2023 PlanetScale post. Three sample tables surface three column-type classes:
- Unconstrained strings — Postgres's
VARCHAR(no length) has no MySQL equivalent; MySQL requiresVARCHAR(N). Drop-in answer: useTEXT. (See concepts/mysql-varchar-length-requirement.) - Spatial data — both engines have
POINT, but MySQL stores it as Well-Known Binary. Reads return0x00000000010100000027DFC4AE…instead ofPOINT(34.41…, -111.68…). Every read path has to wrap the column inST_asText(), or the column has to be split into twoDECIMALcolumns. (See concepts/mysql-spatial-binary-wkb-vs-postgres-point.) - UUIDs and JSON — MySQL has no native
UUID(store asVARCHAR(36), generate withUUID()) and onlyJSONrather thanJSONB. (See concepts/uuid-string-representation-mysql, concepts/postgres-jsonb-vs-mysql-json.)
Five operational-dialect gaps are called out:
DROP TEMPORARY TABLE— MySQL only; Postgres lacks the scoping keyword. (concepts/mysql-temporary-keyword-in-drop-table)TRUNCATE … CASCADE / RESTART IDENTITY— Postgres only; MySQL'sTRUNCATEis also not transaction-safe. (concepts/postgres-truncate-cascade-restart-identity)- Stored-procedure language pluggability — Postgres procedures can be PL/pgSQL, Python, Perl, Ruby, JS; MySQL is SQL only. (concepts/postgres-stored-procedure-language-pluggability)
- Identifier case sensitivity — Postgres quoted identifiers are case-sensitive; MySQL identifiers are case-insensitive always. (concepts/mysql-case-insensitive-identifiers)
- Postgres extensions — each extension in the source schema has to be audited one-by-one; MySQL has no analogous surface.
Why this pattern is distinct from a same-engine upgrade¶
The PlanetScale sources/2026-04-21-planetscale-how-to-upgrade-from-mysql-57-to-80
audit enumerates within-engine breaking changes across a
major-version bump (MySQL 5.7 → 8.0). The cross-engine audit
applies the same discipline across engines, and picks up
a further class of surprises — the representation-layer
differences (MySQL WKB for POINT) that a version-bump audit
would never surface because the representation doesn't change
across MySQL versions.
What the pattern does not cover¶
A cross-engine migration audit is a schema + dialect readiness check. It does not address:
- Data movement — how to copy rows between engines. For the zero-downtime data-movement playbook see sources/2026-02-16-planetscale-zero-downtime-migrations-at-petabyte-scale and patterns/snapshot-plus-catchup-replication.
- Application cutover — how to route writes between old and new engines during overlap. See patterns/dual-write-migration, patterns/expand-migrate-contract.
- Performance regression — query planner differences
can change workload shape.
EXPLAINa representative workload on the target before cutover.
Exit criteria¶
Audit is complete when:
- Every source column type has a declared target equivalent, including representation (not just declaration).
- Every DDL / DML statement in the application's query log has a confirmed target-engine form, or a planned application-side rewrite.
- Every stored procedure, trigger, and extension is either ported or explicitly dropped from the migration scope.
- Identifier collisions under the target engine's case rules have been resolved.
EXPLAINoutput on the target engine for top-N queries has been spot-checked against Postgres's expectations.
See also¶
- sources/2026-04-21-planetscale-migrating-from-postgres-to-mysql — worked Postgres → MySQL instance.
- sources/2026-04-21-planetscale-how-to-upgrade-from-mysql-57-to-80 — same-engine version-bump counterpart.
- patterns/dual-write-migration — cutover mechanism downstream of the audit.
- patterns/expand-migrate-contract — change-control pattern that often pairs with cross-engine migrations.