fix(phase-35): rewrite migration to avoid SQLAlchemy enum auto-create
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Replace sa.Enum column types with raw SQL DO $$ IF NOT EXISTS blocks for enum creation, then sa.Text columns + ALTER TYPE USING casts. This completely bypasses SQLAlchemy's _on_table_create hook that triggers CREATE TYPE without checkfirst, causing DuplicateObject on PostgreSQL when the entrypoint retries after a failed migration.
This commit is contained in:
@@ -19,36 +19,29 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
# ── Enums — create once, then reference with create_type=False ───
|
# ── Enums via raw SQL to avoid any SQLAlchemy auto-create hooks ───
|
||||||
jira_link_entity_type = sa.Enum(
|
op.execute("""
|
||||||
"test", "technique", "campaign", "evidence",
|
DO $$ BEGIN
|
||||||
name="jiralinkentitytype",
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'jiralinkentitytype') THEN
|
||||||
)
|
CREATE TYPE jiralinkentitytype AS ENUM ('test', 'technique', 'campaign', 'evidence');
|
||||||
jira_sync_direction = sa.Enum(
|
END IF;
|
||||||
"aegis_to_jira", "jira_to_aegis", "bidirectional",
|
END $$
|
||||||
name="jirasyncdirection",
|
""")
|
||||||
)
|
op.execute("""
|
||||||
jira_link_entity_type.create(op.get_bind(), checkfirst=True)
|
DO $$ BEGIN
|
||||||
jira_sync_direction.create(op.get_bind(), checkfirst=True)
|
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
|
# Use sa.String for column types — the actual PG column will use the
|
||||||
# CREATE TYPE again (which causes DuplicateObject on PostgreSQL).
|
# enum type via explicit USING casts below. This completely avoids
|
||||||
entity_type_col = sa.Enum(
|
# SQLAlchemy's _on_table_create hook that causes DuplicateObject.
|
||||||
"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 ─────────────────────────────────────────────
|
|
||||||
op.create_table(
|
op.create_table(
|
||||||
"jira_links",
|
"jira_links",
|
||||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
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("entity_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
sa.Column("jira_issue_key", sa.String(50), nullable=False),
|
sa.Column("jira_issue_key", sa.String(50), nullable=False),
|
||||||
sa.Column("jira_issue_id", sa.String(50)),
|
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_priority", sa.String(50)),
|
||||||
sa.Column("jira_assignee", sa.String(255)),
|
sa.Column("jira_assignee", sa.String(255)),
|
||||||
sa.Column("jira_story_points", sa.String(10)),
|
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("last_synced_at", sa.DateTime),
|
||||||
sa.Column("sync_metadata", postgresql.JSONB, server_default="{}"),
|
sa.Column("sync_metadata", postgresql.JSONB, server_default="{}"),
|
||||||
sa.Column("created_by", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id")),
|
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("created_at", sa.DateTime, server_default=sa.func.now()),
|
||||||
sa.Column("updated_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_entity_id", "jira_links", ["entity_id"])
|
||||||
op.create_index("ix_jira_links_issue_key", "jira_links", ["jira_issue_key"])
|
op.create_index("ix_jira_links_issue_key", "jira_links", ["jira_issue_key"])
|
||||||
op.create_index(
|
op.create_index(
|
||||||
@@ -102,5 +100,5 @@ def upgrade() -> None:
|
|||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
op.drop_table("worklogs")
|
op.drop_table("worklogs")
|
||||||
op.drop_table("jira_links")
|
op.drop_table("jira_links")
|
||||||
sa.Enum(name="jirasyncdirection").drop(op.get_bind(), checkfirst=True)
|
op.execute("DROP TYPE IF EXISTS jirasyncdirection")
|
||||||
sa.Enum(name="jiralinkentitytype").drop(op.get_bind(), checkfirst=True)
|
op.execute("DROP TYPE IF EXISTS jiralinkentitytype")
|
||||||
|
|||||||
Reference in New Issue
Block a user