Files
ctms/backend/alembic/versions/20260302_01_add_setup_version_branching.py
T

236 lines
11 KiB
Python

"""add setup config version branching metadata
Revision ID: 20260302_01
Revises: 20260228_02
Create Date: 2026-03-02 16:30:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = "20260302_01"
down_revision: Union[str, None] = "20260228_02"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def _has_table(inspector: sa.Inspector, table_name: str) -> bool:
return table_name in set(inspector.get_table_names())
def _has_column(inspector: sa.Inspector, table_name: str, column_name: str) -> bool:
return column_name in {col["name"] for col in inspector.get_columns(table_name)}
def _has_unique_constraint(inspector: sa.Inspector, table_name: str, constraint_name: str) -> bool:
return constraint_name in {item["name"] for item in inspector.get_unique_constraints(table_name)}
def _has_foreign_key(inspector: sa.Inspector, table_name: str, fk_name: str) -> bool:
return fk_name in {item["name"] for item in inspector.get_foreign_keys(table_name)}
def upgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
if _has_table(inspector, "study_setup_config_versions"):
if not _has_column(inspector, "study_setup_config_versions", "branch_name"):
op.add_column("study_setup_config_versions", sa.Column("branch_name", sa.String(), nullable=True))
if not _has_column(inspector, "study_setup_config_versions", "branch_seq"):
op.add_column("study_setup_config_versions", sa.Column("branch_seq", sa.Integer(), nullable=True))
if not _has_column(inspector, "study_setup_config_versions", "version_label"):
op.add_column("study_setup_config_versions", sa.Column("version_label", sa.String(), nullable=True))
if not _has_column(inspector, "study_setup_config_versions", "parent_version_id"):
op.add_column("study_setup_config_versions", sa.Column("parent_version_id", postgresql.UUID(as_uuid=True), nullable=True))
if not _has_column(inspector, "study_setup_config_versions", "merged_from_version_id"):
op.add_column(
"study_setup_config_versions",
sa.Column("merged_from_version_id", postgresql.UUID(as_uuid=True), nullable=True),
)
op.execute("UPDATE study_setup_config_versions SET branch_name = 'main' WHERE branch_name IS NULL")
op.execute(
"UPDATE study_setup_config_versions SET branch_seq = COALESCE(display_version, version, 1) WHERE branch_seq IS NULL"
)
op.execute(
"UPDATE study_setup_config_versions SET version_label = ('v' || COALESCE(display_version, version, 1)::text) WHERE version_label IS NULL"
)
op.alter_column("study_setup_config_versions", "branch_name", nullable=False)
op.alter_column("study_setup_config_versions", "branch_seq", nullable=False)
op.alter_column("study_setup_config_versions", "version_label", nullable=False)
inspector = sa.inspect(bind)
if not _has_foreign_key(inspector, "study_setup_config_versions", "fk_setup_config_versions_parent_version"):
op.create_foreign_key(
"fk_setup_config_versions_parent_version",
"study_setup_config_versions",
"study_setup_config_versions",
["parent_version_id"],
["id"],
ondelete="SET NULL",
)
if not _has_foreign_key(inspector, "study_setup_config_versions", "fk_setup_config_versions_merged_from_version"):
op.create_foreign_key(
"fk_setup_config_versions_merged_from_version",
"study_setup_config_versions",
"study_setup_config_versions",
["merged_from_version_id"],
["id"],
ondelete="SET NULL",
)
if _has_unique_constraint(inspector, "study_setup_config_versions", "uq_setup_config_versions_study_display_version"):
op.drop_constraint(
"uq_setup_config_versions_study_display_version",
"study_setup_config_versions",
type_="unique",
)
inspector = sa.inspect(bind)
if not _has_unique_constraint(inspector, "study_setup_config_versions", "uq_setup_config_versions_study_branch_seq"):
op.create_unique_constraint(
"uq_setup_config_versions_study_branch_seq",
"study_setup_config_versions",
["study_id", "branch_name", "branch_seq"],
)
if not _has_unique_constraint(inspector, "study_setup_config_versions", "uq_setup_config_versions_study_version_label"):
op.create_unique_constraint(
"uq_setup_config_versions_study_version_label",
"study_setup_config_versions",
["study_id", "version_label"],
)
if _has_table(inspector, "study_setup_configs"):
if not _has_column(inspector, "study_setup_configs", "current_branch_name"):
op.add_column("study_setup_configs", sa.Column("current_branch_name", sa.String(), nullable=True))
if not _has_column(inspector, "study_setup_configs", "active_branch_base_version_id"):
op.add_column(
"study_setup_configs",
sa.Column("active_branch_base_version_id", postgresql.UUID(as_uuid=True), nullable=True),
)
if not _has_column(inspector, "study_setup_configs", "current_published_version_id"):
op.add_column(
"study_setup_configs",
sa.Column("current_published_version_id", postgresql.UUID(as_uuid=True), nullable=True),
)
op.execute("UPDATE study_setup_configs SET current_branch_name = 'main' WHERE current_branch_name IS NULL")
op.alter_column("study_setup_configs", "current_branch_name", nullable=False)
if _has_table(inspector, "study_setup_config_versions"):
op.execute(
"""
UPDATE study_setup_configs
SET current_published_version_id = (
SELECT v.id
FROM study_setup_config_versions AS v
WHERE v.study_id = study_setup_configs.study_id
AND study_setup_configs.published_config IS NOT NULL
AND v.config = study_setup_configs.published_config
ORDER BY
CASE
WHEN study_setup_configs.published_at IS NOT NULL
AND v.published_at = study_setup_configs.published_at THEN 0
ELSE 1
END,
v.version DESC
LIMIT 1
)
WHERE current_published_version_id IS NULL
"""
)
inspector = sa.inspect(bind)
if not _has_foreign_key(inspector, "study_setup_configs", "fk_setup_configs_active_branch_base_version"):
op.create_foreign_key(
"fk_setup_configs_active_branch_base_version",
"study_setup_configs",
"study_setup_config_versions",
["active_branch_base_version_id"],
["id"],
ondelete="SET NULL",
)
if not _has_foreign_key(inspector, "study_setup_configs", "fk_setup_configs_current_published_version"):
op.create_foreign_key(
"fk_setup_configs_current_published_version",
"study_setup_configs",
"study_setup_config_versions",
["current_published_version_id"],
["id"],
ondelete="SET NULL",
)
def downgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
if _has_table(inspector, "study_setup_configs"):
if _has_foreign_key(inspector, "study_setup_configs", "fk_setup_configs_current_published_version"):
op.drop_constraint(
"fk_setup_configs_current_published_version",
"study_setup_configs",
type_="foreignkey",
)
if _has_foreign_key(inspector, "study_setup_configs", "fk_setup_configs_active_branch_base_version"):
op.drop_constraint(
"fk_setup_configs_active_branch_base_version",
"study_setup_configs",
type_="foreignkey",
)
if _has_column(inspector, "study_setup_configs", "current_published_version_id"):
op.drop_column("study_setup_configs", "current_published_version_id")
if _has_column(inspector, "study_setup_configs", "active_branch_base_version_id"):
op.drop_column("study_setup_configs", "active_branch_base_version_id")
if _has_column(inspector, "study_setup_configs", "current_branch_name"):
op.drop_column("study_setup_configs", "current_branch_name")
inspector = sa.inspect(bind)
if _has_table(inspector, "study_setup_config_versions"):
if _has_unique_constraint(inspector, "study_setup_config_versions", "uq_setup_config_versions_study_version_label"):
op.drop_constraint(
"uq_setup_config_versions_study_version_label",
"study_setup_config_versions",
type_="unique",
)
if _has_unique_constraint(inspector, "study_setup_config_versions", "uq_setup_config_versions_study_branch_seq"):
op.drop_constraint(
"uq_setup_config_versions_study_branch_seq",
"study_setup_config_versions",
type_="unique",
)
if not _has_unique_constraint(inspector, "study_setup_config_versions", "uq_setup_config_versions_study_display_version"):
op.create_unique_constraint(
"uq_setup_config_versions_study_display_version",
"study_setup_config_versions",
["study_id", "display_version"],
)
if _has_foreign_key(inspector, "study_setup_config_versions", "fk_setup_config_versions_merged_from_version"):
op.drop_constraint(
"fk_setup_config_versions_merged_from_version",
"study_setup_config_versions",
type_="foreignkey",
)
if _has_foreign_key(inspector, "study_setup_config_versions", "fk_setup_config_versions_parent_version"):
op.drop_constraint(
"fk_setup_config_versions_parent_version",
"study_setup_config_versions",
type_="foreignkey",
)
if _has_column(inspector, "study_setup_config_versions", "merged_from_version_id"):
op.drop_column("study_setup_config_versions", "merged_from_version_id")
if _has_column(inspector, "study_setup_config_versions", "parent_version_id"):
op.drop_column("study_setup_config_versions", "parent_version_id")
if _has_column(inspector, "study_setup_config_versions", "version_label"):
op.drop_column("study_setup_config_versions", "version_label")
if _has_column(inspector, "study_setup_config_versions", "branch_seq"):
op.drop_column("study_setup_config_versions", "branch_seq")
if _has_column(inspector, "study_setup_config_versions", "branch_name"):
op.drop_column("study_setup_config_versions", "branch_name")