2013-02-13 6 views
24

Jak dodać element do pola wyliczenia w migracji alembiku, gdy używana jest wersja PostgreSQL starsza niż 9.1 (która dodaje TYP ALTER dla wyliczenia)? This Pytanie SO wyjaśnia bezpośredni proces, ale nie jestem do końca pewien, jak najlepiej przetłumaczyć to przy użyciu alembika.Zmiana pola wyliczeniowego przy użyciu Alembic

To jest to, co mam:

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) 

Powyższy niestety tylko produkuje ALTER TABLE testcaseresult ALTER COLUMN status TYPE status po uaktualnieniu, które w zasadzie nic nie robi.

Odpowiedz

24

Postanowiłem spróbować jak najlepiej postępować zgodnie z postgres approach i wprowadziłem następującą migrację.

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) 

Wydaje się, że nie ma bezpośredniego alembic obsługę rachunku w jego metodzie alter_tableUSING.

+0

Działa z alembikami 0.7 i Postgres 9.4. PITA musi to zrobić w ten sposób. Zdecydowanie mam nadzieję, że nie będę musiał dokonywać żadnych zmian w moich ENUM-ach! –

+0

@ Two-BitAlchemist - czy widzisz http://stackoverflow.com/a/16821396/176978? W PostgreSQL 9.4 powinieneś być w stanie zrobić to znacznie prościej. – bboe

+0

Tak, ale niestety, raczej zrzuciłem, niż dodałem, na które nie ma jeszcze wsparcia, [nawet w 9.4] (http://www.postgresql.org/docs/9.4/static/sql-altertype.html). –

3

w prostych SQL, to będzie działać dla PostgreSQL, jeśli kolejność rzeczy w swoim wyliczenia nie musi być dokładnie tak, jak powyżej:

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

To wsparcie zostało dodane w PostgreSQL 9.1. Zaktualizuję pytanie, aby było bardziej szczegółowe (chciałbym, abym używał 9.1). – bboe

+1

Głównym celem używania Alembic i SQLAlchemy jest unikanie pisania czystego SQL, chyba że musisz. Najlepiej pobrać skrypty migracyjne generowane automatycznie na podstawie modeli SQLAlchemy. – schatten

+1

Głównym problemem jest to, że 'ALTER TYPE ... ADD VALUE ...' nie może być używane w ramach transakcji. – JelteF

7

Od PostgreSQL 9.1 dodając nową wartość do enum można wykonać za pomocą instrukcji ALTER TYPE. Jest to skomplikowane przez fakt, że it cannot be done in a transaction. Można to jednak obejść, wykonując transakcję alembika: see here.

Rzeczywiście miałem problemy z używaniem starszego, bardziej gadatliwego rozwiązania, ponieważ Postgres nie mógł automatycznie przekonwertować wartości domyślnej dla kolumny.

10

Użyłem nieco prostszego podejścia z mniejszą liczbą kroków niż zaakceptowana odpowiedź, na której ją oparłem. W tym przykładzie będę udawał, że enum, o którym mowa, nazywa się "status_enum", ponieważ w przyjętej odpowiedzi pomieszało mi użycie "statusu" zarówno dla kolumny, jak i dla enum.

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

Miałem ten sam problem podczas próby migracji typu kolumny do innej. Używam następujące wymagania:

Alembic==0.9.4 
SQLAlchemy==1.1.12 

Możesz podać argument postgresql_using jako kwarg z alembic.op.alter_column.

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", 
) 

Mam nadzieję, że to może pomóc.