Skip to content

fix(pg-delta): suppress Wasm FDW dependents and pgmq queue triggers in supabase integration#261

Open
avallete wants to merge 9 commits into
mainfrom
fix/supress-fasm-fwd-server-and-followup
Open

fix(pg-delta): suppress Wasm FDW dependents and pgmq queue triggers in supabase integration#261
avallete wants to merge 9 commits into
mainfrom
fix/supress-fasm-fwd-server-and-followup

Conversation

@avallete
Copy link
Copy Markdown
Member

Summary

Follow-up to #258 / CLI-1470. Extends the Supabase integration filter so db pull migrations no longer emit DDL that local supabase db reset cannot replay:

  1. Wasm FDW dependentsCREATE SERVER, CREATE FOREIGN TABLE, and CREATE USER MAPPING for platform Wasm wrappers (clerk, clerk_oauth, …) whose handler/validator lives in extensions.*.
  2. pgmq queue triggers — user CREATE TRIGGER on pgmq.q_* / pgmq.a_* tables materialized by pgmq.create(), when the pg_depend link to the pgmq extension is missing (notably pgmq 1.4.4 on Supabase Cloud).

Also adds Verdaccio tooling so you can publish @supabase/pg-delta locally and point the CLI at it without waiting for npm.

Problem

After pulling a remote schema with pg-delta, supabase db reset failed at different statements:

Symptom Example from repro
Missing Wasm FDW ERROR: foreign-data wrapper "clerk_oauth" does not exist at CREATE SERVER clerk_oauth_server ...
Missing pgmq queue table ERROR: relation "pgmq.q_processed_milestones_queue" does not exist at CREATE TRIGGER after_insert_processed_milestones_queue ... ON pgmq.q_processed_milestones_queue

CLI-1470 already suppressed CREATE/DROP/ALTER FOREIGN DATA WRAPPER for Wasm wrappers in extensions.*, but dependent server/foreign-table/user-mapping DDL still leaked through. For pgmq, the trigger extractor’s pg_depend deptype='e' filter works on newer pgmq, but pgmq 1.4.4 (what Cloud ships today) never records that dependency for dynamically created q_* / a_* tables — verified on a real project where all 59 queue/archive tables had has_ext_link = false while cron.job, pgmq.meta, vault.secrets, etc. were linked correctly.

Solution

Wasm FDW dependents

  • Join parent wrapper handler / validator onto server, foreign_table, and user_mapping models at extract time (pg_foreign_data_wrapper + pg_proc), omitted from dataFields() so diffs stay stable.
  • Extend the Supabase filter to drop those dependents when either reference matches ^extensions\..
  • Exception: server privilege scope is not suppressed — GRANT/REVOKE ON SERVER does not require superuser; user postgres_fdw servers legitimately install into extensions and their ACL must roundtrip (covered by existing e2e “preserves GRANT on user-owned FOREIGN SERVER”).

pgmq queue triggers

  • Defensive fallback on the existing “include user triggers on managed schemas when function_schema is user-owned” rule: exclude triggers on pgmq tables whose name matches ^[qa]_.
  • Complements extension_table_oids in trigger.model.ts for projects where pg_depend never recorded the extension link.

Local dev (Verdaccio)

  • bun run verdaccio:start + bun run pg-delta:publish-local publish @supabase/pg-delta as 0.0.0-local.<timestamp> to http://localhost:4873/.
  • Optional --write-version-to=<project>/supabase/.temp/pgdelta-version so the CLI resolves the local build via PGDELTA_NPM_REGISTRY.

Test plan

  • Unit: packages/pg-delta/src/core/integrations/supabase.test.ts — Wasm FDW dependents + pgmq queue triggers (22 cases)
  • Integration: tests/integration/supabase-dsl-e2e.test.ts — Wasm dependents, pgmq trigger with simulated missing pg_depend, auth.users trigger preserved, FDW ACL regressions
  • bun run format-and-lint --write --unsafe && bun run check-types && bun run knip --fix

