fix(web): query cases through PostgreSQL views - #340
Conversation
94dcb25 to
9aca951
Compare
berntpopp
left a comment
There was a problem hiding this comment.
Review — approve
Rebased onto main (330d736a) and reviewed in depth. This is not a cleanup PR: it fixes a bug that is live on main right now.
Root cause confirmed
Migration 0015_import_visibility — already merged — renames cases → cases_all and recreates cases as a VIEW. PostgreSQL only infers primary-key functional dependency for a base table, never through a view, so GROUP BY c.id while projecting c.name, c.file_path, … is rejected outright.
I verified this empirically by reverting only the source hunk and keeping the new test:
error: column "c.name" must appear in the GROUP BY clause or be used in an aggregate function
So case listing in web/Postgres mode is currently broken on main. This should land promptly.
The fix is complete, not partial
I audited every GROUP BY in src/main/storage/postgres/ (20 sites) against the two relations 0015 converts to views (cases, variants):
PostgresPanelsRepositoryGROUP BY p.id/gl.id—panelsandgene_listsare base tables, so PK functional dependency is legal. Not affected.PostgresCohortRepository:472(GROUP BY v.case_id, c.name),PostgresCaseLifecycleRepository:148,getSummary's grouped subquery — all project exactly their grouped columns plus aggregates. Legal.PostgresCasesQueryRepository.queryCases— the only site that projected ungrouped columns from a view.
The new GROUP BY list matches the SELECT list exactly (7 c.* columns + cm.affected_status + cm.sex, with cohort_names/cohort_ids aggregated). Complete.
Count query is safe
countSql reuses whereSql but has no joins, which would break if a filter referenced cm.* or cg.*. It doesn't: all three predicates reference only c.name/c.id or are self-contained EXISTS subqueries. No latent breakage, and total_count stays consistent with the grouped row count.
Test quality
The e2e is a genuine regression guard, not a tautology — I confirmed it fails without the fix and passes with it. Isolated per-run schema, DROP SCHEMA … CASCADE cleanup in afterAll, correctly gated behind VARLENS_RUN_POSTGRES_E2E=1, and wired into make web-gate-postgres. The unit-test addition asserting each projected column appears in the GROUP BY is a nice cheap guard that runs without Postgres.
This gap is exactly why the bug shipped: the only prior coverage of /api/cases/list was openapi.test.ts, which asserts the route's schema, never executing the query.
Verification
make typecheck— cleanVARLENS_WEB=1 make test— 4785 passed (matchesmainbaseline)- Postgres integration gate against a fresh DB — 15 passed / 1 skipped
- New e2e — passes on the fix, fails without it
- No
console.*, no Electron/fuse/IPC invariants touched, no new SQL interpolation (onlyschemaName, viaquoteIdentifier)
Non-blocking notes
- Grouping by 7 columns instead of 1 is a wider sort key, but
casesis a small relation — immaterial. - The test's fallback
PG_URLuses port55432while.env.postgres.localin this repo uses55434. Harmless (CI setsVARLENS_PG_URL), just noting the drift. - Conflicts with #332 in the
Makefile— both append aVARLENS_RUN_POSTGRES_E2E=1line toweb-gate-postgres. Trivial: merge the two file lists into one invocation.
Summary
web-gate-postgresWhy
PostgreSQL cannot infer primary-key functional dependencies through the migrated
casesview, so grouping only byc.idfails.Validation
VARLENS_WEB=1 make cimake web-gate-postgresmake agent-check