video enrichment: survive legacy UNIQUE on tmdb_id/tvdb_id (no scan crash)
Existing DBs created before the schema dropped UNIQUE still have shows.tvdb_id / movies.tmdb_id UNIQUE, so enrichment matching two items to the same id threw 'UNIQUE constraint failed' repeatedly. enrichment_apply now catches the IntegrityError and retries without the id columns — keeps the existing (authoritative) id and still records match_status + metadata. Non-destructive (no table rebuild). Test simulates the legacy unique index.
This commit is contained in:
parent
2901e4ec4d
commit
776afdd1fd
2 changed files with 45 additions and 10 deletions
|
|
@ -179,21 +179,39 @@ class VideoDatabase:
|
||||||
if not spec:
|
if not spec:
|
||||||
return
|
return
|
||||||
tbl, idc, sc, ac = spec
|
tbl, idc, sc, ac = spec
|
||||||
sets = [f"{sc}=?", f"{ac}=CURRENT_TIMESTAMP"]
|
|
||||||
params = ["matched" if matched else "not_found"]
|
|
||||||
if matched and external_id is not None:
|
|
||||||
sets.append(f"{idc}=?")
|
|
||||||
params.append(external_id)
|
|
||||||
allowed = _ENRICH_META_COLS.get(tbl, set())
|
allowed = _ENRICH_META_COLS.get(tbl, set())
|
||||||
for col, val in (metadata or {}).items():
|
# On legacy DBs tmdb_id/tvdb_id may still carry a UNIQUE index; if a match
|
||||||
if val is not None and col in allowed:
|
# would collide with another row's id we drop the id columns and keep the
|
||||||
|
# existing (authoritative) id, still recording status + metadata.
|
||||||
|
id_cols = {"tmdb_id", "tvdb_id", "imdb_id"}
|
||||||
|
|
||||||
|
def build(include_ids):
|
||||||
|
sets = [f"{sc}=?", f"{ac}=CURRENT_TIMESTAMP"]
|
||||||
|
params = ["matched" if matched else "not_found"]
|
||||||
|
if matched and external_id is not None and include_ids:
|
||||||
|
sets.append(f"{idc}=?")
|
||||||
|
params.append(external_id)
|
||||||
|
for col, val in (metadata or {}).items():
|
||||||
|
if val is None or col not in allowed:
|
||||||
|
continue
|
||||||
|
if not include_ids and col in id_cols:
|
||||||
|
continue
|
||||||
sets.append(f"{col}=?")
|
sets.append(f"{col}=?")
|
||||||
params.append(val)
|
params.append(val)
|
||||||
params.append(item_id)
|
params.append(item_id)
|
||||||
|
return f"UPDATE {tbl} SET {', '.join(sets)} WHERE id=?", params
|
||||||
|
|
||||||
conn = self._get_connection()
|
conn = self._get_connection()
|
||||||
try:
|
try:
|
||||||
conn.execute(f"UPDATE {tbl} SET {', '.join(sets)} WHERE id=?", params)
|
sql, params = build(True)
|
||||||
conn.commit()
|
try:
|
||||||
|
conn.execute(sql, params)
|
||||||
|
conn.commit()
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
conn.rollback()
|
||||||
|
sql, params = build(False) # keep existing id, just record status/metadata
|
||||||
|
conn.execute(sql, params)
|
||||||
|
conn.commit()
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,23 @@ def test_enrichment_apply_matched_sets_id_status_and_metadata(db):
|
||||||
assert (row["overview"], row["backdrop_url"], row["imdb_id"]) == ("O", "/b.jpg", "tt1")
|
assert (row["overview"], row["backdrop_url"], row["imdb_id"]) == ("O", "/b.jpg", "tt1")
|
||||||
|
|
||||||
|
|
||||||
|
def test_enrichment_apply_survives_legacy_unique(db):
|
||||||
|
# Simulate a pre-existing DB where tvdb_id still carries a UNIQUE index.
|
||||||
|
with db.connect() as c:
|
||||||
|
c.execute("CREATE UNIQUE INDEX ux_legacy_shows_tvdb ON shows(tvdb_id)")
|
||||||
|
c.commit()
|
||||||
|
a = db.upsert_show_tree("plex", {"server_id": "s1", "title": "A", "seasons": []})
|
||||||
|
b = db.upsert_show_tree("plex", {"server_id": "s2", "title": "B", "seasons": []})
|
||||||
|
db.enrichment_apply("tvdb", "show", a, matched=True, external_id=555, metadata={"overview": "OA"})
|
||||||
|
# b would collide on tvdb_id=555 — must NOT crash; keeps existing id, records the rest.
|
||||||
|
db.enrichment_apply("tvdb", "show", b, matched=True, external_id=555, metadata={"overview": "OB"})
|
||||||
|
with db.connect() as c:
|
||||||
|
ra = c.execute("SELECT tvdb_id, tvdb_match_status FROM shows WHERE id=?", (a,)).fetchone()
|
||||||
|
rb = c.execute("SELECT tvdb_id, tvdb_match_status, overview FROM shows WHERE id=?", (b,)).fetchone()
|
||||||
|
assert (ra["tvdb_id"], ra["tvdb_match_status"]) == (555, "matched")
|
||||||
|
assert rb["tvdb_id"] is None and rb["tvdb_match_status"] == "matched" and rb["overview"] == "OB"
|
||||||
|
|
||||||
|
|
||||||
def test_enrichment_breakdown_unmatched_retry(db):
|
def test_enrichment_breakdown_unmatched_retry(db):
|
||||||
a = db.upsert_movie("plex", {"server_id": "m1", "title": "A"})
|
a = db.upsert_movie("plex", {"server_id": "m1", "title": "A"})
|
||||||
b = db.upsert_movie("plex", {"server_id": "m2", "title": "B"})
|
b = db.upsert_movie("plex", {"server_id": "m2", "title": "B"})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue