在alembic中添加/删除PostgreSQL ENUM类型的值
在alembic中添加/删除PostgreSQL ENUM类型的值
我在使用SQLAlchemy和Alembic时遇到了一个问题,即更改现有的postgresql.ENUM
列。\n我想在alembic中向postgresql.ENUM类型的列添加/删除一个值。\n具体而言,当前的枚举类型是通过以下两个alembic修订创建的:\n
# 修订1 def 升级(): op.create_table('kernels', sa.Column('status', sa.String(), nullable=True), ... ) # 修订2 kernelstatus_choices = ( 'PREPARING', 'BUILDING', 'RUNNING', 'RESTARTING', 'RESIZING', 'SUSPENDED', 'TERMINATING', 'TERMINATED', 'ERROR', ) kernelstatus = postgresql.ENUM( *kernelstatus_choices, name='kernelstatus') def 升级(): op.alter_column('kernels', column_name='status', type_=sa.Enum(*kernelstatus_choices, name='kernelstatus'), postgresql_using='status::kernelstatus')
\n现在,我想将\'PENDING\'
状态添加到kernelstatus
类型中。所以我按照以下方式实现,参考了一些 文章。\n
prev_kernelstatus_choices = ( 'PREPARING', 'BUILDING', 'RUNNING', 'RESTARTING', 'RESIZING', 'SUSPENDED', 'TERMINATING', 'TERMINATED', 'ERROR', ) prev_kernelstatus = postgresql.ENUM( *prev_kernelstatus_choices, name='kernelstatus') curr_kernelstatus_choices = ('PENDING',) + prev_kernelstatus_choices curr_kernelstatus = postgresql.ENUM( *curr_kernelstatus_choices, name='kernelstatus') def 升级(): op.execute('ALTER TYPE kernelstatus RENAME TO kernelstatus_old;') curr_kernelstatus.create(op.get_bind()) op.alter_column('kernels', column_name='status', type_=sa.Enum(*curr_kernelstatus_choices, name='kernelstatus'), postgresql_using='status::text::kernelstatus') op.execute('DROP TYPE kernelstatus_old;')
\n但它一直生成以下错误:\n
sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) operator does not exist: kernelstatus <> kernelstatus_old HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts. [SQL: 'ALTER TABLE kernels ALTER COLUMN status TYPE kernelstatus USING status::text::kernelstatus']
\n我已经尝试了向枚举类型添加值的解决方案,但这在与Alembic一起使用时不起作用,因为每个Alembic修订在一个事务中运行,而ALTER TYPE
语句无法在事务中运行。此外,在PostgreSQL中没有删除枚举类型值的语句,因此在我的情况下,仅向枚举类型中添加一个值不可能是最终解决方案。\n有人能帮助我吗?
问题出现的原因是因为在将status
列的枚举类型转换时,需要显式定义<>
SQL运算符,以保持此约束检查在转换过程中的一致性。但问题是,PostgreSQL的错误消息只是说“运算符不存在”,而没有提到“必须定义运算符以在转换期间保持约束”。为了解决这个问题,可以通过将旧值和新值都转换为字符串,临时添加一个比较运算符来实现。
解决方法如下:
from alembic import op import sqlalchemy as sa import textwrap from sqlalchemy.dialects import postgresql kernelstatus_new_values = [ 'PENDING', # added 'PREPARING', ... ] kernelstatus_new = postgresql.ENUM(*kernelstatus_new_values, name='kernelstatus') kernelstatus_old_values = [ 'PREPARING', ... ] kernelstatus_old = postgresql.ENUM(*kernelstatus_old_values, name='kernelstatus') def upgrade(): conn = op.get_bind() sessionresult.create(conn) sessiontypes.create(conn) conn.execute('ALTER TYPE kernelstatus RENAME TO kernelstatus_old;') kernelstatus_new.create(conn) conn.execute(textwrap.dedent('''\ CREATE FUNCTION new_old_compare( new_enum_val kernelstatus, old_enum_val kernelstatus_old ) RETURNS boolean AS $$ SELECT new_enum_val::text <> old_enum_val::text; $$ LANGUAGE SQL IMMUTABLE; CREATE OPERATOR <> ( leftarg = kernelstatus, rightarg = kernelstatus_old, procedure = new_old_compare ); ''')) op.alter_column( table_name='kernels', column_name='status', type_=kernelstatus_new, postgresql_using='status::text::kernelstatus', ) conn.execute(textwrap.dedent('''\ DROP FUNCTION new_old_compare( new_enum_val kernelstatus, old_enum_val kernelstatus_old ) CASCADE; DROP TYPE kernelstatus_old; ''')) ... # the rest of migration