Skip to content

Multiquery + run-selection: execute a ;-separated script (DDL / INSERT / single-row SELECT) with per-statement output #83

Description

@BorisTyshkevich

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

  • CREATE; INSERT; SELECT script runs in order with one summary-grid row per statement.
  • First failing statement halts the run and is shown as the last row; later statements skipped.
  • Single statement (± trailing ;) is byte-for-byte the current behavior.
  • Non-empty selection runs only the selected statement(s); empty selection runs the whole editor; Run button shows "Run selection" while text is highlighted.
  • Cancel aborts mid-script.
  • SELECT outcomes are valid JSON — single row as a bare object, multiple rows as a […] array — capped at 100 rows via max_result_rows/result_overflow_mode=break plus a JS line cap.
  • npm test green at the per-file coverage gate (splitter + JSON-assembly helper at 100%).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions