chicagobuss commented on issue #15812:
URL: https://github.com/apache/datafusion/issues/15812#issuecomment-6463260117
I can reproduce this with DataFusion engine 55.2.0, the latest stable crate
version on 2026-10-28. Here is a four-row PyArrow reproduction, including
equality with NaN and the settings needed to recover the rows.
Environment: Linux x86_64, kernel 7.0.0-38-generic, glibc 2.43; CPython
3.14.4; PyArrow 26.0.0. The PyPI package is `datafusion==55.0.0`; `SELECT
version()` reports `Apache DataFusion 55.2.0, x86_64 on linux`. The standalone
`datafusion-cli 55.2.0` gives the same control results below.
Save this as `repro_datafusion_pruning.py`. In a fresh virtual environment
install `datafusion==55.0.0 pyarrow==26.0.0`, then run `python
repro_datafusion_pruning.py`. The script creates its own input beside itself.
```python
from pathlib import Path
import datafusion as df
import pyarrow as pa
import pyarrow.parquet as pq
path = Path(__file__).with_name('df_nan.parquet')
pq.write_table(pa.table({'x': [float('nan'), 0.0, 0.25, 1.0]}), path,
row_group_size=3, use_dictionary=False,
write_page_index=False)
print('datafusion Python:', df.__version__, 'pyarrow:', pa.__version__)
print(df.SessionContext().sql('SELECT version()').collect())
for pruning, statistics, page_index in [(True, True, True), (False, True,
True),
(True, False, True), (True, True,
False),
(False, False, False)]:
config = df.SessionConfig().with_target_partitions(1)
config.set('datafusion.execution.collect_statistics',
str(statistics).lower())
config.set('datafusion.execution.parquet.pruning', str(pruning).lower())
config.set('datafusion.execution.parquet.enable_page_index',
str(page_index).lower())
ctx = df.SessionContext(config)
# register_parquet's explicit pruning option overrides the session
setting.
ctx.register_parquet('t', path, parquet_pruning=pruning)
print('pruning/statistics/page_index:', pruning, statistics, page_index)
for query, expected in [
('SELECT count(*) AS n FROM t WHERE x >= 0.5', 2),
('SELECT count(*) AS n FROM t WHERE x > 0.5', 2),
("SELECT count(*) AS n FROM t WHERE x = 'NaN'::DOUBLE", 1),
('SELECT max(x) AS m FROM t', 'NaN'),
('SELECT max(x+0) AS m FROM t', 'NaN'),
]:
observed = [r for b in ctx.sql(query).collect() for r in
b.to_pylist()]
print(query, 'observed:', observed, 'expected:', expected)
```
Observed results (both `>` and `>=` give the range-count column below):
| Row-group pruning | Collect statistics | Page index | range count | NaN
equality count | `MAX(x)` |
|---|---|---|---:|---:|---|
| on | on | on | 1 | 0 | 1.0 |
| off | on | on | 2 | 0 | 1.0 |
| on | off | on | 1 | 0 | NaN |
| on | on | off | 1 | 0 | 1.0 |
| off | off | off | 2 | 1 | NaN |
| expected | | | 2 | 1 | NaN |
`MAX(x+0)` returns NaN in every configuration. A separate CLI control with
pruning and statistics off but page index **on** also gives 2, 1, NaN. The file
contains no page index, so disabling that feature has no effect here. Disabling
row-group pruning alone fixes the range query; equality also needs collected
statistics disabled. That distinguishes row-group pruning from an additional
use of collected bounds before scanning. The metadata aggregate shortcut is
included as supporting evidence for the same unsafe bounds, not as a claim that
all consumers share one function.
After the Python script has created `df_nan.parquet`, the equivalent CLI
control is:
```sh
datafusion-cli -q --format csv \
-c "SET datafusion.execution.parquet.pruning = false;" \
-c "SET datafusion.execution.collect_statistics = false;" \
-c "SELECT count(*) AS n FROM 'df_nan.parquet' WHERE x >= 0.5;" \
-c "SELECT count(*) AS n FROM 'df_nan.parquet' WHERE x = 'NaN'::DOUBLE;"
# n = 2, then n = 1; defaults give 1, then 0.
```
The Python API's `register_parquet(..., parquet_pruning=True)` default
overrides the session pruning option. The reproduction passes the flag
explicitly so the off control actually disables pruning. The CLI does not have
that registration override.
[DataFusion's data type
guide](https://datafusion.apache.org/user-guide/sql/data_types.html) identifies
Arrow as its execution type system. [Arrow Rust's comparison-kernel
documentation](https://github.com/apache/arrow-rs/blob/60.0.0/arrow-ord/src/cmp.rs#L63-L80)
specifies IEEE totalOrder. [DataFusion's own release
tests](https://github.com/apache/datafusion/blob/55.2.0/datafusion/sqllogictest/test_files/select.slt#L571-L575)
expect positive NaN to compare greater than zero. On the same values in
memory, NaN equality matches and the range predicate includes NaN. This example
uses only the ordinary positive NaN written by Python, not distinct signs or
payloads.
[Parquet format
2.14.0](https://github.com/apache/parquet-format/blob/apache-parquet-format-2.14.0/src/main/thrift/parquet.thrift#L312-L318)
says that when `nan_count` is absent readers “MUST assume NaNs may be
present”. Its [TYPE_ORDER reading
rules](https://github.com/apache/parquet-format/blob/apache-parquet-format-2.14.0/src/main/thrift/parquet.thrift#L1159-L1170)
say that when looking for NaN, “min and max should be ignored”. A present
count can establish whether NaNs exist; the bounds describe non-NaN values.
These reading rules also cover files written before counts were introduced. The
generated file has TYPE_ORDER, finite bounds, and no `nan_count` or page index.
Main runtime status: **not tested**. I found no packaged official DataFusion
nightly; GitHub release APIs offered no executable assets, and a source build
was not undertaken. At main `6341b5c9fc30c0215ba326a170eaa294ca1870b7`,
[row-group min/max
conversion](https://github.com/apache/datafusion/blob/6341b5c9fc30c0215ba326a170eaa294ca1870b7/datafusion/datasource-parquet/src/row_group_filter.rs#L568-L584)
still supplies the bounds without consulting `nan_count`. This suggests the
pruning path remains relevant, but is a source-based inference, not a main
reproduction. [PR #24804](https://github.com/apache/datafusion/pull/24804),
addressing #15812, remains open and unmerged as checked on 2026-10-28.
Related: #15812 is the existing report for this root cause.
[#26490](https://github.com/apache/datafusion/issues/26490) separately tracks
replacing a float column with a constant from `min == max`; this reproduction
keeps distinct finite bounds in the NaN-containing group to avoid that
confound. [#24432](https://github.com/apache/datafusion/issues/24432) is the
separate grouped-accumulator bug, already fixed on main; the `MAX` queries
above have no `GROUP BY`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]