Files
supabase/packages
Ping-Min LinandJoshen Lim 700365ffba fix(pg-meta): generate privilege, publication, role and schema SQL that servers can statically check (#51457)
## What kind of change does this PR introduce?

Bug fix.

## What is the current behavior?

On a project whose database server statically checks `DO` blocks before
running them (Multigres does), several Studio actions fail:

- Creating a table shows "Failed to update API access privileges for new
table", although the table is created.
- Toggling Realtime for a table fails with `EXECUTE of a runtime-built
statement inside a PL/pgSQL body is not supported`.
- The same error blocks editing a role or schema (rename, owner, drop)
and changing column privileges.

All of these come from SQL that `@supabase/pg-meta` generates. Such a
server only accepts a dynamic `EXECUTE` when it can prove the statement
is fixed, and two shapes in our SQL prevent that:

- parentheses around the payload: `execute(format(...))`
- `%s` conversions in `format()` strings, which substitute raw text (for
example `%s` fed by `<oid>::regclass`, by a `string_agg(...)` subselect,
or by a variable holding a table list)

## What is the new behavior?

`tablePrivileges.grant/revoke`, `columnPrivileges.grant/revoke`,
`publications.update`, `roles.update/remove` and `schemas.update/remove`
generate SQL with no parentheses around the payload and only `%I`/`%L`
conversions:

- Table and column privileges name the table with `%I.%I`, taking the
schema and name from `pg_class`/`pg_namespace`.
- The publication update loops over the tables to drop and to add, with
the same `tables` semantics as before (`undefined` keeps the tables,
`null` publishes all tables, a list replaces them, bare names resolve
through the search path).
- `schemas.remove` picks between `drop schema … cascade` and `…
restrict` with two plain statements instead of a `%s` fed by a `CASE`.
- The grant and revoke format strings are built with `literal()`, so a
quote in a role name can't end the string early.

The statements are equivalent on regular Postgres.

## Additional context

- New tests: replacing, clearing and keeping a publication's tables,
switching between a table list and all tables, and a test (no database
needed) that fails if the generated SQL of any of these builders goes
back to `execute(...)` or `%s`.
- I ran the pg-meta tests against a local Postgres 17. A set of existing
snapshot tests were recorded on Postgres 14 and fail there with and
without this change (for example the new `MAINTAIN` privilege in table
privileges), so CI is the real check for those. The tests covering the
changed SQL pass.
- Some other Studio SQL is blocked on such servers for different reasons
(for example creating foreign servers, and `SET` inside a `DO` block).
It isn't addressed here.

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-10-13 09:14:50 -07:00
..