Repository navigation
NaN in FLOAT/DOUBLE columns is returned as None (same as NULL) when pandas is enabled (#978) - #1005
peco-engineer-bot[bot] wants to merge 3 commits into
Conversation
…andas is enabled (#978) Signed-off-by: peco-engineer-bot[bot] <3815206+peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Low
Looks good: the fix restores NaN from the Arrow column after the pandas conversion, so NaN and NULL are now distinct. It covers chunked arrays and duplicate column names and is tested at both the unit and E2E levels. One low-severity suggestion: the per-cell restore loop could be a single vectorized assignment.
Addresses: - #4418224915 at src/databricks/sql/result_set.py:147 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Low
Looks good. The fix is correct and well-scoped, with one low-severity performance note. The Arrow-side is_nan mask (with nulls filled as False) is applied after pandas conversion, so it handles chunked columns, duplicate column names and empty tables. If a pandas version already keeps NaN, the fix just writes NaN again, so nothing changes.
Addresses: - #4418281963 at src/databricks/sql/result_set.py:143 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
Summary
Automated fix for #978 — NaN in FLOAT/DOUBLE columns is returned as None (same as NULL) when pandas is enabled.
With pandas enabled, a NaN in a FLOAT or DOUBLE column now comes back as
float('nan')instead ofNone, so it can be told apart from SQL NULL; NULL still comes back asNone. InResultSet._convert_arrow_table(src/databricks/sql/result_set.py), after the pandasto_numpyconversion, the code now usespyarrow.compute.is_nanto find the NaN cells in each floating-point Arrow column and puts NaN back in those cells. The output now matches the_disable_pandas=Truepath. All 4 new tests failed before the change and pass after it: the 3 E2E tests ran against the live warehouse on both Thrift and SEA (6 cases), plus the unit test. The full unit suite also passes (1267 passed, run with -m "not realkernel").Root cause & plan
Root cause: ResultSet._convert_arrow_table (src/databricks/sql/result_set.py, ~L109-133) maps pyarrow float32/float64 to pandas' nullable Float32Dtype/Float64Dtype in the to_pandas types_mapper. That conversion treats IEEE NaN as missing (pd.NA), and df.to_numpy(na_value=None, dtype="object") then turns it into None. The result is that NaN can't be told apart from SQL NULL. The _disable_pandas=True path uses as_py() and correctly returns float('nan'). All three backends (Thrift, SEA, kernel) call this shared method from fetchone/fetchmany/fetchall, so all of them are affected.
Files:
src/databricks/sql/result_set.py,tests/e2e/test_driver.py,tests/unit/test_pandas_compatibility.pyPlanned coverage:
SELECT * FROM VALUES (1, CAST('NaN' AS DOUBLE)), (2, CAST(NULL AS DOUBLE)), (3, CAST('Infinity' AS DOUBLE)), (4, CAST(1.5 AS DOUBLE)) AS t(id, d) ORDER BY idwith the default (pandas enabled) connection and calls fetchall(). Assert: row0.d is a float and math.isnan(row0.d); row1.d is None; row2.d == float('inf'); row3.d == 1.5. Today row0.d is None, so the test fails. (DOUBLE NaN vs NULL, pandas enabled (E2E, Thrift + SEA))Files changed
tests/e2e/test_driver.pytests/unit/test_pandas_compatibility.pysrc/databricks/sql/result_set.pyTest plan
tests/e2e/test_driver.py::TestPySQLCoreSuite::test_double_nan_distinct_from_null_with_pandas— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_float_nan_distinct_from_null_matches_disable_pandas— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_nan_preserved_in_fetchone_and_fetchmany— fails (red) against the original code, passes (green) after the fixtests/unit/test_pandas_compatibility.py::TestConvertArrowTablePandasCompat::test_float_nan_distinct_from_null— fails (red) against the original code, passes (green) after the fix🤖 Generated by engineer-bot (bug-fix flow) — review before merge.