"""Extend UserRole enum: add global_admin and tenant_admin. Existing 'admin' users are backfilled to 'global_admin'. The legacy 'admin' value is kept in the enum so existing tokens/sessions remain valid during the transition period. It will be removed in a later cleanup migration once all call sites are updated. Revision ID: 049 Revises: 048 """ from alembic import op import sqlalchemy as sa revision = "049" down_revision = "048" branch_labels = None depends_on = None def upgrade() -> None: # PostgreSQL requires ALTER TYPE ADD VALUE to be committed before the new value # can be referenced in the same session. With asyncpg + alembic the connection # already has an open transaction (begin_transaction), so we cannot change # isolation level via execution_options. Instead we manually COMMIT the outer # transaction, run the DDL, then open a new transaction for the DML backfill. conn = op.get_bind() # Commit the alembic-managed transaction so ALTER TYPE runs outside a tx block conn.execute(sa.text("COMMIT")) conn.execute(sa.text("ALTER TYPE userrole ADD VALUE IF NOT EXISTS 'global_admin'")) conn.execute(sa.text("ALTER TYPE userrole ADD VALUE IF NOT EXISTS 'tenant_admin'")) # Start a new transaction for the backfill DML conn.execute(sa.text("BEGIN")) conn.execute( sa.text("UPDATE users SET role = 'global_admin'::userrole WHERE role = 'admin'::userrole") ) def downgrade() -> None: # Restore global_admin/tenant_admin back to admin (for rollback) op.execute( "UPDATE users SET role = 'admin'::userrole WHERE role = 'global_admin'::userrole" ) op.execute( "UPDATE users SET role = 'client'::userrole WHERE role = 'tenant_admin'::userrole" ) # Note: cannot DROP enum values in PostgreSQL without recreating the type # The values will remain in the enum but unused after downgrade