Cross-engine sync moves data between different database engines (e.g. Postgres → MySQL) by pivoting through a canonical schema: the source's native column types are normalized into an engine-independent vocabulary, then mapped back to the target engine's dialect. Rows travel as JSONL with per-engine identifier quoting and bind-parameter placeholders.
The canonical-schema machinery lives in internal/app/canonical*.go:
CanonicalType/CanonicalColumn/CanonicalSchemaare the engine-independent pivot (canonical.go).MapToNative(engine, col)renders a canonical column as a native column type forpostgres,mysql, ormariadb.VARCHAR/NUMERICcarry their precision (and scale, forNUMERIC) when known; otherwise the type is emitted bare and the engine applies its default.- Identifiers are quoted per engine (
"…"for Postgres,`…`for MySQL/MariaDB) and escaped, since table/column names cannot be passed as bind parameters. Placeholders are$n(Postgres) or?(MySQL/MariaDB). - The emit/consume halves (
canonical_emit.go,canonical_consume.go) stream a schema + rows as JSONL.
This machinery is built and unit-tested (canonical_test.go).
# Cross-engine sync from a Postgres source profile into a MySQL target profile.
siphon sync --from pg-prod --to mysql-staging --cross-engine| Capability | Status |
|---|---|
Canonical type vocabulary + MapToNative matrix |
✅ Works (unit-tested) |
| JSONL emit/consume with per-engine quoting + placeholders | ✅ Works (unit-tested) |
sync --cross-engine end-to-end |
The type-mapping and JSONL-transfer machinery exists and is tested, but
sync --cross-engine is capability-gated on CrossEngineSource
(source driver) and CrossEngineTarget (target driver). No driver declares
either capability today, so the request is rejected with
ErrDriverUnsupported (internal/app/sync.go, runCrossEngineSync).
The reason is deliberate: cross-engine translation needs a typed
CanonicalSchema (column types), and driver.Inspect does not carry column
types yet. Rather than fabricate a schema, the path stays gated until typed
schema introspection lands and a driver flips its cross-engine capabilities to
true. At that point runCrossEngineSync gains the real
emit → translate → consume pipeline.
siphon sync --from pg-prod --to mysql-staging --cross-engine
# Error: cross-engine sync requires typed schema introspection, not yet availableMapToNative maps each canonical type to a native type. These mappings are real
and usable even though the CLI path is gated:
| Canonical type | Postgres | MySQL | MariaDB |
|---|---|---|---|
int |
integer |
INT |
INT |
bigint |
bigint |
BIGINT |
BIGINT |
text |
text |
TEXT |
TEXT |
varchar |
varchar |
VARCHAR |
VARCHAR |
boolean |
boolean |
TINYINT(1) |
TINYINT(1) |
numeric |
numeric |
DECIMAL |
DECIMAL |
uuid |
uuid |
CHAR(36) |
CHAR(36) |
timestamptz |
timestamptz |
TIMESTAMP |
TIMESTAMP |
json |
jsonb |
JSON |
JSON |
varchar with a known length renders as e.g. VARCHAR(255); numeric with
known precision/scale renders as e.g. DECIMAL(10,2).
The v1 cross-engine scope (once the path is wired) is data only:
- Triggers, views, and stored functions are skipped.
- Indexes are created but not optimized for the target engine.
- Foreign keys are deferred.
- Engines covered by the matrix:
postgres,mysql,mariadb. An unknown engine or an unmappable canonical type is an error fromMapToNative.