From c700c2a5e36adcdf053c3fef2f56207a3bc747f8 Mon Sep 17 00:00:00 2001 From: ghostiee-11 <168410465+ghostiee-11@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:47:40 +0530 Subject: [PATCH 1/2] Skip string coordinates in partition bound computation String-typed coordinates (dtype U, S, O) crash in _block_metadata() because numpy min/max ufuncs do not support string comparison. Since ScalarBound only supports Int64/Float64/TimestampNanos, skip string coordinates so the Rust pruning logic treats the dimension conservatively (never prunes on it). Partial fix for #121. --- tests/test_sql.py | 19 +++++++++++++++++++ xarray_sql/df.py | 44 +++++++++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/tests/test_sql.py b/tests/test_sql.py index 08bde14..b4e2bfd 100644 --- a/tests/test_sql.py +++ b/tests/test_sql.py @@ -1,5 +1,6 @@ """SQL functionality tests for xarray-sql using pytest.""" +import numpy as np import pytest import xarray as xr @@ -127,3 +128,21 @@ def test_cross_join(air_and_stations): "SELECT COUNT(*) AS total FROM air_data CROSS JOIN stations" ).to_pandas() assert result["total"].iloc[0] > 0 + + +def test_string_coordinates(): + """String-typed coordinates should not crash during registration.""" + ds = xr.Dataset( + {"score": (["student", "subject"], np.random.rand(3, 2))}, + coords={ + "student": ["alice", "bob", "charlie"], + "subject": ["math", "science"], + }, + ) + ctx = XarrayContext() + ctx.from_dataset("scores", ds.chunk({"student": 3, "subject": 2})) + result = ctx.sql("SELECT * FROM scores").to_pandas() + assert len(result) == 6 + assert "student" in result.columns + assert "subject" in result.columns + assert "score" in result.columns diff --git a/xarray_sql/df.py b/xarray_sql/df.py index 756de20..d3f050f 100644 --- a/xarray_sql/df.py +++ b/xarray_sql/df.py @@ -323,25 +323,31 @@ def _block_metadata(coord_arrays: dict, block: Block) -> PartitionBounds: ranges: PartitionBounds = {} for dim, slc in block.items(): coord_values = coord_arrays[str(dim)][slc] - if len(coord_values) > 0: - # Use actual min/max rather than first/last so that non-monotonic - # coordinate axes (e.g. descending latitude 90→-90) are handled - # correctly. np.min/max work for both numeric and datetime64 arrays. - min_val = coord_values.min() - max_val = coord_values.max() - - if isinstance(min_val, (np.datetime64, pd.Timestamp)): - min_val = int(pd.Timestamp(min_val).value) - max_val = int(pd.Timestamp(max_val).value) - ranges[str(dim)] = (min_val, max_val, "timestamp_ns") - elif hasattr(min_val, "item"): - min_val = min_val.item() - max_val = max_val.item() - dtype = "float64" if isinstance(min_val, float) else "int64" - ranges[str(dim)] = (min_val, max_val, dtype) - else: - dtype = "float64" if isinstance(min_val, float) else "int64" - ranges[str(dim)] = (min_val, max_val, dtype) + if len(coord_values) == 0: + continue + # String/object dtypes are not representable as ScalarBound + # (Int64/Float64/TimestampNanos) and numpy min/max ufuncs do not + # support them. Skip so pruning treats the dimension conservatively. + if coord_values.dtype.kind in ('U', 'S', 'O'): + continue + # Use actual min/max rather than first/last so that non-monotonic + # coordinate axes (e.g. descending latitude 90→-90) are handled + # correctly. np.min/max work for both numeric and datetime64 arrays. + min_val = coord_values.min() + max_val = coord_values.max() + + if isinstance(min_val, (np.datetime64, pd.Timestamp)): + min_val = int(pd.Timestamp(min_val).value) + max_val = int(pd.Timestamp(max_val).value) + ranges[str(dim)] = (min_val, max_val, "timestamp_ns") + elif hasattr(min_val, "item"): + min_val = min_val.item() + max_val = max_val.item() + dtype = "float64" if isinstance(min_val, float) else "int64" + ranges[str(dim)] = (min_val, max_val, dtype) + else: + dtype = "float64" if isinstance(min_val, float) else "int64" + ranges[str(dim)] = (min_val, max_val, dtype) return ranges From e57a2b430aea42db0753eeb038bfd2997617249b Mon Sep 17 00:00:00 2001 From: ghostiee-11 <168410465+ghostiee-11@users.noreply.github.com> Date: Sat, 21 Mar 2026 03:11:20 +0530 Subject: [PATCH 2/2] style: use double quotes to satisfy pyink formatter The project uses pyink-use-majority-quotes = true, which enforces double quotes. Single quotes in the string dtype check caused the lint CI to fail. --- xarray_sql/df.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray_sql/df.py b/xarray_sql/df.py index d3f050f..0945f06 100644 --- a/xarray_sql/df.py +++ b/xarray_sql/df.py @@ -328,7 +328,7 @@ def _block_metadata(coord_arrays: dict, block: Block) -> PartitionBounds: # String/object dtypes are not representable as ScalarBound # (Int64/Float64/TimestampNanos) and numpy min/max ufuncs do not # support them. Skip so pruning treats the dimension conservatively. - if coord_values.dtype.kind in ('U', 'S', 'O'): + if coord_values.dtype.kind in ("U", "S", "O"): continue # Use actual min/max rather than first/last so that non-monotonic # coordinate axes (e.g. descending latitude 90→-90) are handled