I have a table column that uses an enum
type. I wish to update that enum
type to have an additional possible value. I don\'t want to delete any exi
If you fall into situation when you should add enum
values in transaction, f.e. execute it in flyway migration on ALTER TYPE
statement you will be get error ERROR: ALTER TYPE ... ADD cannot run inside a transaction block
(see flyway issue #350) you could add such values into pg_enum
directly as workaround (type_egais_units
is name of target enum
):
INSERT INTO pg_enum (enumtypid, enumlabel, enumsortorder)
SELECT 'type_egais_units'::regtype::oid, 'NEW_ENUM_VALUE', ( SELECT MAX(enumsortorder) + 1 FROM pg_enum WHERE enumtypid = 'type_egais_units'::regtype )
Here is a more general but a rather fast-working solution, which apart from changing the type itself updates all columns in the database using it. The method can be applied even if a new version of ENUM is different by more than one label or misses some of the original ones. The code below replaces my_schema.my_type AS ENUM ('a', 'b', 'c')
with ENUM ('a', 'b', 'd', 'e')
:
CREATE OR REPLACE FUNCTION tmp() RETURNS BOOLEAN AS
$BODY$
DECLARE
item RECORD;
BEGIN
-- 1. create new type in replacement to my_type
CREATE TYPE my_schema.my_type_NEW
AS ENUM ('a', 'b', 'd', 'e');
-- 2. select all columns in the db that have type my_type
FOR item IN
SELECT table_schema, table_name, column_name, udt_schema, udt_name
FROM information_schema.columns
WHERE
udt_schema = 'my_schema'
AND udt_name = 'my_type'
LOOP
-- 3. Change the type of every column using my_type to my_type_NEW
EXECUTE
' ALTER TABLE ' || item.table_schema || '.' || item.table_name
|| ' ALTER COLUMN ' || item.column_name
|| ' TYPE my_schema.my_type_NEW'
|| ' USING ' || item.column_name || '::text::my_schema.my_type_NEW;';
END LOOP;
-- 4. Delete an old version of the type
DROP TYPE my_schema.my_type;
-- 5. Remove _NEW suffix from the new type
ALTER TYPE my_schema.my_type_NEW
RENAME TO my_type;
RETURN true;
END
$BODY$
LANGUAGE 'plpgsql';
SELECT * FROM tmp();
DROP FUNCTION tmp();
The whole process will run fairly quickly, because if the order of labels persists, no actual change of data will happen. I applied the method on 5 tables using my_type
and having 50,000−70,000 rows in each, and the whole process took just 10 seconds.
Of course, the function will return an exception in case if labels that are missing in the new version of the ENUM are used somewhere in the data, but in such situation something should be done beforehand anyway.
If you are using Postgres 12 you can just run ALTER TYPE ... ADD VALUE
inside of transaction (documentation).
If ALTER TYPE ... ADD VALUE (the form that adds a new value to an enum type) is executed inside a transaction block, the new value cannot be used until after the transaction has been committed.
So no hacks needed in migrations.
UPD: here is an example (thanks to Nick for it)
ALTER TYPE enum_type ADD VALUE 'new_value';
Can't add a comment to the appropriate place, but ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_type
with a default on the column failed. I had to:
ALTER table ALTER COLUMN bar DROP DEFAULT
;
and then it worked.
Complementing @Dariusz 1
For Rails 4.2.1, there's this doc section:
== Transactional Migrations
If the database adapter supports DDL transactions, all migrations will automatically be wrapped in a transaction. There are queries that you can't execute inside a transaction though, and for these situations you can turn the automatic transactions off.
class ChangeEnum < ActiveRecord::Migration
disable_ddl_transaction!
def up
execute "ALTER TYPE model_size ADD VALUE 'new_value'"
end
end
just in case, if you are using Rails and you have several statements you will need to execute one by one, like:
execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'YYY';"
execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'ZZZ';"