fix: add db.rollback() to batch dedup error handlers
SQLAlchemy sessions enter a failed state after SQL errors. Without rollback(), all subsequent queries on the same session fail with InFailedSqlTransaction. Added try/except with rollback in _mark_duplicate, _mark_duplicate_to, _write_review, cross-group pass, and the main phase1 loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -141,6 +141,10 @@ class BatchDedupRunner:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("BatchDedup Phase 1 error on hint %s: %s", hint, e)
|
logger.error("BatchDedup Phase 1 error on hint %s: %s", hint, e)
|
||||||
self.stats["errors"] += 1
|
self.stats["errors"] += 1
|
||||||
|
try:
|
||||||
|
self.db.rollback()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"BatchDedup Phase 1 done: %d masters, %d linked, %d review",
|
"BatchDedup Phase 1 done: %d masters, %d linked, %d review",
|
||||||
@@ -372,6 +376,7 @@ class BatchDedupRunner:
|
|||||||
# Simple check: different control UUID is enough
|
# Simple check: different control UUID is enough
|
||||||
if match_score > LINK_THRESHOLD:
|
if match_score > LINK_THRESHOLD:
|
||||||
# Mark the worse one as duplicate
|
# Mark the worse one as duplicate
|
||||||
|
try:
|
||||||
self.db.execute(text("""
|
self.db.execute(text("""
|
||||||
UPDATE canonical_controls
|
UPDATE canonical_controls
|
||||||
SET release_state = 'duplicate', merged_into_uuid = CAST(:master AS uuid)
|
SET release_state = 'duplicate', merged_into_uuid = CAST(:master AS uuid)
|
||||||
@@ -392,6 +397,11 @@ class BatchDedupRunner:
|
|||||||
|
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
cross_linked += 1
|
cross_linked += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("BatchDedup cross-group link error %s→%s: %s",
|
||||||
|
uuid, match_uuid, e)
|
||||||
|
self.db.rollback()
|
||||||
|
self.stats["errors"] += 1
|
||||||
break # Only one cross-link per control
|
break # Only one cross-link per control
|
||||||
elif match_score > REVIEW_THRESHOLD:
|
elif match_score > REVIEW_THRESHOLD:
|
||||||
self._write_review(
|
self._write_review(
|
||||||
@@ -474,6 +484,7 @@ class BatchDedupRunner:
|
|||||||
|
|
||||||
async def _mark_duplicate(self, master: dict, candidate: dict, confidence: float):
|
async def _mark_duplicate(self, master: dict, candidate: dict, confidence: float):
|
||||||
"""Mark candidate as duplicate of master, transfer parent links."""
|
"""Mark candidate as duplicate of master, transfer parent links."""
|
||||||
|
try:
|
||||||
self.db.execute(text("""
|
self.db.execute(text("""
|
||||||
UPDATE canonical_controls
|
UPDATE canonical_controls
|
||||||
SET release_state = 'duplicate', merged_into_uuid = CAST(:master AS uuid)
|
SET release_state = 'duplicate', merged_into_uuid = CAST(:master AS uuid)
|
||||||
@@ -491,9 +502,15 @@ class BatchDedupRunner:
|
|||||||
self.stats["parent_links_transferred"] += transferred
|
self.stats["parent_links_transferred"] += transferred
|
||||||
|
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("BatchDedup _mark_duplicate error %s→%s: %s",
|
||||||
|
candidate["uuid"], master["uuid"], e)
|
||||||
|
self.db.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
async def _mark_duplicate_to(self, master_uuid: str, candidate: dict, confidence: float):
|
async def _mark_duplicate_to(self, master_uuid: str, candidate: dict, confidence: float):
|
||||||
"""Mark candidate as duplicate of a Qdrant-matched master."""
|
"""Mark candidate as duplicate of a Qdrant-matched master."""
|
||||||
|
try:
|
||||||
self.db.execute(text("""
|
self.db.execute(text("""
|
||||||
UPDATE canonical_controls
|
UPDATE canonical_controls
|
||||||
SET release_state = 'duplicate', merged_into_uuid = CAST(:master AS uuid)
|
SET release_state = 'duplicate', merged_into_uuid = CAST(:master AS uuid)
|
||||||
@@ -511,6 +528,11 @@ class BatchDedupRunner:
|
|||||||
self.stats["parent_links_transferred"] += transferred
|
self.stats["parent_links_transferred"] += transferred
|
||||||
|
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("BatchDedup _mark_duplicate_to error %s→%s: %s",
|
||||||
|
candidate["uuid"], master_uuid, e)
|
||||||
|
self.db.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
def _transfer_parent_links(self, master_uuid: str, duplicate_uuid: str) -> int:
|
def _transfer_parent_links(self, master_uuid: str, duplicate_uuid: str) -> int:
|
||||||
"""Move existing parent links from duplicate to master."""
|
"""Move existing parent links from duplicate to master."""
|
||||||
@@ -549,6 +571,7 @@ class BatchDedupRunner:
|
|||||||
|
|
||||||
def _write_review(self, candidate: dict, matched_payload: dict, score: float):
|
def _write_review(self, candidate: dict, matched_payload: dict, score: float):
|
||||||
"""Write a dedup review entry for borderline matches."""
|
"""Write a dedup review entry for borderline matches."""
|
||||||
|
try:
|
||||||
self.db.execute(text("""
|
self.db.execute(text("""
|
||||||
INSERT INTO control_dedup_reviews
|
INSERT INTO control_dedup_reviews
|
||||||
(candidate_control_id, candidate_title, candidate_objective,
|
(candidate_control_id, candidate_title, candidate_objective,
|
||||||
@@ -569,6 +592,10 @@ class BatchDedupRunner:
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("BatchDedup _write_review error: %s", e)
|
||||||
|
self.db.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
# ── Progress ─────────────────────────────────────────────────────────
|
# ── Progress ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user