Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Jan 13, 2026

Summary

Passes join information to queryFn in on-demand mode, enabling server-side joins before pagination. This fixes inconsistent page sizes when queries combine pagination with filters on joined collections.


Reviewer Guidance

Root Cause

When using queryCollection with joins and pagination (e.g., limit: 10), the pagination was applied before join filters:

  1. queryFn received { limit: 10, where: <main collection filters only> }
  2. Backend returned 10 rows from main collection
  3. Client-side join with other collections
  4. Join filters applied client-side
  5. Result: Only 6 rows match → inconsistent page size

The join information was lost during compilation, so queryFn had no way to construct a server-side query that joins and filters before paginating.

Approach

Extract join information during query optimization and thread it through the compilation pipeline to LoadSubsetOptions:

// Now available in queryFn
context.meta.loadSubsetOptions.joins = [
  {
    collectionId: 'accounts',
    alias: 'account',
    type: 'inner',
    localKey: { type: 'ref', path: ['task', 'account_id'] },
    foreignKey: { type: 'ref', path: ['account', 'id'] },
    where: /* filters on account collection */,
    orderBy: /* order by expressions referencing account */,
  }
]

Key Invariants

  1. Join info is keyed by main source alias - A query FROM tasks JOIN accounts produces { tasks: [accountJoinInfo] }
  2. WHERE clauses are partitioned - Filters referencing account.* go into joinInfo.where, not loadSubsetOptions.where
  3. Key direction is normalized - localKey always references main collection, foreignKey always references joined collection (even if IR has them swapped)
  4. Subqueries are traversed - getInnermostCollectionId() recursively finds the actual collection ID through QueryRef chains

Non-goals

  • No automatic server-side query generation - We provide the information; queryFn implementations decide how to use it
  • No changes to client-side join execution - Existing IVM-based joins continue to work unchanged
  • No breaking changes - joins is optional; existing queryFn implementations work without modification

Trade-offs

Why extract at optimizer level?
The optimizer already analyzes WHERE clauses by source alias (sourceWhereClauses). Extracting join info here reuses that analysis and keeps join-related logic centralized.

Why include where and orderBy in JoinInfo?
Server-side queries need the complete picture. Including these avoids multiple round-trips through the compilation result to reconstruct which filters apply to which joins.


Verification

# Run optimizer tests (includes new join info extraction tests)
pnpm vitest --run optimizer.test.ts

# Run query-db-collection tests (includes serialization)
cd packages/query-db-collection && pnpm vitest --run

Files Changed

File Change
packages/db/src/types.ts Add JoinInfo type, extend LoadSubsetOptions with joins
packages/db/src/query/optimizer.ts Add extractJoinInfo(), include joinInfoBySource in optimization result
packages/db/src/query/compiler/index.ts Pass joinInfoBySource through CompilationResult
packages/db/src/query/live/collection-config-builder.ts Store joinInfoBySourceCache
packages/db/src/query/live/collection-subscriber.ts Add getJoinInfoForAlias(), pass to subscriptions
packages/db/src/collection/subscription.ts Include joins in loadSubset calls
packages/query-db-collection/src/serialization.ts Serialize joins in query keys with validation
packages/db/tests/query/optimizer.test.ts Add 8 tests for join info extraction

… mode

Investigates the pagination issue when using queryCollection with joins
in on-demand syncMode. Documents the root cause (LoadSubsetOptions lacks
join information) and proposes extending the type to include join details
so backends can perform server-side joins before pagination.
This enables server-side joins before pagination for queryCollection,
solving the issue where pagination was applied before join filters,
leading to inconsistent page sizes.

Changes:
- Add JoinInfo type with collection ID, alias, type, keys, where, orderBy
- Extend LoadSubsetOptions with optional joins array
- Extract join info during query optimization (extractJoinInfo)
- Pass join info through CompilationResult → CollectionConfigBuilder
- Include join info when creating subscriptions
- Pass joins in loadSubset calls from CollectionSubscription
- Serialize joins in query keys for queryCollection

The queryFn now receives join information in context.meta.loadSubsetOptions.joins,
allowing construction of server-side join queries that filter before pagination.

Closes: Investigation for passing join info to queryFn in on-demand mode
@changeset-bot
Copy link

changeset-bot bot commented Jan 13, 2026

🦋 Changeset detected

Latest commit: f529c55

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@tanstack/db Patch
@tanstack/query-db-collection Patch
@tanstack/angular-db Patch
@tanstack/electric-db-collection Patch
@tanstack/offline-transactions Patch
@tanstack/powersync-db-collection Patch
@tanstack/react-db Patch
@tanstack/rxdb-db-collection Patch
@tanstack/solid-db Patch
@tanstack/svelte-db Patch
@tanstack/trailbase-db-collection Patch
@tanstack/vue-db Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 13, 2026

More templates

@tanstack/angular-db

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/electric-db-collection

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: f529c55

@github-actions
Copy link
Contributor

github-actions bot commented Jan 13, 2026

Size Change: +641 B (+0.71%)

Total Size: 91.2 kB

Filename Size Change
./packages/db/dist/esm/collection/subscription.js 3.66 kB +37 B (+1.02%)
./packages/db/dist/esm/query/compiler/index.js 1.98 kB +18 B (+0.92%)
./packages/db/dist/esm/query/live/collection-config-builder.js 5.42 kB +19 B (+0.35%)
./packages/db/dist/esm/query/live/collection-subscriber.js 2.1 kB +171 B (+8.85%) 🔍
./packages/db/dist/esm/query/optimizer.js 2.96 kB +396 B (+15.47%) ⚠️
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/changes.js 1.19 kB
./packages/db/dist/esm/collection/events.js 388 B
./packages/db/dist/esm/collection/index.js 3.32 kB
./packages/db/dist/esm/collection/indexes.js 1.1 kB
./packages/db/dist/esm/collection/lifecycle.js 1.67 kB
./packages/db/dist/esm/collection/mutations.js 2.34 kB
./packages/db/dist/esm/collection/state.js 3.46 kB
./packages/db/dist/esm/collection/sync.js 2.38 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.49 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/index.js 2.69 kB
./packages/db/dist/esm/indexes/auto-index.js 742 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 1.93 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.1 kB
./packages/db/dist/esm/indexes/reverse-index.js 513 B
./packages/db/dist/esm/local-only.js 837 B
./packages/db/dist/esm/local-storage.js 2.1 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 733 B
./packages/db/dist/esm/query/builder/index.js 4.08 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 1.05 kB
./packages/db/dist/esm/query/compiler/evaluators.js 1.42 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 1.87 kB
./packages/db/dist/esm/query/compiler/joins.js 2 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.45 kB
./packages/db/dist/esm/query/compiler/select.js 1.06 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 673 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/internal.js 145 B
./packages/db/dist/esm/query/predicate-utils.js 2.97 kB
./packages/db/dist/esm/query/subset-dedupe.js 921 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 2.9 kB
./packages/db/dist/esm/utils.js 924 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 852 B
./packages/db/dist/esm/utils/cursor.js 457 B
./packages/db/dist/esm/utils/index-optimization.js 1.51 kB
./packages/db/dist/esm/utils/type-guards.js 157 B

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

@github-actions
Copy link
Contributor

github-actions bot commented Jan 13, 2026

Size Change: 0 B

Total Size: 3.47 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 225 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.17 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.12 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 559 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

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

3 participants