Part of #68 (Roadmap to 1.0.0) — Editor intelligence track.
Problem
Today the whole editor is sent as a single POST body (src/ui/app.js:419), and ClickHouse's HTTP interface runs exactly one statement per request — so pasting CREATE …; INSERT …; SELECT … errors out. There's no semicolon splitting in src/core/, no run-selection path (the textarea selection is used only for bracket-matching, src/ui/editor.js:122), and each tab holds one tab.result overwritten per run (src/core/stream.js:11, src/state.js:35). Users can't run a setup script (a few DDLs, an INSERT, a sanity-check SELECT) in one shot, nor run just the statement(s) they've highlighted.
Proposed solution
Split client-side and run sequentially — the same model as clickhouse-client --multiquery. Render a compact summary grid, one row per statement.
UX decisions (settled):
- Trigger: auto-detect. >1 statement → ⌘+Enter runs them in order; a single statement (± trailing
;) behaves exactly as today.
- Run selection. A non-empty textarea selection runs only the selected text (fed through the same split → may be one or many statements); empty selection runs the whole editor. The Run button flips its label to "Run selection" while text is highlighted, so the mode is discoverable.
- On error: stop on first failure — show its error, skip the rest.
- SELECT output: valid JSON, capped at 100 rows (details below).
- FORMAT / EXPLAIN per statement are out of scope for v1 — script mode is plain.
SELECT output model
- Send each SELECT with format
CSVWithNames and settings max_result_rows=100&result_overflow_mode=break so the server caps cleanly at minimal rows instead of throwing (added as query params via the existing chUrl builder; reuses the raw / wait_end_of_query path — no stream parsing). Actual number can be more due the block size.
- show first row in Col 2 comma separated
- click to col2 opens right pane with table view of all received rows
Scope
src/core/sql-split.js (new, 100% covered) — tokenizer that splits on ; while skipping '…' / "…" / `…` literals and -- / /* */ comments. Single statement → single-element list (preserves today's path).
src/core/ JSON-assembly helper (100% covered) — NDJSON lines → valid JSON per the 0/1/≥2-row rule above, with the 100-line cap.
- Run-selection wiring in
src/ui/app.js — read selectionStart/selectionEnd from the editor textarea; non-empty (non-whitespace) range becomes the run input instead of tab.sql. Button label reflects selection state.
- Sequential runner in
src/ui/app.js — loop over the split statements, reuse ch.runQuery per statement (no change to src/net/ch-client.js), honor the existing AbortController so cancel stops the remaining statements.
- Multi-result state — array of per-statement outcomes (
{ sql, status: 'ok'|'rows'|'error', json|error }) in src/core/stream.js / src/state.js.
- Summary grid in
src/ui/results.js — new "script" view rendered as a 2-column table, one row per executed statement:
- Col 1 — truncated statement text (full text on hover/title).
- Col 2 — outcome:
OK (DDL), status line (INSERT), or the capped JSON string (SELECT).
- The failing statement is the last row run (stop-on-first-error) and shows its error message.
Known limitation
INSERT … FORMAT CSV\n<inline data> containing a ; inside the data would mis-split. Document it; inline-data inserts should be run on their own.
Acceptance
Part of #68 (Roadmap to 1.0.0) — Editor intelligence track.
Problem
Today the whole editor is sent as a single POST body (
src/ui/app.js:419), and ClickHouse's HTTP interface runs exactly one statement per request — so pastingCREATE …; INSERT …; SELECT …errors out. There's no semicolon splitting insrc/core/, no run-selection path (the textarea selection is used only for bracket-matching,src/ui/editor.js:122), and each tab holds onetab.resultoverwritten per run (src/core/stream.js:11,src/state.js:35). Users can't run a setup script (a few DDLs, an INSERT, a sanity-check SELECT) in one shot, nor run just the statement(s) they've highlighted.Proposed solution
Split client-side and run sequentially — the same model as
clickhouse-client --multiquery. Render a compact summary grid, one row per statement.UX decisions (settled):
;) behaves exactly as today.SELECT output model
CSVWithNamesand settingsmax_result_rows=100&result_overflow_mode=breakso the server caps cleanly at minimal rows instead of throwing (added as query params via the existingchUrlbuilder; reuses the raw /wait_end_of_querypath — no stream parsing). Actual number can be more due the block size.Scope
src/core/sql-split.js(new, 100% covered) — tokenizer that splits on;while skipping'…'/"…"/`…`literals and--//* */comments. Single statement → single-element list (preserves today's path).src/core/JSON-assembly helper (100% covered) — NDJSON lines → valid JSON per the 0/1/≥2-row rule above, with the 100-line cap.src/ui/app.js— readselectionStart/selectionEndfrom the editor textarea; non-empty (non-whitespace) range becomes the run input instead oftab.sql. Button label reflects selection state.src/ui/app.js— loop over the split statements, reusech.runQueryper statement (no change tosrc/net/ch-client.js), honor the existingAbortControllerso cancel stops the remaining statements.{ sql, status: 'ok'|'rows'|'error', json|error }) insrc/core/stream.js/src/state.js.src/ui/results.js— new "script" view rendered as a 2-column table, one row per executed statement:OK(DDL), status line (INSERT), or the capped JSON string (SELECT).Known limitation
INSERT … FORMAT CSV\n<inline data>containing a;inside the data would mis-split. Document it; inline-data inserts should be run on their own.Acceptance
CREATE; INSERT; SELECTscript runs in order with one summary-grid row per statement.;) is byte-for-byte the current behavior.[…]array — capped at 100 rows viamax_result_rows/result_overflow_mode=breakplus a JS line cap.npm testgreen at the per-file coverage gate (splitter + JSON-assembly helper at 100%).