diff --git a/drizzle.config.ts b/drizzle.config.ts index 2928e91..e21e9c2 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -5,7 +5,11 @@ export default defineConfig({ out: "./drizzle", dialect: "postgresql", dbCredentials: { - url: process.env.DATABASE_URL ?? "postgres://continuum:continuum@localhost:6432/continuum", + // Matches scripts/dev-env.sh's DEV_DATABASE_URL. It pointed at 6432 (a + // pooler) where nothing listens in this environment, so a bare db:migrate + // failed confusingly. Migrations want Postgres directly in any case: see + // the note in src/db/index.ts about parameterised queries through pgCat. + url: process.env.DATABASE_URL ?? "postgres://continuum:continuum@localhost:5432/continuum", }, verbose: true, strict: true, diff --git a/scripts/dev-env.sh b/scripts/dev-env.sh index fe1e4f6..1d8f55e 100755 --- a/scripts/dev-env.sh +++ b/scripts/dev-env.sh @@ -35,8 +35,10 @@ cd "$REPO_ROOT" PG_VERSION=16 PG_PORT=5432 VALKEY_PORT=6379 -# Straight at Postgres, not pgCat on 6432: .env.example points at the pooler for -# production, and neither backend here runs one. +# Straight at Postgres, not pgCat: .env.example points at the pooler for +# production, and neither backend here runs one. Keep it that way even where a +# pooler is reachable - parameterised queries from postgres-js hang through +# pgCat 1.2.0, and drizzle-kit cannot migrate through it. See src/db/index.ts. DEV_DATABASE_URL="postgres://continuum:continuum@localhost:${PG_PORT}/continuum" DEV_VALKEY_URL="redis://localhost:${VALKEY_PORT}" diff --git a/scripts/e2e-authz.sh b/scripts/e2e-authz.sh index 0358e3e..8a63db3 100755 --- a/scripts/e2e-authz.sh +++ b/scripts/e2e-authz.sh @@ -50,7 +50,11 @@ new_uuid() { fi } -psql_q() { PGPASSWORD=continuum psql -h localhost -U continuum -d continuum -tAq -c "$1"; } +# Uses DATABASE_URL rather than a hardcoded localhost. The backend under test +# reads that variable, so a psql pointed anywhere else seeds fixtures into a +# different database than the one being exercised - and the failure message +# below names DATABASE_URL, which would then be naming a URL nothing tried. +psql_q() { psql "$DATABASE_URL" -tAq -c "$1"; } cleanup() { local rc=$? diff --git a/scripts/e2e-edge.sh b/scripts/e2e-edge.sh index 74210d3..29c4509 100755 --- a/scripts/e2e-edge.sh +++ b/scripts/e2e-edge.sh @@ -62,7 +62,11 @@ new_uuid() { fi } -psql_q() { PGPASSWORD=continuum psql -h localhost -U continuum -d continuum -tAq -c "$1"; } +# Uses DATABASE_URL rather than a hardcoded localhost. The backend under test +# reads that variable, so a psql pointed anywhere else seeds fixtures into a +# different database than the one being exercised - and the failure message +# below names DATABASE_URL, which would then be naming a URL nothing tried. +psql_q() { psql "$DATABASE_URL" -tAq -c "$1"; } # The farm's operator. A real Supabase user id would come from the project; # here it only has to be a uuid that the token's `sub` and the org_members row diff --git a/src/db/index.ts b/src/db/index.ts index 8ba70e6..5ac2a2f 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -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");