From 66777fa1747a7065f4cd1131bbcfab3cde22a317 Mon Sep 17 00:00:00 2001 From: souplearn Date: Sat, 25 Jul 2026 02:50:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=A0=A1=E9=AA=8C=E6=8A=A5=E5=B7=A5?= =?UTF-8?q?=E5=BD=92=E5=B1=9E=E7=B4=A2=E5=BC=95=E5=94=AF=E4=B8=80=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/migrate_report_allocations.py | 78 +++++++++++++++++++++------ 1 file changed, 61 insertions(+), 17 deletions(-) diff --git a/scripts/migrate_report_allocations.py b/scripts/migrate_report_allocations.py index b51275e..74e20fc 100644 --- a/scripts/migrate_report_allocations.py +++ b/scripts/migrate_report_allocations.py @@ -55,6 +55,7 @@ REQUIRED_FOREIGN_KEYS = { "report_id": ("production_reports", "id", "CASCADE"), "report_item_id": ("production_report_items", "id", "CASCADE"), } +IndexMetadata = dict[str, tuple[str, ...] | int] def quote_identifier(identifier: str) -> str: @@ -102,11 +103,11 @@ def existing_columns(conn) -> dict[str, dict[str, str | None]]: } -def existing_indexes(conn) -> dict[str, tuple[str, ...]]: +def existing_indexes(conn) -> dict[str, IndexMetadata]: rows = conn.execute( text( """ - SELECT index_name, column_name + SELECT index_name, non_unique, column_name FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = :table_name @@ -115,10 +116,17 @@ def existing_indexes(conn) -> dict[str, tuple[str, ...]]: ), {"table_name": TABLE_NAME}, ).all() - indexes: dict[str, list[str]] = {} - for index_name, column_name in rows: - indexes.setdefault(index_name, []).append(column_name) - return {index_name: tuple(columns) for index_name, columns in indexes.items()} + indexes: dict[str, dict[str, object]] = {} + for index_name, non_unique, column_name in rows: + metadata = indexes.setdefault(index_name, {"columns": [], "non_unique": int(non_unique)}) + metadata["columns"].append(column_name) + return { + index_name: { + "columns": tuple(metadata["columns"]), + "non_unique": metadata["non_unique"], + } + for index_name, metadata in indexes.items() + } def existing_foreign_keys(conn) -> dict[str, tuple[str, str, str]]: @@ -141,9 +149,9 @@ def existing_foreign_keys(conn) -> dict[str, tuple[str, str, str]]: return {row[0]: (row[1], row[2], row[3]) for row in rows} -def find_equivalent_index(indexes: dict[str, tuple[str, ...]], columns: tuple[str, ...]) -> str | None: - for index_name, index_columns in indexes.items(): - if index_name != "PRIMARY" and index_columns == columns: +def find_equivalent_index(indexes: dict[str, IndexMetadata], columns: tuple[str, ...]) -> str | None: + for index_name, metadata in indexes.items(): + if index_name != "PRIMARY" and metadata["columns"] == columns and metadata["non_unique"] == 1: return index_name return None @@ -179,10 +187,16 @@ def cleanup_duplicate_fk_indexes(conn) -> None: indexes = existing_indexes(conn) duplicates = [ index_name - for index_name, columns in indexes.items() + for index_name, metadata in indexes.items() if ( - (columns == ("report_id",) and index_name != "idx_report_allocations_report") - or (columns == ("report_item_id",) and index_name != "idx_report_allocations_item") + metadata["non_unique"] == 1 + and ( + (metadata["columns"] == ("report_id",) and index_name != "idx_report_allocations_report") + or ( + metadata["columns"] == ("report_item_id",) + and index_name != "idx_report_allocations_item" + ) + ) ) ] for index_name in duplicates: @@ -300,19 +314,33 @@ def verify_schema(conn) -> None: raise RuntimeError(f"{TABLE_NAME} missing indexes: {', '.join(missing_indexes)}") wrong_indexes = [ - f"{index_name}({', '.join(indexes[index_name])})" + f"{index_name}({', '.join(indexes[index_name]['columns'])})" for index_name, columns in REQUIRED_INDEXES.items() - if indexes.get(index_name) != columns + if indexes.get(index_name, {}).get("columns") != columns ] if wrong_indexes: raise RuntimeError(f"{TABLE_NAME} incompatible indexes: {', '.join(wrong_indexes)}") + unique_required_indexes = [ + index_name + for index_name in REQUIRED_INDEXES + if indexes[index_name]["non_unique"] != 1 + ] + if unique_required_indexes: + raise RuntimeError(f"{TABLE_NAME} required indexes are unique: {', '.join(unique_required_indexes)}") + duplicate_single_column_indexes = [ index_name - for index_name, columns in indexes.items() + for index_name, metadata in indexes.items() if ( - (columns == ("report_id",) and index_name != "idx_report_allocations_report") - or (columns == ("report_item_id",) and index_name != "idx_report_allocations_item") + metadata["non_unique"] == 1 + and ( + (metadata["columns"] == ("report_id",) and index_name != "idx_report_allocations_report") + or ( + metadata["columns"] == ("report_item_id",) + and index_name != "idx_report_allocations_item" + ) + ) ) ] if duplicate_single_column_indexes: @@ -320,6 +348,22 @@ def verify_schema(conn) -> None: f"{TABLE_NAME} duplicate FK indexes: {', '.join(sorted(duplicate_single_column_indexes))}" ) + unique_single_column_indexes = [ + index_name + for index_name, metadata in indexes.items() + if ( + metadata["non_unique"] != 1 + and ( + metadata["columns"] == ("report_id",) + or metadata["columns"] == ("report_item_id",) + ) + ) + ] + if unique_single_column_indexes: + raise RuntimeError( + f"{TABLE_NAME} unique FK indexes are incompatible: {', '.join(sorted(unique_single_column_indexes))}" + ) + foreign_keys = existing_foreign_keys(conn) missing_foreign_keys = [ column