Repository navigation
fix(sdk): stop hardcoding received_timestamp in default query order - #272
Conversation
The query builder hardcoded `ORDER BY received_timestamp DESC` as both the default query order (_buildAST) and the pagination cursor column (_fetchNext). On bring-your-own-schema tables lacking that column, wh.from(table).fetch() emitted invalid SQL -> ClickHouse "Unknown expression identifier received_timestamp" -> HTTP 500. Make the fallback order opt-in via a new `options.defaultOrderBy` on ClientConfig (OrderClause | OrderClause[]), threaded client -> from() -> TableRef -> QueryBuilder (new 4th ctor arg; _clone carries it). A new _effectiveOrderBy() resolves the order: an explicit .orderBy() wins, else the configured default (never for aggregation queries), else none. _buildAST sends no order_by when empty; _fetchNext derives the cursor from the effective order with no hardcoded fallback and pins the next page to it; fetch() attaches next() only when an order exists but still reports hasMore honestly from the row count. Net: .fetch() works on any schema (no 500); deterministic pagination via an explicit .orderBy() or opt-in defaultOrderBy. Also sidesteps the #175 default-cursor tie bug for unconfigured clients. Stream dedup in stream/live-query.ts and stream/sse.ts still uses received_timestamp and is intentionally out of scope. Closes #270. Relates to #175; builds on #199. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Free Run ID: 📒 Files selected for processing (3)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe TS SDK query builder no longer emits a default ORDER BY; ast.order_by is emitted only when .orderBy() is set. fetch() reports hasMore when limit is hit but attaches next() only if an explicit order exists. Tests, e2e, and docs were updated to match this behavior. ChangesRemove hardcoded ORDER BY and make cursor pagination optional
🎯 3 (Moderate) | ⏱️ ~20 minutes Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
|
📚 Docs preview is live: https://6f496484-wavehouse-docs.wave-rf.workers.dev Updated for commit 5364110 |
The first cut of this branch added an opt-in `options.defaultOrderBy` to the SDK as a configurable fallback order. Drop it: a client-global default is the wrong granularity (one order for every table, and it re-trips the invalid-SQL 500 on any queried table that lacks that column) and duplicates each table's sort key in every client and language. Revert types.ts / client.ts / table.ts to main; QueryBuilder keeps only the core fix — no hardcoded received_timestamp default, ORDER BY emitted solely on an explicit .orderBy(). The right home for a default sort order is the backend, per-table, designed together with server-side pagination (subsuming #175); tracked as #274. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…274 Address PR review feedback: - Re-add ascending-cursor pagination coverage (the gt keyset path lost when the defaultOrderBy tests were removed): asserts the cursor filter, the pinned order, and the returned next page. - Add skipped placeholders (unit + e2e) for bare-.fetch() pagination, which should expose next() again once a backend per-table default order lands — TODO(#274). - Trim review-flagged verbose comments in query-builder.ts (the no-default ORDER BY line is self-evident; the fetch()/_fetchNext notes are now one-liners). No behavior change; net #270 fix is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
wh.from(table).fetch()hardcodedORDER BY received_timestamp DESCas both the default query order and the cursor-pagination column. On bring-your-own-schema tables without that column, the SDK emitted invalid SQL → ClickHouseUnknown expression identifier received_timestamp→ HTTP 500.This removes the hardcoded default. The SDK now sends
ORDER BYonly when the caller sets one via.orderBy():_buildASTno longer injects thereceived_timestampdefault — noorder_byis sent unless.orderBy()was called._fetchNextderives the keyset cursor from the explicit order (this._state.orderBy[0]), with no hardcoded fallback; it returns a terminal empty page if there is no cursor.fetch()attachesnext()only when there is an explicit order, but still reportshasMorehonestly from the row count — sohasMore: truewithnext: undefinedis a valid, honest state.Net:
.fetch()works on any schema (no 500). Deterministic pagination requires an explicit.orderBy(). This also sidesteps the #175 default-cursor tie bug — there is no longer a default cursor to tie on.Soft behavior change:
result.nextis nowundefined(rather than always defined whenhasMoreistrue) unless the query has an explicit.orderBy(). Callers paginating viaawait result.next!()should guard onresult.next(thesdk.md/ README examples already do).Why no client-side
defaultOrderByoption?An earlier draft of this PR added an opt-in
options.defaultOrderByto the SDK. It was dropped: a client-global default is the wrong granularity (one order for every table — and it re-introduces the invalid-SQL footgun for any queried table lacking that column), and it duplicates per-table sort-key knowledge in every client and language. The right home for a default sort order is the backend, per-table, designed together with pagination (ideally a server-side cursor token that would also lay the #175 tie bug to rest). Tracked as a follow-up.Tests
clients/ts/src/query-builder.test.ts: a bare.fetch()now omitsorder_by;.limit(n).fetch()with no order →hasMore: true, next: undefined. The explicit-.orderBy()keyset-cursor test still pins the pagination path.tests/e2e/sdk/query.test.ts: regression test — a table created withoutreceived_timestampreturnserror === nullfrom a bare.fetch(). The previously-skipped feat: embedded ClickHouse strict validation #175 default-cursor test is repurposed to assert the newhasMore-without-next()contract.Docs
docs/src/content/docs/sdk.md: the Pagination section now statesnext()requires an explicit.orderBy()(and corrects a pre-existing "exactly limit" → "at least limit" inaccuracy).CHANGELOG.md:### Fixedentry under## Unreleased.Out of scope
clients/ts/src/stream/live-query.tsandstream/sse.tsusereceived_timestampfor stream dedup — left as-is (the live ingest stream always carries that column).Closes #270. Relates to #175 (default-cursor tie); builds on #199 (query-builder structure). The backend-owned, per-table default sort order + server-side pagination is tracked as follow-up #274.
🤖 Generated with Claude Code