Skip to content

Commit 01669a0

Browse files
fix[sparse]: correctly handle cast for all-patched (#6979)
Fixes #6932 Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent b59349c commit 01669a0

1 file changed

Lines changed: 58 additions & 2 deletions

File tree

  • encodings/sparse/src/compute

encodings/sparse/src/compute/cast.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use vortex_array::ArrayRef;
55
use vortex_array::IntoArray;
66
use vortex_array::builtins::ArrayBuiltins;
77
use vortex_array::dtype::DType;
8+
use vortex_array::scalar::Scalar;
89
use vortex_array::scalar_fn::fns::cast::CastReduce;
910
use vortex_error::VortexResult;
1011

@@ -13,8 +14,14 @@ use crate::SparseArray;
1314

1415
impl CastReduce for Sparse {
1516
fn cast(array: &SparseArray, dtype: &DType) -> VortexResult<Option<ArrayRef>> {
16-
// Cast both the patches values and the fill value
17-
let casted_fill = array.fill_scalar().cast(dtype)?;
17+
let casted_fill = if array.patches().num_patches() == array.len() {
18+
// When every position is patched the fill is unused — skip casting it
19+
// entirely and substitute a zero value for the target dtype.
20+
Scalar::zero_value(dtype)
21+
} else {
22+
// Otherwise the cast must succeed.
23+
array.fill_scalar().cast(dtype)?
24+
};
1825
let casted_patches = array
1926
.patches()
2027
.clone()
@@ -114,4 +121,53 @@ mod tests {
114121
fn test_cast_sparse_conformance(#[case] array: SparseArray) {
115122
test_cast_conformance(&array.into_array());
116123
}
124+
125+
#[test]
126+
fn test_cast_sparse_null_fill_all_patched_to_non_nullable() -> vortex_error::VortexResult<()> {
127+
// Regression test for https://github.com/vortex-data/vortex/issues/6932
128+
//
129+
// When all positions are patched the null fill is unused, so a cast to
130+
// non-nullable is valid. Sparse::cast detects this case, substitutes a
131+
// zero fill, and keeps the result in the Sparse encoding.
132+
let sparse = SparseArray::try_new(
133+
buffer![0u64, 1, 2, 3, 4].into_array(),
134+
buffer![10u64, 20, 30, 40, 50].into_array(),
135+
5,
136+
Scalar::null_native::<u64>(),
137+
)?;
138+
139+
let casted = sparse
140+
.into_array()
141+
.cast(DType::Primitive(PType::U64, Nullability::NonNullable))?;
142+
143+
assert_eq!(
144+
casted.dtype(),
145+
&DType::Primitive(PType::U64, Nullability::NonNullable)
146+
);
147+
148+
let expected = PrimitiveArray::from_iter([10u64, 20, 30, 40, 50]);
149+
assert_arrays_eq!(casted.to_primitive(), expected);
150+
Ok(())
151+
}
152+
153+
#[test]
154+
fn test_fill_null_sparse_with_null_fill() -> vortex_error::VortexResult<()> {
155+
// Regression test for https://github.com/vortex-data/vortex/issues/6932
156+
// fill_null on a sparse array with null fill triggers an internal cast to
157+
// non-nullable, which must not panic.
158+
let sparse = SparseArray::try_new(
159+
buffer![1u64, 3].into_array(),
160+
buffer![10u64, 20].into_array(),
161+
5,
162+
Scalar::null_native::<u64>(),
163+
)?;
164+
165+
let filled = sparse.into_array().fill_null(Scalar::from(0u64))?;
166+
167+
assert_eq!(
168+
filled.dtype(),
169+
&DType::Primitive(PType::U64, Nullability::NonNullable)
170+
);
171+
Ok(())
172+
}
117173
}

0 commit comments

Comments
 (0)