"""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")