From b431797d3275683af9d63bdabf75dd8950ef8bc9 Mon Sep 17 00:00:00 2001 From: ppfeiffer Date: Fri, 20 Feb 2026 22:08:10 +0100 Subject: [PATCH] fix(db): upsert_node Race-Condition behoben (UNIQUE constraint) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit INSERT OR IGNORE + UPDATE statt SELECT → INSERT eliminiert den UNIQUE-constraint-Fehler bei konkurrierenden async-Aufrufen. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 7 +++++++ config/config.yaml | 2 +- meshbot/database.py | 36 +++++++++++++----------------------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e7f2b..16ce59b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.08.20] - 2026-02-20 + +### Fixed +- **`upsert_node` Race-Condition** (`UNIQUE constraint failed: nodes.node_id`): + Statt SELECT → INSERT nutzt die Methode jetzt `INSERT OR IGNORE` + `UPDATE`, + wodurch konkurrierende Aufrufe keinen Constraint-Fehler mehr auslösen. + ## [0.08.19] - 2026-02-20 ### Added diff --git a/config/config.yaml b/config/config.yaml index b09dc6b..2d6298b 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,4 +1,4 @@ -version: "0.08.19" +version: "0.08.20" bot: name: "MeshDD-Bot" diff --git a/meshbot/database.py b/meshbot/database.py index 0d57f1e..54997b6 100644 --- a/meshbot/database.py +++ b/meshbot/database.py @@ -129,29 +129,19 @@ class Database: async def upsert_node(self, node_id: str, **kwargs) -> dict: now = time.time() - existing = await self.get_node(node_id) - - if existing: - updates = {k: v for k, v in kwargs.items() if v is not None} - if not updates: - return dict(existing) - updates["last_seen"] = now - set_clause = ", ".join(f"{k} = ?" for k in updates) - values = list(updates.values()) + [node_id] - await self.db.execute( - f"UPDATE nodes SET {set_clause} WHERE node_id = ?", values - ) - else: - kwargs["node_id"] = node_id - kwargs.setdefault("first_seen", now) - kwargs["last_seen"] = now - cols = ", ".join(kwargs.keys()) - placeholders = ", ".join("?" for _ in kwargs) - await self.db.execute( - f"INSERT INTO nodes ({cols}) VALUES ({placeholders})", - list(kwargs.values()), - ) - + # Row anlegen falls nicht vorhanden (first_seen nur beim ersten Mal gesetzt) + await self.db.execute( + "INSERT OR IGNORE INTO nodes (node_id, first_seen, last_seen) VALUES (?, ?, ?)", + (node_id, now, now), + ) + # Nicht-None-Felder + last_seen immer aktualisieren + updates = {k: v for k, v in kwargs.items() if v is not None} + updates["last_seen"] = now + set_clause = ", ".join(f"{k} = ?" for k in updates) + values = list(updates.values()) + [node_id] + await self.db.execute( + f"UPDATE nodes SET {set_clause} WHERE node_id = ?", values + ) await self.db.commit() return dict(await self.get_node(node_id))