Skip to content

Commit a30298b

Browse files
authored
Fix: masked dtype mismatch (#6008)
Fixes: #5989 Signed-off-by: Connor Tsui <[email protected]>
1 parent aa5d235 commit a30298b

File tree

1 file changed

+48
-32
lines changed
  • vortex-array/src/arrays/masked/vtable

1 file changed

+48
-32
lines changed

vortex-array/src/arrays/masked/vtable/mod.rs

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,23 @@ impl VTable for MaskedVTable {
130130
}
131131

132132
fn execute(array: &Self::Array, ctx: &mut ExecutionCtx) -> VortexResult<Canonical> {
133-
if let Some(canonical) = execute_fast_path(array, ctx)? {
134-
return Ok(canonical);
133+
let validity_mask = array.validity_mask();
134+
135+
// Fast path: all masked means result is all nulls.
136+
if validity_mask.all_false() {
137+
return ConstantArray::new(Scalar::null(array.dtype().as_nullable()), array.len())
138+
.into_array()
139+
.execute::<Canonical>(ctx);
135140
}
136141

142+
// NB: We intentionally do NOT have a fast path for `validity_mask.all_true()`.
143+
// `MaskedArray`'s dtype is always `Nullable`, but the child has `NonNullable` `DType` (by
144+
// invariant). Simply returning the child's canonical would cause a dtype mismatch.
145+
// While we could manually convert the dtype, `mask_validity_canonical` is already O(1) for
146+
// `AllTrue` masks (no data copying), so there's no benefit.
147+
137148
let child = array.child().clone().execute::<Canonical>(ctx)?;
138-
let canonical = mask_validity_canonical(child, &array.validity_mask())?;
149+
let canonical = mask_validity_canonical(child, &validity_mask)?;
139150

140151
vortex_ensure!(
141152
canonical.as_ref().dtype() == array.dtype(),
@@ -176,42 +187,18 @@ impl VTable for MaskedVTable {
176187
}
177188
}
178189

179-
/// Check for fast-path execution conditions.
180-
pub(super) fn execute_fast_path(
181-
array: &MaskedArray,
182-
ctx: &mut ExecutionCtx,
183-
) -> VortexResult<Option<Canonical>> {
184-
let validity_mask = array.validity_mask();
185-
186-
// All valid - no masking needed
187-
if validity_mask.all_true() {
188-
return Ok(Some(array.child.clone().execute(ctx)?));
189-
}
190-
191-
// All masked - result is all nulls
192-
if validity_mask.all_false() {
193-
return Ok(Some(
194-
ConstantArray::new(Scalar::null(array.dtype().as_nullable()), array.len())
195-
.into_array()
196-
.execute::<Canonical>(ctx)?,
197-
));
198-
}
199-
200-
// Child is already all nulls - masking has no effect
201-
if array.child.all_invalid() {
202-
return Ok(Some(array.child.clone().execute(ctx)?));
203-
}
204-
205-
Ok(None)
206-
}
207-
208190
#[cfg(test)]
209191
mod tests {
210192
use rstest::rstest;
211193
use vortex_buffer::ByteBufferMut;
194+
use vortex_dtype::Nullability;
195+
use vortex_error::VortexError;
212196

213197
use crate::ArrayContext;
198+
use crate::Canonical;
214199
use crate::IntoArray;
200+
use crate::LEGACY_SESSION;
201+
use crate::VortexSessionExecute;
215202
use crate::arrays::MaskedArray;
216203
use crate::arrays::MaskedVTable;
217204
use crate::arrays::PrimitiveArray;
@@ -265,4 +252,33 @@ mod tests {
265252
decoded.display_values().to_string()
266253
);
267254
}
255+
256+
/// Regression test for issue #5989: execute_fast_path returns child with wrong dtype.
257+
///
258+
/// When MaskedArray's validity mask is all true, returning the child's canonical form
259+
/// directly would cause a dtype mismatch because the child has NonNullable dtype while
260+
/// MaskedArray always has Nullable dtype.
261+
#[test]
262+
fn test_execute_with_all_valid_preserves_nullable_dtype() -> Result<(), VortexError> {
263+
// Create a MaskedArray with AllValid validity.
264+
265+
// Child has NonNullable dtype, but MaskedArray's dtype is Nullable.
266+
let child = PrimitiveArray::from_iter([1i32, 2, 3]).into_array();
267+
assert_eq!(child.dtype().nullability(), Nullability::NonNullable);
268+
269+
let array = MaskedArray::try_new(child, Validity::AllValid)?;
270+
assert_eq!(array.dtype().nullability(), Nullability::Nullable);
271+
272+
// Execute the array. This should produce a Canonical with Nullable dtype.
273+
let mut ctx = LEGACY_SESSION.create_execution_ctx();
274+
let result: Canonical = array.into_array().execute(&mut ctx)?;
275+
276+
assert_eq!(
277+
result.as_ref().dtype().nullability(),
278+
Nullability::Nullable,
279+
"MaskedArray execute should produce Nullable dtype"
280+
);
281+
282+
Ok(())
283+
}
268284
}

0 commit comments

Comments
 (0)