Skip to content

Commit af6554e

Browse files
Lukas Geigerclaude
andcommitted
Fix: INSERT OR REPLACE mit AUTOINCREMENT erzeugte verwaiste FTS-Eintraege in ProfilerBridge
Bei jedem Re-Index einer bereits indexierten Datei loeschte INSERT OR REPLACE die alte Zeile und fuegte eine neue mit neuer AUTOINCREMENT-id ein. Der FTS-Eintrag fuer die alte rowid blieb als Leiche erhalten. Nach N Re-Indizes lieferte die Suche N Treffer fuer dieselbe Datei. Fix: ON CONFLICT(path) DO UPDATE bewahrt die urspruengliche id — kein Rowid-Wechsel, kein FTS-Orphan. Regressionstest hinzugefuegt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2a1ad69 commit af6554e

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

src/modules/filemanager/profiler_bridge.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,18 @@ def index_file(self, file_path: str, project_path: str = None) -> bool:
171171
cursor = conn.cursor()
172172

173173
cursor.execute('''
174-
INSERT OR REPLACE INTO files
174+
INSERT INTO files
175175
(path, name, extension, size, modified, hash, content, indexed_at, project_path)
176176
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
177+
ON CONFLICT(path) DO UPDATE SET
178+
name=excluded.name,
179+
extension=excluded.extension,
180+
size=excluded.size,
181+
modified=excluded.modified,
182+
hash=excluded.hash,
183+
content=excluded.content,
184+
indexed_at=excluded.indexed_at,
185+
project_path=excluded.project_path
177186
''', (
178187
str(path),
179188
path.name,

tests/test_profiler_bridge.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# -*- coding: utf-8 -*-
2+
"""Regressionstests für ProfilerBridge — insbesondere Re-Index ohne FTS-Duplikate."""
3+
4+
import os
5+
import sys
6+
import tempfile
7+
import unittest
8+
from pathlib import Path
9+
10+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
11+
12+
from modules.filemanager.profiler_bridge import ProfilerBridge
13+
14+
15+
class ProfilerBridgeTests(unittest.TestCase):
16+
17+
def setUp(self):
18+
self.tmp = tempfile.TemporaryDirectory()
19+
self.addCleanup(self.tmp.cleanup)
20+
db_path = str(Path(self.tmp.name) / "test_index.db")
21+
self.bridge = ProfilerBridge(db_path=db_path)
22+
23+
def _make_file(self, name: str, content: str = "hello") -> str:
24+
path = Path(self.tmp.name) / name
25+
path.write_text(content, encoding="utf-8")
26+
return str(path)
27+
28+
def test_index_file_creates_entry(self):
29+
f = self._make_file("sample.py", "def foo(): pass")
30+
self.assertTrue(self.bridge.index_file(f))
31+
results = self.bridge.search("foo")
32+
self.assertEqual(len(results), 1)
33+
34+
def test_reindex_same_file_no_duplicate_results(self):
35+
"""Re-Indexieren einer Datei darf keine doppelten Such-Ergebnisse erzeugen.
36+
37+
Bug: INSERT OR REPLACE auf AUTOINCREMENT-Tabelle ändert die id und hinterlässt
38+
einen verwaisten FTS-Eintrag. Nach N Re-Indizes liefert die Suche N Treffer
39+
für dieselbe Datei. Fix: ON CONFLICT DO UPDATE bewahrt die ursprüngliche id.
40+
"""
41+
f = self._make_file("unique_module.py", "def alpha(): pass")
42+
43+
for _ in range(5):
44+
self.bridge.index_file(f)
45+
46+
results = self.bridge.search("alpha")
47+
paths = [r.file.path for r in results]
48+
self.assertEqual(
49+
len(paths),
50+
len(set(paths)),
51+
"Suche liefert Duplikate nach mehrfachem Re-Index derselben Datei",
52+
)
53+
self.assertEqual(len(results), 1, "Erwartet genau 1 Treffer, nicht mehr")
54+
55+
def test_statistics_total_files_not_inflated(self):
56+
f = self._make_file("stats_test.py", "x = 1")
57+
for _ in range(3):
58+
self.bridge.index_file(f)
59+
stats = self.bridge.get_statistics()
60+
self.assertEqual(stats["total_files"], 1)
61+
62+
63+
if __name__ == "__main__":
64+
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)