## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Bug fix (sanitization hardening).
## What is the current behavior?
`pg-format`'s `keyword()` helper validates with `/^[A-Za-z][A-Za-z0-9_
]*$/`, which allows any space-separated word sequence. Phrases like
`'DROP TABLE'` or `'DELETE FROM users'` pass validation and can be
interpolated into queries.
## What is the new behavior?
`keyword()` accepts either a single word matching
`[A-Za-z][A-Za-z0-9_]*` (no spaces) or a phrase from a small
case-insensitive allow-list (`'INSTEAD OF'`, `'BY DEFAULT'`) — which
covers every multi-word value real callers actually produce (trigger
activation, identity generation). Arbitrary multi-word phrases now
throw. Tests updated accordingly.
## Additional context
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Reinforced SQL keyword validation with stricter security controls to
prevent unsafe keyword usage. The system now only permits single-word
identifiers or specific pre-approved multi-word keyword phrases.
Previously accepted multi-word inputs that don't match the curated
allowlist are now properly rejected with improved error messaging.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46076?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Third PR in the SafeSql migration stack. Flips the input/output types on
`pgMeta.functions/policies/triggers`'s `.create/.update/.remove` to use
`SafeSqlFragment`, and updates every Studio consumer atomically.
### pg-meta
- `pgMeta.functions/policies/triggers` `.create/.update/.remove` now
return `{ sql: SafeSqlFragment }` and accept branded input parameters
(`PGFunctionCreate`, `PGSavedFunction`, `PolicyCreate/UpdateParams`,
`PGTriggerCreate` with branded condition).
- `QueryModifier.toSql()` returns `SafeSqlFragment`.
### Studio consumers updated to the new branded API
- `data/database-functions/*` (query, create/update/delete mutations)
- `data/database-policies/*` (create, update mutations)
- `data/database-triggers/database-trigger-update-transaction-mutation`
- `components/Database/Triggers/TriggerSheet`
- `components/Database/Functions/CreateFunction`
- `components/Auth/Policies/PolicyEditorPanel`
These consumers land atomically with the pg-meta API change because the
input-type strictness flip (string → `SafeSqlFragment` for SQL fields)
forces every call site to update together.
## Stack
- 1/7: #45897 (merged)
- 2/7: #45903 (merged)
- 3/7: this PR
- 4/7–7/7: upcoming
## Test plan
- [x] `pnpm typecheck` passes
- [x] `pnpm --filter @supabase/pg-meta test` passes
- [x] Dev-server smoke test: function editor, policy editor, trigger
sheet
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Strengthened SQL safety across policy, function, and trigger workflows
by converting raw SQL strings to typed SQL fragments and safer
composition
* Updated editor behavior to handle policy conditions/checks as typed
SQL fragments with improved initialization and template handling
* Aligned query and modifier interfaces to return typed SQL fragments
for safer composition
* **Tests**
* Updated tests to use typed SQL fragments and synchronous builders
where applicable
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45990)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Refactor + bug fixes (part of the SafeSql migration stack — PR 2 of 7,
stacks on top of #45897).
## What is the current behavior?
- `pgMeta.columns.create` and the table-editor SQL builder take column
type as a string with array suffix and schema baked in (e.g.
`'private.test_enum'`, `'int4[]'`).
- The studio table-editor SQL emits the legacy schema-embedded `format`
string for enums in non-public schemas, while the pg-meta columns SQL
already returns the new shape (bare `format` + separate
`format_schema`). The two queries disagree on how to represent the same
column, surfacing as a false-positive type mismatch in the FK selector
when both ends are an enum from a non-public schema.
- The FK selector compares column types by `format` alone — same-named
enums in different schemas appear equal, and arrays vs. scalars of the
same base type pass the family check.
- `displayColumnType` renders arrays as the raw `_typname` pg-meta emits
(e.g. `_int4` instead of `int4[]`).
## What is the new behavior?
**pg-meta**
- Introduce `ColumnTypeRef` (`{ schema?, name, isArray? }`) for column
type input, replacing the legacy string-with-array-suffix format.
`pgMeta.columns.create` and the table-editor SQL builder consume the new
shape.
- Add `format_schema` to the column zod schema; pg-meta SQL emits the
type's schema for the table editor's ColumnType dropdown.
- `pgMeta.columns.create` returns a `SafeSqlFragment`.
- Studio table-editor SQL now emits bare `format` + `format_schema`,
matching pg-meta's columns SQL.
**Studio**
- `SafePostgresColumn`/`SafePostgresTable` extend the new `PG*` types
(master dropped postgres-meta).
- Pipe `ColumnTypeRef` through `SidePanelEditor` → `ColumnEditor` →
`TableEditor`, along with the column-create mutation, table
retrieve/list queries, and the `TableList`/`ColumnList` surfaces.
- `displayColumnType` helper renders arrays as `type[]` (or
`schema.type[]`) and handles non-implicit schemas.
- FK selector now carries `sourceIsArray`/`targetIsArray` and compares
the full `(format, format_schema, isArray)` triple. Family checks for
numeric/text/uuid skip when either side is an array (FKs across array
boundaries are never compatible).
- Type-mismatch and type-notice alerts pass `isArray` to the display
helper.
- Bundle `Policies.utils` + `Policies.types` + `sql-policy-mutation`,
`PolicyEditorModal`, and `SchemaGraph` here because `SidePanelEditor`
consumes `acceptGeneratedPolicy`/`AcceptedGeneratedPolicy` — splitting
requires temporary overloads with no architectural payoff.
## Additional context
Part of the SafeSql migration stack. Stacks on top of #45897.
### Manual test checklist
Surfaces touched by this PR — please exercise each:
**Table editor**
- [x] Create a new table with a mix of column types (scalar, array,
enum, foreign key)
- [x] Add a column to an existing table; verify the type dropdown lists
scalars + arrays separately and shows schema-qualified names for
non-public enums
- [x] Edit an existing column's type (scalar ↔ array, switch between
enums in different schemas) and save
- [x] Verify enum types from a non-public schema (e.g.
`private.my_enum`) display as `private.my_enum` in the column list
**Foreign key selector**
- [x] Open the FK selector for a column and pick a target column with a
matching type — no mismatch warning
- [x] Pick a target column whose type differs only by schema (two
same-named enums in different schemas) — should show a type-mismatch
alert
- [x] Pick a target column where one side is an array and the other is a
scalar of the same base type — should show a type-mismatch alert (no
auto-cast across array boundary)
- [x] When FK target sets the column type, verify `format_schema` and
`isArray` are preserved on the source column
- [x] Type-mismatch and type-notice alert messages render array types as
`type[]` (not `_type`)
**Column list / table list**
- [x] Schema-qualified type names display correctly for columns whose
type lives in a non-public schema
- [x] Array columns display as `type[]` (or `schema.type[]`)
**Policies (bundled due to import dependency)**
- [x] Open the Policies page; create/edit/delete a row-level policy via
the modal
- [x] Generate a policy via the AI assistant and accept it through
`SidePanelEditor` — verify the accepted policy lands in the editor
correctly
**Schema visualizer**
- [x] Open the Schemas → Schema Visualizer page; verify it renders
without type errors and shows tables/relationships
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Improvements**
* Support for column types in non-public schemas and richer column type
presentation (includes schema and array info).
* Stronger SQL safety around policies and constraints; draft policy SQL
is now promoted explicitly on save.
* Improved foreign-key type validation and compatibility checks using
enhanced type metadata.
* **Tests**
* Updated snapshots and tests to reflect new column metadata and SQL
fragment handling.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45903)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Mark provenance of SQL via the branded types SafeSqlFragment and
UntrustedSqlFragment. Only SafeSqlFragment should be executed;
UntrustedSqlFragments require some kind of implicit user approval (show
on screen + user has to click something) before they are promoted to
SafeSqlFragment.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Editor and RLS tester show loading states for inferred/generated SQL
and include a dedicated user SQL editor for safer edits.
* **Refactor**
* Platform-wide SQL handling tightened: snippets and AI-generated SQL
are treated as untrusted/display-only until promoted, improving safety
and consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## I have read the CONTRIBUTING.md file.
YES
## What kind of change does this PR introduce?
Bug fix
## What is the current behavior?
The query in `sql/indexes.ts` joins `pg_class` with `pg_indexes` using
only the index name:
```sql
JOIN pg_indexes ix ON c.relname = ix.indexname
```
This can lead to incorrect results when multiple schemas contain indexes
with the same name. PostgreSQL allows identical index names across
different schemas, so this join may return the wrong index_definition.
## What is the new behavior?
The join condition now includes the schema name:
```sql
JOIN pg_indexes ix
ON c.relname = ix.indexname
AND n.nspname = ix.schemaname
```
This ensures the correct index_definition is retrieved for each index,
even when duplicate index names exist across schemas.
## Additional context
- Added a test case to verify behavior when the same index name exists
in multiple schemas.
- This change prevents cross-schema collisions and ensures accurate
index metadata retrieval.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved index listing so indexes that share the same name in
different schemas are correctly distinguished and reported.
* **Tests**
* Added a regression test ensuring indexes with identical names in
separate schemas are both detected and contain expected schema-qualified
references.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Charis Lam <26616127+charislam@users.noreply.github.com>
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Refactor of the pg-meta test setup scripts.
## What is the current behavior?
The test command runs multiple sequential npm-run-s scripts (`db:clean`,
`db:run`, `test:run`, `db:clean`) with a hardcoded port 5432, causing
container name and port collisions when running tests across multiple
git worktrees in parallel.
## What is the new behavior?
A single `test/run-tests.sh` wrapper script handles the full test
lifecycle: it finds an available port dynamically (scanning 5432–5531),
sets a unique Docker Compose project name based on a hash of the package
directory path, starts the DB, runs the test command, and tears down on
exit. This allows pg-meta tests to run in parallel across multiple
worktrees without conflicts.
## Additional context
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Streamlined test execution to centralize setup and teardown for
reliable runs.
* Made the test database port configurable with automatic local port
discovery and fail-fast behavior.
* Created isolated test environments per workspace to avoid
container/name collisions and improve cleanup.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## I have read the CONTRIBUTING.md file.
YES
## What kind of change does this PR introduce?
Bug fix
## What is the current behavior?
When updating a table with `replica_identity = 'INDEX'`, it is possible
to omit the `replica_identity_index`.
This results in invalid SQL being generated:
`REPLICA IDENTITY USING INDEX`
PostgreSQL requires an index name for this operation.
No existing issue found. This PR fixes a validation gap discovered
during development.
## What is the new behavior?
- Throws an error if `replica_identity` is set to `INDEX` without
providing `replica_identity_index`
- Ensures generated SQL is always valid
## Additional context
- Added validation in `pg-meta-tables.ts`
- Added test case to verify an error is thrown when the index name is
missing
This aligns pg-meta behavior with PostgreSQL requirements for `REPLICA
IDENTITY USING INDEX`.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **Bug Fixes**
* Enhanced validation for database replica identity configuration to
prevent incomplete or invalid configurations.
* **Tests**
* Added test coverage for replica identity validation scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Improved SQL construction across the studio to make queries safer and
more consistent.
* Safer parameter handling for optional schema and remediation links to
prevent injection risks.
* Deterministic query header formatting and stable date/comments in
generated SQL.
* More robust user-count and paginated-user queries for accurate counts,
sorting and pagination.
* Updated tests to align with the new safe query handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* **New Features**
* Added new SQL formatting utilities including keyword validation and
safe SQL composition functions
* Introduced type-safe SQL fragment handling with branded types to
prevent SQL injection vulnerabilities
* Expanded available exports for improved code organization and
accessibility
* Enhanced SQL query building capabilities with improved validation and
composition support
## Context
Shifts all remaining dashboard queries into pg-meta so that we
centralize all manually written queries in one place
Having them in packages/pg-meta also allows us to write tests for them
## To test
Just needs a smoke test on
- Role Impersonation
- Lints
- Data API
- Database
- Enumerated Types
- Integrations
- Foreign Data Wrappers
- Vault
## Context
We reached to a consensus that we'll keep manually written queries
within the `pg-meta` package
I'll shift queries over in several PRs but wanted to double check if the
changes here is a good folder structure
## Changes involved
- Am organizing dashboard queries in `pg-meta` based on product
- So folder structure will be `/sql/studio/[product]/*.ts`
- Every constant will be exported directly from `pg-meta`'s index
- So consumption in dashboard will just be `import { xxx } from
'@supabase/pg-meta`
cc @avallete and @soedirgo on this one - lemme know if this is alright,
or if there's a preferred way to do this
Sorted all imports in all packages, `cms`, `design-system` and
`ui-library` apps by running `pnpm format` on them.
All changes in this PR are done by the script.
* add initial installation flow of stripe sync engine
* update docs link
* Add supabase_vault extension dep
* Add stripe logo to sync engine integration
* Move overview content to bottom of integration pages
* Add sync state to stripe sync page
* only check sync state if stripe integration is installed
* Use proper stripe-sync package and setup flows
* Improve sync engine installation ux
* Remove unused hardcoded dep
* Add alpha status to stripe sync engine integration
* fix typo
* run format
* fix types
* Rename the stripe-sync path to remove the 'integration". The path needs to have BASE_PATH to work on prod.
* Design tidy up (#41337)
UI tidy up
* update to latest sync engine package
* Add stripe key verification
* Remove noop try/catch
* Add integration isntallation telelemtry
* Add basic settings page
* Address coderabbit comments
* remove unused dep
* Remove state setting on render
* s/description/comment
* Cleanup settings screen UI
* Improve settings screen design
* update schema test snapshot
* Use latest stripe-sync-engine package
* Update repo url to new official location
* revert marketing change
* Update stripe sync engine package
* Add link to table from overview page
* Add feature flag and improve telemetry
* Fix missing useMemo dep
* add uninstall telemetry note
---------
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
Co-authored-by: Saxon Fletcher <saxonafletcher@gmail.com>
There is an edge case interaction between the Postgres query parser and
MSSQL foreign tables, where the query parser may drop sort clauses that
are redundant with applied filters. This leads to invalid MSSQL syntax,
because the resulting query has a `limit` but no `sort`, and the user
sees a confusing error message.
This PR detects this edge case on MSSQL foreign tables. There are three
cases:
1. The user filters by a column, but there are still other columns
available for sorting. The default search for a sorting column will
leave out the filtered column.
2. The user filters by a column/several columns, and there are no more
columns that can be used for sorting. We stop the query and show an
admonition.
3. The user filters by a column, then tries to sort by the same column.
We stop the query and show an admonition.
* Updated to use optimized search columns when getting user counts
* added unit tests for query builders
* minor
* Nit fix keywords URL param not getting loaded into search input field
* removed user footer
* updated tests for upstream
* updated integration tests
* updated tests to be int for paginated
* updated schema
* updated types
* updated type imports
* updated type imports
* Apply suggestion from @avallete
Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
* updated literal import
* refactor: use common escaping (#40186)
refactor: use utils for sql escaping
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
* feat(pgmeta): filter by composite values
Current behavior:
Only single columns can be used as filters: `where version = 2`
New behavior:
Composite values can be used as filters: `where (id, version) = (1, 2)`
Motivation:
In order to correct an existing bug when fetching table rows, we need to
use cursor pagination on the primary key, but in order to handle
composite primary keys, we need to be able to filter by composite
values.
* refactor(pgmeta): allowlist in-tuple operators
* Table editor cast column type to text if filtering with like based filter
* Update pg-meta tests
* Fix test cases for ~~ in advanced-query.test
* Fix tests in table-row-query.test
* Clean up usage of newOrgAiOptIn and useBedrockAssistant feature flags
* Remove all OpenAI endpoints
* Fix for self-hosted
* Default isLimited to false
* Update PG meta tests
* Fix unit tests for model
* Revert pg meta tests
* Fix test
---------
Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
* fix: move table create update delete to query route
* chore: implement query to fetch a single table
* fix: retrieve table after update
* chore: assign type to update table payload
* chore: use updated table columns for edit
* chore: make executeSql castable with generic (#35685)
* Chore/refactor derivate more types from queries (#35687)
* chore: make executeSql castable with generic
* chore: derivate types from performed queries
- It allows to decouple more the frontend logic and the pg-meta/sql-query logic allowing to reduce the number of cast
and get closer types between what we do fetch and what we expect in our components
* fix: remove existing check
* chore: handle null comment and check
* fix: format check name as identifier
---------
Co-authored-by: avallete <andrew.valleteau@supabase.io>
Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
* chore(studio): move Query to pgMeta add tests
- Move the Query builder from studio to pgMeta
- Add e2e tests over the generated sql to ensure syntax and runtime
result over pg database
- fix bug with orde by for table with undefined column
* chore: add table-row-query to pgMeta and tests
* chore: fix query import path
* chore: reduce maxArraySize
* chore: use pg-meta getTableRowsSql implementation in studio
* chore: add truncation on large array fields
* chore: set ES target for lint
* chore: update comment
* chore: reduce test size for CI
* chore(studio): move Query to pgMeta add tests
- Move the Query builder from studio to pgMeta
- Add e2e tests over the generated sql to ensure syntax and runtime
result over pg database
- fix bug with orde by for table with undefined column
* chore: fix query import path
* chore: set ES target for lint
* chore: add github action for pg-meta test package
* chore: add tsconfig to sparse checkout
* chore(pg-meta): port functions tests
* chore: add new test for CURRENT arg creation
* chore: revert query style change
* chore: restore PGFunction type inference
* wip: remove type cast int8 on arg_types
* Revert "wip: remove type cast int8 on arg_types"
This reverts commit 6878a93bfb.
* wip: make tests work with both clients
* chore: use pg and type parser
* chore: use only pg
* chore: migrate table-privileges tests