Skip to content

fix: prevent unsafe integer interval propagation - #25234

Merged
kosiew merged 12 commits into
apache:mainfrom
haohuaijin:fix/integer-interval-propagation
Sep 18, 2026
Merged

kosiew merged 12 commits into
apache:mainfrom
haohuaijin:fix/integer-interval-propagation

Conversation

@haohuaijin

@haohuaijin haohuaijin commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Arithmetic filters can incorrectly identify a column as constant and eliminate a required SortExec, returning rows in the wrong order. Integer multiplication can wrap, and integer division truncates, so their mathematical inverses do not necessarily describe all valid inputs.

CREATE TABLE wrap_test (a INT) AS VALUES (-2147483647), (1);
SELECT a FROM wrap_test
WHERE a * 2::INT = 2::INT ORDER BY a DESC;
-- Expected: 1, -2147483647
-- Actual before this fix: -2147483647, 1

CREATE TABLE division_test (a INT) AS VALUES (2), (3);
SELECT a FROM division_test
WHERE a / 2::INT = 1::INT ORDER BY a DESC;
-- Expected: 3, 2
-- Actual before this fix: 2, 3

Both multiplication inputs evaluate to 2; both division inputs evaluate to 1. Nevertheless, interval inference narrows a to a singleton and removes the sort. Without the filters, the sorts are retained and these queries return the correct order. With ORDER BY ... LIMIT, incorrect sort elimination can also change which rows are returned.

What changes are included in this PR?

  • Return unbounded output intervals when unchecked integer addition, subtraction or multiplication may wrap.
  • Skip inverse propagation for integer division, potentially wrapping arithmetic, and zero products where an operand may be zero.
  • Retain interval narrowing for supported arithmetic proven safe from these cases.
  • Correct a Filter statistics test whose previous lower bound excluded a valid wrapping-subtraction input.

What is the testing strategy for this PR?

  • SQL regressions in filter_without_sort_exec.slt cover both reproductions and sorting a wrapped expression; verified failing before the fix and passing afterward.
  • Unit tests enumerate selected Int8 domains under checked and wrapping arithmetic, ensuring forward and propagated intervals retain valid runtime values. Additional tests cover unsigned subtraction underflow and safe multiplication narrowing.
  • Relevant interval, analysis and Filter tests, the complete affected SLT file, Clippy and repository lint checks pass locally.

Are there any user-facing changes?

Affected queries retain necessary sorting. Unsafe interval optimizations are skipped; SQL arithmetic semantics and public APIs are unchanged.

@github-actions github-actions Bot added physical-expr Changes to the physical-expr crates sqllogictest SQL Logic Tests (.slt) physical-plan Changes to the physical-plan crate labels Sep 12, 2026
@haohuaijin haohuaijin changed the title Fix/integer interval propagation fix: prevent unsafe integer interval propagation Sep 12, 2026
@github-actions github-actions Bot added the logical-expr Logical plan and expressions label Sep 13, 2026
Comment thread datafusion/expr-common/src/interval_arithmetic.rs Outdated
@haohuaijin
haohuaijin marked this pull request as ready for review September 13, 2026 09:40
@codecov-commenter

codecov-commenter commented Sep 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.09910% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.29%. Comparing base (be5a96e) to head (6a42c2d).
⚠️ Report is 25 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/physical-expr/src/expressions/binary.rs 99.06% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25234      +/-   ##
==========================================
+ Coverage   81.94%   82.29%   +0.35%     
==========================================
  Files        1135     1137       +2     
  Lines      428166   430429    +2263     
  Branches   428166   430429    +2263     
==========================================
+ Hits       350841   354216    +3375     
+ Misses      56378    54789    -1589     
- Partials    20947    21424     +477     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@haohuaijin

haohuaijin commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi @kosiew would you mind reviewing this fix?

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

@haohuaijin, thanks for working on this. The changes look good to me. I like that this addresses both wrapping integer arithmetic and truncating division while keeping the interval and sort-property inference conservative. The regression coverage around ordering and statistics is also helpful.

