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