Skip to content

Commit 2fbe1e5

Browse files
committed
Clarify decimal Interleave source preparation
Prepare each decimal source as one typed gather input, with an early return for constants. Keep buffer normalization separate from selector dispatch. Parameterize bounds-check cases and report assertion failures at their callers. Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
1 parent 96d42ef commit 2fbe1e5

3 files changed

Lines changed: 73 additions & 53 deletions

File tree

‎vortex-array/src/arrays/interleave/execute/decimal/mod.rs‎

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use super::super::InterleaveSlots;
1616
use super::fixed_width::Source;
1717
use super::fixed_width::gather;
1818
use crate::AnyColumnar;
19+
use crate::ArrayRef;
1920
use crate::array::Array;
2021
use crate::arrays::Constant;
2122
use crate::arrays::Decimal;
@@ -59,40 +60,7 @@ fn gather_values<T: NativeDecimalType>(
5960
) -> VortexResult<Buffer<T>> {
6061
let allocator = ctx.allocator().clone();
6162
let values = (0..array.num_values())
62-
.map(|i| {
63-
let value = array.value(i);
64-
let len = value.len();
65-
let (data, row_mask) = if let Some(constant) = value.as_opt::<Constant>() {
66-
let payload = match constant.scalar().as_decimal().decimal_value() {
67-
Some(value) => value.cast::<T>().ok_or_else(|| {
68-
vortex_err!("interleave decimal constant must fit {dtype}, got {value}")
69-
})?,
70-
None => T::default(),
71-
};
72-
(Buffer::full_in(payload, 1, allocator.clone()), 0)
73-
} else {
74-
let decimal = value.as_::<Decimal>();
75-
let data = if decimal.values_type() == T::DECIMAL_TYPE {
76-
decimal.buffer::<T>()
77-
} else {
78-
// A decimal source can have noncanonical storage. Normalize before indexing
79-
// rows so that every source has the same physical width. A narrowing copy
80-
// checks the precision and zeros arbitrary null payloads.
81-
let mask = decimal.validity()?.execute_mask(len, ctx)?;
82-
let copy = match_each_decimal_value_type!(decimal.values_type(), |S| {
83-
copy_to_canonical_width(decimal.buffer::<S>().as_slice(), &mask, dtype)?
84-
});
85-
Buffer::<T>::from_byte_buffer(copy.unwrap_host())
86-
};
87-
(data, usize::MAX)
88-
};
89-
90-
Ok(Source {
91-
data,
92-
len,
93-
row_mask,
94-
})
95-
})
63+
.map(|i| prepare_source::<T>(array.value(i), dtype, ctx))
9664
.collect::<VortexResult<Vec<_>>>()?;
9765
let branches = array.array_indices().as_::<Primitive>();
9866
let rows = array.row_indices().as_::<Primitive>();
@@ -109,5 +77,47 @@ fn gather_values<T: NativeDecimalType>(
10977
})
11078
}
11179

80+
/// Normalizes one decimal source while retaining a single payload for a constant.
81+
fn prepare_source<T: NativeDecimalType>(
82+
value: &ArrayRef,
83+
dtype: DecimalDType,
84+
ctx: &mut ExecutionCtx,
85+
) -> VortexResult<Source<T>> {
86+
let len = value.len();
87+
if let Some(constant) = value.as_opt::<Constant>() {
88+
let payload = match constant.scalar().as_decimal().decimal_value() {
89+
Some(value) => value.cast::<T>().ok_or_else(|| {
90+
vortex_err!("interleave decimal constant must fit {dtype}, got {value}")
91+
})?,
92+
None => T::default(),
93+
};
94+
95+
return Ok(Source {
96+
data: Buffer::full_in(payload, 1, ctx.allocator().clone()),
97+
len,
98+
row_mask: 0,
99+
});
100+
}
101+
102+
let decimal = value.as_::<Decimal>();
103+
let data = if decimal.values_type() == T::DECIMAL_TYPE {
104+
decimal.buffer::<T>()
105+
} else {
106+
// Decimal sources can still have noncanonical storage. A narrowing copy must check the
107+
// precision and zero null slots before the gather reads the buffer.
108+
let mask = decimal.validity()?.execute_mask(len, ctx)?;
109+
let copy = match_each_decimal_value_type!(decimal.values_type(), |S| {
110+
copy_to_canonical_width(decimal.buffer::<S>().as_slice(), &mask, dtype)?
111+
});
112+
Buffer::<T>::from_byte_buffer(copy.unwrap_host())
113+
};
114+
115+
Ok(Source {
116+
data,
117+
len,
118+
row_mask: usize::MAX,
119+
})
120+
}
121+
112122
#[cfg(test)]
113123
mod tests;

