From 0be5cba055b215be2557fb72ff65e72aaef7988b Mon Sep 17 00:00:00 2001 From: Luke Mino-Altherr Date: Wed, 11 Feb 2026 20:13:09 -0800 Subject: [PATCH] refactor(migrations): merge migrations 0002-0005 into single migration Consolidate all branch migrations into 0002_merge_to_asset_references. Drops old tables (asset_info_meta, asset_info_tags, asset_cache_state, assets_info), truncates assets, and creates the new unified schema (asset_references, asset_reference_tags, asset_reference_meta). Amp-Thread-ID: https://ampcode.com/threads/T-019c5005-66e9-719f-94df-ebb4b25bcf14 Co-authored-by: Amp --- .../0002_add_is_missing_to_cache_state.py | 37 -- .../0002_merge_to_asset_references.py | 177 ++++++++ .../versions/0003_add_enrichment_level.py | 44 -- .../0004_drop_asset_info_unique_constraint.py | 32 -- .../0005_merge_to_asset_references.py | 422 ------------------ 5 files changed, 177 insertions(+), 535 deletions(-) delete mode 100644 alembic_db/versions/0002_add_is_missing_to_cache_state.py create mode 100644 alembic_db/versions/0002_merge_to_asset_references.py delete mode 100644 alembic_db/versions/0003_add_enrichment_level.py delete mode 100644 alembic_db/versions/0004_drop_asset_info_unique_constraint.py delete mode 100644 alembic_db/versions/0005_merge_to_asset_references.py diff --git a/alembic_db/versions/0002_add_is_missing_to_cache_state.py b/alembic_db/versions/0002_add_is_missing_to_cache_state.py deleted file mode 100644 index 04377bebc..000000000 --- a/alembic_db/versions/0002_add_is_missing_to_cache_state.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Add is_missing column to asset_cache_state for non-destructive soft-delete - -Revision ID: 0002_add_is_missing -Revises: 0001_assets -Create Date: 2025-02-05 00:00:00 -""" - -from alembic import op -import sqlalchemy as sa - -revision = "0002_add_is_missing" -down_revision = "0001_assets" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - op.add_column( - "asset_cache_state", - sa.Column( - "is_missing", - sa.Boolean(), - nullable=False, - server_default=sa.text("false"), - ), - ) - op.create_index( - "ix_asset_cache_state_is_missing", - "asset_cache_state", - ["is_missing"], - ) - - -def downgrade() -> None: - op.drop_index("ix_asset_cache_state_is_missing", table_name="asset_cache_state") - op.drop_column("asset_cache_state", "is_missing") diff --git a/alembic_db/versions/0002_merge_to_asset_references.py b/alembic_db/versions/0002_merge_to_asset_references.py new file mode 100644 index 000000000..dbf0f9f7f --- /dev/null +++ b/alembic_db/versions/0002_merge_to_asset_references.py @@ -0,0 +1,177 @@ +""" +Merge AssetInfo and AssetCacheState into unified asset_references table. + +This migration drops old tables and creates the new unified schema. +All existing data is discarded. + +Revision ID: 0002_merge_to_asset_references +Revises: 0001_assets +Create Date: 2025-02-11 +""" + +from alembic import op +import sqlalchemy as sa + +revision = "0002_merge_to_asset_references" +down_revision = "0001_assets" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Drop old tables (order matters due to FK constraints) + op.drop_index("ix_asset_info_meta_key_val_bool", table_name="asset_info_meta") + op.drop_index("ix_asset_info_meta_key_val_num", table_name="asset_info_meta") + op.drop_index("ix_asset_info_meta_key_val_str", table_name="asset_info_meta") + op.drop_index("ix_asset_info_meta_key", table_name="asset_info_meta") + op.drop_table("asset_info_meta") + + op.drop_index("ix_asset_info_tags_asset_info_id", table_name="asset_info_tags") + op.drop_index("ix_asset_info_tags_tag_name", table_name="asset_info_tags") + op.drop_table("asset_info_tags") + + op.drop_index("ix_asset_cache_state_asset_id", table_name="asset_cache_state") + op.drop_index("ix_asset_cache_state_file_path", table_name="asset_cache_state") + op.drop_table("asset_cache_state") + + op.drop_index("ix_assets_info_owner_name", table_name="assets_info") + op.drop_index("ix_assets_info_last_access_time", table_name="assets_info") + op.drop_index("ix_assets_info_created_at", table_name="assets_info") + op.drop_index("ix_assets_info_name", table_name="assets_info") + op.drop_index("ix_assets_info_asset_id", table_name="assets_info") + op.drop_index("ix_assets_info_owner_id", table_name="assets_info") + op.drop_table("assets_info") + + # Truncate assets table (cascades handled by dropping dependent tables first) + op.execute("DELETE FROM assets") + + # Create asset_references table + op.create_table( + "asset_references", + sa.Column("id", sa.String(length=36), primary_key=True), + sa.Column( + "asset_id", + sa.String(length=36), + sa.ForeignKey("assets.id", ondelete="CASCADE"), + nullable=False, + ), + sa.Column("file_path", sa.Text(), nullable=True), + sa.Column("mtime_ns", sa.BigInteger(), nullable=True), + sa.Column( + "needs_verify", + sa.Boolean(), + nullable=False, + server_default=sa.text("false"), + ), + sa.Column( + "is_missing", sa.Boolean(), nullable=False, server_default=sa.text("false") + ), + sa.Column("enrichment_level", sa.Integer(), nullable=False, server_default="0"), + sa.Column("owner_id", sa.String(length=128), nullable=False, server_default=""), + sa.Column("name", sa.String(length=512), nullable=False), + sa.Column( + "preview_id", + sa.String(length=36), + sa.ForeignKey("assets.id", ondelete="SET NULL"), + nullable=True, + ), + sa.Column("user_metadata", sa.JSON(), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=False), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=False), nullable=False), + sa.Column("last_access_time", sa.DateTime(timezone=False), nullable=False), + sa.CheckConstraint( + "(mtime_ns IS NULL) OR (mtime_ns >= 0)", name="ck_ar_mtime_nonneg" + ), + sa.CheckConstraint( + "enrichment_level >= 0 AND enrichment_level <= 2", + name="ck_ar_enrichment_level_range", + ), + ) + op.create_index( + "uq_asset_references_file_path", "asset_references", ["file_path"], unique=True + ) + op.create_index("ix_asset_references_asset_id", "asset_references", ["asset_id"]) + op.create_index("ix_asset_references_owner_id", "asset_references", ["owner_id"]) + op.create_index("ix_asset_references_name", "asset_references", ["name"]) + op.create_index("ix_asset_references_is_missing", "asset_references", ["is_missing"]) + op.create_index( + "ix_asset_references_enrichment_level", "asset_references", ["enrichment_level"] + ) + op.create_index("ix_asset_references_created_at", "asset_references", ["created_at"]) + op.create_index( + "ix_asset_references_last_access_time", "asset_references", ["last_access_time"] + ) + op.create_index( + "ix_asset_references_owner_name", "asset_references", ["owner_id", "name"] + ) + + # Create asset_reference_tags table + op.create_table( + "asset_reference_tags", + sa.Column( + "asset_reference_id", + sa.String(length=36), + sa.ForeignKey("asset_references.id", ondelete="CASCADE"), + nullable=False, + ), + sa.Column( + "tag_name", + sa.String(length=512), + sa.ForeignKey("tags.name", ondelete="RESTRICT"), + nullable=False, + ), + sa.Column( + "origin", sa.String(length=32), nullable=False, server_default="manual" + ), + sa.Column("added_at", sa.DateTime(timezone=False), nullable=False), + sa.PrimaryKeyConstraint( + "asset_reference_id", "tag_name", name="pk_asset_reference_tags" + ), + ) + op.create_index( + "ix_asset_reference_tags_tag_name", "asset_reference_tags", ["tag_name"] + ) + op.create_index( + "ix_asset_reference_tags_asset_reference_id", + "asset_reference_tags", + ["asset_reference_id"], + ) + + # Create asset_reference_meta table + op.create_table( + "asset_reference_meta", + sa.Column( + "asset_reference_id", + sa.String(length=36), + sa.ForeignKey("asset_references.id", ondelete="CASCADE"), + nullable=False, + ), + sa.Column("key", sa.String(length=256), nullable=False), + sa.Column("ordinal", sa.Integer(), nullable=False, server_default="0"), + sa.Column("val_str", sa.String(length=2048), nullable=True), + sa.Column("val_num", sa.Numeric(38, 10), nullable=True), + sa.Column("val_bool", sa.Boolean(), nullable=True), + sa.Column("val_json", sa.JSON(), nullable=True), + sa.PrimaryKeyConstraint( + "asset_reference_id", "key", "ordinal", name="pk_asset_reference_meta" + ), + ) + op.create_index("ix_asset_reference_meta_key", "asset_reference_meta", ["key"]) + op.create_index( + "ix_asset_reference_meta_key_val_str", "asset_reference_meta", ["key", "val_str"] + ) + op.create_index( + "ix_asset_reference_meta_key_val_num", "asset_reference_meta", ["key", "val_num"] + ) + op.create_index( + "ix_asset_reference_meta_key_val_bool", + "asset_reference_meta", + ["key", "val_bool"], + ) + + +def downgrade() -> None: + raise NotImplementedError( + "Downgrade from 0002_merge_to_asset_references is not supported. " + "Please restore from backup if needed." + ) diff --git a/alembic_db/versions/0003_add_enrichment_level.py b/alembic_db/versions/0003_add_enrichment_level.py deleted file mode 100644 index c9a0ee212..000000000 --- a/alembic_db/versions/0003_add_enrichment_level.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Add enrichment_level column to asset_cache_state for phased scanning - -Level 0: Stub record (path, size, mtime only) -Level 1: Metadata extracted (safetensors header, mime type) -Level 2: Hash computed (blake3) - -Revision ID: 0003_add_enrichment_level -Revises: 0002_add_is_missing -Create Date: 2025-02-10 00:00:00 -""" - -from alembic import op -import sqlalchemy as sa - -revision = "0003_add_enrichment_level" -down_revision = "0002_add_is_missing" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - op.add_column( - "asset_cache_state", - sa.Column( - "enrichment_level", - sa.Integer(), - nullable=False, - server_default=sa.text("0"), - ), - ) - op.create_index( - "ix_asset_cache_state_enrichment_level", - "asset_cache_state", - ["enrichment_level"], - ) - # Treat existing records as fully enriched (level 1 = metadata done) - # since they were created with the old scanner that extracted metadata - op.execute("UPDATE asset_cache_state SET enrichment_level = 1") - - -def downgrade() -> None: - op.drop_index("ix_asset_cache_state_enrichment_level", table_name="asset_cache_state") - op.drop_column("asset_cache_state", "enrichment_level") diff --git a/alembic_db/versions/0004_drop_asset_info_unique_constraint.py b/alembic_db/versions/0004_drop_asset_info_unique_constraint.py deleted file mode 100644 index f8eb337f4..000000000 --- a/alembic_db/versions/0004_drop_asset_info_unique_constraint.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Drop unique constraint on assets_info (asset_id, owner_id, name) - -Allow multiple files with the same name to reference the same asset. - -Revision ID: 0004_drop_asset_info_unique -Revises: 0003_add_enrichment_level -Create Date: 2025-02-11 00:00:00 -""" - -from alembic import op -import sqlalchemy as sa - -revision = "0004_drop_asset_info_unique" -down_revision = "0003_add_enrichment_level" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - with op.batch_alter_table("assets_info") as batch_op: - batch_op.drop_constraint( - "uq_assets_info_asset_owner_name", type_="unique" - ) - - -def downgrade() -> None: - with op.batch_alter_table("assets_info") as batch_op: - batch_op.create_unique_constraint( - "uq_assets_info_asset_owner_name", - ["asset_id", "owner_id", "name"], - ) diff --git a/alembic_db/versions/0005_merge_to_asset_references.py b/alembic_db/versions/0005_merge_to_asset_references.py deleted file mode 100644 index 02deb2cd5..000000000 --- a/alembic_db/versions/0005_merge_to_asset_references.py +++ /dev/null @@ -1,422 +0,0 @@ -""" -Merge AssetInfo and AssetCacheState into unified asset_references table. - -This migration: -1. Creates asset_references table with combined columns -2. Creates asset_reference_tags and asset_reference_meta tables -3. Migrates data from assets_info and asset_cache_state, merging where unambiguous -4. Migrates tags and metadata -5. Drops old tables - -Revision ID: 0005_merge_to_asset_references -Revises: 0004_drop_asset_info_unique_constraint -Create Date: 2025-02-11 -""" -# ruff: noqa: E501 - -import os -import uuid -from datetime import datetime - -from alembic import op -import sqlalchemy as sa -from sqlalchemy import text - -revision = "0005_merge_to_asset_references" -down_revision = "0004_drop_asset_info_unique_constraint" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - conn = op.get_bind() - - # Step 1: Create asset_references table - op.create_table( - "asset_references", - sa.Column("id", sa.String(length=36), primary_key=True), - sa.Column( - "asset_id", - sa.String(length=36), - sa.ForeignKey("assets.id", ondelete="CASCADE"), - nullable=False, - ), - # From AssetCacheState - sa.Column("file_path", sa.Text(), nullable=True), - sa.Column("mtime_ns", sa.BigInteger(), nullable=True), - sa.Column( - "needs_verify", - sa.Boolean(), - nullable=False, - server_default=sa.text("false"), - ), - sa.Column( - "is_missing", sa.Boolean(), nullable=False, server_default=sa.text("false") - ), - sa.Column("enrichment_level", sa.Integer(), nullable=False, server_default="0"), - # From AssetInfo - sa.Column("owner_id", sa.String(length=128), nullable=False, server_default=""), - sa.Column("name", sa.String(length=512), nullable=False), - sa.Column( - "preview_id", - sa.String(length=36), - sa.ForeignKey("assets.id", ondelete="SET NULL"), - nullable=True, - ), - sa.Column("user_metadata", sa.JSON(), nullable=True), - sa.Column("created_at", sa.DateTime(timezone=False), nullable=False), - sa.Column("updated_at", sa.DateTime(timezone=False), nullable=False), - sa.Column("last_access_time", sa.DateTime(timezone=False), nullable=False), - # Constraints - sa.CheckConstraint( - "(mtime_ns IS NULL) OR (mtime_ns >= 0)", name="ck_ar_mtime_nonneg" - ), - sa.CheckConstraint( - "enrichment_level >= 0 AND enrichment_level <= 2", - name="ck_ar_enrichment_level_range", - ), - ) - - # Create unique index on file_path where not null (partial unique). - # SQLite UNIQUE on nullable columns works as expected. - op.create_index( - "uq_asset_references_file_path", - "asset_references", - ["file_path"], - unique=True, - ) - op.create_index("ix_asset_references_asset_id", "asset_references", ["asset_id"]) - op.create_index("ix_asset_references_owner_id", "asset_references", ["owner_id"]) - op.create_index("ix_asset_references_name", "asset_references", ["name"]) - op.create_index( - "ix_asset_references_is_missing", "asset_references", ["is_missing"] - ) - op.create_index( - "ix_asset_references_enrichment_level", "asset_references", ["enrichment_level"] - ) - op.create_index( - "ix_asset_references_created_at", "asset_references", ["created_at"] - ) - op.create_index( - "ix_asset_references_last_access_time", "asset_references", ["last_access_time"] - ) - op.create_index( - "ix_asset_references_owner_name", "asset_references", ["owner_id", "name"] - ) - - # Step 2: Create asset_reference_tags table - op.create_table( - "asset_reference_tags", - sa.Column( - "asset_reference_id", - sa.String(length=36), - sa.ForeignKey("asset_references.id", ondelete="CASCADE"), - nullable=False, - ), - sa.Column( - "tag_name", - sa.String(length=512), - sa.ForeignKey("tags.name", ondelete="RESTRICT"), - nullable=False, - ), - sa.Column( - "origin", sa.String(length=32), nullable=False, server_default="manual" - ), - sa.Column("added_at", sa.DateTime(timezone=False), nullable=False), - sa.PrimaryKeyConstraint( - "asset_reference_id", "tag_name", name="pk_asset_reference_tags" - ), - ) - op.create_index( - "ix_asset_reference_tags_tag_name", "asset_reference_tags", ["tag_name"] - ) - op.create_index( - "ix_asset_reference_tags_asset_reference_id", - "asset_reference_tags", - ["asset_reference_id"], - ) - - # Step 3: Create asset_reference_meta table - op.create_table( - "asset_reference_meta", - sa.Column( - "asset_reference_id", - sa.String(length=36), - sa.ForeignKey("asset_references.id", ondelete="CASCADE"), - nullable=False, - ), - sa.Column("key", sa.String(length=256), nullable=False), - sa.Column("ordinal", sa.Integer(), nullable=False, server_default="0"), - sa.Column("val_str", sa.String(length=2048), nullable=True), - sa.Column("val_num", sa.Numeric(38, 10), nullable=True), - sa.Column("val_bool", sa.Boolean(), nullable=True), - sa.Column("val_json", sa.JSON(), nullable=True), - sa.PrimaryKeyConstraint( - "asset_reference_id", "key", "ordinal", name="pk_asset_reference_meta" - ), - ) - op.create_index("ix_asset_reference_meta_key", "asset_reference_meta", ["key"]) - op.create_index( - "ix_asset_reference_meta_key_val_str", - "asset_reference_meta", - ["key", "val_str"], - ) - op.create_index( - "ix_asset_reference_meta_key_val_num", - "asset_reference_meta", - ["key", "val_num"], - ) - op.create_index( - "ix_asset_reference_meta_key_val_bool", - "asset_reference_meta", - ["key", "val_bool"], - ) - - # Step 4: Migrate data - # Create mapping from cache_state to info that should absorb it. - # Merge when: same asset_id AND exactly one cache_state AND basename == name - now = datetime.utcnow().isoformat() - - # Find unambiguous matches: assets_info rows that have exactly one matching cache_state - # where basename(file_path) == name AND same asset_id - # We'll do this in Python for clarity and SQLite compatibility - - # Get all assets_info rows - info_rows = conn.execute( - text(""" - SELECT id, owner_id, name, asset_id, preview_id, user_metadata, - created_at, updated_at, last_access_time - FROM assets_info - """) - ).fetchall() - - # Get all asset_cache_state rows - cache_rows = conn.execute( - text(""" - SELECT id, asset_id, file_path, mtime_ns, needs_verify, is_missing, enrichment_level - FROM asset_cache_state - """) - ).fetchall() - - # Build mapping: asset_id -> list of cache_state rows - cache_by_asset: dict = {} - for row in cache_rows: - ( - cache_id, - asset_id, - file_path, - mtime_ns, - needs_verify, - is_missing, - enrichment_level, - ) = row - if asset_id not in cache_by_asset: - cache_by_asset[asset_id] = [] - cache_by_asset[asset_id].append( - { - "cache_id": cache_id, - "file_path": file_path, - "mtime_ns": mtime_ns, - "needs_verify": needs_verify, - "is_missing": is_missing, - "enrichment_level": enrichment_level, - } - ) - - # Track which cache_states get merged (so we don't insert them separately) - merged_cache_ids: set = set() - # Track info_id -> cache_data for merged rows - info_to_cache: dict = {} - - for info_row in info_rows: - ( - info_id, - owner_id, - name, - asset_id, - preview_id, - user_metadata, - created_at, - updated_at, - last_access, - ) = info_row - caches = cache_by_asset.get(asset_id, []) - - # Only merge if exactly one cache_state AND basename matches - if len(caches) == 1: - cache = caches[0] - basename = os.path.basename(cache["file_path"]) - if basename == name: - merged_cache_ids.add(cache["cache_id"]) - info_to_cache[info_id] = cache - - # Insert merged and non-merged assets_info rows into asset_references - for info_row in info_rows: - ( - info_id, - owner_id, - name, - asset_id, - preview_id, - user_metadata, - created_at, - updated_at, - last_access, - ) = info_row - - cache = info_to_cache.get(info_id) - if cache: - # Merged row: has file_path and cache data - conn.execute( - text(""" - INSERT INTO asset_references ( - id, asset_id, file_path, mtime_ns, needs_verify, is_missing, - enrichment_level, owner_id, name, preview_id, user_metadata, - created_at, updated_at, last_access_time - ) VALUES ( - :id, :asset_id, :file_path, :mtime_ns, :needs_verify, :is_missing, - :enrichment_level, :owner_id, :name, :preview_id, :user_metadata, - :created_at, :updated_at, :last_access_time - ) - """), - { - "id": info_id, - "asset_id": asset_id, - "file_path": cache["file_path"], - "mtime_ns": cache["mtime_ns"], - "needs_verify": cache["needs_verify"], - "is_missing": cache["is_missing"], - "enrichment_level": cache["enrichment_level"], - "owner_id": owner_id or "", - "name": name, - "preview_id": preview_id, - "user_metadata": user_metadata, - "created_at": created_at, - "updated_at": updated_at, - "last_access_time": last_access, - }, - ) - else: - # Non-merged row: no file_path - conn.execute( - text(""" - INSERT INTO asset_references ( - id, asset_id, file_path, mtime_ns, needs_verify, is_missing, - enrichment_level, owner_id, name, preview_id, user_metadata, - created_at, updated_at, last_access_time - ) VALUES ( - :id, :asset_id, NULL, NULL, false, false, 0, - :owner_id, :name, :preview_id, :user_metadata, - :created_at, :updated_at, :last_access_time - ) - """), - { - "id": info_id, - "asset_id": asset_id, - "owner_id": owner_id or "", - "name": name, - "preview_id": preview_id, - "user_metadata": user_metadata, - "created_at": created_at, - "updated_at": updated_at, - "last_access_time": last_access, - }, - ) - - # Insert remaining (non-merged) cache_state rows as new asset_references - for cache_row in cache_rows: - ( - cache_id, - asset_id, - file_path, - mtime_ns, - needs_verify, - is_missing, - enrichment_level, - ) = cache_row - if cache_id in merged_cache_ids: - continue - - new_id = str(uuid.uuid4()) - basename = os.path.basename(file_path) if file_path else "unknown" - - conn.execute( - text(""" - INSERT INTO asset_references ( - id, asset_id, file_path, mtime_ns, needs_verify, is_missing, - enrichment_level, owner_id, name, preview_id, user_metadata, - created_at, updated_at, last_access_time - ) VALUES ( - :id, :asset_id, :file_path, :mtime_ns, :needs_verify, :is_missing, - :enrichment_level, '', :name, NULL, NULL, - :now, :now, :now - ) - """), - { - "id": new_id, - "asset_id": asset_id, - "file_path": file_path, - "mtime_ns": mtime_ns, - "needs_verify": needs_verify, - "is_missing": is_missing, - "enrichment_level": enrichment_level, - "name": basename, - "now": now, - }, - ) - - # Step 5: Migrate tags (asset_info_id maps directly to asset_reference_id since we reused IDs) - conn.execute( - text(""" - INSERT INTO asset_reference_tags (asset_reference_id, tag_name, origin, added_at) - SELECT asset_info_id, tag_name, origin, added_at - FROM asset_info_tags - WHERE asset_info_id IN (SELECT id FROM asset_references) - """) - ) - - # Step 6: Migrate metadata - conn.execute( - text(""" - INSERT INTO asset_reference_meta (asset_reference_id, key, ordinal, val_str, val_num, val_bool, val_json) - SELECT asset_info_id, key, ordinal, val_str, val_num, val_bool, val_json - FROM asset_info_meta - WHERE asset_info_id IN (SELECT id FROM asset_references) - """) - ) - - # Step 7: Drop old tables - op.drop_index("ix_asset_info_meta_key_val_bool", table_name="asset_info_meta") - op.drop_index("ix_asset_info_meta_key_val_num", table_name="asset_info_meta") - op.drop_index("ix_asset_info_meta_key_val_str", table_name="asset_info_meta") - op.drop_index("ix_asset_info_meta_key", table_name="asset_info_meta") - op.drop_table("asset_info_meta") - - op.drop_index("ix_asset_info_tags_asset_info_id", table_name="asset_info_tags") - op.drop_index("ix_asset_info_tags_tag_name", table_name="asset_info_tags") - op.drop_table("asset_info_tags") - - op.drop_index("ix_asset_cache_state_asset_id", table_name="asset_cache_state") - op.drop_index("ix_asset_cache_state_file_path", table_name="asset_cache_state") - op.drop_index("ix_asset_cache_state_is_missing", table_name="asset_cache_state") - op.drop_index( - "ix_asset_cache_state_enrichment_level", table_name="asset_cache_state" - ) - op.drop_table("asset_cache_state") - - op.drop_index("ix_assets_info_owner_name", table_name="assets_info") - op.drop_index("ix_assets_info_last_access_time", table_name="assets_info") - op.drop_index("ix_assets_info_created_at", table_name="assets_info") - op.drop_index("ix_assets_info_name", table_name="assets_info") - op.drop_index("ix_assets_info_asset_id", table_name="assets_info") - op.drop_index("ix_assets_info_owner_id", table_name="assets_info") - op.drop_table("assets_info") - - -def downgrade() -> None: - # This is a complex migration - downgrade would require careful data splitting - # For safety, we don't support automatic downgrade - raise NotImplementedError( - "Downgrade from 0005_merge_to_asset_references is not supported. " - "Please restore from backup if needed." - )