Findings No. 32
The Turso engine canonicalizes stored DDL, so `drizzle-kit push` can never converge
Turso's engine rewrites stored DDL into its own canonical text; drizzle-kit push diffs DDL as text, so every DEFAULT column reads as changed. Use generate and migrate.
The Turso engine does not store your DDL as you wrote it. It stores a canonical form, and drizzle-kit push compares DDL as text, so the two can never agree.
We proved it with a throwaway table. A column default written as DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) came back as DEFAULT (CAST (unixepoch ('subsecond') * 1000 AS integer)), and an index named _ddl_probe_createdAt_idx came back lowercased. Keywords and literals are uppercased, function calls gain a space, identifiers are lowercased. Against Replicator's schema, push reported 131 DEFAULT-bearing columns as changed. Its remedy, ALTER TABLE t ALTER COLUMN c TO c <def>, is a libsql extension the engine rejects as duplicate column name. Rewriting our schema to match the engine's text changed nothing, because drizzle normalizes its own expected text in the opposite direction.
The fix was to stop using push and switch to generate plus migrate. Those diff against drizzle's JSON snapshot and replay explicit SQL, so the database is never introspected and its canonical text never matters.
The general rule: a schema tool that diffs DDL as text only works against a database that stores DDL verbatim. When the database normalizes, move to snapshot-based migrations instead of chasing the normalizer.
Receipt: Replicator v3.295.0 (the _ddl_probe probe table; FINDINGS.md, "Turso engine, Drizzle and SQLite").