-
Notifications
You must be signed in to change notification settings - Fork 251
fix: enforce all where conditions when index optimization is partial #1582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
kevin-dp
merged 21 commits into
TanStack:main
from
kevin-dp:fix/index-optimization-partial-and-or
Jun 26, 2026
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
bec0ba9
test: add failing tests for index-optimized queries mixing indexed an…
kevin-dp ac26205
test: add failing tests for range query boundary handling
kevin-dp 0dac2f8
test: add failing test for compound range query with undefined bound
kevin-dp 029e759
test: add failing tests for nullish values in indexed eq/in/range que…
kevin-dp fc07196
test: add failing tests for locale string range and NaN index queries
kevin-dp 9d5c125
test: add failing tests for range predicates over non-orderable index…
kevin-dp 13f7ead
test: add failing tests for ordering values that have no natural order
kevin-dp aa343b1
fix: enforce all where conditions when index optimization is partial
kevin-dp f18393e
fix: apply strictest bound in compound range queries and fix related …
kevin-dp 4a4c305
test: add failing test for exclusive lower bound without a from bound
kevin-dp 1e17c29
fix: re-filter compound range queries that use a null/undefined bound
kevin-dp 9a505eb
fix: only exclude exclusive lower bound when a from bound is provided
kevin-dp 240f417
fix: re-filter index results that can include nullish-keyed rows
kevin-dp f8084d8
ci: apply automated fixes
autofix-ci[bot] b9cca21
fix: avoid locale string range index lookups and re-filter NaN results
kevin-dp d09ec6b
fix: only use indexes for range predicates when ordering is trustworthy
kevin-dp 8a731a9
ci: apply automated fixes
autofix-ci[bot] 4b97808
fix: give NaN and invalid Dates a stable sort position
kevin-dp 35fb96d
feat: adopt PostgreSQL float semantics for NaN (supersedes #1617)
kevin-dp c7f0796
chore: mark NaN-semantics changeset as patch (no minor before 1.0)
kevin-dp a155f99
test: fold nan-semantics tests into existing well-suited test files
kevin-dp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| --- | ||
| '@tanstack/db': patch | ||
| --- | ||
|
|
||
| Fix incorrect results from index-optimized `where` clauses that combine indexed and non-indexed conditions. | ||
|
|
||
| - `OR` expressions are now only served from indexes when every disjunct can use an index; otherwise the query falls back to a full scan. Previously, rows matched only by a non-indexed disjunct were missing from the result. | ||
| - `AND` expressions still use indexes for the conditions that have them, but the remaining conditions are now enforced by re-checking each candidate row against the full expression. Previously, non-indexed conditions were silently dropped, returning rows that did not match the query. | ||
| - Compound range conditions (e.g. `age > 5 AND age < 10`) combined with conditions on other fields no longer ignore those other conditions. | ||
| - Compound range conditions sharing the same boundary value (e.g. `age >= 5 AND age > 5`) now apply the strictest bound regardless of the order the conditions appear in, using the same value comparison semantics as the indexes (dates, locale strings, ...). | ||
| - Compound range conditions that only bound one side (e.g. `age > 5 AND age >= 8`) no longer return an empty result. | ||
| - Strict range comparisons (`gt`/`lt`) on BTree-indexed fields holding normalized values such as dates now correctly exclude the boundary value. | ||
| - Compound range conditions with a `null`/`undefined` bound (e.g. `gt(score, undefined)`) now re-filter against the full expression instead of returning index-ordered rows, matching the semantics of a full scan (a comparison against `null`/`undefined` is never true). | ||
| - Index-optimized `eq`, `IN`, and range queries on a field that has rows with `null`/`undefined` values no longer leak those rows into results. BTree indexes store and return such rows (they sort as the smallest key), but a comparison against `null`/`undefined` is never true, so these results are now re-filtered against the full expression to stay equivalent to a full scan. | ||
| - String range conditions (`gt`/`gte`/`lt`/`lte`) on a collection using locale string collation (the default) are no longer served by the index. The index orders strings with `localeCompare` while the `where` evaluator compares them with standard relational operators, so an index range lookup could omit matching rows; these conditions now fall back to a full scan. | ||
| - Range conditions whose operand is not ordered the same way by the index and the `where` evaluator (arrays, plain objects, Temporal values) now fall back to a full scan instead of using the index, which could otherwise omit matching rows. | ||
| - Range conditions on an index created with a custom comparator now fall back to a full scan, since the comparator's ordering may not match the `where` evaluator's relational operators. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| --- | ||
| '@tanstack/db': patch | ||
| --- | ||
|
|
||
| Adopt PostgreSQL float semantics for `NaN` in `where` clauses and ordering. | ||
|
|
||
| `NaN` (and invalid `Date` values, whose timestamp is `NaN`) previously had no consistent order — `NaN === NaN` is `false` in JavaScript, so `NaN` compared unequal to everything and could not be sorted or indexed deterministically. Following PostgreSQL, `NaN` is now treated as **equal to itself** and **greater than every other non-null value**: | ||
|
|
||
| - `eq(row.value, NaN)` matches rows whose value is `NaN`; `inArray(row.value, [NaN, ...])` matches them too. | ||
| - Range comparisons treat `NaN` as the greatest value: `gt`/`gte` include it, `lt`/`lte` exclude it. | ||
| - Ordering by a field containing `NaN` is now deterministic, with `NaN` sorting last (and `null` still ordered by `NULLS FIRST`/`NULLS LAST`). | ||
|
|
||
| `null`/`undefined` are unaffected: they continue to use three-valued logic (a comparison with `null` yields `UNKNOWN`). | ||
|
|
||
| This makes results independent of whether a query is served from an index or a full scan. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Preserve the unorderable value's kind.
Line 28 collapses numeric
NaNand invalidDateinto the same bucket, and Lines 52-56 then make every pair in that bucket compare equal. The downstreamvaluesEqualhelper inpackages/db/src/query/compiler/evaluators.tsinherits that too, so mixed-type rows can satisfyeq/INand be deduped together even though the new semantics only require self-equality. A kinded helper (nanvsinvalid-date) would keep the total order without making those two types equal.Also applies to: 48-56
🤖 Prompt for AI Agents