Skip to main content

vortex_array/arrays/decimal/compute/
take.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::ptr;
5
6use itertools::Itertools as _;
7use vortex_buffer::Buffer;
8use vortex_buffer::BufferMut;
9use vortex_error::VortexResult;
10use vortex_error::vortex_ensure;
11use vortex_error::vortex_err;
12
13use crate::ArrayRef;
14use crate::Columnar;
15use crate::IntoArray;
16use crate::array::ArrayView;
17use crate::arrays::Decimal;
18use crate::arrays::DecimalArray;
19use crate::arrays::PiecewiseSequence;
20use crate::arrays::PrimitiveArray;
21use crate::arrays::dict::TakeExecute;
22use crate::arrays::piecewise_sequence::constant_unsigned_usize;
23use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
24use crate::dtype::IntegerPType;
25use crate::dtype::NativeDecimalType;
26use crate::dtype::UnsignedPType;
27use crate::executor::ExecutionCtx;
28use crate::match_each_decimal_value_type;
29use crate::match_each_integer_ptype;
30use crate::match_each_unsigned_integer_ptype;
31use crate::validity::Validity;
32
33impl TakeExecute for Decimal {
34    fn take(
35        array: ArrayView<'_, Decimal>,
36        indices: &ArrayRef,
37        ctx: &mut ExecutionCtx,
38    ) -> VortexResult<Option<ArrayRef>> {
39        if let Some(piecewise_indices) = indices.as_opt::<PiecewiseSequence>()
40            && let Some(taken) = take_contiguous_ranges(array, piecewise_indices, indices, ctx)?
41        {
42            return Ok(Some(taken));
43        }
44
45        let indices = indices.clone().execute::<PrimitiveArray>(ctx)?;
46        let validity = array.validity()?.take(&indices.clone().into_array())?;
47
48        // TODO(joe): if the true count of take indices validity is low, only take array values with
49        // valid indices.
50        let decimal = match_each_decimal_value_type!(array.values_type(), |D| {
51            match_each_integer_ptype!(indices.ptype(), |I| {
52                let buffer =
53                    take_to_buffer::<I, D>(indices.as_slice::<I>(), array.buffer::<D>().as_slice());
54                // SAFETY: Take operation preserves decimal dtype and creates valid buffer.
55                // Validity is computed correctly from the parent array and indices.
56                unsafe { DecimalArray::new_unchecked(buffer, array.decimal_dtype(), validity) }
57            })
58        });
59
60        Ok(Some(decimal.into_array()))
61    }
62}
63
64fn take_contiguous_ranges(
65    array: ArrayView<'_, Decimal>,
66    indices: ArrayView<'_, PiecewiseSequence>,
67    indices_ref: &ArrayRef,
68    ctx: &mut ExecutionCtx,
69) -> VortexResult<Option<ArrayRef>> {
70    let Some((starts, lengths)) = maybe_contiguous_slices(indices, ctx)? else {
71        return Ok(None);
72    };
73    let validity = array.validity()?.take(indices_ref)?;
74    let output_len = indices_ref.len();
75    let taken = match lengths {
76        Columnar::Constant(lengths) => {
77            let length = constant_unsigned_usize(&lengths);
78            take_slices_constant_length(array, &starts, length, validity, output_len)?
79        }
80        Columnar::Canonical(lengths) => {
81            let lengths = lengths.into_primitive();
82            take_slices(array, &starts, &lengths, validity, output_len)?
83        }
84    };
85    Ok(Some(taken))
86}
87
88fn take_slices_constant_length(
89    array: ArrayView<'_, Decimal>,
90    starts: &PrimitiveArray,
91    length: usize,
92    validity: Validity,
93    output_len: usize,
94) -> VortexResult<ArrayRef> {
95    match_each_decimal_value_type!(array.values_type(), |D| {
96        take_slices_constant_length_typed::<D>(array, starts, length, validity, output_len)
97    })
98}
99
100fn take_slices_constant_length_typed<D>(
101    array: ArrayView<'_, Decimal>,
102    starts: &PrimitiveArray,
103    length: usize,
104    validity: Validity,
105    output_len: usize,
106) -> VortexResult<ArrayRef>
107where
108    D: NativeDecimalType,
109{
110    match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
111        let values = take_slices_constant_length_to_buffer::<S, D>(
112            starts.as_slice::<S>(),
113            length,
114            array.buffer::<D>().as_slice(),
115            output_len,
116        )?;
117
118        // SAFETY: contiguous gather preserves the decimal dtype and value representation.
119        Ok(
120            unsafe { DecimalArray::new_unchecked(values, array.decimal_dtype(), validity) }
121                .into_array(),
122        )
123    })
124}
125
126fn take_slices(
127    array: ArrayView<'_, Decimal>,
128    starts: &PrimitiveArray,
129    lengths: &PrimitiveArray,
130    validity: Validity,
131    output_len: usize,
132) -> VortexResult<ArrayRef> {
133    match_each_decimal_value_type!(array.values_type(), |D| {
134        take_slices_typed::<D>(array, starts, lengths, validity, output_len)
135    })
136}
137
138fn take_slices_typed<D>(
139    array: ArrayView<'_, Decimal>,
140    starts: &PrimitiveArray,
141    lengths: &PrimitiveArray,
142    validity: Validity,
143    output_len: usize,
144) -> VortexResult<ArrayRef>
145where
146    D: NativeDecimalType,
147{
148    match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
149        take_slices_start_typed::<D, S>(array, starts, lengths, validity, output_len)
150    })
151}
152
153fn take_slices_start_typed<D, S>(
154    array: ArrayView<'_, Decimal>,
155    starts: &PrimitiveArray,
156    lengths: &PrimitiveArray,
157    validity: Validity,
158    output_len: usize,
159) -> VortexResult<ArrayRef>
160where
161    D: NativeDecimalType,
162    S: UnsignedPType,
163{
164    match_each_unsigned_integer_ptype!(lengths.ptype(), |L| {
165        let values = take_slices_to_buffer::<S, L, D>(
166            starts.as_slice::<S>(),
167            lengths.as_slice::<L>(),
168            array.buffer::<D>().as_slice(),
169            output_len,
170        )?;
171
172        // SAFETY: contiguous gather preserves the decimal dtype and value representation.
173        Ok(
174            unsafe { DecimalArray::new_unchecked(values, array.decimal_dtype(), validity) }
175                .into_array(),
176        )
177    })
178}
179
180fn take_to_buffer<I: IntegerPType, T: NativeDecimalType>(indices: &[I], values: &[T]) -> Buffer<T> {
181    indices.iter().map(|idx| values[idx.as_()]).collect()
182}
183
184fn take_slices_constant_length_to_buffer<S, T>(
185    starts: &[S],
186    length: usize,
187    values: &[T],
188    output_len: usize,
189) -> VortexResult<Buffer<T>>
190where
191    S: UnsignedPType,
192    T: NativeDecimalType,
193{
194    let computed_len = starts
195        .len()
196        .checked_mul(length)
197        .ok_or_else(|| vortex_err!("PiecewiseSequenceArray output length overflows usize"))?;
198    vortex_ensure!(
199        computed_len == output_len,
200        "PiecewiseSequenceArray expanded length {computed_len} does not match declared length {output_len}"
201    );
202
203    let mut result = BufferMut::<T>::with_capacity(output_len);
204    let spare = &mut result.spare_capacity_mut()[..output_len];
205    let mut cursor = 0usize;
206    for &start in starts {
207        let start = start.as_();
208        let src = &values[start..][..length];
209        // SAFETY: `src` and the checked `spare` range have equal lengths and cannot overlap.
210        unsafe {
211            ptr::copy_nonoverlapping(
212                src.as_ptr(),
213                spare[cursor..][..src.len()].as_mut_ptr().cast::<T>(),
214                src.len(),
215            );
216        }
217        cursor += src.len();
218    }
219    // SAFETY: the loop initialized the prefix `0..cursor` of the spare capacity.
220    unsafe { result.set_len(cursor) };
221    vortex_ensure!(
222        result.len() == output_len,
223        "PiecewiseSequenceArray expanded length {} does not match declared length {output_len}",
224        result.len()
225    );
226    Ok(result.freeze())
227}
228
229fn take_slices_to_buffer<S, L, T>(
230    starts: &[S],
231    lengths: &[L],
232    values: &[T],
233    output_len: usize,
234) -> VortexResult<Buffer<T>>
235where
236    S: UnsignedPType,
237    L: UnsignedPType,
238    T: NativeDecimalType,
239{
240    let mut result = BufferMut::<T>::with_capacity(output_len);
241    let spare = &mut result.spare_capacity_mut()[..output_len];
242    let mut cursor = 0usize;
243    for (&start, &length) in starts.iter().zip_eq(lengths) {
244        let start = start.as_();
245        let length = length.as_();
246        let src = &values[start..][..length];
247        // SAFETY: `src` and the checked `spare` range have equal lengths and cannot overlap.
248        unsafe {
249            ptr::copy_nonoverlapping(
250                src.as_ptr(),
251                spare[cursor..][..src.len()].as_mut_ptr().cast::<T>(),
252                src.len(),
253            );
254        }
255        cursor += src.len();
256    }
257    // SAFETY: the loop initialized the prefix `0..cursor` of the spare capacity.
258    unsafe { result.set_len(cursor) };
259    vortex_ensure!(
260        result.len() == output_len,
261        "PiecewiseSequenceArray expanded length {} does not match declared length {output_len}",
262        result.len()
263    );
264    Ok(result.freeze())
265}
266
267#[cfg(test)]
268mod tests {
269    use rstest::rstest;
270    use vortex_buffer::Buffer;
271    use vortex_buffer::buffer;
272
273    use crate::IntoArray;
274    use crate::VortexSessionExecute;
275    use crate::array_session;
276    use crate::arrays::DecimalArray;
277    use crate::arrays::PrimitiveArray;
278    use crate::assert_arrays_eq;
279    use crate::compute::conformance::take::test_take_conformance;
280    use crate::dtype::DecimalDType;
281    use crate::validity::Validity;
282
283    #[test]
284    fn test_take() {
285        let mut ctx = array_session().create_execution_ctx();
286        let ddtype = DecimalDType::new(19, 1);
287        let array = DecimalArray::new(
288            buffer![10i128, 11i128, 12i128, 13i128],
289            ddtype,
290            Validity::NonNullable,
291        );
292
293        let indices = buffer![0, 2, 3].into_array();
294        let taken = array.take(indices).unwrap();
295
296        let expected = DecimalArray::from_iter([10i128, 12, 13], ddtype);
297        assert_arrays_eq!(expected, taken, &mut ctx);
298    }
299
300    #[test]
301    fn test_take_null_indices() {
302        let mut ctx = array_session().create_execution_ctx();
303        let ddtype = DecimalDType::new(19, 1);
304        let array = DecimalArray::new(
305            buffer![i128::MAX, 11i128, 12i128, 13i128],
306            ddtype,
307            Validity::NonNullable,
308        );
309
310        let indices = PrimitiveArray::from_option_iter([None, Some(2), Some(3)]).into_array();
311        let taken = array.take(indices).unwrap();
312
313        let expected = DecimalArray::from_option_iter([None, Some(12i128), Some(13)], ddtype);
314        assert_arrays_eq!(expected, taken, &mut ctx);
315    }
316
317    #[rstest]
318    #[case(DecimalArray::new(
319        buffer![100i128, 200i128, 300i128, 400i128, 500i128],
320        DecimalDType::new(19, 2),
321        Validity::NonNullable,
322    ))]
323    #[case(DecimalArray::new(
324        buffer![10i64, 20i64, 30i64, 40i64, 50i64],
325        DecimalDType::new(10, 1),
326        Validity::NonNullable,
327    ))]
328    #[case(DecimalArray::new(
329        buffer![1i32, 2i32, 3i32, 4i32, 5i32],
330        DecimalDType::new(5, 0),
331        Validity::NonNullable,
332    ))]
333    #[case(DecimalArray::new(
334        buffer![1000i128, 2000i128, 3000i128, 4000i128, 5000i128],
335        DecimalDType::new(19, 3),
336        Validity::from_iter([true, false, true, true, false]),
337    ))]
338    #[case(DecimalArray::new(
339        buffer![42i128],
340        DecimalDType::new(19, 0),
341        Validity::NonNullable,
342    ))]
343    #[case({
344        let values: Vec<i128> = (0..100).map(|i| i * 1000).collect();
345        DecimalArray::new(
346            Buffer::from_iter(values),
347            DecimalDType::new(19, 4),
348            Validity::NonNullable,
349        )
350    })]
351    fn test_take_decimal_conformance(#[case] array: DecimalArray) {
352        test_take_conformance(
353            &array.into_array(),
354            &mut array_session().create_execution_ctx(),
355        );
356    }
357}