Skip to content

fix: do not treat floating point columns with equal min/max statistics as constant - #27283

Open
compiler-segfault wants to merge 1 commit into
apache:mainfrom
compiler-segfault:fix/parquet-float-constant-stats
Open

compiler-segfault wants to merge 1 commit into
apache:mainfrom
compiler-segfault:fix/parquet-float-constant-stats

Conversation

@compiler-segfault

@compiler-segfault compiler-segfault commented Nov 10, 2026 •

Copy link
Copy Markdown

Rationale for this change

A plain SELECT of a Parquet Float32/Float64 column can return values that are not in the file: rows holding NaN come back as the statistics constant.

Parquet statistics exclude NaN from min/max, so a chunk holding 1.0, NaN, 1.0 reports min == max == 1.0 with null_count == 0. The skip-scan path in datafusion/datasource-parquet treats min == max as proof that the column is constant and substitutes the statistic for the data, so the NaN row is silently rewritten as 1.0. -0.0 and 0.0 are affected the same way, because they compare equal. COPY ... TO from such a table writes the wrong values too.

What changes are included in this PR?

  • constant_value_from_stats no longer derives a constant from min == max when the column type (or a dictionary's value type) is floating point. The all-null case is unchanged.
  • Regression test datafusion/sqllogictest/test_files/parquet_constant_columns.slt, which writes a Parquet file with 1.0, NaN, 1.0 and reads it back. The Rust integration test from the first revision is replaced by it, as suggested in review.

min == max still proves constancy for exact types, so the shortcut is only given up where it is not a proof.

What is the testing strategy for this PR?

  • The new sqllogictest fails before the change (the scan returns 1, 1, 1, so the NaN row is missing) and passes after it (1, 1, NaN): cargo test --test sqllogictests -- parquet_constant_columns.
  • cargo fmt --all -- --check and cargo clippy -p datafusion-datasource-parquet --all-targets -- -D warnings are green.

Are there any user-facing changes?

No API change. Floating point columns whose statistics happen to have min == max are now read from the file instead of being replaced by a constant; that costs a read of those chunks and returns the correct values.

AI Disclosure

This change was prepared with an AI coding assistant; I reproduced the bug locally and reviewed the diff end to end, and can answer questions about it.

Open question I would flag for review: the alternative is to plumb Parquet's nan_count into ColumnStatistics and keep the shortcut for float columns that report nan_count == 0. That is a public API change, and files written by older writers do not carry the count, so this PR takes the conservative route instead.

@github-actions github-actions Bot added core Core DataFusion crate datasource Changes to the datasource crate labels Nov 10, 2026
}

#[tokio::test]
async fn nan_rows_are_not_replaced_by_the_statistics_constant() {

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.

I think its generally preferred to use SLT for this sort of tests.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, moved it to a sqllogictest: datafusion/sqllogictest/test_files/parquet_constant_columns.slt (5f0bfe2), and rebased the branch on current main.

…s as constant

Parquet writers exclude `NaN` from floating point `min`/`max` statistics, as
the format requires, so a column holding `1.0`, `NaN` and `1.0` has
`min == max == 1.0` and `null_count == 0`. `constant_value_from_stats` read
that as proof that the column is constant and replaced it with the statistic
value, silently turning the `NaN` row into a `1.0` row. `-0.0` and `0.0`
compare equal in the same way, so the substituted value also loses the sign.

Skip statistics-based constant substitution for floating point columns
(including dictionary encoded ones); exact types keep the optimization.

The regression test is a sqllogictest, as suggested in review.
@compiler-segfault
compiler-segfault force-pushed the fix/parquet-float-constant-stats branch from 3ef0df8 to 5f0bfe2 Compare November 11, 2026 12:38
@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) and removed core Core DataFusion crate labels Nov 11, 2026
@compiler-segfault

Copy link
Copy Markdown
Author

Fork CI cannot be dispatched at the moment: GitHub returns Actions has been disabled for this repository for the fork, so Rust (fork) shows as skipped rather than running.

Verified locally on top of main at 34eb6dc: cargo test --test sqllogictests -- parquet_constant_columns is green (without the change the scan returns 1, 1, 1 instead of 1, 1, NaN), and cargo fmt --all -- --check plus cargo clippy -p datafusion-datasource-parquet --all-targets -- -D warnings are clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

datasource Changes to the datasource crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plain SELECT of a Parquet float column returns wrong values: NaN rows are replaced by the statistics constant when min == max

2 participants