Files
supabase/apps/studio/data/database/keys.ts
Vaibhav fdf8732727 fix: Include security_invoker in definition (#44936)
## TL;DR

the table editor definition panel was showing incomplete SQL for views
with `WITH (security_invoker = true)`
ignoring the reloption and making it easy to accidentally strip it when
recreating the view

## prob

When viewing a security invoker view in the Table Editor, the Definition
panel only showed `CREATE VIEW ... AS ...`
 without the `WITH (security_invoker = true)` clause

which caused two issues:

1. the displayed SQL was incomplete and didn't match the actual view
definition
2. users copying the SQL to recreate the view would unintentionally lose
the security_invoker setting


## ex:

| Before | After |
|--------|-------|
| `create view public.exposed_api as`<br>`select id, secret from
public.rls_protected_table;` | `create view public.exposed_api with
(security_invoker = true) as`<br>`select id, secret from
public.rls_protected_table;` |

## ref:
- closes https://github.com/supabase/supabase/issues/44934

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* View definitions now show the full CREATE statement (including
materialized views and WITH (...) options) and preserve security options
like security_invoker when viewed or opened in the SQL editor.

* **Tests**
* Added end-to-end test verifying security option preservation in view
definitions and when opening them in the SQL editor.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-16 07:03:54 -06:00

74 lines
3.5 KiB
TypeScript

import { sqlKeys } from '@/data/sql/keys'
export const databaseKeys = {
schemas: (projectRef: string | undefined) => ['projects', projectRef, 'schemas'] as const,
keywords: (projectRef: string | undefined) => ['projects', projectRef, 'keywords'] as const,
migrations: (projectRef: string | undefined) => ['projects', projectRef, 'migrations'] as const,
tableColumns: (
projectRef: string | undefined,
schema: string | undefined,
table: string | undefined
) => ['projects', projectRef, 'table-columns', schema, table] as const,
databaseFunctions: (projectRef: string | undefined) =>
['projects', projectRef, 'database-functions'] as const,
entityDefinition: (projectRef: string | undefined, id?: number) =>
['projects', projectRef, 'entity-definition', id] as const,
entityDefinitions: (projectRef: string | undefined, schemas: string[]) =>
['projects', projectRef, 'entity-definitions', schemas] as const,
tableDefinition: (projectRef: string | undefined, id?: number) =>
['projects', projectRef, 'table-definition', id] as const,
viewDefinition: (projectRef: string | undefined, id?: number, includeCreateStatement?: boolean) =>
['projects', projectRef, 'view-definition', id, includeCreateStatement ?? false] as const,
backups: (projectRef: string | undefined) =>
['projects', projectRef, 'database', 'backups'] as const,
poolingConfiguration: (projectRef: string | undefined) =>
['projects', projectRef, 'database', 'pooling-configuration'] as const,
indexesFromQuery: (projectRef: string | undefined, query: string) =>
['projects', projectRef, 'indexes', { query }] as const,
indexAdvisorFromQuery: (
projectRef: string | undefined,
query: string,
connectionString?: string
) => {
// Use only the host (no credentials) as a safe cache discriminator
let connectionFingerprint: string | undefined
if (connectionString) {
try {
connectionFingerprint = new URL(connectionString).host
} catch {
connectionFingerprint = undefined
}
}
return ['projects', projectRef, 'index-advisor', { query, connectionFingerprint }] as const
},
tableConstraints: (projectRef: string | undefined, id?: number) =>
['projects', projectRef, 'table-constraints', id] as const,
foreignKeyConstraints: (projectRef: string | undefined, schema?: string, options = {}) =>
['projects', projectRef, 'foreign-key-constraints', schema, options] as const,
databaseSize: (projectRef: string | undefined) =>
['projects', projectRef, 'database-size'] as const,
maxConnections: (projectRef: string | undefined) =>
['projects', projectRef, 'max-connections'] as const,
pgbouncerStatus: (projectRef: string | undefined) =>
['projects', projectRef, 'pgbouncer', 'status'] as const,
pgbouncerConfig: (projectRef: string | undefined) =>
['projects', projectRef, 'pgbouncer', 'config'] as const,
checkPrimaryKeysExists: (
projectRef: string | undefined,
tables: { name: string; schema: string }[]
) => ['projects', projectRef, 'check-primary-keys', tables] as const,
tableIndexAdvisor: (
projectRef: string | undefined,
schema: string | undefined,
table: string | undefined
) => ['projects', projectRef, 'table-index-advisor', schema, table] as const,
supamonitorEnabled: (projectRef: string | undefined) =>
['projects', projectRef, 'supamonitor-enabled'] as const,
}
export const getLiveTupleEstimateKey = (
projectRef: string | undefined,
table: string,
schema = 'public'
) => sqlKeys.query(projectRef, ['live-tuple-estimate', schema, table])