1use std::fmt::Debug;
5use std::hash::Hash;
6use std::ops::Range;
7
8use num_traits::NumCast;
9use vortex_buffer::BitBuffer;
10use vortex_buffer::BufferAllocatorRef;
11use vortex_buffer::BufferMut;
12use vortex_error::VortexError;
13use vortex_error::VortexExpect as _;
14use vortex_error::VortexResult;
15use vortex_error::vortex_bail;
16use vortex_error::vortex_ensure;
17use vortex_error::vortex_ensure_eq;
18use vortex_error::vortex_err;
19use vortex_mask::AllOr;
20use vortex_mask::Mask;
21use vortex_mask::MaskValues;
22use vortex_utils::aliases::hash_map::HashMap;
23
24use crate::ArrayRef;
25use crate::ArraySlots;
26use crate::ExecutionCtx;
27use crate::IntoArray;
28use crate::VortexSessionExecute;
29use crate::arrays::Primitive;
30use crate::arrays::PrimitiveArray;
31use crate::arrays::primitive::PrimitiveArrayExt;
32use crate::builtins::ArrayBuiltins;
33use crate::dtype::DType;
34use crate::dtype::IntegerPType;
35use crate::dtype::NativePType;
36use crate::dtype::Nullability;
37use crate::dtype::Nullability::NonNullable;
38use crate::dtype::PType;
39use crate::dtype::UnsignedPType;
40use crate::legacy_session;
41use crate::match_each_unsigned_integer_ptype;
42use crate::scalar::Scalar;
43use crate::search_sorted::SearchResult;
44use crate::search_sorted::SearchSorted;
45use crate::search_sorted::SearchSortedPrimitiveArray;
46use crate::search_sorted::SearchSortedSide;
47use crate::validity::Validity;
48
49pub const PATCH_CHUNK_SIZE: usize = 1024;
52
53#[derive(Copy, Clone, prost::Message)]
54pub struct PatchesMetadata {
55 #[prost(uint64, tag = "1")]
56 len: u64,
57 #[prost(uint64, tag = "2")]
58 offset: u64,
59 #[prost(enumeration = "PType", tag = "3")]
60 indices_ptype: i32,
61 #[prost(uint64, optional, tag = "4")]
62 chunk_offsets_len: Option<u64>,
63 #[prost(enumeration = "PType", optional, tag = "5")]
64 chunk_offsets_ptype: Option<i32>,
65 #[prost(uint64, optional, tag = "6")]
66 offset_within_chunk: Option<u64>,
67}
68
69impl PatchesMetadata {
70 #[inline]
71 pub fn new(
72 len: usize,
73 offset: usize,
74 indices_ptype: PType,
75 chunk_offsets_len: Option<usize>,
76 chunk_offsets_ptype: Option<PType>,
77 offset_within_chunk: Option<usize>,
78 ) -> Self {
79 Self {
80 len: len as u64,
81 offset: offset as u64,
82 indices_ptype: indices_ptype as i32,
83 chunk_offsets_len: chunk_offsets_len.map(|len| len as u64),
84 chunk_offsets_ptype: chunk_offsets_ptype.map(|pt| pt as i32),
85 offset_within_chunk: offset_within_chunk.map(|len| len as u64),
86 }
87 }
88
89 #[inline]
90 pub fn len(&self) -> VortexResult<usize> {
91 usize::try_from(self.len).map_err(|_| vortex_err!("len does not fit in usize"))
92 }
93
94 #[inline]
95 pub fn is_empty(&self) -> bool {
96 self.len == 0
97 }
98
99 #[inline]
100 pub fn offset(&self) -> VortexResult<usize> {
101 usize::try_from(self.offset).map_err(|_| vortex_err!("offset does not fit in usize"))
102 }
103
104 #[inline]
105 pub fn chunk_offsets_dtype(&self) -> VortexResult<Option<DType>> {
106 self.chunk_offsets_ptype
107 .map(|t| {
108 PType::try_from(t)
109 .map_err(|e| vortex_err!("invalid i32 value {t} for PType: {}", e))
110 .map(|ptype| DType::Primitive(ptype, NonNullable))
111 })
112 .transpose()
113 }
114
115 #[inline]
116 pub fn indices_dtype(&self) -> VortexResult<DType> {
117 let ptype = PType::try_from(self.indices_ptype).map_err(|e| {
118 vortex_err!("invalid i32 value {} for PType: {}", self.indices_ptype, e)
119 })?;
120 vortex_ensure!(
121 ptype.is_unsigned_int(),
122 "Patch indices must be unsigned integers"
123 );
124 Ok(DType::Primitive(ptype, NonNullable))
125 }
126}
127
128#[derive(Clone, Debug, Hash, PartialEq, Eq)]
133pub struct PatchesData {
134 offset: usize,
135 offset_within_chunk: Option<usize>,
136}
137
138#[derive(Copy, Clone, Debug)]
140pub struct PatchSlotIndices {
141 pub indices: usize,
142 pub values: usize,
143 pub chunk_offsets: usize,
144}
145
146impl PatchesData {
147 pub fn from_patches(patches: &Patches) -> Self {
149 Self {
150 offset: patches.offset(),
151 offset_within_chunk: patches.offset_within_chunk(),
152 }
153 }
154
155 pub fn patches_from_slots(
160 patches_data: Option<&Self>,
161 len: usize,
162 slots: &[Option<ArrayRef>],
163 slot_idx: PatchSlotIndices,
164 ) -> Option<Patches> {
165 let data = patches_data?;
166 let indices = slots[slot_idx.indices]
167 .as_ref()
168 .vortex_expect("patches_data is set but patch_indices slot is missing");
169 let values = slots[slot_idx.values]
170 .as_ref()
171 .vortex_expect("patches_data is set but patch_values slot is missing");
172 Some(unsafe {
173 Patches::new_unchecked(
174 len,
175 data.offset,
176 indices.clone(),
177 values.clone(),
178 slots[slot_idx.chunk_offsets].clone(),
179 data.offset_within_chunk,
180 )
181 })
182 }
183
184 pub fn push_slots(slots: &mut ArraySlots, patches: Option<&Patches>) {
188 match patches {
189 Some(p) => {
190 slots.push(Some(p.indices().clone()));
191 slots.push(Some(p.values().clone()));
192 slots.push(p.chunk_offsets().clone());
193 }
194 None => {
195 slots.push(None);
196 slots.push(None);
197 slots.push(None);
198 }
199 }
200 }
201
202 #[inline]
204 pub fn offset(&self) -> usize {
205 self.offset
206 }
207
208 #[inline]
210 pub fn offset_within_chunk(&self) -> Option<usize> {
211 self.offset_within_chunk
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct Patches {
218 array_len: usize,
219 offset: usize,
220 indices: ArrayRef,
221 values: ArrayRef,
222 chunk_offsets: Option<ArrayRef>,
229 offset_within_chunk: Option<usize>,
239}
240
241impl Patches {
242 #[allow(clippy::disallowed_methods)]
243 pub fn new(
244 array_len: usize,
245 offset: usize,
246 indices: ArrayRef,
247 values: ArrayRef,
248 chunk_offsets: Option<ArrayRef>,
249 ) -> VortexResult<Self> {
250 vortex_ensure_eq!(indices.len(), values.len());
251 vortex_ensure!(
252 indices.dtype().is_unsigned_int() && !indices.dtype().is_nullable(),
253 "Patch indices must be non-nullable unsigned integers, got {:?}",
254 indices.dtype()
255 );
256
257 vortex_ensure!(
258 indices.len() <= array_len,
259 "Patch indices must be shorter than the array length"
260 );
261 vortex_ensure!(!indices.is_empty(), "Patch indices must not be empty");
262
263 if indices.is_host() && values.is_host() {
266 let max = usize::try_from(&indices.execute_scalar(
267 indices.len() - 1,
268 &mut legacy_session().create_execution_ctx(),
269 )?)
270 .map_err(|_| vortex_err!("indices must be a number"))?;
271 vortex_ensure!(
272 max - offset < array_len,
273 "Patch indices {max:?}, offset {offset} are longer than the array length {array_len}"
274 );
275
276 #[cfg(debug_assertions)]
277 {
278 use crate::aggregate_fn::fns::is_sorted::is_sorted;
279 let mut ctx = legacy_session().create_execution_ctx();
280 assert!(
281 is_sorted(&indices, &mut ctx).unwrap_or(false),
282 "Patch indices must be sorted"
283 );
284 }
285 }
286
287 Ok(Self {
288 array_len,
289 offset,
290 indices,
291 values,
292 chunk_offsets: chunk_offsets.clone(),
293 offset_within_chunk: chunk_offsets.map(|_| 0),
295 })
296 }
297
298 pub unsafe fn new_unchecked(
308 array_len: usize,
309 offset: usize,
310 indices: ArrayRef,
311 values: ArrayRef,
312 chunk_offsets: Option<ArrayRef>,
313 offset_within_chunk: Option<usize>,
314 ) -> Self {
315 Self {
316 array_len,
317 offset,
318 indices,
319 values,
320 chunk_offsets,
321 offset_within_chunk,
322 }
323 }
324
325 #[inline]
326 pub fn array_len(&self) -> usize {
327 self.array_len
328 }
329
330 #[inline]
331 pub fn num_patches(&self) -> usize {
332 self.indices.len()
333 }
334
335 #[inline]
336 pub fn dtype(&self) -> &DType {
337 self.values.dtype()
338 }
339
340 #[inline]
341 pub fn indices(&self) -> &ArrayRef {
342 &self.indices
343 }
344
345 #[inline]
346 pub fn into_indices(self) -> ArrayRef {
347 self.indices
348 }
349
350 #[inline]
351 pub fn indices_mut(&mut self) -> &mut ArrayRef {
352 &mut self.indices
353 }
354
355 #[inline]
356 pub fn values(&self) -> &ArrayRef {
357 &self.values
358 }
359
360 #[inline]
361 pub fn into_values(self) -> ArrayRef {
362 self.values
363 }
364
365 #[inline]
366 pub fn values_mut(&mut self) -> &mut ArrayRef {
367 &mut self.values
368 }
369
370 #[inline]
371 pub fn offset(&self) -> usize {
373 self.offset
374 }
375
376 #[inline]
377 pub fn chunk_offsets(&self) -> &Option<ArrayRef> {
378 &self.chunk_offsets
379 }
380
381 #[inline]
382 #[allow(clippy::disallowed_methods)]
383 pub fn chunk_offset_at(&self, idx: usize) -> VortexResult<usize> {
384 let Some(chunk_offsets) = &self.chunk_offsets else {
385 vortex_bail!("chunk_offsets must be set to retrieve offset at index")
386 };
387
388 chunk_offsets
389 .execute_scalar(idx, &mut legacy_session().create_execution_ctx())?
390 .as_primitive()
391 .as_::<usize>()
392 .ok_or_else(|| vortex_err!("chunk offset does not fit in usize"))
393 }
394
395 #[inline]
404 pub fn offset_within_chunk(&self) -> Option<usize> {
405 self.offset_within_chunk
406 }
407
408 #[inline]
409 pub fn indices_ptype(&self) -> VortexResult<PType> {
410 PType::try_from(self.indices.dtype())
411 .map_err(|_| vortex_err!("indices dtype is not primitive"))
412 }
413
414 pub fn to_metadata(&self, len: usize, dtype: &DType) -> VortexResult<PatchesMetadata> {
415 if self.indices.len() > len {
416 vortex_bail!(
417 "Patch indices {} are longer than the array length {}",
418 self.indices.len(),
419 len
420 );
421 }
422 if self.values.dtype() != dtype {
423 vortex_bail!(
424 "Patch values dtype {} does not match array dtype {}",
425 self.values.dtype(),
426 dtype
427 );
428 }
429 let chunk_offsets_len = self.chunk_offsets.as_ref().map(|co| co.len());
430 let chunk_offsets_ptype = self.chunk_offsets.as_ref().map(|co| co.dtype().as_ptype());
431
432 Ok(PatchesMetadata::new(
433 self.indices.len(),
434 self.offset,
435 self.indices.dtype().as_ptype(),
436 chunk_offsets_len,
437 chunk_offsets_ptype,
438 self.offset_within_chunk,
439 ))
440 }
441
442 #[allow(clippy::disallowed_methods)]
444 pub fn get_patched(&self, index: usize) -> VortexResult<Option<Scalar>> {
445 self.search_index(index)?
446 .to_found()
447 .map(|patch_idx| {
448 self.values()
449 .execute_scalar(patch_idx, &mut legacy_session().create_execution_ctx())
450 })
451 .transpose()
452 }
453
454 pub fn search_index(&self, index: usize) -> VortexResult<SearchResult> {
472 if self.chunk_offsets.is_some() {
473 return self.search_index_chunked(index);
474 }
475
476 search_index_binary_search(&self.indices, index + self.offset)
477 }
478
479 fn search_index_chunked(&self, index: usize) -> VortexResult<SearchResult> {
489 let Some(chunk_offsets) = &self.chunk_offsets else {
490 vortex_bail!("chunk_offsets is required to be set")
491 };
492
493 let Some(offset_within_chunk) = self.offset_within_chunk else {
494 vortex_bail!("offset_within_chunk is required to be set")
495 };
496
497 if index >= self.array_len() {
498 return Ok(SearchResult::NotFound(self.indices().len()));
499 }
500
501 let chunk_idx = (index + self.offset % PATCH_CHUNK_SIZE) / PATCH_CHUNK_SIZE;
502
503 let base_offset = self.chunk_offset_at(0)?;
505
506 let patches_start_idx = (self.chunk_offset_at(chunk_idx)? - base_offset)
507 .saturating_sub(offset_within_chunk);
514
515 let patches_end_idx = if chunk_idx < chunk_offsets.len() - 1 {
516 (self.chunk_offset_at(chunk_idx + 1)? - base_offset)
517 .saturating_sub(offset_within_chunk)
518 .min(self.indices.len())
519 } else {
520 self.indices.len()
521 };
522
523 let chunk_indices = self.indices.slice(patches_start_idx..patches_end_idx)?;
524 let result = search_index_binary_search(&chunk_indices, index + self.offset)?;
525
526 Ok(match result {
527 SearchResult::Found(idx) => SearchResult::Found(patches_start_idx + idx),
528 SearchResult::NotFound(idx) => SearchResult::NotFound(patches_start_idx + idx),
529 })
530 }
531
532 fn search_index_chunked_batch<T, O>(
538 &self,
539 indices: &[T],
540 chunk_offsets: &[O],
541 index: T,
542 ) -> VortexResult<SearchResult>
543 where
544 T: UnsignedPType,
545 O: UnsignedPType,
546 usize: TryFrom<T>,
547 usize: TryFrom<O>,
548 {
549 let Some(offset_within_chunk) = self.offset_within_chunk else {
550 vortex_bail!("offset_within_chunk is required to be set")
551 };
552
553 let chunk_idx = {
554 let Ok(index) = usize::try_from(index) else {
555 return Ok(SearchResult::NotFound(indices.len()));
557 };
558
559 if index >= self.array_len() {
560 return Ok(SearchResult::NotFound(self.indices().len()));
561 }
562
563 (index + self.offset % PATCH_CHUNK_SIZE) / PATCH_CHUNK_SIZE
564 };
565
566 let chunk_offset = usize::try_from(chunk_offsets[chunk_idx] - chunk_offsets[0])
568 .map_err(|_| vortex_err!("chunk_offset failed to convert to usize"))?;
569
570 let patches_start_idx = chunk_offset
571 .saturating_sub(offset_within_chunk);
578
579 let patches_end_idx = if chunk_idx < chunk_offsets.len() - 1 {
580 usize::try_from(chunk_offsets[chunk_idx + 1] - chunk_offsets[0])
581 .map_err(|_| vortex_err!("patches_end_idx failed to convert to usize"))?
582 .saturating_sub(offset_within_chunk)
583 .min(indices.len())
584 } else {
585 self.indices.len()
586 };
587
588 let Some(offset) = T::from(self.offset) else {
589 return Ok(SearchResult::NotFound(indices.len()));
591 };
592
593 let chunk_indices = &indices[patches_start_idx..patches_end_idx];
594 let result = chunk_indices.search_sorted(&(index + offset), SearchSortedSide::Left)?;
595
596 Ok(match result {
597 SearchResult::Found(idx) => SearchResult::Found(patches_start_idx + idx),
598 SearchResult::NotFound(idx) => SearchResult::NotFound(patches_start_idx + idx),
599 })
600 }
601
602 #[allow(clippy::disallowed_methods)]
604 pub fn min_index(&self) -> VortexResult<usize> {
605 let first = self
606 .indices
607 .execute_scalar(0, &mut legacy_session().create_execution_ctx())?
608 .as_primitive()
609 .as_::<usize>()
610 .ok_or_else(|| vortex_err!("index does not fit in usize"))?;
611 Ok(first - self.offset)
612 }
613
614 #[allow(clippy::disallowed_methods)]
616 pub fn max_index(&self) -> VortexResult<usize> {
617 let last = self
618 .indices
619 .execute_scalar(
620 self.indices.len() - 1,
621 &mut legacy_session().create_execution_ctx(),
622 )?
623 .as_primitive()
624 .as_::<usize>()
625 .ok_or_else(|| vortex_err!("index does not fit in usize"))?;
626 Ok(last - self.offset)
627 }
628
629 pub fn filter(&self, mask: &Mask, ctx: &mut ExecutionCtx) -> VortexResult<Option<Self>> {
631 if mask.len() != self.array_len {
632 vortex_bail!(
633 "Filter mask length {} does not match array length {}",
634 mask.len(),
635 self.array_len
636 );
637 }
638
639 match mask {
640 Mask::AllTrue(_) => Ok(Some(self.clone())),
641 Mask::AllFalse(_) => Ok(None),
642 Mask::Values(mask_values) => {
643 let flat_indices = self.indices().clone().execute::<PrimitiveArray>(ctx)?;
644 match_each_unsigned_integer_ptype!(flat_indices.ptype(), |I| {
645 filter_patches_with_mask(
646 flat_indices.as_slice::<I>(),
647 self.offset(),
648 self.values(),
649 mask_values,
650 ctx.allocator(),
651 )
652 })
653 }
654 }
655 }
656
657 pub fn mask(&self, mask: &Mask, ctx: &mut ExecutionCtx) -> VortexResult<Option<Self>> {
666 if mask.len() != self.array_len {
667 vortex_bail!(
668 "Filter mask length {} does not match array length {}",
669 mask.len(),
670 self.array_len
671 );
672 }
673
674 let filter_mask = match mask.bit_buffer() {
675 AllOr::All => return Ok(None),
676 AllOr::None => return self.clone().into_nullable_values().map(Some),
677 AllOr::Some(masked) => {
678 let patch_indices = self.indices().clone().execute::<PrimitiveArray>(ctx)?;
679 match_each_unsigned_integer_ptype!(patch_indices.ptype(), |P| {
680 let patch_indices = patch_indices.as_slice::<P>();
681 Mask::from_buffer(BitBuffer::collect_bool_in(
682 patch_indices.len(),
683 |i| {
684 #[allow(clippy::cast_possible_truncation)]
685 let idx = (patch_indices[i] as usize) - self.offset;
686 !masked.value(idx)
687 },
688 ctx.allocator().clone(),
689 ))
690 })
691 }
692 };
693
694 if filter_mask.all_false() {
695 return Ok(None);
696 }
697
698 let filtered_indices = self.indices.filter(filter_mask.clone())?;
700 let filtered_values = self.values.filter(filter_mask)?;
701
702 Self {
703 array_len: self.array_len,
704 offset: self.offset,
705 indices: filtered_indices,
706 values: filtered_values,
707 chunk_offsets: None,
709 offset_within_chunk: self.offset_within_chunk,
710 }
711 .into_nullable_values()
712 .map(Some)
713 }
714
715 fn into_nullable_values(self) -> VortexResult<Self> {
720 if self.values.dtype().is_nullable() {
721 return Ok(self);
722 }
723 let nullable = self.values.dtype().as_nullable();
724 self.map_values(|values| values.cast(nullable))
725 }
726
727 #[allow(clippy::disallowed_methods)]
729 pub fn slice(&self, range: Range<usize>) -> VortexResult<Option<Self>> {
730 let slice_start_idx = self.search_index(range.start)?.to_index();
731 let slice_end_idx = self.search_index(range.end)?.to_index();
732
733 if slice_start_idx == slice_end_idx {
734 return Ok(None);
735 }
736
737 let values = self.values().slice(slice_start_idx..slice_end_idx)?;
738 let indices = self.indices().slice(slice_start_idx..slice_end_idx)?;
739
740 let new_chunk_offsets = self
741 .chunk_offsets
742 .as_ref()
743 .map(|chunk_offsets| -> VortexResult<ArrayRef> {
744 let chunk_relative_offset = self.offset % PATCH_CHUNK_SIZE;
745 let chunk_start_idx = (chunk_relative_offset + range.start) / PATCH_CHUNK_SIZE;
746 let chunk_end_idx = (chunk_relative_offset + range.end).div_ceil(PATCH_CHUNK_SIZE);
747 chunk_offsets.slice(chunk_start_idx..chunk_end_idx)
748 })
749 .transpose()?;
750
751 let offset_within_chunk = new_chunk_offsets
752 .as_ref()
753 .map(|new_chunk_offsets| -> VortexResult<usize> {
754 let new_chunk_base = new_chunk_offsets
755 .execute_scalar(0, &mut legacy_session().create_execution_ctx())?
756 .as_primitive()
757 .as_::<usize>()
758 .ok_or_else(|| vortex_err!("chunk offset does not fit in usize"))?;
759 let parent_chunk_base = self.chunk_offset_at(0)?;
760 let parent_within = self.offset_within_chunk.unwrap_or(0);
761 Ok(parent_chunk_base + parent_within + slice_start_idx - new_chunk_base)
762 })
763 .transpose()?;
764
765 Ok(Some(Self {
766 array_len: range.len(),
767 offset: range.start + self.offset(),
768 indices,
769 values,
770 chunk_offsets: new_chunk_offsets,
771 offset_within_chunk,
772 }))
773 }
774
775 const PREFER_MAP_WHEN_PATCHES_OVER_INDICES_LESS_THAN: f64 = 5.0;
777
778 fn is_map_faster_than_search(&self, take_indices: &PrimitiveArray) -> bool {
779 (self.num_patches() as f64 / take_indices.len() as f64)
780 < Self::PREFER_MAP_WHEN_PATCHES_OVER_INDICES_LESS_THAN
781 }
782
783 pub fn take_with_nulls(
787 &self,
788 take_indices: &ArrayRef,
789 ctx: &mut ExecutionCtx,
790 ) -> VortexResult<Option<Self>> {
791 if take_indices.is_empty() {
792 return Ok(None);
793 }
794
795 let take_indices = take_indices.clone().execute::<PrimitiveArray>(ctx)?;
796 if self.is_map_faster_than_search(&take_indices) {
797 self.take_map(take_indices, true, ctx)
798 } else {
799 self.take_search(take_indices, true, ctx)
800 }
801 }
802
803 pub fn take(
807 &self,
808 take_indices: &ArrayRef,
809 ctx: &mut ExecutionCtx,
810 ) -> VortexResult<Option<Self>> {
811 if take_indices.is_empty() {
812 return Ok(None);
813 }
814
815 let take_indices = take_indices.clone().execute::<PrimitiveArray>(ctx)?;
816 if self.is_map_faster_than_search(&take_indices) {
817 self.take_map(take_indices, false, ctx)
818 } else {
819 self.take_search(take_indices, false, ctx)
820 }
821 }
822
823 #[expect(
824 clippy::cognitive_complexity,
825 reason = "complexity is from nested match_each_* macros"
826 )]
827 pub fn take_search(
828 &self,
829 take_indices: PrimitiveArray,
830 include_nulls: bool,
831 ctx: &mut ExecutionCtx,
832 ) -> VortexResult<Option<Self>> {
833 let take_indices_validity = take_indices.validity()?;
834 let take_indices_unsigned =
837 take_indices.reinterpret_cast(take_indices.ptype().to_unsigned());
838 let patch_indices = self.indices.clone().execute::<PrimitiveArray>(ctx)?;
839 let chunk_offsets = self
840 .chunk_offsets()
841 .as_ref()
842 .map(|co| co.clone().execute::<PrimitiveArray>(ctx))
843 .transpose()?;
844
845 let (values_indices, new_indices): (BufferMut<u64>, BufferMut<u64>) =
846 match_each_unsigned_integer_ptype!(patch_indices.ptype(), |PatchT| {
847 let patch_indices_slice = patch_indices.as_slice::<PatchT>();
848 match_each_unsigned_integer_ptype!(take_indices_unsigned.ptype(), |TakeT| {
849 let take_slice = take_indices_unsigned.as_slice::<TakeT>();
850
851 if let Some(chunk_offsets) = chunk_offsets {
852 match_each_unsigned_integer_ptype!(chunk_offsets.ptype(), |OffsetT| {
853 let chunk_offsets = chunk_offsets.as_slice::<OffsetT>();
854 take_indices_with_search_fn(
855 patch_indices_slice,
856 take_slice,
857 take_indices
858 .as_ref()
859 .validity()?
860 .execute_mask(take_indices.as_ref().len(), ctx)?,
861 include_nulls,
862 ctx.allocator(),
863 |take_idx| {
864 self.search_index_chunked_batch(
865 patch_indices_slice,
866 chunk_offsets,
867 take_idx,
868 )
869 },
870 )?
871 })
872 } else {
873 take_indices_with_search_fn(
874 patch_indices_slice,
875 take_slice,
876 take_indices
877 .as_ref()
878 .validity()?
879 .execute_mask(take_indices.as_ref().len(), ctx)?,
880 include_nulls,
881 ctx.allocator(),
882 |take_idx| {
883 let Some(offset) = <PatchT as NumCast>::from(self.offset) else {
884 return Ok(SearchResult::NotFound(patch_indices_slice.len()));
886 };
887
888 patch_indices_slice
889 .search_sorted(&(take_idx + offset), SearchSortedSide::Left)
890 },
891 )?
892 }
893 })
894 });
895
896 if new_indices.is_empty() {
897 return Ok(None);
898 }
899
900 let new_indices = new_indices.into_array();
901 let new_array_len = take_indices.len();
902 let values_validity = take_indices_validity.take(&new_indices)?;
903
904 Ok(Some(Self {
905 array_len: new_array_len,
906 offset: 0,
907 indices: new_indices,
908 values: self
909 .values()
910 .take(PrimitiveArray::new(values_indices, values_validity).into_array())?,
911 chunk_offsets: None,
912 offset_within_chunk: Some(0), }))
914 }
915
916 pub fn take_map(
917 &self,
918 take_indices: PrimitiveArray,
919 include_nulls: bool,
920 ctx: &mut ExecutionCtx,
921 ) -> VortexResult<Option<Self>> {
922 let indices = self.indices.clone().execute::<PrimitiveArray>(ctx)?;
923 let new_length = take_indices.len();
924 let take_indices_unsigned =
926 take_indices.reinterpret_cast(take_indices.ptype().to_unsigned());
927
928 let min_index = self.min_index()?;
929 let max_index = self.max_index()?;
930
931 let Some((new_sparse_indices, value_indices)) =
932 match_each_unsigned_integer_ptype!(indices.ptype(), |Indices| {
933 match_each_unsigned_integer_ptype!(take_indices_unsigned.ptype(), |TakeIndices| {
934 let take_validity = take_indices
935 .validity()?
936 .execute_mask(take_indices.len(), ctx)?;
937 let take_nullability = take_indices.validity()?.nullability();
938 let take_slice = take_indices_unsigned.as_slice::<TakeIndices>();
939 take_map::<_, TakeIndices>(
940 indices.as_slice::<Indices>(),
941 take_slice,
942 take_validity,
943 take_nullability,
944 self.offset(),
945 min_index,
946 max_index,
947 include_nulls,
948 ctx.allocator(),
949 )?
950 })
951 })
952 else {
953 return Ok(None);
954 };
955
956 let taken_values = self.values().take(value_indices)?;
957
958 Ok(Some(Patches {
959 array_len: new_length,
960 offset: 0,
961 indices: new_sparse_indices,
962 values: taken_values,
963 chunk_offsets: None,
965 offset_within_chunk: self.offset_within_chunk,
966 }))
967 }
968
969 pub fn map_values<F>(self, f: F) -> VortexResult<Self>
970 where
971 F: FnOnce(ArrayRef) -> VortexResult<ArrayRef>,
972 {
973 let values = f(self.values)?;
974 if self.indices.len() != values.len() {
975 vortex_bail!(
976 "map_values must preserve length: expected {} received {}",
977 self.indices.len(),
978 values.len()
979 )
980 }
981
982 Ok(Self {
983 array_len: self.array_len,
984 offset: self.offset,
985 indices: self.indices,
986 values,
987 chunk_offsets: self.chunk_offsets,
988 offset_within_chunk: self.offset_within_chunk,
989 })
990 }
991}
992
993fn search_index_binary_search(indices: &ArrayRef, needle: usize) -> VortexResult<SearchResult> {
999 if let Some(primitive) = indices.as_opt::<Primitive>() {
1000 match_each_unsigned_integer_ptype!(primitive.ptype(), |T| {
1001 let Ok(needle) = T::try_from(needle) else {
1002 return Ok(SearchResult::NotFound(primitive.len()));
1007 };
1008 return primitive
1009 .as_slice::<T>()
1010 .search_sorted(&needle, SearchSortedSide::Left);
1011 });
1012 }
1013
1014 search_index_binary_search_scalar(indices, needle)
1015}
1016
1017#[allow(clippy::disallowed_methods)]
1018fn search_index_binary_search_scalar(
1019 indices: &ArrayRef,
1020 needle: usize,
1021) -> VortexResult<SearchResult> {
1022 match_each_unsigned_integer_ptype!(indices.dtype().as_ptype(), |T| {
1023 SearchSortedPrimitiveArray::<T>::new(indices, &mut legacy_session().create_execution_ctx())
1024 .search_sorted(&needle, SearchSortedSide::Left)
1025 })
1026}
1027
1028#[expect(clippy::too_many_arguments)] fn take_map<I: NativePType + Hash + Eq + TryFrom<usize>, T: NativePType>(
1030 indices: &[I],
1031 take_indices: &[T],
1032 take_validity: Mask,
1033 take_nullability: Nullability,
1034 indices_offset: usize,
1035 min_index: usize,
1036 max_index: usize,
1037 include_nulls: bool,
1038 allocator: &BufferAllocatorRef,
1039) -> VortexResult<Option<(ArrayRef, ArrayRef)>>
1040where
1041 usize: TryFrom<T>,
1042 VortexError: From<<I as TryFrom<usize>>::Error>,
1043{
1044 let offset_i = I::try_from(indices_offset)?;
1045
1046 let sparse_index_to_value_index: HashMap<I, usize> = indices
1047 .iter()
1048 .copied()
1049 .map(|idx| idx - offset_i)
1050 .enumerate()
1051 .map(|(value_index, sparse_index)| (sparse_index, value_index))
1052 .collect();
1053
1054 let mut new_sparse_indices =
1055 BufferMut::<u64>::with_capacity_in(take_indices.len(), allocator.clone());
1056 let mut value_indices =
1057 BufferMut::<u64>::with_capacity_in(take_indices.len(), allocator.clone());
1058
1059 for (idx_in_take, &take_idx) in take_indices.iter().enumerate() {
1060 let ti = usize::try_from(take_idx)
1061 .map_err(|_| vortex_err!("Failed to convert index to usize"))?;
1062
1063 let is_null = match take_validity.bit_buffer() {
1065 AllOr::All => false,
1066 AllOr::None => true,
1067 AllOr::Some(buf) => !buf.value(idx_in_take),
1068 };
1069 if is_null {
1070 if include_nulls {
1071 new_sparse_indices.push(idx_in_take as u64);
1072 value_indices.push(0);
1073 }
1074 } else if ti >= min_index && ti <= max_index {
1075 let ti_as_i = I::try_from(ti)
1076 .map_err(|_| vortex_err!("take index does not fit in index type"))?;
1077 if let Some(&value_index) = sparse_index_to_value_index.get(&ti_as_i) {
1078 new_sparse_indices.push(idx_in_take as u64);
1079 value_indices.push(value_index as u64);
1080 }
1081 }
1082 }
1083
1084 if new_sparse_indices.is_empty() {
1085 return Ok(None);
1086 }
1087
1088 let new_sparse_indices = new_sparse_indices.into_array();
1089 let values_validity =
1090 Validity::from_mask(take_validity, take_nullability).take(&new_sparse_indices)?;
1091 Ok(Some((
1092 new_sparse_indices,
1093 PrimitiveArray::new(value_indices, values_validity).into_array(),
1094 )))
1095}
1096
1097fn filter_patches_with_mask<T: IntegerPType>(
1110 patch_indices: &[T],
1111 offset: usize,
1112 patch_values: &ArrayRef,
1113 mask: &MaskValues,
1114 allocator: &BufferAllocatorRef,
1115) -> VortexResult<Option<Patches>> {
1116 let true_count = mask.true_count();
1117 let patch_count = patch_indices.len();
1118 let expected_matches = true_count.min(patch_count);
1119
1120 let mut new_patch_indices =
1122 BufferMut::<u64>::with_capacity_in(expected_matches, allocator.clone());
1123 let mut kept_patches = Vec::with_capacity(expected_matches);
1124
1125 if patch_count > true_count {
1126 gallop_patches_for_mask_indices(
1127 patch_indices,
1128 offset,
1129 mask,
1130 &mut new_patch_indices,
1131 &mut kept_patches,
1132 )?;
1133 } else {
1134 match mask.cached_indices() {
1135 Some(mask_indices) => gallop_mask_indices_for_patches(
1136 patch_indices,
1137 offset,
1138 mask_indices,
1139 &mut new_patch_indices,
1140 &mut kept_patches,
1141 )?,
1142 None => rank_patches_in_mask_bitmap(
1143 patch_indices,
1144 offset,
1145 mask.bit_buffer(),
1146 &mut new_patch_indices,
1147 &mut kept_patches,
1148 )?,
1149 }
1150 }
1151
1152 if kept_patches.is_empty() {
1153 return Ok(None);
1154 }
1155
1156 let new_patch_indices = new_patch_indices.into_array();
1157 let new_patch_values =
1158 patch_values.filter(Mask::from_indices(patch_values.len(), kept_patches))?;
1159
1160 Ok(Some(unsafe {
1163 Patches::new_unchecked(
1164 true_count,
1165 0,
1166 new_patch_indices,
1167 new_patch_values,
1168 None,
1170 None,
1171 )
1172 }))
1173}
1174
1175fn gallop_lower_bound<T: Ord>(values: &[T], start: usize, needle: &T) -> usize {
1179 let mut low = start;
1180 let mut high = start;
1181 let mut step = 1;
1182
1183 while high < values.len() && values[high] < *needle {
1184 low = high + 1;
1185 high += step;
1186 step *= 2;
1187 }
1188
1189 let high = high.min(values.len());
1190 low + values[low..high].partition_point(|value| value < needle)
1191}
1192
1193fn gallop_patches_for_mask_indices<T: IntegerPType>(
1197 patch_indices: &[T],
1198 offset: usize,
1199 mask: &MaskValues,
1200 new_patch_indices: &mut BufferMut<u64>,
1201 kept_patches: &mut Vec<usize>,
1202) -> VortexResult<()> {
1203 let Some(&last_patch_index) = patch_indices.last() else {
1204 return Ok(());
1205 };
1206 let last_mask_index = patch_index_to_usize(last_patch_index, offset)?;
1207
1208 let mut patch_position = 0;
1209 let mut mask_position = 0;
1210
1211 let mut visit = |mask_index: usize| {
1212 let needle = <T as NumCast>::from(mask_index + offset)
1214 .vortex_expect("mask index is at most the last patch index");
1215
1216 patch_position = gallop_lower_bound(patch_indices, patch_position, &needle);
1217 if patch_indices[patch_position] == needle {
1218 new_patch_indices.push(mask_position as u64);
1219 kept_patches.push(patch_position);
1220 patch_position += 1;
1221 }
1222
1223 mask_position += 1;
1224 };
1225
1226 match mask.cached_indices() {
1227 Some(mask_indices) => {
1228 let end = mask_indices.partition_point(|&mask_index| mask_index <= last_mask_index);
1229 mask_indices[..end]
1230 .iter()
1231 .for_each(|&mask_index| visit(mask_index));
1232 }
1233 None => mask
1234 .bit_buffer()
1235 .slice(..=last_mask_index)
1236 .for_each_set_index(visit),
1237 }
1238
1239 Ok(())
1240}
1241
1242fn gallop_mask_indices_for_patches<T: IntegerPType>(
1244 patch_indices: &[T],
1245 offset: usize,
1246 mask_indices: &[usize],
1247 new_patch_indices: &mut BufferMut<u64>,
1248 kept_patches: &mut Vec<usize>,
1249) -> VortexResult<()> {
1250 let mut mask_position = 0;
1251
1252 for (patch_position, patch_index) in patch_indices.iter().enumerate() {
1253 let index = patch_index_to_usize(*patch_index, offset)?;
1254
1255 mask_position = gallop_lower_bound(mask_indices, mask_position, &index);
1256 if mask_position == mask_indices.len() {
1257 break;
1258 }
1259
1260 if mask_indices[mask_position] == index {
1261 new_patch_indices.push(mask_position as u64);
1262 kept_patches.push(patch_position);
1263 mask_position += 1;
1264 }
1265 }
1266
1267 Ok(())
1268}
1269
1270fn rank_patches_in_mask_bitmap<T: IntegerPType>(
1274 patch_indices: &[T],
1275 offset: usize,
1276 mask: &BitBuffer,
1277 new_patch_indices: &mut BufferMut<u64>,
1278 kept_patches: &mut Vec<usize>,
1279) -> VortexResult<()> {
1280 let mut rank = 0;
1281 let mut previous_index = 0;
1282
1283 for (patch_position, patch_index) in patch_indices.iter().enumerate() {
1284 let index = patch_index_to_usize(*patch_index, offset)?;
1285
1286 rank += mask.count_range(previous_index, index);
1287 previous_index = index;
1288
1289 if mask.value(index) {
1290 new_patch_indices.push(rank as u64);
1291 kept_patches.push(patch_position);
1292 }
1293 }
1294
1295 Ok(())
1296}
1297
1298#[inline]
1299fn patch_index_to_usize<T: IntegerPType>(patch_index: T, offset: usize) -> VortexResult<usize> {
1300 Ok(patch_index
1301 .to_usize()
1302 .ok_or_else(|| vortex_err!("patch index does not fit in usize"))?
1303 - offset)
1304}
1305
1306fn take_indices_with_search_fn<
1307 I: UnsignedPType,
1308 T: IntegerPType,
1309 F: Fn(I) -> VortexResult<SearchResult>,
1310>(
1311 indices: &[I],
1312 take_indices: &[T],
1313 take_validity: Mask,
1314 include_nulls: bool,
1315 allocator: &BufferAllocatorRef,
1316 search_fn: F,
1317) -> VortexResult<(BufferMut<u64>, BufferMut<u64>)> {
1318 let mut values_indices = BufferMut::with_capacity_in(take_indices.len(), allocator.clone());
1319 let mut new_indices = BufferMut::with_capacity_in(take_indices.len(), allocator.clone());
1320
1321 for (new_patch_idx, &take_idx) in take_indices.iter().enumerate() {
1322 if !take_validity.value(new_patch_idx) {
1323 if include_nulls {
1324 values_indices.push(0u64);
1326 new_indices.push(new_patch_idx as u64);
1327 }
1328 continue;
1329 } else {
1330 let search_result = match I::from(take_idx) {
1331 Some(idx) => search_fn(idx)?,
1332 None => SearchResult::NotFound(indices.len()),
1333 };
1334
1335 if let Some(patch_idx) = search_result.to_found() {
1336 values_indices.push(patch_idx as u64);
1337 new_indices.push(new_patch_idx as u64);
1338 }
1339 }
1340 }
1341
1342 Ok((values_indices, new_indices))
1343}
1344
1345#[cfg(test)]
1346mod test {
1347 use rstest::rstest;
1348 use vortex_buffer::BitBuffer;
1349 use vortex_buffer::Buffer;
1350 use vortex_buffer::BufferMut;
1351 use vortex_buffer::buffer;
1352 use vortex_error::VortexExpect;
1353 use vortex_error::VortexResult;
1354 use vortex_mask::Mask;
1355
1356 use crate::IntoArray;
1357 use crate::VortexSessionExecute;
1358 use crate::array_session;
1359 use crate::assert_arrays_eq;
1360 use crate::patches::Patches;
1361 use crate::patches::PrimitiveArray;
1362 use crate::search_sorted::SearchResult;
1363 use crate::validity::Validity;
1364
1365 #[test]
1366 fn test_filter() {
1367 let mut ctx = array_session().create_execution_ctx();
1368 let patches = Patches::new(
1369 100,
1370 0,
1371 buffer![10u32, 11, 20].into_array(),
1372 buffer![100, 110, 200].into_array(),
1373 None,
1374 )
1375 .unwrap();
1376
1377 let filtered = patches
1378 .filter(&Mask::from_indices(100, vec![10, 20, 30]), &mut ctx)
1379 .unwrap()
1380 .unwrap();
1381
1382 assert_arrays_eq!(
1383 filtered.indices(),
1384 PrimitiveArray::from_iter([0u64, 1]),
1385 &mut ctx
1386 );
1387 assert_arrays_eq!(
1388 filtered.values(),
1389 PrimitiveArray::from_iter([100i32, 200]),
1390 &mut ctx
1391 );
1392 }
1393
1394 #[test]
1395 fn take_with_nulls() {
1396 let mut ctx = array_session().create_execution_ctx();
1397 let patches = Patches::new(
1398 20,
1399 0,
1400 buffer![2u64, 9, 15].into_array(),
1401 PrimitiveArray::new(buffer![33_i32, 44, 55], Validity::AllValid).into_array(),
1402 None,
1403 )
1404 .unwrap();
1405
1406 let taken = patches
1407 .take(
1408 &PrimitiveArray::new(buffer![9, 0], Validity::from_iter(vec![true, false]))
1409 .into_array(),
1410 &mut ctx,
1411 )
1412 .unwrap()
1413 .unwrap();
1414 let primitive_values = taken
1415 .values()
1416 .clone()
1417 .execute::<PrimitiveArray>(&mut ctx)
1418 .unwrap();
1419 let primitive_indices = taken
1420 .indices()
1421 .clone()
1422 .execute::<PrimitiveArray>(&mut ctx)
1423 .unwrap();
1424 assert_eq!(taken.array_len(), 2);
1425 assert_arrays_eq!(
1426 primitive_values,
1427 PrimitiveArray::from_option_iter([Some(44i32)]),
1428 &mut ctx
1429 );
1430 assert_arrays_eq!(
1431 primitive_indices,
1432 PrimitiveArray::from_iter([0u64]),
1433 &mut ctx
1434 );
1435 assert_eq!(
1436 primitive_values
1437 .as_ref()
1438 .validity()
1439 .unwrap()
1440 .execute_mask(primitive_values.as_ref().len(), &mut ctx)
1441 .unwrap(),
1442 Mask::from_iter(vec![true])
1443 );
1444 }
1445
1446 #[test]
1447 fn take_search_with_nulls_chunked() {
1448 let mut ctx = array_session().create_execution_ctx();
1449 let patches = Patches::new(
1450 20,
1451 0,
1452 buffer![2u64, 9, 15].into_array(),
1453 buffer![33_i32, 44, 55].into_array(),
1454 Some(buffer![0u64].into_array()),
1455 )
1456 .unwrap();
1457
1458 let taken = patches
1459 .take_search(
1460 PrimitiveArray::new(buffer![9, 0], Validity::from_iter([true, false])),
1461 true,
1462 &mut ctx,
1463 )
1464 .unwrap()
1465 .unwrap();
1466
1467 let primitive_values = taken
1468 .values()
1469 .clone()
1470 .execute::<PrimitiveArray>(&mut ctx)
1471 .unwrap();
1472 assert_eq!(taken.array_len(), 2);
1473 assert_arrays_eq!(
1474 primitive_values,
1475 PrimitiveArray::from_option_iter([Some(44i32), None]),
1476 &mut ctx
1477 );
1478 assert_arrays_eq!(
1479 taken.indices(),
1480 PrimitiveArray::from_iter([0u64, 1]),
1481 &mut ctx
1482 );
1483
1484 assert_eq!(
1485 primitive_values
1486 .as_ref()
1487 .validity()
1488 .unwrap()
1489 .execute_mask(primitive_values.as_ref().len(), &mut ctx)
1490 .unwrap(),
1491 Mask::from_iter([true, false])
1492 );
1493 }
1494
1495 #[test]
1496 fn take_search_chunked_multiple_chunks() {
1497 let mut ctx = array_session().create_execution_ctx();
1498 let patches = Patches::new(
1499 2048,
1500 0,
1501 buffer![100u64, 500, 1200, 1800].into_array(),
1502 buffer![10_i32, 20, 30, 40].into_array(),
1503 Some(buffer![0u64, 2].into_array()),
1504 )
1505 .unwrap();
1506
1507 let taken = patches
1508 .take_search(
1509 PrimitiveArray::new(buffer![500, 1200, 999], Validity::AllValid),
1510 true,
1511 &mut ctx,
1512 )
1513 .unwrap()
1514 .unwrap();
1515
1516 assert_eq!(taken.array_len(), 3);
1517 assert_arrays_eq!(
1518 taken.values(),
1519 PrimitiveArray::from_option_iter([Some(20i32), Some(30)]),
1520 &mut ctx
1521 );
1522 }
1523
1524 #[test]
1525 fn take_search_chunked_indices_with_no_patches() {
1526 let mut ctx = array_session().create_execution_ctx();
1527 let patches = Patches::new(
1528 20,
1529 0,
1530 buffer![2u64, 9, 15].into_array(),
1531 buffer![33_i32, 44, 55].into_array(),
1532 Some(buffer![0u64].into_array()),
1533 )
1534 .unwrap();
1535
1536 let taken = patches
1537 .take_search(
1538 PrimitiveArray::new(buffer![3, 4, 5], Validity::AllValid),
1539 true,
1540 &mut ctx,
1541 )
1542 .unwrap();
1543
1544 assert!(taken.is_none());
1545 }
1546
1547 #[test]
1548 fn take_search_chunked_interleaved() {
1549 let mut ctx = array_session().create_execution_ctx();
1550 let patches = Patches::new(
1551 30,
1552 0,
1553 buffer![5u64, 10, 20, 25].into_array(),
1554 buffer![100_i32, 200, 300, 400].into_array(),
1555 Some(buffer![0u64].into_array()),
1556 )
1557 .unwrap();
1558
1559 let taken = patches
1560 .take_search(
1561 PrimitiveArray::new(buffer![10, 15, 20, 99], Validity::AllValid),
1562 true,
1563 &mut ctx,
1564 )
1565 .unwrap()
1566 .unwrap();
1567
1568 assert_eq!(taken.array_len(), 4);
1569 assert_arrays_eq!(
1570 taken.values(),
1571 PrimitiveArray::from_option_iter([Some(200i32), Some(300)]),
1572 &mut ctx
1573 );
1574 }
1575
1576 #[test]
1577 fn test_take_search_multiple_chunk_offsets() {
1578 let mut ctx = array_session().create_execution_ctx();
1579 let patches = Patches::new(
1580 1500,
1581 0,
1582 BufferMut::from_iter(0..1500u64).into_array(),
1583 BufferMut::from_iter(0..1500i32).into_array(),
1584 Some(buffer![0u64, 1024u64].into_array()),
1585 )
1586 .unwrap();
1587
1588 let taken = patches
1589 .take_search(
1590 PrimitiveArray::new(BufferMut::from_iter(0..1500u64), Validity::AllValid),
1591 false,
1592 &mut ctx,
1593 )
1594 .unwrap()
1595 .unwrap();
1596
1597 assert_eq!(taken.array_len(), 1500);
1598 }
1599
1600 #[test]
1601 fn test_slice() {
1602 let mut ctx = array_session().create_execution_ctx();
1603 let values = buffer![15_u32, 135, 13531, 42].into_array();
1604 let indices = buffer![10_u64, 11, 50, 100].into_array();
1605
1606 let patches = Patches::new(101, 0, indices, values, None).unwrap();
1607
1608 let sliced = patches.slice(15..100).unwrap().unwrap();
1609 assert_eq!(sliced.array_len(), 100 - 15);
1610 assert_arrays_eq!(
1611 sliced.values(),
1612 PrimitiveArray::from_iter([13531u32]),
1613 &mut ctx
1614 );
1615 }
1616
1617 #[test]
1618 fn doubly_sliced() {
1619 let mut ctx = array_session().create_execution_ctx();
1620 let values = buffer![15_u32, 135, 13531, 42].into_array();
1621 let indices = buffer![10_u64, 11, 50, 100].into_array();
1622
1623 let patches = Patches::new(101, 0, indices, values, None).unwrap();
1624
1625 let sliced = patches.slice(15..100).unwrap().unwrap();
1626 assert_eq!(sliced.array_len(), 100 - 15);
1627 assert_arrays_eq!(
1628 sliced.values(),
1629 PrimitiveArray::from_iter([13531u32]),
1630 &mut ctx
1631 );
1632
1633 let doubly_sliced = sliced.slice(35..36).unwrap().unwrap();
1634 assert_arrays_eq!(
1635 doubly_sliced.values(),
1636 PrimitiveArray::from_iter([13531u32]),
1637 &mut ctx
1638 );
1639 }
1640
1641 #[test]
1642 fn test_mask_all_true() {
1643 let mut ctx = array_session().create_execution_ctx();
1644 let patches = Patches::new(
1645 10,
1646 0,
1647 buffer![2u64, 5, 8].into_array(),
1648 buffer![100i32, 200, 300].into_array(),
1649 None,
1650 )
1651 .unwrap();
1652
1653 let mask = Mask::new_true(10);
1654 let masked = patches.mask(&mask, &mut ctx).unwrap();
1655 assert!(masked.is_none());
1656 }
1657
1658 #[test]
1659 fn test_mask_all_false() {
1660 let mut ctx = array_session().create_execution_ctx();
1661 let patches = Patches::new(
1662 10,
1663 0,
1664 buffer![2u64, 5, 8].into_array(),
1665 buffer![100i32, 200, 300].into_array(),
1666 None,
1667 )
1668 .unwrap();
1669
1670 let mask = Mask::new_false(10);
1671 let masked = patches.mask(&mask, &mut ctx).unwrap().unwrap();
1672
1673 assert!(masked.values().dtype().is_nullable());
1675 assert_arrays_eq!(
1676 masked.values(),
1677 PrimitiveArray::from_option_iter([Some(100i32), Some(200), Some(300)]),
1678 &mut ctx
1679 );
1680 assert!(masked.values().is_valid(0, &mut ctx).unwrap());
1681 assert!(masked.values().is_valid(1, &mut ctx).unwrap());
1682 assert!(masked.values().is_valid(2, &mut ctx).unwrap());
1683
1684 assert_arrays_eq!(
1686 masked.indices(),
1687 PrimitiveArray::from_iter([2u64, 5, 8]),
1688 &mut ctx
1689 );
1690 }
1691
1692 #[test]
1693 fn test_mask_partial() {
1694 let mut ctx = array_session().create_execution_ctx();
1695 let patches = Patches::new(
1696 10,
1697 0,
1698 buffer![2u64, 5, 8].into_array(),
1699 buffer![100i32, 200, 300].into_array(),
1700 None,
1701 )
1702 .unwrap();
1703
1704 let mask = Mask::from_iter([
1706 false, false, true, false, false, false, false, false, true, false,
1707 ]);
1708 let masked = patches.mask(&mask, &mut ctx).unwrap().unwrap();
1709
1710 assert_eq!(masked.values().len(), 1);
1712 assert_arrays_eq!(
1713 masked.values(),
1714 PrimitiveArray::from_option_iter([Some(200i32)]),
1715 &mut ctx
1716 );
1717
1718 assert_arrays_eq!(
1720 masked.indices(),
1721 PrimitiveArray::from_iter([5u64]),
1722 &mut ctx
1723 );
1724 }
1725
1726 #[test]
1727 fn test_mask_with_offset() {
1728 let mut ctx = array_session().create_execution_ctx();
1729 let patches = Patches::new(
1730 10,
1731 5, buffer![7u64, 10, 13].into_array(), buffer![100i32, 200, 300].into_array(),
1734 None,
1735 )
1736 .unwrap();
1737
1738 let mask = Mask::from_iter([
1740 false, false, true, false, false, false, false, false, false, false,
1741 ]);
1742
1743 let masked = patches.mask(&mask, &mut ctx).unwrap().unwrap();
1744 assert_eq!(masked.array_len(), 10);
1745 assert_eq!(masked.offset(), 5);
1746 assert_arrays_eq!(
1747 masked.indices(),
1748 PrimitiveArray::from_iter([10u64, 13]),
1749 &mut ctx
1750 );
1751 assert_arrays_eq!(
1752 masked.values(),
1753 PrimitiveArray::from_option_iter([Some(200i32), Some(300)]),
1754 &mut ctx
1755 );
1756 }
1757
1758 #[test]
1759 fn test_mask_nullable_values() {
1760 let mut ctx = array_session().create_execution_ctx();
1761 let patches = Patches::new(
1762 10,
1763 0,
1764 buffer![2u64, 5, 8].into_array(),
1765 PrimitiveArray::from_option_iter([Some(100i32), None, Some(300)]).into_array(),
1766 None,
1767 )
1768 .unwrap();
1769
1770 let mask = Mask::from_iter([
1772 false, false, true, false, false, false, false, false, false, false,
1773 ]);
1774 let masked = patches.mask(&mask, &mut ctx).unwrap().unwrap();
1775
1776 assert_arrays_eq!(
1778 masked.indices(),
1779 PrimitiveArray::from_iter([5u64, 8]),
1780 &mut ctx
1781 );
1782
1783 let masked_values = masked
1785 .values()
1786 .clone()
1787 .execute::<PrimitiveArray>(&mut ctx)
1788 .unwrap();
1789 assert_eq!(masked_values.len(), 2);
1790 assert!(!masked_values.is_valid(0, &mut ctx).unwrap()); assert!(masked_values.is_valid(1, &mut ctx).unwrap()); assert_eq!(
1793 i32::try_from(&masked_values.execute_scalar(1, &mut ctx).unwrap()).unwrap(),
1794 300i32
1795 );
1796 }
1797
1798 #[rstest]
1800 #[case::sparse_mask((0..1000).step_by(2).collect(), (0..1000).step_by(97).collect())]
1801 #[case::sparse_patches((0..1000).step_by(97).collect(), (0..1000).step_by(2).collect())]
1802 #[case::similar_sizes((0..1000).step_by(3).collect(), (0..1000).step_by(2).collect())]
1803 #[case::no_overlap((0..500).collect(), (500..1000).step_by(50).collect())]
1804 #[case::no_overlap_sparse_patches((500..1000).step_by(100).collect(), (0..500).collect())]
1805 #[case::edges(vec![0, 1, 998, 999], vec![0, 999])]
1806 fn test_filter_matches_naive(
1807 #[case] patch_indices: Vec<usize>,
1808 #[case] mask_indices: Vec<usize>,
1809 #[values(0, 5)] offset: usize,
1810 #[values(true, false)] cache_mask_indices: bool,
1811 ) -> VortexResult<()> {
1812 const LEN: usize = 1000;
1813 let mut ctx = array_session().create_execution_ctx();
1814
1815 let patches = Patches::new(
1816 LEN,
1817 offset,
1818 patch_indices
1819 .iter()
1820 .map(|&index| u32::try_from(index + offset).vortex_expect("index fits in u32"))
1821 .collect::<Buffer<u32>>()
1822 .into_array(),
1823 (0i32..)
1824 .take(patch_indices.len())
1825 .collect::<Buffer<i32>>()
1826 .into_array(),
1827 None,
1828 )?;
1829
1830 let mask = if cache_mask_indices {
1831 Mask::from_indices(LEN, mask_indices.iter().copied())
1832 } else {
1833 Mask::from_buffer(BitBuffer::from_iter(
1834 (0..LEN).map(|index| mask_indices.binary_search(&index).is_ok()),
1835 ))
1836 };
1837
1838 let (expected_indices, expected_values): (Vec<u64>, Vec<i32>) = mask_indices
1839 .iter()
1840 .enumerate()
1841 .filter_map(|(mask_position, index)| {
1842 patch_indices
1843 .binary_search(index)
1844 .ok()
1845 .map(|patch_position| {
1846 (
1847 mask_position as u64,
1848 i32::try_from(patch_position).vortex_expect("position fits in i32"),
1849 )
1850 })
1851 })
1852 .unzip();
1853
1854 let filtered = patches.filter(&mask, &mut ctx)?;
1855
1856 let Some(filtered) = filtered else {
1857 assert!(expected_indices.is_empty());
1858 return Ok(());
1859 };
1860
1861 assert_eq!(filtered.array_len(), mask_indices.len());
1862 assert_arrays_eq!(
1863 filtered.indices(),
1864 PrimitiveArray::from_iter(expected_indices),
1865 &mut ctx
1866 );
1867 assert_arrays_eq!(
1868 filtered.values(),
1869 PrimitiveArray::from_iter(expected_values),
1870 &mut ctx
1871 );
1872
1873 Ok(())
1874 }
1875
1876 #[test]
1878 fn test_filter_sparse_mask_past_index_type_max() -> VortexResult<()> {
1879 let mut ctx = array_session().create_execution_ctx();
1880 let patches = Patches::new(
1881 1000,
1882 0,
1883 (0..=u8::MAX).collect::<Buffer<u8>>().into_array(),
1884 (0..256i32).collect::<Buffer<i32>>().into_array(),
1885 None,
1886 )?;
1887
1888 let mask = Mask::from_indices(1000, [3, 255, 256, 900]);
1889 let filtered = patches
1890 .filter(&mask, &mut ctx)?
1891 .expect("patches at 3 and 255 are kept");
1892
1893 assert_arrays_eq!(
1894 filtered.indices(),
1895 PrimitiveArray::from_iter([0u64, 1]),
1896 &mut ctx
1897 );
1898 assert_arrays_eq!(
1899 filtered.values(),
1900 PrimitiveArray::from_iter([3i32, 255]),
1901 &mut ctx
1902 );
1903
1904 Ok(())
1905 }
1906
1907 #[test]
1908 fn test_filter_keep_all() {
1909 let mut ctx = array_session().create_execution_ctx();
1910 let patches = Patches::new(
1911 10,
1912 0,
1913 buffer![2u64, 5, 8].into_array(),
1914 buffer![100i32, 200, 300].into_array(),
1915 None,
1916 )
1917 .unwrap();
1918
1919 let mask = Mask::from_indices(10, 0..10);
1921 let filtered = patches.filter(&mask, &mut ctx).unwrap().unwrap();
1922
1923 assert_arrays_eq!(
1924 filtered.indices(),
1925 PrimitiveArray::from_iter([2u64, 5, 8]),
1926 &mut ctx
1927 );
1928 assert_arrays_eq!(
1929 filtered.values(),
1930 PrimitiveArray::from_iter([100i32, 200, 300]),
1931 &mut ctx
1932 );
1933 }
1934
1935 #[test]
1936 fn test_filter_none() {
1937 let mut ctx = array_session().create_execution_ctx();
1938 let patches = Patches::new(
1939 10,
1940 0,
1941 buffer![2u64, 5, 8].into_array(),
1942 buffer![100i32, 200, 300].into_array(),
1943 None,
1944 )
1945 .unwrap();
1946
1947 let mask = Mask::from_indices(10, vec![]);
1949 let filtered = patches.filter(&mask, &mut ctx).unwrap();
1950 assert!(filtered.is_none());
1951 }
1952
1953 #[test]
1954 fn test_filter_with_indices() {
1955 let mut ctx = array_session().create_execution_ctx();
1956 let patches = Patches::new(
1957 10,
1958 0,
1959 buffer![2u64, 5, 8].into_array(),
1960 buffer![100i32, 200, 300].into_array(),
1961 None,
1962 )
1963 .unwrap();
1964
1965 let mask = Mask::from_indices(10, vec![2, 5, 9]);
1967 let filtered = patches.filter(&mask, &mut ctx).unwrap().unwrap();
1968
1969 assert_arrays_eq!(
1970 filtered.indices(),
1971 PrimitiveArray::from_iter([0u64, 1]),
1972 &mut ctx
1973 ); assert_arrays_eq!(
1975 filtered.values(),
1976 PrimitiveArray::from_iter([100i32, 200]),
1977 &mut ctx
1978 );
1979 }
1980
1981 #[test]
1982 fn test_slice_full_range() {
1983 let mut ctx = array_session().create_execution_ctx();
1984 let patches = Patches::new(
1985 10,
1986 0,
1987 buffer![2u64, 5, 8].into_array(),
1988 buffer![100i32, 200, 300].into_array(),
1989 None,
1990 )
1991 .unwrap();
1992
1993 let sliced = patches.slice(0..10).unwrap().unwrap();
1994
1995 assert_arrays_eq!(
1996 sliced.indices(),
1997 PrimitiveArray::from_iter([2u64, 5, 8]),
1998 &mut ctx
1999 );
2000 assert_arrays_eq!(
2001 sliced.values(),
2002 PrimitiveArray::from_iter([100i32, 200, 300]),
2003 &mut ctx
2004 );
2005 }
2006
2007 #[test]
2008 fn test_slice_partial() {
2009 let mut ctx = array_session().create_execution_ctx();
2010 let patches = Patches::new(
2011 10,
2012 0,
2013 buffer![2u64, 5, 8].into_array(),
2014 buffer![100i32, 200, 300].into_array(),
2015 None,
2016 )
2017 .unwrap();
2018
2019 let sliced = patches.slice(3..8).unwrap().unwrap();
2021
2022 assert_arrays_eq!(
2023 sliced.indices(),
2024 PrimitiveArray::from_iter([5u64]),
2025 &mut ctx
2026 ); assert_arrays_eq!(
2028 sliced.values(),
2029 PrimitiveArray::from_iter([200i32]),
2030 &mut ctx
2031 );
2032 assert_eq!(sliced.array_len(), 5); assert_eq!(sliced.offset(), 3); }
2035
2036 #[test]
2037 fn test_slice_no_patches() {
2038 let patches = Patches::new(
2039 10,
2040 0,
2041 buffer![2u64, 5, 8].into_array(),
2042 buffer![100i32, 200, 300].into_array(),
2043 None,
2044 )
2045 .unwrap();
2046
2047 let sliced = patches.slice(6..7).unwrap();
2049 assert!(sliced.is_none());
2050 }
2051
2052 #[test]
2053 fn test_slice_with_offset() {
2054 let mut ctx = array_session().create_execution_ctx();
2055 let patches = Patches::new(
2056 10,
2057 5, buffer![7u64, 10, 13].into_array(), buffer![100i32, 200, 300].into_array(),
2060 None,
2061 )
2062 .unwrap();
2063
2064 let sliced = patches.slice(3..8).unwrap().unwrap();
2066
2067 assert_arrays_eq!(
2068 sliced.indices(),
2069 PrimitiveArray::from_iter([10u64]),
2070 &mut ctx
2071 ); assert_arrays_eq!(
2073 sliced.values(),
2074 PrimitiveArray::from_iter([200i32]),
2075 &mut ctx
2076 );
2077 assert_eq!(sliced.offset(), 8); }
2079
2080 #[test]
2081 fn test_patch_values() {
2082 let mut ctx = array_session().create_execution_ctx();
2083 let patches = Patches::new(
2084 10,
2085 0,
2086 buffer![2u64, 5, 8].into_array(),
2087 buffer![100i32, 200, 300].into_array(),
2088 None,
2089 )
2090 .unwrap();
2091
2092 let values = patches
2093 .values()
2094 .clone()
2095 .execute::<PrimitiveArray>(&mut ctx)
2096 .unwrap();
2097 assert_eq!(
2098 i32::try_from(&values.execute_scalar(0, &mut ctx).unwrap()).unwrap(),
2099 100i32
2100 );
2101 assert_eq!(
2102 i32::try_from(&values.execute_scalar(1, &mut ctx).unwrap()).unwrap(),
2103 200i32
2104 );
2105 assert_eq!(
2106 i32::try_from(&values.execute_scalar(2, &mut ctx).unwrap()).unwrap(),
2107 300i32
2108 );
2109 }
2110
2111 #[test]
2112 fn test_indices_range() {
2113 let patches = Patches::new(
2114 10,
2115 0,
2116 buffer![2u64, 5, 8].into_array(),
2117 buffer![100i32, 200, 300].into_array(),
2118 None,
2119 )
2120 .unwrap();
2121
2122 assert_eq!(patches.min_index().unwrap(), 2);
2123 assert_eq!(patches.max_index().unwrap(), 8);
2124 }
2125
2126 #[test]
2127 fn test_search_index() {
2128 let patches = Patches::new(
2129 10,
2130 0,
2131 buffer![2u64, 5, 8].into_array(),
2132 buffer![100i32, 200, 300].into_array(),
2133 None,
2134 )
2135 .unwrap();
2136
2137 assert_eq!(patches.search_index(2).unwrap(), SearchResult::Found(0));
2139 assert_eq!(patches.search_index(5).unwrap(), SearchResult::Found(1));
2140 assert_eq!(patches.search_index(8).unwrap(), SearchResult::Found(2));
2141
2142 assert_eq!(patches.search_index(0).unwrap(), SearchResult::NotFound(0));
2144 assert_eq!(patches.search_index(3).unwrap(), SearchResult::NotFound(1));
2145 assert_eq!(patches.search_index(6).unwrap(), SearchResult::NotFound(2));
2146 assert_eq!(patches.search_index(9).unwrap(), SearchResult::NotFound(3));
2147 }
2148
2149 #[test]
2150 fn test_mask_boundary_patches() {
2151 let mut ctx = array_session().create_execution_ctx();
2152 let patches = Patches::new(
2154 10,
2155 0,
2156 buffer![0u64, 9].into_array(),
2157 buffer![100i32, 200].into_array(),
2158 None,
2159 )
2160 .unwrap();
2161
2162 let mask = Mask::from_iter([
2163 true, false, false, false, false, false, false, false, false, false,
2164 ]);
2165 let masked = patches.mask(&mask, &mut ctx).unwrap();
2166 assert!(masked.is_some());
2167 let masked = masked.unwrap();
2168 assert_arrays_eq!(
2169 masked.indices(),
2170 PrimitiveArray::from_iter([9u64]),
2171 &mut ctx
2172 );
2173 assert_arrays_eq!(
2174 masked.values(),
2175 PrimitiveArray::from_option_iter([Some(200i32)]),
2176 &mut ctx
2177 );
2178 }
2179
2180 #[test]
2181 fn test_mask_all_patches_removed() {
2182 let mut ctx = array_session().create_execution_ctx();
2183 let patches = Patches::new(
2185 10,
2186 0,
2187 buffer![2u64, 5, 8].into_array(),
2188 buffer![100i32, 200, 300].into_array(),
2189 None,
2190 )
2191 .unwrap();
2192
2193 let mask = Mask::from_iter([
2195 false, false, true, false, false, true, false, false, true, false,
2196 ]);
2197 let masked = patches.mask(&mask, &mut ctx).unwrap();
2198 assert!(masked.is_none());
2199 }
2200
2201 #[test]
2202 fn test_mask_no_patches_removed() {
2203 let mut ctx = array_session().create_execution_ctx();
2204 let patches = Patches::new(
2206 10,
2207 0,
2208 buffer![2u64, 5, 8].into_array(),
2209 buffer![100i32, 200, 300].into_array(),
2210 None,
2211 )
2212 .unwrap();
2213
2214 let mask = Mask::from_iter([
2216 true, false, false, true, false, false, true, false, false, true,
2217 ]);
2218 let masked = patches.mask(&mask, &mut ctx).unwrap().unwrap();
2219
2220 assert_arrays_eq!(
2221 masked.indices(),
2222 PrimitiveArray::from_iter([2u64, 5, 8]),
2223 &mut ctx
2224 );
2225 assert_arrays_eq!(
2226 masked.values(),
2227 PrimitiveArray::from_option_iter([Some(100i32), Some(200), Some(300)]),
2228 &mut ctx
2229 );
2230 }
2231
2232 #[test]
2233 fn test_mask_single_patch() {
2234 let mut ctx = array_session().create_execution_ctx();
2235 let patches = Patches::new(
2237 5,
2238 0,
2239 buffer![2u64].into_array(),
2240 buffer![42i32].into_array(),
2241 None,
2242 )
2243 .unwrap();
2244
2245 let mask = Mask::from_iter([false, false, true, false, false]);
2247 let masked = patches.mask(&mask, &mut ctx).unwrap();
2248 assert!(masked.is_none());
2249
2250 let mask = Mask::from_iter([true, false, false, true, false]);
2252 let masked = patches.mask(&mask, &mut ctx).unwrap().unwrap();
2253 assert_arrays_eq!(
2254 masked.indices(),
2255 PrimitiveArray::from_iter([2u64]),
2256 &mut ctx
2257 );
2258 }
2259
2260 #[test]
2261 fn test_mask_contiguous_patches() {
2262 let mut ctx = array_session().create_execution_ctx();
2263 let patches = Patches::new(
2265 10,
2266 0,
2267 buffer![3u64, 4, 5, 6].into_array(),
2268 buffer![100i32, 200, 300, 400].into_array(),
2269 None,
2270 )
2271 .unwrap();
2272
2273 let mask = Mask::from_iter([
2275 false, false, false, false, true, true, false, false, false, false,
2276 ]);
2277 let masked = patches.mask(&mask, &mut ctx).unwrap().unwrap();
2278
2279 assert_arrays_eq!(
2280 masked.indices(),
2281 PrimitiveArray::from_iter([3u64, 6]),
2282 &mut ctx
2283 );
2284 assert_arrays_eq!(
2285 masked.values(),
2286 PrimitiveArray::from_option_iter([Some(100i32), Some(400)]),
2287 &mut ctx
2288 );
2289 }
2290
2291 #[test]
2292 fn test_mask_with_large_offset() {
2293 let mut ctx = array_session().create_execution_ctx();
2294 let patches = Patches::new(
2296 20,
2297 15,
2298 buffer![16u64, 17, 19].into_array(), buffer![100i32, 200, 300].into_array(),
2300 None,
2301 )
2302 .unwrap();
2303
2304 let mask = Mask::from_iter([
2306 false, false, true, false, false, false, false, false, false, false, false, false,
2307 false, false, false, false, false, false, false, false,
2308 ]);
2309 let masked = patches.mask(&mask, &mut ctx).unwrap().unwrap();
2310
2311 assert_arrays_eq!(
2312 masked.indices(),
2313 PrimitiveArray::from_iter([16u64, 19]),
2314 &mut ctx
2315 );
2316 assert_arrays_eq!(
2317 masked.values(),
2318 PrimitiveArray::from_option_iter([Some(100i32), Some(300)]),
2319 &mut ctx
2320 );
2321 }
2322
2323 #[test]
2324 #[should_panic(expected = "Filter mask length 5 does not match array length 10")]
2325 fn test_mask_wrong_length() {
2326 let mut ctx = array_session().create_execution_ctx();
2327 let patches = Patches::new(
2328 10,
2329 0,
2330 buffer![2u64, 5, 8].into_array(),
2331 buffer![100i32, 200, 300].into_array(),
2332 None,
2333 )
2334 .unwrap();
2335
2336 let mask = Mask::from_iter([false, false, true, false, false]);
2338 patches.mask(&mask, &mut ctx).unwrap();
2339 }
2340
2341 #[test]
2342 fn test_chunk_offsets_search() {
2343 let indices = buffer![100u64, 200, 3000, 3100].into_array();
2344 let values = buffer![10i32, 20, 30, 40].into_array();
2345 let chunk_offsets = buffer![0u64, 2, 2, 3].into_array();
2346 let patches = Patches::new(4096, 0, indices, values, Some(chunk_offsets)).unwrap();
2347
2348 assert!(patches.chunk_offsets.is_some());
2349
2350 assert_eq!(patches.search_index(100).unwrap(), SearchResult::Found(0));
2352 assert_eq!(patches.search_index(200).unwrap(), SearchResult::Found(1));
2353
2354 assert_eq!(
2356 patches.search_index(1500).unwrap(),
2357 SearchResult::NotFound(2)
2358 );
2359 assert_eq!(
2360 patches.search_index(2000).unwrap(),
2361 SearchResult::NotFound(2)
2362 );
2363
2364 assert_eq!(patches.search_index(3000).unwrap(), SearchResult::Found(2));
2366 assert_eq!(patches.search_index(3100).unwrap(), SearchResult::Found(3));
2367
2368 assert_eq!(
2369 patches.search_index(1024).unwrap(),
2370 SearchResult::NotFound(2)
2371 );
2372 }
2373
2374 #[test]
2375 fn test_chunk_offsets_with_slice() {
2376 let indices = buffer![100u64, 500, 1200, 1300, 1500, 1800, 2100, 2500].into_array();
2377 let values = buffer![10i32, 20, 30, 35, 40, 45, 50, 60].into_array();
2378 let chunk_offsets = buffer![0u64, 2, 6].into_array();
2379 let patches = Patches::new(3000, 0, indices, values, Some(chunk_offsets)).unwrap();
2380
2381 let sliced = patches.slice(1000..2200).unwrap().unwrap();
2382
2383 assert!(sliced.chunk_offsets.is_some());
2384 assert_eq!(sliced.offset(), 1000);
2385
2386 assert_eq!(sliced.search_index(200).unwrap(), SearchResult::Found(0));
2387 assert_eq!(sliced.search_index(500).unwrap(), SearchResult::Found(2));
2388 assert_eq!(sliced.search_index(1100).unwrap(), SearchResult::Found(4));
2389
2390 assert_eq!(sliced.search_index(250).unwrap(), SearchResult::NotFound(1));
2391 }
2392
2393 #[test]
2394 fn test_chunk_offsets_with_slice_after_first_chunk() {
2395 let indices = buffer![100u64, 500, 1200, 1300, 1500, 1800, 2100, 2500].into_array();
2396 let values = buffer![10i32, 20, 30, 35, 40, 45, 50, 60].into_array();
2397 let chunk_offsets = buffer![0u64, 2, 6].into_array();
2398 let patches = Patches::new(3000, 0, indices, values, Some(chunk_offsets)).unwrap();
2399
2400 let sliced = patches.slice(1300..2200).unwrap().unwrap();
2401
2402 assert!(sliced.chunk_offsets.is_some());
2403 assert_eq!(sliced.offset(), 1300);
2404
2405 assert_eq!(sliced.search_index(0).unwrap(), SearchResult::Found(0));
2406 assert_eq!(sliced.search_index(200).unwrap(), SearchResult::Found(1));
2407 assert_eq!(sliced.search_index(500).unwrap(), SearchResult::Found(2));
2408 assert_eq!(sliced.search_index(250).unwrap(), SearchResult::NotFound(2));
2409 assert_eq!(sliced.search_index(900).unwrap(), SearchResult::NotFound(4));
2410 }
2411
2412 #[test]
2413 fn test_chunk_offsets_slice_empty_result() {
2414 let indices = buffer![100u64, 200, 3000, 3100].into_array();
2415 let values = buffer![10i32, 20, 30, 40].into_array();
2416 let chunk_offsets = buffer![0u64, 2].into_array();
2417 let patches = Patches::new(4000, 0, indices, values, Some(chunk_offsets)).unwrap();
2418
2419 let sliced = patches.slice(1000..2000).unwrap();
2420 assert!(sliced.is_none());
2421 }
2422
2423 #[test]
2424 fn test_chunk_offsets_slice_single_patch() {
2425 let indices = buffer![100u64, 1200, 1300, 2500].into_array();
2426 let values = buffer![10i32, 20, 30, 40].into_array();
2427 let chunk_offsets = buffer![0u64, 1, 3].into_array();
2428 let patches = Patches::new(3000, 0, indices, values, Some(chunk_offsets)).unwrap();
2429
2430 let sliced = patches.slice(1100..1250).unwrap().unwrap();
2431
2432 assert_eq!(sliced.num_patches(), 1);
2433 assert_eq!(sliced.offset(), 1100);
2434 assert_eq!(sliced.search_index(100).unwrap(), SearchResult::Found(0)); assert_eq!(sliced.search_index(50).unwrap(), SearchResult::NotFound(0));
2436 assert_eq!(sliced.search_index(150).unwrap(), SearchResult::NotFound(1));
2437 }
2438
2439 #[test]
2440 fn test_chunk_offsets_slice_across_chunks() {
2441 let indices = buffer![100u64, 200, 1100, 1200, 2100, 2200].into_array();
2442 let values = buffer![10i32, 20, 30, 40, 50, 60].into_array();
2443 let chunk_offsets = buffer![0u64, 2, 4].into_array();
2444 let patches = Patches::new(3000, 0, indices, values, Some(chunk_offsets)).unwrap();
2445
2446 let sliced = patches.slice(150..2150).unwrap().unwrap();
2447
2448 assert_eq!(sliced.num_patches(), 4);
2449 assert_eq!(sliced.offset(), 150);
2450
2451 assert_eq!(sliced.search_index(50).unwrap(), SearchResult::Found(0)); assert_eq!(sliced.search_index(950).unwrap(), SearchResult::Found(1)); assert_eq!(sliced.search_index(1050).unwrap(), SearchResult::Found(2)); assert_eq!(sliced.search_index(1950).unwrap(), SearchResult::Found(3)); }
2456
2457 #[test]
2458 fn test_chunk_offsets_boundary_searches() {
2459 let indices = buffer![1023u64, 1024, 1025, 2047, 2048].into_array();
2460 let values = buffer![10i32, 20, 30, 40, 50].into_array();
2461 let chunk_offsets = buffer![0u64, 1, 4].into_array();
2462 let patches = Patches::new(3000, 0, indices, values, Some(chunk_offsets)).unwrap();
2463
2464 assert_eq!(patches.search_index(1023).unwrap(), SearchResult::Found(0));
2465 assert_eq!(patches.search_index(1024).unwrap(), SearchResult::Found(1));
2466 assert_eq!(patches.search_index(1025).unwrap(), SearchResult::Found(2));
2467 assert_eq!(patches.search_index(2047).unwrap(), SearchResult::Found(3));
2468 assert_eq!(patches.search_index(2048).unwrap(), SearchResult::Found(4));
2469
2470 assert_eq!(
2471 patches.search_index(1022).unwrap(),
2472 SearchResult::NotFound(0)
2473 );
2474 assert_eq!(
2475 patches.search_index(2046).unwrap(),
2476 SearchResult::NotFound(3)
2477 );
2478 }
2479
2480 #[test]
2481 fn test_chunk_offsets_slice_edge_cases() {
2482 let indices = buffer![0u64, 1, 1023, 1024, 2047, 2048].into_array();
2483 let values = buffer![10i32, 20, 30, 40, 50, 60].into_array();
2484 let chunk_offsets = buffer![0u64, 3, 5].into_array();
2485 let patches = Patches::new(3000, 0, indices, values, Some(chunk_offsets)).unwrap();
2486
2487 let sliced = patches.slice(0..10).unwrap().unwrap();
2489 assert_eq!(sliced.num_patches(), 2);
2490 assert_eq!(sliced.search_index(0).unwrap(), SearchResult::Found(0));
2491 assert_eq!(sliced.search_index(1).unwrap(), SearchResult::Found(1));
2492
2493 let sliced = patches.slice(2040..3000).unwrap().unwrap();
2495 assert_eq!(sliced.num_patches(), 2); assert_eq!(sliced.search_index(7).unwrap(), SearchResult::Found(0)); assert_eq!(sliced.search_index(8).unwrap(), SearchResult::Found(1)); }
2499
2500 #[test]
2501 fn test_chunk_offsets_slice_nested() {
2502 let indices = buffer![100u64, 200, 300, 400, 500, 600].into_array();
2503 let values = buffer![10i32, 20, 30, 40, 50, 60].into_array();
2504 let chunk_offsets = buffer![0u64].into_array();
2505 let patches = Patches::new(1000, 0, indices, values, Some(chunk_offsets)).unwrap();
2506
2507 let sliced1 = patches.slice(150..550).unwrap().unwrap();
2508 assert_eq!(sliced1.num_patches(), 4); let sliced2 = sliced1.slice(100..250).unwrap().unwrap();
2511 assert_eq!(sliced2.num_patches(), 1); assert_eq!(sliced2.offset(), 250);
2513
2514 assert_eq!(sliced2.search_index(50).unwrap(), SearchResult::Found(0)); assert_eq!(
2516 sliced2.search_index(150).unwrap(),
2517 SearchResult::NotFound(1)
2518 );
2519 }
2520
2521 #[test]
2522 fn test_nested_slice_with_dropped_first_chunk() {
2523 let indices = buffer![0u64, 1024].into_array();
2525 let values = buffer![1i32, 2].into_array();
2526 let chunk_offsets = buffer![0u64, 1].into_array();
2527 let patches = Patches::new(2048, 0, indices, values, Some(chunk_offsets)).unwrap();
2528
2529 let dropped_first = patches.slice(1024..2048).unwrap().unwrap();
2531 let resliced = dropped_first.slice(0..1024).unwrap().unwrap();
2532 assert_eq!(resliced.num_patches(), 1);
2533 }
2534
2535 #[test]
2536 fn test_index_larger_than_length() {
2537 let chunk_offsets = buffer![0u64].into_array();
2538 let indices = buffer![1023u64].into_array();
2539 let values = buffer![42i32].into_array();
2540 let patches = Patches::new(1024, 0, indices, values, Some(chunk_offsets)).unwrap();
2541 assert_eq!(
2542 patches.search_index(2048).unwrap(),
2543 SearchResult::NotFound(1)
2544 );
2545 }
2546}