I left one non-blocking suggestion for some additional coercion-path coverage.

Comment thread datafusion/expr-common/src/interval_arithmetic.rs Outdated
@haohuaijin

Copy link
Copy Markdown
Contributor Author

Thanks @kosiew , i apply suggestion in 99eaf75

@kosiew

kosiew commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

🚀
@haohuaijin
Thank you for your contribution.

@kosiew
kosiew added this pull request to the merge queue Sep 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 16, 2026
@kosiew
kosiew added this pull request to the merge queue Sep 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 16, 2026
@haohuaijin

Copy link
Copy Markdown
Contributor Author

Hi @kosiew, I looked into the merge queue failures. The widening change exposed two existing gaps in interval handling: negation doesn’t handle i32::MIN, and narrowing casts can fail on estimated bounds even when the actual data fits. Previously, those endpoints stayed unbounded, so these paths didn’t encounter the concrete limits. Preserving the source type’s bounds is valid, but it brought these cases into play, so we need to address them or leave widening out of this PR.

I think we have two options:

  1. Remove the widening change, use Float64 for the FIFO test’s ordered column, and update the sort expectation. I tested this, and all 154 previously failing SQLite files pass.
  2. Keep widening and fix how negation and narrowing casts handle those bounds.

I’m perfer 1 to keep this PR focused, then opening a separate issue for the widening optimization. What do you think? I’d like to hear your thoughts before I update the PR.

@kosiew

kosiew commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

@haohuaijin

I agree with option 1. The widening optimization exposes pre-existing interval-bound failures in negation and narrowing casts, and fixing both correctly needs broader boundary coverage than this sort-correctness fix. Please remove the widening change, keep the overflow/division fixes, and update the FIFO and sort expectations. We can track widening separately with targeted tests for those bound transitions.

@github-actions github-actions Bot added core Core DataFusion crate and removed logical-expr Logical plan and expressions labels Sep 17, 2026
@haohuaijin

haohuaijin commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi @kosiew , i applied the option 1 and raise the follow up issue #25407

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

@haohuaijin,

Thanks for the follow-up. The option 1 changes look good to me.

Removing the widening-integer interval-bound propagation keeps the cast endpoints conservative, and the FIFO fixture and physical-plan SLT expectation have been updated accordingly. The FIFO test now avoids the unrelated integer-bound transition, while widening integer arithmetic correctly retains the required sort.

All of my previous comments have been addressed. The earlier non-blocking suggestion around the coercion path is no longer relevant given the agreed scope reduction.

I don't see any new correctness issues introduced by these follow-up changes. Thanks for working through this!

@kosiew
kosiew added this pull request to the merge queue Sep 18, 2026
Merged via the queue into apache:main with commit b9cea81 Sep 18, 2026
41 checks passed
@haohuaijin

Copy link
Copy Markdown
Contributor Author

thanks again @kosiew

@haohuaijin
haohuaijin deleted the fix/integer-interval-propagation branch September 18, 2026 02:32
haohuaijin added a commit to haohuaijin/arrow-datafusion that referenced this pull request Sep 19, 2026
## Which issue does this PR close?

- close: apache#25232

## Rationale for this change

Arithmetic filters can incorrectly identify a column as constant and
eliminate a required `SortExec`, returning rows in the wrong order.
Integer multiplication can wrap, and integer division truncates, so
their mathematical inverses do not necessarily describe all valid
inputs.

```sql
CREATE TABLE wrap_test (a INT) AS VALUES (-2147483647), (1);
SELECT a FROM wrap_test
WHERE a * 2::INT = 2::INT ORDER BY a DESC;
-- Expected: 1, -2147483647
-- Actual before this fix: -2147483647, 1

CREATE TABLE division_test (a INT) AS VALUES (2), (3);
SELECT a FROM division_test
WHERE a / 2::INT = 1::INT ORDER BY a DESC;
-- Expected: 3, 2
-- Actual before this fix: 2, 3
```

