mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix: move triggers requests to query route (#35667)
This commit is contained in:
1 parent
a57e30f863
commit
b7057fa9d8
9 files changed
+102
-126
No files matched your search
@@ -12,7 +12,7 @@ interface DeleteHookModalProps {
|
||||
}
|
||||
|
||||
const DeleteHookModal = ({ selectedHook, visible, onClose }: DeleteHookModalProps) => {
|
||||
const { id, name, schema } = selectedHook ?? {}
|
||||
const { name, schema } = selectedHook ?? {}
|
||||
|
||||
const { project } = useProjectContext()
|
||||
const { mutate: deleteDatabaseTrigger, isLoading: isDeleting } = useDatabaseTriggerDeleteMutation(
|
||||
@@ -28,12 +28,12 @@ const DeleteHookModal = ({ selectedHook, visible, onClose }: DeleteHookModalProp
|
||||
if (!project) {
|
||||
return console.error('Project ref is required')
|
||||
}
|
||||
if (!id) {
|
||||
if (!selectedHook) {
|
||||
return toast.error('Unable find selected hook')
|
||||
}
|
||||
|
||||
deleteDatabaseTrigger({
|
||||
id,
|
||||
trigger: selectedHook,
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
})
|
||||
|
||||
@@ -14,11 +14,12 @@ import {
|
||||
} from 'data/edge-functions/edge-functions-query'
|
||||
import { getTableEditor } from 'data/table-editor/table-editor-query'
|
||||
import { useTablesQuery } from 'data/tables/tables-query'
|
||||
import { isValidHttpUrl, tryParseJson, uuidv4 } from 'lib/helpers'
|
||||
import { isValidHttpUrl, uuidv4 } from 'lib/helpers'
|
||||
import { Button, Checkbox, Form, Input, Listbox, Radio, SidePanel } from 'ui'
|
||||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
import HTTPRequestFields from './HTTPRequestFields'
|
||||
import { AVAILABLE_WEBHOOK_TYPES, HOOK_EVENTS } from './Hooks.constants'
|
||||
import { PGTriggerCreate } from '@supabase/pg-meta/src/pg-meta-triggers'
|
||||
|
||||
export interface EditHookPanelProps {
|
||||
visible: boolean
|
||||
@@ -231,15 +232,13 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) =
|
||||
return value
|
||||
})
|
||||
|
||||
const payload: any = {
|
||||
const payload: PGTriggerCreate = {
|
||||
events,
|
||||
activation: 'AFTER',
|
||||
orientation: 'ROW',
|
||||
enabled_mode: 'ORIGIN',
|
||||
name: values.name,
|
||||
table: selectedTable.name,
|
||||
schema: selectedTable.schema,
|
||||
table_id: values.table_id,
|
||||
function_name: 'http_request',
|
||||
function_schema: 'supabase_functions',
|
||||
function_args: [
|
||||
@@ -262,7 +261,7 @@ const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) =
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
originalTrigger: selectedHook,
|
||||
updatedTrigger: payload,
|
||||
updatedTrigger: { ...payload, enabled_mode: 'ORIGIN' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,19 +13,19 @@ interface DeleteTriggerProps {
|
||||
|
||||
export const DeleteTrigger = ({ trigger, visible, setVisible }: DeleteTriggerProps) => {
|
||||
const { project } = useProjectContext()
|
||||
const { id, name, schema } = trigger ?? {}
|
||||
const { name, schema } = trigger ?? {}
|
||||
|
||||
const { mutate: deleteDatabaseTrigger, isLoading } = useDatabaseTriggerDeleteMutation()
|
||||
|
||||
async function handleDelete() {
|
||||
if (!project) return console.error('Project is required')
|
||||
if (!id) return console.error('Trigger ID is required')
|
||||
if (!trigger) return console.error('Trigger ID is required')
|
||||
|
||||
deleteDatabaseTrigger(
|
||||
{
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
id,
|
||||
trigger,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -50,9 +50,9 @@ const FormSchema = z.object({
|
||||
.regex(/^\S+$/, 'Name should not contain spaces or whitespaces'),
|
||||
schema: z.string(),
|
||||
table: z.string(),
|
||||
activation: z.string(),
|
||||
enabled_mode: z.string(),
|
||||
orientation: z.string(),
|
||||
activation: z.enum(['BEFORE', 'AFTER', 'INSTEAD OF']),
|
||||
enabled_mode: z.enum(['ORIGIN', 'REPLICA', 'ALWAYS', 'DISABLED']),
|
||||
orientation: z.enum(['ROW', 'STATEMENT']),
|
||||
function_name: z.string().min(1, 'Please select a database function for your trigger to call'),
|
||||
function_schema: z.string(),
|
||||
events: z.array(z.string()).min(1, 'Please select at least one event'),
|
||||
@@ -61,7 +61,7 @@ const FormSchema = z.object({
|
||||
tableId: z.string().optional(),
|
||||
})
|
||||
|
||||
const defaultValues = {
|
||||
const defaultValues: z.infer<typeof FormSchema> = {
|
||||
name: '',
|
||||
schema: '',
|
||||
table: '',
|
||||
@@ -133,7 +133,7 @@ export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetPro
|
||||
updateDatabaseTrigger({
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
id: selectedTrigger.id,
|
||||
originalTrigger: selectedTrigger,
|
||||
payload: { name: payload.name, enabled_mode: payload.enabled_mode },
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { handleError, post } from 'data/fetchers'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseTriggerKeys } from './keys'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { PGTriggerCreate } from '@supabase/pg-meta/src/pg-meta-triggers'
|
||||
|
||||
export type DatabaseTriggerCreateVariables = {
|
||||
projectRef: string
|
||||
connectionString?: string | null
|
||||
payload: any
|
||||
payload: PGTriggerCreate
|
||||
}
|
||||
|
||||
export async function createDatabaseTrigger({
|
||||
@@ -16,20 +17,16 @@ export async function createDatabaseTrigger({
|
||||
connectionString,
|
||||
payload,
|
||||
}: DatabaseTriggerCreateVariables) {
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
const { sql } = pgMeta.triggers.create(payload)
|
||||
|
||||
const { data, error } = await post('/platform/pg-meta/{ref}/triggers', {
|
||||
params: {
|
||||
header: { 'x-connection-encrypted': connectionString! },
|
||||
path: { ref: projectRef },
|
||||
},
|
||||
body: payload,
|
||||
headers,
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['trigger', 'create'],
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
return result
|
||||
}
|
||||
|
||||
type DatabaseTriggerCreateData = Awaited<ReturnType<typeof createDatabaseTrigger>>
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
import type { PostgresTrigger } from '@supabase/postgres-meta'
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { del, handleError } from 'data/fetchers'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseTriggerKeys } from './keys'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
|
||||
export type DatabaseTriggerDeleteVariables = {
|
||||
id: number
|
||||
trigger: {
|
||||
id: number
|
||||
name: string
|
||||
schema: string
|
||||
table: string
|
||||
}
|
||||
projectRef: string
|
||||
connectionString?: string | null
|
||||
}
|
||||
|
||||
type DeleteDatabaseTriggerResponse = PostgresTrigger & { error?: any }
|
||||
|
||||
export async function deleteDatabaseTrigger({
|
||||
id,
|
||||
trigger,
|
||||
projectRef,
|
||||
connectionString,
|
||||
}: DatabaseTriggerDeleteVariables) {
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
const { sql } = pgMeta.triggers.remove(trigger)
|
||||
|
||||
const { data, error } = await del('/platform/pg-meta/{ref}/triggers', {
|
||||
params: {
|
||||
header: { 'x-connection-encrypted': connectionString! },
|
||||
path: { ref: projectRef },
|
||||
query: { id },
|
||||
},
|
||||
headers,
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['trigger', 'delete', trigger.id],
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
return result
|
||||
}
|
||||
|
||||
type DatabaseTriggerDeleteData = Awaited<ReturnType<typeof deleteDatabaseTrigger>>
|
||||
|
||||
@@ -1,38 +1,39 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { handleError, patch } from 'data/fetchers'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseTriggerKeys } from './keys'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { PGTriggerUpdate } from '@supabase/pg-meta/src/pg-meta-triggers'
|
||||
|
||||
export type DatabaseTriggerUpdateVariables = {
|
||||
id: number
|
||||
originalTrigger: {
|
||||
id: number
|
||||
name: string
|
||||
schema: string
|
||||
table: string
|
||||
}
|
||||
projectRef: string
|
||||
connectionString?: string | null
|
||||
payload: any
|
||||
payload: PGTriggerUpdate
|
||||
}
|
||||
|
||||
export async function updateDatabaseTrigger({
|
||||
id,
|
||||
originalTrigger,
|
||||
projectRef,
|
||||
connectionString,
|
||||
payload,
|
||||
}: DatabaseTriggerUpdateVariables) {
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
const { sql } = pgMeta.triggers.update(originalTrigger, payload)
|
||||
|
||||
const { data, error } = await patch('/platform/pg-meta/{ref}/triggers', {
|
||||
params: {
|
||||
header: { 'x-connection-encrypted': connectionString! },
|
||||
path: { ref: projectRef },
|
||||
query: { id },
|
||||
},
|
||||
body: payload,
|
||||
headers,
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['trigger', 'update', originalTrigger.id],
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
return result
|
||||
}
|
||||
|
||||
type DatabaseTriggerUpdateData = Awaited<ReturnType<typeof updateDatabaseTrigger>>
|
||||
|
||||
@@ -5,6 +5,8 @@ import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { quoteLiteral } from 'lib/pg-format'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseTriggerKeys } from './keys'
|
||||
import { PostgresTrigger } from '@supabase/postgres-meta'
|
||||
import { PGTrigger, PGTriggerCreate } from '@supabase/pg-meta/src/pg-meta-triggers'
|
||||
|
||||
// [Joshen] Writing this query within FE as the PATCH endpoint from pg-meta only supports updating
|
||||
// trigger name and enabled mode. So we'll delete and create the trigger, within a single transaction
|
||||
@@ -13,8 +15,8 @@ import { databaseTriggerKeys } from './keys'
|
||||
export type DatabaseTriggerUpdateVariables = {
|
||||
projectRef: string
|
||||
connectionString?: string | null
|
||||
originalTrigger: any
|
||||
updatedTrigger: any
|
||||
originalTrigger: PostgresTrigger
|
||||
updatedTrigger: PGTriggerCreate & Pick<PGTrigger, 'enabled_mode'>
|
||||
}
|
||||
|
||||
export function getDatabaseTriggerUpdateSQL({
|
||||
@@ -40,7 +42,12 @@ export async function updateDatabaseTrigger({
|
||||
updatedTrigger,
|
||||
}: DatabaseTriggerUpdateVariables) {
|
||||
const sql = getDatabaseTriggerUpdateSQL({ originalTrigger, updatedTrigger })
|
||||
await executeSql({ projectRef, connectionString, sql })
|
||||
await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['trigger', 'update', originalTrigger.id],
|
||||
})
|
||||
return updatedTrigger
|
||||
}
|
||||
|
||||
|
||||
@@ -121,14 +121,14 @@ export function create({
|
||||
} {
|
||||
const qualifiedTableName = `${ident(schema)}.${ident(table)}`
|
||||
const qualifiedFunctionName = `${ident(function_schema)}.${ident(function_name)}`
|
||||
const triggerEvents = events.join(' OR ')
|
||||
const triggerOrientation = orientation ? `FOR EACH ${orientation}` : ''
|
||||
const triggerCondition = condition ? `WHEN (${condition})` : ''
|
||||
const triggerEvents = events.join(' or ')
|
||||
const triggerOrientation = orientation ? `for each ${orientation}` : ''
|
||||
const triggerCondition = condition ? `when (${condition})` : ''
|
||||
const functionArgsStr = function_args.map(literal).join(',')
|
||||
|
||||
const sql = `CREATE TRIGGER ${ident(
|
||||
const sql = `create trigger ${ident(
|
||||
name
|
||||
)} ${activation} ${triggerEvents} ON ${qualifiedTableName} ${triggerOrientation} ${triggerCondition} EXECUTE FUNCTION ${qualifiedFunctionName}(${functionArgsStr});`
|
||||
)} ${activation} ${triggerEvents} on ${qualifiedTableName} ${triggerOrientation} ${triggerCondition} execute function ${qualifiedFunctionName}(${functionArgsStr});`
|
||||
|
||||
return {
|
||||
sql,
|
||||
@@ -144,82 +144,55 @@ export const pgTriggerUpdateZod = z.object({
|
||||
export type PGTriggerUpdate = z.infer<typeof pgTriggerUpdateZod>
|
||||
|
||||
export function update(
|
||||
identifier: TriggerIdentifier,
|
||||
id: { name: string; schema: string; table: string },
|
||||
params: PGTriggerUpdate
|
||||
): {
|
||||
sql: string
|
||||
zod: z.ZodType<void>
|
||||
} {
|
||||
const whereIdentifierCondition = getIdentifierWhereClause(identifier)
|
||||
const qualifiedTableName = `${ident(id.schema)}.${ident(id.table)}`
|
||||
|
||||
const sql = `
|
||||
do $$
|
||||
declare
|
||||
old record;
|
||||
begin
|
||||
with triggers as (${TRIGGERS_SQL})
|
||||
select * into old from triggers where ${whereIdentifierCondition};
|
||||
|
||||
if old is null then
|
||||
raise exception 'Cannot find trigger: %', ${literal(whereIdentifierCondition)};
|
||||
end if;
|
||||
let enabledModeSql = ''
|
||||
|
||||
${
|
||||
params.enabled_mode
|
||||
? `
|
||||
execute(format('alter table %I.%I ${
|
||||
params.enabled_mode === 'DISABLED'
|
||||
? 'DISABLE'
|
||||
: 'ENABLE' +
|
||||
(params.enabled_mode === 'ALWAYS' || params.enabled_mode === 'REPLICA'
|
||||
? ' ' + params.enabled_mode
|
||||
: '')
|
||||
} TRIGGER %I',
|
||||
old.schema, old.table, old.name));`
|
||||
: ''
|
||||
switch (params.enabled_mode) {
|
||||
case 'ORIGIN':
|
||||
enabledModeSql = `alter table ${qualifiedTableName} enable trigger ${ident(id.name)};`
|
||||
break
|
||||
case 'DISABLED':
|
||||
enabledModeSql = `alter table ${qualifiedTableName} disable trigger ${ident(id.name)};`
|
||||
break
|
||||
case 'REPLICA':
|
||||
case 'ALWAYS':
|
||||
enabledModeSql = `alter table ${qualifiedTableName} enable ${params.enabled_mode} trigger ${ident(id.name)};`
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
${
|
||||
params.name
|
||||
? `
|
||||
-- Using the same name in the rename clause gives an error, so only do it if the new name is different.
|
||||
if ${literal(params.name)} != old.name then
|
||||
execute(format('alter trigger %I on %I.%I rename to %I;', old.name, old.schema, old.table, ${literal(params.name)}));
|
||||
end if;`
|
||||
const updateNameSql =
|
||||
params.name && params.name !== id.name
|
||||
? `alter trigger ${ident(id.name)} on ${qualifiedTableName} rename to ${ident(params.name)};`
|
||||
: ''
|
||||
}
|
||||
end
|
||||
$$;`
|
||||
|
||||
// updateNameSql must be last
|
||||
const sql = `begin; ${enabledModeSql}; ${updateNameSql}; commit;`
|
||||
|
||||
return {
|
||||
sql,
|
||||
zod: z.void(),
|
||||
}
|
||||
}
|
||||
|
||||
export function remove(
|
||||
identifier: TriggerIdentifier,
|
||||
id: { name: string; schema: string; table: string },
|
||||
{ cascade = false } = {}
|
||||
): {
|
||||
sql: string
|
||||
zod: z.ZodType<void>
|
||||
} {
|
||||
const whereIdentifierCondition = getIdentifierWhereClause(identifier)
|
||||
const qualifiedTableName = `${ident(id.schema)}.${ident(id.table)}`
|
||||
|
||||
const sql = `
|
||||
do $$
|
||||
declare
|
||||
old record;
|
||||
begin
|
||||
with triggers as (${TRIGGERS_SQL})
|
||||
select * into old from triggers where ${whereIdentifierCondition};
|
||||
|
||||
if old is null then
|
||||
raise exception 'Cannot find trigger';
|
||||
end if;
|
||||
|
||||
execute(format('DROP TRIGGER %I ON %I.%I ${cascade ? 'CASCADE' : ''}',
|
||||
old.name, old.schema, old.table));
|
||||
end
|
||||
$$;`
|
||||
const sql = `drop trigger ${ident(id.name)} on ${qualifiedTableName} ${cascade ? 'cascade' : ''};`
|
||||
|
||||
return {
|
||||
sql,
|
||||
|
||||
Reference in new issue
Block a user