Skip to content

bug(sdk): keyset cursor drops boundary ties (strict <, single-column) + accumulates the cursor predicate #401

Description

@EricAndrechek

Area: sdk · query — bug (correctness + perf) · found via WaveHouse-Stats dogfooding

Expected: Paginating e.g. from('gh_events').orderBy('event_ts','desc').limit(50) through Result.next()/hasMore returns every row exactly once, and each page's request stays a fixed size.

Actual — two defects in QueryBuilder._fetchNext (clients/ts/src/query-builder.ts:239-260):

  • (a) drops boundary ties. The cursor keys only on orderBy[0] (:245) with a strict </> (cursorOp = dir === "desc" ? "lt" : "gt", :254). When more rows share the boundary event_ts than fit in a page, the remainder are excluded from the next page and never returned — a silent gap on any non-unique sort column. There is no compound/tiebreak cursor (e.g. a unique event_id secondary key).
  • (b) accumulates the cursor predicate. Each next() does filters: [...this._state.filters, cursorFilter] (:257), keeping the prior page's bound and appending a new one, so page N sends N event_ts < filters (only the tightest binds). Correctness holds, but request body + server-side parse cost grow linearly with pagination depth.

Impact: Silent row loss at page seams on non-unique-ordered columns — fine for a recent-activity feed, wrong for exhaustive/audit reads (Stats skipped a same-second CI event on "Load more" over the live feed). (b) is request-size cruft at shallow depths. Neither defect causes duplication.

Note (not a spec): a fix picks a tiebreak/compound cursor key and replaces rather than accumulates the cursor bound. Client-side keyset cursor — distinct from the backend-owned server-side cursor follow-up.

Related: #274


From WaveHouse-Stats WAVEHOUSE-FEEDBACK.md dogfooding (logged 2026-07-08, Stats #57); validated by code-read against 774faec (origin/main) on 2026-07-09.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/queryStructured query AST, SQL builderarea/sdkTypeScript SDK (clients/ts/)bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions