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

@AdamGS

AdamGS commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

can you enable support for actions on the fork?

@compiler-segfault
compiler-segfault force-pushed the fix/parquet-float-constant-stats branch from 5f0bfe2 to 994a87e Compare November 13, 2026 03:04
…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 994a87e to 504998d Compare November 13, 2026 03:05
@compiler-segfault

Copy link
Copy Markdown
Author

I tried, but GitHub has disabled workflows on that fork at the repository level and
the REST API does not clear it:

  • the Actions tab of compiler-segfault/datafusion says "Workflows are not currently being
    run on this fork due to the scale of GitHub Actions usage. A repository maintainer can
    choose to re-enable these." - caused by the full Rust runs I did on the fork earlier;
  • GET /repos/compiler-segfault/datafusion/actions/permissions reports
    {"enabled": true, "allowed_actions": "all"}; PUT with that body returns 204 and
    PUT .../actions/workflows/{id}/enable returns 204 for every workflow, but
    POST .../actions/workflows/410339485/dispatches still returns
    422 Actions has been disabled for this repository, on main and on this branch alike;
  • deleting the fork's runs and caches, and toggling the setting off and on, does not
    change it, and a push to the branch does not create a run either, so Mirror the fork run
    has nothing to pick up.

If the Rust (fork) check is needed for this to land, I can re-create the fork and open a
fresh PR so the run and the mirror happen; I would rather not do that unilaterally and
lose this thread, so tell me if you want it.

Meanwhile the branch is rebased onto current main (55de8615) and verified on it
locally, and the rebase also moved the new helper below constant_columns_from_stats
(it had been inserted between that function and its doc comment):

  • cargo test -p datafusion-sqllogictest --test sqllogictests -- parquet_constant_columns
    passes; without the change the scan returns 1, 1, 1 instead of 1, 1, NaN;
  • cargo fmt --all -- --check clean;
  • cargo clippy -p datafusion-datasource-parquet --all-targets -- -D warnings clean.

Head is 504998d.

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