Skip to main content
Lightning Jar - Web Studio Lightning Jar Wordmark

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.

Replicator Databases verified Turso engine database; drizzle-kit push, 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").

Ask Eljay

Ask about the studio's work, research, packages, or writing. A few starters:

Answers come from this site's own content and link their sources. For anything that matters, email hello@lightningjar.com.