Record that this client cannot run behind pgCat 1.2.0, and stop lying about it

A real pooled database became available, so the deployment assumption in
src/db/index.ts - "points at pgCat/PgBouncer, so keep prepared statements
disabled" - was finally testable. It does not hold.

Through pgCat 1.2.0 an unparameterised `select 1` succeeds, and so does
`select count(*) from organisations`, but any query carrying a bind
parameter hangs forever. That is true with prepare:false, with
prepare:true, with fetch_types:false, and inside an explicit
transaction. The same queries straight at Postgres all pass, and psql's
\bind through the same pool returns its row, so the fault is specific to
how postgres-js frames those messages rather than to extended protocol.
Every Drizzle query binds parameters, so no subset of this app works
there. drizzle-kit hung at commit for the same reason - it builds its
own client and drizzle.config.ts cannot pass prepare:false - having
executed all 14 statements of 0000, which then rolled back.

Transaction-mode pooling itself was confirmed, not assumed: in one
client session consecutive statements hopped between server backends
while statements inside BEGIN..COMMIT stayed pinned to one.

So the comment now records what was measured, names PgBouncer as the
pooler this configuration actually suits, and says migrations go
straight at Postgres. dev-env.sh's note gets the same correction.

Two real bugs found on the way there:

- e2e-authz.sh and e2e-edge.sh hardcoded `psql -h localhost -U continuum
  -d continuum` while both read DATABASE_URL for the backend under test.
  Point DATABASE_URL elsewhere and the suite seeds fixtures into one
  database while exercising another - and its own failure message says
  "no database at $DATABASE_URL", naming a URL nothing ever tried. Both
  now use `psql "$DATABASE_URL"`.

- drizzle.config.ts fell back to port 6432, where nothing listens here,
  so a bare db:migrate failed confusingly. It now matches dev-env.sh's
  DEV_DATABASE_URL.

bun test 128 pass / 0 fail; e2e-authz.sh PASS with 68 checks against
local Postgres after the psql_q change. e2e-edge.sh was not re-run
because it drives the real printers, and its change is the same line.
This commit is contained in:
2026-10-20 09:39:39 +00:00
parent adf77fe0ef
commit 0e6a4aaa93
5 changed files with 40 additions and 7 deletions
+21 -2
View File
@@ -2,8 +2,27 @@ import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";
// Points at pgCat/PgBouncer, not Postgres directly, so keep prepared
// statements disabled and cap the pool per-instance connections low.
// `prepare: false` because this is meant to sit behind a transaction-mode
// pooler, where a named statement prepared on one server connection is not
// there on the next one. `max` is low for the same reason: the pooler
// multiplexes, so a high per-instance count buys nothing and starves peers.
//
// Measured 2026-10-20, and the reason to read this before assuming the
// deployment works: against pgCat 1.2.0 this client cannot run at all.
// Through that pooler an unparameterised `select 1` succeeds, and so does
// `select count(*) from organisations`, but *any* query carrying a bind
// parameter hangs forever - with `prepare: false`, with `prepare: true`, with
// `fetch_types: false`, and inside an explicit transaction alike. The same
// queries straight at Postgres are fine, and psql's `\bind` through the same
// pool is fine, so it is specific to how postgres-js frames those messages,
// not to extended protocol in general. Every Drizzle query binds parameters,
// so there is no subset of this app that works there.
//
// So `prepare: false` is necessary but nowhere near sufficient, and running
// behind pgCat is an unverified claim rather than a tested path. PgBouncer is
// the pooler this configuration is actually known to suit. Migrations go
// straight at Postgres regardless - drizzle.config.ts has no way to pass
// `prepare: false`, and drizzle-kit hung at commit for the same reason.
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
throw new Error("DATABASE_URL is not set");