Skip to content

feat: query variables ({name:Type}) with native param_ substitution (#134)#138

Merged
BorisTyshkevich merged 2 commits into
mainfrom
feat/query-variables-134
Jul 3, 2026
Merged

feat: query variables ({name:Type}) with native param_ substitution (#134)#138
BorisTyshkevich merged 2 commits into
mainfrom
feat/query-variables-134

Conversation

@BorisTyshkevich

@BorisTyshkevich BorisTyshkevich commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

What & why

Closes #134.

Adds query variables: ClickHouse typed placeholders {name:Type} are detected while editing, and a single-line strip below the editor toolbar shows one input per detected variable (it scrolls horizontally when there are many, and is hidden when there are none). Run is disabled until every variable has a value.

On execution the entered values are sent as ClickHouse's native param_<name> query-string arguments, so the server substitutes them per the declared type — injection-safe and type-correct (String/Identifier/DateTime/Array(…)/Map(…) all work). The SQL text itself is sent unchanged; the app never string-rewrites the query.

Substitution is gated on row-returning statements only, so a CREATE VIEW … {x:String} … definition is stored with its placeholder intact — which is exactly how ClickHouse parameterized views work. The gate and the param-passing live at the shared execution choke points (run / runScript / export), so Run, ⌘↵, Explain, and Export all honor them.

Design decisions (agreed on the issue)

  • Native param_* substitution, not client-side string rewriting.
  • Substitute reads only (SELECT/WITH…SELECT/EXPLAIN); never DDL/CREATE VIEW.
  • Values are shared by variable name and persisted (asb:varValues): a value typed once is reused/prefilled wherever the same variable appears — across queries, tabs, history/library loads, and reloads. (Updated from the original "per-tab, in-memory" decision per review feedback on the PR.)
  • Run disabled until all filled; the strip is a single non-wrapping row that scrolls at any width (verified at 360px on chromium + webkit — single row, scrolls, no page clip).

Distinct from #39's {{name}} composable-query CTE-merge (different syntax and purpose) — noted in the roadmap and the issue.

How it's built

  • New pure src/core/query-params.jsdetectParams (lexer skips string literals + comments and handles nested-paren types, mirroring sql-split.js), readStatementParams, paramArgs, missingValues, unfilledParams100% covered.
  • src/ui/app.js — variable strip render, the shared varGateBlocked gate, and param_* merge into run/runScript/exportDirect/exportScript. No new dependency; rides the existing fetch seam.

Follow-up (filed separately, not in this PR)

Checklist

🤖 Generated with Claude Code

…134)

Detect ClickHouse typed placeholders `{name:Type}` while editing and show a
single-line input strip below the editor toolbar (scrolls horizontally, hidden
when none). Run is disabled until every detected variable has a value.

On execution the values ride along as ClickHouse's native `param_<name>`
query-string arguments, so the server substitutes them per the declared type
(injection-safe; String/Identifier/DateTime/Array/Map all work) — the SQL text
is sent unchanged. Substitution is gated on row-returning statements, so a
`CREATE VIEW … {x:String} …` definition is stored verbatim (matching ClickHouse
parameterized views). The gate + param-passing live at the execution choke
points (run/runScript/export), so Run, ⌘↵, Explain, and Export all honor them.

- New pure `src/core/query-params.js` (detect/read/paramArgs/missing/unfilled),
  100% covered; lexer skips string literals + comments like sql-split.js.
- Distinct from #39's `{{name}}` composable-query CTE-merge (different syntax).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
…back)

Move variable values from per-tab, in-memory storage to a single shared,
name-keyed store on `state.varValues`, persisted to localStorage (asb:varValues):

- A value typed for `{database:String}` is reused — prefilled automatically —
  by every query that references the same variable, no retyping.
- Values survive tab switches, loading queries from history/library, and page
  reloads.

The variable strip prefills each input from the shared store and writes back
(persisting) on edit; the Run gate, run/runScript, and both export paths all read
the shared store. The strip's rebuild signature no longer keys on tab id, since
values are global by name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014X92mUpk2ts56USdMGKaNa
@BorisTyshkevich BorisTyshkevich merged commit 6ac308e into main Jul 3, 2026
6 checks passed
@BorisTyshkevich BorisTyshkevich deleted the feat/query-variables-134 branch July 3, 2026 11:01
@BorisTyshkevich BorisTyshkevich mentioned this pull request Jul 3, 2026
30 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support variables in SELECT queries

1 participant