mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-08-20 21:22:54 +00:00
fix(restore): leave an extension's own objects to the extension
The --empty pre-clean picked its candidates by owner, on the stated
assumption that extension members are superuser-owned and would never be
selected. That holds only when a superuser installed the extension. A role
that installs one itself owns its functions, so they were listed for a
one-by-one DROP that postgres refuses:
cannot drop function vector_in(cstring,oid,integer)
because extension vector requires it
Under ON_ERROR_STOP that ends the whole restore, which is how a discourse
generation - it declares the vector extension - became unreplayable.
Membership now comes from pg_depend rather than from ownership. Each branch
carries its oid and classid so one NOT EXISTS covers all seven instead of
seven separate predicates, and the schema branch gets the same guard because
an extension can own a schema too. Skipping the members is enough: the dump's
CREATE EXTENSION IF NOT EXISTS finds the surviving extension either way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,12 @@
|
||||
-- would run every DROP in one transaction and exhaust max_locks_per_transaction on
|
||||
-- large schemas (e.g. gitlab). Also drops user-owned non-public schemas so a dump
|
||||
-- that CREATE SCHEMAs (e.g. discourse's discourse_functions) does not fail on an
|
||||
-- already-existing schema. Extension members (pg_trgm's set_limit) are
|
||||
-- superuser-owned; IF EXISTS absorbs the CASCADE fallout.
|
||||
-- already-existing schema. Objects belonging to an extension are skipped: postgres
|
||||
-- refuses to drop them one by one ("cannot drop function vector_in(...) because
|
||||
-- extension vector requires it"), and the dump's CREATE EXTENSION IF NOT EXISTS
|
||||
-- finds the surviving extension either way. Owning them is not enough to make them
|
||||
-- droppable - an extension a role installed itself is owned by that role, so the
|
||||
-- owner filter alone lets pgvector's members through.
|
||||
SELECT format('DROP %s IF EXISTS public.%s CASCADE', obj.type, obj.name)
|
||||
FROM (
|
||||
SELECT format('%I', c.relname) AS name,
|
||||
@@ -13,7 +17,8 @@ SELECT format('DROP %s IF EXISTS public.%s CASCADE', obj.type, obj.name)
|
||||
WHEN 'm' THEN 'MATERIALIZED VIEW'
|
||||
WHEN 'f' THEN 'FOREIGN TABLE'
|
||||
ELSE 'TABLE'
|
||||
END AS type
|
||||
END AS type,
|
||||
c.oid AS objid, 'pg_class'::regclass AS classid
|
||||
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'public' AND c.relkind IN ('r', 'p', 'v', 'm', 'f')
|
||||
AND pg_get_userbyid(c.relowner) = current_user
|
||||
@@ -21,17 +26,20 @@ SELECT format('DROP %s IF EXISTS public.%s CASCADE', obj.type, obj.name)
|
||||
-- Overloaded functions share a proname; DROP needs the identity
|
||||
-- signature or psql aborts with "function name is not unique".
|
||||
SELECT format('%I(%s)', p.proname, pg_get_function_identity_arguments(p.oid)) AS name,
|
||||
CASE p.prokind WHEN 'p' THEN 'PROCEDURE' ELSE 'FUNCTION' END AS type
|
||||
CASE p.prokind WHEN 'p' THEN 'PROCEDURE' ELSE 'FUNCTION' END AS type,
|
||||
p.oid AS objid, 'pg_proc'::regclass AS classid
|
||||
FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
||||
WHERE n.nspname = 'public' AND p.prokind IN ('f', 'p', 'w')
|
||||
AND pg_get_userbyid(p.proowner) = current_user
|
||||
UNION ALL
|
||||
SELECT format('%I', c.relname) AS name, 'SEQUENCE' AS type
|
||||
SELECT format('%I', c.relname) AS name, 'SEQUENCE' AS type,
|
||||
c.oid AS objid, 'pg_class'::regclass AS classid
|
||||
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'public' AND c.relkind = 'S'
|
||||
AND pg_get_userbyid(c.relowner) = current_user
|
||||
UNION ALL
|
||||
SELECT format('%I', t.typname) AS name, 'TYPE' AS type
|
||||
SELECT format('%I', t.typname) AS name, 'TYPE' AS type,
|
||||
t.oid AS objid, 'pg_type'::regclass AS classid
|
||||
FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE n.nspname = 'public'
|
||||
AND pg_get_userbyid(t.typowner) = current_user
|
||||
@@ -40,25 +48,36 @@ SELECT format('DROP %s IF EXISTS public.%s CASCADE', obj.type, obj.name)
|
||||
SELECT 1 FROM pg_class c2
|
||||
WHERE c2.oid = t.typrelid AND c2.relkind = 'c')))
|
||||
UNION ALL
|
||||
SELECT format('%I', col.collname) AS name, 'COLLATION' AS type
|
||||
SELECT format('%I', col.collname) AS name, 'COLLATION' AS type,
|
||||
col.oid AS objid, 'pg_collation'::regclass AS classid
|
||||
FROM pg_collation col JOIN pg_namespace n ON n.oid = col.collnamespace
|
||||
WHERE n.nspname = 'public'
|
||||
AND pg_get_userbyid(col.collowner) = current_user
|
||||
UNION ALL
|
||||
SELECT format('%I', ts.cfgname) AS name, 'TEXT SEARCH CONFIGURATION' AS type
|
||||
SELECT format('%I', ts.cfgname) AS name, 'TEXT SEARCH CONFIGURATION' AS type,
|
||||
ts.oid AS objid, 'pg_ts_config'::regclass AS classid
|
||||
FROM pg_ts_config ts JOIN pg_namespace n ON n.oid = ts.cfgnamespace
|
||||
WHERE n.nspname = 'public'
|
||||
AND pg_get_userbyid(ts.cfgowner) = current_user
|
||||
UNION ALL
|
||||
SELECT format('%I', d.dictname) AS name, 'TEXT SEARCH DICTIONARY' AS type
|
||||
SELECT format('%I', d.dictname) AS name, 'TEXT SEARCH DICTIONARY' AS type,
|
||||
d.oid AS objid, 'pg_ts_dict'::regclass AS classid
|
||||
FROM pg_ts_dict d JOIN pg_namespace n ON n.oid = d.dictnamespace
|
||||
WHERE n.nspname = 'public'
|
||||
AND pg_get_userbyid(d.dictowner) = current_user
|
||||
) obj
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM pg_depend dep
|
||||
WHERE dep.classid = obj.classid AND dep.objid = obj.objid
|
||||
AND dep.deptype = 'e')
|
||||
UNION ALL
|
||||
SELECT format('DROP SCHEMA IF EXISTS %I CASCADE', n.nspname)
|
||||
FROM pg_namespace n
|
||||
WHERE NOT starts_with(n.nspname, 'pg_')
|
||||
AND n.nspname NOT IN ('public', 'information_schema')
|
||||
AND pg_get_userbyid(n.nspowner) = current_user
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM pg_depend dep
|
||||
WHERE dep.classid = 'pg_namespace'::regclass AND dep.objid = n.oid
|
||||
AND dep.deptype = 'e')
|
||||
\gexec
|
||||
|
||||
Reference in New Issue
Block a user