Files
ThothII/harness/tht/rest/execute.py
T
marcopan ea6412fafc feat(harness): port backend Onda 0 — vendor, lshindex, sqlcheck, execute, rest/exec, ctetest, report, datamart
8 moduli leaf portati verbatim da ChironeWp3 con rename psdwp3→tht:
- vendor/thoth_lsh (MinHash/LSH, leaf puro datasketch+tqdm) + VENDORED.md
- lshindex/ (build/save/load/query, dipende vendor + LshConfig)
- sqlcheck/ (validate_sql, leaf ExecutionConfig+mschema)
- execute/ + execute/warnings (run_controlled/explain, leaf sqlglot+sqlalchemy)
- rest/execute + rest/explain (REST variants, dipendono execute+rest.client)
- ctetest (CTE test records, leaf sqlglot+pydantic)
- report (validation report rendering, dipende execute+sqlcheck)
- datamart (stub NotImplementedError)

Verifica: import smoke catena completa OK, pytest 109 passed. Deps (datasketch, sqlglot,
sqlalchemy, pydantic, requests, tqdm) già in pyproject. VENDORED.md neutralizzato
(riferimenti PsdWp3→Thoth).
2026-06-27 10:34:23 +02:00

39 lines
1.4 KiB
Python

"""Esecuzione controllata e EXPLAIN via REST (rpc `run_query`/`explain_query`).
Produce gli stessi tipi del transport diretto (`ExecResult`, `PlanSummary`) e traduce
`RestError` in `ExecutionError`, così le fasi 6/7 e la gestione errori della CLI non cambiano.
Differenze rispetto al diretto: read-only e statement_timeout sono garantiti lato server;
il client mantiene solo l'iniezione del LIMIT (per il rilevamento del troncamento in preview).
"""
import time
from tht.execute import ExecResult, ExecutionError, PlanSummary, _inject_limit
from tht.rest.client import RestError
from tht.rest.explain import parse_text_plan
def run_controlled_rest(client, sql: str, *, limit: int) -> ExecResult:
final_sql, injected = _inject_limit(sql, limit)
start = time.monotonic()
try:
rows_dicts = client.run_query(final_sql)
except RestError as e:
raise ExecutionError(str(e)) from e
elapsed_ms = int((time.monotonic() - start) * 1000)
columns = list(rows_dicts[0].keys()) if rows_dicts else []
rows = [tuple(r.get(c) for c in columns) for r in rows_dicts]
truncated = injected and len(rows) > limit
return ExecResult(
columns=columns, rows=rows[:limit], execution_ms=elapsed_ms, truncated=truncated
)
def explain_rest(client, sql: str) -> PlanSummary:
try:
lines = client.explain_query(sql)
except RestError as e:
raise ExecutionError(str(e)) from e
return parse_text_plan(lines)