Skip to content

fix(cli): preserve view reloptions in db diff output#5478

Closed
nirav-gondaliya wants to merge 1 commit into
supabase:developfrom
nirav-gondaliya:fix/view-reloptions-3973
Closed

fix(cli): preserve view reloptions in db diff output#5478
nirav-gondaliya wants to merge 1 commit into
supabase:developfrom
nirav-gondaliya:fix/view-reloptions-3973

Conversation

@nirav-gondaliya
Copy link
Copy Markdown

@nirav-gondaliya nirav-gondaliya commented Jun 4, 2026

Summary

supabase db diff was silently dropping the WITH (...) clause on CREATE VIEW statements (security_invoker, security_barrier, check_option, plus reloptions on materialized views). The underlying diff engines (djrobstep/migra, @pgkit/migra) don't read pg_class.reloptions, so a declarative view defined as

create or replace view "public"."user_details"
with (security_invoker=true) as
select id, name from "private"."users";

ended up in the generated migration as create or replace view "public"."user_details" as SELECT ...

— silently turning a SECURITY INVOKER view into a SECURITY DEFINER one and bypassing RLS on the underlying table.

What changed

  • New helper apps/cli-go/internal/db/diff/view_options.go queries pg_class.reloptions on the diff target and splices a WITH (...) clause back into any CREATE [OR REPLACE] [MATERIALIZED] VIEW statement that lacks one. Failures (connection / query) are soft — the original diff is returned with a stderr warning so the diff is never lost.
  • DiffDatabase calls the helper after the underlying diff engine returns, so the fix flows to every DiffFunc implementation.
  • Removed the matching limitation from apps/cli-go/docs/supabase/db/diff.md.

Scope

This PR closes the new-view case from #3973. A related gap surfaced during testing — migra doesn't detect reloption-only changes on existing views, so flipping security_invoker=true → false on an already-applied view produces No schema changes found. That is a separate migra limitation (the patcher only runs when there's a CREATE VIEW to patch) and is tracked separately at #5476.

Why the fix is engine-agnostic

The patcher's regex matches view "x"."y"\s+as — bare as. Engines that already emit with (...) between the name and as (e.g. pg-schema-diff) do not match the regex and pass through unchanged.

Closes #3973

Re-attach pg_class.reloptions to CREATE VIEW statements emitted by db diff, so declarative schemas containing `with (security_invoker=true)` (or any other view-level reloption) round-trip through the generated migration instead of silently losing the clause.

The patcher hooks into DiffDatabase, so the fix applies to every diff engine (migra, migra-bash fallback, pg-schema-diff, pg-delta). On engines that already emit the WITH clause (e.g. pg-schema-diff), the regex deliberately does not match, leaving their output untouched.

Closes supabase#3973
@avallete
Copy link
Copy Markdown
Member

avallete commented Jun 5, 2026

Hey there !

Thank's for your contribution. Moving forward we want to deprecate diff based on migra/pgschema in favor of diff based on pg-delta which already cover this case: #3973 (comment)

Could you give it a try ? This code path will be removed bit by bit as users migrate and pg-delta mature.

@nirav-gondaliya
Copy link
Copy Markdown
Author

Hey @avallete , thanks for the pointer, confirmed pg-delta handles this end-to-end. I diffed two DBs where the target had:

  • A regular view with WITH (security_invoker=true, security_barrier=true, check_option=local)
  • A materialized view with WITH (autovacuum_enabled=false)

supabase db diff --from <src> --to <tgt> (which routes through DiffPgDeltaRef) emitted both CREATE VIEW … WITH (…) AS … and CREATE MATERIALIZED VIEW … WITH (…) AS … WITH DATA with every reloption preserved. So the catalog-level diff in pg-delta solves the original #3973 footgun cleanly — no SQL-regen patch needed on that path.

One open question before I close: pg-delta is still flagged experimental and --use-migra is the default. Until pg-delta is the default (or graduates from experimental), users on the stock supabase db diff will still silently lose security_invoker and fall through to SECURITY DEFINER, which is a real security regression rather than a cosmetic one. Would you want a minimal stopgap on the migra path for the security-relevant reloptions (security_invoker, security_barrier) until pg-delta is the default? Happy to either:

  1. Trim this PR to just those two options on the migra/pgschema paths as a stopgap, or
  2. Close it and rely entirely on pg-delta + the --use-pg-delta / [experimental.pgdelta] enabled = true docs.

Either works for me — your call on which fits the deprecation timeline better.

@avallete
Copy link
Copy Markdown
Member

avallete commented Jun 5, 2026

Hey there ! Thanks for testing it out !

We plan to make delta the new default very soon, this issue/limitation with migra has been around for a long time. I think we can rely on pg-delta being pushed along as the new default.

@nirav-gondaliya
Copy link
Copy Markdown
Author

nirav-gondaliya commented Jun 5, 2026

Sounds good — closing this in favor of pg-delta then. Thanks @avallete for the quick turnaround let me know if I can help with specific issues.

Closes without merge — fix is already covered by pg-delta (catalog-level diff preserves view reloptions natively). Users on the current default can opt in via --use-pg-delta or [experimental.pgdelta] enabled = true in config.toml until it becomes the default.

@nirav-gondaliya nirav-gondaliya deleted the fix/view-reloptions-3973 branch June 5, 2026 11:09
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.

Declarative Database Schema - Views do NOT apply 'with(security_invoker=true)'

2 participants