1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::hash::Hash;
8use std::hash::Hasher;
9
10use prost::Message;
11use vortex_array::Array;
12use vortex_array::ArrayEq;
13use vortex_array::ArrayHash;
14use vortex_array::ArrayId;
15use vortex_array::ArrayParts;
16use vortex_array::ArrayRef;
17use vortex_array::ArrayView;
18use vortex_array::EqMode;
19use vortex_array::ExecutionCtx;
20use vortex_array::ExecutionResult;
21use vortex_array::IntoArray;
22use vortex_array::TypedArrayRef;
23use vortex_array::VortexSessionExecute;
24use vortex_array::array_slots;
25use vortex_array::arrays::Primitive;
26use vortex_array::arrays::VarBinViewArray;
27use vortex_array::buffer::BufferHandle;
28use vortex_array::dtype::DType;
29use vortex_array::dtype::Nullability;
30use vortex_array::dtype::PType;
31use vortex_array::legacy_session;
32use vortex_array::serde::ArrayChildren;
33use vortex_array::validity::Validity;
34use vortex_array::vtable::VTable;
35use vortex_array::vtable::ValidityVTable;
36use vortex_error::VortexExpect as _;
37use vortex_error::VortexResult;
38use vortex_error::vortex_bail;
39use vortex_error::vortex_ensure;
40use vortex_error::vortex_panic;
41use vortex_session::VortexSession;
42use vortex_session::registry::CachedId;
43
44use crate::compress::runend_decode_primitive;
45use crate::compress::runend_decode_varbinview;
46use crate::compress::runend_encode;
47use crate::decompress_bool::runend_decode_bools;
48use crate::ops::find_physical_index;
49use crate::ops::find_slice_end_index;
50use crate::rules::RULES;
51
52pub type RunEndArray = Array<RunEnd>;
54
55#[derive(Clone, prost::Message)]
56pub struct RunEndMetadata {
57 #[prost(enumeration = "PType", tag = "1")]
58 pub ends_ptype: i32,
59 #[prost(uint64, tag = "2")]
60 pub num_runs: u64,
61 #[prost(uint64, tag = "3")]
62 pub offset: u64,
63}
64
65impl ArrayHash for RunEndData {
66 fn array_hash<H: Hasher>(&self, state: &mut H, _accuracy: EqMode) {
67 self.offset.hash(state);
68 }
69}
70
71impl ArrayEq for RunEndData {
72 fn array_eq(&self, other: &Self, _accuracy: EqMode) -> bool {
73 self.offset == other.offset
74 }
75}
76
77impl VTable for RunEnd {
78 type TypedArrayData = RunEndData;
79
80 type OperationsVTable = Self;
81 type ValidityVTable = Self;
82
83 fn id(&self) -> ArrayId {
84 static ID: CachedId = CachedId::new("vortex.runend");
85 *ID
86 }
87
88 #[allow(clippy::disallowed_methods)]
89 fn validate(
90 &self,
91 data: &Self::TypedArrayData,
92 dtype: &DType,
93 len: usize,
94 slots: &[Option<ArrayRef>],
95 ) -> VortexResult<()> {
96 let run_end_slots = RunEndSlotsView::from_slots(slots);
97 let ends = run_end_slots.ends;
98 let values = run_end_slots.values;
99 let mut ctx = legacy_session().create_execution_ctx();
101 RunEndData::validate_parts(ends, values, data.offset, len, &mut ctx)?;
102 vortex_ensure!(
103 values.dtype() == dtype,
104 "expected dtype {}, got {}",
105 dtype,
106 values.dtype()
107 );
108 Ok(())
109 }
110
111 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
112 0
113 }
114
115 fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
116 vortex_panic!("RunEndArray buffer index {idx} out of bounds")
117 }
118
119 fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
120 vortex_panic!("RunEndArray buffer_name index {idx} out of bounds")
121 }
122
123 fn with_buffers(
124 &self,
125 array: ArrayView<'_, Self>,
126 buffers: &[BufferHandle],
127 ) -> VortexResult<ArrayParts<Self>> {
128 vortex_array::vtable::with_empty_buffers(self, array, buffers)
129 }
130
131 fn serialize(
132 array: ArrayView<'_, Self>,
133 _session: &VortexSession,
134 ) -> VortexResult<Option<Vec<u8>>> {
135 Ok(Some(
136 RunEndMetadata {
137 ends_ptype: PType::try_from(array.ends().dtype())
138 .vortex_expect("Must be a valid PType") as i32,
139 num_runs: array.ends().len() as u64,
140 offset: array.offset() as u64,
141 }
142 .encode_to_vec(),
143 ))
144 }
145
146 fn deserialize(
147 &self,
148 dtype: &DType,
149 len: usize,
150 metadata: &[u8],
151 _buffers: &[BufferHandle],
152 children: &dyn ArrayChildren,
153 _session: &VortexSession,
154 ) -> VortexResult<ArrayParts<Self>> {
155 let metadata = RunEndMetadata::decode(metadata)?;
156 let ends_dtype = DType::Primitive(metadata.ends_ptype(), Nullability::NonNullable);
157 let runs = usize::try_from(metadata.num_runs).vortex_expect("Must be a valid usize");
158 let ends = children.get(0, &ends_dtype, runs)?;
159
160 let values = children.get(1, dtype, runs)?;
161 let offset = usize::try_from(metadata.offset).vortex_expect("Offset must be a valid usize");
162 let slots = RunEndSlots { ends, values }.into_slots();
163 let data = RunEndData::new(offset);
164 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
165 }
166
167 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
168 RunEndSlots::NAMES[idx].to_string()
169 }
170
171 fn reduce_parent(
172 array: ArrayView<'_, Self>,
173 parent: &ArrayRef,
174 child_idx: usize,
175 ) -> VortexResult<Option<ArrayRef>> {
176 RULES.evaluate(array, parent, child_idx)
177 }
178
179 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
180 run_end_canonicalize(&array, ctx).map(ExecutionResult::done)
181 }
182}
183
184#[array_slots(RunEnd)]
185pub struct RunEndSlots {
186 #[slot(0)]
188 pub ends: ArrayRef,
189 #[slot(1)]
191 pub values: ArrayRef,
192}
193
194#[derive(Clone, Debug)]
195pub struct RunEndData {
196 offset: usize,
197}
198
199impl Display for RunEndData {
200 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
201 write!(f, "offset: {}", self.offset)
202 }
203}
204
205pub struct RunEndDataParts {
206 pub ends: ArrayRef,
207 pub values: ArrayRef,
208 pub offset: usize,
209}
210
211pub trait RunEndArrayExt: RunEndArraySlotsExt {
212 fn offset(&self) -> usize {
213 self.offset
214 }
215
216 fn dtype(&self) -> &DType {
217 self.values().dtype()
218 }
219
220 fn find_physical_index(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
221 find_physical_index(self.ends(), index + self.offset(), ctx)
222 }
223
224 fn find_slice_end_index(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
225 find_slice_end_index(self.ends(), index + self.offset(), ctx)
226 }
227}
228
229impl<T: TypedArrayRef<RunEnd>> RunEndArrayExt for T {}
230
231#[derive(Clone, Debug)]
232pub struct RunEnd;
233
234impl RunEnd {
235 pub unsafe fn new_unchecked(
240 ends: ArrayRef,
241 values: ArrayRef,
242 offset: usize,
243 length: usize,
244 ) -> RunEndArray {
245 let dtype = values.dtype().clone();
246 let slots = RunEndSlots { ends, values }.into_slots();
247 let data = unsafe { RunEndData::new_unchecked(offset) };
248 unsafe {
249 Array::from_parts_unchecked(
250 ArrayParts::new(RunEnd, dtype, length, data).with_slots(slots),
251 )
252 }
253 }
254
255 pub fn try_new(
257 ends: ArrayRef,
258 values: ArrayRef,
259 ctx: &mut ExecutionCtx,
260 ) -> VortexResult<RunEndArray> {
261 let len = RunEndData::logical_len_from_ends(&ends, ctx)?;
262 RunEndData::validate_parts(&ends, &values, 0, len, ctx)?;
263 let dtype = values.dtype().clone();
264 let slots = RunEndSlots { ends, values }.into_slots();
265 let data = RunEndData::new(0);
266 Array::try_from_parts(ArrayParts::new(RunEnd, dtype, len, data).with_slots(slots))
267 }
268
269 pub fn try_new_offset_length(
271 ends: ArrayRef,
272 values: ArrayRef,
273 offset: usize,
274 length: usize,
275 ctx: &mut ExecutionCtx,
276 ) -> VortexResult<RunEndArray> {
277 RunEndData::validate_parts(&ends, &values, offset, length, ctx)?;
278 let dtype = values.dtype().clone();
279 let slots = RunEndSlots { ends, values }.into_slots();
280 let data = RunEndData::new(offset);
281 Array::try_from_parts(ArrayParts::new(RunEnd, dtype, length, data).with_slots(slots))
282 }
283
284 pub fn new(ends: ArrayRef, values: ArrayRef, ctx: &mut ExecutionCtx) -> RunEndArray {
286 Self::try_new(ends, values, ctx).vortex_expect("RunEndData is always valid")
287 }
288
289 pub fn encode(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<RunEndArray> {
291 if let Some(parray) = array.as_opt::<Primitive>() {
292 let (ends, values) = runend_encode(parray, ctx);
293 let ends = ends.into_array();
294 let len = array.len();
295 let dtype = values.dtype().clone();
296 let slots = RunEndSlots { ends, values }.into_slots();
297 let data = unsafe { RunEndData::new_unchecked(0) };
298 Array::try_from_parts(ArrayParts::new(RunEnd, dtype, len, data).with_slots(slots))
299 } else {
300 vortex_bail!("REE can only encode primitive arrays")
301 }
302 }
303}
304
305impl RunEndData {
306 fn logical_len_from_ends(ends: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
307 if ends.is_empty() {
308 Ok(0)
309 } else {
310 usize::try_from(&ends.execute_scalar(ends.len() - 1, ctx)?)
311 }
312 }
313
314 pub fn validate_parts(
317 ends: &ArrayRef,
318 values: &ArrayRef,
319 offset: usize,
320 length: usize,
321 ctx: &mut ExecutionCtx,
322 ) -> VortexResult<()> {
323 vortex_ensure!(
325 ends.dtype().is_unsigned_int(),
326 "run ends must be unsigned integers, was {}",
327 ends.dtype(),
328 );
329 vortex_ensure!(
330 ends.len() == values.len(),
331 "run ends len != run values len, {} != {}",
332 ends.len(),
333 values.len()
334 );
335
336 if ends.is_empty() {
338 vortex_ensure!(
339 offset == 0,
340 "non-zero offset provided for empty RunEndArray"
341 );
342 return Ok(());
343 }
344
345 if length == 0 {
347 return Ok(());
348 }
349
350 #[cfg(debug_assertions)]
351 {
352 let pre_validation = ends.statistics().to_owned();
354
355 let is_sorted = ends
356 .statistics()
357 .compute_is_strict_sorted(ctx)
358 .unwrap_or(false);
359
360 ends.statistics().inherit(pre_validation.iter());
363 debug_assert!(is_sorted);
364 }
365
366 if !ends.is_host() {
368 return Ok(());
369 }
370
371 if offset != 0 && length != 0 {
373 let first_run_end = usize::try_from(&ends.execute_scalar(0, ctx)?)?;
374 if first_run_end < offset {
375 vortex_bail!("First run end {first_run_end} must be >= offset {offset}");
376 }
377 }
378
379 let last_run_end = usize::try_from(&ends.execute_scalar(ends.len() - 1, ctx)?)?;
380 let min_required_end = offset + length;
381 if last_run_end < min_required_end {
382 vortex_bail!("Last run end {last_run_end} must be >= offset+length {min_required_end}");
383 }
384
385 Ok(())
386 }
387}
388
389impl RunEndData {
390 pub fn new(offset: usize) -> Self {
420 Self { offset }
421 }
422
423 pub unsafe fn new_unchecked(offset: usize) -> Self {
433 Self { offset }
434 }
435
436 pub fn encode(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
438 if let Some(parray) = array.as_opt::<Primitive>() {
439 let (_ends, _values) = runend_encode(parray, ctx);
440 unsafe { Ok(Self::new_unchecked(0)) }
442 } else {
443 vortex_bail!("REE can only encode primitive arrays")
444 }
445 }
446
447 pub fn into_parts(self, ends: ArrayRef, values: ArrayRef) -> RunEndDataParts {
448 RunEndDataParts {
449 ends,
450 values,
451 offset: self.offset,
452 }
453 }
454}
455
456impl ValidityVTable<RunEnd> for RunEnd {
457 fn validity(array: ArrayView<'_, RunEnd>) -> VortexResult<Validity> {
458 Ok(match array.values().validity()? {
459 Validity::NonNullable | Validity::AllValid => Validity::AllValid,
460 Validity::AllInvalid => Validity::AllInvalid,
461 Validity::Array(values_validity) => Validity::Array(unsafe {
462 RunEnd::new_unchecked(
463 array.ends().clone(),
464 values_validity,
465 array.offset(),
466 array.len(),
467 )
468 .into_array()
469 }),
470 })
471 }
472}
473
474pub(super) fn run_end_canonicalize(
475 array: &RunEndArray,
476 ctx: &mut ExecutionCtx,
477) -> VortexResult<ArrayRef> {
478 let pends = array.ends().clone().execute_as("ends", ctx)?;
479
480 Ok(match array.dtype() {
481 DType::Bool(_) => {
482 let bools = array.values().clone().execute_as("values", ctx)?;
483 runend_decode_bools(pends, bools, array.offset(), array.len(), ctx)?
484 }
485 DType::Primitive(..) => {
486 let pvalues = array.values().clone().execute_as("values", ctx)?;
487 runend_decode_primitive(pends, pvalues, array.offset(), array.len(), ctx)?.into_array()
488 }
489 DType::Utf8(_) | DType::Binary(_) => {
490 let values = array
491 .values()
492 .clone()
493 .execute_as::<VarBinViewArray>("values", ctx)?;
494 runend_decode_varbinview(pends, values, array.offset(), array.len(), ctx)?.into_array()
495 }
496 _ => vortex_bail!("Unsupported RunEnd value type: {}", array.dtype()),
497 })
498}
499
500#[cfg(test)]
501mod tests {
502 use std::sync::LazyLock;
503
504 use vortex_array::IntoArray;
505 use vortex_array::VortexSessionExecute;
506 use vortex_array::arrays::DictArray;
507 use vortex_array::arrays::VarBinViewArray;
508 use vortex_array::assert_arrays_eq;
509 use vortex_array::builders::VarBinBuilder;
510 use vortex_array::dtype::DType;
511 use vortex_array::dtype::Nullability;
512 use vortex_array::dtype::PType;
513 use vortex_buffer::buffer;
514 use vortex_session::VortexSession;
515
516 use crate::RunEnd;
517
518 static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
519 let session = vortex_array::array_session();
520 crate::initialize(&session);
521 session
522 });
523
524 #[test]
525 fn test_runend_constructor() {
526 let mut ctx = SESSION.create_execution_ctx();
527 let arr = RunEnd::new(
528 buffer![2u32, 5, 10].into_array(),
529 buffer![1i32, 2, 3].into_array(),
530 &mut ctx,
531 );
532 assert_eq!(arr.len(), 10);
533 assert_eq!(
534 arr.dtype(),
535 &DType::Primitive(PType::I32, Nullability::NonNullable)
536 );
537
538 let expected = buffer![1, 1, 2, 2, 2, 3, 3, 3, 3, 3].into_array();
542 assert_arrays_eq!(arr.into_array(), expected, &mut ctx);
543 }
544
545 #[test]
546 fn test_runend_utf8() {
547 let mut ctx = SESSION.create_execution_ctx();
548 let values =
549 VarBinViewArray::from_iter_nullable_str([Some("a"), None, Some("c")]).into_array();
550 let arr = RunEnd::new(buffer![2u32, 5, 10].into_array(), values, &mut ctx);
551 assert_eq!(arr.len(), 10);
552 assert_eq!(arr.dtype(), &DType::Utf8(Nullability::Nullable));
553
554 let expected = VarBinViewArray::from_iter_nullable_str([
555 Some("a"),
556 Some("a"),
557 None,
558 None,
559 None,
560 Some("c"),
561 Some("c"),
562 Some("c"),
563 Some("c"),
564 Some("c"),
565 ])
566 .into_array();
567 let mut builder = VarBinBuilder::<i32>::with_capacity(arr.dtype().clone(), arr.len());
568 arr.append_to_builder(&mut builder, &mut ctx).unwrap();
569 assert_arrays_eq!(builder.finish_into_varbin(), expected, &mut ctx);
570 assert_arrays_eq!(arr.into_array(), expected, &mut ctx);
571 }
572
573 #[test]
574 fn test_runend_dict() {
575 let mut ctx = SESSION.create_execution_ctx();
576 let dict_values = VarBinViewArray::from_iter_str(["x", "y", "z"]).into_array();
577 let dict_codes = buffer![0u32, 1, 2].into_array();
578 let dict = DictArray::try_new(dict_codes, dict_values).unwrap();
579
580 let arr = RunEnd::try_new(
581 buffer![2u32, 5, 10].into_array(),
582 dict.into_array(),
583 &mut ctx,
584 )
585 .unwrap();
586 assert_eq!(arr.len(), 10);
587
588 let expected =
589 VarBinViewArray::from_iter_str(["x", "x", "y", "y", "y", "z", "z", "z", "z", "z"])
590 .into_array();
591 assert_arrays_eq!(arr.into_array(), expected, &mut ctx);
592 }
593}