My solution starts from the fact that I don't have permissions in my scenario to perform DELETE FROM pg_enum
as I'm getting a permissions error.
Starting from other replies, I created a generic function that can be used to remove a single value from a enum, supporting the update value to release the use of given value
-- https://stackoverflow.com/a/62444685
-- https://stackoverflow.com/a/51073579
create or replace function remove_enum_value(
type_name text, -- Name of the type where you need to remove a value from
value_to_delete text, -- Specific value of the given type you want to remove
value_fallback text, -- Which new value columns will have instead of the value deleted
column_default text -- DEFAULT value for the column after type alteration (DEFAULT need to be disabled before changing type, https://stackoverflow.com/a/41149789)
)
RETURNS VOID AS $body$
declare
-- Used as temporary type
_type_name_tmp text := type_name || '_tmp_' || floor(extract(epoch from now()) * 1000);
-- Used to store statements to execute
_sql text;
-- Used to loop tables and switch type from current to temporary
_column_data record;
_table_name varchar(255);
_column_name varchar(255);
begin
--------------------------------------------------------------------------------------------------------------
-- Check: required inputs
if type_name is null
then
raise exception 'Parameter type_name is null';
end if;
if value_to_delete is null
then
raise exception 'Parameter value_to_delete is null';
end if;
-- Check: type exists
IF not EXISTS (SELECT 1 FROM pg_type WHERE typname = type_name) THEN
raise info 'Type %s does not exists', type_name;
return;
END IF;
-- Check: value to delete exists
if not exists(
select *
FROM pg_enum -- check existing of value to delete
WHERE enumtypid = (select oid from pg_type where typName=cast(type_name as varchar) limit 1) and enumlabel=cast(value_to_delete as varchar)
)
then
raise info 'Value to delete % does not exists in type %s', value_to_delete, type_name;
return;
end if;
-- Check: fallback value is provided and exists
if value_fallback is not null and not exists(
select *
FROM pg_enum -- check existing of value to delete
where
enumtypid = (select oid from pg_type where typName=cast(type_name as varchar) limit 1)
and enumlabel=cast(value_fallback as varchar)
)
then
raise info 'Fallback value % does not exists in type %s', value_fallback, type_name;
return;
end if;
-- Check values are different
if value_fallback = value_to_delete
then
raise info 'Value to delete %s is the same as fallback value %', value_to_delete, value_fallback;
return;
end if;
raise info 'Checks passed, ready to process!';
--------------------------------------------------------------------------------------------------------------
-- Retrieve current values of type
_sql := format('
SELECT string_agg(quote_literal(value), '','')
FROM unnest(enum_range(NULL::%s)) value
WHERE value <> ''%s''
', type_name, value_to_delete);
raise info '%', _sql;
execute _sql into _sql;
-- Create temporary enum
_sql := format(
'CREATE TYPE %s AS ENUM (%s)',
_type_name_tmp,
_sql
);
raise info '%', _sql;
execute _sql;
-- Rename all values from value that need to be deleted to new value (selecting all tables with schemas which has column with enum relation)
for _column_data in (
select
concat(c.table_schema,'.',c.table_name ) as table_name,
c.column_name
FROM information_schema.columns c
where
c.udt_name = cast(type_name as varchar)
and c.table_schema=c.udt_schema
and data_type = 'USER-DEFINED'
)
LOOP
_sql:= format('UPDATE %1$s set %2$s = %3$L where %2$s=%4$L', _column_data.table_name, _column_data.column_name, value_fallback, value_to_delete);
raise info 'Update by looping: %', _sql;
EXECUTE _sql;
END LOOP;
-- Switch type from current to temporary
FOR _column_data in (
SELECT cols.table_name, cols.column_name
FROM information_schema.columns cols
WHERE udt_name = type_name
)
LOOP
_table_name := _column_data.table_name;
_column_name := _column_data.column_name;
_sql := format(
'
ALTER TABLE %s
ALTER COLUMN %s DROP DEFAULT,
ALTER COLUMN %s TYPE %s USING %s::text::%s,
ALTER COLUMN %s SET DEFAULT %s;
',
_table_name,
_column_name,
_column_name, _type_name_tmp, _column_name, _type_name_tmp,
_column_name, (case when column_default is null then null else '''' || column_default || '''::' || _type_name_tmp end)
);
raise info '%', _sql;
execute _sql;
END LOOP;
-- Drop previous type
_sql := format('DROP TYPE %s;', type_name);
raise info '%', _sql;
execute _sql;
-- Rename type to previous name
_sql := format('ALTER TYPE %s RENAME TO %s;', _type_name_tmp, type_name);
raise info '%', _sql;
execute _sql;
END $body$
LANGUAGE plpgsql;
(psycopg2.InternalError) ALTER TYPE ... ADD cannot run inside a transaction block
– Evelinevelina