1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::hash::Hash;
8use std::hash::Hasher;
9use std::sync::Arc;
10
11use itertools::Itertools as _;
12use prost::Message as _;
13use vortex_array::Array;
14use vortex_array::ArrayEq;
15use vortex_array::ArrayHash;
16use vortex_array::ArrayId;
17use vortex_array::ArrayParts;
18use vortex_array::ArrayRef;
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::ConstantArray;
26use vortex_array::arrays::PrimitiveArray;
27use vortex_array::arrays::VarBinViewArray;
28use vortex_array::arrays::varbinview::build_views::BinaryView;
29use vortex_array::arrays::varbinview::build_views::MAX_BUFFER_LEN;
30use vortex_array::buffer::BufferHandle;
31use vortex_array::dtype::DType;
32use vortex_array::scalar::Scalar;
33use vortex_array::serde::ArrayChildren;
34use vortex_array::smallvec::smallvec;
35use vortex_array::validity::Validity;
36use vortex_array::vtable::OperationsVTable;
37use vortex_array::vtable::VTable;
38use vortex_array::vtable::ValidityVTable;
39use vortex_array::vtable::child_to_validity;
40use vortex_array::vtable::validity_to_child;
41use vortex_buffer::Alignment;
42use vortex_buffer::Buffer;
43use vortex_buffer::BufferMut;
44use vortex_buffer::ByteBuffer;
45use vortex_buffer::ByteBufferMut;
46use vortex_error::VortexError;
47use vortex_error::VortexExpect;
48use vortex_error::VortexResult;
49use vortex_error::vortex_bail;
50use vortex_error::vortex_ensure;
51use vortex_error::vortex_err;
52use vortex_error::vortex_panic;
53use vortex_mask::AllOr;
54use vortex_session::VortexSession;
55use vortex_session::registry::CachedId;
56
57use crate::ZstdFrameMetadata;
58use crate::ZstdMetadata;
59
60const MIN_SAMPLES_FOR_DICTIONARY: usize = 8;
62type ViewLen = u32;
63
64pub type ZstdArray = Array<Zstd>;
84
85impl ArrayHash for ZstdData {
86 fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: EqMode) {
87 match &self.dictionary {
88 Some(dict) => {
89 true.hash(state);
90 dict.array_hash(state, accuracy);
91 }
92 None => {
93 false.hash(state);
94 }
95 }
96 for frame in &self.frames {
97 frame.array_hash(state, accuracy);
98 }
99 self.unsliced_n_rows.hash(state);
100 self.slice_start.hash(state);
101 self.slice_stop.hash(state);
102 }
103}
104
105impl ArrayEq for ZstdData {
106 fn array_eq(&self, other: &Self, accuracy: EqMode) -> bool {
107 if !match (&self.dictionary, &other.dictionary) {
108 (Some(d1), Some(d2)) => d1.array_eq(d2, accuracy),
109 (None, None) => true,
110 _ => false,
111 } {
112 return false;
113 }
114 if self.frames.len() != other.frames.len() {
115 return false;
116 }
117 for (a, b) in self.frames.iter().zip(&other.frames) {
118 if !a.array_eq(b, accuracy) {
119 return false;
120 }
121 }
122 self.unsliced_n_rows == other.unsliced_n_rows
123 && self.slice_start == other.slice_start
124 && self.slice_stop == other.slice_stop
125 }
126}
127
128impl VTable for Zstd {
129 type TypedArrayData = ZstdData;
130
131 type OperationsVTable = Self;
132 type ValidityVTable = Self;
133
134 fn id(&self) -> ArrayId {
135 static ID: CachedId = CachedId::new("vortex.zstd");
136 *ID
137 }
138
139 fn validate(
140 &self,
141 data: &Self::TypedArrayData,
142 dtype: &DType,
143 len: usize,
144 slots: &[Option<ArrayRef>],
145 ) -> VortexResult<()> {
146 let validity = child_to_validity(slots[0].as_ref(), dtype.nullability());
147 data.validate(dtype, len, &validity)
148 }
149
150 fn nbuffers(array: ArrayView<'_, Self>) -> usize {
151 array.dictionary.is_some() as usize + array.frames.len()
152 }
153
154 fn buffer(array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
155 if let Some(dict) = &array.dictionary {
156 if idx == 0 {
157 return BufferHandle::new_host(dict.clone());
158 }
159 BufferHandle::new_host(array.frames[idx - 1].clone())
160 } else {
161 BufferHandle::new_host(array.frames[idx].clone())
162 }
163 }
164
165 fn buffer_name(array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
166 if array.dictionary.is_some() {
167 if idx == 0 {
168 Some("dictionary".to_string())
169 } else {
170 Some(format!("frame_{}", idx - 1))
171 }
172 } else {
173 Some(format!("frame_{idx}"))
174 }
175 }
176
177 fn with_buffers(
178 &self,
179 array: ArrayView<'_, Self>,
180 buffers: &[BufferHandle],
181 ) -> VortexResult<ArrayParts<Self>> {
182 let mut data = array.data().clone();
183 if data.dictionary.is_some() {
184 let Some((dictionary, frames)) = buffers.split_first() else {
185 vortex_bail!("Expected dictionary buffer");
186 };
187 data.dictionary = Some(dictionary.clone().try_to_host_sync()?);
188 data.frames = frames
189 .iter()
190 .map(|buffer| buffer.clone().try_to_host_sync())
191 .collect::<VortexResult<Vec<_>>>()?;
192 } else {
193 data.frames = buffers
194 .iter()
195 .map(|buffer| buffer.clone().try_to_host_sync())
196 .collect::<VortexResult<Vec<_>>>()?;
197 }
198 Ok(
199 ArrayParts::new(self.clone(), array.dtype().clone(), array.len(), data)
200 .with_slots(array.slots().iter().cloned().collect()),
201 )
202 }
203
204 fn serialize(
205 array: ArrayView<'_, Self>,
206 _session: &VortexSession,
207 ) -> VortexResult<Option<Vec<u8>>> {
208 Ok(Some(array.metadata.clone().encode_to_vec()))
209 }
210
211 fn deserialize(
212 &self,
213 dtype: &DType,
214 len: usize,
215 metadata: &[u8],
216 buffers: &[BufferHandle],
217 children: &dyn ArrayChildren,
218 _session: &VortexSession,
219 ) -> VortexResult<ArrayParts<Self>> {
220 let metadata = ZstdMetadata::decode(metadata)?;
221 let validity = if children.is_empty() {
222 Validity::from(dtype.nullability())
223 } else if children.len() == 1 {
224 let validity = children.get(0, &Validity::DTYPE, len)?;
225 Validity::Array(validity)
226 } else {
227 vortex_bail!("ZstdArray expected 0 or 1 child, got {}", children.len());
228 };
229
230 let (dictionary_buffer, compressed_buffers) = if metadata.dictionary_size == 0 {
231 (
233 None,
234 buffers
235 .iter()
236 .map(|b| b.clone().try_to_host_sync())
237 .collect::<VortexResult<Vec<_>>>()?,
238 )
239 } else {
240 (
242 Some(buffers[0].clone().try_to_host_sync()?),
243 buffers[1..]
244 .iter()
245 .map(|b| b.clone().try_to_host_sync())
246 .collect::<VortexResult<Vec<_>>>()?,
247 )
248 };
249
250 let slots = smallvec![validity_to_child(&validity, len)];
251 let data = ZstdData::new(dictionary_buffer, compressed_buffers, metadata, len);
252 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
253 }
254
255 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
256 SLOT_NAMES[idx].to_string()
257 }
258
259 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
260 let unsliced_validity = child_to_validity(
261 array.as_ref().slots()[0].as_ref(),
262 array.dtype().nullability(),
263 );
264 array
265 .data()
266 .decompress(array.dtype(), &unsliced_validity, ctx)?
267 .execute::<ArrayRef>(ctx)
268 .map(ExecutionResult::done)
269 }
270
271 fn reduce_parent(
272 array: ArrayView<'_, Self>,
273 parent: &ArrayRef,
274 child_idx: usize,
275 ) -> VortexResult<Option<ArrayRef>> {
276 crate::rules::RULES.evaluate(array, parent, child_idx)
277 }
278}
279
280#[derive(Clone, Debug)]
281pub struct Zstd;
283
284impl Zstd {
285 pub fn try_new(dtype: DType, data: ZstdData, validity: Validity) -> VortexResult<ZstdArray> {
287 let len = data.len();
288 data.validate(&dtype, len, &validity)?;
289 let slots = smallvec![validity_to_child(&validity, data.unsliced_n_rows())];
290 Ok(unsafe {
291 Array::from_parts_unchecked(ArrayParts::new(Zstd, dtype, len, data).with_slots(slots))
292 })
293 }
294
295 pub fn from_var_bin_view_without_dict(
297 vbv: &VarBinViewArray,
298 level: i32,
299 values_per_frame: usize,
300 ctx: &mut ExecutionCtx,
301 ) -> VortexResult<ZstdArray> {
302 let validity = vbv.validity()?;
303 Self::try_new(
304 vbv.dtype().clone(),
305 ZstdData::from_var_bin_view_without_dict(vbv, level, values_per_frame, ctx)?,
306 validity,
307 )
308 }
309
310 pub fn from_primitive(
312 parray: &PrimitiveArray,
313 level: i32,
314 values_per_frame: usize,
315 ctx: &mut ExecutionCtx,
316 ) -> VortexResult<ZstdArray> {
317 let validity = parray.validity()?;
318 Self::try_new(
319 parray.dtype().clone(),
320 ZstdData::from_primitive(parray, level, values_per_frame, ctx)?,
321 validity,
322 )
323 }
324
325 pub fn from_var_bin_view(
327 vbv: &VarBinViewArray,
328 level: i32,
329 values_per_frame: usize,
330 ctx: &mut ExecutionCtx,
331 ) -> VortexResult<ZstdArray> {
332 let validity = vbv.validity()?;
333 Self::try_new(
334 vbv.dtype().clone(),
335 ZstdData::from_var_bin_view(vbv, level, values_per_frame, ctx)?,
336 validity,
337 )
338 }
339
340 pub fn decompress(array: &ZstdArray, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> {
342 let unsliced_validity = child_to_validity(
343 array.as_ref().slots()[0].as_ref(),
344 array.dtype().nullability(),
345 );
346 array
347 .data()
348 .decompress(array.dtype(), &unsliced_validity, ctx)
349 }
350}
351
352pub(super) const NUM_SLOTS: usize = 1;
354pub(super) const SLOT_NAMES: [&str; NUM_SLOTS] = ["validity"];
355
356#[derive(Clone, Debug)]
357pub struct ZstdData {
359 pub(crate) dictionary: Option<ByteBuffer>,
360 pub(crate) frames: Vec<ByteBuffer>,
361 pub(crate) metadata: ZstdMetadata,
362 unsliced_n_rows: usize,
363 slice_start: usize,
364 slice_stop: usize,
365}
366
367impl Display for ZstdData {
368 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
369 write!(
370 f,
371 "nrows: {}, slice: {}..{}",
372 self.unsliced_n_rows, self.slice_start, self.slice_stop
373 )
374 }
375}
376
377pub struct ZstdDataParts {
379 pub dictionary: Option<ByteBuffer>,
381 pub frames: Vec<ByteBuffer>,
383 pub metadata: ZstdMetadata,
385 pub validity: Validity,
387 pub n_rows: usize,
389 pub slice_start: usize,
391 pub slice_stop: usize,
393}
394
395#[derive(Debug)]
397struct Frames {
398 dictionary: Option<ByteBuffer>,
399 frames: Vec<ByteBuffer>,
400 frame_metas: Vec<ZstdFrameMetadata>,
401}
402
403fn choose_max_dict_size(uncompressed_size: usize) -> usize {
404 (uncompressed_size / 100).clamp(256, 100 * 1024)
409}
410
411fn collect_valid_primitive(
412 parray: &PrimitiveArray,
413 ctx: &mut ExecutionCtx,
414) -> VortexResult<PrimitiveArray> {
415 let mask = parray
416 .as_ref()
417 .validity()?
418 .execute_mask(parray.as_ref().len(), ctx)?;
419 let result = parray.filter(mask)?.execute::<PrimitiveArray>(ctx)?;
420 Ok(result)
421}
422
423fn collect_valid_vbv(
424 vbv: &VarBinViewArray,
425 ctx: &mut ExecutionCtx,
426) -> VortexResult<(ByteBuffer, Vec<usize>)> {
427 let mask = vbv
428 .as_ref()
429 .validity()?
430 .execute_mask(vbv.as_ref().len(), ctx)?;
431 let buffer_and_value_byte_indices = match mask.bit_buffer() {
432 AllOr::None => (Buffer::empty(), Vec::new()),
433 _ => {
434 let mut buffer = BufferMut::with_capacity(
435 usize::try_from(vbv.nbytes()).vortex_expect("must fit into buffer")
436 + mask.true_count() * size_of::<ViewLen>(),
437 );
438 let mut value_byte_indices = Vec::new();
439 let views = vbv.views();
440 let buffers = vbv
441 .data_buffers()
442 .iter()
443 .map(|b| b.as_host())
444 .collect::<Vec<_>>();
445 for (i, view) in views.iter().enumerate() {
447 if !mask.value(i) {
448 continue;
449 }
450 let value = if view.is_inlined() {
451 view.as_inlined().value()
452 } else {
453 let view_ref = view.as_view();
454 &buffers[view_ref.buffer_index as usize][view_ref.as_range()]
455 };
456 value_byte_indices.push(buffer.len());
457 buffer.extend_trusted(ViewLen::try_from(value.len())?.to_le_bytes().into_iter());
459 buffer.extend_from_slice(value);
460 }
461 (buffer.freeze(), value_byte_indices)
462 }
463 };
464 Ok(buffer_and_value_byte_indices)
465}
466
467pub fn reconstruct_views(
476 buffer: &ByteBuffer,
477 max_buffer_len: usize,
478) -> (Vec<ByteBuffer>, Buffer<BinaryView>) {
479 let mut views = BufferMut::<BinaryView>::empty();
480 let mut buffers = Vec::new();
481 let mut segment_start: usize = 0;
482 let mut offset = 0;
483
484 while offset < buffer.len() {
485 let str_len = ViewLen::from_le_bytes(
486 buffer
487 .get(offset..offset + size_of::<ViewLen>())
488 .vortex_expect("corrupted zstd length")
489 .try_into()
490 .ok()
491 .vortex_expect("must fit ViewLen size"),
492 ) as usize;
493
494 let value_data_offset = offset + size_of::<ViewLen>();
495 let local_offset = value_data_offset - segment_start;
496
497 if local_offset + str_len > max_buffer_len && offset > segment_start {
498 buffers.push(buffer.slice(segment_start..offset));
499 segment_start = offset;
500 }
501
502 let local_offset = u32::try_from(value_data_offset - segment_start)
503 .vortex_expect("local offset within segment must fit in u32");
504 let buf_index = u32::try_from(buffers.len()).vortex_expect("buffer index must fit in u32");
505 let value = &buffer[value_data_offset..value_data_offset + str_len];
506 views.push(BinaryView::make_view(value, buf_index, local_offset));
507 offset = value_data_offset + str_len;
508 }
509
510 if segment_start < buffer.len() {
511 buffers.push(buffer.slice(segment_start..buffer.len()));
512 }
513
514 (buffers, views.freeze())
515}
516
517impl ZstdData {
518 pub fn new(
520 dictionary: Option<ByteBuffer>,
521 frames: Vec<ByteBuffer>,
522 metadata: ZstdMetadata,
523 n_rows: usize,
524 ) -> Self {
525 Self {
526 dictionary,
527 frames,
528 metadata,
529 unsliced_n_rows: n_rows,
530 slice_start: 0,
531 slice_stop: n_rows,
532 }
533 }
534
535 pub fn validate(&self, dtype: &DType, len: usize, validity: &Validity) -> VortexResult<()> {
537 vortex_ensure!(
538 matches!(
539 dtype,
540 DType::Primitive(..) | DType::Binary(_) | DType::Utf8(_)
541 ),
542 "Unsupported dtype for Zstd array: {dtype}"
543 );
544 vortex_ensure!(
545 self.slice_start <= self.slice_stop,
546 "Invalid slice range {}..{}",
547 self.slice_start,
548 self.slice_stop
549 );
550 vortex_ensure!(
551 self.slice_stop <= self.unsliced_n_rows,
552 "Slice stop {} exceeds unsliced row count {}",
553 self.slice_stop,
554 self.unsliced_n_rows
555 );
556 vortex_ensure!(
557 self.slice_stop - self.slice_start == len,
558 "Slice length {} does not match array length {}",
559 self.slice_stop - self.slice_start,
560 len
561 );
562 if let Some(validity_len) = validity.maybe_len() {
563 vortex_ensure!(
564 validity_len == self.unsliced_n_rows,
565 "Validity length {} does not match unsliced row count {}",
566 validity_len,
567 self.unsliced_n_rows
568 );
569 }
570
571 match &self.dictionary {
572 Some(dictionary) => vortex_ensure!(
573 usize::try_from(self.metadata.dictionary_size)? == dictionary.len(),
574 "Dictionary size metadata {} does not match buffer size {}",
575 self.metadata.dictionary_size,
576 dictionary.len()
577 ),
578 None => vortex_ensure!(
579 self.metadata.dictionary_size == 0,
580 "Dictionary metadata present without dictionary buffer"
581 ),
582 }
583 vortex_ensure!(
584 self.frames.len() == self.metadata.frames.len(),
585 "Frame count {} does not match metadata frame count {}",
586 self.frames.len(),
587 self.metadata.frames.len()
588 );
589
590 Ok(())
591 }
592
593 pub(crate) fn with_slice(&self, start: usize, stop: usize) -> Self {
594 let new_start = self.slice_start + start;
595 let new_stop = self.slice_start + stop;
596
597 assert!(
598 new_start <= self.slice_stop,
599 "new slice start {new_start} exceeds end {}",
600 self.slice_stop
601 );
602
603 assert!(
604 new_stop <= self.slice_stop,
605 "new slice stop {new_stop} exceeds end {}",
606 self.slice_stop
607 );
608
609 Self {
610 slice_start: new_start,
611 slice_stop: new_stop,
612 ..self.clone()
613 }
614 }
615
616 fn compress_values(
617 value_bytes: &ByteBuffer,
618 frame_byte_starts: &[usize],
619 level: i32,
620 values_per_frame: usize,
621 n_values: usize,
622 use_dictionary: bool,
623 ) -> VortexResult<Frames> {
624 let n_frames = frame_byte_starts.len();
625
626 let mut sample_sizes = Vec::with_capacity(n_frames);
628 for i in 0..n_frames {
629 let frame_byte_end = frame_byte_starts
630 .get(i + 1)
631 .copied()
632 .unwrap_or(value_bytes.len());
633 sample_sizes.push(frame_byte_end - frame_byte_starts[i]);
634 }
635 debug_assert_eq!(sample_sizes.iter().sum::<usize>(), value_bytes.len());
636
637 let (dictionary, mut compressor) = if !use_dictionary
638 || sample_sizes.len() < MIN_SAMPLES_FOR_DICTIONARY
639 {
640 (None, zstd::bulk::Compressor::new(level)?)
642 } else {
643 let max_dict_size = choose_max_dict_size(value_bytes.len());
645 let dict = zstd::dict::from_continuous(value_bytes, &sample_sizes, max_dict_size)
646 .map_err(|err| VortexError::from(err).with_context("while training dictionary"))?;
647
648 let compressor = zstd::bulk::Compressor::with_dictionary(level, &dict)?;
649 (Some(ByteBuffer::from(dict)), compressor)
650 };
651
652 let mut frame_metas = vec![];
653 let mut frames = vec![];
654 for i in 0..n_frames {
655 let frame_byte_end = frame_byte_starts
656 .get(i + 1)
657 .copied()
658 .unwrap_or(value_bytes.len());
659
660 let uncompressed = &value_bytes.slice(frame_byte_starts[i]..frame_byte_end);
661 let compressed = compressor
662 .compress(uncompressed)
663 .map_err(|err| VortexError::from(err).with_context("while compressing"))?;
664 frame_metas.push(ZstdFrameMetadata {
665 uncompressed_size: uncompressed.len() as u64,
666 n_values: values_per_frame.min(n_values - i * values_per_frame) as u64,
667 });
668 frames.push(ByteBuffer::from(compressed));
669 }
670
671 Ok(Frames {
672 dictionary,
673 frames,
674 frame_metas,
675 })
676 }
677
678 pub fn from_primitive(
685 parray: &PrimitiveArray,
686 level: i32,
687 values_per_frame: usize,
688 ctx: &mut ExecutionCtx,
689 ) -> VortexResult<Self> {
690 Self::from_primitive_impl(parray, level, values_per_frame, true, ctx)
691 }
692
693 pub fn from_primitive_without_dict(
707 parray: &PrimitiveArray,
708 level: i32,
709 values_per_frame: usize,
710 ctx: &mut ExecutionCtx,
711 ) -> VortexResult<Self> {
712 Self::from_primitive_impl(parray, level, values_per_frame, false, ctx)
713 }
714
715 fn from_primitive_impl(
716 parray: &PrimitiveArray,
717 level: i32,
718 values_per_frame: usize,
719 use_dictionary: bool,
720 ctx: &mut ExecutionCtx,
721 ) -> VortexResult<Self> {
722 let byte_width = parray.ptype().byte_width();
723
724 let values = collect_valid_primitive(parray, ctx)?;
726 let n_values = values.len();
727 let values_per_frame = if values_per_frame > 0 {
728 values_per_frame
729 } else {
730 n_values
731 };
732
733 let value_bytes = values.buffer_handle().try_to_host_sync()?;
734 let alignment = *value_bytes.alignment();
736 let step_width = (values_per_frame * byte_width).div_ceil(alignment) * alignment;
737
738 let frame_byte_starts = (0..n_values * byte_width)
739 .step_by(step_width)
740 .collect::<Vec<_>>();
741 let Frames {
742 dictionary,
743 frames,
744 frame_metas,
745 } = Self::compress_values(
746 &value_bytes,
747 &frame_byte_starts,
748 level,
749 values_per_frame,
750 n_values,
751 use_dictionary,
752 )?;
753
754 let metadata = ZstdMetadata {
755 dictionary_size: dictionary
756 .as_ref()
757 .map_or(0, |dict| dict.len())
758 .try_into()?,
759 frames: frame_metas,
760 };
761
762 Ok(ZstdData::new(dictionary, frames, metadata, parray.len()))
763 }
764
765 pub fn from_var_bin_view(
772 vbv: &VarBinViewArray,
773 level: i32,
774 values_per_frame: usize,
775 ctx: &mut ExecutionCtx,
776 ) -> VortexResult<Self> {
777 Self::from_var_bin_view_impl(vbv, level, values_per_frame, true, ctx)
778 }
779
780 pub fn from_var_bin_view_without_dict(
794 vbv: &VarBinViewArray,
795 level: i32,
796 values_per_frame: usize,
797 ctx: &mut ExecutionCtx,
798 ) -> VortexResult<Self> {
799 Self::from_var_bin_view_impl(vbv, level, values_per_frame, false, ctx)
800 }
801
802 fn from_var_bin_view_impl(
803 vbv: &VarBinViewArray,
804 level: i32,
805 values_per_frame: usize,
806 use_dictionary: bool,
807 ctx: &mut ExecutionCtx,
808 ) -> VortexResult<Self> {
809 let (value_bytes, value_byte_indices) = collect_valid_vbv(vbv, ctx)?;
816 let n_values = value_byte_indices.len();
817 let values_per_frame = if values_per_frame > 0 {
818 values_per_frame
819 } else {
820 n_values
821 };
822
823 let frame_byte_starts = (0..n_values)
824 .step_by(values_per_frame)
825 .map(|i| value_byte_indices[i])
826 .collect::<Vec<_>>();
827 let Frames {
828 dictionary,
829 frames,
830 frame_metas,
831 } = Self::compress_values(
832 &value_bytes,
833 &frame_byte_starts,
834 level,
835 values_per_frame,
836 n_values,
837 use_dictionary,
838 )?;
839
840 let metadata = ZstdMetadata {
841 dictionary_size: dictionary
842 .as_ref()
843 .map_or(0, |dict| dict.len())
844 .try_into()?,
845 frames: frame_metas,
846 };
847 Ok(ZstdData::new(dictionary, frames, metadata, vbv.len()))
848 }
849
850 pub fn from_canonical(
854 canonical: &Canonical,
855 level: i32,
856 values_per_frame: usize,
857 ctx: &mut ExecutionCtx,
858 ) -> VortexResult<Option<Self>> {
859 match canonical {
860 Canonical::Primitive(parray) => Ok(Some(ZstdData::from_primitive(
861 parray,
862 level,
863 values_per_frame,
864 ctx,
865 )?)),
866 Canonical::VarBinView(vbv) => Ok(Some(ZstdData::from_var_bin_view(
867 vbv,
868 level,
869 values_per_frame,
870 ctx,
871 )?)),
872 _ => Ok(None),
873 }
874 }
875
876 pub fn from_array(
882 array: ArrayRef,
883 level: i32,
884 values_per_frame: usize,
885 ctx: &mut ExecutionCtx,
886 ) -> VortexResult<Self> {
887 let canonical = array.execute::<Canonical>(ctx)?;
888 Self::from_canonical(&canonical, level, values_per_frame, ctx)?
889 .ok_or_else(|| vortex_err!("Zstd can only encode Primitive and VarBinView arrays"))
890 }
891
892 fn byte_width(dtype: &DType) -> usize {
893 if dtype.is_primitive() {
894 dtype.as_ptype().byte_width()
895 } else {
896 1
897 }
898 }
899
900 fn decompress(
901 &self,
902 dtype: &DType,
903 unsliced_validity: &Validity,
904 ctx: &mut ExecutionCtx,
905 ) -> VortexResult<ArrayRef> {
906 let byte_width = Self::byte_width(dtype);
909 let slice_n_rows = self.slice_stop - self.slice_start;
910 let slice_value_indices = unsliced_validity
911 .execute_mask(self.unsliced_n_rows, ctx)?
912 .valid_counts_for_indices(&[self.slice_start, self.slice_stop]);
913
914 let slice_value_idx_start = slice_value_indices[0];
915 let slice_value_idx_stop = slice_value_indices[1];
916
917 let mut frames_to_decompress = vec![];
918 let mut value_idx_start = 0;
919 let mut uncompressed_size_to_decompress = 0;
920 let mut n_skipped_values = 0;
921 for (frame, frame_meta) in self.frames.iter().zip(&self.metadata.frames) {
922 if value_idx_start >= slice_value_idx_stop {
923 break;
924 }
925
926 let frame_uncompressed_size = usize::try_from(frame_meta.uncompressed_size)
927 .vortex_expect("Uncompressed size must fit in usize");
928 let frame_n_values = if frame_meta.n_values == 0 {
929 frame_uncompressed_size / byte_width
931 } else {
932 usize::try_from(frame_meta.n_values).vortex_expect("frame size must fit usize")
933 };
934
935 let value_idx_stop = value_idx_start + frame_n_values;
936 if value_idx_stop > slice_value_idx_start {
937 frames_to_decompress.push(frame);
939 uncompressed_size_to_decompress += frame_uncompressed_size;
940 } else {
941 n_skipped_values += frame_n_values;
942 }
943 value_idx_start = value_idx_stop;
944 }
945
946 let mut decompressor = if let Some(dictionary) = &self.dictionary {
948 zstd::bulk::Decompressor::with_dictionary(dictionary)?
949 } else {
950 zstd::bulk::Decompressor::new()?
951 };
952 let mut decompressed = ByteBufferMut::with_capacity_aligned(
953 uncompressed_size_to_decompress,
954 Alignment::new(byte_width),
955 );
956 unsafe {
957 decompressed.set_len(uncompressed_size_to_decompress);
960 }
961 let mut uncompressed_start = 0;
962 for frame in frames_to_decompress {
963 let uncompressed_written = decompressor
964 .decompress_to_buffer(frame.as_slice(), &mut decompressed[uncompressed_start..])?;
965 uncompressed_start += uncompressed_written;
966 }
967 if uncompressed_start != uncompressed_size_to_decompress {
968 vortex_panic!(
969 "Zstd metadata or frames were corrupt; expected {} bytes but decompressed {}",
970 uncompressed_size_to_decompress,
971 uncompressed_start
972 );
973 }
974
975 let decompressed = decompressed.freeze();
976 let mut slice_validity = unsliced_validity.slice(self.slice_start..self.slice_stop)?;
978
979 if !dtype.is_nullable() && !matches!(slice_validity, Validity::NonNullable) {
988 assert!(
989 matches!(slice_validity, Validity::AllValid),
990 "ZSTD array expects to be non-nullable but there are nulls after decompression"
991 );
992
993 slice_validity = Validity::NonNullable;
994 } else if dtype.is_nullable() && matches!(slice_validity, Validity::NonNullable) {
995 slice_validity = Validity::AllValid;
996 }
997 match dtype {
1001 DType::Primitive(..) => {
1002 let slice_values_buffer = decompressed.slice(
1003 (slice_value_idx_start - n_skipped_values) * byte_width
1004 ..(slice_value_idx_stop - n_skipped_values) * byte_width,
1005 );
1006 let primitive = PrimitiveArray::from_values_byte_buffer(
1007 slice_values_buffer,
1008 dtype.as_ptype(),
1009 slice_validity,
1010 slice_n_rows,
1011 ctx,
1012 );
1013
1014 Ok(primitive.into_array())
1015 }
1016 DType::Binary(_) | DType::Utf8(_) => {
1017 match slice_validity.execute_mask(slice_n_rows, ctx)?.indices() {
1018 AllOr::All => {
1019 let (buffers, all_views) = reconstruct_views(&decompressed, MAX_BUFFER_LEN);
1020 let valid_views = all_views.slice(
1021 slice_value_idx_start - n_skipped_values
1022 ..slice_value_idx_stop - n_skipped_values,
1023 );
1024
1025 Ok(unsafe {
1027 VarBinViewArray::new_unchecked(
1028 valid_views,
1029 Arc::from(buffers),
1030 dtype.clone(),
1031 slice_validity,
1032 )
1033 }
1034 .into_array())
1035 }
1036 AllOr::None => Ok(ConstantArray::new(
1037 Scalar::null(dtype.clone()),
1038 slice_n_rows,
1039 )
1040 .into_array()),
1041 AllOr::Some(valid_indices) => {
1042 let (buffers, all_views) = reconstruct_views(&decompressed, MAX_BUFFER_LEN);
1043 let valid_views = all_views.slice(
1044 slice_value_idx_start - n_skipped_values
1045 ..slice_value_idx_stop - n_skipped_values,
1046 );
1047
1048 let mut views = BufferMut::<BinaryView>::zeroed(slice_n_rows);
1049 for (view, index) in valid_views.into_iter().zip_eq(valid_indices) {
1050 views[*index] = view
1051 }
1052
1053 Ok(unsafe {
1055 VarBinViewArray::new_unchecked(
1056 views.freeze(),
1057 Arc::from(buffers),
1058 dtype.clone(),
1059 slice_validity,
1060 )
1061 }
1062 .into_array())
1063 }
1064 }
1065 }
1066 _ => vortex_panic!("Unsupported dtype for Zstd array: {}", dtype),
1067 }
1068 }
1069
1070 #[inline]
1072 pub fn len(&self) -> usize {
1073 self.slice_stop - self.slice_start
1074 }
1075
1076 #[inline]
1078 pub fn is_empty(&self) -> bool {
1079 self.slice_stop == self.slice_start
1080 }
1081
1082 pub fn into_parts(self, validity: Validity) -> ZstdDataParts {
1084 ZstdDataParts {
1085 dictionary: self.dictionary,
1086 frames: self.frames,
1087 metadata: self.metadata,
1088 validity,
1089 n_rows: self.unsliced_n_rows,
1090 slice_start: self.slice_start,
1091 slice_stop: self.slice_stop,
1092 }
1093 }
1094
1095 pub(crate) fn slice_start(&self) -> usize {
1096 self.slice_start
1097 }
1098
1099 pub(crate) fn slice_stop(&self) -> usize {
1100 self.slice_stop
1101 }
1102
1103 pub(crate) fn unsliced_n_rows(&self) -> usize {
1104 self.unsliced_n_rows
1105 }
1106}
1107
1108impl ValidityVTable<Zstd> for Zstd {
1109 fn validity(array: ArrayView<'_, Zstd>) -> VortexResult<Validity> {
1110 let unsliced_validity =
1111 child_to_validity(array.slots()[0].as_ref(), array.dtype().nullability());
1112 unsliced_validity.slice(array.slice_start()..array.slice_stop())
1113 }
1114}
1115
1116impl OperationsVTable<Zstd> for Zstd {
1117 fn scalar_at(
1118 array: ArrayView<'_, Zstd>,
1119 index: usize,
1120 ctx: &mut ExecutionCtx,
1121 ) -> VortexResult<Scalar> {
1122 let unsliced_validity =
1123 child_to_validity(array.slots()[0].as_ref(), array.dtype().nullability());
1124 let sliced = array.data().with_slice(index, index + 1);
1125 sliced
1126 .decompress(array.dtype(), &unsliced_validity, ctx)?
1127 .execute_scalar(0, ctx)
1128 }
1129}
1130
1131#[cfg(test)]
1132#[expect(clippy::cast_possible_truncation)]
1133mod tests {
1134 use vortex_buffer::ByteBuffer;
1135
1136 use super::reconstruct_views;
1137 use crate::array::BinaryView;
1138
1139 fn make_interleaved(strings: &[&[u8]]) -> ByteBuffer {
1141 let mut buf = Vec::new();
1142 for s in strings {
1143 let len = s.len() as u32;
1144 buf.extend_from_slice(&len.to_le_bytes());
1145 buf.extend_from_slice(s);
1146 }
1147 ByteBuffer::copy_from(buf.as_slice())
1148 }
1149
1150 #[test]
1151 fn test_reconstruct_views_no_split() {
1152 let strings: &[&[u8]] = &[b"hello", b"world"];
1153 let buf = make_interleaved(strings);
1154 let (buffers, views) = reconstruct_views(&buf, 1024);
1155
1156 assert_eq!(buffers.len(), 1);
1157 assert_eq!(views.len(), 2);
1158 assert_eq!(views[0], BinaryView::make_view(b"hello", 0, 4));
1160 assert_eq!(views[1], BinaryView::make_view(b"world", 0, 13));
1161 }
1162
1163 #[test]
1164 fn test_reconstruct_views_split_across_segments() {
1165 let strings: &[&[u8]] = &[b"aaaaaaaaaaaaa", b"bbbbbbbbbbbbb"];
1170 let buf = make_interleaved(strings);
1171 let (buffers, views) = reconstruct_views(&buf, 20);
1172
1173 assert_eq!(buffers.len(), 2);
1174 assert_eq!(views.len(), 2);
1175 assert_eq!(views[0], BinaryView::make_view(b"aaaaaaaaaaaaa", 0, 4));
1176 assert_eq!(views[1], BinaryView::make_view(b"bbbbbbbbbbbbb", 1, 4));
1178 }
1179}