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::arrays::Primitive;
25use vortex_array::arrays::VarBinViewArray;
26use vortex_array::buffer::BufferHandle;
27use vortex_array::dtype::DType;
28use vortex_array::dtype::Nullability;
29use vortex_array::dtype::PType;
30use vortex_array::legacy_session;
31use vortex_array::serde::ArrayChildren;
32use vortex_array::smallvec::smallvec;
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 ends = slots[ENDS_SLOT]
97 .as_ref()
98 .vortex_expect("RunEndArray ends slot");
99 let values = slots[VALUES_SLOT]
100 .as_ref()
101 .vortex_expect("RunEndArray values slot");
102 let mut ctx = legacy_session().create_execution_ctx();
104 RunEndData::validate_parts(ends, values, data.offset, len, &mut ctx)?;
105 vortex_ensure!(
106 values.dtype() == dtype,
107 "expected dtype {}, got {}",
108 dtype,
109 values.dtype()
110 );
111 Ok(())
112 }
113
114 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
115 0
116 }
117
118 fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
119 vortex_panic!("RunEndArray buffer index {idx} out of bounds")
120 }
121
122 fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
123 vortex_panic!("RunEndArray buffer_name index {idx} out of bounds")
124 }
125
126 fn with_buffers(
127 &self,
128 array: ArrayView<'_, Self>,
129 buffers: &[BufferHandle],
130 ) -> VortexResult<ArrayParts<Self>> {
131 vortex_array::vtable::with_empty_buffers(self, array, buffers)
132 }
133
134 fn serialize(
135 array: ArrayView<'_, Self>,
136 _session: &VortexSession,
137 ) -> VortexResult<Option<Vec<u8>>> {
138 Ok(Some(
139 RunEndMetadata {
140 ends_ptype: PType::try_from(array.ends().dtype())
141 .vortex_expect("Must be a valid PType") as i32,
142 num_runs: array.ends().len() as u64,
143 offset: array.offset() as u64,
144 }
145 .encode_to_vec(),
146 ))
147 }
148
149 fn deserialize(
150 &self,
151 dtype: &DType,
152 len: usize,
153 metadata: &[u8],
154 _buffers: &[BufferHandle],
155 children: &dyn ArrayChildren,
156 _session: &VortexSession,
157 ) -> VortexResult<ArrayParts<Self>> {
158 let metadata = RunEndMetadata::decode(metadata)?;
159 let ends_dtype = DType::Primitive(metadata.ends_ptype(), Nullability::NonNullable);
160 let runs = usize::try_from(metadata.num_runs).vortex_expect("Must be a valid usize");
161 let ends = children.get(0, &ends_dtype, runs)?;
162
163 let values = children.get(1, dtype, runs)?;
164 let offset = usize::try_from(metadata.offset).vortex_expect("Offset must be a valid usize");
165 let slots = smallvec![Some(ends), Some(values)];
166 let data = RunEndData::new(offset);
167 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
168 }
169
170 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
171 SLOT_NAMES[idx].to_string()
172 }
173
174 fn reduce_parent(
175 array: ArrayView<'_, Self>,
176 parent: &ArrayRef,
177 child_idx: usize,
178 ) -> VortexResult<Option<ArrayRef>> {
179 RULES.evaluate(array, parent, child_idx)
180 }
181
182 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
183 run_end_canonicalize(&array, ctx).map(ExecutionResult::done)
184 }
185}
186
187pub(super) const ENDS_SLOT: usize = 0;
189pub(super) const VALUES_SLOT: usize = 1;
191pub(super) const NUM_SLOTS: usize = 2;
192pub(super) const SLOT_NAMES: [&str; NUM_SLOTS] = ["ends", "values"];
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: TypedArrayRef<RunEnd> {
212 fn offset(&self) -> usize {
213 self.offset
214 }
215
216 fn ends(&self) -> &ArrayRef {
217 self.as_ref().slots()[ENDS_SLOT]
218 .as_ref()
219 .vortex_expect("RunEndArray ends slot")
220 }
221
222 fn values(&self) -> &ArrayRef {
223 self.as_ref().slots()[VALUES_SLOT]
224 .as_ref()
225 .vortex_expect("RunEndArray values slot")
226 }
227
228 fn dtype(&self) -> &DType {
229 self.values().dtype()
230 }
231
232 fn find_physical_index(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
233 find_physical_index(self.ends(), index + self.offset(), ctx)
234 }
235
236 fn find_slice_end_index(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
237 find_slice_end_index(self.ends(), index + self.offset(), ctx)
238 }
239}
240
241impl<T: TypedArrayRef<RunEnd>> RunEndArrayExt for T {}
242
243#[derive(Clone, Debug)]
244pub struct RunEnd;
245
246impl RunEnd {
247 pub unsafe fn new_unchecked(
252 ends: ArrayRef,
253 values: ArrayRef,
254 offset: usize,
255 length: usize,
256 ) -> RunEndArray {
257 let dtype = values.dtype().clone();
258 let slots = smallvec![Some(ends), Some(values)];
259 let data = unsafe { RunEndData::new_unchecked(offset) };
260 unsafe {
261 Array::from_parts_unchecked(
262 ArrayParts::new(RunEnd, dtype, length, data).with_slots(slots),
263 )
264 }
265 }
266
267 pub fn try_new(
269 ends: ArrayRef,
270 values: ArrayRef,
271 ctx: &mut ExecutionCtx,
272 ) -> VortexResult<RunEndArray> {
273 let len = RunEndData::logical_len_from_ends(&ends, ctx)?;
274 RunEndData::validate_parts(&ends, &values, 0, len, ctx)?;
275 let dtype = values.dtype().clone();
276 let slots = smallvec![Some(ends), Some(values)];
277 let data = RunEndData::new(0);
278 Array::try_from_parts(ArrayParts::new(RunEnd, dtype, len, data).with_slots(slots))
279 }
280
281 pub fn try_new_offset_length(
283 ends: ArrayRef,
284 values: ArrayRef,
285 offset: usize,
286 length: usize,
287 ctx: &mut ExecutionCtx,
288 ) -> VortexResult<RunEndArray> {
289 RunEndData::validate_parts(&ends, &values, offset, length, ctx)?;
290 let dtype = values.dtype().clone();
291 let slots = smallvec![Some(ends), Some(values)];
292 let data = RunEndData::new(offset);
293 Array::try_from_parts(ArrayParts::new(RunEnd, dtype, length, data).with_slots(slots))
294 }
295
296 pub fn new(ends: ArrayRef, values: ArrayRef, ctx: &mut ExecutionCtx) -> RunEndArray {
298 Self::try_new(ends, values, ctx).vortex_expect("RunEndData is always valid")
299 }
300
301 pub fn encode(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<RunEndArray> {
303 if let Some(parray) = array.as_opt::<Primitive>() {
304 let (ends, values) = runend_encode(parray, ctx);
305 let ends = ends.into_array();
306 let len = array.len();
307 let dtype = values.dtype().clone();
308 let slots = smallvec![Some(ends), Some(values)];
309 let data = unsafe { RunEndData::new_unchecked(0) };
310 Array::try_from_parts(ArrayParts::new(RunEnd, dtype, len, data).with_slots(slots))
311 } else {
312 vortex_bail!("REE can only encode primitive arrays")
313 }
314 }
315}
316
317impl RunEndData {
318 fn logical_len_from_ends(ends: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
319 if ends.is_empty() {
320 Ok(0)
321 } else {
322 usize::try_from(&ends.execute_scalar(ends.len() - 1, ctx)?)
323 }
324 }
325
326 pub(crate) fn validate_parts(
327 ends: &ArrayRef,
328 values: &ArrayRef,
329 offset: usize,
330 length: usize,
331 ctx: &mut ExecutionCtx,
332 ) -> VortexResult<()> {
333 vortex_ensure!(
335 ends.dtype().is_unsigned_int(),
336 "run ends must be unsigned integers, was {}",
337 ends.dtype(),
338 );
339 vortex_ensure!(
340 ends.len() == values.len(),
341 "run ends len != run values len, {} != {}",
342 ends.len(),
343 values.len()
344 );
345
346 if ends.is_empty() {
348 vortex_ensure!(
349 offset == 0,
350 "non-zero offset provided for empty RunEndArray"
351 );
352 return Ok(());
353 }
354
355 if length == 0 {
357 return Ok(());
358 }
359
360 #[cfg(debug_assertions)]
361 {
362 let pre_validation = ends.statistics().to_owned();
364
365 let is_sorted = ends
366 .statistics()
367 .compute_is_strict_sorted(ctx)
368 .unwrap_or(false);
369
370 ends.statistics().inherit(pre_validation.iter());
373 debug_assert!(is_sorted);
374 }
375
376 if !ends.is_host() {
378 return Ok(());
379 }
380
381 if offset != 0 && length != 0 {
383 let first_run_end = usize::try_from(&ends.execute_scalar(0, ctx)?)?;
384 if first_run_end < offset {
385 vortex_bail!("First run end {first_run_end} must be >= offset {offset}");
386 }
387 }
388
389 let last_run_end = usize::try_from(&ends.execute_scalar(ends.len() - 1, ctx)?)?;
390 let min_required_end = offset + length;
391 if last_run_end < min_required_end {
392 vortex_bail!("Last run end {last_run_end} must be >= offset+length {min_required_end}");
393 }
394
395 Ok(())
396 }
397}
398
399impl RunEndData {
400 pub fn new(offset: usize) -> Self {
430 Self { offset }
431 }
432
433 pub unsafe fn new_unchecked(offset: usize) -> Self {
443 Self { offset }
444 }
445
446 pub fn encode(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
448 if let Some(parray) = array.as_opt::<Primitive>() {
449 let (_ends, _values) = runend_encode(parray, ctx);
450 unsafe { Ok(Self::new_unchecked(0)) }
452 } else {
453 vortex_bail!("REE can only encode primitive arrays")
454 }
455 }
456
457 pub fn into_parts(self, ends: ArrayRef, values: ArrayRef) -> RunEndDataParts {
458 RunEndDataParts {
459 ends,
460 values,
461 offset: self.offset,
462 }
463 }
464}
465
466impl ValidityVTable<RunEnd> for RunEnd {
467 fn validity(array: ArrayView<'_, RunEnd>) -> VortexResult<Validity> {
468 Ok(match array.values().validity()? {
469 Validity::NonNullable | Validity::AllValid => Validity::AllValid,
470 Validity::AllInvalid => Validity::AllInvalid,
471 Validity::Array(values_validity) => Validity::Array(unsafe {
472 RunEnd::new_unchecked(
473 array.ends().clone(),
474 values_validity,
475 array.offset(),
476 array.len(),
477 )
478 .into_array()
479 }),
480 })
481 }
482}
483
484pub(super) fn run_end_canonicalize(
485 array: &RunEndArray,
486 ctx: &mut ExecutionCtx,
487) -> VortexResult<ArrayRef> {
488 let pends = array.ends().clone().execute_as("ends", ctx)?;
489
490 Ok(match array.dtype() {
491 DType::Bool(_) => {
492 let bools = array.values().clone().execute_as("values", ctx)?;
493 runend_decode_bools(pends, bools, array.offset(), array.len(), ctx)?
494 }
495 DType::Primitive(..) => {
496 let pvalues = array.values().clone().execute_as("values", ctx)?;
497 runend_decode_primitive(pends, pvalues, array.offset(), array.len(), ctx)?.into_array()
498 }
499 DType::Utf8(_) | DType::Binary(_) => {
500 let values = array
501 .values()
502 .clone()
503 .execute_as::<VarBinViewArray>("values", ctx)?;
504 runend_decode_varbinview(pends, values, array.offset(), array.len(), ctx)?.into_array()
505 }
506 _ => vortex_bail!("Unsupported RunEnd value type: {}", array.dtype()),
507 })
508}
509
510#[cfg(test)]
511mod tests {
512 use std::sync::LazyLock;
513
514 use vortex_array::IntoArray;
515 use vortex_array::VortexSessionExecute;
516 use vortex_array::arrays::DictArray;
517 use vortex_array::arrays::VarBinViewArray;
518 use vortex_array::assert_arrays_eq;
519 use vortex_array::dtype::DType;
520 use vortex_array::dtype::Nullability;
521 use vortex_array::dtype::PType;
522 use vortex_buffer::buffer;
523 use vortex_session::VortexSession;
524
525 use crate::RunEnd;
526
527 static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
528 let session = vortex_array::array_session();
529 crate::initialize(&session);
530 session
531 });
532
533 #[test]
534 fn test_runend_constructor() {
535 let mut ctx = SESSION.create_execution_ctx();
536 let arr = RunEnd::new(
537 buffer![2u32, 5, 10].into_array(),
538 buffer![1i32, 2, 3].into_array(),
539 &mut ctx,
540 );
541 assert_eq!(arr.len(), 10);
542 assert_eq!(
543 arr.dtype(),
544 &DType::Primitive(PType::I32, Nullability::NonNullable)
545 );
546
547 let expected = buffer![1, 1, 2, 2, 2, 3, 3, 3, 3, 3].into_array();
551 assert_arrays_eq!(arr.into_array(), expected, &mut ctx);
552 }
553
554 #[test]
555 fn test_runend_utf8() {
556 let mut ctx = SESSION.create_execution_ctx();
557 let values = VarBinViewArray::from_iter_str(["a", "b", "c"]).into_array();
558 let arr = RunEnd::new(buffer![2u32, 5, 10].into_array(), values, &mut ctx);
559 assert_eq!(arr.len(), 10);
560 assert_eq!(arr.dtype(), &DType::Utf8(Nullability::NonNullable));
561
562 let expected =
563 VarBinViewArray::from_iter_str(["a", "a", "b", "b", "b", "c", "c", "c", "c", "c"])
564 .into_array();
565 assert_arrays_eq!(arr.into_array(), expected, &mut ctx);
566 }
567
568 #[test]
569 fn test_runend_dict() {
570 let mut ctx = SESSION.create_execution_ctx();
571 let dict_values = VarBinViewArray::from_iter_str(["x", "y", "z"]).into_array();
572 let dict_codes = buffer![0u32, 1, 2].into_array();
573 let dict = DictArray::try_new(dict_codes, dict_values).unwrap();
574
575 let arr = RunEnd::try_new(
576 buffer![2u32, 5, 10].into_array(),
577 dict.into_array(),
578 &mut ctx,
579 )
580 .unwrap();
581 assert_eq!(arr.len(), 10);
582
583 let expected =
584 VarBinViewArray::from_iter_str(["x", "x", "y", "y", "y", "z", "z", "z", "z", "z"])
585 .into_array();
586 assert_arrays_eq!(arr.into_array(), expected, &mut ctx);
587 }
588}