1use std::cmp;
5use std::fmt::Debug;
6use std::fmt::Display;
7use std::fmt::Formatter;
8use std::hash::Hash;
9use std::hash::Hasher;
10
11use pco::ChunkConfig;
12use pco::PagingSpec;
13use pco::data_types::Number;
14use pco::data_types::NumberType;
15use pco::errors::PcoError;
16use pco::match_number_enum;
17use pco::wrapped::ChunkDecompressor;
18use pco::wrapped::FileCompressor;
19use pco::wrapped::FileDecompressor;
20use prost::Message;
21use vortex_array::Array;
22use vortex_array::ArrayEq;
23use vortex_array::ArrayHash;
24use vortex_array::ArrayId;
25use vortex_array::ArrayParts;
26use vortex_array::ArrayRef;
27use vortex_array::ArrayView;
28use vortex_array::EqMode;
29use vortex_array::ExecutionCtx;
30use vortex_array::ExecutionResult;
31use vortex_array::IntoArray;
32use vortex_array::TypedArrayRef;
33use vortex_array::array_slots;
34use vortex_array::arrays::Primitive;
35use vortex_array::arrays::PrimitiveArray;
36use vortex_array::buffer::BufferHandle;
37use vortex_array::dtype::DType;
38use vortex_array::dtype::PType;
39use vortex_array::dtype::half;
40use vortex_array::scalar::Scalar;
41use vortex_array::serde::ArrayChildren;
42use vortex_array::validity::Validity;
43use vortex_array::vtable::OperationsVTable;
44use vortex_array::vtable::VTable;
45use vortex_array::vtable::ValidityVTable;
46use vortex_array::vtable::child_to_validity;
47use vortex_array::vtable::validity_to_child;
48use vortex_buffer::BufferMut;
49use vortex_buffer::ByteBuffer;
50use vortex_buffer::ByteBufferMut;
51use vortex_error::VortexError;
52use vortex_error::VortexResult;
53use vortex_error::vortex_bail;
54use vortex_error::vortex_ensure;
55use vortex_error::vortex_err;
56use vortex_session::VortexSession;
57use vortex_session::registry::CachedId;
58
59use crate::PcoChunkInfo;
60use crate::PcoMetadata;
61use crate::PcoPageInfo;
62
63const VALUES_PER_CHUNK: usize = pco::DEFAULT_MAX_PAGE_N;
82
83pub type PcoArray = Array<Pco>;
85
86impl ArrayHash for PcoData {
87 fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: EqMode) {
88 self.unsliced_n_rows.hash(state);
89 self.slice_start.hash(state);
90 self.slice_stop.hash(state);
91 for chunk_meta in &self.chunk_metas {
93 chunk_meta.array_hash(state, accuracy);
94 }
95 for page in &self.pages {
96 page.array_hash(state, accuracy);
97 }
98 }
99}
100
101impl ArrayEq for PcoData {
102 fn array_eq(&self, other: &Self, accuracy: EqMode) -> bool {
103 if self.unsliced_n_rows != other.unsliced_n_rows
104 || self.slice_start != other.slice_start
105 || self.slice_stop != other.slice_stop
106 || self.chunk_metas.len() != other.chunk_metas.len()
107 || self.pages.len() != other.pages.len()
108 {
109 return false;
110 }
111 for (a, b) in self.chunk_metas.iter().zip(&other.chunk_metas) {
112 if !a.array_eq(b, accuracy) {
113 return false;
114 }
115 }
116 for (a, b) in self.pages.iter().zip(&other.pages) {
117 if !a.array_eq(b, accuracy) {
118 return false;
119 }
120 }
121 true
122 }
123}
124
125impl VTable for Pco {
126 type TypedArrayData = PcoData;
127
128 type OperationsVTable = Self;
129 type ValidityVTable = Self;
130
131 fn id(&self) -> ArrayId {
132 static ID: CachedId = CachedId::new("vortex.pco");
133 *ID
134 }
135
136 fn validate(
137 &self,
138 data: &PcoData,
139 dtype: &DType,
140 len: usize,
141 slots: &[Option<ArrayRef>],
142 ) -> VortexResult<()> {
143 let validity = child_to_validity(
144 PcoSlotsView::from_slots(slots).validity,
145 dtype.nullability(),
146 );
147 data.validate(dtype, len, &validity)
148 }
149
150 fn nbuffers(array: ArrayView<'_, Self>) -> usize {
151 array.chunk_metas.len() + array.pages.len()
152 }
153
154 fn buffer(array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
155 if idx < array.chunk_metas.len() {
156 BufferHandle::new_host(array.chunk_metas[idx].clone())
157 } else {
158 let page_idx = idx - array.chunk_metas.len();
159 BufferHandle::new_host(array.pages[page_idx].clone())
160 }
161 }
162
163 fn buffer_name(array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
164 if idx < array.chunk_metas.len() {
165 Some(format!("chunk_meta_{idx}"))
166 } else {
167 Some(format!("page_{}", idx - array.chunk_metas.len()))
168 }
169 }
170
171 fn with_buffers(
172 &self,
173 array: ArrayView<'_, Self>,
174 buffers: &[BufferHandle],
175 ) -> VortexResult<ArrayParts<Self>> {
176 let mut data = array.data().clone();
177 let chunk_metas_len = data.metadata.chunks.len();
178 vortex_ensure!(buffers.len() >= chunk_metas_len);
179 data.chunk_metas = buffers[..chunk_metas_len]
180 .iter()
181 .map(|buffer| buffer.clone().try_to_host_sync())
182 .collect::<VortexResult<Vec<_>>>()?;
183 data.pages = buffers[chunk_metas_len..]
184 .iter()
185 .map(|buffer| buffer.clone().try_to_host_sync())
186 .collect::<VortexResult<Vec<_>>>()?;
187 Ok(
188 ArrayParts::new(self.clone(), array.dtype().clone(), array.len(), data)
189 .with_slots(array.slots().iter().cloned().collect()),
190 )
191 }
192
193 fn serialize(
194 array: ArrayView<'_, Self>,
195 _session: &VortexSession,
196 ) -> VortexResult<Option<Vec<u8>>> {
197 Ok(Some(array.metadata.clone().encode_to_vec()))
198 }
199
200 fn deserialize(
201 &self,
202 dtype: &DType,
203 len: usize,
204 metadata: &[u8],
205 buffers: &[BufferHandle],
206 children: &dyn ArrayChildren,
207 _session: &VortexSession,
208 ) -> VortexResult<ArrayParts<Self>> {
209 let metadata = PcoMetadata::decode(metadata)?;
210 let validity = if children.is_empty() {
211 Validity::from(dtype.nullability())
212 } else if children.len() == 1 {
213 let validity = children.get(0, &Validity::DTYPE, len)?;
214 Validity::Array(validity)
215 } else {
216 vortex_bail!("PcoArray expected 0 or 1 child, got {}", children.len());
217 };
218
219 vortex_ensure!(buffers.len() >= metadata.chunks.len());
220 let chunk_metas = buffers[..metadata.chunks.len()]
221 .iter()
222 .map(|b| b.clone().try_to_host_sync())
223 .collect::<VortexResult<Vec<_>>>()?;
224 let pages = buffers[metadata.chunks.len()..]
225 .iter()
226 .map(|b| b.clone().try_to_host_sync())
227 .collect::<VortexResult<Vec<_>>>()?;
228
229 let expected_n_pages = metadata
230 .chunks
231 .iter()
232 .map(|info| info.pages.len())
233 .sum::<usize>();
234 vortex_ensure!(pages.len() == expected_n_pages);
235
236 let slots = PcoSlots {
237 validity: validity_to_child(&validity, len),
238 }
239 .into_slots();
240 let data =
243 unsafe { PcoData::new_unchecked(chunk_metas, pages, dtype.as_ptype(), metadata, len) };
244 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
245 }
246
247 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
248 PcoSlots::NAMES[idx].to_string()
249 }
250
251 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
252 let unsliced_validity = array.unsliced_validity();
253 Ok(ExecutionResult::done(
254 array
255 .data()
256 .decompress(&unsliced_validity, ctx)?
257 .into_array(),
258 ))
259 }
260
261 fn reduce_parent(
262 array: ArrayView<'_, Self>,
263 parent: &ArrayRef,
264 child_idx: usize,
265 ) -> VortexResult<Option<ArrayRef>> {
266 crate::rules::RULES.evaluate(array, parent, child_idx)
267 }
268}
269
270pub(crate) fn number_type_from_dtype(dtype: &DType) -> NumberType {
271 number_type_from_ptype(dtype.as_ptype())
272}
273
274pub(crate) fn number_type_from_ptype(ptype: PType) -> NumberType {
275 match ptype {
276 PType::F16 => NumberType::F16,
277 PType::F32 => NumberType::F32,
278 PType::F64 => NumberType::F64,
279 PType::I16 => NumberType::I16,
280 PType::I32 => NumberType::I32,
281 PType::I64 => NumberType::I64,
282 PType::U16 => NumberType::U16,
283 PType::U32 => NumberType::U32,
284 PType::U64 => NumberType::U64,
285 _ => unreachable!("PType not supported by Pco: {:?}", ptype),
286 }
287}
288
289fn collect_valid(
290 parray: ArrayView<'_, Primitive>,
291 ctx: &mut ExecutionCtx,
292) -> VortexResult<PrimitiveArray> {
293 let mask = parray
294 .array()
295 .validity()?
296 .execute_mask(parray.array().len(), ctx)?;
297 let result = parray
298 .array()
299 .filter(mask)?
300 .execute::<PrimitiveArray>(ctx)?;
301 Ok(result)
302}
303
304pub(crate) fn vortex_err_from_pco(err: PcoError) -> VortexError {
305 use pco::errors::ErrorKind::*;
306 match err.kind {
307 Io(io_kind) => VortexError::from(std::io::Error::new(io_kind, err.message)),
308 InvalidArgument => vortex_err!(InvalidArgument: "{}", err.message),
309 other => vortex_err!("Pco {:?} error: {}", other, err.message),
310 }
311}
312
313#[derive(Clone, Debug)]
314pub struct Pco;
316
317impl Pco {
318 pub fn try_new(dtype: DType, data: PcoData, validity: Validity) -> VortexResult<PcoArray> {
320 let len = data.len();
321 data.validate(&dtype, len, &validity)?;
322 Ok(unsafe { Self::new_unchecked(dtype, data, validity) })
324 }
325
326 pub unsafe fn new_unchecked(dtype: DType, data: PcoData, validity: Validity) -> PcoArray {
333 let len = data.len();
334 let slots = PcoSlots {
335 validity: validity_to_child(&validity, data.unsliced_n_rows()),
336 }
337 .into_slots();
338 unsafe {
339 Array::from_parts_unchecked(ArrayParts::new(Pco, dtype, len, data).with_slots(slots))
340 }
341 }
342
343 pub fn from_primitive(
345 parray: ArrayView<'_, Primitive>,
346 level: usize,
347 values_per_page: usize,
348 ctx: &mut ExecutionCtx,
349 ) -> VortexResult<PcoArray> {
350 let dtype = parray.dtype().clone();
351 let validity = parray.validity()?;
352 let data = PcoData::from_primitive(parray, level, values_per_page, ctx)?;
353 Self::try_new(dtype, data, validity)
354 }
355}
356
357#[array_slots(Pco)]
358pub struct PcoSlots {
359 #[slot(0)]
361 pub validity: Option<ArrayRef>,
362}
363
364pub trait PcoArrayExt: PcoArraySlotsExt {
366 fn unsliced_validity(&self) -> Validity {
368 child_to_validity(
369 self.as_ref().slots()[PcoSlots::VALIDITY].as_ref(),
370 self.as_ref().dtype().nullability(),
371 )
372 }
373}
374impl<T: TypedArrayRef<Pco>> PcoArrayExt for T {}
375
376#[derive(Clone, Debug)]
377pub struct PcoData {
379 pub(crate) chunk_metas: Vec<ByteBuffer>,
380 pub(crate) pages: Vec<ByteBuffer>,
381 pub(crate) metadata: PcoMetadata,
382 ptype: PType,
383 unsliced_n_rows: usize,
384 slice_start: usize,
385 slice_stop: usize,
386}
387
388impl Display for PcoData {
389 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
390 write!(
391 f,
392 "ptype: {}, nrows: {}, slice: {}..{}",
393 self.ptype, self.unsliced_n_rows, self.slice_start, self.slice_stop
394 )
395 }
396}
397
398impl PcoData {
399 pub fn validate(&self, dtype: &DType, len: usize, validity: &Validity) -> VortexResult<()> {
401 let _ = number_type_from_ptype(self.ptype);
402 vortex_ensure!(
403 dtype.as_ptype() == self.ptype,
404 "expected ptype {}, got {}",
405 self.ptype,
406 dtype.as_ptype()
407 );
408 vortex_ensure!(
409 dtype.nullability() == validity.nullability(),
410 "expected nullability {}, got {}",
411 validity.nullability(),
412 dtype.nullability()
413 );
414 vortex_ensure!(
415 self.slice_start <= self.slice_stop && self.slice_stop <= self.unsliced_n_rows,
416 "invalid slice range {}..{} for {} rows",
417 self.slice_start,
418 self.slice_stop,
419 self.unsliced_n_rows
420 );
421 vortex_ensure!(
422 self.slice_stop - self.slice_start == len,
423 "expected len {len}, got {}",
424 self.slice_stop - self.slice_start
425 );
426 if let Some(validity_len) = validity.maybe_len() {
427 vortex_ensure!(
428 validity_len == self.unsliced_n_rows,
429 "expected validity len {}, got {}",
430 self.unsliced_n_rows,
431 validity_len
432 );
433 }
434 vortex_ensure!(
435 self.chunk_metas.len() == self.metadata.chunks.len(),
436 "expected {} chunk metas, got {}",
437 self.metadata.chunks.len(),
438 self.chunk_metas.len()
439 );
440 vortex_ensure!(
441 self.pages.len()
442 == self
443 .metadata
444 .chunks
445 .iter()
446 .map(|chunk| chunk.pages.len())
447 .sum::<usize>(),
448 "page count does not match metadata"
449 );
450
451 let mut n_values = 0usize;
452 for (chunk_idx, chunk) in self.metadata.chunks.iter().enumerate() {
453 let mut chunk_n_values = 0usize;
454 for page in &chunk.pages {
455 let page_n_values = page.n_values as usize;
456 vortex_ensure!(
457 page_n_values != 0,
458 "Pco chunk {chunk_idx} contains an empty page"
459 );
460 chunk_n_values = chunk_n_values.checked_add(page_n_values).ok_or_else(|| {
461 vortex_err!("Pco chunk {chunk_idx} value count overflows usize")
462 })?;
463 }
464 vortex_ensure!(
465 chunk_n_values <= VALUES_PER_CHUNK,
466 "Pco chunk {chunk_idx} contains {chunk_n_values} values, exceeding the maximum of {VALUES_PER_CHUNK}"
467 );
468 n_values = n_values
469 .checked_add(chunk_n_values)
470 .ok_or_else(|| vortex_err!("Pco value count overflows usize"))?;
471 }
472 vortex_ensure!(
473 n_values <= self.unsliced_n_rows,
474 "Pco contains {n_values} values for only {} rows",
475 self.unsliced_n_rows
476 );
477 if validity.definitely_no_nulls() {
478 vortex_ensure!(
479 n_values == self.unsliced_n_rows,
480 "Pco contains {n_values} values for {} non-null rows",
481 self.unsliced_n_rows
482 );
483 } else if validity.definitely_all_null() {
484 vortex_ensure!(n_values == 0, "Pco contains values for an all-null array");
485 }
486 Ok(())
487 }
488
489 pub unsafe fn new_unchecked(
496 chunk_metas: Vec<ByteBuffer>,
497 pages: Vec<ByteBuffer>,
498 ptype: PType,
499 metadata: PcoMetadata,
500 len: usize,
501 ) -> Self {
502 Self {
503 chunk_metas,
504 pages,
505 metadata,
506 ptype,
507 unsliced_n_rows: len,
508 slice_start: 0,
509 slice_stop: len,
510 }
511 }
512
513 pub fn from_primitive(
515 parray: ArrayView<'_, Primitive>,
516 level: usize,
517 values_per_page: usize,
518 ctx: &mut ExecutionCtx,
519 ) -> VortexResult<Self> {
520 Self::from_primitive_with_values_per_chunk(
521 parray,
522 level,
523 VALUES_PER_CHUNK,
524 values_per_page,
525 ctx,
526 )
527 }
528
529 pub(crate) fn from_primitive_with_values_per_chunk(
530 parray: ArrayView<'_, Primitive>,
531 level: usize,
532 values_per_chunk: usize,
533 values_per_page: usize,
534 ctx: &mut ExecutionCtx,
535 ) -> VortexResult<Self> {
536 let number_type = number_type_from_dtype(parray.dtype());
537 let values_per_page = if values_per_page == 0 {
538 values_per_chunk
539 } else {
540 values_per_page
541 };
542
543 let chunk_config = ChunkConfig::default()
545 .with_compression_level(level)
546 .with_paging_spec(PagingSpec::EqualPagesUpTo(values_per_page));
547
548 let values = collect_valid(parray, ctx)?;
549 let n_values = values.len();
550
551 let fc = FileCompressor::default();
552 let mut header = vec![];
553 fc.write_header(&mut header).map_err(vortex_err_from_pco)?;
554
555 let mut chunk_meta_buffers = vec![]; let mut chunk_infos = vec![]; let mut page_buffers = vec![];
558 for chunk_start in (0..n_values).step_by(values_per_chunk) {
559 let chunk_end = cmp::min(n_values, chunk_start + values_per_chunk);
560 let mut cc = match_number_enum!(
561 number_type,
562 NumberType<T> => {
563 let values = values.to_buffer::<T>();
564 let chunk = &values.as_slice()[chunk_start..chunk_end];
565 fc
566 .chunk_compressor(chunk, &chunk_config)
567 .map_err(vortex_err_from_pco)?
568 }
569 );
570
571 let mut chunk_meta_buffer = ByteBufferMut::with_capacity(cc.meta_size_hint());
572 cc.write_meta(&mut chunk_meta_buffer)
573 .map_err(vortex_err_from_pco)?;
574 chunk_meta_buffers.push(chunk_meta_buffer.freeze());
575
576 let mut page_infos = vec![];
577 for (page_idx, page_n_values) in cc.n_per_page().into_iter().enumerate() {
578 let mut page = ByteBufferMut::with_capacity(cc.page_size_hint(page_idx));
579 cc.write_page(page_idx, &mut page)
580 .map_err(vortex_err_from_pco)?;
581 page_buffers.push(page.freeze());
582 page_infos.push(PcoPageInfo {
583 n_values: u32::try_from(page_n_values)?,
584 });
585 }
586 chunk_infos.push(PcoChunkInfo { pages: page_infos })
587 }
588
589 let metadata = PcoMetadata {
590 header,
591 chunks: chunk_infos,
592 };
593 Ok(unsafe {
596 PcoData::new_unchecked(
597 chunk_meta_buffers,
598 page_buffers,
599 parray.dtype().as_ptype(),
600 metadata,
601 parray.len(),
602 )
603 })
604 }
605
606 pub fn from_array(
612 array: ArrayRef,
613 level: usize,
614 nums_per_page: usize,
615 ctx: &mut ExecutionCtx,
616 ) -> VortexResult<Self> {
617 let parray = array.try_downcast::<Primitive>().map_err(|a| {
618 vortex_err!(
619 "Pco can only encode primitive arrays, got {}",
620 a.encoding_id()
621 )
622 })?;
623 Self::from_primitive(parray.as_view(), level, nums_per_page, ctx)
624 }
625
626 pub(crate) fn decompress(
628 &self,
629 unsliced_validity: &Validity,
630 ctx: &mut ExecutionCtx,
631 ) -> VortexResult<PrimitiveArray> {
632 let number_type = number_type_from_ptype(self.ptype);
635 let values_byte_buffer = match_number_enum!(
636 number_type,
637 NumberType<T> => {
638 self.decompress_values_typed::<T>(unsliced_validity, ctx)?
639 }
640 );
641
642 Ok(PrimitiveArray::from_values_byte_buffer(
643 values_byte_buffer,
644 self.ptype,
645 unsliced_validity.slice(self.slice_start..self.slice_stop)?,
646 self.slice_stop - self.slice_start,
647 ctx,
648 ))
649 }
650
651 fn decompress_values_typed<T: Number>(
652 &self,
653 unsliced_validity: &Validity,
654 ctx: &mut ExecutionCtx,
655 ) -> VortexResult<ByteBuffer> {
656 let slice_value_indices = unsliced_validity
658 .execute_mask(self.unsliced_n_rows, ctx)?
659 .valid_counts_for_indices(&[self.slice_start, self.slice_stop]);
660 let slice_value_start = slice_value_indices[0];
661 let slice_value_stop = slice_value_indices[1];
662 let slice_n_values = slice_value_stop - slice_value_start;
663
664 let (fd, _) =
667 FileDecompressor::new(self.metadata.header.as_slice()).map_err(vortex_err_from_pco)?;
668 let mut decompressed_values =
669 BufferMut::<T>::with_capacity(slice_n_values.min(VALUES_PER_CHUNK));
670 let mut page_idx = 0;
673 let mut page_value_start = 0usize;
674 let mut n_skipped_values = 0;
675 for (chunk_info, chunk_meta) in self.metadata.chunks.iter().zip(&self.chunk_metas) {
676 let mut chunk_decompressor: Option<ChunkDecompressor<T>> = None;
678 for page_info in &chunk_info.pages {
679 let page_n_values = page_info.n_values as usize;
680 let page_value_stop = page_value_start + page_n_values;
681
682 if page_value_start >= slice_value_stop {
683 break;
684 }
685
686 if page_value_stop > slice_value_start {
687 let old_len = decompressed_values.len();
689 let new_len = old_len + page_n_values;
690 decompressed_values.reserve(page_n_values);
691 unsafe {
692 decompressed_values.set_len(new_len);
693 }
694 let page: &[u8] = self
695 .pages
696 .get(page_idx)
697 .ok_or_else(|| vortex_err!("Missing Pco page {page_idx}"))?
698 .as_ref();
699
700 let mut cd = match chunk_decompressor.take() {
701 Some(d) => d,
702 None => {
703 let (new_cd, _) = fd
704 .chunk_decompressor(chunk_meta.as_ref())
705 .map_err(vortex_err_from_pco)?;
706 new_cd
707 }
708 };
709
710 let mut pd = cd
711 .page_decompressor(page, page_n_values)
712 .map_err(vortex_err_from_pco)?;
713 pd.read(&mut decompressed_values[old_len..new_len])
714 .map_err(vortex_err_from_pco)?;
715
716 chunk_decompressor = Some(cd);
717 } else {
718 n_skipped_values += page_n_values;
719 }
720
721 page_value_start = page_value_stop;
722 page_idx += 1;
723 }
724 }
725
726 let value_offset = slice_value_start - n_skipped_values;
730 let value_stop = value_offset + slice_n_values;
731 vortex_ensure!(
732 value_stop <= decompressed_values.len(),
733 "Pco contains {} decompressed values, but the requested range ends at {value_stop}",
734 decompressed_values.len()
735 );
736 Ok(decompressed_values
737 .freeze()
738 .slice(value_offset..value_stop)
739 .into_byte_buffer())
740 }
741
742 pub(crate) fn _slice(&self, start: usize, stop: usize) -> Self {
743 PcoData {
744 slice_start: self.slice_start + start,
745 slice_stop: self.slice_start + stop,
746 ..self.clone()
747 }
748 }
749
750 pub fn len(&self) -> usize {
752 self.slice_stop - self.slice_start
753 }
754
755 pub fn is_empty(&self) -> bool {
757 self.slice_stop == self.slice_start
758 }
759
760 pub(crate) fn slice_start(&self) -> usize {
761 self.slice_start
762 }
763
764 pub(crate) fn slice_stop(&self) -> usize {
765 self.slice_stop
766 }
767
768 pub(crate) fn unsliced_n_rows(&self) -> usize {
769 self.unsliced_n_rows
770 }
771}
772
773impl ValidityVTable<Pco> for Pco {
774 fn validity(array: ArrayView<'_, Pco>) -> VortexResult<Validity> {
775 array
776 .unsliced_validity()
777 .slice(array.slice_start()..array.slice_stop())
778 }
779}
780
781impl OperationsVTable<Pco> for Pco {
782 fn scalar_at(
783 array: ArrayView<'_, Pco>,
784 index: usize,
785 ctx: &mut ExecutionCtx,
786 ) -> VortexResult<Scalar> {
787 let unsliced_validity = array.unsliced_validity();
788 array
789 ._slice(index, index + 1)
790 .decompress(&unsliced_validity, ctx)?
791 .into_array()
792 .execute_scalar(0, ctx)
793 }
794}
795
796#[cfg(test)]
797mod tests {
798 use vortex_array::IntoArray;
799 use vortex_array::VortexSessionExecute;
800 use vortex_array::array_session;
801 use vortex_array::arrays::PrimitiveArray;
802 use vortex_array::assert_arrays_eq;
803 use vortex_array::validity::Validity;
804 use vortex_buffer::buffer;
805 use vortex_error::VortexResult;
806
807 use super::VALUES_PER_CHUNK;
808 use crate::Pco;
809
810 #[test]
811 fn test_slice_nullable() {
812 let mut ctx = array_session().create_execution_ctx();
813 let values = PrimitiveArray::new(
815 buffer![10u32, 20, 30, 40, 50, 60],
816 Validity::from_iter([false, true, true, true, true, false]),
817 );
818 let pco = Pco::from_primitive(values.as_view(), 0, 128, &mut ctx).unwrap();
819 assert_arrays_eq!(
820 pco,
821 PrimitiveArray::from_option_iter([
822 None,
823 Some(20u32),
824 Some(30),
825 Some(40),
826 Some(50),
827 None
828 ]),
829 &mut ctx
830 );
831
832 let sliced = pco.slice(1..5).unwrap();
834 let expected =
835 PrimitiveArray::from_option_iter([Some(20u32), Some(30), Some(40), Some(50)])
836 .into_array();
837 assert_arrays_eq!(sliced, expected, &mut ctx);
838 }
839
840 #[test]
841 fn test_decompress_bounds_initial_allocation() -> VortexResult<()> {
842 let mut ctx = array_session().create_execution_ctx();
843 let values = PrimitiveArray::from_iter([42u32]);
844 let pco = Pco::from_primitive(values.as_view(), 0, 128, &mut ctx)?;
845 let mut data = pco.data().clone();
846
847 data.unsliced_n_rows = usize::MAX;
850 data.slice_stop = usize::MAX;
851
852 let result = data.decompress_values_typed::<u32>(&Validity::NonNullable, &mut ctx);
853 assert!(result.is_err());
854 Ok(())
855 }
856
857 #[test]
858 fn test_validate_rejects_oversized_chunk() -> VortexResult<()> {
859 let mut ctx = array_session().create_execution_ctx();
860 let values = PrimitiveArray::from_iter([42u32]);
861 let pco = Pco::from_primitive(values.as_view(), 0, 128, &mut ctx)?;
862 let mut data = pco.data().clone();
863 data.metadata.chunks[0].pages[0].n_values = u32::try_from(VALUES_PER_CHUNK + 1)?;
864
865 assert!(
866 data.validate(pco.dtype(), pco.len(), &Validity::NonNullable)
867 .is_err()
868 );
869 Ok(())
870 }
871}