Skip to main content

vortex_runend/
compress.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use itertools::Itertools;
5use vortex_array::ArrayRef;
6use vortex_array::ArrayView;
7use vortex_array::ExecutionCtx;
8use vortex_array::IntoArray;
9use vortex_array::arrays::BoolArray;
10use vortex_array::arrays::ConstantArray;
11use vortex_array::arrays::DecimalArray;
12use vortex_array::arrays::Primitive;
13use vortex_array::arrays::PrimitiveArray;
14use vortex_array::arrays::VarBinViewArray;
15use vortex_array::arrays::bool::BoolArrayExt;
16use vortex_array::arrays::decimal::DecimalArrayExt;
17use vortex_array::arrays::primitive::PrimitiveArrayExt;
18use vortex_array::buffer::BufferHandle;
19use vortex_array::dtype::NativePType;
20use vortex_array::dtype::Nullability;
21use vortex_array::expr::stats::Precision;
22use vortex_array::expr::stats::Stat;
23use vortex_array::match_each_decimal_value_type;
24use vortex_array::match_each_native_ptype;
25use vortex_array::match_each_unsigned_integer_ptype;
26use vortex_array::scalar::Scalar;
27use vortex_array::validity::Validity;
28use vortex_buffer::BitBuffer;
29use vortex_buffer::BitBufferMut;
30use vortex_buffer::Buffer;
31use vortex_buffer::BufferMut;
32use vortex_buffer::buffer;
33use vortex_error::VortexExpect;
34use vortex_error::VortexResult;
35use vortex_mask::Mask;
36
37use crate::iter::trimmed_ends_iter;
38
39/// Run-end encode a `PrimitiveArray`, returning a tuple of `(ends, values)`.
40pub fn runend_encode(
41    array: ArrayView<Primitive>,
42    ctx: &mut ExecutionCtx,
43) -> (PrimitiveArray, ArrayRef) {
44    let validity = match array
45        .validity()
46        .vortex_expect("run-end validity should be derivable")
47    {
48        Validity::NonNullable => None,
49        Validity::AllValid => None,
50        Validity::AllInvalid => {
51            // We can trivially return an all-null REE array
52            let ends = PrimitiveArray::new(buffer![array.len() as u64], Validity::NonNullable);
53            ends.statistics()
54                .set(Stat::IsStrictSorted, Precision::Exact(true.into()));
55            return (
56                ends,
57                ConstantArray::new(Scalar::null(array.dtype().clone()), 1).into_array(),
58            );
59        }
60        Validity::Array(a) => {
61            let bool_array = a
62                .execute::<BoolArray>(ctx)
63                .vortex_expect("validity array must be convertible to bool");
64            Some(bool_array.to_bit_buffer())
65        }
66    };
67
68    let (ends, values) = match validity {
69        None => {
70            match_each_native_ptype!(array.ptype(), |P| {
71                let (ends, values) = runend_encode_primitive(array.as_slice::<P>());
72                (
73                    PrimitiveArray::new(ends, Validity::NonNullable),
74                    PrimitiveArray::new(values, array.dtype().nullability().into()).into_array(),
75                )
76            })
77        }
78        Some(validity) => {
79            match_each_native_ptype!(array.ptype(), |P| {
80                let (ends, values) =
81                    runend_encode_nullable_primitive(array.as_slice::<P>(), validity);
82                (
83                    PrimitiveArray::new(ends, Validity::NonNullable),
84                    values.into_array(),
85                )
86            })
87        }
88    };
89
90    let ends = ends
91        .narrow(ctx)
92        .vortex_expect("Ends must succeed downcasting");
93
94    ends.statistics()
95        .set(Stat::IsStrictSorted, Precision::Exact(true.into()));
96
97    (ends, values)
98}
99
100fn runend_encode_primitive<T: NativePType>(elements: &[T]) -> (Buffer<u64>, Buffer<T>) {
101    let mut ends = BufferMut::empty();
102    let mut values = BufferMut::empty();
103
104    if elements.is_empty() {
105        return (ends.freeze(), values.freeze());
106    }
107
108    // Run-end encode the values
109    let mut prev = elements[0];
110    let mut end = 1;
111    for &e in elements.iter().skip(1) {
112        if e != prev {
113            ends.push(end);
114            values.push(prev);
115        }
116        prev = e;
117        end += 1;
118    }
119    ends.push(end);
120    values.push(prev);
121
122    (ends.freeze(), values.freeze())
123}
124
125fn runend_encode_nullable_primitive<T: NativePType>(
126    elements: &[T],
127    element_validity: BitBuffer,
128) -> (Buffer<u64>, PrimitiveArray) {
129    let mut ends = BufferMut::empty();
130    let mut values = BufferMut::empty();
131    let mut validity = BitBufferMut::with_capacity(values.capacity());
132
133    if elements.is_empty() {
134        return (
135            ends.freeze(),
136            PrimitiveArray::new(
137                values,
138                Validity::Array(BoolArray::from(validity.freeze()).into_array()),
139            ),
140        );
141    }
142
143    // Run-end encode the values
144    let mut prev = element_validity.value(0).then(|| elements[0]);
145    let mut end = 1;
146    for e in elements
147        .iter()
148        .zip(element_validity.iter())
149        .map(|(&e, is_valid)| is_valid.then_some(e))
150        .skip(1)
151    {
152        if e != prev {
153            ends.push(end);
154            match prev {
155                None => {
156                    validity.append(false);
157                    values.push(T::default());
158                }
159                Some(p) => {
160                    validity.append(true);
161                    values.push(p);
162                }
163            }
164        }
165        prev = e;
166        end += 1;
167    }
168    ends.push(end);
169
170    match prev {
171        None => {
172            validity.append(false);
173            values.push(T::default());
174        }
175        Some(p) => {
176            validity.append(true);
177            values.push(p);
178        }
179    }
180
181    (
182        ends.freeze(),
183        PrimitiveArray::new(values, Validity::from(validity.freeze())),
184    )
185}
186
187pub fn runend_decode_primitive(
188    ends: PrimitiveArray,
189    values: PrimitiveArray,
190    offset: usize,
191    length: usize,
192    ctx: &mut ExecutionCtx,
193) -> VortexResult<PrimitiveArray> {
194    let validity_mask = values
195        .as_ref()
196        .validity()?
197        .execute_mask(values.as_ref().len(), ctx)?;
198    Ok(match_each_native_ptype!(values.ptype(), |P| {
199        match_each_unsigned_integer_ptype!(ends.ptype(), |E| {
200            runend_decode_typed_primitive(
201                trimmed_ends_iter(ends.as_slice::<E>(), offset, length),
202                values.as_slice::<P>(),
203                validity_mask,
204                values.dtype().nullability(),
205                length,
206            )
207        })
208    }))
209}
210
211/// Decode a run-end encoded slice of values into a flat `Buffer<T>` and `Validity`.
212///
213/// This is the core decode loop shared by primitive and varbinview run-end decoding.
214fn runend_decode_slice<T: Copy + Default>(
215    run_ends: impl Iterator<Item = usize>,
216    values: &[T],
217    values_validity: Mask,
218    values_nullability: Nullability,
219    length: usize,
220) -> (Buffer<T>, Validity) {
221    match values_validity {
222        Mask::AllTrue(_) => {
223            let mut decoded: BufferMut<T> = BufferMut::with_capacity(length);
224            for (end, value) in run_ends.zip_eq(values) {
225                assert!(
226                    end >= decoded.len(),
227                    "Runend ends must be monotonic, got {end} after {}",
228                    decoded.len()
229                );
230                assert!(end <= length, "Runend end must be less than overall length");
231                // SAFETY:
232                // We preallocate enough capacity because we know the total length
233                unsafe { decoded.push_n_unchecked(*value, end - decoded.len()) };
234            }
235            (decoded.into(), values_nullability.into())
236        }
237        Mask::AllFalse(_) => (Buffer::<T>::zeroed(length), Validity::AllInvalid),
238        Mask::Values(mask) => {
239            let mut decoded = BufferMut::with_capacity(length);
240            let mut decoded_validity = BitBufferMut::with_capacity(length);
241            for (end, value) in run_ends.zip_eq(
242                values
243                    .iter()
244                    .zip(mask.bit_buffer().iter())
245                    .map(|(&v, is_valid)| is_valid.then_some(v)),
246            ) {
247                assert!(
248                    end >= decoded.len(),
249                    "Runend ends must be monotonic, got {end} after {}",
250                    decoded.len()
251                );
252                assert!(end <= length, "Runend end must be less than overall length");
253                match value {
254                    None => {
255                        decoded_validity.append_n(false, end - decoded.len());
256                        // SAFETY:
257                        // We preallocate enough capacity because we know the total length
258                        unsafe { decoded.push_n_unchecked(T::default(), end - decoded.len()) };
259                    }
260                    Some(value) => {
261                        decoded_validity.append_n(true, end - decoded.len());
262                        // SAFETY:
263                        // We preallocate enough capacity because we know the total length
264                        unsafe { decoded.push_n_unchecked(value, end - decoded.len()) };
265                    }
266                }
267            }
268            (decoded.into(), Validity::from(decoded_validity.freeze()))
269        }
270    }
271}
272
273pub fn runend_decode_typed_primitive<T: NativePType>(
274    run_ends: impl Iterator<Item = usize>,
275    values: &[T],
276    values_validity: Mask,
277    values_nullability: Nullability,
278    length: usize,
279) -> PrimitiveArray {
280    let (decoded, validity) = runend_decode_slice(
281        run_ends,
282        values,
283        values_validity,
284        values_nullability,
285        length,
286    );
287    PrimitiveArray::new(decoded, validity)
288}
289
290/// Decode a run-end encoded decimal array by expanding its native values.
291pub fn runend_decode_decimal(
292    ends: PrimitiveArray,
293    values: DecimalArray,
294    offset: usize,
295    length: usize,
296    ctx: &mut ExecutionCtx,
297) -> VortexResult<DecimalArray> {
298    let validity_mask = values
299        .as_ref()
300        .validity()?
301        .execute_mask(values.as_ref().len(), ctx)?;
302
303    Ok(match_each_decimal_value_type!(values.values_type(), |D| {
304        let (decoded, validity) = match_each_unsigned_integer_ptype!(ends.ptype(), |E| {
305            runend_decode_slice(
306                trimmed_ends_iter(ends.as_slice::<E>(), offset, length),
307                values.buffer::<D>().as_slice(),
308                validity_mask,
309                values.dtype().nullability(),
310                length,
311            )
312        });
313        DecimalArray::new(decoded, values.decimal_dtype(), validity)
314    }))
315}
316
317/// Decode a run-end encoded VarBinView array by expanding views directly.
318pub fn runend_decode_varbinview(
319    ends: PrimitiveArray,
320    values: VarBinViewArray,
321    offset: usize,
322    length: usize,
323    ctx: &mut ExecutionCtx,
324) -> VortexResult<VarBinViewArray> {
325    let validity_mask = values
326        .as_ref()
327        .validity()?
328        .execute_mask(values.as_ref().len(), ctx)?;
329    let views = values.views();
330
331    let (decoded_views, validity) = match_each_unsigned_integer_ptype!(ends.ptype(), |E| {
332        runend_decode_slice(
333            trimmed_ends_iter(ends.as_slice::<E>(), offset, length),
334            views,
335            validity_mask,
336            values.dtype().nullability(),
337            length,
338        )
339    });
340
341    let parts = values.into_data_parts();
342    let view_handle = BufferHandle::new_host(decoded_views.into_byte_buffer());
343
344    // SAFETY: we are expanding views from a valid VarBinViewArray with the same
345    // buffers, so all buffer indices and offsets remain valid.
346    Ok(unsafe {
347        VarBinViewArray::new_handle_unchecked(view_handle, parts.buffers, parts.dtype, validity)
348    })
349}
350
351#[cfg(test)]
352mod tests {
353    use std::sync::LazyLock;
354
355    use vortex_array::VortexSessionExecute;
356    use vortex_array::arrays::PrimitiveArray;
357    use vortex_array::assert_arrays_eq;
358    use vortex_array::validity::Validity;
359    use vortex_buffer::BitBuffer;
360    use vortex_buffer::buffer;
361    use vortex_error::VortexResult;
362    use vortex_session::VortexSession;
363
364    use crate::compress::runend_decode_primitive;
365    use crate::compress::runend_encode;
366
367    static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
368        let session = vortex_array::array_session();
369        crate::initialize(&session);
370        session
371    });
372
373    #[test]
374    fn encode() -> VortexResult<()> {
375        let mut ctx = SESSION.create_execution_ctx();
376        let arr = PrimitiveArray::from_iter([1i32, 1, 2, 2, 2, 3, 3, 3, 3, 3]);
377        let (ends, values) = runend_encode(arr.as_view(), &mut ctx);
378        let values = values.execute::<PrimitiveArray>(&mut ctx)?;
379
380        let expected_ends = PrimitiveArray::from_iter(vec![2u8, 5, 10]);
381        assert_arrays_eq!(ends, expected_ends, &mut ctx);
382        let expected_values = PrimitiveArray::from_iter(vec![1i32, 2, 3]);
383        assert_arrays_eq!(values, expected_values, &mut ctx);
384        Ok(())
385    }
386
387    #[test]
388    fn encode_nullable() -> VortexResult<()> {
389        let mut ctx = SESSION.create_execution_ctx();
390        let arr = PrimitiveArray::new(
391            buffer![1i32, 1, 2, 2, 2, 3, 3, 3, 3, 3],
392            Validity::from(BitBuffer::from(vec![
393                true, true, false, false, true, true, true, true, false, false,
394            ])),
395        );
396        let (ends, values) = runend_encode(arr.as_view(), &mut ctx);
397        let values = values.execute::<PrimitiveArray>(&mut ctx)?;
398
399        let expected_ends = PrimitiveArray::from_iter(vec![2u8, 4, 5, 8, 10]);
400        assert_arrays_eq!(ends, expected_ends, &mut ctx);
401        let expected_values =
402            PrimitiveArray::from_option_iter(vec![Some(1i32), None, Some(2), Some(3), None]);
403        assert_arrays_eq!(values, expected_values, &mut ctx);
404        Ok(())
405    }
406
407    #[test]
408    fn encode_all_null() -> VortexResult<()> {
409        let mut ctx = SESSION.create_execution_ctx();
410        let arr = PrimitiveArray::new(
411            buffer![0, 0, 0, 0, 0],
412            Validity::from(BitBuffer::new_unset(5)),
413        );
414        let (ends, values) = runend_encode(arr.as_view(), &mut ctx);
415        let values = values.execute::<PrimitiveArray>(&mut ctx)?;
416
417        let expected_ends = PrimitiveArray::from_iter(vec![5u64]);
418        assert_arrays_eq!(ends, expected_ends, &mut ctx);
419        let expected_values = PrimitiveArray::from_option_iter(vec![Option::<i32>::None]);
420        assert_arrays_eq!(values, expected_values, &mut ctx);
421        Ok(())
422    }
423
424    #[test]
425    fn decode() -> VortexResult<()> {
426        let mut ctx = SESSION.create_execution_ctx();
427        let ends = PrimitiveArray::from_iter([2u32, 5, 10]);
428        let values = PrimitiveArray::from_iter([1i32, 2, 3]);
429        let decoded = runend_decode_primitive(ends, values, 0, 10, &mut ctx)?;
430
431        let expected = PrimitiveArray::from_iter(vec![1i32, 1, 2, 2, 2, 3, 3, 3, 3, 3]);
432        assert_arrays_eq!(decoded, expected, &mut ctx);
433        Ok(())
434    }
435}