fix(phase-35): use pure SQL for jira_links migration
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 all sa.Enum / op.create_table / ALTER TABLE approach with a single op.execute() containing raw DDL. This sidesteps every SQLAlchemy hook (enum auto-create, default cast conflicts) by letting PostgreSQL handle CREATE TYPE IF NOT EXISTS, CREATE TABLE IF NOT EXISTS, and CREATE INDEX IF NOT EXISTS directly.
This commit is contained in:
@@ -19,86 +19,77 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
# ── Enums via raw SQL to avoid any SQLAlchemy auto-create hooks ───
|
# ── jira_links: 100 % raw SQL to avoid all SQLAlchemy enum hooks ──
|
||||||
op.execute("""
|
op.execute("""
|
||||||
DO $$ BEGIN
|
DO $$ BEGIN
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'jiralinkentitytype') THEN
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'jiralinkentitytype') THEN
|
||||||
CREATE TYPE jiralinkentitytype AS ENUM ('test', 'technique', 'campaign', 'evidence');
|
CREATE TYPE jiralinkentitytype AS ENUM ('test', 'technique', 'campaign', 'evidence');
|
||||||
END IF;
|
END IF;
|
||||||
END $$
|
END $$;
|
||||||
""")
|
|
||||||
op.execute("""
|
|
||||||
DO $$ BEGIN
|
DO $$ BEGIN
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'jirasyncdirection') THEN
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'jirasyncdirection') THEN
|
||||||
CREATE TYPE jirasyncdirection AS ENUM ('aegis_to_jira', 'jira_to_aegis', 'bidirectional');
|
CREATE TYPE jirasyncdirection AS ENUM ('aegis_to_jira', 'jira_to_aegis', 'bidirectional');
|
||||||
END IF;
|
END IF;
|
||||||
END $$
|
END $$;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS jira_links (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
entity_type jiralinkentitytype NOT NULL,
|
||||||
|
entity_id UUID NOT NULL,
|
||||||
|
jira_issue_key VARCHAR(50) NOT NULL,
|
||||||
|
jira_issue_id VARCHAR(50),
|
||||||
|
jira_project_key VARCHAR(20),
|
||||||
|
jira_status VARCHAR(100),
|
||||||
|
jira_priority VARCHAR(50),
|
||||||
|
jira_assignee VARCHAR(255),
|
||||||
|
jira_story_points VARCHAR(10),
|
||||||
|
sync_direction jirasyncdirection DEFAULT 'bidirectional',
|
||||||
|
last_synced_at TIMESTAMP,
|
||||||
|
sync_metadata JSONB DEFAULT '{}',
|
||||||
|
created_by UUID REFERENCES users(id),
|
||||||
|
created_at TIMESTAMP DEFAULT now(),
|
||||||
|
updated_at TIMESTAMP DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_jira_links_entity_id
|
||||||
|
ON jira_links (entity_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_jira_links_issue_key
|
||||||
|
ON jira_links (jira_issue_key);
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_jira_links_entity_type_entity_id
|
||||||
|
ON jira_links (entity_type, entity_id);
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Use sa.String for column types — the actual PG column will use the
|
# ── worklogs table (no enums, straightforward) ───────────────────
|
||||||
# enum type via explicit USING casts below. This completely avoids
|
op.execute("""
|
||||||
# SQLAlchemy's _on_table_create hook that causes DuplicateObject.
|
CREATE TABLE IF NOT EXISTS worklogs (
|
||||||
op.create_table(
|
id UUID PRIMARY KEY,
|
||||||
"jira_links",
|
entity_type VARCHAR(50) NOT NULL,
|
||||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
entity_id UUID NOT NULL,
|
||||||
sa.Column("entity_type", sa.Text, nullable=False),
|
user_id UUID NOT NULL REFERENCES users(id),
|
||||||
sa.Column("entity_id", postgresql.UUID(as_uuid=True), nullable=False),
|
activity_type VARCHAR(100) NOT NULL,
|
||||||
sa.Column("jira_issue_key", sa.String(50), nullable=False),
|
started_at TIMESTAMP NOT NULL,
|
||||||
sa.Column("jira_issue_id", sa.String(50)),
|
ended_at TIMESTAMP,
|
||||||
sa.Column("jira_project_key", sa.String(20)),
|
duration_seconds INTEGER NOT NULL,
|
||||||
sa.Column("jira_status", sa.String(100)),
|
description TEXT,
|
||||||
sa.Column("jira_priority", sa.String(50)),
|
tempo_synced TIMESTAMP,
|
||||||
sa.Column("jira_assignee", sa.String(255)),
|
tempo_worklog_id VARCHAR(100),
|
||||||
sa.Column("jira_story_points", sa.String(10)),
|
integrity_hash VARCHAR(64),
|
||||||
sa.Column("sync_direction", sa.Text, server_default="bidirectional"),
|
created_at TIMESTAMP DEFAULT now(),
|
||||||
sa.Column("last_synced_at", sa.DateTime),
|
metadata JSONB 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_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
|
CREATE INDEX IF NOT EXISTS ix_worklogs_entity_id
|
||||||
op.execute("ALTER TABLE jira_links ALTER COLUMN entity_type TYPE jiralinkentitytype USING entity_type::jiralinkentitytype")
|
ON worklogs (entity_id);
|
||||||
op.execute("ALTER TABLE jira_links ALTER COLUMN sync_direction TYPE jirasyncdirection USING sync_direction::jirasyncdirection")
|
CREATE INDEX IF NOT EXISTS ix_worklogs_user_id
|
||||||
|
ON worklogs (user_id);
|
||||||
op.create_index("ix_jira_links_entity_id", "jira_links", ["entity_id"])
|
CREATE INDEX IF NOT EXISTS ix_worklogs_entity_type_entity_id
|
||||||
op.create_index("ix_jira_links_issue_key", "jira_links", ["jira_issue_key"])
|
ON worklogs (entity_type, entity_id);
|
||||||
op.create_index(
|
""")
|
||||||
"ix_jira_links_entity_type_entity_id",
|
|
||||||
"jira_links",
|
|
||||||
["entity_type", "entity_id"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── worklogs table ───────────────────────────────────────────────
|
|
||||||
op.create_table(
|
|
||||||
"worklogs",
|
|
||||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
|
||||||
sa.Column("entity_type", sa.String(50), nullable=False),
|
|
||||||
sa.Column("entity_id", postgresql.UUID(as_uuid=True), nullable=False),
|
|
||||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id"), nullable=False),
|
|
||||||
sa.Column("activity_type", sa.String(100), nullable=False),
|
|
||||||
sa.Column("started_at", sa.DateTime, nullable=False),
|
|
||||||
sa.Column("ended_at", sa.DateTime),
|
|
||||||
sa.Column("duration_seconds", sa.Integer, nullable=False),
|
|
||||||
sa.Column("description", sa.Text),
|
|
||||||
sa.Column("tempo_synced", sa.DateTime),
|
|
||||||
sa.Column("tempo_worklog_id", sa.String(100)),
|
|
||||||
sa.Column("integrity_hash", sa.String(64)),
|
|
||||||
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
|
|
||||||
sa.Column("metadata", postgresql.JSONB, server_default="{}"),
|
|
||||||
)
|
|
||||||
op.create_index("ix_worklogs_entity_id", "worklogs", ["entity_id"])
|
|
||||||
op.create_index("ix_worklogs_user_id", "worklogs", ["user_id"])
|
|
||||||
op.create_index(
|
|
||||||
"ix_worklogs_entity_type_entity_id",
|
|
||||||
"worklogs",
|
|
||||||
["entity_type", "entity_id"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
op.drop_table("worklogs")
|
op.execute("DROP TABLE IF EXISTS worklogs")
|
||||||
op.drop_table("jira_links")
|
op.execute("DROP TABLE IF EXISTS jira_links")
|
||||||
op.execute("DROP TYPE IF EXISTS jirasyncdirection")
|
op.execute("DROP TYPE IF EXISTS jirasyncdirection")
|
||||||
op.execute("DROP TYPE IF EXISTS jiralinkentitytype")
|
op.execute("DROP TYPE IF EXISTS jiralinkentitytype")
|
||||||
|
|||||||
Reference in New Issue
Block a user