Skip to content

fix(query-db): clean up empty ownership sets#1672

Open
KyleAMathews wants to merge 8 commits into
mainfrom
query-ownership-empty-set-cleanup
Open

fix(query-db): clean up empty ownership sets#1672
KyleAMathews wants to merge 8 commits into
mainfrom
query-ownership-empty-set-cleanup

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Cleans up Query DB ownership bookkeeping so empty row/query ownership sets are not retained, while preserving the semantic distinction between an unresolved query and a resolved query with an empty result.

This keeps ownership state bounded across subset unloads, cache expiry, persisted-row hydration, and empty revalidation without deleting rows that are still shared by another query.

Design

Root cause

rowToQueries and queryToRows used empty Sets both as stale bookkeeping and as a marker that a query's ownership had been resolved. Removing those empty entries directly would lose the resolved-empty baseline and could cause hydration or revalidation to infer ownership again incorrectly.

Approach

  • Delete reverse and forward ownership-map entries as soon as their sets become empty.
  • Track authoritative query ownership separately in resolvedOwnershipQueries, including valid empty results.
  • Clear that marker when retained ownership expires or the query/collection is cleaned up.
  • Add test-only ownership inspection and lifecycle coverage for overlapping subsets, cache GC, persisted hydration, empty revalidation, and retained ownership expiry.

Invariants

  • Ownership maps contain only non-empty sets.
  • A row is removed only after its final query owner is removed.
  • A resolved empty result remains authoritative until its query lifecycle ends.
  • Persisted retention markers and in-memory ownership markers expire together.

Non-goals and tradeoffs

  • No public API or persistence-format changes.
  • The separate resolved-query set adds a small amount of state, but avoids overloading empty map entries and is explicitly bounded by existing cleanup paths.
  • Internal ownership-map inspection is exposed only in test environments.

Verification

  • pnpm --filter @tanstack/query-db-collection test -- query.test.ts
    • 232 tests passed and type assertions reported no errors in the target suite.
    • Command exits non-zero because Vitest also type-checks db-collection-e2e sources that cannot resolve @tanstack/electric-db-collection in this worktree (3 unrelated source errors).

Files

  • packages/query-db-collection/src/query.ts — compact ownership maps and preserve resolved-empty semantics with bounded lifecycle tracking.
  • packages/query-db-collection/tests/query.test.ts — characterize and verify ownership cleanup, hydration, shared rows, cache expiry, and retained-query cleanup.

Summary by CodeRabbit

  • Bug Fixes

    • Improved cleanup of query ownership state when relationships are fully removed.
    • Empty query results now correctly remove obsolete ownership information without affecting authoritative results.
    • Fixed retained-row cleanup during query revalidation, unloading, and cache expiration.
    • Improved hydration behavior to ensure previously known rows still exist before reuse.
  • Tests

    • Expanded coverage for overlapping queries, retained rows, revalidation, invalidation, and expiration scenarios.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates query ownership bookkeeping to remove empty relationship entries, track authoritative ownership resolution, adjust persisted baseline hydration, and clear ownership state during cleanup. It adds test-only ownership-map inspection, lifecycle characterization tests, TTL assertions, a task report, and a patch changeset.

Changes

Query ownership lifecycle

Layer / File(s) Summary
Ownership bookkeeping primitives
packages/query-db-collection/src/query.ts
Adds resolved ownership tracking, removes empty row/query relationships safely, and exposes ownership maps through a test-only hook.
Hydration and lifecycle cleanup
packages/query-db-collection/src/query.ts
Uses resolved ownership during hydration, validates persisted known rows, and clears markers during placeholder and query cleanup.
Lifecycle characterization and release records
packages/query-db-collection/tests/query.test.ts, .changeset/clean-query-ownership.md, .superpowers/sdd/task-2-report.md
Tests overlapping owners, cache expiry, retained-row revalidation, and TTL cleanup while documenting verification and the patch release.Estimated code review effort: 3 (Moderate)

Sequence Diagram(s)

sequenceDiagram
  participant QueryResult
  participant OwnershipMaps
  participant PersistedBaseline
  participant Collection
  QueryResult->>OwnershipMaps: mark query ownership resolved
  QueryResult->>OwnershipMaps: add row/query relationships
  PersistedBaseline->>OwnershipMaps: check resolved ownership
  PersistedBaseline->>Collection: validate known rows
  Collection-->>PersistedBaseline: return existing rows
  QueryResult->>OwnershipMaps: remove final relationships for empty result
Loading

Possibly related PRs

  • TanStack/db#1626: Updates persisted row-owner metadata used during query-owned row reconciliation.
  • TanStack/db#1664: Refactors the query and row ownership helpers updated by this PR.

Suggested reviewers: kevin-dp, spokodev, samwillis, claude[bot], delphinaubin

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: cleaning up empty ownership sets.
Description check ✅ Passed The description is detailed and covers the change, design, verification, and files, though it doesn't use the template's exact headings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch query-ownership-empty-set-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 13, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@1672

@tanstack/browser-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/browser-db-sqlite-persistence@1672

@tanstack/capacitor-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/capacitor-db-sqlite-persistence@1672

@tanstack/cloudflare-durable-objects-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/cloudflare-durable-objects-db-sqlite-persistence@1672

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@1672

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@1672

@tanstack/db-sqlite-persistence-core

npm i https://pkg.pr.new/@tanstack/db-sqlite-persistence-core@1672

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@1672

@tanstack/electron-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/electron-db-sqlite-persistence@1672

@tanstack/expo-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/expo-db-sqlite-persistence@1672

@tanstack/node-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/node-db-sqlite-persistence@1672

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@1672

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@1672

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@1672

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@1672

@tanstack/react-native-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/react-native-db-sqlite-persistence@1672

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@1672

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@1672

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@1672

@tanstack/tauri-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/tauri-db-sqlite-persistence@1672

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@1672

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@1672

commit: 9c3c658

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 125 kB

ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/collection/change-events.js 1.43 kB
packages/db/dist/esm/collection/changes.js 1.38 kB
packages/db/dist/esm/collection/cleanup-queue.js 810 B
packages/db/dist/esm/collection/events.js 434 B
packages/db/dist/esm/collection/index.js 3.62 kB
packages/db/dist/esm/collection/indexes.js 1.99 kB
packages/db/dist/esm/collection/lifecycle.js 1.69 kB
packages/db/dist/esm/collection/mutations.js 2.47 kB
packages/db/dist/esm/collection/state.js 5.48 kB
packages/db/dist/esm/collection/subscription.js 3.74 kB
packages/db/dist/esm/collection/sync.js 2.88 kB
packages/db/dist/esm/collection/transaction-metadata.js 144 B
packages/db/dist/esm/deferred.js 207 B
packages/db/dist/esm/errors.js 5.1 kB
packages/db/dist/esm/event-emitter.js 748 B
packages/db/dist/esm/index.js 3.16 kB
packages/db/dist/esm/indexes/auto-index.js 829 B
packages/db/dist/esm/indexes/base-index.js 767 B
packages/db/dist/esm/indexes/basic-index.js 2.06 kB
packages/db/dist/esm/indexes/btree-index.js 2.19 kB
packages/db/dist/esm/indexes/index-registry.js 820 B
packages/db/dist/esm/indexes/reverse-index.js 557 B
packages/db/dist/esm/live-query-adapter.js 318 B
packages/db/dist/esm/local-only.js 916 B
packages/db/dist/esm/local-storage.js 2.12 kB
packages/db/dist/esm/optimistic-action.js 359 B
packages/db/dist/esm/paced-mutations.js 496 B
packages/db/dist/esm/proxy.js 3.75 kB
packages/db/dist/esm/query/builder/functions.js 1.47 kB
packages/db/dist/esm/query/builder/index.js 5.84 kB
packages/db/dist/esm/query/builder/ref-proxy.js 1.24 kB
packages/db/dist/esm/query/compiler/evaluators.js 1.89 kB
packages/db/dist/esm/query/compiler/expressions.js 430 B
packages/db/dist/esm/query/compiler/group-by.js 3.56 kB
packages/db/dist/esm/query/compiler/index.js 6.67 kB
packages/db/dist/esm/query/compiler/joins.js 2.5 kB
packages/db/dist/esm/query/compiler/lazy-targets.js 923 B
packages/db/dist/esm/query/compiler/order-by.js 1.74 kB
packages/db/dist/esm/query/compiler/select.js 1.53 kB
packages/db/dist/esm/query/effect.js 4.77 kB
packages/db/dist/esm/query/expression-helpers.js 1.43 kB
packages/db/dist/esm/query/ir.js 1.25 kB
packages/db/dist/esm/query/live-query-collection.js 360 B
packages/db/dist/esm/query/live/collection-config-builder.js 9.1 kB
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 1.93 kB
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/live/utils.js 1.81 kB
packages/db/dist/esm/query/optimizer.js 2.92 kB
packages/db/dist/esm/query/predicate-utils.js 2.97 kB
packages/db/dist/esm/query/query-once.js 359 B
packages/db/dist/esm/query/subset-dedupe.js 960 B
packages/db/dist/esm/scheduler.js 1.3 kB
packages/db/dist/esm/SortedMap.js 1.3 kB
packages/db/dist/esm/strategies/debounceStrategy.js 247 B
packages/db/dist/esm/strategies/queueStrategy.js 428 B
packages/db/dist/esm/strategies/throttleStrategy.js 246 B
packages/db/dist/esm/transactions.js 3.04 kB
packages/db/dist/esm/utils.js 927 B
packages/db/dist/esm/utils/array-utils.js 273 B
packages/db/dist/esm/utils/browser-polyfills.js 304 B
packages/db/dist/esm/utils/btree.js 5.61 kB
packages/db/dist/esm/utils/comparison.js 1.11 kB
packages/db/dist/esm/utils/cursor.js 457 B
packages/db/dist/esm/utils/index-optimization.js 2.39 kB
packages/db/dist/esm/utils/type-guards.js 157 B
packages/db/dist/esm/utils/uuid.js 449 B
packages/db/dist/esm/virtual-props.js 360 B

compressed-size-action::db-package-size

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 4.22 kB

ℹ️ View Unchanged
Filename Size
packages/react-db/dist/esm/index.js 249 B
packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.32 kB
packages/react-db/dist/esm/useLiveQuery.js 1.33 kB
packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 567 B
packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/query-db-collection/src/query.ts`:
- Line 1656: Update collection cleanup to build its key set from
state.observers, queryToRows, and resolvedOwnershipQueries so retained ownership
queries are removed before TTL expiry or revalidation; preserve deletion of
queryToRows, rowToQueries, and resolvedOwnershipQueries for every collected key.
Add a regression test covering collection.cleanup() while a retained placeholder
is pending, asserting all three structures are empty.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 225651f4-0c6e-4d6d-b6cc-a51d4b9b8966

📥 Commits

Reviewing files that changed from the base of the PR and between 09d7d67 and 29eefca.

📒 Files selected for processing (4)
  • .changeset/clean-query-ownership.md
  • .superpowers/sdd/task-2-report.md
  • packages/query-db-collection/src/query.ts
  • packages/query-db-collection/tests/query.test.ts


state.observers.delete(hashedQueryKey)
queryToRows.delete(hashedQueryKey)
resolvedOwnershipQueries.delete(hashedQueryKey)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Include retained ownership keys in explicit collection cleanup.

Line 1656 is never reached for retained queries removed from state.observers at Line 1726. Since cleanup() only iterates observer keys, cleanup before TTL expiry/revalidation leaves queryToRows, rowToQueries, and resolvedOwnershipQueries retained.

Build the cleanup key set from state.observers, queryToRows, and resolvedOwnershipQueries. Add a regression test that calls collection.cleanup() while a retained placeholder is still pending and asserts all three structures are empty.

As per coding guidelines, “Always add unit tests that reproduce a bug before fixing it to ensure the bug is fixed and prevent regression.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/query-db-collection/src/query.ts` at line 1656, Update collection
cleanup to build its key set from state.observers, queryToRows, and
resolvedOwnershipQueries so retained ownership queries are removed before TTL
expiry or revalidation; preserve deletion of queryToRows, rowToQueries, and
resolvedOwnershipQueries for every collected key. Add a regression test covering
collection.cleanup() while a retained placeholder is pending, asserting all
three structures are empty.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant