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).
39 lines
1.4 KiB
Python
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)
|