Skip to content

fix(query): lazy-join loads through the collection the join key resolves to#1614

Open
kevin-dp wants to merge 5 commits into
TanStack:mainfrom
kevin-dp:test/lazy-join-index-warning-repro
Open

fix(query): lazy-join loads through the collection the join key resolves to#1614
kevin-dp wants to merge 5 commits into
TanStack:mainfrom
kevin-dp:test/lazy-join-index-warning-repro

Conversation

@kevin-dp

@kevin-dp kevin-dp commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What

Fixes #1494.

When a subquery used in a JOIN clause selects its join key from a joined source rather than from its own from clause, the outer join key resolves to a different collection than the subquery's from source. The lazy-join loader diverged here:

  • followRef traced the index requirement to the collection the key actually resolves to (e.g. teams.id), but
  • the subscription it drove loading through was looked up via the subquery's from alias (e.g. members).

Because those two diverged, the loader queried the wrong subscription, fell back to a full load, and emitted a Join requires an index warning naming the already-indexed collection — so the advice ("create an index") was already satisfied and unactionable. Data was still correct via the fallback, so this was a diagnostic-/efficiency-quality bug, not a correctness one.

Fix

followRef now also returns the alias of the source the ref ultimately resolves to. getLazyLoadTargets uses that alias as the subscription target (falling back to the existing from-clause remapping when the key resolves directly to the from source). Lazy loading then drives through the correct collection's index — no spurious warning, no unnecessary full-load.

Tests

Adds a regression test in packages/db/tests/query/join-subquery.test.ts: a subquery whose join key resolves to an already-indexed collection must load through that index without emitting a Join requires an index warning that points at it. The test fails without the fix and passes with it.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed lazy-join behavior so nested joins now resolve through the correct collection when the join key comes from a joined subquery.
    • Prevented incorrect “Join requires an index” warnings in cases where the resolved collection is already indexed.
    • Improved join loading to avoid unnecessary full-load fallback in these scenarios.
  • Tests

    • Added coverage for lazy joins involving indexed and unindexed collections to verify the correct result and warning behavior.

…ction

Add a failing reproduction: when a subquery used in a JOIN clause selects
its join key from the joined side of the subquery, the outer join key
resolves to a collection that is already indexed. The lazy-join loader
should load through that index, but today it emits a "Join requires an
index" warning naming the already-indexed collection (and falls back to a
full load). Data is still correct via the fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

followRef now returns a resolved alias for collection references, lazy-load targets prefer that alias, and a regression test verifies nested subquery joins still load ['m1'] without warning about lazy-join-teams. A changeset note records the patch release.

Changes

Lazy join alias resolution

Layer / File(s) Summary
Resolved alias from followRef
packages/db/src/query/ir.ts
followRef now returns an optional alias alongside collection and path, including it when a nested PropRef resolves across a joined source.
Lazy load target alias selection
packages/db/src/query/compiler/lazy-targets.ts
getLazyLoadTargets uses the alias returned by followRef before falling back to alias remapping or the original lazy alias.
Regression test and changeset
packages/db/tests/query/join-subquery.test.ts, .changeset/lazy-join-resolved-collection.md
Adds a nested lazy-join regression test with indexed and unindexed collections, checks the warning text, and documents the patch release.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related issues

Poem

🐇 I hopped through refs with a twitchy ear,
Found the right alias, now the path is clear.
No false index warning, no sidetrack drift,
Just m1 in the burrow — a tidy little gift. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and accurately summarizes the lazy-join alias-resolution fix.
Description check ✅ Passed The description covers the bug, fix, and tests, with only the template checklist/release-impact fields omitted.
Linked Issues check ✅ Passed The code and regression test address #1494 by routing lazy loading through the resolved alias and avoiding the misleading warning.
Out of Scope Changes check ✅ Passed All changes are directly related to the lazy-join fix, regression test, and release note.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 Jun 24, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-db

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

@tanstack/browser-db-sqlite-persistence

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

@tanstack/capacitor-db-sqlite-persistence

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

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

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

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

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

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

@tanstack/expo-db-sqlite-persistence

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

@tanstack/node-db-sqlite-persistence

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

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

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 283eb0c

kevin-dp and others added 2 commits June 24, 2026 16:01
Import BTreeIndex from src/indexes/btree-index.js (not collection/index)
and type the collections via factory-wrapper ReturnType so the test passes
typecheck. The index-warning assertion still fails as intended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a subquery used in a JOIN clause selects its join key from a joined
source rather than its own from clause, followRef traced the index
requirement to the resolved collection while the lazy loader still
subscribed to the subquery's from alias. The two diverged, producing a
misleading "Join requires an index" warning that named an already-indexed
collection and an unnecessary full-load fallback.

followRef now also returns the alias of the source the ref resolves to;
getLazyLoadTargets uses it as the subscription alias (falling back to the
from-clause remapping when the key resolves directly to the from source),
so lazy loading drives through the correct collection's index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kevin-dp kevin-dp changed the title test: lazy-join index warning must not blame an already-indexed collection fix(query): lazy-join loads through the collection the join key resolves to Jun 24, 2026

@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.

🧹 Nitpick comments (1)
packages/db/src/query/compiler/lazy-targets.ts (1)

58-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use ?? instead of || for the alias default chain.

|| treats an empty-string alias as falsy and would skip it, whereas ?? only falls through on null/undefined. Switch to nullish coalescing per the project convention.

♻️ Proposed change
-      alias: followRefResult.alias || aliasRemapping[lazyAlias] || lazyAlias,
+      alias: followRefResult.alias ?? aliasRemapping[lazyAlias] ?? lazyAlias,

As per coding guidelines: "Use the nullish coalescing operator ?? for default values instead of checking for null and undefined separately".

🤖 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/db/src/query/compiler/lazy-targets.ts` around lines 58 - 64, The
alias fallback in the lazy target mapping uses a falsy chain, so update the
logic in the result object construction to use nullish coalescing instead of the
current defaulting behavior. In the code path that builds the array return value
in lazy-targets.ts, adjust the alias selection in the
followRefResult/aliasRemapping/lazyAlias chain so empty-string aliases are
preserved and only null or undefined falls through, matching the project
convention.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@packages/db/src/query/compiler/lazy-targets.ts`:
- Around line 58-64: The alias fallback in the lazy target mapping uses a falsy
chain, so update the logic in the result object construction to use nullish
coalescing instead of the current defaulting behavior. In the code path that
builds the array return value in lazy-targets.ts, adjust the alias selection in
the followRefResult/aliasRemapping/lazyAlias chain so empty-string aliases are
preserved and only null or undefined falls through, matching the project
convention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 585dcc2b-743d-498e-820a-5d4d9dd7de14

📥 Commits

Reviewing files that changed from the base of the PR and between 22ff2ab and 283eb0c.

📒 Files selected for processing (3)
  • .changeset/lazy-join-resolved-collection.md
  • packages/db/src/query/compiler/lazy-targets.ts
  • packages/db/src/query/ir.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/lazy-join-resolved-collection.md

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.

Lazy-join Join requires an index warning blames the wrong collection when a subquery's select field traces across an inner join

1 participant