‎vortex-array/src/arrays/interleave/execute/decimal/tests.rs‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ use crate::scalar::Scalar;
3030
use crate::validity::Validity;
3131

3232
/// Executes the gather, checks its physical width and dtype, and compares it with a canonical oracle.
33-
fn check(values: Vec<ArrayRef>, indices: &[(u8, u64)], expected: DecimalArray) -> VortexResult<()> {
33+
#[track_caller]
34+
fn assert_interleaved(
35+
values: Vec<ArrayRef>,
36+
indices: &[(u8, u64)],
37+
expected: DecimalArray,
38+
) -> VortexResult<()> {
3439
let branches = PrimitiveArray::from_iter(indices.iter().map(|&(branch, _)| branch));
3540
let rows = PrimitiveArray::from_iter(indices.iter().map(|&(_, row)| row));
3641
let array = InterleaveArray::try_new(values, branches.into_array(), rows.into_array())?;
@@ -87,7 +92,7 @@ fn test_decimal_interleave(
8792
dtype,
8893
output_validity,
8994
);
90-
check(
95+
assert_interleaved(
9196
vec![first.into_array(), second.into_array()],
9297
&[(1, 2), (0, 0), (0, 1), (1, 0), (0, 2), (0, 0)],
9398
expected,
@@ -133,7 +138,7 @@ fn test_decimal_interleave_constants(
133138
Validity::NonNullable
134139
},
135140
);
136-
check(
141+
assert_interleaved(
137142
vec![constant, column],
138143
&[(0, 2), (1, 0), (0, 1), (0, 2)],
139144
expected,
@@ -167,7 +172,7 @@ fn test_decimal_interleave_mixed_stored_widths(#[case] precision: u8) -> VortexR
167172
dtype,
168173
Validity::from_iter([true, true, false, true, false]),
169174
);
170-
check(
175+
assert_interleaved(
171176
vec![first, second],
172177
&[(1, 0), (0, 1), (1, 1), (0, 0), (1, 2)],
173178
expected,
@@ -196,15 +201,15 @@ fn test_decimal_interleave_without_valid_values(#[case] indices: &[(u8, u64)]) -
196201
dtype,
197202
Validity::AllInvalid,
198203
);
199-
check(vec![first, second], indices, expected)
204+
assert_interleaved(vec![first, second], indices, expected)
200205
}
201206

202207
#[test]
203208
fn test_decimal_interleave_empty_sources() -> VortexResult<()> {
204209
let dtype = DecimalDType::new(9, 0);
205210
let empty = DecimalArray::new(Buffer::<i8>::empty(), dtype, Validity::NonNullable).into_array();
206211
let expected = DecimalArray::new(Buffer::<i32>::empty(), dtype, Validity::NonNullable);
207-
check(vec![empty.clone(), empty], &[], expected)
212+
assert_interleaved(vec![empty.clone(), empty], &[], expected)
208213
}
209214

210215
#[test]

‎vortex-array/src/arrays/interleave/execute/fixed_width.rs‎

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,27 +65,32 @@ where
6565

6666
#[cfg(test)]
6767
mod tests {
68-
use super::*;
68+
use rstest::rstest;
69+
use vortex_buffer::Buffer;
70+
use vortex_error::VortexResult;
71+
72+
use super::Source;
73+
use super::gather;
6974
use crate::memory::BufferAllocatorRef;
7075

71-
#[test]
72-
fn rejects_out_of_bounds_selectors() {
76+
#[rstest]
77+
#[case::array_index(1, 0, "array index out of bounds")]
78+
#[case::row_index(0, 1, "row index out of bounds")]
79+
fn rejects_out_of_bounds_selectors(
80+
#[case] branch: u8,
81+
#[case] row: u8,
82+
#[case] message: &str,
83+
) -> VortexResult<()> {
7384
let values = [Source {
7485
data: Buffer::full_in(1u32, 1, BufferAllocatorRef::statically_allocated()),
7586
len: 1,
7687
row_mask: 0,
7788
}];
7889

7990
let allocator = BufferAllocatorRef::statically_allocated();
80-
let error = gather(&values, &[1u8], &[0u8], &allocator).unwrap_err();
81-
assert!(
82-
error.to_string().contains("array index out of bounds"),
83-
"{error}"
84-
);
85-
let error = gather(&values, &[0u8], &[1u8], &allocator).unwrap_err();
86-
assert!(
87-
error.to_string().contains("row index out of bounds"),
88-
"{error}"
89-
);
91+
let error = gather(&values, &[branch], &[row], &allocator).unwrap_err();
92+
assert!(error.to_string().contains(message), "{error}");
93+
94+
Ok(())
9095
}
9196
}

0 commit comments

Comments
 (0)