Manual validation (reporter):

  1. bun run pg-delta:publish-local --write-version-to=<project>/supabase/.temp/pgdelta-version
  2. PGDELTA_NPM_REGISTRY=http://host.docker.internal:4873 (or equivalent) + supabase db pull --diff-engine pg-delta
  3. Confirm migration no longer contains CREATE SERVER ... clerk_oauth or CREATE TRIGGER ... ON pgmq.q_*
  4. supabase db reset completes without 42704 / 42P01 on those statements

Commit structure (RED → GREEN)

Commit Purpose
test(pg-delta): add failing regression for Wasm FDW server, foreign table, and user mapping dependents RED — tests only
fix(pg-delta): suppress Wasm FDW server, foreign table, and user mapping dependents GREEN — models + filter + changeset
test(pg-delta): add failing regression for user triggers on pgmq queue tables RED — tests only
fix(pg-delta): suppress user triggers on pgmq queue/archive tables GREEN — filter + changeset
feat: integrate Verdaccio for local pg-delta development Dev workflow
chore: update .gitignore for Verdaccio storage Hygiene

Changesets

  • .changeset/wasm-fdw-dependents.md — patch
  • .changeset/pgmq-queue-trigger-fallback.md — patch

Follow-ups / out of scope

  • normalizeCatalog drops foreignTable.security_labels — latent gap masked by symmetric roundtrip normalization; needs a direct extractCatalog assertion + dummy_seclabel; not fixed here.
  • Upstream pgmq 1.4.4 — does not register pg_depend deptype='e' on pgmq.q_* / pgmq.a_*. A Supabase Cloud pgmq upgrade would reduce reliance on the name-match fallback over time.
  • Other dynamic extension tables (partman partitions, etc.) — not observed on the reproducer project; track separately if they surface.

avallete added 7 commits May 21, 2026 15:07
…able, and user mapping dependents in supabase integration

Pins the post-CLI-1470 gap: suppressing CREATE/DROP/ALTER FOREIGN DATA
WRAPPER for Wasm wrappers (`clerk`, `clerk_oauth`, ...) on its own
leaves dependent CREATE SERVER / CREATE FOREIGN TABLE / CREATE USER
MAPPING in the diff, which breaks `supabase db reset` locally with
`foreign-data wrapper "clerk_oauth" does not exist`.

Refs #258
…ing dependents in supabase integration

Follow-up to CLI-1470 / #258. The supabase filter previously
suppressed CREATE/DROP/ALTER FOREIGN DATA WRAPPER for Wasm-based
wrappers (handler/validator in `extensions.*`), but left their
dependent CREATE SERVER, CREATE FOREIGN TABLE, and CREATE USER
MAPPING in the diff. Local `supabase db reset` then aborts at the
SERVER statement with:

  ERROR: foreign-data wrapper "clerk_oauth" does not exist (SQLSTATE 42704)
  At statement: 9
  CREATE SERVER clerk_oauth_server FOREIGN DATA WRAPPER clerk_oauth OPTIONS (...);

Carry the parent wrapper's handler/validator on the server,
foreign_table, and user_mapping models at extract time (joined to
`pg_foreign_data_wrapper` + `pg_proc`, omitted from `dataFields()`
so diffs stay stable) and extend the supabase integration filter
to drop these dependents when either is in `extensions.*`. Server
*privilege* scope is intentionally exempt because `postgres_fdw`
legitimately installs into `extensions` and its server ACL is
user-declarative state (covered by the existing CLI-1469
companion test "preserves GRANT on user-owned FOREIGN SERVER").

RED output (from preceding test commit):

  (fail) supabase integration filter — Wasm FDW dependents > suppresses CREATE SERVER bound to extensions.* Wasm FDW
    expect(received).toBe(expected); Expected: false; Received: true
  (fail) supabase integration filter — Wasm FDW dependents > suppresses DROP FOREIGN TABLE bound to extensions.* Wasm FDW
    expect(received).toBe(expected); Expected: false; Received: true
  (fail) supabase integration filter — Wasm FDW dependents > suppresses ALTER FOREIGN TABLE bound to extensions.* Wasm FDW
    expect(received).toBe(expected); Expected: false; Received: true
  (fail) supabase integration filter — Wasm FDW dependents > suppresses DROP USER MAPPING bound to extensions.* Wasm FDW
    expect(received).toBe(expected); Expected: false; Received: true
  (fail) supabase integration filter — Wasm FDW dependents > suppresses CREATE USER MAPPING when only wrapper validator is in extensions
    expect(received).toBe(expected); Expected: false; Received: true
  (fail) supabase integration e2e (pg17) > suppresses Wasm FDW server, foreign table, and user mapping dependents
    expect(received).toStrictEqual(expected); received: ["CREATE USER MAPPING FOR postgres SERVER wasm_server OPTIONS (user 'remote', password '__OPTION_PASSWORD__')"]

