Skip to content

#447 follow-ups: option-column type checking is Test-only, and dedup runs after the server LIMIT #461

Description

@BorisTyshkevich

Rewritten 2026-07-27 after a code review against main. The original body was
written during #447 phase 2 and is now wrong in both halves — item 2 is
implemented
(only its regression test is missing), and item 1's citation names a
file that no longer exists, though the defect it describes is real and open.

1. The two-String-column rule is enforced on ONE path, and the batch still cannot check it — OPEN

Corrected citation. The original said the only caller is "the variable editor's
Test action (ui/variable-editor.ts)". That file was deleted by #457 along with
the option-SQL drawer and its Test action. The check was then re-hosted by #465
— shipped in PR #493 (fix(#465): validate a dashboard-variable tab's Run as a variable option query, merged 2026-07-27, a0417ed; #465 closed as completed) —
on the dashboard-variable tab's ordinary Run:

  • src/ui/workbench/workbench-session.ts:441-448optionSqlDiagnostics(sql) runs
    first and sends nothing when the text is already knowably wrong;
  • :475 — the SQL executes through compileOptionProbe(sql), not raw, so Run cannot
    pass for SQL the batch would reject (compileOptionProbe shares nestBounded with
    compileVariableOptionBatch);
  • :487-489 — on a clean transport, validateOptionColumns(result.columns) and its
    message becomes the result's error.

The asymmetry is unchanged. readVariableOptionBatch
(src/core/variable-options.ts:413) checks only that the merged response has the
three columns the compiler projects, because UNION ALL reports ONE merged column
list for every branch — names from the first branch, types promoted to a supertype —
so per-branch column types are not visible in the response at all. The code now
documents this deliberately at variable-options.ts:351-361 and :310-322.

Consequence, still live: SELECT id, name FROM countries with id UInt64 runs fine
in the refresh batch and its options are offered (the transport stringifies every
cell), while Run correctly reports "Both option columns must be String". Run is
stricter than the runtime, and a user can "fix" SQL that was working.

What is missing

Nothing warns the user before they hit the difference. There is no proactive hint
about the contract anywhere in the variable tab — the strings "two columns" /
"both … String" exist only inside the two diagnostics, i.e. after a failure.

Options, unchanged from the original filing:

  1. Accept the asymmetry and state it in the variable tab's hint — cheapest, and
    consistent with the spec designating the single-variable path as the diagnostic
    path. The code already documents the asymmetry; this only surfaces it.
  2. Make Run as lenient as the runtime — weakens the contract Replace curated Dashboard filters with inferred Variables and batched option queries #447 states.
  3. Project a per-branch type witness into the batch (an earlier draft used
    toTypeName(tuple(*))) — it works, but costs a fourth column on every row of
    every refresh, and was cut as engineering past the spec.

Recommended: option 1.

2. Duplicate collapsing vs. the server-side LIMIT — IMPLEMENTED, test missing

The original concern was that duplicates collapse client-side after each branch's
server LIMIT 1001, so SELECT country, country FROM visits could return an
arbitrary short list presented as complete, with no truncation warning because
the collapsed count lands under the cap.

That is fixed on main, by the detection route this issue proposed — but it
shipped under an unrelated issue number, which is why it looks untouched.

Provenance (why nobody could find this)

Commit Merged as Issue What it did
16cabe7 #447 phase 2 #447 Introduced the truncation flag, derived from the kept count — the state this issue was filed against
249f7a0 PR #469 (feat(#468): restore the searchable multi-select for Array(scalar T) Dashboard variables, merged 2026-07-26) #468 fix(#468): close three selection-destroying paths found in review — switched the flag to the raw count. This is item 2's actual fix.

So item 2 was resolved as collateral in a review of an unrelated issue — one of
#468's "three selection-destroying paths." #468's body never mentions #461, PR #469's
title does not either, and this issue was never updated. The only trace linking
the fix back here is the (#461) reference inside the code comment at
src/core/variable-options.ts:436.

That also explains the missing test below: the fix arrived under #468's
selection-preservation lens (don't prune a committed selection against a partial
list) rather than under this issue's completeness lens, so it got a test for #468's
concern and none for #461's own collapse case.
readVariableOptionBatch (src/core/variable-options.ts:432-441) counts RAW rows
before the blank/duplicate filters and flags from that count:

const raw = rawCount.get(name)! + 1;
rawCount.set(name, raw);
if (raw > VARIABLE_OPTION_CAP) truncated.add(name);

Its comment cites this issue by number and states the reasoning exactly: "a query
returning 1,001 rows of 500 distinct values is still an incomplete list"
.

It also reaches the user, which the original did not ask for:

  • dashboard-viewer-session.ts:1061-1066 raises a variable-options-truncated
    diagnostic naming the affected variables — "Narrow the option SQL to see the rest."
  • :1071 passes the flag into applyOptions, so a committed selection is not pruned
    against a list known to be incomplete.

The one thing left: make it falsifiable

No test covers the collapsing case, and this issue is cited in no test. The only
truncation test — tests/unit/variable-options.test.ts:427 "caps each variable
independently and reports which were truncated"
— pushes VARIABLE_OPTION_CAP + 5
rows of distinct values (v${i}), so raw == kept and it cannot distinguish
raw-count logic from kept-count logic.

Concretely: swapping line 441 for a kept-count test such as
if (options.length >= VARIABLE_OPTION_CAP) still passes the whole existing suite,
while silently restoring the exact bug this issue reported.

Add one regression test: a branch returning VARIABLE_OPTION_CAP + 1 raw rows
that collapse to a handful of distinct values must still land in truncated.
Optionally assert the variable-options-truncated diagnostic in
dashboard-viewer-session.test.ts for the same shape. Cite #461 in the test
name, so the next reader does not have to reconstruct the provenance table above.

Scope

Two small, independent changes:

  • a hint in the variable tab stating the two-String-column contract (item 1,
    option 1);
  • the collapse-case regression test, and optionally its viewer-session counterpart
    (item 2).

Neither blocks #447 phase 3.

Metadata

Metadata

Assignees

No one assigned

    Labels

    inboxFiled mid-task; not yet triaged into the roadmap

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions