1use std::fmt::Display;
5use std::fmt::Formatter;
6use std::ops::Range;
7
8use vortex_buffer::Buffer;
9use vortex_buffer::BufferMut;
10use vortex_error::VortexResult;
11use vortex_error::vortex_ensure;
12use vortex_error::vortex_ensure_eq;
13use vortex_error::vortex_err;
14
15use crate::ArrayRef;
16use crate::ArraySlots;
17use crate::Canonical;
18use crate::ExecutionCtx;
19use crate::IntoArray;
20use crate::array::Array;
21use crate::array::ArrayParts;
22use crate::array::TypedArrayRef;
23use crate::array_slots;
24use crate::arrays::Patched;
25use crate::arrays::PrimitiveArray;
26use crate::arrays::patched::TransposedPatches;
27use crate::arrays::patched::patch_lanes;
28use crate::buffer::BufferHandle;
29use crate::dtype::DType;
30use crate::dtype::IntegerPType;
31use crate::dtype::NativePType;
32use crate::dtype::PType;
33use crate::match_each_native_ptype;
34use crate::match_each_unsigned_integer_ptype;
35use crate::patches::Patches;
36use crate::validity::Validity;
37
38#[derive(Debug, Clone)]
39pub struct PatchedData {
40 pub(super) n_lanes: usize,
44
45 pub(super) offset: usize,
51}
52
53#[array_slots(Patched)]
54pub struct PatchedSlots {
55 #[slot(0)]
57 pub inner: ArrayRef,
58 #[slot(1)]
60 pub lane_offsets: ArrayRef,
61 #[slot(2)]
63 pub patch_indices: ArrayRef,
64 #[slot(3)]
66 pub patch_values: ArrayRef,
67}
68
69impl Display for PatchedData {
70 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
71 write!(f, "n_lanes: {}, offset: {}", self.n_lanes, self.offset)
72 }
73}
74
75impl PatchedData {
76 pub(crate) fn validate(
77 &self,
78 dtype: &DType,
79 len: usize,
80 slots: &PatchedSlotsView,
81 ) -> VortexResult<()> {
82 vortex_ensure_eq!(
83 slots.inner.dtype(),
84 dtype,
85 "PatchedArray base dtype does not match outer dtype",
86 );
87 vortex_ensure_eq!(
88 slots.inner.len(),
89 len,
90 "PatchedArray base len does not match outer len",
91 );
92 vortex_ensure_eq!(
93 slots.patch_indices.len(),
94 slots.patch_values.len(),
95 "PatchedArray patch indices len does not match patch values len",
96 );
97 Ok(())
98 }
99}
100
101pub trait PatchedArrayExt: PatchedArraySlotsExt {
102 #[inline]
103 fn n_lanes(&self) -> usize {
104 self.n_lanes
105 }
106
107 #[inline]
108 fn offset(&self) -> usize {
109 self.offset
110 }
111
112 #[inline]
113 fn lane_range(
114 &self,
115 chunk: usize,
116 lane: usize,
117 ctx: &mut ExecutionCtx,
118 ) -> VortexResult<Range<usize>> {
119 assert!(chunk * 1024 <= self.len() + self.offset());
120 assert!(lane < self.n_lanes());
121
122 let start = self
123 .lane_offsets()
124 .execute_scalar(chunk * self.n_lanes() + lane, ctx)?;
125 let stop = self
126 .lane_offsets()
127 .execute_scalar(chunk * self.n_lanes() + lane + 1, ctx)?;
128
129 let start = start
130 .as_primitive()
131 .as_::<usize>()
132 .ok_or_else(|| vortex_err!("could not cast lane_offset to usize"))?;
133
134 let stop = stop
135 .as_primitive()
136 .as_::<usize>()
137 .ok_or_else(|| vortex_err!("could not cast lane_offset to usize"))?;
138
139 Ok(start..stop)
140 }
141
142 fn slice_chunks(&self, chunks: Range<usize>) -> VortexResult<Array<Patched>> {
143 let lane_offsets_start = chunks.start * self.n_lanes();
144 let lane_offsets_stop = chunks.end * self.n_lanes() + 1;
145
146 let sliced_lane_offsets = self
147 .lane_offsets()
148 .slice(lane_offsets_start..lane_offsets_stop)?;
149 let indices = self.patch_indices().clone();
150 let values = self.patch_values().clone();
151
152 let begin = (chunks.start * 1024).saturating_sub(self.offset());
153 let end = (chunks.end * 1024)
154 .saturating_sub(self.offset())
155 .min(self.len());
156
157 let offset = if chunks.start == 0 { self.offset() } else { 0 };
158 let inner = self.inner().slice(begin..end)?;
159 let len = inner.len();
160 let dtype = self.dtype().clone();
161 let slots = PatchedSlots {
162 inner,
163 lane_offsets: sliced_lane_offsets,
164 patch_indices: indices,
165 patch_values: values,
166 }
167 .into_slots();
168
169 Ok(unsafe { Patched::new_unchecked(dtype, len, slots, self.n_lanes(), offset) })
170 }
171}
172
173impl<T: TypedArrayRef<Patched>> PatchedArrayExt for T {}
174
175impl Patched {
176 pub fn from_array_and_patches(
177 inner: ArrayRef,
178 patches: &Patches,
179 ctx: &mut ExecutionCtx,
180 ) -> VortexResult<Array<Patched>> {
181 vortex_ensure!(
182 inner.dtype().eq_with_nullability_superset(patches.dtype()),
183 "array DType must match patches DType"
184 );
185
186 vortex_ensure!(
187 inner.dtype().is_primitive(),
188 "Creating PatchedArray from Patches only supported for primitive arrays"
189 );
190
191 vortex_ensure!(
192 patches.num_patches() <= u32::MAX as usize,
193 "PatchedArray does not support > u32::MAX patch values"
194 );
195
196 vortex_ensure!(
197 patches.values().all_valid(ctx)?,
198 "PatchedArray cannot be built from Patches with nulls"
199 );
200
201 let values_ptype = patches.dtype().as_ptype();
202
203 let TransposedPatches {
204 n_lanes,
205 lane_offsets,
206 indices,
207 values,
208 } = transpose_patches(patches, ctx)?;
209
210 let lane_offsets = PrimitiveArray::from_buffer_handle(
211 BufferHandle::new_host(lane_offsets),
212 PType::U32,
213 Validity::NonNullable,
214 )
215 .into_array();
216 let indices = PrimitiveArray::from_buffer_handle(
217 BufferHandle::new_host(indices),
218 PType::U16,
219 Validity::NonNullable,
220 )
221 .into_array();
222 let values = PrimitiveArray::from_buffer_handle(
223 BufferHandle::new_host(values),
224 values_ptype,
225 Validity::NonNullable,
226 )
227 .into_array();
228
229 let dtype = inner.dtype().clone();
230 let len = inner.len();
231 let slots = PatchedSlots {
232 inner,
233 lane_offsets,
234 patch_indices: indices,
235 patch_values: values,
236 }
237 .into_slots();
238 Ok(unsafe { Self::new_unchecked(dtype, len, slots, n_lanes, 0) })
239 }
240
241 pub(crate) unsafe fn new_unchecked(
242 dtype: DType,
243 len: usize,
244 slots: ArraySlots,
245 n_lanes: usize,
246 offset: usize,
247 ) -> Array<Patched> {
248 unsafe {
249 Array::from_parts_unchecked(ArrayParts::new(
250 Patched,
251 dtype,
252 len,
253 PatchedData { n_lanes, offset },
254 slots,
255 ))
256 }
257 }
258}
259
260fn transpose_patches(patches: &Patches, ctx: &mut ExecutionCtx) -> VortexResult<TransposedPatches> {
262 let array_len = patches.array_len();
263 let offset = patches.offset();
264
265 let indices = patches
266 .indices()
267 .clone()
268 .execute::<Canonical>(ctx)?
269 .into_primitive();
270
271 let values = patches
272 .values()
273 .clone()
274 .execute::<Canonical>(ctx)?
275 .into_primitive();
276
277 let indices_ptype = indices.ptype();
278 let values_ptype = values.ptype();
279
280 let indices = indices.buffer_handle().clone().unwrap_host();
281 let values = values.buffer_handle().clone().unwrap_host();
282
283 match_each_unsigned_integer_ptype!(indices_ptype, |I| {
284 match_each_native_ptype!(values_ptype, |V| {
285 let indices: Buffer<I> = Buffer::from_byte_buffer(indices);
286 let values: Buffer<V> = Buffer::from_byte_buffer(values);
287
288 Ok(transpose(
289 indices.as_slice(),
290 values.as_slice(),
291 offset,
292 array_len,
293 ))
294 })
295 })
296}
297
298#[expect(clippy::cast_possible_truncation)]
299fn transpose<I: IntegerPType, V: NativePType>(
300 indices_in: &[I],
301 values_in: &[V],
302 offset: usize,
303 array_len: usize,
304) -> TransposedPatches {
305 let n_chunks = array_len.div_ceil(1024);
307 assert!(
308 n_chunks <= u32::MAX as usize,
309 "Cannot transpose patches for array with >= 4 trillion elements"
310 );
311
312 let n_lanes = patch_lanes::<V>();
313
314 let mut indices_buffer = BufferMut::with_capacity(indices_in.len());
316 let mut values_buffer = BufferMut::with_capacity(values_in.len());
317
318 let mut lane_offsets: BufferMut<u32> = BufferMut::zeroed(n_chunks * n_lanes + 1);
320
321 for index in indices_in {
323 let index = index.as_() - offset;
324 let chunk = index / 1024;
325 let lane = index % n_lanes;
326
327 lane_offsets[chunk * n_lanes + lane + 1] += 1;
328 }
329
330 for index in 1..lane_offsets.len() {
331 lane_offsets[index] += lane_offsets[index - 1];
332 }
333
334 let indices_out = indices_buffer.spare_capacity_mut();
336 let values_out = values_buffer.spare_capacity_mut();
337 for (index, &value) in std::iter::zip(indices_in, values_in) {
338 let index = index.as_() - offset;
339 let chunk = index / 1024;
340 let lane = index % n_lanes;
341
342 let position = &mut lane_offsets[chunk * n_lanes + lane];
343 indices_out[*position as usize].write((index % 1024) as u16);
344 values_out[*position as usize].write(value);
345 *position += 1;
346 }
347
348 unsafe {
349 indices_buffer.set_len(indices_in.len());
350 values_buffer.set_len(values_in.len());
351 }
352
353 for index in indices_in {
354 let index = index.as_() - offset;
355 let chunk = index / 1024;
356 let lane = index % n_lanes;
357
358 lane_offsets[chunk * n_lanes + lane] -= 1;
359 }
360
361 TransposedPatches {
362 n_lanes,
363 lane_offsets: lane_offsets.freeze().into_byte_buffer(),
364 indices: indices_buffer.freeze().into_byte_buffer(),
365 values: values_buffer.freeze().into_byte_buffer(),
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use vortex_buffer::Buffer;
372 use vortex_buffer::buffer;
373
374 use super::PatchedSlots;
375 use crate::ArrayRef;
376 use crate::IntoArray;
377 use crate::array_slots;
378 use crate::arrays::Chunked;
379 use crate::arrays::Null;
380 use crate::arrays::PrimitiveArray;
381 use crate::arrays::Union;
382 use crate::validity::Validity;
383
384 #[array_slots(Null)]
385 struct OptionalPatchedSlots {
386 #[slot(0)]
387 required: ArrayRef,
388 #[slot(1)]
389 maybe: Option<ArrayRef>,
390 }
391
392 #[array_slots(Chunked)]
393 struct VariadicSlots {
394 #[slot(0)]
395 offsets: ArrayRef,
396 #[slot(1)]
397 maybe_validity: Option<ArrayRef>,
398 #[slot(2..)]
399 chunks: Vec<ArrayRef>,
400 }
401
402 #[array_slots(Union)]
405 struct ShuffledVariadicSlots {
406 #[slot(2..)]
407 chunks: Vec<ArrayRef>,
408 #[slot(1)]
409 maybe_validity: Option<ArrayRef>,
410 #[slot(0)]
411 offsets: ArrayRef,
412 }
413
414 #[test]
415 fn generated_slots_round_trip() {
416 let required = PrimitiveArray::new(buffer![1u8, 2, 3], Validity::NonNullable).into_array();
417 let optional = PrimitiveArray::new(buffer![4u8, 5, 6], Validity::NonNullable).into_array();
418
419 let slot_vec = vec![Some(required.clone()), Some(optional.clone())];
420 let view = OptionalPatchedSlotsView::from_slots(&slot_vec);
421 assert!(ArrayRef::ptr_eq(view.required, &required));
422 assert!(ArrayRef::ptr_eq(
423 view.maybe.expect("optional slot"),
424 &optional
425 ));
426 assert_eq!(view.required.len(), 3);
427 assert_eq!(view.maybe.expect("optional slot").len(), 3);
428
429 let cloned = OptionalPatchedSlots::from_slots(slot_vec.into());
430 assert!(ArrayRef::ptr_eq(&cloned.required, &required));
431 assert!(ArrayRef::ptr_eq(
432 cloned.maybe.as_ref().expect("optional clone"),
433 &optional
434 ));
435 assert_eq!(cloned.required.len(), required.len());
436 assert_eq!(cloned.maybe.expect("optional clone").len(), optional.len());
437
438 let rebuilt = PatchedSlots::from_slots(
439 vec![
440 Some(required.clone()),
441 Some(optional.clone()),
442 Some(required.clone()),
443 Some(optional.clone()),
444 ]
445 .into(),
446 );
447 assert!(ArrayRef::ptr_eq(&rebuilt.inner, &required));
448 assert!(ArrayRef::ptr_eq(&rebuilt.lane_offsets, &optional));
449 assert!(ArrayRef::ptr_eq(&rebuilt.patch_indices, &required));
450 assert!(ArrayRef::ptr_eq(&rebuilt.patch_values, &optional));
451 assert_eq!(rebuilt.inner.len(), required.len());
452 assert_eq!(rebuilt.patch_values.len(), optional.len());
453 }
454
455 #[test]
456 #[allow(clippy::cognitive_complexity)]
457 fn variadic_slots_round_trip() {
458 let offsets = PrimitiveArray::new(buffer![0u64, 3, 5], Validity::NonNullable).into_array();
459 let chunk0 = PrimitiveArray::new(buffer![1u8, 2, 3], Validity::NonNullable).into_array();
460 let chunk1 = PrimitiveArray::new(buffer![4u8, 5], Validity::NonNullable).into_array();
461
462 assert_eq!(VariadicSlots::OFFSETS, 0);
463 assert_eq!(VariadicSlots::MAYBE_VALIDITY, 1);
464 assert_eq!(VariadicSlots::CHUNKS_OFFSET, 2);
465 assert_eq!(VariadicSlots::FIXED_COUNT, 2);
466 assert_eq!(VariadicSlots::slot_name(0), "offsets");
467 assert_eq!(VariadicSlots::slot_name(3), "chunks[1]");
468
469 let slot_vec = vec![
470 Some(offsets.clone()),
471 None,
472 Some(chunk0.clone()),
473 Some(chunk1.clone()),
474 ];
475
476 let view = VariadicSlotsView::from_slots(&slot_vec);
477 assert!(ArrayRef::ptr_eq(view.offsets, &offsets));
478 assert!(ArrayRef::ptr_eq(&view.chunks[0], &chunk0));
479 assert!(ArrayRef::ptr_eq(
480 view.chunks.get(1).expect("second chunk"),
481 &chunk1
482 ));
483 assert_eq!(view.offsets.len(), 3);
484 assert!(view.maybe_validity.is_none());
485 assert_eq!(view.chunks.len(), 2);
486 assert_eq!(view.chunks[0].len(), 3);
487 assert_eq!(view.chunks.get(1).map(|c| c.len()), Some(2));
488 assert!(view.chunks.get(2).is_none());
489 assert_eq!(
490 view.chunks.iter().map(|c| c.len()).collect::<Vec<_>>(),
491 vec![3, 2]
492 );
493
494 let owned = view.to_owned();
495 assert_eq!(owned.chunks.len(), 2);
496 assert!(ArrayRef::ptr_eq(&owned.offsets, &offsets));
497 assert!(owned.maybe_validity.is_none());
498 assert!(ArrayRef::ptr_eq(&owned.chunks[0], &chunk0));
499 assert!(ArrayRef::ptr_eq(&owned.chunks[1], &chunk1));
500
501 let owned = VariadicSlots::from_slots(slot_vec.into());
502 assert_eq!(owned.offsets.len(), offsets.len());
503 assert!(owned.maybe_validity.is_none());
504 assert_eq!(owned.chunks.len(), 2);
505
506 assert!(ArrayRef::ptr_eq(&owned.offsets, &offsets));
507 assert!(ArrayRef::ptr_eq(&owned.chunks[0], &chunk0));
508 assert!(ArrayRef::ptr_eq(&owned.chunks[1], &chunk1));
509 let slots = owned.into_slots();
510 assert_eq!(slots.len(), 4);
511 for (index, expected) in [(0, &offsets), (2, &chunk0), (3, &chunk1)] {
512 assert!(ArrayRef::ptr_eq(
513 slots[index].as_ref().expect("present slot"),
514 expected
515 ));
516 }
517 assert!(slots[1].is_none());
518 assert_eq!(
519 slots[VariadicSlots::CHUNKS_OFFSET]
520 .as_ref()
521 .map(|c| c.len()),
522 Some(3)
523 );
524 }
525
526 #[test]
527 fn variadic_slots_empty_tail() {
528 let offsets = PrimitiveArray::new(buffer![0u64], Validity::NonNullable).into_array();
529 let slot_vec = vec![Some(offsets), None];
530
531 let view = VariadicSlotsView::from_slots(&slot_vec);
532 assert!(view.chunks.is_empty());
533
534 let owned = VariadicSlots::from_slots(slot_vec.into());
535 assert!(owned.chunks.is_empty());
536 assert_eq!(owned.into_slots().len(), 2);
537 }
538
539 #[test]
540 fn variadic_slots_spilled_tail_keeps_order() {
541 let offsets = PrimitiveArray::new(buffer![0u64], Validity::NonNullable).into_array();
542 let chunks: Vec<ArrayRef> = (0..5u8)
543 .map(|len| {
544 PrimitiveArray::new((0..len).collect::<Buffer<u8>>(), Validity::NonNullable)
545 .into_array()
546 })
547 .collect();
548
549 let slots = VariadicSlots {
550 offsets: offsets.clone(),
551 maybe_validity: None,
552 chunks,
553 }
554 .into_slots();
555
556 assert!(slots.spilled());
557 assert_eq!(slots.len(), VariadicSlots::FIXED_COUNT + 5);
558 assert_eq!(
559 slots[VariadicSlots::OFFSETS].as_ref().map(|s| s.len()),
560 Some(offsets.len())
561 );
562 assert!(slots[VariadicSlots::MAYBE_VALIDITY].is_none());
563 assert_eq!(
564 slots[VariadicSlots::CHUNKS_OFFSET..]
565 .iter()
566 .map(|s| s.as_ref().map(|c| c.len()))
567 .collect::<Vec<_>>(),
568 (0..5).map(Some).collect::<Vec<_>>()
569 );
570 }
571
572 #[test]
573 fn slot_indices_follow_annotations_not_declaration_order() {
574 assert_eq!(
575 ShuffledVariadicSlots::OFFSETS,
576 VariadicSlots::OFFSETS,
577 "field declaration order must not move a slot"
578 );
579 assert_eq!(
580 ShuffledVariadicSlots::MAYBE_VALIDITY,
581 VariadicSlots::MAYBE_VALIDITY
582 );
583 assert_eq!(
584 ShuffledVariadicSlots::CHUNKS_OFFSET,
585 VariadicSlots::CHUNKS_OFFSET
586 );
587 assert_eq!(
588 ShuffledVariadicSlots::FIXED_COUNT,
589 VariadicSlots::FIXED_COUNT
590 );
591 assert_eq!(ShuffledVariadicSlots::slot_name(0), "offsets");
592 assert_eq!(ShuffledVariadicSlots::slot_name(1), "maybe_validity");
593 assert_eq!(ShuffledVariadicSlots::slot_name(3), "chunks[1]");
594 }
595
596 #[test]
597 fn shuffled_declaration_order_round_trips_through_storage() {
598 let offsets = PrimitiveArray::new(buffer![0u64, 3], Validity::NonNullable).into_array();
599 let validity = PrimitiveArray::new(buffer![1u8, 1], Validity::NonNullable).into_array();
600 let chunk = PrimitiveArray::new(buffer![1u8, 2, 3], Validity::NonNullable).into_array();
601
602 let slot_vec = vec![
603 Some(offsets.clone()),
604 Some(validity.clone()),
605 Some(chunk.clone()),
606 ];
607
608 let view = ShuffledVariadicSlotsView::from_slots(&slot_vec);
609 assert!(ArrayRef::ptr_eq(view.offsets, &offsets));
610 assert!(ArrayRef::ptr_eq(
611 view.maybe_validity.expect("validity"),
612 &validity
613 ));
614 assert!(ArrayRef::ptr_eq(&view.chunks[0], &chunk));
615 assert_eq!(view.offsets.len(), offsets.len());
616 assert_eq!(view.maybe_validity.map(|v| v.len()), Some(validity.len()));
617 assert_eq!(view.chunks.len(), 1);
618 assert_eq!(view.chunks[0].len(), chunk.len());
619
620 let round_tripped = ShuffledVariadicSlots::from_slots(slot_vec.into()).into_slots();
622 assert_eq!(round_tripped.len(), 3);
623 for (index, expected) in [&offsets, &validity, &chunk].into_iter().enumerate() {
624 assert!(ArrayRef::ptr_eq(
625 round_tripped[index].as_ref().expect("present slot"),
626 expected
627 ));
628 }
629 assert_eq!(
630 round_tripped[ShuffledVariadicSlots::OFFSETS]
631 .as_ref()
632 .map(|s| s.len()),
633 Some(offsets.len())
634 );
635 assert_eq!(
636 round_tripped[ShuffledVariadicSlots::MAYBE_VALIDITY]
637 .as_ref()
638 .map(|s| s.len()),
639 Some(validity.len())
640 );
641 assert_eq!(
642 round_tripped[ShuffledVariadicSlots::CHUNKS_OFFSET]
643 .as_ref()
644 .map(|s| s.len()),
645 Some(chunk.len())
646 );
647 }
648}