2013-02-13 108 views
24

如何在使用PostgreSQL版本比9.1(其中添加了ALUM TYPE for enums)的情况下将元素添加到alembic迁移中的Enum字段? This这个问题解释了直接过程,但我不太确定如何最好地使用alembic来翻译它。使用Alembic更改Enum字段

这是我有:

new_type = sa.Enum('nonexistent_executable', 'output_limit_exceeded', 
        'signal', 'success', 'timed_out', name='status') 
old_type = sa.Enum('nonexistent_executable', 'signal', 'success', 'timed_out', 
        name='status') 
tcr = sa.sql.table('testcaseresult', 
        sa.Column('status', new_type, nullable=False)) 


def upgrade(): 
    op.alter_column('testcaseresult', u'status', type_=new_type, 
        existing_type=old_type) 


def downgrade(): 
    op.execute(tcr.update().where(tcr.c.status==u'output_limit_exceeded') 
       .values(status='timed_out')) 
    op.alter_column('testcaseresult', u'status', type_=old_type, 
        existing_type=new_type) 

以上可惜只产生ALTER TABLE testcaseresult ALTER COLUMN status TYPE status升级后,基本上什么都不做。

回答

24

我决定尽可能直接关注postgres approach,并提出了以下迁移。

from alembic import op 
import sqlalchemy as sa 

old_options = ('nonexistent_executable', 'signal', 'success', 'timed_out') 
new_options = sorted(old_options + ('output_limit_exceeded',)) 

old_type = sa.Enum(*old_options, name='status') 
new_type = sa.Enum(*new_options, name='status') 
tmp_type = sa.Enum(*new_options, name='_status') 

tcr = sa.sql.table('testcaseresult', 
        sa.Column('status', new_type, nullable=False)) 


def upgrade(): 
    # Create a tempoary "_status" type, convert and drop the "old" type 
    tmp_type.create(op.get_bind(), checkfirst=False) 
    op.execute('ALTER TABLE testcaseresult ALTER COLUMN status TYPE _status' 
       ' USING status::text::_status') 
    old_type.drop(op.get_bind(), checkfirst=False) 
    # Create and convert to the "new" status type 
    new_type.create(op.get_bind(), checkfirst=False) 
    op.execute('ALTER TABLE testcaseresult ALTER COLUMN status TYPE status' 
       ' USING status::text::status') 
    tmp_type.drop(op.get_bind(), checkfirst=False) 


def downgrade(): 
    # Convert 'output_limit_exceeded' status into 'timed_out' 
    op.execute(tcr.update().where(tcr.c.status==u'output_limit_exceeded') 
       .values(status='timed_out')) 
    # Create a tempoary "_status" type, convert and drop the "new" type 
    tmp_type.create(op.get_bind(), checkfirst=False) 
    op.execute('ALTER TABLE testcaseresult ALTER COLUMN status TYPE _status' 
       ' USING status::text::_status') 
    new_type.drop(op.get_bind(), checkfirst=False) 
    # Create and convert to the "old" status type 
    old_type.create(op.get_bind(), checkfirst=False) 
    op.execute('ALTER TABLE testcaseresult ALTER COLUMN status TYPE status' 
       ' USING status::text::status') 
    tmp_type.drop(op.get_bind(), checkfirst=False) 

看来,蒸馏器具有在其alter_table方法USING声明没有直接的支持。

+0

适用于alembic 0.7和Postgres 9.4。 PITA必须这样做。绝对希望我不必对我的ENUM进行更多的调整! – 2015-02-18 20:01:50

+0

@ Two-BitAlchemist - 你有没有看到http://stackoverflow.com/a/16821396/176978?在Postgres 9.4上,你应该可以更简单地做到这一点。 – bboe 2015-02-20 03:30:17

+0

是的,但不幸的是,我正在下降,而不是加入哪个似乎没有任何支持,[甚至在9.4](http://www.postgresql.org/docs/9.4/static/sql-altertype.html )。 – 2015-02-20 12:43:34

3

在直SQL,这将工作Postgres的,如果你枚举的东西的顺序并不需要像上面那样:

ALTER TYPE status ADD value 'output_limit_exceeded' after 'timed_out'; 
+2

该支持已添加到PostgreSQL 9.1中。我会更新这个问题以更具体(我希望我使用9.1)。 – bboe 2013-05-29 18:40:14

+1

使用Alembic和SQLAlchemy的重点是避免编写纯SQL,除非必须。理想情况下,根据SQLAlchemy模型自动生成迁移脚本。 – schatten 2014-07-03 19:36:31

+1

这样做的主要问题是'ALTER TYPE ... ADD VALUE ...'不能在事务中使用。 – JelteF 2015-11-09 19:54:23

7

对于Postgres 9.1增加了新的价值的枚举可以通过ALTER TYPE声明完成。 it cannot be done in a transaction这个事实很复杂。然而,这可以通过提交alembic的交易see here解决。

实际上,我使用旧的,更详细的解决方案时遇到问题,因为Postgres无法自动转换列的默认值。

10

我用了比我接受的答案更少一些的简单方法。在这个例子中,我会假装所讨论的枚举被称为'status_enum',因为在接受的答案中列和枚举的'status'的使用使我困惑。

from alembic import op 
import sqlalchemy as sa 

name = 'status_enum' 
tmp_name = 'tmp_' + name 

old_options = ('nonexistent_executable', 'signal', 'success', 'timed_out') 
new_options = sorted(old_options + ('output_limit_exceeded',)) 

new_type = sa.Enum(*new_options, name=name) 
old_type = sa.Enum(*old_options, name=name) 

tcr = sa.sql.table('testcaseresult', 
        sa.Column('status', new_type, nullable=False)) 

def upgrade(): 
    op.execute('ALTER TYPE ' + name + ' RENAME TO ' + tmp_name) 

    new_type.create(op.get_bind()) 
    op.execute('ALTER TABLE testcaseresult ALTER COLUMN status ' + 
       'TYPE ' + name + ' USING status::text::' + name) 
    op.execute('DROP TYPE ' + tmp_name) 


def downgrade(): 
    # Convert 'output_limit_exceeded' status into 'timed_out'                              
    op.execute(tcr.update().where(tcr.c.status=='output_limit_exceeded') 
       .values(status='timed_out')) 

    op.execute('ALTER TYPE ' + name + ' RENAME TO ' + tmp_name) 

    old_type.create(op.get_bind()) 
    op.execute('ALTER TABLE testcaseresult ALTER COLUMN status ' + 
       'TYPE ' + name + ' USING status::text::' + name) 
    op.execute('DROP TYPE ' + tmp_name) 
1

我有同样的问题,试图将列类型迁移到另一个。我用下面的要求:

Alembic==0.9.4 
SQLAlchemy==1.1.12 

您可以提供参数postgresql_usingalembic.op.alter_column一个kwarg。

from alembic import op 
import sqlalchemy as types 

op.alter_column(
    table_name='my_table', 
    column_name='my_column', 
    type_=types.NewType, 
    # allows to use postgresql USING 
    postgresql_using="my_column::PostgesEquivalentOfNewType", 
) 

我希望能帮上忙。