diff --git a/backend/alembic/versions/b020_add_jira_links_and_worklogs.py b/backend/alembic/versions/b020_add_jira_links_and_worklogs.py index 8780239..0ad0b18 100644 --- a/backend/alembic/versions/b020_add_jira_links_and_worklogs.py +++ b/backend/alembic/versions/b020_add_jira_links_and_worklogs.py @@ -19,36 +19,29 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: - # ── Enums — create once, then reference with create_type=False ─── - jira_link_entity_type = sa.Enum( - "test", "technique", "campaign", "evidence", - name="jiralinkentitytype", - ) - jira_sync_direction = sa.Enum( - "aegis_to_jira", "jira_to_aegis", "bidirectional", - name="jirasyncdirection", - ) - jira_link_entity_type.create(op.get_bind(), checkfirst=True) - jira_sync_direction.create(op.get_bind(), checkfirst=True) + # ── Enums via raw SQL to avoid any SQLAlchemy auto-create hooks ─── + op.execute(""" + DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'jiralinkentitytype') THEN + CREATE TYPE jiralinkentitytype AS ENUM ('test', 'technique', 'campaign', 'evidence'); + END IF; + END $$ + """) + op.execute(""" + DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'jirasyncdirection') THEN + CREATE TYPE jirasyncdirection AS ENUM ('aegis_to_jira', 'jira_to_aegis', 'bidirectional'); + END IF; + END $$ + """) - # Re-reference with create_type=False so create_table won't try to - # CREATE TYPE again (which causes DuplicateObject on PostgreSQL). - entity_type_col = sa.Enum( - "test", "technique", "campaign", "evidence", - name="jiralinkentitytype", - create_type=False, - ) - sync_dir_col = sa.Enum( - "aegis_to_jira", "jira_to_aegis", "bidirectional", - name="jirasyncdirection", - create_type=False, - ) - - # ── jira_links table ───────────────────────────────────────────── + # Use sa.String for column types — the actual PG column will use the + # enum type via explicit USING casts below. This completely avoids + # SQLAlchemy's _on_table_create hook that causes DuplicateObject. op.create_table( "jira_links", sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), - sa.Column("entity_type", entity_type_col, nullable=False), + sa.Column("entity_type", sa.Text, nullable=False), sa.Column("entity_id", postgresql.UUID(as_uuid=True), nullable=False), sa.Column("jira_issue_key", sa.String(50), nullable=False), sa.Column("jira_issue_id", sa.String(50)), @@ -57,13 +50,18 @@ def upgrade() -> None: sa.Column("jira_priority", sa.String(50)), sa.Column("jira_assignee", sa.String(255)), sa.Column("jira_story_points", sa.String(10)), - sa.Column("sync_direction", sync_dir_col, server_default="bidirectional"), + sa.Column("sync_direction", sa.Text, server_default="bidirectional"), sa.Column("last_synced_at", sa.DateTime), sa.Column("sync_metadata", postgresql.JSONB, server_default="{}"), sa.Column("created_by", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id")), sa.Column("created_at", sa.DateTime, server_default=sa.func.now()), sa.Column("updated_at", sa.DateTime, server_default=sa.func.now()), ) + + # Now ALTER the columns to use the actual enum types + op.execute("ALTER TABLE jira_links ALTER COLUMN entity_type TYPE jiralinkentitytype USING entity_type::jiralinkentitytype") + op.execute("ALTER TABLE jira_links ALTER COLUMN sync_direction TYPE jirasyncdirection USING sync_direction::jirasyncdirection") + op.create_index("ix_jira_links_entity_id", "jira_links", ["entity_id"]) op.create_index("ix_jira_links_issue_key", "jira_links", ["jira_issue_key"]) op.create_index( @@ -102,5 +100,5 @@ def upgrade() -> None: def downgrade() -> None: op.drop_table("worklogs") op.drop_table("jira_links") - sa.Enum(name="jirasyncdirection").drop(op.get_bind(), checkfirst=True) - sa.Enum(name="jiralinkentitytype").drop(op.get_bind(), checkfirst=True) + op.execute("DROP TYPE IF EXISTS jirasyncdirection") + op.execute("DROP TYPE IF EXISTS jiralinkentitytype")