diff --git a/migrations/versions/47a76cd261fd_scripts_unique_index_include_deleted_at.py b/migrations/versions/47a76cd261fd_scripts_unique_index_include_deleted_at.py new file mode 100644 index 0000000..eca75b6 --- /dev/null +++ b/migrations/versions/47a76cd261fd_scripts_unique_index_include_deleted_at.py @@ -0,0 +1,54 @@ +"""Make the scripts workspace/name/type unique index soft-delete aware. + +The old unique index ``uk_scripts_workspace_name`` on +``(workspace_id, script_name, script_type)`` blocked re-uploading a +script after it had been soft-deleted, because the deleted row was still +part of the index. + +Replace it with ``uk_scripts_workspace_name_active`` on +``(workspace_id, script_name, script_type, deleted_at)``. In MySQL a +unique index treats ``NULL`` values as distinct, so a new active row +(``deleted_at IS NULL``) no longer conflicts with a previously deleted +row (``deleted_at IS NOT NULL``), while two active rows with the same +name still conflict as expected. + +Revision ID: 47a76cd261fd +Revises: 3ba4d8489f36 +Create Date: 2026-08-14 +""" + +from collections.abc import Sequence + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "47a76cd261fd" +down_revision: str | Sequence[str] | None = "3ba4d8489f36" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + op.drop_index( + "uk_scripts_workspace_name", + table_name="scripts", + ) + op.create_index( + "uk_scripts_workspace_name_active", + "scripts", + ["workspace_id", "script_name", "script_type", "deleted_at"], + unique=True, + ) + + +def downgrade() -> None: + op.drop_index( + "uk_scripts_workspace_name_active", + table_name="scripts", + ) + op.create_index( + "uk_scripts_workspace_name", + "scripts", + ["workspace_id", "script_name", "script_type"], + unique=True, + ) diff --git a/migrations/versions/a1b2c3d4e5f6_drop_scripts_data_resources_unique_indexes.py b/migrations/versions/a1b2c3d4e5f6_drop_scripts_data_resources_unique_indexes.py new file mode 100644 index 0000000..5f882a1 --- /dev/null +++ b/migrations/versions/a1b2c3d4e5f6_drop_scripts_data_resources_unique_indexes.py @@ -0,0 +1,63 @@ +"""Drop unique indexes on Scripts and DataResources. + +StorageObjects now carries the physical uniqueness guarantees: + - ``uk_storage_bucket_key`` + - ``uk_storage_workspace_path`` + +Scripts and DataResources therefore no longer need their own unique +indexes on ``current_object_id`` / ``storage_object_id``, and the +soft-delete-aware ``uk_scripts_workspace_name_active`` index is also +removed. Re-uploading a previously soft-deleted script or resource is +allowed because the StorageObjects layer enforces path uniqueness only +for active objects. + +Revision ID: a1b2c3d4e5f6 +Revises: 47a76cd261fd +Create Date: 2026-08-14 +""" + +from collections.abc import Sequence + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "a1b2c3d4e5f6" +down_revision: str | Sequence[str] | None = "47a76cd261fd" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + op.drop_index( + "uk_scripts_workspace_name_active", + table_name="scripts", + ) + op.drop_index( + "uk_scripts_current_object", + table_name="scripts", + ) + op.drop_index( + "uk_data_resources_object", + table_name="data_resources", + ) + + +def downgrade() -> None: + op.create_index( + "uk_scripts_workspace_name_active", + "scripts", + ["workspace_id", "script_name", "script_type", "deleted_at"], + unique=True, + ) + op.create_index( + "uk_scripts_current_object", + "scripts", + ["current_object_id"], + unique=True, + ) + op.create_index( + "uk_data_resources_object", + "data_resources", + ["storage_object_id"], + unique=True, + )