From 6360953ea473bd17f02c0c994e915b118e42e1bc Mon Sep 17 00:00:00 2001 From: breken-ai <312387581+breken-ai@users.noreply.github.com> Date: Fri, 25 Sep 2026 18:53:02 -0700 Subject: [PATCH] Do not push down non-finite float literals from polars JSON has no inf/nan, so polars serializes a Float32/Float64 literal of inf, -inf or nan as null. The pushdown turned that into the SQL text None, which DuckDB binds as a column name, so a lazy filter such as pl.col('x') < float('inf') failed the whole collect() with 'Referenced column "None" not found'. Treat a null float value as not pushable so polars applies the filter. --- duckdb/polars_io.py | 4 ++++ tests/fast/arrow/test_polars.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/duckdb/polars_io.py b/duckdb/polars_io.py index c5cad457..73eb1400 100644 --- a/duckdb/polars_io.py +++ b/duckdb/polars_io.py @@ -313,6 +313,10 @@ def _pl_tree_to_sql(tree: _ExpressionTree) -> str: "Float64", "Boolean", ): + if value[dtype] is None: + # JSON has no inf/nan, polars serializes them as null + msg = f"Non-finite {dtype} literals cannot be pushed down" + raise NotImplementedError(msg) return str(value[dtype]) # Time type diff --git a/tests/fast/arrow/test_polars.py b/tests/fast/arrow/test_polars.py index f3d7e072..2dcaef93 100644 --- a/tests/fast/arrow/test_polars.py +++ b/tests/fast/arrow/test_polars.py @@ -264,6 +264,23 @@ def test_polars_lazy_pushdown_numeric(self, data_type, duckdb_cursor): valid_filter((pl.col("a") == 100) & (pl.col("b") == 10) & (pl.col("c") == 100)) valid_filter((pl.col("a") == 100) | (pl.col("b") == 1)) + @pytest.mark.parametrize("data_type", ["FLOAT", "DOUBLE"]) + def test_polars_lazy_pushdown_non_finite_float(self, data_type, duckdb_cursor): + duckdb_cursor.execute(f"CREATE TABLE test_non_finite (a {data_type})") + duckdb_cursor.execute("INSERT INTO test_non_finite VALUES (1), ('inf'), ('-inf'), ('nan'), (NULL)") + lazy_df = duckdb_cursor.table("test_non_finite").pl(lazy=True) + eager_df = lazy_df.collect() + + # polars serializes inf/nan literals as JSON null, so they cannot be pushed down + for predicate in ( + pl.col("a") < float("inf"), + pl.col("a") > float("-inf"), + pl.col("a") == float("inf"), + pl.col("a") >= float("nan"), + ): + invalid_filter(predicate) + pl_testing.assert_frame_equal(lazy_df.filter(predicate).collect(), eager_df.filter(predicate)) + def test_polars_lazy_pushdown_bool(self, duckdb_cursor): duckdb_cursor.execute( """