Skip to content

fix: use single-column comparator for BTree index in multi-column orderBy#1401

Merged
kevin-dp merged 6 commits intoTanStack:mainfrom
kevin-dp:fix/btree-index-multi-column-comparator
Mar 24, 2026
Merged

fix: use single-column comparator for BTree index in multi-column orderBy#1401
kevin-dp merged 6 commits intoTanStack:mainfrom
kevin-dp:fix/btree-index-multi-column-comparator

Conversation

@kevin-dp
Copy link
Copy Markdown
Contributor

Summary

  • Fixes BTree index corruption when queries use multiple orderBy columns (e.g. .orderBy(createdAt, 'desc').orderBy(id, 'desc'))
  • The order-by compiler's ensureIndexForField was passing the multi-column array comparator to create a single-column BTree index
  • The multi-column comparator expects [col1, col2] arrays but receives individual field values (numbers) — indexing into a number returns undefined, so all values compare as NaN/equal
  • This collapsed the BTree to 1 entry, causing takeFromStart() to return at most 1 key, breaking live query subscriptions for pre-existing data

The fix

One line change in order-by.ts: pass makeComparator(compareOpts) (single-column comparator) instead of compare (multi-column comparator) to ensureIndexForField.

Test plan

  • Added regression test in btree-index-wrong-comparator.test.ts documenting the root cause and verifying the fix
  • All 2038 existing tests pass
  • Verified with reproduction app via Playwright automation — all 7 thread-switching steps return correct counts

🤖 Generated with Claude Code

kevin-dp and others added 2 commits March 23, 2026 14:08
…erBy

When a query has multiple orderBy columns (e.g. `.orderBy(createdAt, 'desc').orderBy(id, 'desc')`), the order-by compiler creates a multi-column comparator that expects array values. Previously, `ensureIndexForField` received this multi-column comparator to create a single-column BTree index on the first field.

The BTree stored individual field values (numbers), but the multi-column comparator treated them as arrays — indexing into a number returns `undefined`, so all values compared as NaN/equal. This collapsed the entire BTree to a single entry, causing `takeFromStart()` to return at most 1 key. Any live query subscription created after data was already in the collection would see 0 results.

The fix passes `makeComparator(compareOpts)` — a proper single-column comparator built from the first orderBy column's compare options — instead of the multi-column `compare` function.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 23, 2026

More templates

@tanstack/angular-db

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

@tanstack/db

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

@tanstack/db-browser-wa-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-browser-wa-sqlite-persisted-collection@1401

@tanstack/db-capacitor-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-capacitor-sqlite-persisted-collection@1401

@tanstack/db-cloudflare-do-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-cloudflare-do-sqlite-persisted-collection@1401

@tanstack/db-electron-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-electron-sqlite-persisted-collection@1401

@tanstack/db-expo-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-expo-sqlite-persisted-collection@1401

@tanstack/db-ivm

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

@tanstack/db-node-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-node-sqlite-persisted-collection@1401

@tanstack/db-react-native-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-react-native-sqlite-persisted-collection@1401

@tanstack/db-sqlite-persisted-collection-core

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-sqlite-persisted-collection-core@1401

@tanstack/db-tauri-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-tauri-sqlite-persisted-collection@1401

@tanstack/electric-db-collection

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

@tanstack/offline-transactions

npm i https://pkg.pr.new/TanStack/db/@tanstack/offline-transactions@1401

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 75eea15

kevin-dp and others added 3 commits March 23, 2026 14:17
Move the single-column comparator and multi-column orderBy tests into
the existing BTreeIndex describe block in deterministic-ordering.test.ts
rather than keeping them in a separate file.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Copy link
Copy Markdown
Collaborator

@samwillis samwillis left a comment

Choose a reason for hiding this comment

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

LGTM :shipit:

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@kevin-dp kevin-dp merged commit f60384b into TanStack:main Mar 24, 2026
7 checks passed
@github-actions github-actions Bot mentioned this pull request Mar 23, 2026
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.

2 participants