Skip to content

Cap SELECT result rows (default 500) with a 100/500/1000/5000/10000 selector #86

Description

@BorisTyshkevich

Part of #68 (Roadmap to 1.0.0).

Problem

A SELECT can return far more rows than anyone will scroll through, and today nothing caps the fetchrunQuery's streaming path accumulates every row (src/core/stream.js:28, src/net/ch-client.js:405). There's only a client-side display cap (VIS_CAP = 5000, src/ui/results.js:21): the rows are still all pulled over the wire, just not all rendered. A wide/huge result wastes bandwidth and memory and can hang the tab.

Proposed solution

Cap result rows with a default of 500 and a selector offering 100 / 500 / 1000 / 5000 / 10000.

Settled decisions:

  • Mechanism: hybrid. Server-side max_result_rows = N + result_overflow_mode = 'break' so ClickHouse stops cleanly at the cap (no error, no further data pulled), plus a small client-side guard that trims the block-boundary overage break can leave and flags the result as capped.
  • Selector re-runs immediately — changing the limit re-runs the current query with the new cap (server-side, so raising it genuinely fetches more).
  • Global, persisted preference — one localStorage pref (like theme/splitters), default 500, applies to all tabs, survives reload.
  • Scope: normal result queries only (Table + explicit-FORMAT SELECTs). EXPLAIN / PIPELINE / ESTIMATE are exempt — small output, and max_result_rows would truncate a plan oddly.

This is independent of #83's fixed 100-row cap for script (multiquery) SELECTs — different context, different cap.

Scope

  • src/state.js — add KEYS.resultRowLimit = 'asb:resultRowLimit' and resultRowLimit (default 500, read from localStorage at init). The option list [100, 500, 1000, 5000, 10000] as a constant.
  • src/net/ch-client.jsrunQuery accepts o.resultRowLimit; when set (and not an EXPLAIN format), chUrl adds max_result_rows=<N> + result_overflow_mode=break via the existing extra dict.
  • src/core/stream.jsapplyStreamLine (or the result model) tracks a row counter; stop pushing past the cap and set result.capped = true once the cap is reached. Pure, 100% covered.
  • src/ui/results.js — row-limit <select> in the result toolbar (after the view tabs, hidden for EXPLAIN views); on change, save the pref and re-run. Make the display cap follow resultRowLimit (so 10000 actually renders 10000, not the old fixed 5000). Show a "showing first N (capped)" badge in the stats row when result.capped.
  • src/ui/app.js — pass resultRowLimit: app.state.resultRowLimit into ch.runQuery; wire the selector's change → savePref + actions.run(); don't pass the cap for EXPLAIN runs.

Acceptance

  • A SELECT over a huge table fetches at most the selected cap (default 500) — verified by row count and that the stream stops (no full pull).
  • Selector offers 100/500/1000/5000/10000; default 500; choice persists across reloads and across tabs.
  • Changing the selector re-runs the current query with the new cap.
  • When the cap is hit, a "first N (capped)" indicator shows in the stats row.
  • Display cap follows the selected limit (10000 renders 10000).
  • EXPLAIN / PIPELINE / ESTIMATE runs are unaffected (no max_result_rows).
  • npm test green at the per-file coverage gate (state/stream/net/results changes 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