Skip to content

Five foreign keys declare an ON DELETE action the database does not have (drizzle-kit cannot see it) #2239

Description

@jakebromberg

Problem

Five foreign keys declare a referential action in shared/database/src/schema.ts that the database does not have. The deployed constraints are all plain NO ACTION. This is invisible to drizzle-kit generate — the snapshots agree with schema.ts, so no catch-up migration is ever emitted, and the drift will not surface on its own.

Verified against both the CI database built from the full migration chain (a ci:db-init from empty) and production on 2026-08-21, which rules out a local-only artifact. pg_constraint.confdeltype = 'a' in both:

constraint schema.ts declares database has
show_djs_show_id_shows_id_fk onDelete: 'cascade' NO ACTION
artist_library_crossreference_artist_id_artists_id_fk onDelete: 'cascade' NO ACTION
genre_artist_crossreference_artist_id_artists_id_fk onDelete: 'cascade' NO ACTION
genre_artist_crossreference_genre_id_genres_id_fk onDelete: 'cascade' NO ACTION
schedule_specialty_id_specialty_shows_id_fk onDelete: 'set null' NO ACTION

The audit that produced the table (run against either database):

SELECT c.conname, c.confdeltype, cl.relname AS child, fcl.relname AS parent
FROM pg_constraint c
JOIN pg_class cl ON cl.oid = c.conrelid
JOIN pg_class fcl ON fcl.oid = c.confrelid
JOIN pg_namespace n ON n.oid = cl.relnamespace
WHERE c.contype = 'f' AND c.confdeltype = 'a' AND n.nspname IN ('wxyc_schema', 'public')
ORDER BY cl.relname, c.conname;

Ten other FKs also come back NO ACTION from that query (library.*, bins.album_id, flowsheet.label_id, library_identity*, shift_covers.schedule_id, shows.specialty_id). Those are correctschema.ts declares no action for them either. Only the five above disagree.

How it was found

Writing the teardown for tests/integration/flowsheet-open-shows.spec.js in #2238. DELETE FROM shows raised

PostgresError: update or delete on table "shows" violates foreign key constraint
"show_djs_show_id_shows_id_fk" on table "show_djs"

against rows that schema.ts says should have cascaded. The spec now deletes show_djs explicitly, with a comment pointing here.

Why it matters

A declared-but-absent ON DELETE is worse than no declaration, because callers are written against the declaration:

  • Deletes fail that look like they should succeed. Any DELETE FROM shows with live show_djs rows raises instead of cascading. That is now a live path: POST /flowsheet/shows/:id/force-end (Operator close for an abandoned show: GET /flowsheet/open-shows + force-end (tubafrenzy parity, dies 8/31) #2235) does not delete shows, but the operator tooling around abandoned shows is the area where someone will reach for one.
  • The artist crossreference FKs are the sharper edge. jobs/artist-unicode-dedup (Unicode-form artist duplication at the catalog write boundary (artistIdFromName) #1897) repoints every artist_id FK explicitly before deleting a duplicate artist, so it is safe because it does not rely on the cascade. Anything that does rely on it — a future artist-merge path, a DELETE /library/:id extension — will fail at runtime against a constraint the schema promised would clean up after it.
  • A reader of schema.ts is being told something false. That file is the reference every job and service is written from.

Desired end state

schema.ts and the database agree, in one direction or the other, for all five. Either is defensible per constraint — the point is that the two stop disagreeing silently.

Suggested approach

Decide direction per constraint before writing anything. They are not one decision:

  • show_djs.show_id — cascade is almost certainly right. A show_djs row has no meaning without its show.
  • genre_artist_crossreference.*, artist_library_crossreference.artist_id — cascade is right for a junction table, but deleting an artist row is not currently a routine operation, and artist-unicode-dedup already handles its own repointing. Worth confirming nothing depends on the absence of the cascade as a safety brake before adding one.
  • schedule.specialty_idSET NULL on a deleted specialty show. Check whether schedule readers tolerate a NULL there.

Then either:

  1. Align the database — a migration issuing ALTER TABLE … DROP CONSTRAINT … ; ALTER TABLE … ADD CONSTRAINT … ON DELETE … per constraint. Read docs/migrations.md first; a constraint-adding migration wants a precondition guard, and scripts/validate-migrations.mjs check 8 warns without one. Note the re-add takes a lock and validates existing rows.
  2. Align schema.ts — drop the onDelete clauses so the file describes what is actually deployed, and fix any caller that was relying on the promise.

A guard is worth more than the fix. The reason this sat undetected is that drizzle-kit does not diff referential actions, so nothing in CI can see it. A scripts/-level check comparing pg_constraint.confdeltype against the onDelete clauses parsed out of schema.ts — run in the migrate-dryrun job, which already has a migrated database in hand — would catch the next one at PR time. Same shape as scripts/check-auth-tables-doc.mjs. Consider splitting that into its own follow-up if the per-constraint decisions above take longer than the guard.

Acceptance criteria

  • A direction is chosen and recorded for each of the five constraints
  • schema.ts and a freshly-migrated database agree on confdeltype for all five
  • Production verified to match after deploy (the audit query above returns none of the five)
  • Any caller relying on a cascade that is being removed — rather than added — is fixed in the same PR
  • Ideally: a CI check that fails on the next referential-action drift
  • tests/integration/flowsheet-open-shows.spec.js's teardown comment updated or removed if show_djs gains its cascade

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmigrationDatabase migration issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions