"""SqlDataProvider — implementacja docelowa (po migracji z Excela). Szkielet. Realizuje TEN SAM interfejs DataProvider, więc przełączenie to tylko zmiana zmiennej środowiskowej DATA_PROVIDER=sql (patrz factory.py). Warstwa logiczna i prezentacji nie zmieniają ani jednej linii. Dane ładuje do bazy skrypt ingest/to_sql.py (ten sam loader Excela -> tabele SQL). """ from __future__ import annotations import time from sqlalchemy import create_engine, text from app.config import Settings from app.models import HealthInfo, SearchQuery, SearchResult from app.providers.base import DataProvider class SqlDataProvider(DataProvider): name = "sql" def __init__(self, settings: Settings) -> None: self.s = settings self.engine = create_engine(settings.sql_url, future=True) def search(self, query: SearchQuery) -> SearchResult: t0 = time.perf_counter() op = "=" if query.exact else "LIKE" val = query.value if query.exact else f"%{query.value}%" cols = ", ".join(query.fields) if query.fields else "*" # UWAGA: nazwy kolumn/tabel walidować względem białej listy schematu. sql = text(f"SELECT {cols} FROM records WHERE {query.key} {op} :v LIMIT :lim") with self.engine.connect() as conn: rows = [dict(r._mapping) for r in conn.execute(sql, {"v": val, "lim": query.limit})] return SearchResult( rows=rows, total=len(rows), elapsed_ms=round((time.perf_counter() - t0) * 1000, 2), cache="miss", provider=self.name, ) def health(self) -> HealthInfo: with self.engine.connect() as conn: n = conn.execute(text("SELECT COUNT(*) FROM records")).scalar() or 0 return HealthInfo(provider=self.name, indexed_files=0, details={"records": int(n)})