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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions