You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Arrow scan: a NULL row passes a pushed-down <= filter against a NaN constant #615
When a WHERE b <= 'nan'::DOUBLE filter is pushed into an Arrow scan, the scan returns rows where b is NULL.
The same predicate over same rows copied to a native table (correctly) excludes the null rows.
This should exclude the nulls (NULL <= x is false): The bug appears to be in the arrow filter_pushdown: disabling the optimizer/pushdowns returns the correct results.
To Reproduce
importduckdbimportpyarrowaspawithduckdb.connect() ascon:
print(f"duckdb {duckdb.__version__}, pyarrow {pa.__version__}", "source id:", con.execute("SELECT source_id FROM pragma_version()").fetchone()[0])
arrow_table=pa.table({"b": pa.array([1.0, float("nan"), None])})
con.register("arrow_t", arrow_table)
result_native=con.execute("create table native_t as select * from arrow_t;select b from native_t where b <= 'nan'::DOUBLE").fetchall()
result=con.execute("select b from arrow_t where b <= 'nan'::DOUBLE").fetchall()
con.execute("PRAGMA disable_optimizer;")
result_no_optimizer=con.execute("select b from arrow_t where b <= 'nan'::DOUBLE").fetchall()
print(f"{result_native=}")
print(f"{result=}")
print(f"{result_no_optimizer=}")
Reproduced on duckdb 1.5.6 / pyarrow 26.0.0 (Linux). I ran the same table through all six comparison operators against 'nan'::DOUBLE, and only <= lets the NULL row through. <, >=, >, = and <> all match the native table. I see the same thing when the source is a polars DataFrame.
I think the cause is the NaN special case in src/arrow/pyarrow_filter_pushdown.cpp (NaNCompare):
case ExpressionType::COMPARE_LESSTHANOREQUALTO:
// Everything is less than or equal to NaN.return dataset_scalar(true);
A literal true keeps NULL rows as well. The other branches go through is_nan(), which is null for a null input, so those rows get dropped as expected. Returning col.attr("is_valid")() in that branch should keep every non-null value (NaN included) and drop the nulls, which is what the native table returns.
src/arrow/polars_filter_pushdown.cpp has the same branch returning pl.lit(true), so it probably needs the equivalent is_not_null() change. I haven't tested the polars lazy pushdown path on its own, though.
Activity
mohamd764 commentedon Oct 12, 2026
Reproduced on duckdb 1.5.6 / pyarrow 26.0.0 (Linux). I ran the same table through all six comparison operators against
'nan'::DOUBLE, and only<=lets the NULL row through.<,>=,>,=and<>all match the native table. I see the same thing when the source is a polars DataFrame.I think the cause is the NaN special case in
src/arrow/pyarrow_filter_pushdown.cpp(NaNCompare):A literal
truekeeps NULL rows as well. The other branches go throughis_nan(), which is null for a null input, so those rows get dropped as expected. Returningcol.attr("is_valid")()in that branch should keep every non-null value (NaN included) and drop the nulls, which is what the native table returns.src/arrow/polars_filter_pushdown.cpphas the same branch returningpl.lit(true), so it probably needs the equivalentis_not_null()change. I haven't tested the polars lazy pushdown path on its own, though.NaN handling in Arrow scans (#657)
evertlammerts commentedon Oct 14, 2026
Thanks! Fixed in #657