Reusable gotchas for connecting a long-lived Rust (sqlx) writer on a GCP VM to Supabase Postgres, found during the slv1 measurement-rig work: (1) which pooler port to use and (2) how the postgres role actually bypasses RLS (both from the spec-grounding pass), plus (3) two sqlx↔Postgres type-decode casts that surfaced building the rig’s report queries (increment 1). Applies to any Supabase-from-GCP service, not just slv1.
For Agents
Reusable infra recipe. A Rust /
sqlxservice on a stock GCP VM writing to Supabase Postgres should use Supavisor session mode on port 5432 and rely on table ownership (notBYPASSRLS) for thepostgresrole. Why the obvious alternatives fail is below.
Connection: use Supavisor session mode (:5432)
For a long-lived Rust writer using sqlx on a stock GCP VM, the correct endpoint is the Supavisor session-mode pooler, port 5432. Both alternatives fail:
| Option | Port / host | Verdict |
|---|---|---|
| Session pooler | Supavisor :5432 | Correct. Persistent server connection per client → prepared statements work |
| Transaction pooler | Supavisor :6543 | Breaks sqlx — prepared statement "sqlx_s_1" already exists / “transaction mode does not support prepared statements”. Transaction mode multiplexes many clients onto few server conns, so prepared statements collide |
| Direct DB host | db.<ref>.supabase.co | IPv6-only without a paid add-on; stock GCP VMs are IPv4-only → unreachable |
The :6543 trap
sqlxprepares statements by default. On the transaction pooler (:6543) that surfaces asprepared statement "sqlx_s_1" already exists. The clean fix is session mode (:5432) for a persistent writer — do not reach for disabling statement caching as a workaround.
RLS: postgres bypasses via OWNERSHIP, not BYPASSRLS
Common misconception: Supabase’s postgres connection role is a superuser or carries BYPASSRLS. Neither is true.
- The rig connects as
postgres, which bypasses RLS because it OWNS the tables — a table owner is exempt from RLS on its own tables (unlessFORCE ROW LEVEL SECURITYis set). postgresis not a superuser and does not carry theBYPASSRLSattribute.BYPASSRLSbelongs toservice_role— a NOLOGIN role that PostgREST assumes via JWT, not a connection-string login. You cannot connect asservice_rolefromsqlx.
Never FORCE RLS on owner-written tables
FORCE ROW LEVEL SECURITYsubjects even the table owner to the policy set. With RLS enabled + no policies (default-deny), that denies ALL writes from the owner — i.e. it breaks the very writer you want. Enable RLS, add no policies (default-deny foranon/authenticated), but neverFORCEit on tables the owner must write.
Recommended pattern (from the slv1 rig)
- Enable RLS on all tables, no policies → PostgREST roles (
anon/authenticated) get default-deny. - Tables owned by
postgres(they are, when migrations run via the Supabase MCP). - Defense-in-depth:
REVOKE ALL ON <tables> FROM anon, authenticated. - Keep the Supabase MCP off the write path — it is a dev/ops tool (migrations, ad-hoc SQL). The service writes via plain
DATABASE_URL.
sqlx ↔ Postgres type-decode casts
Two type-mapping mismatches that sqlx surfaces at runtime (not compile time — the rig uses sqlx::query with .bind, not the query! macro), both fixed with an explicit SQL cast:
make_interval(hours => $1::int)—sqlxbinds a Rusti64as Postgresint8, butmake_interval’s parameters areint4. Passing ani64hours window without the::intcast fails on the function-signature type match. Cast the bind:make_interval(hours => $1::int).sum(x)::bigint—sum()over abigintcolumn returnsnumeric, whichsqlxcannot decode into ani64. Cast the aggregate to the integer type you decode into:sum(x)::bigint.
These are report-query gotchas
Both were hit building the rig’s
reportsubcommand (time-windowed percentile / rollup SQL). The connection-mode gotcha (session vs transaction pooler) is a separate issue — see the Connection section at the top of this note.
Reuse
Other vault projects touching Supabase and/or GCP where this recipe applies if they grow a server-side long-lived pooled writer: levandor-infra (GCP VMs), txArchive 2025 Rewrite and levandor-crm (Supabase backends).
Related
- solv1-rig-increment-1-build-2026-07-03 — the rig build; the
sqlxcast gotchas were found here - slv1 — found during the measurement-rig grounding pass (spec §2.5 storage, §4 schema/RLS)
- solana-v0-alt-classification-trap — sibling finding from the same pass
- levandor-infra — GCP provisioning context