1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::hash::Hash;
8use std::hash::Hasher;
9
10use prost::Message as _;
11use vortex_array::AnyCanonical;
12use vortex_array::Array;
13use vortex_array::ArrayEq;
14use vortex_array::ArrayHash;
15use vortex_array::ArrayId;
16use vortex_array::ArrayParts;
17use vortex_array::ArrayRef;
18use vortex_array::ArraySlots;
19use vortex_array::ArrayView;
20use vortex_array::Canonical;
21use vortex_array::EqMode;
22use vortex_array::ExecutionCtx;
23use vortex_array::ExecutionResult;
24use vortex_array::IntoArray;
25use vortex_array::arrays::BoolArray;
26use vortex_array::arrays::ConstantArray;
27use vortex_array::arrays::Primitive;
28use vortex_array::arrays::PrimitiveArray;
29use vortex_array::arrays::bool::BoolArrayExt;
30use vortex_array::buffer::BufferHandle;
31use vortex_array::builtins::ArrayBuiltins;
32use vortex_array::dtype::DType;
33use vortex_array::dtype::Nullability;
34use vortex_array::patches::PatchSlotIndices;
35use vortex_array::patches::Patches;
36use vortex_array::patches::PatchesData;
37use vortex_array::patches::PatchesMetadata;
38use vortex_array::require_child;
39use vortex_array::require_opt_child;
40use vortex_array::scalar::Scalar;
41use vortex_array::scalar::ScalarValue;
42use vortex_array::scalar_fn::fns::operators::Operator;
43use vortex_array::serde::ArrayChildren;
44use vortex_array::validity::Validity;
45use vortex_array::vtable::VTable;
46use vortex_array::vtable::ValidityVTable;
47use vortex_buffer::Buffer;
48use vortex_buffer::ByteBufferMut;
49use vortex_error::VortexExpect as _;
50use vortex_error::VortexResult;
51use vortex_error::vortex_bail;
52use vortex_error::vortex_ensure;
53use vortex_error::vortex_ensure_eq;
54use vortex_error::vortex_panic;
55use vortex_mask::AllOr;
56use vortex_mask::Mask;
57use vortex_session::VortexSession;
58use vortex_session::registry::CachedId;
59
60use crate::canonical::execute_sparse;
61use crate::rules::RULES;
62
63mod canonical;
64mod compute;
65mod kernel;
66mod ops;
67mod rules;
68mod slice;
69
70use vortex_array::aggregate_fn::AggregateFnVTable as _;
71use vortex_array::aggregate_fn::fns::is_constant::IsConstant;
72use vortex_array::aggregate_fn::fns::min_max::MinMax;
73use vortex_array::aggregate_fn::fns::nan_count::NanCount;
74use vortex_array::aggregate_fn::fns::null_count::NullCount;
75use vortex_array::aggregate_fn::fns::sum::Sum;
76use vortex_array::aggregate_fn::session::AggregateFnSessionExt;
77use vortex_array::session::ArraySessionExt;
78
79pub fn initialize(session: &VortexSession) {
84 session.arrays().register(Sparse);
85 kernel::initialize(session);
86
87 let aggregate_fns = session.aggregate_fns();
88 aggregate_fns.register_aggregate_kernel(
89 Sparse.id(),
90 Some(IsConstant.id()),
91 &compute::is_constant::SparseIsConstantKernel,
92 );
93 aggregate_fns.register_aggregate_kernel(
94 Sparse.id(),
95 Some(Sum.id()),
96 &compute::sum::SparseSumKernel,
97 );
98 aggregate_fns.register_aggregate_kernel(
99 Sparse.id(),
100 Some(MinMax.id()),
101 &compute::min_max::SparseMinMaxKernel,
102 );
103 aggregate_fns.register_aggregate_kernel(
104 Sparse.id(),
105 Some(NullCount.id()),
106 &compute::null_count::SparseNullCountKernel,
107 );
108 aggregate_fns.register_aggregate_kernel(
109 Sparse.id(),
110 Some(NanCount.id()),
111 &compute::nan_count::SparseNanCountKernel,
112 );
113}
114
115pub type SparseArray = Array<Sparse>;
117
118#[vortex_array::array_slots(Sparse)]
119pub struct SparseSlots {
120 pub patch_indices: ArrayRef,
121 pub patch_values: ArrayRef,
122 pub patch_chunk_offsets: Option<ArrayRef>,
123}
124
125pub(crate) struct SparseParts {
127 pub patches: Patches,
128 pub fill_value: Scalar,
129 pub dtype: DType,
130 pub len: usize,
131}
132
133pub(crate) trait SparseOwnedExt {
134 fn into_parts(self) -> VortexResult<SparseParts>;
135}
136
137impl SparseOwnedExt for Array<Sparse> {
138 fn into_parts(self) -> VortexResult<SparseParts> {
139 let patches = Patches::new(
140 self.len(),
141 self.patches().offset(),
142 self.as_ref().slots()[SparseSlots::PATCH_INDICES]
143 .clone()
144 .vortex_expect("indices"),
145 self.as_ref().slots()[SparseSlots::PATCH_VALUES]
146 .clone()
147 .vortex_expect("values"),
148 self.as_ref().slots()[SparseSlots::PATCH_CHUNK_OFFSETS].clone(),
149 )?;
150 Ok(SparseParts {
151 patches,
152 fill_value: self.fill_scalar().clone(),
153 dtype: self.dtype().clone(),
154 len: self.len(),
155 })
156 }
157}
158
159#[derive(Clone, prost::Message)]
160#[repr(C)]
161pub struct SparseMetadata {
162 #[prost(message, required, tag = "1")]
163 patches: PatchesMetadata,
164}
165
166impl ArrayHash for SparseData {
167 fn array_hash<H: Hasher>(&self, state: &mut H, _accuracy: EqMode) {
168 self.array_len.hash(state);
169 self.patches_data.hash(state);
170 self.fill_value.hash(state);
171 }
172}
173
174impl ArrayEq for SparseData {
175 fn array_eq(&self, other: &Self, _accuracy: EqMode) -> bool {
176 self.array_len == other.array_len
177 && self.patches_data == other.patches_data
178 && self.fill_value == other.fill_value
179 }
180}
181
182impl VTable for Sparse {
183 type TypedArrayData = SparseData;
184
185 type OperationsVTable = Self;
186 type ValidityVTable = Self;
187
188 fn id(&self) -> ArrayId {
189 static ID: CachedId = CachedId::new("vortex.sparse");
190 *ID
191 }
192
193 fn validate(
194 &self,
195 data: &Self::TypedArrayData,
196 dtype: &DType,
197 len: usize,
198 slots: &[Option<ArrayRef>],
199 ) -> VortexResult<()> {
200 let patches = SparseData::patches_from_slots(data, len, slots);
201 SparseData::validate(&patches, data.fill_scalar(), dtype, len)
202 }
203
204 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
205 1
206 }
207
208 fn buffer(array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
209 match idx {
210 0 => {
211 let fill_value_buffer =
212 ScalarValue::to_proto_bytes::<ByteBufferMut>(array.fill_value.value()).freeze();
213 BufferHandle::new_host(fill_value_buffer)
214 }
215 _ => vortex_panic!("SparseArray buffer index {idx} out of bounds"),
216 }
217 }
218
219 fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
220 match idx {
221 0 => Some("fill_value".to_string()),
222 _ => vortex_panic!("SparseArray buffer_name index {idx} out of bounds"),
223 }
224 }
225
226 fn with_buffers(
227 &self,
228 array: ArrayView<'_, Self>,
229 buffers: &[BufferHandle],
230 ) -> VortexResult<ArrayParts<Self>> {
231 vortex_array::vtable::unsupported_buffer_replacement(array, buffers)
232 }
233
234 fn serialize(
235 array: ArrayView<'_, Self>,
236 _session: &VortexSession,
237 ) -> VortexResult<Option<Vec<u8>>> {
238 let patches = array.patches().to_metadata(array.len(), array.dtype())?;
239 let metadata = SparseMetadata { patches };
240
241 Ok(Some(metadata.encode_to_vec()))
243 }
244
245 fn deserialize(
246 &self,
247 dtype: &DType,
248 len: usize,
249 metadata: &[u8],
250 buffers: &[BufferHandle],
251 children: &dyn ArrayChildren,
252 session: &VortexSession,
253 ) -> VortexResult<ArrayParts<Self>> {
254 let metadata = SparseMetadata::decode(metadata)?;
255
256 if buffers.len() != 1 {
259 vortex_bail!("Expected 1 buffer, got {}", buffers.len());
260 }
261 let scalar_bytes: &[u8] = &buffers[0].clone().try_to_host_sync()?;
262
263 let scalar_value = ScalarValue::from_proto_bytes(scalar_bytes, dtype, session)?;
264 let fill_value = Scalar::try_new(dtype.clone(), scalar_value)?;
265
266 vortex_ensure_eq!(
267 children.len(),
268 2,
269 "SparseArray expects 2 children for sparse encoding, found {}",
270 children.len()
271 );
272
273 let patch_indices = children.get(
274 0,
275 &metadata.patches.indices_dtype()?,
276 metadata.patches.len()?,
277 )?;
278 let patch_values = children.get(1, dtype, metadata.patches.len()?)?;
279
280 let patches = Patches::new(
281 len,
282 metadata.patches.offset()?,
283 patch_indices,
284 patch_values,
285 None,
286 )?;
287 let slots = SparseData::make_slots(&patches);
288 let data = SparseData::from_patches(&patches, fill_value)?;
289 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
290 }
291
292 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
293 SparseSlots::NAMES[idx].to_string()
294 }
295
296 fn reduce_parent(
297 array: ArrayView<'_, Self>,
298 parent: &ArrayRef,
299 child_idx: usize,
300 ) -> VortexResult<Option<ArrayRef>> {
301 RULES.evaluate(array, parent, child_idx)
302 }
303
304 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
305 let array = if array.patches().offset() != 0 {
309 let offset = array.patches().offset();
310 let indices = array.patch_indices();
311 let values = array.patch_values().clone();
312 let len = array.len();
313 let offset_scalar = Scalar::from(offset).cast(indices.dtype())?;
314 let resolved_indices = indices.binary(
315 ConstantArray::new(offset_scalar, indices.len()).into_array(),
316 Operator::Sub,
317 )?;
318 let patches = Patches::new(len, 0, resolved_indices.clone(), values, None)?;
319 match array.try_into_parts() {
321 Ok(mut parts) => {
322 parts.data.patches_data = PatchesData::from_patches(&patches);
323 parts.slots[SparseSlots::PATCH_INDICES] = Some(resolved_indices);
324 parts.slots[SparseSlots::PATCH_CHUNK_OFFSETS] = None;
325 unsafe { Array::from_parts_unchecked(parts) }
326 }
327 Err(array) => unsafe {
328 Sparse::new_unchecked(patches, array.fill_scalar().clone())
329 },
330 }
331 } else {
332 array
333 };
334
335 let array = require_child!(
338 array, array.patch_indices(), SparseSlots::PATCH_INDICES => Primitive
339 );
340 let array = require_child!(
341 array, array.patch_values(), SparseSlots::PATCH_VALUES => AnyCanonical
342 );
343 require_opt_child!(
344 array,
345 array.patch_chunk_offsets(),
346 SparseSlots::PATCH_CHUNK_OFFSETS => Primitive
347 );
348
349 let parts = array.into_parts()?;
350 execute_sparse(parts, ctx).map(ExecutionResult::done)
352 }
353}
354
355const PATCH_SLOTS: PatchSlotIndices = PatchSlotIndices {
356 indices: SparseSlots::PATCH_INDICES,
357 values: SparseSlots::PATCH_VALUES,
358 chunk_offsets: SparseSlots::PATCH_CHUNK_OFFSETS,
359};
360
361#[derive(Clone, Debug)]
362pub struct SparseData {
363 array_len: usize,
365 patches_data: PatchesData,
367 fill_value: Scalar,
368}
369
370impl Display for SparseData {
371 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
372 write!(f, "fill_value: {}", self.fill_value)
373 }
374}
375
376#[derive(Clone, Debug)]
377pub struct Sparse;
378
379impl Sparse {
380 pub fn try_new(
382 indices: ArrayRef,
383 values: ArrayRef,
384 len: usize,
385 fill_value: Scalar,
386 ) -> VortexResult<SparseArray> {
387 let dtype = fill_value.dtype().clone();
388 vortex_ensure!(
389 values.dtype() == &dtype,
390 "sparse values dtype {} must match fill value dtype {}",
391 values.dtype(),
392 dtype,
393 );
394 let patches = Patches::new(len, 0, indices, values, None)?;
395 let slots = SparseData::make_slots(&patches);
396 let data = SparseData::from_patches(&patches, fill_value)?;
397 Ok(unsafe {
398 Array::from_parts_unchecked(ArrayParts::new(Sparse, dtype, len, data).with_slots(slots))
399 })
400 }
401
402 pub fn try_new_from_patches(patches: Patches, fill_value: Scalar) -> VortexResult<SparseArray> {
403 let dtype = fill_value.dtype().clone();
404 let len = patches.array_len();
405 let slots = SparseData::make_slots(&patches);
406 let data = SparseData::from_patches(&patches, fill_value)?;
407 Ok(unsafe {
408 Array::from_parts_unchecked(ArrayParts::new(Sparse, dtype, len, data).with_slots(slots))
409 })
410 }
411
412 pub(crate) unsafe fn new_unchecked(patches: Patches, fill_value: Scalar) -> SparseArray {
413 let dtype = fill_value.dtype().clone();
414 let len = patches.array_len();
415 let slots = SparseData::make_slots(&patches);
416 let data = SparseData::from_patches_unchecked(&patches, fill_value);
417 unsafe {
418 Array::from_parts_unchecked(ArrayParts::new(Sparse, dtype, len, data).with_slots(slots))
419 }
420 }
421
422 pub fn encode(
424 array: &ArrayRef,
425 fill_value: Option<Scalar>,
426 ctx: &mut ExecutionCtx,
427 ) -> VortexResult<ArrayRef> {
428 SparseData::encode(array, fill_value, ctx)
429 }
430}
431
432impl SparseData {
433 pub fn validate(
434 patches: &Patches,
435 fill_value: &Scalar,
436 dtype: &DType,
437 len: usize,
438 ) -> VortexResult<()> {
439 vortex_ensure!(
440 fill_value.dtype() == dtype,
441 "fill value dtype {} does not match array dtype {}",
442 fill_value.dtype(),
443 dtype,
444 );
445 vortex_ensure!(
446 patches.array_len() == len,
447 "patches length {} does not match array length {}",
448 patches.array_len(),
449 len
450 );
451 vortex_ensure!(
452 patches.values().dtype() == dtype,
453 "patch values dtype {} does not match array dtype {}",
454 patches.values().dtype(),
455 dtype,
456 );
457 Ok(())
458 }
459
460 fn make_slots(patches: &Patches) -> ArraySlots {
461 let mut slots = ArraySlots::with_capacity(SparseSlots::COUNT);
462 PatchesData::push_slots(&mut slots, Some(patches));
463 slots
464 }
465
466 fn patches_from_slots(data: &SparseData, len: usize, slots: &[Option<ArrayRef>]) -> Patches {
468 PatchesData::patches_from_slots(Some(&data.patches_data), len, slots, PATCH_SLOTS)
469 .vortex_expect("SparseArray patch slots must be present")
470 }
471
472 pub fn try_new_from_patches(patches: Patches, fill_value: Scalar) -> VortexResult<Self> {
474 Self::from_patches(&patches, fill_value)
475 }
476
477 fn from_patches(patches: &Patches, fill_value: Scalar) -> VortexResult<Self> {
482 vortex_ensure!(
483 patches.values().dtype() == fill_value.dtype(),
484 "patch values dtype {} must match fill dtype {}",
485 patches.values().dtype(),
486 fill_value.dtype(),
487 );
488 Ok(Self::from_patches_unchecked(patches, fill_value))
489 }
490
491 fn from_patches_unchecked(patches: &Patches, fill_value: Scalar) -> Self {
493 Self {
494 array_len: patches.array_len(),
495 patches_data: PatchesData::from_patches(patches),
496 fill_value,
497 }
498 }
499
500 #[inline]
502 pub fn len(&self) -> usize {
503 self.array_len
504 }
505
506 #[inline]
508 pub fn is_empty(&self) -> bool {
509 self.array_len == 0
510 }
511
512 #[inline]
514 pub fn dtype(&self) -> &DType {
515 self.fill_scalar().dtype()
516 }
517
518 #[inline]
520 pub fn offset(&self) -> usize {
521 self.patches_data.offset()
522 }
523
524 #[inline]
525 pub fn fill_scalar(&self) -> &Scalar {
526 &self.fill_value
527 }
528
529 pub fn encode(
533 array: &ArrayRef,
534 fill_value: Option<Scalar>,
535 ctx: &mut ExecutionCtx,
536 ) -> VortexResult<ArrayRef> {
537 if let Some(fill_value) = fill_value.as_ref()
538 && !array.dtype().eq_ignore_nullability(fill_value.dtype())
539 {
540 vortex_bail!(
541 "Array and fill value types must have the same base type. got {} and {}",
542 array.dtype(),
543 fill_value.dtype()
544 )
545 }
546 let mask = array.validity()?.execute_mask(array.len(), ctx)?;
547
548 if mask.all_false() {
549 return Ok(
551 ConstantArray::new(Scalar::null(array.dtype().clone()), array.len()).into_array(),
552 );
553 } else if mask.false_count() as f64 > (0.9 * mask.len() as f64) {
554 let non_null_values = array
556 .filter(mask.clone())?
557 .execute::<Canonical>(ctx)?
558 .into_array();
559 let non_null_indices = match mask.indices() {
560 AllOr::All => {
561 unreachable!("Mask is mostly null")
563 }
564 AllOr::None => {
565 unreachable!("Mask is mostly null but not all null")
567 }
568 AllOr::Some(values) => {
569 let buffer: Buffer<u32> = values
570 .iter()
571 .map(|&v| v.try_into().vortex_expect("indices must fit in u32"))
572 .collect();
573
574 buffer.into_array()
575 }
576 };
577
578 return Sparse::try_new(
579 non_null_indices,
580 non_null_values,
581 array.len(),
582 Scalar::null(array.dtype().clone()),
583 )
584 .map(IntoArray::into_array);
585 }
586
587 let fill = if let Some(fill) = fill_value {
588 fill.cast(array.dtype())?
589 } else {
590 let primitive = array.clone().execute::<PrimitiveArray>(ctx)?;
592 let (top_pvalue, _) = primitive
593 .top_value()?
594 .vortex_expect("Non empty or all null array");
595
596 Scalar::primitive_value(top_pvalue, top_pvalue.ptype(), array.dtype().nullability())
597 };
598
599 let fill_array = ConstantArray::new(fill.clone(), array.len()).into_array();
600 let non_top_bool = array
601 .binary(fill_array.clone(), Operator::NotEq)?
602 .fill_null(Scalar::bool(true, Nullability::NonNullable))?
603 .execute::<BoolArray>(ctx)?;
604 let non_top_mask = Mask::from_buffer(non_top_bool.to_bit_buffer());
605
606 let non_top_values = array
607 .filter(non_top_mask.clone())?
608 .execute::<Canonical>(ctx)?
609 .into_array();
610
611 let indices: Buffer<u64> = match non_top_mask {
612 Mask::AllTrue(count) => {
613 (0u64..count as u64).collect()
615 }
616 Mask::AllFalse(_) => {
617 return Ok(fill_array);
619 }
620 Mask::Values(values) => values.indices().iter().map(|v| *v as u64).collect(),
621 };
622
623 Sparse::try_new(indices.into_array(), non_top_values, array.len(), fill)
624 .map(IntoArray::into_array)
625 }
626}
627
628pub trait SparseExt {
632 fn patches(&self) -> Patches;
634
635 fn resolved_patches(&self) -> VortexResult<Patches> {
637 let patches = self.patches();
638 let indices_offset = Scalar::from(patches.offset()).cast(patches.indices().dtype())?;
639 let indices = patches.indices().binary(
640 ConstantArray::new(indices_offset, patches.indices().len()).into_array(),
641 Operator::Sub,
642 )?;
643
644 Patches::new(
645 patches.array_len(),
646 0,
647 indices,
648 patches.values().clone(),
649 None,
651 )
652 }
653}
654
655impl SparseExt for ArrayView<'_, Sparse> {
656 fn patches(&self) -> Patches {
657 SparseData::patches_from_slots(self.data(), self.len(), self.slots())
658 }
659}
660
661impl SparseExt for Array<Sparse> {
662 fn patches(&self) -> Patches {
663 SparseData::patches_from_slots(self.data(), self.as_array().len(), self.slots())
664 }
665}
666
667impl ValidityVTable<Sparse> for Sparse {
668 fn validity(array: ArrayView<'_, Sparse>) -> VortexResult<Validity> {
669 let orig_patches = array.patches();
670 let validity_patches = unsafe {
671 Patches::new_unchecked(
672 orig_patches.array_len(),
673 orig_patches.offset(),
674 orig_patches.indices().clone(),
675 orig_patches
676 .values()
677 .validity()?
678 .to_array(orig_patches.values().len()),
679 orig_patches.chunk_offsets().clone(),
680 orig_patches.offset_within_chunk(),
681 )
682 };
683
684 Ok(Validity::Array(
685 unsafe { Sparse::new_unchecked(validity_patches, array.fill_value.is_valid().into()) }
686 .into_array(),
687 ))
688 }
689}
690
691#[cfg(test)]
692mod test {
693 use std::sync::LazyLock;
694
695 use itertools::Itertools;
696 use vortex_array::IntoArray;
697 use vortex_array::VortexSessionExecute;
698 use vortex_array::arrays::ConstantArray;
699 use vortex_array::arrays::PrimitiveArray;
700 use vortex_array::assert_arrays_eq;
701 use vortex_array::builtins::ArrayBuiltins;
702 use vortex_array::dtype::DType;
703 use vortex_array::dtype::Nullability;
704 use vortex_array::dtype::PType;
705 use vortex_array::scalar::Scalar;
706 use vortex_array::validity::Validity;
707 use vortex_buffer::buffer;
708 use vortex_error::VortexExpect;
709
710 use super::*;
711 use crate::Sparse;
712
713 static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
714 let session = vortex_array::array_session();
715 initialize(&session);
716 session
717 });
718
719 fn nullable_fill() -> Scalar {
720 Scalar::null(DType::Primitive(PType::I32, Nullability::Nullable))
721 }
722
723 fn non_nullable_fill() -> Scalar {
724 Scalar::from(42i32)
725 }
726
727 fn sparse_array(fill_value: Scalar) -> ArrayRef {
728 let mut values = buffer![100i32, 200, 300].into_array();
730 values = values.cast(fill_value.dtype().clone()).unwrap();
731
732 Sparse::try_new(buffer![2u64, 5, 8].into_array(), values, 10, fill_value)
733 .unwrap()
734 .into_array()
735 }
736
737 #[test]
738 pub fn test_scalar_at() {
739 let array = sparse_array(nullable_fill());
740
741 assert_eq!(
742 array
743 .execute_scalar(0, &mut SESSION.create_execution_ctx())
744 .unwrap(),
745 nullable_fill()
746 );
747 assert_eq!(
748 array
749 .execute_scalar(2, &mut SESSION.create_execution_ctx())
750 .unwrap(),
751 Scalar::from(Some(100_i32))
752 );
753 assert_eq!(
754 array
755 .execute_scalar(5, &mut SESSION.create_execution_ctx())
756 .unwrap(),
757 Scalar::from(Some(200_i32))
758 );
759 }
760
761 #[test]
762 #[should_panic(expected = "out of bounds")]
763 fn test_scalar_at_oob() {
764 let array = sparse_array(nullable_fill());
765 array
766 .execute_scalar(10, &mut SESSION.create_execution_ctx())
767 .unwrap();
768 }
769
770 #[test]
771 pub fn test_scalar_at_again() {
772 let arr = Sparse::try_new(
773 ConstantArray::new(10u32, 1).into_array(),
774 ConstantArray::new(Scalar::primitive(1234u32, Nullability::Nullable), 1).into_array(),
775 100,
776 Scalar::null(DType::Primitive(PType::U32, Nullability::Nullable)),
777 )
778 .unwrap();
779
780 assert_eq!(
781 arr.execute_scalar(10, &mut SESSION.create_execution_ctx())
782 .unwrap()
783 .as_primitive()
784 .typed_value::<u32>(),
785 Some(1234)
786 );
787 assert!(
788 arr.execute_scalar(0, &mut SESSION.create_execution_ctx())
789 .unwrap()
790 .is_null()
791 );
792 assert!(
793 arr.execute_scalar(99, &mut SESSION.create_execution_ctx())
794 .unwrap()
795 .is_null()
796 );
797 }
798
799 #[test]
800 pub fn scalar_at_sliced() {
801 let sliced = sparse_array(nullable_fill()).slice(2..7).unwrap();
802 assert_eq!(
803 usize::try_from(
804 &sliced
805 .execute_scalar(0, &mut SESSION.create_execution_ctx())
806 .unwrap()
807 )
808 .unwrap(),
809 100
810 );
811 }
812
813 #[test]
814 pub fn validity_mask_sliced_null_fill() {
815 let sliced = sparse_array(nullable_fill()).slice(2..7).unwrap();
816 assert_eq!(
817 sliced
818 .validity()
819 .unwrap()
820 .execute_mask(sliced.len(), &mut SESSION.create_execution_ctx())
821 .unwrap(),
822 Mask::from_iter(vec![true, false, false, true, false])
823 );
824 }
825
826 #[test]
827 pub fn validity_mask_sliced_nonnull_fill() {
828 let sliced = Sparse::try_new(
829 buffer![2u64, 5, 8].into_array(),
830 ConstantArray::new(
831 Scalar::null(DType::Primitive(PType::F32, Nullability::Nullable)),
832 3,
833 )
834 .into_array(),
835 10,
836 Scalar::primitive(1.0f32, Nullability::Nullable),
837 )
838 .unwrap()
839 .slice(2..7)
840 .unwrap();
841
842 assert_eq!(
843 sliced
844 .validity()
845 .unwrap()
846 .execute_mask(sliced.len(), &mut SESSION.create_execution_ctx())
847 .unwrap(),
848 Mask::from_iter(vec![false, true, true, false, true])
849 );
850 }
851
852 #[test]
853 pub fn scalar_at_sliced_twice() {
854 let sliced_once = sparse_array(nullable_fill()).slice(1..8).unwrap();
855 assert_eq!(
856 usize::try_from(
857 &sliced_once
858 .execute_scalar(1, &mut SESSION.create_execution_ctx())
859 .unwrap()
860 )
861 .unwrap(),
862 100
863 );
864
865 let sliced_twice = sliced_once.slice(1..6).unwrap();
866 assert_eq!(
867 usize::try_from(
868 &sliced_twice
869 .execute_scalar(3, &mut SESSION.create_execution_ctx())
870 .unwrap()
871 )
872 .unwrap(),
873 200
874 );
875 }
876
877 #[test]
878 pub fn sparse_validity_mask() {
879 let array = sparse_array(nullable_fill());
880 assert_eq!(
881 array
882 .validity()
883 .unwrap()
884 .execute_mask(array.len(), &mut SESSION.create_execution_ctx())
885 .unwrap()
886 .to_bit_buffer()
887 .iter()
888 .collect_vec(),
889 [
890 false, false, true, false, false, true, false, false, true, false
891 ]
892 );
893 }
894
895 #[test]
896 fn sparse_validity_mask_non_null_fill() {
897 let array = sparse_array(non_nullable_fill());
898 assert!(
899 array
900 .validity()
901 .unwrap()
902 .execute_mask(array.len(), &mut SESSION.create_execution_ctx())
903 .unwrap()
904 .all_true()
905 );
906 }
907
908 #[test]
909 #[should_panic]
910 fn test_invalid_length() {
911 let values = buffer![15_u32, 135, 13531, 42].into_array();
912 let indices = buffer![10_u64, 11, 50, 100].into_array();
913
914 Sparse::try_new(indices, values, 100, 0_u32.into()).unwrap();
915 }
916
917 #[test]
918 fn test_valid_length() {
919 let values = buffer![15_u32, 135, 13531, 42].into_array();
920 let indices = buffer![10_u64, 11, 50, 100].into_array();
921
922 Sparse::try_new(indices, values, 101, 0_u32.into()).unwrap();
923 }
924
925 #[test]
926 fn encode_with_nulls() {
927 let mut ctx = SESSION.create_execution_ctx();
928 let original = PrimitiveArray::new(
929 buffer![0i32, 1, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4],
930 Validity::from_iter(vec![
931 true, true, false, true, false, true, false, true, true, false, true, false,
932 ]),
933 );
934 let sparse = Sparse::encode(&original.clone().into_array(), None, &mut ctx)
935 .vortex_expect("Sparse::encode should succeed for test data");
936 assert_eq!(
937 sparse
938 .validity()
939 .unwrap()
940 .execute_mask(sparse.len(), &mut ctx)
941 .unwrap(),
942 Mask::from_iter(vec![
943 true, true, false, true, false, true, false, true, true, false, true, false,
944 ])
945 );
946 let sparse_primitive = sparse.execute::<PrimitiveArray>(&mut ctx).unwrap();
947 assert_arrays_eq!(sparse_primitive, original, &mut ctx);
948 }
949
950 #[test]
951 fn validity_mask_includes_null_values_when_fill_is_null() {
952 let indices = buffer![0u8, 2, 4, 6, 8].into_array();
953 let values = PrimitiveArray::from_option_iter([Some(0i16), Some(1), None, None, Some(4)])
954 .into_array();
955 let array = Sparse::try_new(indices, values, 10, Scalar::null_native::<i16>()).unwrap();
956 let actual = array
957 .validity()
958 .unwrap()
959 .execute_mask(array.len(), &mut SESSION.create_execution_ctx())
960 .unwrap();
961 let expected = Mask::from_iter([
962 true, false, true, false, false, false, false, false, true, false,
963 ]);
964
965 assert_eq!(actual, expected);
966 }
967}