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::arrays::Primitive;
33use vortex_array::arrays::PrimitiveArray;
34use vortex_array::buffer::BufferHandle;
35use vortex_array::dtype::DType;
36use vortex_array::dtype::PType;
37use vortex_array::dtype::half;
38use vortex_array::scalar::Scalar;
39use vortex_array::serde::ArrayChildren;
40use vortex_array::smallvec::smallvec;
41use vortex_array::validity::Validity;
42use vortex_array::vtable::OperationsVTable;
43use vortex_array::vtable::VTable;
44use vortex_array::vtable::ValidityVTable;
45use vortex_array::vtable::child_to_validity;
46use vortex_array::vtable::validity_to_child;
47use vortex_buffer::BufferMut;
48use vortex_buffer::ByteBuffer;
49use vortex_buffer::ByteBufferMut;
50use vortex_error::VortexError;
51use vortex_error::VortexResult;
52use vortex_error::vortex_bail;
53use vortex_error::vortex_ensure;
54use vortex_error::vortex_err;
55use vortex_session::VortexSession;
56use vortex_session::registry::CachedId;
57
58use crate::PcoChunkInfo;
59use crate::PcoMetadata;
60use crate::PcoPageInfo;
61
62const VALUES_PER_CHUNK: usize = pco::DEFAULT_MAX_PAGE_N;
81
82pub type PcoArray = Array<Pco>;
84
85impl ArrayHash for PcoData {
86 fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: EqMode) {
87 self.unsliced_n_rows.hash(state);
88 self.slice_start.hash(state);
89 self.slice_stop.hash(state);
90 for chunk_meta in &self.chunk_metas {
92 chunk_meta.array_hash(state, accuracy);
93 }
94 for page in &self.pages {
95 page.array_hash(state, accuracy);
96 }
97 }
98}
99
100impl ArrayEq for PcoData {
101 fn array_eq(&self, other: &Self, accuracy: EqMode) -> bool {
102 if self.unsliced_n_rows != other.unsliced_n_rows
103 || self.slice_start != other.slice_start
104 || self.slice_stop != other.slice_stop
105 || self.chunk_metas.len() != other.chunk_metas.len()
106 || self.pages.len() != other.pages.len()
107 {
108 return false;
109 }
110 for (a, b) in self.chunk_metas.iter().zip(&other.chunk_metas) {
111 if !a.array_eq(b, accuracy) {
112 return false;
113 }
114 }
115 for (a, b) in self.pages.iter().zip(&other.pages) {
116 if !a.array_eq(b, accuracy) {
117 return false;
118 }
119 }
120 true
121 }
122}
123
124impl VTable for Pco {
125 type TypedArrayData = PcoData;
126
127 type OperationsVTable = Self;
128 type ValidityVTable = Self;
129
130 fn id(&self) -> ArrayId {
131 static ID: CachedId = CachedId::new("vortex.pco");
132 *ID
133 }
134
135 fn validate(
136 &self,
137 data: &PcoData,
138 dtype: &DType,
139 len: usize,
140 slots: &[Option<ArrayRef>],
141 ) -> VortexResult<()> {
142 let validity = child_to_validity(slots[0].as_ref(), dtype.nullability());
143 data.validate(dtype, len, &validity)
144 }
145
146 fn nbuffers(array: ArrayView<'_, Self>) -> usize {
147 array.chunk_metas.len() + array.pages.len()
148 }
149
150 fn buffer(array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
151 if idx < array.chunk_metas.len() {
152 BufferHandle::new_host(array.chunk_metas[idx].clone())
153 } else {
154 let page_idx = idx - array.chunk_metas.len();
155 BufferHandle::new_host(array.pages[page_idx].clone())
156 }
157 }
158
159 fn buffer_name(array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
160 if idx < array.chunk_metas.len() {
161 Some(format!("chunk_meta_{idx}"))
162 } else {
163 Some(format!("page_{}", idx - array.chunk_metas.len()))
164 }
165 }
166
167 fn with_buffers(
168 &self,
169 array: ArrayView<'_, Self>,
170 buffers: &[BufferHandle],
171 ) -> VortexResult<ArrayParts<Self>> {
172 let mut data = array.data().clone();
173 let chunk_metas_len = data.metadata.chunks.len();
174 vortex_ensure!(buffers.len() >= chunk_metas_len);
175 data.chunk_metas = buffers[..chunk_metas_len]
176 .iter()
177 .map(|buffer| buffer.clone().try_to_host_sync())
178 .collect::<VortexResult<Vec<_>>>()?;
179 data.pages = buffers[chunk_metas_len..]
180 .iter()
181 .map(|buffer| buffer.clone().try_to_host_sync())
182 .collect::<VortexResult<Vec<_>>>()?;
183 Ok(
184 ArrayParts::new(self.clone(), array.dtype().clone(), array.len(), data)
185 .with_slots(array.slots().iter().cloned().collect()),
186 )
187 }
188
189 fn serialize(
190 array: ArrayView<'_, Self>,
191 _session: &VortexSession,
192 ) -> VortexResult<Option<Vec<u8>>> {
193 Ok(Some(array.metadata.clone().encode_to_vec()))
194 }
195
196 fn deserialize(
197 &self,
198 dtype: &DType,
199 len: usize,
200 metadata: &[u8],
201 buffers: &[BufferHandle],
202 children: &dyn ArrayChildren,
203 _session: &VortexSession,
204 ) -> VortexResult<ArrayParts<Self>> {
205 let metadata = PcoMetadata::decode(metadata)?;
206 let validity = if children.is_empty() {
207 Validity::from(dtype.nullability())
208 } else if children.len() == 1 {
209 let validity = children.get(0, &Validity::DTYPE, len)?;
210 Validity::Array(validity)
211 } else {
212 vortex_bail!("PcoArray expected 0 or 1 child, got {}", children.len());
213 };
214
215 vortex_ensure!(buffers.len() >= metadata.chunks.len());
216 let chunk_metas = buffers[..metadata.chunks.len()]
217 .iter()
218 .map(|b| b.clone().try_to_host_sync())
219 .collect::<VortexResult<Vec<_>>>()?;
220 let pages = buffers[metadata.chunks.len()..]
221 .iter()
222 .map(|b| b.clone().try_to_host_sync())
223 .collect::<VortexResult<Vec<_>>>()?;
224
225 let expected_n_pages = metadata
226 .chunks
227 .iter()
228 .map(|info| info.pages.len())
229 .sum::<usize>();
230 vortex_ensure!(pages.len() == expected_n_pages);
231
232 let slots = smallvec![validity_to_child(&validity, len)];
233 let data = PcoData::new(chunk_metas, pages, dtype.as_ptype(), metadata, len);
234 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
235 }
236
237 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
238 SLOT_NAMES[idx].to_string()
239 }
240
241 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
242 let unsliced_validity = child_to_validity(
243 array.as_ref().slots()[0].as_ref(),
244 array.dtype().nullability(),
245 );
246 Ok(ExecutionResult::done(
247 array
248 .data()
249 .decompress(&unsliced_validity, ctx)?
250 .into_array(),
251 ))
252 }
253
254 fn reduce_parent(
255 array: ArrayView<'_, Self>,
256 parent: &ArrayRef,
257 child_idx: usize,
258 ) -> VortexResult<Option<ArrayRef>> {
259 crate::rules::RULES.evaluate(array, parent, child_idx)
260 }
261}
262
263pub(crate) fn number_type_from_dtype(dtype: &DType) -> NumberType {
264 number_type_from_ptype(dtype.as_ptype())
265}
266
267pub(crate) fn number_type_from_ptype(ptype: PType) -> NumberType {
268 match ptype {
269 PType::F16 => NumberType::F16,
270 PType::F32 => NumberType::F32,
271 PType::F64 => NumberType::F64,
272 PType::I16 => NumberType::I16,
273 PType::I32 => NumberType::I32,
274 PType::I64 => NumberType::I64,
275 PType::U16 => NumberType::U16,
276 PType::U32 => NumberType::U32,
277 PType::U64 => NumberType::U64,
278 _ => unreachable!("PType not supported by Pco: {:?}", ptype),
279 }
280}
281
282fn collect_valid(
283 parray: ArrayView<'_, Primitive>,
284 ctx: &mut ExecutionCtx,
285) -> VortexResult<PrimitiveArray> {
286 let mask = parray
287 .array()
288 .validity()?
289 .execute_mask(parray.array().len(), ctx)?;
290 let result = parray
291 .array()
292 .filter(mask)?
293 .execute::<PrimitiveArray>(ctx)?;
294 Ok(result)
295}
296
297pub(crate) fn vortex_err_from_pco(err: PcoError) -> VortexError {
298 use pco::errors::ErrorKind::*;
299 match err.kind {
300 Io(io_kind) => VortexError::from(std::io::Error::new(io_kind, err.message)),
301 InvalidArgument => vortex_err!(InvalidArgument: "{}", err.message),
302 other => vortex_err!("Pco {:?} error: {}", other, err.message),
303 }
304}
305
306#[derive(Clone, Debug)]
307pub struct Pco;
309
310impl Pco {
311 pub(crate) fn try_new(
312 dtype: DType,
313 data: PcoData,
314 validity: Validity,
315 ) -> VortexResult<PcoArray> {
316 let len = data.len();
317 data.validate(&dtype, len, &validity)?;
318 let slots = smallvec![validity_to_child(&validity, data.unsliced_n_rows())];
319 Ok(unsafe {
320 Array::from_parts_unchecked(ArrayParts::new(Pco, dtype, len, data).with_slots(slots))
321 })
322 }
323
324 pub fn from_primitive(
326 parray: ArrayView<'_, Primitive>,
327 level: usize,
328 values_per_page: usize,
329 ctx: &mut ExecutionCtx,
330 ) -> VortexResult<PcoArray> {
331 let dtype = parray.dtype().clone();
332 let validity = parray.validity()?;
333 let data = PcoData::from_primitive(parray, level, values_per_page, ctx)?;
334 Self::try_new(dtype, data, validity)
335 }
336}
337
338pub(super) const NUM_SLOTS: usize = 1;
340pub(super) const SLOT_NAMES: [&str; NUM_SLOTS] = ["validity"];
341
342#[derive(Clone, Debug)]
343pub struct PcoData {
345 pub(crate) chunk_metas: Vec<ByteBuffer>,
346 pub(crate) pages: Vec<ByteBuffer>,
347 pub(crate) metadata: PcoMetadata,
348 ptype: PType,
349 unsliced_n_rows: usize,
350 slice_start: usize,
351 slice_stop: usize,
352}
353
354impl Display for PcoData {
355 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
356 write!(
357 f,
358 "ptype: {}, nrows: {}, slice: {}..{}",
359 self.ptype, self.unsliced_n_rows, self.slice_start, self.slice_stop
360 )
361 }
362}
363
364impl PcoData {
365 pub fn validate(&self, dtype: &DType, len: usize, validity: &Validity) -> VortexResult<()> {
367 let _ = number_type_from_ptype(self.ptype);
368 vortex_ensure!(
369 dtype.as_ptype() == self.ptype,
370 "expected ptype {}, got {}",
371 self.ptype,
372 dtype.as_ptype()
373 );
374 vortex_ensure!(
375 dtype.nullability() == validity.nullability(),
376 "expected nullability {}, got {}",
377 validity.nullability(),
378 dtype.nullability()
379 );
380 vortex_ensure!(
381 self.slice_start <= self.slice_stop && self.slice_stop <= self.unsliced_n_rows,
382 "invalid slice range {}..{} for {} rows",
383 self.slice_start,
384 self.slice_stop,
385 self.unsliced_n_rows
386 );
387 vortex_ensure!(
388 self.slice_stop - self.slice_start == len,
389 "expected len {len}, got {}",
390 self.slice_stop - self.slice_start
391 );
392 if let Some(validity_len) = validity.maybe_len() {
393 vortex_ensure!(
394 validity_len == self.unsliced_n_rows,
395 "expected validity len {}, got {}",
396 self.unsliced_n_rows,
397 validity_len
398 );
399 }
400 vortex_ensure!(
401 self.chunk_metas.len() == self.metadata.chunks.len(),
402 "expected {} chunk metas, got {}",
403 self.metadata.chunks.len(),
404 self.chunk_metas.len()
405 );
406 vortex_ensure!(
407 self.pages.len()
408 == self
409 .metadata
410 .chunks
411 .iter()
412 .map(|chunk| chunk.pages.len())
413 .sum::<usize>(),
414 "page count does not match metadata"
415 );
416 Ok(())
417 }
418
419 pub fn new(
421 chunk_metas: Vec<ByteBuffer>,
422 pages: Vec<ByteBuffer>,
423 ptype: PType,
424 metadata: PcoMetadata,
425 len: usize,
426 ) -> Self {
427 Self {
428 chunk_metas,
429 pages,
430 metadata,
431 ptype,
432 unsliced_n_rows: len,
433 slice_start: 0,
434 slice_stop: len,
435 }
436 }
437
438 pub fn from_primitive(
440 parray: ArrayView<'_, Primitive>,
441 level: usize,
442 values_per_page: usize,
443 ctx: &mut ExecutionCtx,
444 ) -> VortexResult<Self> {
445 Self::from_primitive_with_values_per_chunk(
446 parray,
447 level,
448 VALUES_PER_CHUNK,
449 values_per_page,
450 ctx,
451 )
452 }
453
454 pub(crate) fn from_primitive_with_values_per_chunk(
455 parray: ArrayView<'_, Primitive>,
456 level: usize,
457 values_per_chunk: usize,
458 values_per_page: usize,
459 ctx: &mut ExecutionCtx,
460 ) -> VortexResult<Self> {
461 let number_type = number_type_from_dtype(parray.dtype());
462 let values_per_page = if values_per_page == 0 {
463 values_per_chunk
464 } else {
465 values_per_page
466 };
467
468 let chunk_config = ChunkConfig::default()
470 .with_compression_level(level)
471 .with_paging_spec(PagingSpec::EqualPagesUpTo(values_per_page));
472
473 let values = collect_valid(parray, ctx)?;
474 let n_values = values.len();
475
476 let fc = FileCompressor::default();
477 let mut header = vec![];
478 fc.write_header(&mut header).map_err(vortex_err_from_pco)?;
479
480 let mut chunk_meta_buffers = vec![]; let mut chunk_infos = vec![]; let mut page_buffers = vec![];
483 for chunk_start in (0..n_values).step_by(values_per_chunk) {
484 let chunk_end = cmp::min(n_values, chunk_start + values_per_chunk);
485 let mut cc = match_number_enum!(
486 number_type,
487 NumberType<T> => {
488 let values = values.to_buffer::<T>();
489 let chunk = &values.as_slice()[chunk_start..chunk_end];
490 fc
491 .chunk_compressor(chunk, &chunk_config)
492 .map_err(vortex_err_from_pco)?
493 }
494 );
495
496 let mut chunk_meta_buffer = ByteBufferMut::with_capacity(cc.meta_size_hint());
497 cc.write_meta(&mut chunk_meta_buffer)
498 .map_err(vortex_err_from_pco)?;
499 chunk_meta_buffers.push(chunk_meta_buffer.freeze());
500
501 let mut page_infos = vec![];
502 for (page_idx, page_n_values) in cc.n_per_page().into_iter().enumerate() {
503 let mut page = ByteBufferMut::with_capacity(cc.page_size_hint(page_idx));
504 cc.write_page(page_idx, &mut page)
505 .map_err(vortex_err_from_pco)?;
506 page_buffers.push(page.freeze());
507 page_infos.push(PcoPageInfo {
508 n_values: u32::try_from(page_n_values)?,
509 });
510 }
511 chunk_infos.push(PcoChunkInfo { pages: page_infos })
512 }
513
514 let metadata = PcoMetadata {
515 header,
516 chunks: chunk_infos,
517 };
518 Ok(PcoData::new(
519 chunk_meta_buffers,
520 page_buffers,
521 parray.dtype().as_ptype(),
522 metadata,
523 parray.len(),
524 ))
525 }
526
527 pub fn from_array(
533 array: ArrayRef,
534 level: usize,
535 nums_per_page: usize,
536 ctx: &mut ExecutionCtx,
537 ) -> VortexResult<Self> {
538 let parray = array.try_downcast::<Primitive>().map_err(|a| {
539 vortex_err!(
540 "Pco can only encode primitive arrays, got {}",
541 a.encoding_id()
542 )
543 })?;
544 Self::from_primitive(parray.as_view(), level, nums_per_page, ctx)
545 }
546
547 pub fn decompress(
549 &self,
550 unsliced_validity: &Validity,
551 ctx: &mut ExecutionCtx,
552 ) -> VortexResult<PrimitiveArray> {
553 let number_type = number_type_from_ptype(self.ptype);
556 let values_byte_buffer = match_number_enum!(
557 number_type,
558 NumberType<T> => {
559 self.decompress_values_typed::<T>(unsliced_validity, ctx)?
560 }
561 );
562
563 Ok(PrimitiveArray::from_values_byte_buffer(
564 values_byte_buffer,
565 self.ptype,
566 unsliced_validity.slice(self.slice_start..self.slice_stop)?,
567 self.slice_stop - self.slice_start,
568 ctx,
569 ))
570 }
571
572 fn decompress_values_typed<T: Number>(
573 &self,
574 unsliced_validity: &Validity,
575 ctx: &mut ExecutionCtx,
576 ) -> VortexResult<ByteBuffer> {
577 let slice_value_indices = unsliced_validity
579 .execute_mask(self.unsliced_n_rows, ctx)?
580 .valid_counts_for_indices(&[self.slice_start, self.slice_stop]);
581 let slice_value_start = slice_value_indices[0];
582 let slice_value_stop = slice_value_indices[1];
583 let slice_n_values = slice_value_stop - slice_value_start;
584
585 let (fd, _) =
588 FileDecompressor::new(self.metadata.header.as_slice()).map_err(vortex_err_from_pco)?;
589 let mut decompressed_values = BufferMut::<T>::with_capacity(slice_n_values);
590 let mut page_idx = 0;
591 let mut page_value_start = 0;
592 let mut n_skipped_values = 0;
593 for (chunk_info, chunk_meta) in self.metadata.chunks.iter().zip(&self.chunk_metas) {
594 let mut chunk_decompressor: Option<ChunkDecompressor<T>> = None;
596 for page_info in &chunk_info.pages {
597 let page_n_values = page_info.n_values as usize;
598 let page_value_stop = page_value_start + page_n_values;
599
600 if page_value_start >= slice_value_stop {
601 break;
602 }
603
604 if page_value_stop > slice_value_start {
605 let old_len = decompressed_values.len();
607 let new_len = old_len + page_n_values;
608 decompressed_values.reserve(page_n_values);
609 unsafe {
610 decompressed_values.set_len(new_len);
611 }
612 let page: &[u8] = self.pages[page_idx].as_ref();
613
614 let mut cd = match chunk_decompressor.take() {
615 Some(d) => d,
616 None => {
617 let (new_cd, _) = fd
618 .chunk_decompressor(chunk_meta.as_ref())
619 .map_err(vortex_err_from_pco)?;
620 new_cd
621 }
622 };
623
624 let mut pd = cd
625 .page_decompressor(page, page_n_values)
626 .map_err(vortex_err_from_pco)?;
627 pd.read(&mut decompressed_values[old_len..new_len])
628 .map_err(vortex_err_from_pco)?;
629
630 chunk_decompressor = Some(cd);
631 } else {
632 n_skipped_values += page_n_values;
633 }
634
635 page_value_start = page_value_stop;
636 page_idx += 1;
637 }
638 }
639
640 let value_offset = slice_value_start - n_skipped_values;
642 Ok(decompressed_values
643 .freeze()
644 .slice(value_offset..value_offset + slice_n_values)
645 .into_byte_buffer())
646 }
647
648 pub(crate) fn _slice(&self, start: usize, stop: usize) -> Self {
649 PcoData {
650 slice_start: self.slice_start + start,
651 slice_stop: self.slice_start + stop,
652 ..self.clone()
653 }
654 }
655
656 pub fn len(&self) -> usize {
658 self.slice_stop - self.slice_start
659 }
660
661 pub fn is_empty(&self) -> bool {
663 self.slice_stop == self.slice_start
664 }
665
666 pub(crate) fn slice_start(&self) -> usize {
667 self.slice_start
668 }
669
670 pub(crate) fn slice_stop(&self) -> usize {
671 self.slice_stop
672 }
673
674 pub(crate) fn unsliced_n_rows(&self) -> usize {
675 self.unsliced_n_rows
676 }
677}
678
679impl ValidityVTable<Pco> for Pco {
680 fn validity(array: ArrayView<'_, Pco>) -> VortexResult<Validity> {
681 let unsliced_validity =
682 child_to_validity(array.slots()[0].as_ref(), array.dtype().nullability());
683 unsliced_validity.slice(array.slice_start()..array.slice_stop())
684 }
685}
686
687impl OperationsVTable<Pco> for Pco {
688 fn scalar_at(
689 array: ArrayView<'_, Pco>,
690 index: usize,
691 ctx: &mut ExecutionCtx,
692 ) -> VortexResult<Scalar> {
693 let unsliced_validity =
694 child_to_validity(array.slots()[0].as_ref(), array.dtype().nullability());
695 array
696 ._slice(index, index + 1)
697 .decompress(&unsliced_validity, ctx)?
698 .into_array()
699 .execute_scalar(0, ctx)
700 }
701}
702
703#[cfg(test)]
704mod tests {
705 use vortex_array::IntoArray;
706 use vortex_array::VortexSessionExecute;
707 use vortex_array::array_session;
708 use vortex_array::arrays::PrimitiveArray;
709 use vortex_array::assert_arrays_eq;
710 use vortex_array::validity::Validity;
711 use vortex_buffer::buffer;
712
713 use crate::Pco;
714
715 #[test]
716 fn test_slice_nullable() {
717 let mut ctx = array_session().create_execution_ctx();
718 let values = PrimitiveArray::new(
720 buffer![10u32, 20, 30, 40, 50, 60],
721 Validity::from_iter([false, true, true, true, true, false]),
722 );
723 let pco = Pco::from_primitive(values.as_view(), 0, 128, &mut ctx).unwrap();
724 assert_arrays_eq!(
725 pco,
726 PrimitiveArray::from_option_iter([
727 None,
728 Some(20u32),
729 Some(30),
730 Some(40),
731 Some(50),
732 None
733 ]),
734 &mut ctx
735 );
736
737 let sliced = pco.slice(1..5).unwrap();
739 let expected =
740 PrimitiveArray::from_option_iter([Some(20u32), Some(30), Some(40), Some(50)])
741 .into_array();
742 assert_arrays_eq!(sliced, expected, &mut ctx);
743 }
744}