Both multiplication inputs evaluate to `2`; both division inputs
evaluate to `1`. Nevertheless, interval inference narrows `a` to a
singleton and removes the sort. Without the filters, the sorts are
retained and these queries return the correct order. With `ORDER BY ...
LIMIT`, incorrect sort elimination can also change which rows are
returned.

## What changes are included in this PR?

- Return unbounded output intervals when unchecked integer addition,
subtraction or multiplication may wrap.
- Skip inverse propagation for integer division, potentially wrapping
arithmetic, and zero products where an operand may be zero.
- Retain interval narrowing for supported arithmetic proven safe from
these cases.
- Correct a Filter statistics test whose previous lower bound excluded a
valid wrapping-subtraction input.

## What is the testing strategy for this PR?

- SQL regressions in `filter_without_sort_exec.slt` cover both
reproductions and sorting a wrapped expression; verified failing before
the fix and passing afterward.
- Unit tests enumerate selected Int8 domains under checked and wrapping
arithmetic, ensuring forward and propagated intervals retain valid
runtime values. Additional tests cover unsigned subtraction underflow
and safe multiplication narrowing.
- Relevant interval, analysis and Filter tests, the complete affected
SLT file, Clippy and repository lint checks pass locally.

## Are there any user-facing changes?

Affected queries retain necessary sorting. Unsafe interval optimizations
are skipped; SQL arithmetic semantics and public APIs are unchanged.
cetra3 pushed a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
## Which issue does this PR close?

Closes apache#25794

## Rationale for this change

Integer multiplication overflow can produce false-empty filter
statistics despite returning matching rows.

For TINYINT inputs `(0, -3)` and `(20, 7)`, `SELECT a, b, a * b AS x
FROM mul_wrap WHERE a * b < -100` has the following behavior:

| | Before | After |
| --- | --- | --- |
| Returned row | `(20, 7, -116)` | `(20, 7, -116)` |
| Estimated rows | `Inexact(0)` | `Inexact(2)` |
| Column a bounds | `Min=Exact(Int8(NULL)), Max=Exact(Int8(NULL))` |
`Min=Inexact(Int8(0)), Max=Inexact(Int8(20))` |
| Column b bounds | `Min=Exact(Int8(NULL)), Max=Exact(Int8(NULL))` |
`Min=Inexact(Int8(-3)), Max=Inexact(Int8(7))` |
| Distinct counts | `Exact(0)` for both columns | Unknown |

The estimate of 2 is conservative, not the exact count of the one
surviving row. The fix removes the false-empty inference without
changing runtime arithmetic or query results in these reproductions.

Related: apache#25232 and the merged fix apache#25234. That fix guards wrapping
arithmetic when the inferred interval is unbounded. Here, the
lower-level multiplication helper discards an overflow-generated
unbounded endpoint, preventing that guard from activating.

## What changes are included in this PR?

Distinguish lower- and upper-bound comparisons so multiplication
preserves overflow-generated unbounded endpoints. Reuse the helpers in
intersection and union, preserving their behavior.

In the example, interval multiplication now retains `[-60, +infinity]`
instead of `[-60, 0]`. The existing wrapping-arithmetic protection then
widens the expression range to fully unbounded, allowing the wrapped
value `-116`.

## What is the testing strategy for this PR?

Add interval overflow cases and a Parquet SQL regression checking
results and statistics. Validation on the PR branch:

- `cargo test -p datafusion-expr-common --lib
interval_arithmetic::tests` — 45 passed.
- `cargo test --test sqllogictests -- parquet_statistics` — passed.


## Are there any user-facing changes?

Affected filters report conservative statistics instead of false-empty
statistics. No public API changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate physical-expr Changes to the physical-expr crates physical-plan Changes to the physical-plan crate sqllogictest SQL Logic Tests (.slt) v56.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integer interval inference can incorrectly remove ORDER BY

4 participants