Skip to main content

vortex_fastlanes/rle/vtable/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::hash::Hash;
5use std::hash::Hasher;
6
7use prost::Message;
8use vortex_array::Array;
9use vortex_array::ArrayEq;
10use vortex_array::ArrayHash;
11use vortex_array::ArrayId;
12use vortex_array::ArrayParts;
13use vortex_array::ArrayRef;
14use vortex_array::ArrayView;
15use vortex_array::EqMode;
16use vortex_array::ExecutionCtx;
17use vortex_array::ExecutionResult;
18use vortex_array::IntoArray;
19use vortex_array::arrays::Primitive;
20use vortex_array::buffer::BufferHandle;
21use vortex_array::dtype::DType;
22use vortex_array::dtype::Nullability;
23use vortex_array::dtype::PType;
24use vortex_array::serde::ArrayChildren;
25use vortex_array::vtable::VTable;
26use vortex_error::VortexResult;
27use vortex_error::vortex_ensure;
28use vortex_error::vortex_panic;
29use vortex_session::VortexSession;
30use vortex_session::registry::CachedId;
31
32use crate::RLEData;
33use crate::rle::array::RLEArrayExt;
34use crate::rle::array::RLEArraySlotsExt;
35use crate::rle::array::RLESlots;
36use crate::rle::array::RLESlotsView;
37use crate::rle::array::rle_decompress::rle_decompress;
38use crate::rle::vtable::rules::RULES;
39
40mod operations;
41mod rules;
42mod validity;
43
44/// A [`RLE`]-encoded Vortex array.
45pub type RLEArray = Array<RLE>;
46
47#[derive(Clone, prost::Message)]
48pub struct RLEMetadata {
49    #[prost(uint64, tag = "1")]
50    pub values_len: u64,
51    #[prost(uint64, tag = "2")]
52    pub indices_len: u64,
53    #[prost(enumeration = "PType", tag = "3")]
54    pub indices_ptype: i32,
55    #[prost(uint64, tag = "4")]
56    pub values_idx_offsets_len: u64,
57    #[prost(enumeration = "PType", tag = "5")]
58    pub values_idx_offsets_ptype: i32,
59    #[prost(uint64, tag = "6", default = "0")]
60    pub offset: u64,
61}
62
63impl ArrayHash for RLEData {
64    fn array_hash<H: Hasher>(&self, state: &mut H, _accuracy: EqMode) {
65        self.offset.hash(state);
66    }
67}
68
69impl ArrayEq for RLEData {
70    fn array_eq(&self, other: &Self, _accuracy: EqMode) -> bool {
71        self.offset == other.offset
72    }
73}
74
75impl VTable for RLE {
76    type TypedArrayData = RLEData;
77
78    type OperationsVTable = Self;
79    type ValidityVTable = Self;
80
81    fn id(&self) -> ArrayId {
82        static ID: CachedId = CachedId::new("fastlanes.rle");
83        *ID
84    }
85
86    fn validate(
87        &self,
88        data: &Self::TypedArrayData,
89        dtype: &DType,
90        len: usize,
91        slots: &[Option<ArrayRef>],
92    ) -> VortexResult<()> {
93        let rle_slots = RLESlotsView::from_slots(slots);
94        validate_parts(
95            rle_slots.values,
96            rle_slots.indices,
97            rle_slots.values_idx_offsets,
98            data.offset,
99            dtype,
100            len,
101        )
102    }
103
104    fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
105        0
106    }
107
108    fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
109        vortex_panic!("RLEArray buffer index {idx} out of bounds")
110    }
111
112    fn buffer_name(_array: ArrayView<'_, Self>, _idx: usize) -> Option<String> {
113        None
114    }
115
116    fn with_buffers(
117        &self,
118        array: ArrayView<'_, Self>,
119        buffers: &[BufferHandle],
120    ) -> VortexResult<ArrayParts<Self>> {
121        vortex_array::vtable::with_empty_buffers(self, array, buffers)
122    }
123
124    fn reduce_parent(
125        array: ArrayView<'_, Self>,
126        parent: &ArrayRef,
127        child_idx: usize,
128    ) -> VortexResult<Option<ArrayRef>> {
129        RULES.evaluate(array, parent, child_idx)
130    }
131
132    fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
133        RLESlots::NAMES[idx].to_string()
134    }
135
136    fn serialize(
137        array: ArrayView<'_, Self>,
138        _session: &VortexSession,
139    ) -> VortexResult<Option<Vec<u8>>> {
140        Ok(Some(
141            RLEMetadata {
142                values_len: array.values().len() as u64,
143                indices_len: array.indices().len() as u64,
144                indices_ptype: PType::try_from(array.indices().dtype())? as i32,
145                values_idx_offsets_len: array.values_idx_offsets().len() as u64,
146                values_idx_offsets_ptype: PType::try_from(array.values_idx_offsets().dtype())?
147                    as i32,
148                offset: array.offset() as u64,
149            }
150            .encode_to_vec(),
151        ))
152    }
153
154    fn deserialize(
155        &self,
156        dtype: &DType,
157        len: usize,
158        metadata: &[u8],
159        buffers: &[BufferHandle],
160        children: &dyn ArrayChildren,
161        _session: &VortexSession,
162    ) -> VortexResult<ArrayParts<Self>> {
163        vortex_ensure!(
164            buffers.is_empty(),
165            "RLEArray expects 0 buffers, got {}",
166            buffers.len()
167        );
168        let metadata = RLEMetadata::decode(metadata)?;
169        let values = children.get(
170            0,
171            &DType::Primitive(dtype.as_ptype(), Nullability::NonNullable),
172            usize::try_from(metadata.values_len)?,
173        )?;
174
175        let indices = children.get(
176            1,
177            &DType::Primitive(metadata.indices_ptype(), dtype.nullability()),
178            usize::try_from(metadata.indices_len)?,
179        )?;
180
181        let values_idx_offsets = children.get(
182            2,
183            &DType::Primitive(
184                metadata.values_idx_offsets_ptype(),
185                Nullability::NonNullable,
186            ),
187            usize::try_from(metadata.values_idx_offsets_len)?,
188        )?;
189
190        let slots = RLESlots {
191            values,
192            indices,
193            values_idx_offsets,
194        }
195        .into_slots();
196        let data = RLEData::try_new(metadata.offset as usize)?;
197        Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
198    }
199
200    fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
201        Ok(ExecutionResult::done(
202            rle_decompress(&array, ctx)?.into_array(),
203        ))
204    }
205}
206
207#[derive(Clone, Debug)]
208pub struct RLE;
209
210impl RLE {
211    pub fn try_new(
212        values: ArrayRef,
213        indices: ArrayRef,
214        values_idx_offsets: ArrayRef,
215        offset: usize,
216        length: usize,
217    ) -> VortexResult<RLEArray> {
218        let dtype = DType::Primitive(values.dtype().as_ptype(), indices.dtype().nullability());
219        let slots = RLESlots {
220            values,
221            indices,
222            values_idx_offsets,
223        }
224        .into_slots();
225        let data = RLEData::try_new(offset)?;
226        Array::try_from_parts(ArrayParts::new(RLE, dtype, length, data).with_slots(slots))
227    }
228
229    /// Create a new RLE array without validation.
230    ///
231    /// # Safety
232    /// See [`RLE::validate`] for preconditions.
233    pub unsafe fn new_unchecked(
234        values: ArrayRef,
235        indices: ArrayRef,
236        values_idx_offsets: ArrayRef,
237        offset: usize,
238        length: usize,
239    ) -> RLEArray {
240        let dtype = DType::Primitive(values.dtype().as_ptype(), indices.dtype().nullability());
241        let slots = RLESlots {
242            values,
243            indices,
244            values_idx_offsets,
245        }
246        .into_slots();
247        let data = unsafe { RLEData::new_unchecked(offset) };
248        unsafe {
249            Array::from_parts_unchecked(ArrayParts::new(RLE, dtype, length, data).with_slots(slots))
250        }
251    }
252
253    /// Encode a primitive array using FastLanes RLE.
254    pub fn encode(
255        array: ArrayView<'_, Primitive>,
256        ctx: &mut ExecutionCtx,
257    ) -> VortexResult<RLEArray> {
258        RLEData::encode(array, ctx)
259    }
260}
261
262fn validate_parts(
263    values: &ArrayRef,
264    indices: &ArrayRef,
265    values_idx_offsets: &ArrayRef,
266    offset: usize,
267    dtype: &DType,
268    length: usize,
269) -> VortexResult<()> {
270    vortex_ensure!(
271        matches!(
272            values.dtype(),
273            DType::Primitive(_, Nullability::NonNullable)
274        ),
275        "RLE values must be a non-nullable primitive type, got {}",
276        values.dtype()
277    );
278
279    vortex_ensure!(
280        matches!(indices.dtype().as_ptype(), PType::U8 | PType::U16),
281        "RLE indices must be u8 or u16, got {}",
282        indices.dtype()
283    );
284
285    vortex_ensure!(
286        values_idx_offsets.dtype().is_unsigned_int() && !values_idx_offsets.dtype().is_nullable(),
287        "RLE value idx offsets must be non-nullable unsigned integer, got {}",
288        values_idx_offsets.dtype()
289    );
290
291    vortex_ensure!(
292        indices.len().is_multiple_of(crate::FL_CHUNK_SIZE),
293        "RLE indices length must be a multiple of {}, got {}",
294        crate::FL_CHUNK_SIZE,
295        indices.len()
296    );
297
298    vortex_ensure!(
299        offset + length <= indices.len(),
300        "RLE offset + length, {offset} + {length}, must not exceed the indices length {}",
301        indices.len()
302    );
303
304    vortex_ensure!(
305        indices.len().div_ceil(crate::FL_CHUNK_SIZE) == values_idx_offsets.len(),
306        "RLE must have one value idx offset per chunk, got {}",
307        values_idx_offsets.len()
308    );
309
310    vortex_ensure!(
311        indices.len() >= values.len(),
312        "RLE must have at least as many indices as values, got {} indices and {} values",
313        indices.len(),
314        values.len()
315    );
316
317    let expected_dtype = DType::Primitive(values.dtype().as_ptype(), indices.dtype().nullability());
318    vortex_ensure!(
319        dtype == &expected_dtype,
320        "RLE dtype mismatch: expected {expected_dtype}, got {dtype}"
321    );
322
323    Ok(())
324}
325
326#[cfg(test)]
327mod tests {
328    use prost::Message;
329    use vortex_array::test_harness::check_metadata;
330
331    use super::RLEMetadata;
332
333    #[cfg_attr(miri, ignore)]
334    #[test]
335    fn test_rle_metadata() {
336        check_metadata(
337            "rle.metadata",
338            &RLEMetadata {
339                values_len: u64::MAX,
340                indices_len: u64::MAX,
341                indices_ptype: i32::MAX,
342                values_idx_offsets_len: u64::MAX,
343                values_idx_offsets_ptype: i32::MAX,
344                offset: u64::MAX,
345            }
346            .encode_to_vec(),
347        );
348    }
349}