Repository navigation
feat(filter): keep the scalar index for comparisons of a column cast to a float - #10820
Merged
Xuanwo merged 6 commits intoNov 14, 2026
Conversation
…to a float `CAST(i AS double) > 2.5`, `TRY_CAST(i AS double) = 3.0` and `CAST(f32 AS double) > 0.1` now plan on the column instead of on the cast, so its scalar index is used. DataFusion's own cast unwrapping does not cover float targets. This is the follow-up lance-format#9333 left open. The rewrite keeps the semantics of the cast that was written: - An integer is compared after rounding it to the float type. Rounding is monotonic, so every comparison selects one interval of integers, found by binary search: `CAST(i AS double) = 9007199254740992.0` becomes `i >= 9007199254740992 AND i < 9007199254740994`. - A Float32 widens exactly. A bound it cannot represent becomes the nearest Float32 below it, and `=` / `!=` against it become constant (NULL rows stay NULL). This also covers `f32 > CAST(0.1 AS double)`, which coercion turns into a cast of the column. - NaN keeps its place above every number; a Float32 compared with a NaN is left alone, since the comparison also orders NaN payloads. `BETWEEN` and `IN` are handled too. The rewrite runs after coercion and before the float comparison rewrite.
`IN` becomes an `OR` of the values' equalities, which the simplifier gathers back into a list, `!=` negates `=`, and the integer interval helpers are one function.
`CAST(i AS double) IN (...)` was rewritten into one `OR` level per list value, so a 10,000-value list overflowed the stack in the passes that walk the filter afterwards. Values that a single integer rounds to now stay an `IN` list on the column, and the intervals that several integers round to are OR'd as a balanced tree.
`tenant = 1.5` is coerced to `CAST(tenant AS double) = 1.5`, which the widening cast rewrite now turns back into a comparison on the key: an empty range here, and a plain equality for `tenant = 2.0`.
Past 2^53 several integers round to one double, and each such value of `CAST(i AS double) IN (...)` became a range of its own, evaluated as separate comparisons. The integers are now listed when at most 16 round to the value, which covers doubles up to 2^57 and Float32 up to 2^28, so the filter stays one hash lookup and one index search. Wider intervals keep the balanced ranges.
…own-m0bezu-cast-unwrap
Contributor
There was a problem hiding this comment.
The merged revision preserves cast rounding and NULL behavior while exposing scalar-index predicates. The accepted trade-off remains: rounding intervals containing more than 16 integers use balanced range predicates, so long lists can cost more on scans without an index.
Xuanwo
approved these changes
Nov 14, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Follow-up to #9333, which left explicit column casts open. A comparison of a column cast to a float now plans on the column itself, so its scalar index is used. DataFusion's own cast unwrapping doesn't cover float targets.
The result keeps the semantics of the cast that was written, unlike #9333's exact comparison of a bare
i > 2.5:= 2^53therefore also matches2^53 + 1, as the cast does.=against it becomes constant.BETWEEN,INandNOTforms are covered. AnINlist stays one flat list on the column: where several integers round to a value (past 2^53, or 2^24 for Float32), up to 16 of them are listed individually, soCAST(i AS double) IN (2^53)becomesi IN (2^53, 2^53 + 1). Only wider intervals, near ±2^63, become ranges, OR'd as a balanced tree so long lists don't build deep expressions. The rewrite runs after coercion and before the float comparison rewrite.Tests compare every operator row by row against the rounded or widened value. They cover Int8/Int32/Int64/UInt64 and Float32 against boundary literals (±2^53, ±2^63, 2^64, ±inf, ±NaN, values Float32 can't represent) and assert that no cast is left. A BTREE dataset test compares indexed and unindexed scans.
Validation on the merged tree (main
e0aacd774): the PR selectors pass (widened_39,float_literal_on_integer_key4,test_btree_nullable_filters_match_unindexed_scan1), as do the currentparse_filter_with_schema, session-planner, andoptimizing_twice_changes_nothing(14) tests.cargo fmt --alland full clippy with-D warningsare clean.The author-reported interval-heavy probe (1,000
INvalues above 2^53 over 32,768 Int64 rows) evaluates in 0.11–0.12 ms after e7a6674. Intervals wider than 16 integers still stay scan-only, so this is not a universal speedup. DataFusion's nested casts forCAST(i AS float)against an untyped Float64 fractional literal remain correct but unoptimized.