From 63bae494ad906028b5afd9cd7375bce95aea6030 Mon Sep 17 00:00:00 2001 From: Luke Mino-Altherr Date: Sat, 14 Mar 2026 23:30:15 -0400 Subject: [PATCH] Fix migration constraint naming double-prefix and NULL in mixed metadata lists - Use fully-rendered constraint names in migration 0003 to avoid the naming convention doubling the ck_ prefix on batch operations. - Add table_args to downgrade so SQLite batch mode can find the CHECK constraint (not exposed by SQLite reflection). - Fix model CheckConstraint name to use bare 'has_value' (convention auto-prefixes). - Skip None items when converting metadata lists to rows, preventing all-NULL rows that violate the has_value check constraint. Amp-Thread-ID: https://ampcode.com/threads/T-019cef87-94f9-7172-a6af-c6282290ce4f Co-authored-by: Amp --- alembic_db/versions/0003_add_metadata_job_id.py | 16 ++++++++++++---- app/assets/database/models.py | 2 +- app/assets/database/queries/asset_reference.py | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/alembic_db/versions/0003_add_metadata_job_id.py b/alembic_db/versions/0003_add_metadata_job_id.py index 1d75e836e..2a14ee924 100644 --- a/alembic_db/versions/0003_add_metadata_job_id.py +++ b/alembic_db/versions/0003_add_metadata_job_id.py @@ -53,9 +53,7 @@ def upgrade() -> None: "DELETE FROM asset_reference_meta" " WHERE val_str IS NULL AND val_num IS NULL AND val_bool IS NULL AND val_json IS NULL" ) - with op.batch_alter_table( - "asset_reference_meta", naming_convention=NAMING_CONVENTION - ) as batch_op: + with op.batch_alter_table("asset_reference_meta") as batch_op: batch_op.create_check_constraint( "ck_asset_reference_meta_has_value", "val_str IS NOT NULL OR val_num IS NOT NULL OR val_bool IS NOT NULL OR val_json IS NOT NULL", @@ -63,8 +61,18 @@ def upgrade() -> None: def downgrade() -> None: + # SQLite doesn't reflect CHECK constraints, so we must declare it + # explicitly via table_args for the batch recreate to find it. + # Use the fully-rendered constraint name to avoid the naming convention + # doubling the prefix. with op.batch_alter_table( - "asset_reference_meta", naming_convention=NAMING_CONVENTION + "asset_reference_meta", + table_args=[ + sa.CheckConstraint( + "val_str IS NOT NULL OR val_num IS NOT NULL OR val_bool IS NOT NULL OR val_json IS NOT NULL", + name="ck_asset_reference_meta_has_value", + ), + ], ) as batch_op: batch_op.drop_constraint( "ck_asset_reference_meta_has_value", type_="check" diff --git a/app/assets/database/models.py b/app/assets/database/models.py index 5c7ff8154..a3af8a192 100644 --- a/app/assets/database/models.py +++ b/app/assets/database/models.py @@ -193,7 +193,7 @@ class AssetReferenceMeta(Base): Index("ix_asset_reference_meta_key_val_bool", "key", "val_bool"), CheckConstraint( "val_str IS NOT NULL OR val_num IS NOT NULL OR val_bool IS NOT NULL OR val_json IS NOT NULL", - name="ck_asset_reference_meta_has_value", + name="has_value", ), ) diff --git a/app/assets/database/queries/asset_reference.py b/app/assets/database/queries/asset_reference.py index 7af552483..084a32512 100644 --- a/app/assets/database/queries/asset_reference.py +++ b/app/assets/database/queries/asset_reference.py @@ -66,8 +66,8 @@ def convert_metadata_to_rows(key: str, value) -> list[dict]: if isinstance(value, list): if all(_check_is_scalar(x) for x in value): - return [_scalar_to_row(key, i, x) for i, x in enumerate(value)] - return [{"key": key, "ordinal": i, "val_json": x} for i, x in enumerate(value)] + return [_scalar_to_row(key, i, x) for i, x in enumerate(value) if x is not None] + return [{"key": key, "ordinal": i, "val_json": x} for i, x in enumerate(value) if x is not None] return [{"key": key, "ordinal": 0, "val_json": value}]