ch: only a trailing FORMAT clause disables the JSONEachRow default
1 changed file +7 −2
modified
apps/api/src/internetpressure/db/ch.py
+7 −2
@@ -8,6 +8,7 @@ Usage: | ||
| 8 | 8 | from __future__ import annotations |
| 9 | 9 | |
| 10 | 10 | import logging |
| 11 | +import re | |
| 11 | 12 | from pathlib import Path |
| 12 | 13 | from typing import Any |
| 13 | 14 | |
@@ -63,12 +64,16 @@ async def raw(sql: str, *, database: str | None = None, body: bytes | None = Non | ||
| 63 | 64 | |
| 64 | 65 | |
| 65 | 66 | async def query(sql: str, params: dict | None = None) -> list[dict[str, Any]]: |
| 66 | − if "FORMAT" not in sql.upper(): | |
| 67 | + # only a trailing "FORMAT <name>" counts (an id like "plateforme" inside an IN (...) list must not match) | |
| 68 | + if not re.search(r"\bFORMAT\s+\w+\s*;?\s*$", sql, re.IGNORECASE): | |
| 67 | 69 | sql = sql.rstrip().rstrip(";") + " FORMAT JSONEachRow" |
| 68 | 70 | out = await raw(sql, params=params) |
| 69 | 71 | if not out.strip(): |
| 70 | 72 | return [] |
| 71 | − return [loads(line) for line in out.splitlines() if line.strip()] | |
| 73 | + try: | |
| 74 | + return [loads(line) for line in out.splitlines() if line.strip()] | |
| 75 | + except Exception as exc: # noqa: BLE001 — ClickHouse can emit an exception text after a 200 header was sent | |
| 76 | + raise CHError(f"ClickHouse returned non-JSON rows ({exc}): {out[:400]!r}\n-- {sql[:300]}") from exc | |
| 72 | 77 | |
| 73 | 78 | |
| 74 | 79 | async def query_one(sql: str, params: dict | None = None) -> dict[str, Any] | None: |
| 75 | 80 | |