From 67deabf67e201bb82f9c6a3866beab9fed013a30 Mon Sep 17 00:00:00 2001 From: Han Qiao Date: Mon, 27 Apr 2026 19:45:26 +0800 Subject: [PATCH] fix: create etl publication as postgres (#45043) ## What kind of change does this PR introduce? Bug fix ## What is the current behavior? Creating a schema only branch fails because ETL publication is owned by `supabase_etl_admin` which users have no access. ## What is the new behavior? Since ETL supports user managed publications, create them through pgmeta so it's owned by `postgres` role instead. ## Additional context mirrors [upstream etl](https://github.com/supabase/etl/blob/main/etl-api/src/db/publications.rs#L22-L51) implementation ## Summary by CodeRabbit * **Bug Fixes** * Added guards to prevent creating publications when project or connection info is missing, with clearer error logging. * Ensure the project connection string is explicitly passed so publications target the correct database. * **Refactor** * Publication creation now executes generated SQL directly against the database, with correct handling of empty or selected table lists and proper identifier quoting for reliability. --------- Co-authored-by: Joshen Lim Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CodeRabbit --- .../DestinationForm/NewPublicationPanel.tsx | 4 ++++ .../publication-create-mutation.ts | 21 +++++++------------ .../pg-meta/src/sql/studio/database/index.ts | 1 + .../src/sql/studio/database/publications.ts | 16 ++++++++++++++ 4 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 packages/pg-meta/src/sql/studio/database/publications.ts diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/NewPublicationPanel.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/NewPublicationPanel.tsx index 1e45e63639c..378c0524f61 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/NewPublicationPanel.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/NewPublicationPanel.tsx @@ -22,6 +22,7 @@ import { z } from 'zod' import { useCreatePublicationMutation } from '@/data/replication/publication-create-mutation' import { useReplicationTablesQuery } from '@/data/replication/tables-query' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' interface NewPublicationPanelProps { visible: boolean @@ -31,6 +32,7 @@ interface NewPublicationPanelProps { export const NewPublicationPanel = ({ visible, sourceId, onClose }: NewPublicationPanelProps) => { const { ref: projectRef } = useParams() + const { data: project } = useSelectedProjectQuery() const { mutateAsync: createPublication, isPending: creatingPublication } = useCreatePublicationMutation() const { data: tables } = useReplicationTablesQuery({ @@ -55,6 +57,7 @@ export const NewPublicationPanel = ({ visible, sourceId, onClose }: NewPublicati const onSubmit = async (data: z.infer) => { if (!projectRef) return console.error('Project ref is required') + if (!project) return console.error('Project is required') if (!sourceId) return console.error('Source id is required') try { await createPublication({ @@ -65,6 +68,7 @@ export const NewPublicationPanel = ({ visible, sourceId, onClose }: NewPublicati const [schema, name] = table.split('.') return { schema, name } }), + connectionString: project.connectionString, }) toast.success('Successfully created publication') onClose() diff --git a/apps/studio/data/replication/publication-create-mutation.ts b/apps/studio/data/replication/publication-create-mutation.ts index 9b7745cf43f..3601ebc4d74 100644 --- a/apps/studio/data/replication/publication-create-mutation.ts +++ b/apps/studio/data/replication/publication-create-mutation.ts @@ -1,8 +1,9 @@ +import { getCreatePublicationSQL } from '@supabase/pg-meta' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' +import { executeSql } from '../sql/execute-sql-query' import { replicationKeys } from './keys' -import { handleError, post } from '@/data/fetchers' import type { ResponseError, UseCustomMutationOptions } from '@/types' export type CreatePublicationParams = { @@ -10,27 +11,19 @@ export type CreatePublicationParams = { sourceId: number name: string tables: { schema: string; name: string }[] + connectionString?: string | null } async function createPublication( - { projectRef, sourceId, name, tables }: CreatePublicationParams, + { projectRef, connectionString, name, tables }: CreatePublicationParams, signal?: AbortSignal ) { if (!projectRef) throw new Error('projectRef is required') - const { data, error } = await post( - '/platform/replication/{ref}/sources/{source_id}/publications', - { - params: { path: { ref: projectRef, source_id: sourceId } }, - body: { name, tables }, - signal, - } - ) - if (error) { - handleError(error) - } + const sql = getCreatePublicationSQL({ name, tables }) + const { result } = await executeSql({ projectRef, connectionString, sql }, signal) - return data + return result } type CreatePublicationData = Awaited> diff --git a/packages/pg-meta/src/sql/studio/database/index.ts b/packages/pg-meta/src/sql/studio/database/index.ts index 0b582682048..0954bbafcf0 100644 --- a/packages/pg-meta/src/sql/studio/database/index.ts +++ b/packages/pg-meta/src/sql/studio/database/index.ts @@ -16,3 +16,4 @@ export * from './triggers' export * from './types' export * from './vault' export * from './views' +export * from './publications' diff --git a/packages/pg-meta/src/sql/studio/database/publications.ts b/packages/pg-meta/src/sql/studio/database/publications.ts new file mode 100644 index 00000000000..81173966197 --- /dev/null +++ b/packages/pg-meta/src/sql/studio/database/publications.ts @@ -0,0 +1,16 @@ +import { ident } from '../../../pg-format' + +export const getCreatePublicationSQL = ({ + name, + tables, +}: { + name: string + tables: { schema: string; name: string }[] +}) => { + const query = + tables.length > 0 + ? `FOR TABLE ONLY ${tables.map(({ schema, name }) => `${ident(schema)}.${ident(name)}`).join(', ')} ` + : '' + + return `CREATE PUBLICATION ${ident(name)} ${query}WITH (publish_via_partition_root = true)` +}