From 91b0bf8933e08a780388a71909c325d8174f7587 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Mon, 27 Jul 2026 21:36:58 +0800 Subject: [PATCH] fix(download_queue): deduplicate download_history rows before creating unique index (#1041) --- py/services/download_queue_service.py | 36 +++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/py/services/download_queue_service.py b/py/services/download_queue_service.py index 90a58f85..09ae068f 100644 --- a/py/services/download_queue_service.py +++ b/py/services/download_queue_service.py @@ -31,7 +31,7 @@ class DownloadQueueService: _instance: Optional[DownloadQueueService] = None _class_lock: asyncio.Lock = asyncio.Lock() - _SCHEMA = """ + _SCHEMA_TABLES = """ CREATE TABLE IF NOT EXISTS download_queue ( download_id TEXT PRIMARY KEY, model_id INTEGER, @@ -74,6 +74,9 @@ class DownloadQueueService: ); CREATE INDEX IF NOT EXISTS idx_dh_completed ON download_history(completed_at DESC); CREATE INDEX IF NOT EXISTS idx_dh_status ON download_history(status); + """ + + _CREATE_UNIQUE_INDEX = """ CREATE UNIQUE INDEX IF NOT EXISTS idx_dh_download_id ON download_history(download_id) WHERE download_id IS NOT NULL; """ @@ -115,10 +118,39 @@ class DownloadQueueService: if self._schema_initialized: return with self._connect() as conn: - conn.executescript(self._SCHEMA) + conn.executescript(self._SCHEMA_TABLES) + + # Creating the unique index on download_history.download_id can + # fail if pre-existing rows have duplicate values (e.g. from a + # previous version that lacked the index). Deduplicate first so + # that the migration does not crash on startup. + if not self._index_exists(conn, "idx_dh_download_id"): + self._remove_duplicate_download_ids(conn) + conn.executescript(self._CREATE_UNIQUE_INDEX) + conn.commit() self._schema_initialized = True + @staticmethod + def _index_exists(conn: sqlite3.Connection, name: str) -> bool: + return conn.execute( + "SELECT 1 FROM sqlite_master WHERE type='index' AND name=?", + (name,), + ).fetchone() is not None + + @staticmethod + def _remove_duplicate_download_ids(conn: sqlite3.Connection) -> None: + conn.execute(""" + DELETE FROM download_history + WHERE id NOT IN ( + SELECT MIN(id) + FROM download_history + WHERE download_id IS NOT NULL + GROUP BY download_id + ) + AND download_id IS NOT NULL + """) + def get_database_path(self) -> str: """Return the resolved database file path.""" return self._db_path