feat(harness): tht sql preview --json + --offset for AGGrid paging (BE-2)
Add inject_limit_offset (tht/execute/limit.py) — pure subquery wrapper that applies LIMIT/OFFSET non-destructively without clobbering user-supplied LIMITs. Wire offset param into do_run (pre-processing when offset>0) and add --offset / --json flags to preview_cmd; JSON mode emits pristine stdout with columns, rows, execution_ms, truncated, limit, offset. 5 new tests (4 unit + 1 JSON-purity), 219/219 total passing (no regressions). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -102,8 +102,15 @@ def do_explain(cfg, sql: str):
|
||||
return explain(_ro_engine(cfg), sql, timeout_ms=cfg.execution.statement_timeout_ms)
|
||||
|
||||
|
||||
def do_run(cfg, sql: str, *, limit: int):
|
||||
def do_run(cfg, sql: str, *, limit: int, offset: int = 0):
|
||||
"""Esecuzione controllata secondo il transport configurato (direct|rest)."""
|
||||
from tht.execute.limit import inject_limit_offset
|
||||
|
||||
# Inject OFFSET (and an outer LIMIT) via subquery wrapping when offset > 0.
|
||||
# When offset == 0 we let the inner runners apply LIMIT directly (existing path).
|
||||
if offset:
|
||||
sql = inject_limit_offset(sql, limit=limit, offset=offset)
|
||||
|
||||
if cfg.database.transport == "rest":
|
||||
from tht.rest.execute import run_controlled_rest
|
||||
|
||||
@@ -157,15 +164,13 @@ def explain_cmd(
|
||||
def preview_cmd(
|
||||
file: Path = typer.Argument(...),
|
||||
limit: int = typer.Option(None, "--limit", help="Default: execution.max_preview_rows."),
|
||||
offset: int = typer.Option(0, "--offset", help="Riga di partenza (0-based) per il paging."),
|
||||
session: str = typer.Option(None, "--session"),
|
||||
json_out: bool = typer.Option(False, "--json", help="Output JSON puro per il backend (sopprime tabella rich)."),
|
||||
config: Path = CONFIG_OPT,
|
||||
) -> None:
|
||||
"""Esecuzione controllata con LIMIT iniettato; aggregati mostrati per interi."""
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
from tht.execute import ExecutionError
|
||||
from tht.execute.warnings import runtime_warnings, static_warnings
|
||||
|
||||
cfg = _load_config_or_exit(config)
|
||||
require_action(cfg, "preview")
|
||||
@@ -173,11 +178,30 @@ def preview_cmd(
|
||||
check = validate_or_exit(cfg, sql, session)
|
||||
effective_limit = limit if limit is not None else cfg.execution.max_preview_rows
|
||||
try:
|
||||
result = do_run(cfg, sql, limit=effective_limit)
|
||||
result = do_run(cfg, sql, limit=effective_limit, offset=offset)
|
||||
except ExecutionError as e:
|
||||
typer.secho(f"ERRORE: {e}", fg=typer.colors.RED, err=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
if json_out:
|
||||
typer.echo(json.dumps(
|
||||
{
|
||||
"columns": result.columns,
|
||||
"rows": [list(r) for r in result.rows],
|
||||
"execution_ms": result.execution_ms,
|
||||
"truncated": result.truncated,
|
||||
"limit": effective_limit,
|
||||
"offset": offset,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
))
|
||||
return
|
||||
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
from tht.execute.warnings import runtime_warnings, static_warnings
|
||||
|
||||
cells = len(result.rows) * len(result.columns)
|
||||
is_aggregate = (
|
||||
"aggregate" in cfg.execution.allow
|
||||
|
||||
Reference in New Issue
Block a user