Skip to content

Commit 91a2cbf

Browse files
authored
fix(db): repair invalidated ordered queries (#1886)
* fix(db): repair invalidated ordered queries * docs: add ordered query repair changeset * fix(db): harden ordered repair lifecycle
1 parent b7a7d10 commit 91a2cbf

23 files changed

Lines changed: 2683 additions & 146 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@tanstack/db': patch
3+
'@tanstack/query-db-collection': patch
4+
---
5+
6+
Repair invalidated ordered queries with authoritative provider refetches and keep publication behind the repair. Revalidate active and cached Query Collection observers before applying repaired results.

‎docs/contributing/oracle-coverage.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ comment and the current API/architecture contract before extending its model.
105105
| Collection lifecycle | [mutation startup](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-mutation-startup-oracle.test.ts), [history](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-history.property.test.ts), [publication](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-publication.property.test.ts), [replay](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-replay-oracle.property.test.ts), [effect disposal](https://github.com/TanStack/db/blob/main/packages/db/tests/effect-disposal-oracle.test.ts) | Core Collection `insert`/`update`/`delete` admission while `startSync:false` is idle; ownership and phase histories; exact caller/error/publication evidence; late completion and restart. Query write utilities and effect self-dependent disposal remain separate contracts. |
106106
| Optimistic state | [history model](https://github.com/TanStack/db/blob/main/packages/db/tests/optimistic-history-oracle.ts), [generated histories](https://github.com/TanStack/db/blob/main/packages/db/tests/optimistic-transaction-oracle.property.test.ts), [outcomes](https://github.com/TanStack/db/blob/main/packages/db/tests/optimistic-history-outcomes.test.ts), [publication](https://github.com/TanStack/db/blob/main/packages/db/tests/optimistic-history-publication.test.ts) | Independent whole-row snapshots, rollback dependencies, metadata and prior-value events. Never rebase a pending snapshot merely to simplify the model. |
107107
| Drafts and native values | [proxy](https://github.com/TanStack/db/blob/main/packages/db/tests/proxy.test.ts), [detachment](https://github.com/TanStack/db/blob/main/packages/db/tests/proxy-detachment-contract.test.ts), [iteration](https://github.com/TanStack/db/blob/main/packages/db/tests/proxy-iteration-contract.test.ts) | Native-operation controls, exact patches and actual stored rows; alias/cycle/adversarial-key histories. General native-mutator and symbol-write support is not established by a plain-object oracle. |
108-
| Query DB and observer | [ownership](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/ownership-lifecycle.oracle.test.ts), [load lifecycle](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/load-subset-lifecycle-oracle.test.ts), [observer histories](https://github.com/TanStack/db/blob/main/packages/db/tests/live-query-observer-history.property.test.ts) | Real QueryClient boundary and a per-listener eligibility ledger, not a duplicate dispatch queue. Check reentry, peer survival, FIFO and disposal independently of final rows. |
109-
| Ordered acquisition | [pagination](https://github.com/TanStack/db/blob/main/packages/db/tests/query/pagination-oracle.property.test.ts), [ordered work](https://github.com/TanStack/db/blob/main/packages/db/tests/query/ordered-work-oracle.property.test.ts), [ordered lifecycle](https://github.com/TanStack/db/blob/main/packages/db/tests/query/ordered-lifecycle-oracle.property.test.ts) | Complete finite provider results, inherited collation with exact own-key request options, real lexical/numeric disagreement, pending windows, ties/nulls, ownership and documented repair timing. Request completion is not proof of unrequested source extent. |
108+
| Query DB and observer | [ownership](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/ownership-lifecycle.oracle.test.ts), [load lifecycle](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/load-subset-lifecycle-oracle.test.ts), [observer histories](https://github.com/TanStack/db/blob/main/packages/db/tests/live-query-observer-history.property.test.ts) | Real QueryClient boundary, applied-settlement barriers for existing and cached demand, and a per-listener eligibility ledger, not a duplicate dispatch queue. Check reentry, peer survival, FIFO and disposal independently of final rows. |
109+
| Ordered acquisition | [pagination](https://github.com/TanStack/db/blob/main/packages/db/tests/query/pagination-oracle.property.test.ts), [ordered work](https://github.com/TanStack/db/blob/main/packages/db/tests/query/ordered-work-oracle.property.test.ts), [ordered lifecycle](https://github.com/TanStack/db/blob/main/packages/db/tests/query/ordered-lifecycle-oracle.property.test.ts), [issue #1880 ordered-repair review](oracle-reviews/issue-1880-ordered-repair.md) | Complete finite provider results, inherited collation with exact own-key request options, real lexical/numeric disagreement, pending windows, ties/nulls, lease ownership, Effect callback gates, and documented repair timing. Request completion is not proof of unrequested source extent. |
110110
| Join equality and cold acquisition | `packages/db/tests/query/cold-join-reconciliation-oracle.test.ts` | Independent recomputation for cold acquisition plus direct join/predicate equivalence across established equality domains. Binary/string and nullish classes, replacement histories, raw on-demand values, and both scan/auto-index paths are explicit; compound join syntax is not claimed. |
111111
| Opaque backend pagination | [window oracle](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.oracle.test.ts), [cache histories](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.cache-oracle.test.ts), [cache publication](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.publication-oracle.test.ts), [browser acquisition boundaries](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.boundary-oracle.test.ts), [QueryCollection integration](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.integration.test.ts) | Full filter/sort/slice reference, opaque token transport, actual Query cache expiry/invalidation/GC, forced refresh during growth, protocol failure publication/recovery, bounded slice work, nested cancellation/replacement, reader abort, browser retry defaults, manual-write cache isolation, and production window publications. Stable backend sequences; not snapshot guarantees for changing endpoints. Peek-ahead remains enabled. |
112112
| Electric and TrailBase | [Electric histories](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-oracle.property.test.ts), [recovery histories](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-recovery-oracle.test.ts), [held resume snapshots](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-resume-snapshot-races.test.ts), [PostgreSQL semantics](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/e2e/sql-predicate-semantics.e2e.test.ts), [TrailBase contract](https://github.com/TanStack/db/blob/main/packages/trailbase-db-collection/tests/ORACLE.md) | Installed SDK delivery/framing, independent predicates, exact subscription arguments, restart/reset lineage, held certification races, and late errors. The recovery fixtures use a mocked ShapeStream; they do not establish live Electric-service framing or native persistence-host behavior. |
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Issue #1880 ordered-repair oracle review
2+
3+
## Reviewed state
4+
5+
- Base commit: `4c5a8de61843d6964a2580aded8a2d027b78e135`.
6+
- Reviewed change: the Git tree containing this record and the accompanying
7+
issue #1880 ordered-repair production, test, and architecture changes.
8+
- Primary executable owner:
9+
`packages/db/tests/query/pagination-oracle.property.test.ts`.
10+
- Adjacent owners:
11+
`ordered-work-oracle.property.test.ts`,
12+
`ordered-source-loader-state.test.ts`, `ordered-default-work.test.ts`, and
13+
`ordered-demand-retirement.test.ts`.
14+
- Query-backed application owner:
15+
`packages/query-db-collection/tests/load-subset-lifecycle-oracle.test.ts`.
16+
17+
This state description is intentional: a file cannot contain the hash of a
18+
commit that already contains that file. The eventual commit or pull request
19+
identifies the exact immutable tree; this record fixes its comparison base.
20+
21+
## Claim and limits
22+
23+
The reviewed oracle checks that a settled, expressible ordered window repairs a
24+
visible delete or first-provider-term change with an inherited-predicate,
25+
normalized-order, zero-transport-offset prefix followed by the required tie and
26+
refill requests. It also checks row recomputation, publication, failure upgrade,
27+
and acquisition ownership around that path.
28+
29+
It also checks two termination boundaries that the original grammar missed. An
30+
underfilled indexed repair must retire superseded finite leases when its next
31+
page repeats the last count and boundary. An unindexed repair started by a
32+
window operation must do the same when its prefix count repeats and no more
33+
data is needed. A Query-backed refetch must remain pending while
34+
`deferDataRefresh` prevents its result from being applied, for both existing
35+
and newly observed cached demands.
36+
37+
The follow-up review also extends the boundary around that repair. A mutation
38+
that supersedes an in-flight repair must start its replacement before the old
39+
chain issues more tie or refill work. Effect callbacks must survive an obsolete
40+
repair abort during truncate replay, suppress asynchronous initial ordered rows
41+
under `skipInitial`, and preserve real updates that contain object-valued Sets.
42+
Query-backed refetches must wait for post-write authority, hide the internal
43+
`refetch` control from query-function metadata, and reject the released caller.
44+
An authoritative refetch still replaces an in-flight shared Query attempt; that
45+
is required to revalidate the demand rather than reuse stale work.
46+
47+
The authority is the Ordered loading and Atomic window publication contract in
48+
`packages/db/src/query/live/ARCHITECTURE.md`, plus the applied-settlement and
49+
acquisition-release contract in `docs/guides/collection-options-creator.md`.
50+
The evidence is bounded to the test grammar, in-memory on-demand adapter seam,
51+
and real QueryClient test boundary. It does not certify backend query plans,
52+
network volume, latency, complete SQL predicate semantics, or every
53+
`requiresFullSource` compiler branch.
54+
55+
## ORC-001 through ORC-011
56+
57+
| Requirement | Outcome | Evidence |
58+
| --- | --- | --- |
59+
| ORC-001: contract authority and limits | Pass | The pagination-oracle header names the architecture authority and its limits. The collection-options guide defines applied settlement and acquisition release. This record narrows the repair claim to the tested grammar, adapter seam, and QueryClient boundary. |
60+
| ORC-002: independent judgment | Pass | `referenceWindowRows` filters/sorts/slices plain arrays independently of the ordered loader. Exact request checks use literal normalized paths, directions, field absence, and reference predicate evaluation rather than the production request classifier. |
61+
| ORC-003: distinguishable responsibilities | Pass | The file keeps scenario arbitraries, array recomputation, the real Collection driver, request/publication recorders, and refinement helpers separate. `PendingMutationResult` carries the work observation from the driver to both generated campaigns. |
62+
| ORC-004: generated-history grammar controls | Pass | The bounded grammar crosses insert/update/delete, resolve/reject, before/after settlement, ranks, directions, and limits. Named products reconstruct every mutation/outcome/timing cell; the `[0,1,1,2]` case pins tie plus refill, and missing-row deletion is rejected. Existing fault/ablation cases show the contribution of publication, window, settlement, callback, delete-value, and final-value observations. |
63+
| ORC-005: production path and observation | Pass | The pagination driver uses `createLiveQueryCollection` over a real on-demand Collection. It compares visible rows and callback cuts, records the complete post-mutation adapter request sequence, and observes acquisition releases before cleanup. The Query DB driver uses a real QueryClient and observes transport calls, promise settlement, and applied Collection rows for existing and cached demands. |
64+
| ORC-006: checker calibration | Pass | `rejects a malformed bounded-repair request trace` supplies the old checker's plausible survivor: missing predicate, wrong same-length order terms, `offset: 99`, and an extra request. The exact trace checker rejects it. The preserved RED runs show that the former implementation produced correct final rows while releasing zero superseded leases, and settled both deferred refetch forms before application. Existing captured-history faults calibrate value/publication checks. |
65+
| ORC-007: fixed/random campaigns and replay | Pass | The fixed-seed and random/replayed `pagination.pending-mutation` properties invoke the same arbitrary, `runPendingMutationScenario`, `PendingMutationResult` recorder, request-work checker, and run budget. `oracleRandomParameters` accepts the configured seed and shrink path for direct replay. |
66+
| ORC-008: stateful-model minimality | Not applicable | The repair adds a request-trace result, not new reference-model state, and does not merge or remove model states. |
67+
| ORC-009: vocabulary mapping | Pass | In this record, a repair prefix is an ordered request without a cursor or transport offset; a tie is a predicate-only request for the first provider term; a refill is an ordered cursor request. Full-source recovery omits `orderBy`, `limit`, `cursor`, and `offset`, but still inherits the subscription `where` predicate. The work checker distinguishes a filtered full-source predicate from a tie by evaluating it over distant ranks. `mutationRepairRequests` means adapter requests started after the modeled source mutation, not rows delivered locally. |
68+
| ORC-010: failure fidelity and cleanup | Pass | `PendingMutationTraceAssertionError` retains the violated checkpoint and delivered-row trace. `withHistoryCleanup` preserves the primary failure while settling subscriptions, outstanding requests, the live query, and source cleanup. Partial-write rejection and repeated-continuation termination have direct-loader ownership witnesses. The Query DB cases release their barrier and settle refetch before cleanup. |
69+
| ORC-011: independent second formulation | Pass | The public pagination history and direct `OrderedSourceLoader` state test distinguish the shared-fault risk that correct final rows could hide a malformed or over-broad provider request. Ordered-work parity separately compares Collection and Effect publication behavior. |
70+
71+
ORC-012 is satisfied by this versioned, base-identified closeout record and its
72+
link from `docs/contributing/oracle-coverage.md`.
73+
74+
## Calibration and known omissions
75+
76+
The hostile trace proves that checking only order-term count, limit, and missing
77+
cursor would accept a semantically wrong request. The capped-provider case
78+
proves the deterministic repair chain `prefix -> tie(rank 1) -> cursor refill ->
79+
tie(rank 2)`. A semantic predicate check distinguishes a repair tie from a
80+
filtered full-source fallback. The partial-write integration witness proves the
81+
next request is exactly full-source; the direct-loader ownership test proves
82+
that lease releases occur in order `[3, 0, 1, 2]` after bounded repair fails.
83+
84+
The underfilled integration witness proves that a three-row source under a
85+
ten-row limit releases all three superseded finite acquisitions before cleanup.
86+
Direct loader witnesses separately cover repeated indexed-page and unindexed-
87+
prefix continuations, then start another repair to prove the loader is usable.
88+
The Query DB witness holds result application after refetch transport succeeds;
89+
both an existing observer and a cached observer must remain pending. Additional
90+
Query DB witnesses cover post-write authority, metadata isolation, and final-
91+
owner release. Ordered-work witnesses cover repair replacement before stale
92+
continuation, truncate replay handoff for Effects, and asynchronous
93+
`skipInitial`. The Effect and utility owners pin object-valued Set updates and
94+
Set equality independently.
95+
96+
The complete `requiresFullSource` classifier is documented in architecture but
97+
is not enumerated here branch by branch. Adapter-specific transfer and query
98+
execution remain adapter conformance concerns. Effect failure publication is
99+
covered by source-error disposal plus direct failure ownership evidence; this
100+
review adds the previously missing held-success callback witness.
101+
102+
## Verification
103+
104+
Closeout requires the focused changed owners, both pending-mutation campaigns,
105+
the complete `@tanstack/db` and `@tanstack/query-db-collection` test suites, both
106+
package builds, changed-file lint and format checks, and `git diff --check`.
107+
Exact final counts belong in the pull request or issue receipt because they
108+
describe the immutable execution, not the oracle contract.

‎packages/db/src/collection/subscription.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type { CollectionImpl } from './index.js'
2929
import type { Deferred } from '../deferred.js'
3030

3131
type RequestSnapshotOptions = {
32+
refetch?: boolean
3233
where?: BasicExpression<boolean>
3334
signal?: AbortSignal
3435
optimizedOnly?: boolean
@@ -44,6 +45,7 @@ type RequestSnapshotOptions = {
4445
}
4546

4647
type RequestLimitedSnapshotOptions = {
48+
refetch?: boolean
4749
orderBy: OrderBy
4850
limit: number
4951
/** A single cursor value; composite cursor inputs are rejected. */
@@ -1159,6 +1161,7 @@ export class CollectionSubscription
11591161
// Request the sync layer to load more data
11601162
// don't await it, we will load the data into the collection when it comes in
11611163
const loadOptions: LoadSubsetOptions = {
1164+
...(opts?.refetch ? { refetch: true } : {}),
11621165
where: stateOpts.where,
11631166
signal: opts?.signal,
11641167
subscription: this,
@@ -1357,6 +1360,7 @@ export class CollectionSubscription
13571360
* Note 2: it does not send keys that have already been sent before.
13581361
*/
13591362
requestLimitedSnapshot({
1363+
refetch,
13601364
orderBy,
13611365
limit,
13621366
minValues,
@@ -1524,6 +1528,7 @@ export class CollectionSubscription
15241528
// Note: `where` does NOT include cursor expressions - they are passed separately
15251529
// The sync layer can choose to use cursor-based or offset-based pagination
15261530
const loadOptions: LoadSubsetOptions = {
1531+
...(refetch ? { refetch: true } : {}),
15271532
where, // Main filter only, no cursor
15281533
limit,
15291534
orderBy,

0 commit comments

Comments
 (0)