"""Odwrócony indeks: wartość kanonicznego klucza -> które pliki/arkusze ją mają. POZIOM 4 (i najważniejszy przy skali) — pozwala NIE skanować setek plików przy każdym zapytaniu. Budujemy w SQLite indeks: dla wybranych kluczy (np. name, id, symbol) zapisujemy, w którym pliku/arkuszu występuje dana wartość. Wyszukiwanie najpierw pyta indeks (jeden szybki SELECT), a otwiera tylko trafione pliki. Ten SQLite indeks jest jednocześnie POMOSTEM do pełnej migracji SQL — rozbudowa go o wszystkie kolumny = de facto baza danych (patrz ingest/to_sql.py). """ from __future__ import annotations import sqlite3 from pathlib import Path class InvertedIndex: def __init__(self, cache_dir: Path) -> None: self._db = sqlite3.connect(str(cache_dir / "index.db"), check_same_thread=False) self._db.execute( """ CREATE TABLE IF NOT EXISTS entries ( key TEXT, -- kanoniczne pole, np. 'name' value TEXT, -- znormalizowana (lower) wartość file TEXT, -- ścieżka pliku sheet TEXT ) """ ) self._db.execute("CREATE INDEX IF NOT EXISTS ix_kv ON entries(key, value)") self._db.execute( "CREATE TABLE IF NOT EXISTS files (file TEXT PRIMARY KEY, fingerprint TEXT)" ) self._db.commit() def file_fingerprint(self, file: str) -> str | None: cur = self._db.execute("SELECT fingerprint FROM files WHERE file=?", (file,)) row = cur.fetchone() return row[0] if row else None def reindex_file(self, file: str, fingerprint: str, rows: list[tuple[str, str, str]]) -> None: """rows: lista (key, value, sheet) dla jednego pliku.""" self._db.execute("DELETE FROM entries WHERE file=?", (file,)) self._db.executemany( "INSERT INTO entries(key, value, file, sheet) VALUES (?,?,?,?)", [(k, v.lower(), file, sheet) for (k, v, sheet) in rows], ) self._db.execute( "INSERT OR REPLACE INTO files(file, fingerprint) VALUES (?,?)", (file, fingerprint) ) self._db.commit() def lookup(self, key: str, value: str, exact: bool) -> list[tuple[str, str]]: """Zwraca listę (file, sheet) kandydatów do przeszukania.""" if exact: cur = self._db.execute( "SELECT DISTINCT file, sheet FROM entries WHERE key=? AND value=?", (key, value.lower()), ) else: cur = self._db.execute( "SELECT DISTINCT file, sheet FROM entries WHERE key=? AND value LIKE ?", (key, f"%{value.lower()}%"), ) return [(r[0], r[1]) for r in cur.fetchall()] def count_files(self) -> int: return self._db.execute("SELECT COUNT(*) FROM files").fetchone()[0]