1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::hash::Hash;
8use std::hash::Hasher;
9use std::mem::MaybeUninit;
10use std::ops::Range;
11use std::sync::Arc;
12
13use itertools::Itertools as _;
14use num_traits::AsPrimitive;
15use prost::Message as _;
16use vortex_array::Array;
17use vortex_array::ArrayEq;
18use vortex_array::ArrayHash;
19use vortex_array::ArrayId;
20use vortex_array::ArrayParts;
21use vortex_array::ArrayRef;
22use vortex_array::ArrayView;
23use vortex_array::Canonical;
24use vortex_array::EqMode;
25use vortex_array::ExecutionCtx;
26use vortex_array::ExecutionResult;
27use vortex_array::IntoArray;
28use vortex_array::array_slots;
29use vortex_array::arrays::ConstantArray;
30use vortex_array::arrays::PrimitiveArray;
31use vortex_array::arrays::VarBinViewArray;
32use vortex_array::arrays::varbinview::build_views::BinaryView;
33use vortex_array::arrays::varbinview::build_views::MAX_BUFFER_LEN;
34use vortex_array::buffer::BufferHandle;
35use vortex_array::builders::ArrayBuilder;
36use vortex_array::builders::VarBinBuilder;
37use vortex_array::builders::VarBinViewBuilder;
38use vortex_array::dtype::DType;
39use vortex_array::dtype::OffsetBuilderPType;
40use vortex_array::match_each_varbin_builder;
41use vortex_array::scalar::Scalar;
42use vortex_array::serde::ArrayChildren;
43use vortex_array::smallvec::smallvec;
44use vortex_array::validity::Validity;
45use vortex_array::vtable::OperationsVTable;
46use vortex_array::vtable::VTable;
47use vortex_array::vtable::ValidityVTable;
48use vortex_array::vtable::child_to_validity;
49use vortex_array::vtable::validity_to_child;
50use vortex_buffer::Alignment;
51use vortex_buffer::Buffer;
52use vortex_buffer::BufferMut;
53use vortex_buffer::ByteBuffer;
54use vortex_buffer::ByteBufferMut;
55use vortex_error::VortexError;
56use vortex_error::VortexExpect;
57use vortex_error::VortexResult;
58use vortex_error::vortex_bail;
59use vortex_error::vortex_ensure;
60use vortex_error::vortex_err;
61use vortex_mask::AllOr;
62use vortex_mask::Mask;
63use vortex_session::VortexSession;
64use vortex_session::registry::CachedId;
65use zstd::zstd_safe::WriteBuf;
66
67use crate::ZstdFrameMetadata;
68use crate::ZstdMetadata;
69use crate::validate_frame_content_size;
70
71const MIN_SAMPLES_FOR_DICTIONARY: usize = 8;
73type ViewLen = u32;
74
75pub type ZstdArray = Array<Zstd>;
95
96impl ArrayHash for ZstdData {
97 fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: EqMode) {
98 match &self.dictionary {
99 Some(dict) => {
100 true.hash(state);
101 dict.array_hash(state, accuracy);
102 }
103 None => {
104 false.hash(state);
105 }
106 }
107 for frame in &self.frames {
108 frame.array_hash(state, accuracy);
109 }
110 self.unsliced_n_rows.hash(state);
111 self.slice_start.hash(state);
112 self.slice_stop.hash(state);
113 }
114}
115
116impl ArrayEq for ZstdData {
117 fn array_eq(&self, other: &Self, accuracy: EqMode) -> bool {
118 if !match (&self.dictionary, &other.dictionary) {
119 (Some(d1), Some(d2)) => d1.array_eq(d2, accuracy),
120 (None, None) => true,
121 _ => false,
122 } {
123 return false;
124 }
125 if self.frames.len() != other.frames.len() {
126 return false;
127 }
128 for (a, b) in self.frames.iter().zip(&other.frames) {
129 if !a.array_eq(b, accuracy) {
130 return false;
131 }
132 }
133 self.unsliced_n_rows == other.unsliced_n_rows
134 && self.slice_start == other.slice_start
135 && self.slice_stop == other.slice_stop
136 }
137}
138
139impl VTable for Zstd {
140 type TypedArrayData = ZstdData;
141
142 type OperationsVTable = Self;
143 type ValidityVTable = Self;
144
145 fn id(&self) -> ArrayId {
146 static ID: CachedId = CachedId::new("vortex.zstd");
147 *ID
148 }
149
150 fn validate(
151 &self,
152 data: &Self::TypedArrayData,
153 dtype: &DType,
154 len: usize,
155 slots: &[Option<ArrayRef>],
156 ) -> VortexResult<()> {
157 let validity = child_to_validity(slots[ZstdSlots::VALIDITY].as_ref(), dtype.nullability());
158 data.validate(dtype, len, &validity)
159 }
160
161 fn nbuffers(array: ArrayView<'_, Self>) -> usize {
162 array.dictionary.is_some() as usize + array.frames.len()
163 }
164
165 fn buffer(array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
166 if let Some(dict) = &array.dictionary {
167 if idx == 0 {
168 return BufferHandle::new_host(dict.clone());
169 }
170 BufferHandle::new_host(array.frames[idx - 1].clone())
171 } else {
172 BufferHandle::new_host(array.frames[idx].clone())
173 }
174 }
175
176 fn buffer_name(array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
177 if array.dictionary.is_some() {
178 if idx == 0 {
179 Some("dictionary".to_string())
180 } else {
181 Some(format!("frame_{}", idx - 1))
182 }
183 } else {
184 Some(format!("frame_{idx}"))
185 }
186 }
187
188 fn with_buffers(
189 &self,
190 array: ArrayView<'_, Self>,
191 buffers: &[BufferHandle],
192 ) -> VortexResult<ArrayParts<Self>> {
193 let mut data = array.data().clone();
194 if data.dictionary.is_some() {
195 let Some((dictionary, frames)) = buffers.split_first() else {
196 vortex_bail!("Expected dictionary buffer");
197 };
198 data.dictionary = Some(dictionary.clone().try_to_host_sync()?);
199 data.frames = frames
200 .iter()
201 .map(|buffer| buffer.clone().try_to_host_sync())
202 .collect::<VortexResult<Vec<_>>>()?;
203 } else {
204 data.frames = buffers
205 .iter()
206 .map(|buffer| buffer.clone().try_to_host_sync())
207 .collect::<VortexResult<Vec<_>>>()?;
208 }
209 Ok(
210 ArrayParts::new(self.clone(), array.dtype().clone(), array.len(), data)
211 .with_slots(array.slots().iter().cloned().collect()),
212 )
213 }
214
215 fn serialize(
216 array: ArrayView<'_, Self>,
217 _session: &VortexSession,
218 ) -> VortexResult<Option<Vec<u8>>> {
219 Ok(Some(array.metadata.clone().encode_to_vec()))
220 }
221
222 fn deserialize(
223 &self,
224 dtype: &DType,
225 len: usize,
226 metadata: &[u8],
227 buffers: &[BufferHandle],
228 children: &dyn ArrayChildren,
229 _session: &VortexSession,
230 ) -> VortexResult<ArrayParts<Self>> {
231 let metadata = ZstdMetadata::decode(metadata)?;
232 let validity = if children.is_empty() {
233 Validity::from(dtype.nullability())
234 } else if children.len() == 1 {
235 let validity = children.get(0, &Validity::DTYPE, len)?;
236 Validity::Array(validity)
237 } else {
238 vortex_bail!("ZstdArray expected 0 or 1 child, got {}", children.len());
239 };
240
241 let (dictionary_buffer, compressed_buffers) = if metadata.dictionary_size == 0 {
242 (
244 None,
245 buffers
246 .iter()
247 .map(|b| b.clone().try_to_host_sync())
248 .collect::<VortexResult<Vec<_>>>()?,
249 )
250 } else {
251 (
253 Some(buffers[0].clone().try_to_host_sync()?),
254 buffers[1..]
255 .iter()
256 .map(|b| b.clone().try_to_host_sync())
257 .collect::<VortexResult<Vec<_>>>()?,
258 )
259 };
260
261 let slots = smallvec![validity_to_child(&validity, len)];
262 let data = ZstdData::new(dictionary_buffer, compressed_buffers, metadata, len);
263 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
264 }
265
266 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
267 ZstdSlots::NAMES[idx].to_string()
268 }
269
270 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
271 let unsliced_validity = child_to_validity(
272 array.as_ref().slots()[ZstdSlots::VALIDITY].as_ref(),
273 array.dtype().nullability(),
274 );
275 array
276 .data()
277 .decompress(array.dtype(), &unsliced_validity, ctx)?
278 .execute::<ArrayRef>(ctx)
279 .map(ExecutionResult::done)
280 }
281
282 fn append_to_builder(
283 array: ArrayView<'_, Self>,
284 builder: &mut dyn ArrayBuilder,
285 ctx: &mut ExecutionCtx,
286 ) -> VortexResult<()> {
287 if let Some(result) =
288 match_each_varbin_builder!(builder, |builder| append_to_varbin(array, builder, ctx))
289 {
290 return result;
291 }
292 let Some(builder) = builder.as_any_mut().downcast_mut::<VarBinViewBuilder>() else {
297 vortex_bail!("append_to_builder for Zstd requires a variable-binary builder")
298 };
299 append_to_varbinview(array, builder, ctx)
300 }
301
302 fn reduce_parent(
303 array: ArrayView<'_, Self>,
304 parent: &ArrayRef,
305 child_idx: usize,
306 ) -> VortexResult<Option<ArrayRef>> {
307 crate::rules::RULES.evaluate(array, parent, child_idx)
308 }
309}
310
311fn unsliced_validity(array: ArrayView<'_, Zstd>) -> Validity {
312 child_to_validity(
313 array.slots()[ZstdSlots::VALIDITY].as_ref(),
314 array.dtype().nullability(),
315 )
316}
317
318fn append_to_varbin<O: OffsetBuilderPType>(
324 array: ArrayView<'_, Zstd>,
325 builder: &mut VarBinBuilder<O>,
326 ctx: &mut ExecutionCtx,
327) -> VortexResult<()>
328where
329 usize: AsPrimitive<O>,
330{
331 let slice = array
332 .data()
333 .decompress_slice(array.dtype(), &unsliced_validity(array), ctx)?;
334 let mask = slice.validity.execute_mask(slice.n_rows, ctx)?;
335 append_slice_to_varbin(&slice, &mask, builder)
336}
337
338fn append_slice_to_varbin<O: OffsetBuilderPType>(
340 slice: &DecompressedSlice,
341 mask: &Mask,
342 builder: &mut VarBinBuilder<O>,
343) -> VortexResult<()>
344where
345 usize: AsPrimitive<O>,
346{
347 let (values, num_bytes) = slice.value_bytes()?;
348 let mut values = ZstdValues::new(values);
352 builder.append_valid_slices(num_bytes, mask, |_| values.next_value())
353}
354
355fn append_to_varbinview(
361 array: ArrayView<'_, Zstd>,
362 builder: &mut VarBinViewBuilder,
363 ctx: &mut ExecutionCtx,
364) -> VortexResult<()> {
365 let slice = array
366 .data()
367 .decompress_slice(array.dtype(), &unsliced_validity(array), ctx)?;
368 let mask = slice.validity.execute_mask(slice.n_rows, ctx)?;
369
370 if mask.all_false() {
372 builder.append_nulls(slice.n_rows);
373 return Ok(());
374 }
375
376 let value_bytes = slice.bytes.slice(slice.value_byte_range()?);
381 builder.append_views_built_at(&mask, |next_buffer_index| {
384 let (buffers, valid_views) =
385 try_reconstruct_views(&value_bytes, next_buffer_index, MAX_BUFFER_LEN)?;
386 vortex_ensure!(
387 valid_views.len() == mask.true_count(),
388 "Corrupt zstd metadata: the decompressed frames hold {} values for the {} valid rows \
389 of the slice",
390 valid_views.len(),
391 mask.true_count()
392 );
393
394 let views = match mask.bit_buffer() {
395 AllOr::All => valid_views,
396 AllOr::None => unreachable!("handled above"),
397 AllOr::Some(bits) => {
398 let mut views = BufferMut::<BinaryView>::zeroed(slice.n_rows);
402 let mut valid_row = 0;
403 bits.for_each_set_index(|index| {
404 views[index] = valid_views[valid_row];
407 valid_row += 1;
408 });
409 views.freeze()
410 }
411 };
412 Ok((buffers, views))
413 })
414}
415
416#[derive(Clone, Debug)]
417pub struct Zstd;
419
420impl Zstd {
421 pub fn try_new(dtype: DType, data: ZstdData, validity: Validity) -> VortexResult<ZstdArray> {
423 let len = data.len();
424 data.validate(&dtype, len, &validity)?;
425 let slots = smallvec![validity_to_child(&validity, data.unsliced_n_rows())];
426 Ok(unsafe {
427 Array::from_parts_unchecked(ArrayParts::new(Zstd, dtype, len, data).with_slots(slots))
428 })
429 }
430
431 pub fn from_var_bin_view_without_dict(
433 vbv: &VarBinViewArray,
434 level: i32,
435 values_per_frame: usize,
436 ctx: &mut ExecutionCtx,
437 ) -> VortexResult<ZstdArray> {
438 let validity = vbv.validity()?;
439 Self::try_new(
440 vbv.dtype().clone(),
441 ZstdData::from_var_bin_view_without_dict(vbv, level, values_per_frame, ctx)?,
442 validity,
443 )
444 }
445
446 pub fn from_primitive(
448 parray: &PrimitiveArray,
449 level: i32,
450 values_per_frame: usize,
451 ctx: &mut ExecutionCtx,
452 ) -> VortexResult<ZstdArray> {
453 let validity = parray.validity()?;
454 Self::try_new(
455 parray.dtype().clone(),
456 ZstdData::from_primitive(parray, level, values_per_frame, ctx)?,
457 validity,
458 )
459 }
460
461 pub fn from_var_bin_view(
463 vbv: &VarBinViewArray,
464 level: i32,
465 values_per_frame: usize,
466 ctx: &mut ExecutionCtx,
467 ) -> VortexResult<ZstdArray> {
468 let validity = vbv.validity()?;
469 Self::try_new(
470 vbv.dtype().clone(),
471 ZstdData::from_var_bin_view(vbv, level, values_per_frame, ctx)?,
472 validity,
473 )
474 }
475
476 pub fn decompress(array: &ZstdArray, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> {
478 let unsliced_validity = child_to_validity(
479 array.as_ref().slots()[ZstdSlots::VALIDITY].as_ref(),
480 array.dtype().nullability(),
481 );
482 array
483 .data()
484 .decompress(array.dtype(), &unsliced_validity, ctx)
485 }
486}
487
488#[array_slots(Zstd)]
489pub struct ZstdSlots {
490 #[slot(0)]
492 pub validity: Option<ArrayRef>,
493}
494
495#[derive(Clone, Debug)]
496pub struct ZstdData {
498 pub(crate) dictionary: Option<ByteBuffer>,
499 pub(crate) frames: Vec<ByteBuffer>,
500 pub(crate) metadata: ZstdMetadata,
501 unsliced_n_rows: usize,
502 slice_start: usize,
503 slice_stop: usize,
504}
505
506impl Display for ZstdData {
507 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
508 write!(
509 f,
510 "nrows: {}, slice: {}..{}",
511 self.unsliced_n_rows, self.slice_start, self.slice_stop
512 )
513 }
514}
515
516pub struct ZstdDataParts {
518 pub dictionary: Option<ByteBuffer>,
520 pub frames: Vec<ByteBuffer>,
522 pub metadata: ZstdMetadata,
524 pub validity: Validity,
526 pub n_rows: usize,
528 pub slice_start: usize,
530 pub slice_stop: usize,
532}
533
534#[derive(Debug)]
536struct Frames {
537 dictionary: Option<ByteBuffer>,
538 frames: Vec<ByteBuffer>,
539 frame_metas: Vec<ZstdFrameMetadata>,
540}
541
542fn choose_max_dict_size(uncompressed_size: usize) -> usize {
543 (uncompressed_size / 100).clamp(256, 100 * 1024)
548}
549
550fn collect_valid_primitive(
551 parray: &PrimitiveArray,
552 ctx: &mut ExecutionCtx,
553) -> VortexResult<PrimitiveArray> {
554 let mask = parray
555 .as_ref()
556 .validity()?
557 .execute_mask(parray.as_ref().len(), ctx)?;
558 let result = parray.filter(mask)?.execute::<PrimitiveArray>(ctx)?;
559 Ok(result)
560}
561
562fn collect_valid_vbv(
563 vbv: &VarBinViewArray,
564 ctx: &mut ExecutionCtx,
565) -> VortexResult<(ByteBuffer, Vec<usize>)> {
566 let mask = vbv
567 .as_ref()
568 .validity()?
569 .execute_mask(vbv.as_ref().len(), ctx)?;
570 let buffer_and_value_byte_indices = match mask.bit_buffer() {
571 AllOr::None => (Buffer::empty(), Vec::new()),
572 _ => {
573 let mut buffer = BufferMut::with_capacity(
574 usize::try_from(vbv.nbytes()).vortex_expect("must fit into buffer")
575 + mask.true_count() * size_of::<ViewLen>(),
576 );
577 let mut value_byte_indices = Vec::new();
578 let views = vbv.views();
579 let buffers = vbv
580 .data_buffers()
581 .iter()
582 .map(|b| b.as_host())
583 .collect::<Vec<_>>();
584 for (i, view) in views.iter().enumerate() {
586 if !mask.value(i) {
587 continue;
588 }
589 let value = if view.is_inlined() {
590 view.as_inlined().value()
591 } else {
592 let view_ref = view.as_view();
593 &buffers[view_ref.buffer_index as usize][view_ref.as_range()]
594 };
595 value_byte_indices.push(buffer.len());
596 buffer.extend_trusted(ViewLen::try_from(value.len())?.to_le_bytes().into_iter());
598 buffer.extend_from_slice(value);
599 }
600 (buffer.freeze(), value_byte_indices)
601 }
602 };
603 Ok(buffer_and_value_byte_indices)
604}
605
606pub fn reconstruct_views(
615 buffer: &ByteBuffer,
616 start_buf_index: u32,
617 max_buffer_len: usize,
618) -> (Vec<ByteBuffer>, Buffer<BinaryView>) {
619 let (buffers, views, _) = walk_views(buffer, start_buf_index, max_buffer_len);
620 (buffers, views)
621}
622
623fn try_reconstruct_views(
625 buffer: &ByteBuffer,
626 start_buf_index: u32,
627 max_buffer_len: usize,
628) -> VortexResult<(Vec<ByteBuffer>, Buffer<BinaryView>)> {
629 match walk_views(buffer, start_buf_index, max_buffer_len) {
630 (buffers, views, None) => Ok((buffers, views)),
631 (_, _, Some(error)) => Err(error),
632 }
633}
634
635fn walk_views(
638 buffer: &ByteBuffer,
639 start_buf_index: u32,
640 max_buffer_len: usize,
641) -> (Vec<ByteBuffer>, Buffer<BinaryView>, Option<VortexError>) {
642 let mut views = BufferMut::<BinaryView>::empty();
643 let mut buffers = Vec::new();
644 let mut segment_start: usize = 0;
645 let mut offset = 0;
646 let mut buf_index = start_buf_index;
648 let mut error = None;
649
650 while offset < buffer.len() {
651 let str_len = match zstd_value_len(buffer.as_slice(), offset) {
652 Ok(str_len) => str_len,
653 Err(err) => {
654 error = Some(err);
655 break;
656 }
657 };
658
659 let value_data_offset = offset + size_of::<ViewLen>();
660 let local_offset = value_data_offset - segment_start;
661
662 if local_offset + str_len > max_buffer_len && offset > segment_start {
663 buffers.push(buffer.slice(segment_start..offset));
664 segment_start = offset;
665 let Some(next_index) = buf_index.checked_add(1) else {
666 error = Some(vortex_err!("Zstd values need more than u32::MAX buffers"));
667 break;
668 };
669 buf_index = next_index;
670 }
671
672 let Ok(local_offset) = u32::try_from(value_data_offset - segment_start) else {
673 error = Some(vortex_err!(
674 "Zstd value offset {} does not fit in u32; max_buffer_len {max_buffer_len} is too large",
675 value_data_offset - segment_start
676 ));
677 break;
678 };
679 let Some(value) = buffer.get(value_data_offset..value_data_offset + str_len) else {
680 error = Some(vortex_err!(
681 "Corrupt zstd value: {str_len} bytes at offset {value_data_offset} run past the \
682 end of the {} byte frame buffer",
683 buffer.len()
684 ));
685 break;
686 };
687 views.push(BinaryView::make_view(value, buf_index, local_offset));
688 offset = value_data_offset + str_len;
689 }
690
691 if segment_start < buffer.len() {
692 buffers.push(buffer.slice(segment_start..buffer.len()));
693 }
694
695 (buffers, views.freeze(), error)
696}
697
698fn slice_views(
700 views: &Buffer<BinaryView>,
701 range: Range<usize>,
702) -> VortexResult<Buffer<BinaryView>> {
703 vortex_ensure!(
704 range.end <= views.len(),
705 "Corrupt zstd metadata: values {}..{} are out of bounds of the {} values held by the \
706 decompressed frames",
707 range.start,
708 range.end,
709 views.len()
710 );
711 Ok(views.slice(range))
712}
713
714struct UninitDestination<'a> {
722 spare: &'a mut [MaybeUninit<u8>],
723 filled: usize,
724}
725
726impl<'a> UninitDestination<'a> {
727 fn new(spare: &'a mut [MaybeUninit<u8>]) -> Self {
728 Self { spare, filled: 0 }
729 }
730}
731
732unsafe impl WriteBuf for UninitDestination<'_> {
736 fn as_slice(&self) -> &[u8] {
737 unsafe { std::slice::from_raw_parts(self.spare.as_ptr().cast::<u8>(), self.filled) }
739 }
740
741 fn capacity(&self) -> usize {
742 self.spare.len()
743 }
744
745 fn as_mut_ptr(&mut self) -> *mut u8 {
746 self.spare.as_mut_ptr().cast::<u8>()
747 }
748
749 unsafe fn filled_until(&mut self, n: usize) {
750 self.filled = n;
751 }
752}
753
754struct DecompressedSlice {
755 bytes: ByteBuffer,
756 validity: Validity,
757 byte_width: usize,
758 n_rows: usize,
759 value_idx_start: usize,
760 value_idx_stop: usize,
761 n_skipped_values: usize,
762 n_buffered_values: usize,
765}
766
767impl DecompressedSlice {
768 fn value_range(&self) -> VortexResult<Range<usize>> {
773 let start = self
774 .value_idx_start
775 .checked_sub(self.n_skipped_values)
776 .ok_or_else(|| {
777 vortex_err!(
778 "Corrupt zstd metadata: skipped frames hold {} values, past the first \
779 requested value {}",
780 self.n_skipped_values,
781 self.value_idx_start
782 )
783 })?;
784 let end = self
785 .value_idx_stop
786 .checked_sub(self.n_skipped_values)
787 .ok_or_else(|| {
788 vortex_err!(
789 "Corrupt zstd metadata: skipped frames hold {} values, past the last \
790 requested value {}",
791 self.n_skipped_values,
792 self.value_idx_stop
793 )
794 })?;
795 vortex_ensure!(
796 start <= end,
797 "Corrupt zstd metadata: value range {start}..{end} is not ascending"
798 );
799 Ok(start..end)
800 }
801
802 fn value_byte_range(&self) -> VortexResult<Range<usize>> {
812 let Range { start, end } = self.value_range()?;
813 let buffer = self.bytes.as_slice();
814 let from = zstd_value_offset(buffer, 0, start)?;
815 let to = if end == self.n_buffered_values {
816 buffer.len()
817 } else {
818 zstd_value_offset(buffer, from, end - start)?
819 };
820 vortex_ensure!(
821 from <= to && to <= buffer.len(),
822 "Corrupt zstd metadata: values {from}..{to} are out of bounds of the {} byte frame \
823 buffer",
824 buffer.len()
825 );
826 Ok(from..to)
827 }
828
829 fn value_bytes(&self) -> VortexResult<(&[u8], usize)> {
832 let range = self.value_byte_range()?;
833 let n_values = self.value_range()?.len();
834 let buffer = self.bytes.as_slice();
835 let bytes = buffer.get(range.clone()).ok_or_else(|| {
836 vortex_err!(
837 "Corrupt zstd metadata: values {}..{} are out of bounds of the {} byte frame \
838 buffer",
839 range.start,
840 range.end,
841 buffer.len()
842 )
843 })?;
844 let prefix_bytes = n_values.checked_mul(size_of::<ViewLen>()).ok_or_else(|| {
846 vortex_err!("Corrupt zstd metadata: value count {n_values} overflows a byte count")
847 })?;
848 let num_bytes = bytes.len().checked_sub(prefix_bytes).ok_or_else(|| {
849 vortex_err!(
850 "Corrupt zstd metadata: {n_values} values do not fit in the {} bytes holding them",
851 bytes.len()
852 )
853 })?;
854 Ok((bytes, num_bytes))
855 }
856}
857
858fn zstd_value_offset(buffer: &[u8], mut offset: usize, count: usize) -> VortexResult<usize> {
863 for _ in 0..count {
864 offset += size_of::<ViewLen>() + zstd_value_len(buffer, offset)?;
865 }
866 vortex_ensure!(
867 offset <= buffer.len(),
868 "Corrupt zstd values: walking {count} values ended at offset {offset}, past the end of \
869 the {} byte frame buffer",
870 buffer.len()
871 );
872 Ok(offset)
873}
874
875#[inline]
877fn zstd_value_len(buffer: &[u8], offset: usize) -> VortexResult<usize> {
878 let prefix = buffer
879 .get(offset..)
880 .and_then(|rest| rest.first_chunk::<{ size_of::<ViewLen>() }>())
881 .ok_or_else(|| {
882 vortex_err!(
883 "Corrupt zstd values: length prefix at offset {offset} runs past the end of the \
884 {} byte frame buffer",
885 buffer.len()
886 )
887 })?;
888 Ok(ViewLen::from_le_bytes(*prefix) as usize)
889}
890
891struct ZstdValues<'a> {
893 buffer: &'a [u8],
894 offset: usize,
895}
896
897impl<'a> ZstdValues<'a> {
898 fn new(buffer: &'a [u8]) -> Self {
899 Self { buffer, offset: 0 }
900 }
901
902 fn next_value(&mut self) -> &'a [u8] {
910 let value_start = self.offset + size_of::<ViewLen>();
911 let value = zstd_value_len(self.buffer, self.offset)
912 .ok()
913 .and_then(|len| self.buffer.get(value_start..value_start.checked_add(len)?));
914 match value {
915 Some(value) => {
916 self.offset = value_start + value.len();
917 value
918 }
919 None => &self.buffer[self.offset..],
920 }
921 }
922}
923
924impl ZstdData {
925 pub fn new(
927 dictionary: Option<ByteBuffer>,
928 frames: Vec<ByteBuffer>,
929 metadata: ZstdMetadata,
930 n_rows: usize,
931 ) -> Self {
932 Self {
933 dictionary,
934 frames,
935 metadata,
936 unsliced_n_rows: n_rows,
937 slice_start: 0,
938 slice_stop: n_rows,
939 }
940 }
941
942 pub fn validate(&self, dtype: &DType, len: usize, validity: &Validity) -> VortexResult<()> {
944 vortex_ensure!(
945 matches!(
946 dtype,
947 DType::Primitive(..) | DType::Binary(_) | DType::Utf8(_)
948 ),
949 "Unsupported dtype for Zstd array: {dtype}"
950 );
951 vortex_ensure!(
952 self.slice_start <= self.slice_stop,
953 "Invalid slice range {}..{}",
954 self.slice_start,
955 self.slice_stop
956 );
957 vortex_ensure!(
958 self.slice_stop <= self.unsliced_n_rows,
959 "Slice stop {} exceeds unsliced row count {}",
960 self.slice_stop,
961 self.unsliced_n_rows
962 );
963 vortex_ensure!(
964 self.slice_stop - self.slice_start == len,
965 "Slice length {} does not match array length {}",
966 self.slice_stop - self.slice_start,
967 len
968 );
969 if let Some(validity_len) = validity.maybe_len() {
970 vortex_ensure!(
971 validity_len == self.unsliced_n_rows,
972 "Validity length {} does not match unsliced row count {}",
973 validity_len,
974 self.unsliced_n_rows
975 );
976 }
977
978 match &self.dictionary {
979 Some(dictionary) => vortex_ensure!(
980 usize::try_from(self.metadata.dictionary_size)? == dictionary.len(),
981 "Dictionary size metadata {} does not match buffer size {}",
982 self.metadata.dictionary_size,
983 dictionary.len()
984 ),
985 None => vortex_ensure!(
986 self.metadata.dictionary_size == 0,
987 "Dictionary metadata present without dictionary buffer"
988 ),
989 }
990 vortex_ensure!(
991 self.frames.len() == self.metadata.frames.len(),
992 "Frame count {} does not match metadata frame count {}",
993 self.frames.len(),
994 self.metadata.frames.len()
995 );
996 for (index, (frame, metadata)) in self.frames.iter().zip(&self.metadata.frames).enumerate()
997 {
998 validate_frame_content_size(frame.as_slice(), metadata.uncompressed_size, index)?;
999 }
1000
1001 Ok(())
1002 }
1003
1004 pub(crate) fn with_slice(&self, start: usize, stop: usize) -> Self {
1005 let new_start = self.slice_start + start;
1006 let new_stop = self.slice_start + stop;
1007
1008 assert!(
1009 new_start <= self.slice_stop,
1010 "new slice start {new_start} exceeds end {}",
1011 self.slice_stop
1012 );
1013
1014 assert!(
1015 new_stop <= self.slice_stop,
1016 "new slice stop {new_stop} exceeds end {}",
1017 self.slice_stop
1018 );
1019
1020 Self {
1021 slice_start: new_start,
1022 slice_stop: new_stop,
1023 ..self.clone()
1024 }
1025 }
1026
1027 fn compress_values(
1028 value_bytes: &ByteBuffer,
1029 frame_byte_starts: &[usize],
1030 level: i32,
1031 values_per_frame: usize,
1032 n_values: usize,
1033 use_dictionary: bool,
1034 ) -> VortexResult<Frames> {
1035 let n_frames = frame_byte_starts.len();
1036
1037 let mut sample_sizes = Vec::with_capacity(n_frames);
1039 for i in 0..n_frames {
1040 let frame_byte_end = frame_byte_starts
1041 .get(i + 1)
1042 .copied()
1043 .unwrap_or(value_bytes.len());
1044 sample_sizes.push(frame_byte_end - frame_byte_starts[i]);
1045 }
1046 debug_assert_eq!(sample_sizes.iter().sum::<usize>(), value_bytes.len());
1047
1048 let (dictionary, mut compressor) = if !use_dictionary
1049 || sample_sizes.len() < MIN_SAMPLES_FOR_DICTIONARY
1050 {
1051 (None, zstd::bulk::Compressor::new(level)?)
1053 } else {
1054 let max_dict_size = choose_max_dict_size(value_bytes.len());
1056 let dict = zstd::dict::from_continuous(value_bytes, &sample_sizes, max_dict_size)
1057 .map_err(|err| VortexError::from(err).with_context("while training dictionary"))?;
1058
1059 let compressor = zstd::bulk::Compressor::with_dictionary(level, &dict)?;
1060 (Some(ByteBuffer::from(dict)), compressor)
1061 };
1062
1063 let mut frame_metas = vec![];
1064 let mut frames = vec![];
1065 for i in 0..n_frames {
1066 let frame_byte_end = frame_byte_starts
1067 .get(i + 1)
1068 .copied()
1069 .unwrap_or(value_bytes.len());
1070
1071 let uncompressed = &value_bytes.slice(frame_byte_starts[i]..frame_byte_end);
1072 let mut compressed = compressor
1073 .compress(uncompressed)
1074 .map_err(|err| VortexError::from(err).with_context("while compressing"))?;
1075 compressed.shrink_to_fit();
1076 frame_metas.push(ZstdFrameMetadata {
1077 uncompressed_size: uncompressed.len() as u64,
1078 n_values: values_per_frame.min(n_values - i * values_per_frame) as u64,
1079 });
1080 frames.push(ByteBuffer::from(compressed));
1081 }
1082
1083 Ok(Frames {
1084 dictionary,
1085 frames,
1086 frame_metas,
1087 })
1088 }
1089
1090 pub fn from_primitive(
1097 parray: &PrimitiveArray,
1098 level: i32,
1099 values_per_frame: usize,
1100 ctx: &mut ExecutionCtx,
1101 ) -> VortexResult<Self> {
1102 Self::from_primitive_impl(parray, level, values_per_frame, true, ctx)
1103 }
1104
1105 pub fn from_primitive_without_dict(
1119 parray: &PrimitiveArray,
1120 level: i32,
1121 values_per_frame: usize,
1122 ctx: &mut ExecutionCtx,
1123 ) -> VortexResult<Self> {
1124 Self::from_primitive_impl(parray, level, values_per_frame, false, ctx)
1125 }
1126
1127 fn from_primitive_impl(
1128 parray: &PrimitiveArray,
1129 level: i32,
1130 values_per_frame: usize,
1131 use_dictionary: bool,
1132 ctx: &mut ExecutionCtx,
1133 ) -> VortexResult<Self> {
1134 let byte_width = parray.ptype().byte_width();
1135
1136 let values = collect_valid_primitive(parray, ctx)?;
1138 let n_values = values.len();
1139 let values_per_frame = if values_per_frame > 0 {
1140 values_per_frame
1141 } else {
1142 n_values
1143 };
1144
1145 let value_bytes = values.buffer_handle().try_to_host_sync()?;
1146 let alignment = value_bytes.alignment().as_usize();
1148 let step_width = (values_per_frame * byte_width).div_ceil(alignment) * alignment;
1149
1150 let frame_byte_starts = (0..n_values * byte_width)
1151 .step_by(step_width)
1152 .collect::<Vec<_>>();
1153 let Frames {
1154 dictionary,
1155 frames,
1156 frame_metas,
1157 } = Self::compress_values(
1158 &value_bytes,
1159 &frame_byte_starts,
1160 level,
1161 values_per_frame,
1162 n_values,
1163 use_dictionary,
1164 )?;
1165
1166 let metadata = ZstdMetadata {
1167 dictionary_size: dictionary
1168 .as_ref()
1169 .map_or(0, |dict| dict.len())
1170 .try_into()?,
1171 frames: frame_metas,
1172 };
1173
1174 Ok(ZstdData::new(dictionary, frames, metadata, parray.len()))
1175 }
1176
1177 pub fn from_var_bin_view(
1184 vbv: &VarBinViewArray,
1185 level: i32,
1186 values_per_frame: usize,
1187 ctx: &mut ExecutionCtx,
1188 ) -> VortexResult<Self> {
1189 Self::from_var_bin_view_impl(vbv, level, values_per_frame, true, ctx)
1190 }
1191
1192 pub fn from_var_bin_view_without_dict(
1206 vbv: &VarBinViewArray,
1207 level: i32,
1208 values_per_frame: usize,
1209 ctx: &mut ExecutionCtx,
1210 ) -> VortexResult<Self> {
1211 Self::from_var_bin_view_impl(vbv, level, values_per_frame, false, ctx)
1212 }
1213
1214 fn from_var_bin_view_impl(
1215 vbv: &VarBinViewArray,
1216 level: i32,
1217 values_per_frame: usize,
1218 use_dictionary: bool,
1219 ctx: &mut ExecutionCtx,
1220 ) -> VortexResult<Self> {
1221 let (value_bytes, value_byte_indices) = collect_valid_vbv(vbv, ctx)?;
1228 let n_values = value_byte_indices.len();
1229 let values_per_frame = if values_per_frame > 0 {
1230 values_per_frame
1231 } else {
1232 n_values
1233 };
1234
1235 let frame_byte_starts = (0..n_values)
1236 .step_by(values_per_frame)
1237 .map(|i| value_byte_indices[i])
1238 .collect::<Vec<_>>();
1239 let Frames {
1240 dictionary,
1241 frames,
1242 frame_metas,
1243 } = Self::compress_values(
1244 &value_bytes,
1245 &frame_byte_starts,
1246 level,
1247 values_per_frame,
1248 n_values,
1249 use_dictionary,
1250 )?;
1251
1252 let metadata = ZstdMetadata {
1253 dictionary_size: dictionary
1254 .as_ref()
1255 .map_or(0, |dict| dict.len())
1256 .try_into()?,
1257 frames: frame_metas,
1258 };
1259 Ok(ZstdData::new(dictionary, frames, metadata, vbv.len()))
1260 }
1261
1262 pub fn from_canonical(
1266 canonical: &Canonical,
1267 level: i32,
1268 values_per_frame: usize,
1269 ctx: &mut ExecutionCtx,
1270 ) -> VortexResult<Option<Self>> {
1271 match canonical {
1272 Canonical::Primitive(parray) => Ok(Some(ZstdData::from_primitive(
1273 parray,
1274 level,
1275 values_per_frame,
1276 ctx,
1277 )?)),
1278 Canonical::VarBinView(vbv) => Ok(Some(ZstdData::from_var_bin_view(
1279 vbv,
1280 level,
1281 values_per_frame,
1282 ctx,
1283 )?)),
1284 _ => Ok(None),
1285 }
1286 }
1287
1288 pub fn from_array(
1294 array: ArrayRef,
1295 level: i32,
1296 values_per_frame: usize,
1297 ctx: &mut ExecutionCtx,
1298 ) -> VortexResult<Self> {
1299 let canonical = array.execute::<Canonical>(ctx)?;
1300 Self::from_canonical(&canonical, level, values_per_frame, ctx)?
1301 .ok_or_else(|| vortex_err!("Zstd can only encode Primitive and VarBinView arrays"))
1302 }
1303
1304 fn byte_width(dtype: &DType) -> usize {
1305 if dtype.is_primitive() {
1306 dtype.as_ptype().byte_width()
1307 } else {
1308 1
1309 }
1310 }
1311
1312 fn decompress_slice(
1313 &self,
1314 dtype: &DType,
1315 unsliced_validity: &Validity,
1316 ctx: &mut ExecutionCtx,
1317 ) -> VortexResult<DecompressedSlice> {
1318 let byte_width = Self::byte_width(dtype);
1321 let slice_n_rows = self.slice_stop - self.slice_start;
1322 let unsliced_mask = unsliced_validity.execute_mask(self.unsliced_n_rows, ctx)?;
1323 let slice_value_indices =
1324 unsliced_mask.valid_counts_for_indices(&[self.slice_start, self.slice_stop]);
1325
1326 let slice_value_idx_start = slice_value_indices[0];
1327 let slice_value_idx_stop = slice_value_indices[1];
1328
1329 let mut frames_to_decompress = vec![];
1330 let mut value_idx_start = 0;
1331 let mut uncompressed_size_to_decompress = 0usize;
1332 let mut n_skipped_values = 0;
1333 let mut n_buffered_values = 0;
1334 for (frame, frame_meta) in self.frames.iter().zip(&self.metadata.frames) {
1335 if value_idx_start >= slice_value_idx_stop {
1336 break;
1337 }
1338
1339 let frame_uncompressed_size =
1340 usize::try_from(frame_meta.uncompressed_size).map_err(|_| {
1341 vortex_err!(
1342 "Zstd frame uncompressed size {} does not fit in a usize",
1343 frame_meta.uncompressed_size
1344 )
1345 })?;
1346 let frame_n_values = if frame_meta.n_values != 0 {
1347 usize::try_from(frame_meta.n_values).map_err(|_| {
1348 vortex_err!(
1349 "Zstd frame value count {} does not fit in a usize",
1350 frame_meta.n_values
1351 )
1352 })?
1353 } else if dtype.is_primitive() {
1354 frame_uncompressed_size / byte_width
1357 } else {
1358 vortex_ensure!(
1362 self.frames.len() == 1,
1363 "Zstd frame metadata for a variable-width array is missing its value count"
1364 );
1365 unsliced_mask.true_count()
1366 };
1367
1368 let value_idx_stop = value_idx_start.checked_add(frame_n_values).ok_or_else(|| {
1371 vortex_err!("Corrupt zstd metadata: frame value counts overflow a usize")
1372 })?;
1373 if value_idx_stop > slice_value_idx_start {
1374 frames_to_decompress.push(frame);
1376 uncompressed_size_to_decompress = uncompressed_size_to_decompress
1377 .checked_add(frame_uncompressed_size)
1378 .ok_or_else(|| {
1379 vortex_err!("Corrupt zstd metadata: frame sizes overflow a usize")
1380 })?;
1381 n_buffered_values += frame_n_values;
1382 } else {
1383 n_skipped_values += frame_n_values;
1384 }
1385 value_idx_start = value_idx_stop;
1386 }
1387
1388 let mut decompressor = if let Some(dictionary) = &self.dictionary {
1390 zstd::bulk::Decompressor::with_dictionary(dictionary)?
1391 } else {
1392 zstd::bulk::Decompressor::new()?
1393 };
1394 let mut decompressed = ByteBufferMut::with_capacity_aligned(
1395 uncompressed_size_to_decompress,
1396 Alignment::new(byte_width),
1397 );
1398 let mut uncompressed_start = 0;
1399 for frame in frames_to_decompress {
1400 let mut destination = UninitDestination::new(
1404 &mut decompressed.spare_capacity_mut()
1405 [uncompressed_start..uncompressed_size_to_decompress],
1406 );
1407 uncompressed_start +=
1408 decompressor.decompress_to_buffer(frame.as_slice(), &mut destination)?;
1409 }
1410 if uncompressed_start != uncompressed_size_to_decompress {
1411 vortex_bail!(
1412 "Zstd metadata or frames were corrupt; expected {} bytes but decompressed {}",
1413 uncompressed_size_to_decompress,
1414 uncompressed_start
1415 );
1416 }
1417 unsafe { decompressed.set_len(uncompressed_start) };
1420
1421 let decompressed = decompressed.freeze();
1422 let mut slice_validity = unsliced_validity.slice(self.slice_start..self.slice_stop)?;
1424
1425 if !dtype.is_nullable() && !matches!(slice_validity, Validity::NonNullable) {
1434 vortex_ensure!(
1435 matches!(slice_validity, Validity::AllValid),
1436 "ZSTD array expects to be non-nullable but there are nulls after decompression"
1437 );
1438
1439 slice_validity = Validity::NonNullable;
1440 } else if dtype.is_nullable() && matches!(slice_validity, Validity::NonNullable) {
1441 slice_validity = Validity::AllValid;
1442 }
1443 Ok(DecompressedSlice {
1447 bytes: decompressed,
1448 validity: slice_validity,
1449 byte_width,
1450 n_rows: slice_n_rows,
1451 value_idx_start: slice_value_idx_start,
1452 value_idx_stop: slice_value_idx_stop,
1453 n_skipped_values,
1454 n_buffered_values,
1455 })
1456 }
1457
1458 fn decompress(
1459 &self,
1460 dtype: &DType,
1461 unsliced_validity: &Validity,
1462 ctx: &mut ExecutionCtx,
1463 ) -> VortexResult<ArrayRef> {
1464 let slice = self.decompress_slice(dtype, unsliced_validity, ctx)?;
1465 match dtype {
1466 DType::Primitive(..) => {
1467 let Range { start, end } = slice.value_range()?;
1468 let byte_range = start
1469 .checked_mul(slice.byte_width)
1470 .zip(end.checked_mul(slice.byte_width))
1471 .filter(|(_, byte_stop)| *byte_stop <= slice.bytes.len())
1472 .map(|(byte_start, byte_stop)| byte_start..byte_stop)
1473 .ok_or_else(|| {
1474 vortex_err!(
1475 "Corrupt zstd metadata: values {start}..{end} of {} bytes each are \
1476 out of bounds of the {} byte frame buffer",
1477 slice.byte_width,
1478 slice.bytes.len()
1479 )
1480 })?;
1481 let slice_values_buffer = slice.bytes.slice(byte_range);
1482 let primitive = PrimitiveArray::from_values_byte_buffer(
1483 slice_values_buffer,
1484 dtype.as_ptype(),
1485 slice.validity,
1486 slice.n_rows,
1487 ctx,
1488 );
1489
1490 Ok(primitive.into_array())
1491 }
1492 DType::Binary(_) | DType::Utf8(_) => {
1493 match slice.validity.execute_mask(slice.n_rows, ctx)?.indices() {
1494 AllOr::All => {
1495 let (buffers, all_views) =
1496 try_reconstruct_views(&slice.bytes, 0, MAX_BUFFER_LEN)?;
1497 let valid_views = slice_views(&all_views, slice.value_range()?)?;
1498
1499 Ok(unsafe {
1501 VarBinViewArray::new_unchecked(
1502 valid_views,
1503 Arc::from(buffers),
1504 dtype.clone(),
1505 slice.validity,
1506 )
1507 }
1508 .into_array())
1509 }
1510 AllOr::None => Ok(ConstantArray::new(
1511 Scalar::null(dtype.clone()),
1512 slice.n_rows,
1513 )
1514 .into_array()),
1515 AllOr::Some(valid_indices) => {
1516 let (buffers, all_views) =
1517 try_reconstruct_views(&slice.bytes, 0, MAX_BUFFER_LEN)?;
1518 let valid_views = slice_views(&all_views, slice.value_range()?)?;
1519
1520 let mut views = BufferMut::<BinaryView>::zeroed(slice.n_rows);
1521 for (view, index) in valid_views.into_iter().zip_eq(valid_indices) {
1522 views[*index] = view
1523 }
1524
1525 Ok(unsafe {
1527 VarBinViewArray::new_unchecked(
1528 views.freeze(),
1529 Arc::from(buffers),
1530 dtype.clone(),
1531 slice.validity,
1532 )
1533 }
1534 .into_array())
1535 }
1536 }
1537 }
1538 _ => vortex_bail!("Unsupported dtype for Zstd array: {}", dtype),
1539 }
1540 }
1541
1542 #[inline]
1544 pub fn len(&self) -> usize {
1545 self.slice_stop - self.slice_start
1546 }
1547
1548 #[inline]
1550 pub fn is_empty(&self) -> bool {
1551 self.slice_stop == self.slice_start
1552 }
1553
1554 pub fn into_parts(self, validity: Validity) -> ZstdDataParts {
1556 ZstdDataParts {
1557 dictionary: self.dictionary,
1558 frames: self.frames,
1559 metadata: self.metadata,
1560 validity,
1561 n_rows: self.unsliced_n_rows,
1562 slice_start: self.slice_start,
1563 slice_stop: self.slice_stop,
1564 }
1565 }
1566
1567 pub(crate) fn slice_start(&self) -> usize {
1568 self.slice_start
1569 }
1570
1571 pub(crate) fn slice_stop(&self) -> usize {
1572 self.slice_stop
1573 }
1574
1575 pub(crate) fn unsliced_n_rows(&self) -> usize {
1576 self.unsliced_n_rows
1577 }
1578}
1579
1580impl ValidityVTable<Zstd> for Zstd {
1581 fn validity(array: ArrayView<'_, Zstd>) -> VortexResult<Validity> {
1582 let unsliced_validity = child_to_validity(
1583 array.slots()[ZstdSlots::VALIDITY].as_ref(),
1584 array.dtype().nullability(),
1585 );
1586 unsliced_validity.slice(array.slice_start()..array.slice_stop())
1587 }
1588}
1589
1590impl OperationsVTable<Zstd> for Zstd {
1591 fn scalar_at(
1592 array: ArrayView<'_, Zstd>,
1593 index: usize,
1594 ctx: &mut ExecutionCtx,
1595 ) -> VortexResult<Scalar> {
1596 let unsliced_validity = child_to_validity(
1597 array.slots()[ZstdSlots::VALIDITY].as_ref(),
1598 array.dtype().nullability(),
1599 );
1600 let sliced = array.data().with_slice(index, index + 1);
1601 sliced
1602 .decompress(array.dtype(), &unsliced_validity, ctx)?
1603 .execute_scalar(0, ctx)
1604 }
1605}
1606
1607#[cfg(test)]
1608#[expect(clippy::cast_possible_truncation)]
1609mod tests {
1610 use rstest::rstest;
1611 use vortex_array::arrays::varbin::VarBinArrayExt as _;
1612 use vortex_array::builders::VarBinBuilder;
1613 use vortex_array::dtype::DType;
1614 use vortex_array::dtype::Nullability::NonNullable;
1615 use vortex_array::validity::Validity;
1616 use vortex_buffer::ByteBuffer;
1617 use vortex_error::VortexResult;
1618 use vortex_mask::Mask;
1619
1620 use super::DecompressedSlice;
1621 use super::ViewLen;
1622 use super::append_slice_to_varbin;
1623 use super::reconstruct_views;
1624 use super::try_reconstruct_views;
1625 use super::zstd_value_len;
1626 use super::zstd_value_offset;
1627 use crate::array::BinaryView;
1628
1629 fn make_interleaved(strings: &[&[u8]]) -> ByteBuffer {
1631 let mut buf = Vec::new();
1632 for s in strings {
1633 let len = s.len() as u32;
1634 buf.extend_from_slice(&len.to_le_bytes());
1635 buf.extend_from_slice(s);
1636 }
1637 ByteBuffer::copy_from(buf.as_slice())
1638 }
1639
1640 fn decompressed_slice(
1642 bytes: ByteBuffer,
1643 value_idx_start: usize,
1644 value_idx_stop: usize,
1645 n_skipped_values: usize,
1646 n_buffered_values: usize,
1647 ) -> DecompressedSlice {
1648 DecompressedSlice {
1649 bytes,
1650 validity: Validity::NonNullable,
1651 byte_width: 1,
1652 n_rows: value_idx_stop - value_idx_start,
1653 value_idx_start,
1654 value_idx_stop,
1655 n_skipped_values,
1656 n_buffered_values,
1657 }
1658 }
1659
1660 #[test]
1661 fn test_reconstruct_views_no_split() {
1662 let strings: &[&[u8]] = &[b"hello", b"world"];
1663 let buf = make_interleaved(strings);
1664 let (buffers, views) = reconstruct_views(&buf, 0, 1024);
1665
1666 assert_eq!(buffers.len(), 1);
1667 assert_eq!(views.len(), 2);
1668 assert_eq!(views[0], BinaryView::make_view(b"hello", 0, 4));
1670 assert_eq!(views[1], BinaryView::make_view(b"world", 0, 13));
1671 }
1672
1673 #[test]
1674 fn test_reconstruct_views_split_across_segments() {
1675 let strings: &[&[u8]] = &[b"aaaaaaaaaaaaa", b"bbbbbbbbbbbbb"];
1680 let buf = make_interleaved(strings);
1681 let (buffers, views) = reconstruct_views(&buf, 0, 20);
1682
1683 assert_eq!(buffers.len(), 2);
1684 assert_eq!(views.len(), 2);
1685 assert_eq!(views[0], BinaryView::make_view(b"aaaaaaaaaaaaa", 0, 4));
1686 assert_eq!(views[1], BinaryView::make_view(b"bbbbbbbbbbbbb", 1, 4));
1688 }
1689
1690 fn make_overrunning() -> ByteBuffer {
1692 let mut buf = Vec::new();
1693 buf.extend_from_slice(&5u32.to_le_bytes());
1694 buf.extend_from_slice(b"hello");
1695 buf.extend_from_slice(&9u32.to_le_bytes());
1696 buf.extend_from_slice(b"ab");
1697 ByteBuffer::copy_from(buf.as_slice())
1698 }
1699
1700 #[test]
1701 fn test_reconstruct_views_rejects_overrunning_value() {
1702 let buf = make_overrunning();
1703 assert!(try_reconstruct_views(&buf, 0, 1024).is_err());
1704
1705 let (buffers, views) = reconstruct_views(&buf, 0, 1024);
1707 assert_eq!(buffers.len(), 1);
1708 assert_eq!(views.len(), 1);
1709 assert_eq!(views[0], BinaryView::make_view(b"hello", 0, 4));
1710 }
1711
1712 #[test]
1713 fn test_reconstruct_views_rejects_truncated_length_prefix() {
1714 let buf =
1716 ByteBuffer::copy_from([5u8, 0, 0, 0, b'h', b'e', b'l', b'l', b'o', 1, 0].as_ref());
1717 assert!(try_reconstruct_views(&buf, 0, 1024).is_err());
1718 assert_eq!(reconstruct_views(&buf, 0, 1024).1.len(), 1);
1719 }
1720
1721 #[rstest]
1722 #[case::truncated_buffer(&[0u8, 0, 0], 0)]
1723 #[case::truncated_tail(&[4u8, 0, 0, 0], 2)]
1724 #[case::offset_at_end(&[4u8, 0, 0, 0], 4)]
1725 #[case::offset_past_end(&[4u8, 0, 0, 0], 64)]
1726 fn test_zstd_value_len_rejects_out_of_bounds(#[case] buffer: &[u8], #[case] offset: usize) {
1727 assert!(zstd_value_len(buffer, offset).is_err());
1728 }
1729
1730 #[test]
1731 fn test_zstd_value_offset_rejects_walking_past_the_end() -> VortexResult<()> {
1732 let buf = make_interleaved(&[b"hello", b"world"]);
1733 assert_eq!(zstd_value_offset(buf.as_slice(), 0, 2)?, buf.len());
1734 assert!(zstd_value_offset(buf.as_slice(), 0, 3).is_err());
1736 Ok(())
1737 }
1738
1739 #[test]
1740 fn test_value_range_rejects_skipping_past_the_requested_values() {
1741 let slice = decompressed_slice(make_interleaved(&[b"hello"]), 2, 3, 4, 1);
1743 assert!(slice.value_range().is_err());
1744 assert!(slice.value_bytes().is_err());
1745 }
1746
1747 #[rstest]
1748 #[case::exact_metadata(2)]
1750 #[case::walked_end(9)]
1752 fn test_value_bytes_totals_the_stored_values(
1753 #[case] n_buffered_values: usize,
1754 ) -> VortexResult<()> {
1755 let buf = make_interleaved(&[b"hello", b"world"]);
1756 let slice = decompressed_slice(buf.clone(), 0, 2, 0, n_buffered_values);
1757 let (bytes, num_bytes) = slice.value_bytes()?;
1758 assert_eq!(bytes, buf.as_slice());
1759 assert_eq!(num_bytes, buf.len() - 2 * size_of::<ViewLen>());
1760 Ok(())
1761 }
1762
1763 #[test]
1764 fn test_value_bytes_rejects_more_values_than_the_buffer_holds() {
1765 let slice = decompressed_slice(make_interleaved(&[b"hello", b"world"]), 0, 5, 0, 9);
1767 assert!(slice.value_bytes().is_err());
1768 }
1769
1770 #[test]
1771 fn test_append_to_varbin_copies_the_stored_values() -> VortexResult<()> {
1772 let slice = decompressed_slice(make_interleaved(&[b"hello", b"world"]), 0, 2, 0, 2);
1773 let mut builder = VarBinBuilder::<i32>::new_in(
1774 DType::Utf8(NonNullable),
1775 vortex_buffer::BufferAllocatorRef::static_ref(),
1776 );
1777 append_slice_to_varbin(&slice, &Mask::new_true(2), &mut builder)?;
1778
1779 let appended = builder.finish_into_varbin();
1780 assert_eq!(appended.bytes_at(0).as_slice(), b"hello");
1781 assert_eq!(appended.bytes_at(1).as_slice(), b"world");
1782 Ok(())
1783 }
1784
1785 #[test]
1786 fn test_append_to_varbin_rejects_a_dangling_length_prefix() {
1787 let mut buffer = Vec::new();
1788 buffer.extend_from_slice(&3u32.to_le_bytes());
1789 buffer.extend_from_slice(b"cat");
1790 buffer.extend_from_slice(&1u32.to_le_bytes());
1794
1795 let slice = decompressed_slice(ByteBuffer::copy_from(buffer.as_slice()), 0, 2, 0, 2);
1796 let mut builder = VarBinBuilder::<i32>::new_in(
1797 DType::Utf8(NonNullable),
1798 vortex_buffer::BufferAllocatorRef::static_ref(),
1799 );
1800 assert!(append_slice_to_varbin(&slice, &Mask::new_true(2), &mut builder).is_err());
1801 assert_eq!(builder.finish_into_varbin().len(), 0);
1803 }
1804}