Refs #258
- Added Verdaccio configuration for local package publishing.
- Introduced scripts for starting Verdaccio and publishing pg-delta locally.
- Updated .gitignore to include Verdaccio storage and configuration files.
- Updated package.json to include Verdaccio as a dependency and new scripts.
- Changed pg-delta versioning to a local format for development.

This setup facilitates easier local development and testing of the pg-delta package.
…e tables in supabase integration

Pins the case where `db pull` emits CREATE TRIGGER against
`pgmq.q_<name>` / `pgmq.a_<name>` tables that are dynamically
materialized by `select pgmq.create('<name>')`. On a healthy
install the trigger extractor's `pg_depend deptype='e'` filter
already drops these, but real-world cloud projects (e.g. pgmq
1.4.4 which is what Supabase Cloud currently ships) can lose
that row, causing `supabase db reset` to abort with
`relation "pgmq.q_<name>" does not exist`.

Refs #258
… supabase integration

Follow-up to the Wasm FDW dependents fix in #258. The trigger
extractor already drops user triggers on tables that pgmq
records as `pg_depend deptype='e'` to the pgmq extension, but
real-world cloud projects can be missing that row. The
immediate motivator is pgmq `1.4.4` (the version Supabase
Cloud currently ships) which never records the dependency
for `pgmq.q_<name>` / `pgmq.a_<name>` tables; verified on a
real cloud project where all 59 queue/archive tables show
`has_ext_link = false` while every other system-schema
table (`cron.job`, `pgmq.meta`, `pgsodium.key`, `vault.secrets`)
is properly linked. Older pgmq installs and manual
`pg_dump`/restore that loses extension deps hit the same gap.

When the row is missing, `supabase db reset` aborts at the
trigger statement with:

  ERROR: relation "pgmq.q_<name>" does not exist (SQLSTATE 42P01)
  CREATE TRIGGER ... AFTER INSERT ON pgmq.q_<name> ...

Add a defensive name-match fallback in the supabase
integration filter that drops user triggers on
`pgmq.q_*` / `pgmq.a_*` regardless of pg_depend state.
A pgmq upgrade on the Supabase Cloud side closes the
upstream root cause; this fix covers the window until
all projects upgrade.

RED output (from preceding test commit):

  (fail) supabase integration filter — pgmq queue triggers > suppresses CREATE trigger on pgmq.q_<name> calling a public function
    expect(received).toBe(expected); Expected: false; Received: true
  (fail) supabase integration filter — pgmq queue triggers > suppresses DROP trigger on pgmq.a_<name> calling a public function
    expect(received).toBe(expected); Expected: false; Received: true
  (fail) supabase integration e2e (pg17) > suppresses user triggers on pgmq queue tables when pg_depend link is missing
    received: ["CREATE TRIGGER after_insert_processed_milestones_queue AFTER INSERT ON pgmq.q_processed_milestones_queue FOR EACH ROW EXECUTE FUNCTION move_data_from_queue()"]

Refs #258
…ge directories

- Added 'verdaccio/.verdaccio/' to ignore Verdaccio's local storage.
- Ensured '.verdaccio/plugins/' is also included for completeness.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 21, 2026

🦋 Changeset detected

Latest commit: 9467092

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@supabase/pg-delta Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/supabase/pg-toolbelt/@supabase/pg-delta@261
npm i https://pkg.pr.new/supabase/pg-toolbelt/@supabase/pg-topo@261

commit: 9467092

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant