Skip to content

fix(db): isolate reused subquery placements - #1878

Open
KyleAMathews wants to merge 2 commits into
pr-1876from
fix-reused-subquery-placements
Open

KyleAMathews wants to merge 2 commits into
pr-1876from
fix-reused-subquery-placements

Conversation

@KyleAMathews

Copy link
Copy Markdown
Collaborator

Reusing one subquery builder in both FROM and JOIN now behaves like two lexical placements of the same relation. Same-key self-joins no longer collapse to an empty result.

Root cause

CollectionRef.sourceId identifies a lexical source position, but embedding a query builder reused its existing IR and therefore reused its source IDs. The live-query graph then treated both sides of a self-join as one source position. Changing or bypassing the compiled-query cache did not help because the collision existed before compilation.

Approach

  • Clone a subquery's IR when it is placed into another query.
  • Preserve the query's expressions, callbacks, Collections, and public semantics while assigning fresh IDs to nested CollectionRef nodes.
  • Clone nested query/include structure safely, retaining shared and cyclic object topology with a WeakMap.
  • Apply the placement boundary to FROM, JOIN, and included subqueries.

Key invariants

  • A source ID belongs to one lexical query-source placement, not to a Collection or reusable builder.
  • Reused placements still read the same underlying Collections.
  • Optimizer and compiled-query cache semantics remain unchanged.
  • Auto-index off and eager produce the same public rows.

Non-goals

  • This does not redesign the compiled-query cache.
  • This does not allow duplicate aliases across unionAll branches; that existing validation remains unchanged.
  • This does not change query types or public APIs.

Trade-offs

Cloning the small IR tree at placement time does modest extra construction work, but keeps runtime graph ownership explicit. Caching a logical plan factory could also instantiate fresh graph state, but would be a substantially larger compiler redesign.

Verification

pnpm vitest run --pool-options.threads.maxThreads=2 packages/db/tests/query/join-subquery.test.ts packages/db/tests/query/compiler/subquery-caching.test.ts packages/db/tests/query/optimizer.test.ts packages/db/tests/query/ir-stable-identity.test.ts packages/db/tests/query/union-all.test.ts packages/db/tests/query/builder/union-all.test.ts
pnpm exec eslint packages/db/src/query/builder/clone-query.ts packages/db/src/query/builder/index.ts packages/db/tests/query/join-subquery.test.ts
pnpm exec tsc --noEmit -p packages/db/tsconfig.json
pnpm --filter @tanstack/db build

The focused test set passes 200/200.

Files changed

  • clone-query.ts: cycle-safe placement cloning with fresh source identities.
  • builder/index.ts: clones builders at subquery and include placement boundaries.
  • join-subquery.test.ts: shared-versus-separate builder matrix across predicate and auto-index modes.
  • Changeset: records the runtime bug fix for @tanstack/db.

Stacked on #1877, which supplies the adjacent pushed-predicate and alias-remapping fixes.

@coderabbitai

coderabbitai Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 61ffbd46-89a3-4e8e-9fb9-56c9891ea7a2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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 Sep 23, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-db

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

@tanstack/browser-db-sqlite-persistence

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

@tanstack/capacitor-db-sqlite-persistence

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

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

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

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

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

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

@tanstack/expo-db-sqlite-persistence

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

@tanstack/node-db-sqlite-persistence

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

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

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

@tanstack/react-router-with-db

npm i https://pkg.pr.new/@tanstack/react-router-with-db@1878

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 6560ad0

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: +774 B (+0.47%)

Total Size: 167 kB

📦 View Changed
Filename Size Change
packages/db/dist/esm/query/builder/clone-query.js 743 B +743 B (new file) 🆕
packages/db/dist/esm/query/builder/index.js 6.72 kB +31 B (+0.46%)
ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/client.js 3.66 kB
packages/db/dist/esm/collection-options.js 236 B
packages/db/dist/esm/collection/change-events.js 1.44 kB
packages/db/dist/esm/collection/changes.js 2.4 kB
packages/db/dist/esm/collection/cleanup-queue.js 794 B
packages/db/dist/esm/collection/events.js 481 B
packages/db/dist/esm/collection/index.js 4.36 kB
packages/db/dist/esm/collection/indexes.js 1.99 kB
packages/db/dist/esm/collection/lifecycle.js 2.15 kB
packages/db/dist/esm/collection/mutations.js 2.61 kB
packages/db/dist/esm/collection/state.js 6.51 kB
packages/db/dist/esm/collection/subscription.js 8.73 kB
packages/db/dist/esm/collection/sync.js 4.63 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.26 kB
packages/db/dist/esm/event-emitter.js 964 B
packages/db/dist/esm/index.js 3.71 kB
packages/db/dist/esm/indexes/auto-index.js 829 B
packages/db/dist/esm/indexes/base-index.js 1.14 kB
packages/db/dist/esm/indexes/basic-index.js 2.07 kB
packages/db/dist/esm/indexes/btree-index.js 2.26 kB
packages/db/dist/esm/indexes/index-registry.js 820 B
packages/db/dist/esm/indexes/reverse-index.js 376 B
packages/db/dist/esm/live-query-adapter.js 318 B
packages/db/dist/esm/live-query-observer.js 3.69 kB
packages/db/dist/esm/live-query-options.js 702 B
packages/db/dist/esm/live-query-window-controller.js 4.36 kB
packages/db/dist/esm/local-only.js 989 B
packages/db/dist/esm/local-storage.js 2.17 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.32 kB
packages/db/dist/esm/query/builder/functions.js 1.47 kB
packages/db/dist/esm/query/builder/query-ir.js 116 B
packages/db/dist/esm/query/builder/ref-proxy.js 1.24 kB
packages/db/dist/esm/query/compiler/evaluators.js 1.96 kB
packages/db/dist/esm/query/compiler/expressions.js 560 B
packages/db/dist/esm/query/compiler/group-by.js 4.13 kB
packages/db/dist/esm/query/compiler/index.js 9.11 kB
packages/db/dist/esm/query/compiler/joins.js 2.99 kB
packages/db/dist/esm/query/compiler/lazy-targets.js 1.1 kB
packages/db/dist/esm/query/compiler/order-by.js 1.91 kB
packages/db/dist/esm/query/compiler/parent-routes.js 319 B
packages/db/dist/esm/query/compiler/query-equivalence.js 455 B
packages/db/dist/esm/query/compiler/route-metadata.js 1.24 kB
packages/db/dist/esm/query/compiler/select.js 1.58 kB
packages/db/dist/esm/query/effect.js 4.6 kB
packages/db/dist/esm/query/equality-value-identity.js 591 B
packages/db/dist/esm/query/expression-helpers.js 1.43 kB
packages/db/dist/esm/query/ir-stable-identity.js 4.04 kB
packages/db/dist/esm/query/ir.js 1.59 kB
packages/db/dist/esm/query/live-query-collection.js 391 B
packages/db/dist/esm/query/live/bucket-facade-adapter.js 2.73 kB
packages/db/dist/esm/query/live/collection-config-builder.js 6.97 kB
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 2.26 kB
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/live/materialized-pipeline.js 2.32 kB
packages/db/dist/esm/query/live/ordered-source-loader.js 3.14 kB
packages/db/dist/esm/query/live/subset-demand-controller.js 1.26 kB
packages/db/dist/esm/query/live/utils.js 1.14 kB
packages/db/dist/esm/query/optimizer.js 3.05 kB
packages/db/dist/esm/query/query-once.js 359 B
packages/db/dist/esm/query/runtime-reference-identity.js 572 B
packages/db/dist/esm/query/subset-dedupe.js 486 B
packages/db/dist/esm/scheduler.js 1.34 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.71 kB
packages/db/dist/esm/utils.js 1.08 kB
packages/db/dist/esm/utils/array-utils.js 270 B
packages/db/dist/esm/utils/browser-polyfills.js 304 B
packages/db/dist/esm/utils/btree.js 4.51 kB
packages/db/dist/esm/utils/callbacks.js 174 B
packages/db/dist/esm/utils/comparison.js 1.49 kB
packages/db/dist/esm/utils/cursor.js 676 B
packages/db/dist/esm/utils/error.js 167 B
packages/db/dist/esm/utils/get-or-create.js 155 B
packages/db/dist/esm/utils/index-optimization.js 2.42 kB
packages/db/dist/esm/utils/type-guards.js 230 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: 7.34 kB

ℹ️ View Unchanged
Filename Size
packages/react-db/dist/esm/DbProvider.js 317 B
packages/react-db/dist/esm/HydrationBoundary.js 263 B
packages/react-db/dist/esm/index.js 330 B
packages/react-db/dist/esm/live-query-internals.js 282 B
packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.9 kB
packages/react-db/dist/esm/useLiveQuery.js 2.68 kB
packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 812 B
packages/react-db/dist/esm/usePacedMutations.js 401 B

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

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