fix(db): make db seed work on SQLite - #540
Conversation
db seed failed immediately on SQLite because the seed bookkeeping DDL was
hardcoded PostgreSQL. Fixing that surfaced four further blockers, found by
running the whole seed registry against a migrated SQLite database.
Seed bookkeeping DDL now lives in per-dialect .sql files under
seeder/schema/, embedded with go:embed and split on --bun:split, so the SQL
stays reviewable instead of sitting in Go string literals. Statements execute
one at a time, which SQLite drivers require and which names the failing
statement when something breaks.
Model defaults: the jsonb casts in bun default tags ('{}'::jsonb) emitted a
'::' token SQLite cannot parse. The casts are redundant on PostgreSQL too,
since a string literal assignment-casts to jsonb, so they are simply dropped.
Converter fixes:
- Array columns map to TEXT holding JSON, so the PostgreSQL empty-array literal
'{}' has to become '[]'. As an empty JSON object it failed to unmarshal into
a Go slice.
- numeric/decimal map to REAL rather than NUMERIC. NUMERIC affinity demotes
integral values to INTEGER, which then refuses to scan into a Go float64.
Precision is unchanged; SQLite has no exact decimal either way.
- DROP COLUMN is emitted again instead of being skipped wholesale. Skipping it
left columns PostgreSQL had dropped behind as NOT NULL, which broke inserts.
SQLite only refuses the drop when an index or constraint still references the
column, so the converter now tracks emitted indexes and constraint columns,
drops blocking indexes first, and skips only genuinely blocked drops.
All 1518 translated statements apply cleanly, up from 1471, and the seed run
reaches 21 of 25 seeds. The remaining blocker is an upstream bun bug, reported
separately.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXYnxBszfwkNUeeAcVSSNf
bun decides which columns a bulk insert writes by looking only at the first element of the slice (uptrace/bun#1394). A notnull column whose first row holds a zero value is dropped from the statement entirely, so every row silently gets the table default instead of its own value. SQLite hits this on every bulk insert; Postgres only when the table has an identity column. Removing default:0 and default:false from plain scalar fields sidesteps it, because bun then writes the actual value rather than treating zero as "use the default". The column keeps its database default for inserts that omit it, so nothing changes on Postgres. The tag is left alone on pointer, nullzero and nullable fields such as decimal.NullDecimal. bun writes NULL rather than the zero value there, so the default is load-bearing and removing it violates the notnull constraint. That was caught by the seed run, on shipments.other_charge_amount. Adds a test that migrates a throwaway SQLite database and runs the whole seed registry, which is what found this and the four dialect problems before it. All 25 seeds now apply on SQLite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXYnxBszfwkNUeeAcVSSNf
…te-support-4oc8h8
|
Important Review skippedToo many files! This PR contains 181 files, which is 81 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (181)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
trenova | f0cbb77 | Aug 12 2026, 03:39 AM |
Description
db seedfailed on the first statement under SQLite, because the seed bookkeeping DDL was hardcoded PostgreSQL:Fixing that surfaced five more blockers behind it. All six are fixed here and all 25 seeds now apply on SQLite. PostgreSQL behaviour is unchanged throughout.
Related Issue or Discussion
Follow-up to #536. The last blocker is an upstream bun bug, uptrace/bun#1394, with a fix open at uptrace/bun#1412.
Type of Change
Scope
Seed bookkeeping DDL —
seeder/schema/{postgres,sqlite}/*.sql, embedded withgo:embedand split on--bun:split. The SQL is out of the Go string literals so it stays reviewable and diffable, matching how the migrations are written. Statements run one at a time, which SQLite drivers require and which names the failing statement when one breaks.Model default tags —
'{}'::jsonbemitted a::token SQLite cannot parse. The cast is redundant on PostgreSQL too, since a string literal assignment-casts tojsonb, so it is dropped.Converter,
scripts/dialect-convert/'{}'has to become'[]'. As an empty JSON object it failed to unmarshal into a Go slice.numeric/decimalmap toREALrather thanNUMERIC. NUMERIC affinity demotes integral values to INTEGER, which then refuses to scan into a Gofloat64. Precision is unchanged either way; SQLite has no exact decimal regardless.DROP COLUMNis emitted again rather than skipped wholesale. Skipping it left columns PostgreSQL had dropped behind asNOT NULL, which broke inserts. SQLite only refuses the drop when an index or constraint still references the column, so the converter tracks emitted indexes and constraint-pinned columns, drops blocking indexes first, and skips only genuinely blocked drops. This raised the translated statement count from 1471 to 1518, all applying cleanly.Zero-value default tags — bun picks the columns for a bulk insert by looking only at the first element of the slice (uptrace/bun#1394). A
notnullcolumn whose first row holds a zero value is dropped from the statement, so every row silently takes the table default instead of its own value. Removingdefault:0anddefault:falsefrom plain scalar fields sidesteps it: bun then writes the actual value. 344 tags across 125 files.The tag is deliberately kept on the 44 pointer,
nullzeroand nullable fields such asdecimal.NullDecimal. bun writes NULL rather than the zero value there, so the default is load-bearing and removing it violates thenotnullconstraint —shipments.other_charge_amountis one, and it would have broken PostgreSQL too. Those 44 remain exposed to the upstream bug until #1412 lands.Validation
cd services/tms && task test— passescd services/tms && task lint— not run. golangci-lint is unavailable in this environment: it is built against Go 1.25 while the module targets Go 1.26 and exits withcan't load config.gofmtandgo vetare clean over the changed packages.cd client && pnpm build— no client changescd client && pnpm lint— no client changespython3 scripts/dialect-convert/convert.py sqlite --check— 1518/1518 statements apply, 273 tablesgo build ./...andgo vet ./internal/core/domain/...— cleanAdds
TestFullSeedRunOnSQLite, which migrates a throwaway SQLite database and runs the whole seed registry. It is what found every problem above, including a first attempt at the tag sweep that was too broad. Runs in about 7 seconds and needs no Docker.Deployment Notes
No migrations, config, or env changes for PostgreSQL deployments.
The default-tag removals change generated SQL on PostgreSQL: bun now writes the Go zero value where it previously wrote the column default. For the fields touched those are the same value, which is why the sweep was restricted to plain scalars.
Checklist
AGENTS.md,CLAUDE.md, and existing repository patterns.Generated by Claude Code