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