mirror of
https://github.com/supabase/supabase.git
synced 2026-09-08 19:08:44 +08:00
## 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 -->
95 lines
2.6 KiB
TypeScript
95 lines
2.6 KiB
TypeScript
import { untrustedSql, type UntrustedSqlFragment } from '@supabase/pg-meta'
|
|
import { useMutation } from '@tanstack/react-query'
|
|
import { toast } from 'sonner'
|
|
|
|
import { fetchPost } from '@/data/fetchers'
|
|
import { BASE_PATH } from '@/lib/constants'
|
|
import { ResponseError, UseCustomMutationOptions } from '@/types'
|
|
|
|
export type SqlPolicyGenerateVariables = {
|
|
tableName: string
|
|
schema?: string
|
|
columns?: string[]
|
|
projectRef: string
|
|
connectionString: string
|
|
orgSlug?: string
|
|
message?: string
|
|
}
|
|
|
|
type RawSqlPolicy = {
|
|
name: string
|
|
table: string
|
|
schema: string
|
|
action: 'PERMISSIVE' | 'RESTRICTIVE'
|
|
roles: string[]
|
|
command?: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'ALL'
|
|
definition?: string
|
|
check?: string
|
|
sql: string
|
|
}
|
|
|
|
/**
|
|
* AI-generated policy. `definition` and `check` are branded `UntrustedSqlFragment` at the
|
|
* boundary — they originate from third-party AI output and must be promoted via
|
|
* `acceptUntrustedSql` at a user gesture before executing.
|
|
*/
|
|
export type SqlPolicyGenerateResponse = Array<
|
|
Omit<RawSqlPolicy, 'definition' | 'check'> & {
|
|
definition?: UntrustedSqlFragment
|
|
check?: UntrustedSqlFragment
|
|
}
|
|
>
|
|
|
|
export async function generateSqlPolicy({
|
|
tableName,
|
|
schema,
|
|
columns,
|
|
projectRef,
|
|
connectionString,
|
|
orgSlug,
|
|
message,
|
|
}: SqlPolicyGenerateVariables): Promise<SqlPolicyGenerateResponse> {
|
|
const result = await fetchPost<Array<RawSqlPolicy>>(`${BASE_PATH}/api/ai/sql/policy`, {
|
|
tableName,
|
|
schema,
|
|
columns,
|
|
projectRef,
|
|
connectionString,
|
|
orgSlug,
|
|
message,
|
|
})
|
|
|
|
if ('error' in result) throw new ResponseError((result.error as any).message, 400)
|
|
return (result as Array<RawSqlPolicy>).map((policy) => ({
|
|
...policy,
|
|
definition: policy.definition === undefined ? undefined : untrustedSql(policy.definition),
|
|
check: policy.check === undefined ? undefined : untrustedSql(policy.check),
|
|
}))
|
|
}
|
|
|
|
type SqlPolicyGenerateData = Awaited<ReturnType<typeof generateSqlPolicy>>
|
|
|
|
export const useSqlPolicyGenerateMutation = ({
|
|
onSuccess,
|
|
onError,
|
|
...options
|
|
}: Omit<
|
|
UseCustomMutationOptions<SqlPolicyGenerateData, ResponseError, SqlPolicyGenerateVariables>,
|
|
'mutationFn'
|
|
> = {}) => {
|
|
return useMutation<SqlPolicyGenerateResponse, ResponseError, SqlPolicyGenerateVariables>({
|
|
mutationFn: (vars) => generateSqlPolicy(vars),
|
|
async onSuccess(data, variables, context) {
|
|
await onSuccess?.(data, variables, context)
|
|
},
|
|
async onError(data, variables, context) {
|
|
if (onError === undefined) {
|
|
toast.error(`Failed to generate policy: ${data.message}`)
|
|
} else {
|
|
onError(data, variables, context)
|
|
}
|
|
},
|
|
...options,
|
|
})
|
|
}
|