python - Alterar un campo Enum usando Alambique
django enum (5)
En SQL directo, esto funcionaría para Postgres, si el orden de las cosas en su enumeración no tiene que ser exactamente como el anterior:
ALTER TYPE status ADD value ''output_limit_exceeded'' after ''timed_out'';
¿Cómo puedo agregar un elemento a un campo Enum en una migración de alambiques cuando uso una versión de PostgreSQL anterior a la 9.1 (que agrega ALTER TYPE para las enumeraciones)? Esta pregunta SO explica el proceso directo, pero no estoy seguro de cómo traducirlo usando alambique.
Esto es lo que tengo:
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)
Lo anterior desafortunadamente solo produce el estado ALTER TABLE testcaseresult ALTER COLUMN status TYPE status
al momento de la actualización, que básicamente no hace nada.
A partir de Postgres 9.1 agregar un nuevo valor a una enumeración se puede hacer con la instrucción ALTER TYPE
. Esto se complica por el hecho de que no se puede hacer en una transacción . Sin embargo, esto se puede solucionar comprometiendo la transacción del alambique, ver aquí .
De hecho, tuve problemas al usar la solución anterior, más detallada, porque Postgres no pudo convertir automáticamente el valor predeterminado para la columna.
Utilicé un enfoque un poco más simple con menos pasos que la respuesta aceptada, en la cual basé esto. En este ejemplo pretenderé que la enumeración en cuestión se llama ''status_enum'', porque en la respuesta aceptada el uso de ''status'' para la columna y la enumeración me confundió.
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)
Decidí tratar de seguir el enfoque de postgres lo más directamente posible y surgió la siguiente migración.
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)
Parece que el alambique no tiene soporte directo para la instrucción USING
en su método alter_table
.
Tuve el mismo problema al intentar migrar un tipo de columna a otro. Yo uso los siguientes requisitos:
Alembic==0.9.4
SQLAlchemy==1.1.12
Puede proporcionar el argumento postgresql_using
como un kwarg de 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",
)
Espero que pueda ayudar.