1use std::any::Any;
5use std::collections::VecDeque;
6use std::fmt::Debug;
7use std::ops::Range;
8use std::sync::Arc;
9use std::vec;
10
11use arrow_buffer::ScalarBuffer;
12use bytes::Bytes;
13use futures::FutureExt;
14use futures::future::BoxFuture;
15use itertools::Itertools;
16use lance_core::Result;
17use lance_core::cache::{Context, DeepSizeOf};
18use lance_core::error::Error;
19
20use crate::EncodingsIo;
21use crate::buffer::LanceBuffer;
22use crate::compression::{
23 DecompressionStrategy, FixedPerValueDecompressor, VariablePerValueDecompressor,
24};
25use crate::data::{BlockInfo, DataBlockBuilder, FixedWidthDataBlock, VariableWidthBlock};
26use crate::decoder::{DecodePageTask, DecodedPage, StructuralPageDecoder};
27use crate::encodings::logical::primitive::fullzip::PerValueDataBlock;
28use crate::format::{EncodingUtils, metadata};
29use crate::repdef::{
30 ControlWordParser, DefinitionInterpretation, RepDefUnraveler, max_visible_level,
31};
32use crate::utils::bytepack::ByteUnpacker;
33
34use super::{
35 CachedPageData, NoCachedPageData, PageInitialization, PageInitializationBuffers, PageLoadTask,
36 StructuralPageScheduler,
37};
38
39#[derive(Debug, Clone, Copy)]
40struct FullZipRepIndexDetails {
41 buf_position: u64,
42 bytes_per_value: u64, }
44
45#[derive(Debug)]
46enum PerValueDecompressor {
47 Fixed(Arc<dyn FixedPerValueDecompressor>),
48 Variable(Arc<dyn VariablePerValueDecompressor>),
49}
50
51#[derive(Debug)]
52struct FullZipDecodeDetails {
53 value_decompressor: PerValueDecompressor,
54 def_meaning: Arc<[DefinitionInterpretation]>,
55 ctrl_word_parser: ControlWordParser,
56 max_rep: u16,
57 max_visible_def: u16,
58}
59
60#[derive(Debug, Clone)]
72enum FullZipReadSource {
73 Remote(Arc<dyn EncodingsIo>),
75 PrefetchedPage { base_offset: u64, data: LanceBuffer },
77}
78
79impl FullZipReadSource {
80 fn fetch(
84 &self,
85 ranges: &[Range<u64>],
86 priority: u64,
87 ) -> BoxFuture<'static, Result<VecDeque<LanceBuffer>>> {
88 match self {
89 Self::Remote(io) => {
90 let io = io.clone();
91 let ranges = ranges.to_vec();
92 async move {
93 let data = io.submit_request(ranges, priority).await?;
94 Ok(data
95 .into_iter()
96 .map(|bytes| LanceBuffer::from_bytes(bytes, 1))
97 .collect::<VecDeque<_>>())
98 }
99 .boxed()
100 }
101 Self::PrefetchedPage { base_offset, data } => {
102 let base_offset = *base_offset;
103 let data = data.clone();
104 let page_end = base_offset + data.len() as u64;
105 std::future::ready(
106 ranges
107 .iter()
108 .map(|range| {
109 if range.start > range.end
110 || range.start < base_offset
111 || range.end > page_end
112 {
113 return Err(Error::internal(format!(
114 "Requested range {:?} is outside page range {}..{}",
115 range, base_offset, page_end
116 )));
117 }
118 let start = (range.start - base_offset) as usize;
119 let len = (range.end - range.start) as usize;
120 Ok(data.slice_with_length(start, len))
121 })
122 .collect::<Result<VecDeque<_>>>(),
123 )
124 .boxed()
125 }
126 }
127 }
128}
129
130#[derive(Debug)]
138pub struct FullZipScheduler {
139 data_buf_position: u64,
140 pub(super) data_buf_size: u64,
141 rep_index: Option<FullZipRepIndexDetails>,
142 priority: u64,
143 rows_in_page: u64,
144 bits_per_offset: u8,
145 details: Arc<FullZipDecodeDetails>,
146 cached_state: Option<Arc<FullZipCacheableState>>,
148 pub(super) enable_cache: bool,
150}
151
152impl FullZipScheduler {
153 pub(super) fn try_new(
154 buffer_offsets_and_sizes: &[(u64, u64)],
155 priority: u64,
156 rows_in_page: u64,
157 layout: &metadata::FullZipLayout,
158 decompressors: &dyn DecompressionStrategy,
159 ) -> Result<Self> {
160 let (data_buf_position, data_buf_size) = buffer_offsets_and_sizes[0];
161 let rep_index = buffer_offsets_and_sizes.get(1).map(|(pos, len)| {
162 let num_reps = rows_in_page + 1;
163 let bytes_per_rep = len / num_reps;
164 debug_assert_eq!(len % num_reps, 0);
165 debug_assert!(
166 bytes_per_rep == 1
167 || bytes_per_rep == 2
168 || bytes_per_rep == 4
169 || bytes_per_rep == 8
170 );
171 FullZipRepIndexDetails {
172 buf_position: *pos,
173 bytes_per_value: bytes_per_rep,
174 }
175 });
176
177 let value_decompressor = match layout.details {
178 Some(metadata::full_zip_layout::Details::BitsPerValue(_)) => {
179 let decompressor = decompressors.create_fixed_per_value_decompressor(
180 layout.value_compression.as_ref().unwrap(),
181 )?;
182 PerValueDecompressor::Fixed(decompressor.into())
183 }
184 Some(metadata::full_zip_layout::Details::BitsPerOffset(_)) => {
185 let decompressor = decompressors.create_variable_per_value_decompressor(
186 layout.value_compression.as_ref().unwrap(),
187 )?;
188 PerValueDecompressor::Variable(decompressor.into())
189 }
190 None => {
191 panic!("Full-zip layout must have a `details` field");
192 }
193 };
194 let ctrl_word_parser = ControlWordParser::new(
195 layout.bits_rep.try_into().unwrap(),
196 layout.bits_def.try_into().unwrap(),
197 );
198 let def_meaning = layout
199 .layers
200 .iter()
201 .map(|l| EncodingUtils::repdef_layer_to_def_interp(*l))
202 .collect::<Vec<_>>();
203
204 let max_rep = def_meaning.iter().filter(|d| d.is_list()).count() as u16;
205 let max_visible_def = max_visible_level(&def_meaning);
206
207 let bits_per_offset = match layout.details {
208 Some(metadata::full_zip_layout::Details::BitsPerValue(_)) => 32,
209 Some(metadata::full_zip_layout::Details::BitsPerOffset(bits_per_offset)) => {
210 bits_per_offset as u8
211 }
212 None => panic!("Full-zip layout must have a `details` field"),
213 };
214
215 let details = Arc::new(FullZipDecodeDetails {
216 value_decompressor,
217 def_meaning: def_meaning.into(),
218 ctrl_word_parser,
219 max_rep,
220 max_visible_def,
221 });
222 Ok(Self {
223 data_buf_position,
224 data_buf_size,
225 rep_index,
226 details,
227 priority,
228 rows_in_page,
229 bits_per_offset,
230 cached_state: None,
231 enable_cache: false,
232 })
233 }
234
235 fn covers_entire_page(ranges: &[Range<u64>], rows_in_page: u64) -> bool {
236 if ranges.is_empty() {
237 return false;
238 }
239 let mut expected_start = 0;
240 for range in ranges {
241 if range.start != expected_start || range.end > rows_in_page || range.end < range.start
242 {
243 return false;
244 }
245 expected_start = range.end;
246 }
247 expected_start == rows_in_page
248 }
249
250 fn create_page_load_task(
251 io_future: BoxFuture<'static, Result<Vec<Bytes>>>,
252 num_rows: u64,
253 details: Arc<FullZipDecodeDetails>,
254 bits_per_offset: u8,
255 ) -> PageLoadTask {
256 let load_task = async move {
257 let buffers = io_future.await?;
258 let data = buffers
259 .into_iter()
260 .map(|bytes| LanceBuffer::from_bytes(bytes, 1))
261 .collect::<VecDeque<_>>();
262 Self::create_decoder(details, data, num_rows, bits_per_offset)
263 }
264 .boxed();
265 PageLoadTask {
266 decoder_fut: load_task,
267 num_rows,
268 }
269 }
270
271 fn create_decoder(
273 details: Arc<FullZipDecodeDetails>,
274 data: VecDeque<LanceBuffer>,
275 num_rows: u64,
276 bits_per_offset: u8,
277 ) -> Result<Box<dyn StructuralPageDecoder>> {
278 match &details.value_decompressor {
279 PerValueDecompressor::Fixed(decompressor) => {
280 let bits_per_value = decompressor.bits_per_value();
281 if bits_per_value % 8 != 0 {
282 return Err(lance_core::Error::not_supported_source("Bit-packed full-zip encoding (non-byte-aligned values) is not yet implemented".into()));
283 }
284 let bytes_per_value = bits_per_value / 8;
285 let total_bytes_per_value =
286 bytes_per_value as usize + details.ctrl_word_parser.bytes_per_word();
287 Ok(Box::new(FixedFullZipDecoder {
292 details,
293 data,
294 num_rows,
295 offset_in_current: 0,
296 bytes_per_value: bytes_per_value as usize,
297 total_bytes_per_value,
298 }) as Box<dyn StructuralPageDecoder>)
299 }
300 PerValueDecompressor::Variable(_decompressor) => {
301 Ok(Box::new(VariableFullZipDecoder::new(
302 details,
303 data,
304 num_rows,
305 bits_per_offset,
306 bits_per_offset,
307 )?))
308 }
309 }
310 }
311
312 fn extract_byte_ranges_from_pairs(
315 buffer: LanceBuffer,
316 bytes_per_value: u64,
317 data_buf_position: u64,
318 ) -> Vec<Range<u64>> {
319 ByteUnpacker::new(buffer, bytes_per_value as usize)
320 .chunks(2)
321 .into_iter()
322 .map(|mut c| {
323 let start = c.next().unwrap() + data_buf_position;
324 let end = c.next().unwrap() + data_buf_position;
325 start..end
326 })
327 .collect::<Vec<_>>()
328 }
329
330 fn extract_byte_ranges_from_cached(
333 buffer: &LanceBuffer,
334 ranges: &[Range<u64>],
335 bytes_per_value: u64,
336 data_buf_position: u64,
337 ) -> Vec<Range<u64>> {
338 ranges
339 .iter()
340 .map(|r| {
341 let start_offset = (r.start * bytes_per_value) as usize;
342 let end_offset = (r.end * bytes_per_value) as usize;
343
344 let start_slice = &buffer[start_offset..start_offset + bytes_per_value as usize];
345 let start_val =
346 ByteUnpacker::new(start_slice.iter().copied(), bytes_per_value as usize)
347 .next()
348 .unwrap();
349
350 let end_slice = &buffer[end_offset..end_offset + bytes_per_value as usize];
351 let end_val =
352 ByteUnpacker::new(end_slice.iter().copied(), bytes_per_value as usize)
353 .next()
354 .unwrap();
355
356 (data_buf_position + start_val)..(data_buf_position + end_val)
357 })
358 .collect()
359 }
360
361 fn compute_rep_index_ranges(
363 ranges: &[Range<u64>],
364 rep_index: &FullZipRepIndexDetails,
365 ) -> Vec<Range<u64>> {
366 ranges
367 .iter()
368 .flat_map(|r| {
369 let first_val_start =
370 rep_index.buf_position + (r.start * rep_index.bytes_per_value);
371 let first_val_end = first_val_start + rep_index.bytes_per_value;
372 let last_val_start = rep_index.buf_position + (r.end * rep_index.bytes_per_value);
373 let last_val_end = last_val_start + rep_index.bytes_per_value;
374 [first_val_start..first_val_end, last_val_start..last_val_end]
375 })
376 .collect()
377 }
378
379 fn schedule_ranges_rep(
381 &self,
382 ranges: &[Range<u64>],
383 io: &Arc<dyn EncodingsIo>,
384 rep_index: FullZipRepIndexDetails,
385 ) -> Result<Vec<PageLoadTask>> {
386 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
387 let data_buf_position = self.data_buf_position;
388 let priority = self.priority;
389 let details = self.details.clone();
390 let bits_per_offset = self.bits_per_offset;
391
392 if Self::covers_entire_page(ranges, self.rows_in_page) {
393 let full_range = self.data_buf_position..(self.data_buf_position + self.data_buf_size);
394 let page_data = io.submit_single(full_range.clone(), priority);
395 let load_task = async move {
396 let page_data = page_data.await?;
397 let source = FullZipReadSource::PrefetchedPage {
398 base_offset: full_range.start,
399 data: LanceBuffer::from_bytes(page_data, 1),
400 };
401 let read_ranges = vec![full_range];
402 let data = source.fetch(&read_ranges, priority).await?;
403 Self::create_decoder(details, data, num_rows, bits_per_offset)
404 }
405 .boxed();
406 let page_load_task = PageLoadTask {
407 decoder_fut: load_task,
408 num_rows,
409 };
410 return Ok(vec![page_load_task]);
411 }
412
413 if let Some(cached_state) = &self.cached_state {
414 let byte_ranges = Self::extract_byte_ranges_from_cached(
415 &cached_state.rep_index_buffer,
416 ranges,
417 rep_index.bytes_per_value,
418 data_buf_position,
419 );
420 let io_future = io.submit_request(byte_ranges, priority);
421 let page_load_task =
422 Self::create_page_load_task(io_future, num_rows, details, bits_per_offset);
423 return Ok(vec![page_load_task]);
424 }
425
426 let rep_ranges = Self::compute_rep_index_ranges(ranges, &rep_index);
427 let rep_data = io.submit_request(rep_ranges, priority);
428 let io_clone = io.clone();
429 let load_task = async move {
430 let rep_data = rep_data.await?;
431 let rep_buffer = LanceBuffer::concat(
432 &rep_data
433 .into_iter()
434 .map(|d| LanceBuffer::from_bytes(d, 1))
435 .collect::<Vec<_>>(),
436 );
437 let byte_ranges = Self::extract_byte_ranges_from_pairs(
438 rep_buffer,
439 rep_index.bytes_per_value,
440 data_buf_position,
441 );
442 let source = FullZipReadSource::Remote(io_clone);
443 let data = source.fetch(&byte_ranges, priority).await?;
444 Self::create_decoder(details, data, num_rows, bits_per_offset)
445 }
446 .boxed();
447 let page_load_task = PageLoadTask {
448 decoder_fut: load_task,
449 num_rows,
450 };
451 Ok(vec![page_load_task])
452 }
453
454 fn schedule_ranges_simple(
458 &self,
459 ranges: &[Range<u64>],
460 io: &Arc<dyn EncodingsIo>,
461 ) -> Result<Vec<PageLoadTask>> {
462 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
464
465 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor else {
466 unreachable!()
467 };
468
469 let bits_per_value = decompressor.bits_per_value();
471 if !bits_per_value.is_multiple_of(8) {
472 return Err(Error::invalid_input_source(
473 format!(
474 "Full-zip fixed-width values must be byte aligned, got {} bits per value",
475 bits_per_value
476 )
477 .into(),
478 ));
479 }
480 let bytes_per_value = bits_per_value / 8;
481 let bytes_per_cw = self.details.ctrl_word_parser.bytes_per_word();
482 let total_bytes_per_value = bytes_per_value + bytes_per_cw as u64;
483 let byte_ranges = ranges
484 .iter()
485 .map(|r| {
486 debug_assert!(r.end <= self.rows_in_page);
487 let start = self.data_buf_position + r.start * total_bytes_per_value;
488 let end = self.data_buf_position + r.end * total_bytes_per_value;
489 start..end
490 })
491 .collect::<Vec<_>>();
492
493 let io_future = io.submit_request(byte_ranges, self.priority);
494 let page_load_task = Self::create_page_load_task(
495 io_future,
496 num_rows,
497 self.details.clone(),
498 self.bits_per_offset,
499 );
500 Ok(vec![page_load_task])
501 }
502}
503
504#[derive(Debug)]
506struct FullZipCacheableState {
507 rep_index_buffer: LanceBuffer,
509}
510
511impl DeepSizeOf for FullZipCacheableState {
512 fn deep_size_of_children(&self, _context: &mut Context) -> usize {
513 self.rep_index_buffer.len()
514 }
515}
516
517impl CachedPageData for FullZipCacheableState {
518 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
519 self
520 }
521}
522
523impl StructuralPageScheduler for FullZipScheduler {
524 fn needs_initialization(&self) -> bool {
525 self.enable_cache && self.rep_index.is_some()
526 }
527
528 fn init_layout(&self) -> Result<PageInitialization> {
529 let repetition_index_range = if self.enable_cache
530 && let Some(rep_index) = self.rep_index
531 {
532 let total_size = (self.rows_in_page + 1) * rep_index.bytes_per_value;
533 Some(rep_index.buf_position..(rep_index.buf_position + total_size))
534 } else {
535 None
536 };
537 Ok(PageInitialization::new([repetition_index_range]))
538 }
539
540 fn init_from_buffers<'a>(
541 &'a mut self,
542 buffers: PageInitializationBuffers,
543 _io: &Arc<dyn EncodingsIo>,
544 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
545 let [rep_index_buffer] = match buffers.try_into_array("full-zip") {
546 Ok(buffers) => buffers,
547 Err(error) => return std::future::ready(Err(error)).boxed(),
548 };
549 if let Some(rep_index_buffer) = rep_index_buffer {
550 let state = Arc::new(FullZipCacheableState {
551 rep_index_buffer: LanceBuffer::from_bytes(rep_index_buffer, 1),
552 });
553 std::future::ready(Ok(state as Arc<dyn CachedPageData>)).boxed()
554 } else {
555 std::future::ready(Ok(Arc::new(NoCachedPageData) as Arc<dyn CachedPageData>)).boxed()
556 }
557 }
558
559 fn try_load(&mut self, cache: &Arc<dyn CachedPageData>) -> Result<()> {
563 self.cached_state = Some(cache.clone().as_arc_any().downcast().map_err(|_| {
564 Error::invalid_input_source("Cached full-zip page data has an unexpected type".into())
565 })?);
566 Ok(())
567 }
568
569 fn schedule_ranges(
570 &self,
571 ranges: &[Range<u64>],
572 io: &Arc<dyn EncodingsIo>,
573 ) -> Result<Vec<PageLoadTask>> {
574 if let Some(rep_index) = self.rep_index {
575 self.schedule_ranges_rep(ranges, io, rep_index)
576 } else {
577 self.schedule_ranges_simple(ranges, io)
578 }
579 }
580}
581
582#[derive(Debug)]
590struct FixedFullZipDecoder {
591 details: Arc<FullZipDecodeDetails>,
592 data: VecDeque<LanceBuffer>,
593 offset_in_current: usize,
594 bytes_per_value: usize,
595 total_bytes_per_value: usize,
596 num_rows: u64,
597}
598
599impl FixedFullZipDecoder {
600 fn slice_next_task(&mut self, num_rows: u64) -> Result<FullZipDecodeTaskItem> {
601 debug_assert!(num_rows > 0);
602 let cur_buf = self.data.front_mut().ok_or_else(|| {
603 Error::corrupt_file_named(
604 "fixed_full_zip",
605 format!("page data ran out with {num_rows} row(s) left to decode"),
606 )
607 })?;
608 let start = self.offset_in_current;
609 if self.details.ctrl_word_parser.has_rep() {
610 let mut rows_started = 0;
613 let mut num_items = 0;
616 let bytes_per_word = self.details.ctrl_word_parser.bytes_per_word();
617 while self.offset_in_current < cur_buf.len() {
618 let bytes_avail = cur_buf.len() - self.offset_in_current;
623 if bytes_avail < bytes_per_word {
624 return Err(Error::corrupt_file_named(
625 "fixed_full_zip",
626 format!(
627 "truncated control word: {bytes_avail} byte(s) remain in the page \
628 buffer but a control word requires {bytes_per_word}"
629 ),
630 ));
631 }
632 let control = self.details.ctrl_word_parser.parse_desc(
633 &cur_buf[self.offset_in_current..],
634 self.details.max_rep,
635 self.details.max_visible_def,
636 );
637 if control.is_new_row {
638 if rows_started == num_rows {
639 break;
640 }
641 rows_started += 1;
642 }
643 num_items += 1;
644 let bytes_in_item = if control.is_visible {
645 self.total_bytes_per_value
646 } else {
647 bytes_per_word
648 };
649 if bytes_in_item > bytes_avail {
650 return Err(Error::corrupt_file_named(
651 "fixed_full_zip",
652 format!(
653 "truncated item: {bytes_avail} byte(s) remain in the page buffer at \
654 offset {} but the item requires {bytes_in_item}",
655 self.offset_in_current
656 ),
657 ));
658 }
659 self.offset_in_current += bytes_in_item;
660 }
661
662 let task_slice = cur_buf.slice_with_length(start, self.offset_in_current - start);
663 if self.offset_in_current == cur_buf.len() {
664 self.data.pop_front();
665 self.offset_in_current = 0;
666 }
667
668 Ok(FullZipDecodeTaskItem {
669 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
670 data: task_slice,
671 bits_per_value: self.bytes_per_value as u64 * 8,
672 num_values: num_items,
673 block_info: BlockInfo::new(),
674 }),
675 rows_in_buf: rows_started,
676 })
677 } else {
678 let cur_buf = self.data.front_mut().unwrap();
681 let bytes_avail = cur_buf.len() - self.offset_in_current;
682 let offset_in_cur = self.offset_in_current;
683
684 let bytes_needed = num_rows as usize * self.total_bytes_per_value;
685 let mut rows_taken = num_rows;
686 let task_slice = if bytes_needed >= bytes_avail {
687 self.offset_in_current = 0;
688 rows_taken = bytes_avail as u64 / self.total_bytes_per_value as u64;
689 self.data
690 .pop_front()
691 .unwrap()
692 .slice_with_length(offset_in_cur, bytes_avail)
693 } else {
694 self.offset_in_current += bytes_needed;
695 cur_buf.slice_with_length(offset_in_cur, bytes_needed)
696 };
697 Ok(FullZipDecodeTaskItem {
698 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
699 data: task_slice,
700 bits_per_value: self.bytes_per_value as u64 * 8,
701 num_values: rows_taken,
702 block_info: BlockInfo::new(),
703 }),
704 rows_in_buf: rows_taken,
705 })
706 }
707 }
708}
709
710impl StructuralPageDecoder for FixedFullZipDecoder {
711 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
712 if self.total_bytes_per_value == 0 {
713 return Ok(Box::new(FixedFullZipDecodeTask {
716 details: self.details.clone(),
717 data: vec![FullZipDecodeTaskItem {
718 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
719 data: LanceBuffer::empty(),
720 bits_per_value: 0,
721 num_values: num_rows,
722 block_info: BlockInfo::new(),
723 }),
724 rows_in_buf: num_rows,
725 }],
726 bytes_per_value: 0,
727 num_rows: num_rows as usize,
728 }));
729 }
730 let mut task_data = Vec::with_capacity(self.data.len());
731 let mut remaining = num_rows;
732 while remaining > 0 {
733 let task_item = self.slice_next_task(remaining)?;
734 remaining -= task_item.rows_in_buf;
735 task_data.push(task_item);
736 }
737 Ok(Box::new(FixedFullZipDecodeTask {
738 details: self.details.clone(),
739 data: task_data,
740 bytes_per_value: self.bytes_per_value,
741 num_rows: num_rows as usize,
742 }))
743 }
744
745 fn num_rows(&self) -> u64 {
746 self.num_rows
747 }
748}
749
750#[derive(Debug)]
755struct VariableFullZipDecoder {
756 details: Arc<FullZipDecodeDetails>,
757 decompressor: Arc<dyn VariablePerValueDecompressor>,
758 data: LanceBuffer,
759 offsets: LanceBuffer,
760 rep: ScalarBuffer<u16>,
761 def: ScalarBuffer<u16>,
762 repdef_starts: Vec<usize>,
763 data_starts: Vec<usize>,
764 offset_starts: Vec<usize>,
765 visible_item_counts: Vec<u64>,
766 bits_per_offset: u8,
767 current_idx: usize,
768 num_rows: u64,
769}
770
771impl VariableFullZipDecoder {
772 fn new(
773 details: Arc<FullZipDecodeDetails>,
774 data: VecDeque<LanceBuffer>,
775 num_rows: u64,
776 in_bits_per_length: u8,
777 out_bits_per_offset: u8,
778 ) -> Result<Self> {
779 let decompressor = match details.value_decompressor {
780 PerValueDecompressor::Variable(ref d) => d.clone(),
781 _ => unreachable!(),
782 };
783
784 assert_eq!(in_bits_per_length % 8, 0);
785 assert!(out_bits_per_offset == 32 || out_bits_per_offset == 64);
786
787 let mut decoder = Self {
788 details,
789 decompressor,
790 data: LanceBuffer::empty(),
791 offsets: LanceBuffer::empty(),
792 rep: LanceBuffer::empty().borrow_to_typed_slice(),
793 def: LanceBuffer::empty().borrow_to_typed_slice(),
794 bits_per_offset: out_bits_per_offset,
795 repdef_starts: Vec::with_capacity(num_rows as usize + 1),
796 data_starts: Vec::with_capacity(num_rows as usize + 1),
797 offset_starts: Vec::with_capacity(num_rows as usize + 1),
798 visible_item_counts: Vec::with_capacity(num_rows as usize + 1),
799 current_idx: 0,
800 num_rows,
801 };
802
803 decoder.unzip(data, in_bits_per_length, out_bits_per_offset, num_rows)?;
824
825 Ok(decoder)
826 }
827
828 fn slice_batch_data_and_rebase_offsets_typed<T>(
829 data: &LanceBuffer,
830 offsets: &LanceBuffer,
831 ) -> Result<(LanceBuffer, LanceBuffer)>
832 where
833 T: arrow_buffer::ArrowNativeType
834 + Copy
835 + PartialOrd
836 + std::ops::Sub<Output = T>
837 + std::fmt::Display
838 + TryInto<usize>,
839 {
840 let offsets_slice = offsets.borrow_to_typed_slice::<T>();
841 let offsets_slice = offsets_slice.as_ref();
842 if offsets_slice.is_empty() {
843 return Err(Error::internal(
844 "Variable offsets cannot be empty".to_string(),
845 ));
846 }
847
848 let base = offsets_slice[0];
849 let end = *offsets_slice.last().unwrap();
850 if end < base {
851 return Err(Error::internal(format!(
852 "Invalid variable offsets: end ({end}) is less than base ({base})"
853 )));
854 }
855
856 let data_start = base.try_into().map_err(|_| {
857 Error::internal(format!("Variable offset ({base}) does not fit into usize"))
858 })?;
859 let data_end = end.try_into().map_err(|_| {
860 Error::internal(format!("Variable offset ({end}) does not fit into usize"))
861 })?;
862 if data_end > data.len() {
863 return Err(Error::internal(format!(
864 "Invalid variable offsets: end ({data_end}) exceeds data len ({})",
865 data.len()
866 )));
867 }
868
869 let mut rebased_offsets = Vec::with_capacity(offsets_slice.len());
870 for &offset in offsets_slice {
871 if offset < base {
872 return Err(Error::internal(format!(
873 "Invalid variable offsets: offset ({offset}) is less than base ({base})"
874 )));
875 }
876 rebased_offsets.push(offset - base);
877 }
878
879 let sliced_data = data.slice_with_length(data_start, data_end - data_start);
880 let sliced_data = LanceBuffer::copy_slice(&sliced_data);
882 let rebased_offsets = LanceBuffer::reinterpret_vec(rebased_offsets);
883 Ok((sliced_data, rebased_offsets))
884 }
885
886 fn slice_batch_data_and_rebase_offsets(
887 data: &LanceBuffer,
888 offsets: &LanceBuffer,
889 bits_per_offset: u8,
890 ) -> Result<(LanceBuffer, LanceBuffer)> {
891 match bits_per_offset {
892 32 => Self::slice_batch_data_and_rebase_offsets_typed::<u32>(data, offsets),
893 64 => Self::slice_batch_data_and_rebase_offsets_typed::<u64>(data, offsets),
894 _ => Err(Error::internal(format!(
895 "Unsupported bits_per_offset={bits_per_offset}"
896 ))),
897 }
898 }
899
900 fn parse_length(data: &[u8], bits_per_offset: u8) -> Result<u64> {
907 let width = bits_per_offset as usize / 8;
908 if data.len() < width {
909 return Err(Error::corrupt_file_named(
910 "variable_full_zip",
911 format!(
912 "truncated length prefix: {} byte(s) remain in the page buffer but a \
913 {}-bit length prefix requires {}",
914 data.len(),
915 bits_per_offset,
916 width
917 ),
918 ));
919 }
920 Ok(match bits_per_offset {
921 8 => data[0] as u64,
922 16 => u16::from_le_bytes(data[..2].try_into().unwrap()) as u64,
923 32 => u32::from_le_bytes(data[..4].try_into().unwrap()) as u64,
924 64 => u64::from_le_bytes(data[..8].try_into().unwrap()),
925 _ => unreachable!(),
926 })
927 }
928
929 fn unzip(
930 &mut self,
931 data: VecDeque<LanceBuffer>,
932 in_bits_per_length: u8,
933 out_bits_per_offset: u8,
934 num_rows: u64,
935 ) -> Result<()> {
936 let mut rep = Vec::with_capacity(num_rows as usize);
938 let mut def = Vec::with_capacity(num_rows as usize);
939 let bytes_cw = self.details.ctrl_word_parser.bytes_per_word() * num_rows as usize;
940
941 let bytes_per_offset = out_bits_per_offset as usize / 8;
944 let bytes_offsets = bytes_per_offset * (num_rows as usize + 1);
945 let mut offsets_data = Vec::with_capacity(bytes_offsets);
946
947 let bytes_per_length = in_bits_per_length as usize / 8;
948 let bytes_lengths = bytes_per_length * num_rows as usize;
949
950 let bytes_data = data.iter().map(|d| d.len()).sum::<usize>();
951 let mut unzipped_data =
954 Vec::with_capacity((bytes_data - bytes_cw).saturating_sub(bytes_lengths));
955
956 let mut current_offset = 0_u64;
957 let mut visible_item_count = 0_u64;
958 for databuf in data.into_iter() {
959 let mut databuf = databuf.as_ref();
960 while !databuf.is_empty() {
961 let data_start = unzipped_data.len();
962 let offset_start = offsets_data.len();
963 let repdef_start = rep.len().max(def.len());
966 let ctrl_desc = self.details.ctrl_word_parser.parse_desc(
968 databuf,
969 self.details.max_rep,
970 self.details.max_visible_def,
971 );
972 self.details
973 .ctrl_word_parser
974 .parse(databuf, &mut rep, &mut def);
975 databuf = &databuf[self.details.ctrl_word_parser.bytes_per_word()..];
976
977 if ctrl_desc.is_new_row {
978 self.repdef_starts.push(repdef_start);
979 self.data_starts.push(data_start);
980 self.offset_starts.push(offset_start);
981 self.visible_item_counts.push(visible_item_count);
982 }
983 if ctrl_desc.is_visible {
984 visible_item_count += 1;
985 if ctrl_desc.is_valid_item {
986 let length = Self::parse_length(databuf, in_bits_per_length)?;
987 match out_bits_per_offset {
988 32 => offsets_data
989 .extend_from_slice(&(current_offset as u32).to_le_bytes()),
990 64 => offsets_data.extend_from_slice(¤t_offset.to_le_bytes()),
991 _ => unreachable!(),
992 };
993 databuf = &databuf[bytes_per_offset..];
994 if length as usize > databuf.len() {
995 return Err(Error::corrupt_file_named(
996 "variable_full_zip",
997 format!(
998 "item length {} exceeds the {} byte(s) remaining in the \
999 page buffer",
1000 length,
1001 databuf.len()
1002 ),
1003 ));
1004 }
1005 unzipped_data.extend_from_slice(&databuf[..length as usize]);
1006 databuf = &databuf[length as usize..];
1007 current_offset += length;
1008 } else {
1009 match out_bits_per_offset {
1011 32 => offsets_data
1012 .extend_from_slice(&(current_offset as u32).to_le_bytes()),
1013 64 => offsets_data.extend_from_slice(¤t_offset.to_le_bytes()),
1014 _ => unreachable!(),
1015 }
1016 }
1017 }
1018 }
1019 }
1020 self.repdef_starts.push(rep.len().max(def.len()));
1021 self.data_starts.push(unzipped_data.len());
1022 self.offset_starts.push(offsets_data.len());
1023 self.visible_item_counts.push(visible_item_count);
1024 match out_bits_per_offset {
1025 32 => offsets_data.extend_from_slice(&(current_offset as u32).to_le_bytes()),
1026 64 => offsets_data.extend_from_slice(¤t_offset.to_le_bytes()),
1027 _ => unreachable!(),
1028 };
1029 self.rep = ScalarBuffer::from(rep);
1030 self.def = ScalarBuffer::from(def);
1031 self.data = LanceBuffer::from(unzipped_data);
1032 self.offsets = LanceBuffer::from(offsets_data);
1033 Ok(())
1034 }
1035}
1036
1037impl StructuralPageDecoder for VariableFullZipDecoder {
1038 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
1039 let start = self.current_idx;
1040 let end = start + num_rows as usize;
1041
1042 let offset_start = self.offset_starts[start];
1043 let offset_end = self.offset_starts[end] + (self.bits_per_offset as usize / 8);
1044 let offsets = self
1045 .offsets
1046 .slice_with_length(offset_start, offset_end - offset_start);
1047 let (data, offsets) =
1049 Self::slice_batch_data_and_rebase_offsets(&self.data, &offsets, self.bits_per_offset)?;
1050
1051 let repdef_start = self.repdef_starts[start];
1052 let repdef_end = self.repdef_starts[end];
1053 let rep = if self.rep.is_empty() {
1054 self.rep.clone()
1055 } else {
1056 self.rep.slice(repdef_start, repdef_end - repdef_start)
1057 };
1058 let def = if self.def.is_empty() {
1059 self.def.clone()
1060 } else {
1061 self.def.slice(repdef_start, repdef_end - repdef_start)
1062 };
1063
1064 let visible_item_counts_start = self.visible_item_counts[start];
1065 let visible_item_counts_end = self.visible_item_counts[end];
1066 let num_visible_items = visible_item_counts_end - visible_item_counts_start;
1067
1068 self.current_idx += num_rows as usize;
1069
1070 Ok(Box::new(VariableFullZipDecodeTask {
1071 details: self.details.clone(),
1072 decompressor: self.decompressor.clone(),
1073 data,
1074 offsets,
1075 bits_per_offset: self.bits_per_offset,
1076 num_visible_items,
1077 rep,
1078 def,
1079 }))
1080 }
1081
1082 fn num_rows(&self) -> u64 {
1083 self.num_rows
1084 }
1085}
1086
1087#[derive(Debug)]
1088struct VariableFullZipDecodeTask {
1089 details: Arc<FullZipDecodeDetails>,
1090 decompressor: Arc<dyn VariablePerValueDecompressor>,
1091 data: LanceBuffer,
1092 offsets: LanceBuffer,
1093 bits_per_offset: u8,
1094 num_visible_items: u64,
1095 rep: ScalarBuffer<u16>,
1096 def: ScalarBuffer<u16>,
1097}
1098
1099impl DecodePageTask for VariableFullZipDecodeTask {
1100 fn decode(self: Box<Self>) -> Result<DecodedPage> {
1101 let block = VariableWidthBlock {
1102 data: self.data,
1103 offsets: self.offsets,
1104 bits_per_offset: self.bits_per_offset,
1105 num_values: self.num_visible_items,
1106 block_info: BlockInfo::new(),
1107 };
1108 let decomopressed = self.decompressor.decompress(block)?;
1109 let rep = if self.rep.is_empty() {
1110 None
1111 } else {
1112 Some(self.rep.to_vec())
1113 };
1114 let def = if self.def.is_empty() {
1115 None
1116 } else {
1117 Some(self.def.to_vec())
1118 };
1119 let unraveler = RepDefUnraveler::new(
1120 rep,
1121 def,
1122 self.details.def_meaning.clone(),
1123 self.num_visible_items,
1124 );
1125 Ok(DecodedPage {
1126 data: decomopressed,
1127 repdef: unraveler,
1128 })
1129 }
1130}
1131
1132#[derive(Debug)]
1133struct FullZipDecodeTaskItem {
1134 data: PerValueDataBlock,
1135 rows_in_buf: u64,
1136}
1137
1138#[derive(Debug)]
1141struct FixedFullZipDecodeTask {
1142 details: Arc<FullZipDecodeDetails>,
1143 data: Vec<FullZipDecodeTaskItem>,
1144 num_rows: usize,
1145 bytes_per_value: usize,
1146}
1147
1148impl DecodePageTask for FixedFullZipDecodeTask {
1149 fn decode(self: Box<Self>) -> Result<DecodedPage> {
1150 let estimated_size_bytes = if self.details.ctrl_word_parser.bytes_per_word() == 0 {
1151 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor else {
1152 return Err(Error::internal(
1153 "FixedFullZipDecodeTask requires a fixed-width decompressor",
1154 ));
1155 };
1156 decompressor
1157 .decoded_size_bytes(self.num_rows as u64)
1158 .unwrap_or_else(|| {
1159 self.data
1160 .iter()
1161 .map(|task_item| task_item.data.data_size())
1162 .sum::<u64>()
1163 * 2
1164 })
1165 } else {
1166 self.data
1169 .iter()
1170 .map(|task_item| task_item.data.data_size())
1171 .sum::<u64>()
1172 * 2
1173 };
1174 let mut data_builder = DataBlockBuilder::with_capacity_estimate(estimated_size_bytes);
1175
1176 if self.details.ctrl_word_parser.bytes_per_word() == 0 {
1177 for task_item in self.data.into_iter() {
1181 let PerValueDataBlock::Fixed(fixed_data) = task_item.data else {
1182 unreachable!()
1183 };
1184 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor
1185 else {
1186 unreachable!()
1187 };
1188 debug_assert_eq!(fixed_data.num_values, task_item.rows_in_buf);
1189 let decompressed = decompressor.decompress(fixed_data, task_item.rows_in_buf)?;
1190 data_builder.append(&decompressed, 0..task_item.rows_in_buf)?;
1191 }
1192
1193 let unraveler = RepDefUnraveler::new(
1194 None,
1195 None,
1196 self.details.def_meaning.clone(),
1197 self.num_rows as u64,
1198 );
1199
1200 Ok(DecodedPage {
1201 data: data_builder.finish(),
1202 repdef: unraveler,
1203 })
1204 } else {
1205 let mut rep = Vec::with_capacity(self.num_rows);
1207 let mut def = Vec::with_capacity(self.num_rows);
1208 let mut num_visible_items: u64 = 0;
1209
1210 for task_item in self.data.into_iter() {
1211 let PerValueDataBlock::Fixed(fixed_data) = task_item.data else {
1212 unreachable!()
1213 };
1214 let mut buf_slice = fixed_data.data.as_ref();
1215 let num_values = fixed_data.num_values as usize;
1216 let mut values = Vec::with_capacity(
1219 fixed_data.data.len()
1220 - (self.details.ctrl_word_parser.bytes_per_word() * num_values),
1221 );
1222 let mut visible_items = 0;
1223 for _ in 0..num_values {
1224 self.details
1226 .ctrl_word_parser
1227 .parse(buf_slice, &mut rep, &mut def);
1228 buf_slice = &buf_slice[self.details.ctrl_word_parser.bytes_per_word()..];
1229
1230 let is_visible = def
1231 .last()
1232 .map(|d| *d <= self.details.max_visible_def)
1233 .unwrap_or(true);
1234 if is_visible {
1235 values.extend_from_slice(buf_slice[..self.bytes_per_value].as_ref());
1237 buf_slice = &buf_slice[self.bytes_per_value..];
1238 visible_items += 1;
1239 }
1240 }
1241
1242 let values_buf = LanceBuffer::from(values);
1244 let fixed_data = FixedWidthDataBlock {
1245 bits_per_value: self.bytes_per_value as u64 * 8,
1246 block_info: BlockInfo::new(),
1247 data: values_buf,
1248 num_values: visible_items,
1249 };
1250 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor
1251 else {
1252 unreachable!()
1253 };
1254 let decompressed = decompressor.decompress(fixed_data, visible_items)?;
1255 data_builder.append(&decompressed, 0..visible_items)?;
1256 num_visible_items += visible_items;
1257 }
1258
1259 let repetition = if rep.is_empty() { None } else { Some(rep) };
1260 let definition = if def.is_empty() { None } else { Some(def) };
1261
1262 let unraveler = RepDefUnraveler::new(
1265 repetition,
1266 definition,
1267 self.details.def_meaning.clone(),
1268 num_visible_items,
1269 );
1270 let data = data_builder.finish();
1271
1272 Ok(DecodedPage {
1273 data,
1274 repdef: unraveler,
1275 })
1276 }
1277 }
1278}
1279
1280#[cfg(test)]
1281#[allow(clippy::single_range_in_vec_init)]
1282mod tests {
1283 use std::collections::HashMap;
1284 use std::sync::Arc;
1285
1286 use arrow_array::{Array, FixedSizeListArray, Float32Array, UInt8Array, make_array};
1287 use arrow_schema::{DataType, Field as ArrowField};
1288
1289 use crate::buffer::LanceBuffer;
1290 use crate::compression::FixedPerValueDecompressor;
1291 use crate::constants::{STRUCTURAL_ENCODING_META_KEY, STRUCTURAL_ENCODING_MINIBLOCK};
1292 use crate::data::{BlockInfo, DataBlock, FixedWidthDataBlock};
1293 use crate::decoder::DecodePageTask;
1294 use crate::encodings::logical::primitive::StructuralPageScheduler;
1295 use crate::encodings::logical::primitive::fullzip::{PerValueCompressor, PerValueDataBlock};
1296 use crate::encodings::physical::value::{ValueDecompressor, ValueEncoder};
1297 use crate::format::metadata::compressive_encoding::Compression;
1298 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
1299
1300 use super::{
1301 FixedFullZipDecodeTask, FullZipCacheableState, FullZipDecodeDetails, FullZipDecodeTaskItem,
1302 FullZipReadSource, FullZipRepIndexDetails, FullZipScheduler, PerValueDecompressor,
1303 VariableFullZipDecoder,
1304 };
1305
1306 fn decode_fixed_fullzip_no_levels(
1307 decompressor: Arc<dyn FixedPerValueDecompressor>,
1308 data: Vec<FullZipDecodeTaskItem>,
1309 num_rows: usize,
1310 bytes_per_value: usize,
1311 ) -> DataBlock {
1312 Box::new(FixedFullZipDecodeTask {
1313 details: Arc::new(FullZipDecodeDetails {
1314 value_decompressor: PerValueDecompressor::Fixed(decompressor),
1315 def_meaning: Arc::from([]),
1316 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 0),
1317 max_rep: 0,
1318 max_visible_def: u16::MAX,
1319 }),
1320 data,
1321 num_rows,
1322 bytes_per_value,
1323 })
1324 .decode()
1325 .unwrap()
1326 .data
1327 }
1328
1329 #[test]
1330 fn test_fixed_fullzip_decode_preallocates_exact_output_size() {
1331 #[derive(Debug)]
1332 struct IdentityFixedDecompressor;
1333
1334 impl FixedPerValueDecompressor for IdentityFixedDecompressor {
1335 fn decompress(
1336 &self,
1337 data: FixedWidthDataBlock,
1338 num_rows: u64,
1339 ) -> crate::Result<DataBlock> {
1340 assert_eq!(data.num_values, num_rows);
1341 Ok(DataBlock::FixedWidth(data))
1342 }
1343
1344 fn bits_per_value(&self) -> u64 {
1345 32
1346 }
1347
1348 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
1349 num_values.checked_mul(4)
1350 }
1351 }
1352
1353 let make_item = |num_rows: u64| FullZipDecodeTaskItem {
1354 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
1355 data: LanceBuffer::from(vec![7_u8; num_rows as usize * 4]),
1356 bits_per_value: 32,
1357 num_values: num_rows,
1358 block_info: BlockInfo::new(),
1359 }),
1360 rows_in_buf: num_rows,
1361 };
1362
1363 let num_rows = 512;
1364 let decoded = decode_fixed_fullzip_no_levels(
1365 Arc::new(IdentityFixedDecompressor),
1366 vec![make_item(128), make_item(384)],
1367 num_rows,
1368 4,
1369 );
1370 let values = decoded.as_fixed_width_ref().unwrap();
1371 let expected_size = num_rows * 4;
1372 assert_eq!(values.data.len(), expected_size);
1373 assert_eq!(values.data.clone().into_buffer().capacity(), expected_size);
1374 }
1375
1376 #[test]
1377 fn test_fixed_fullzip_decode_falls_back_when_output_size_is_not_exact() {
1378 #[derive(Debug)]
1379 struct FallbackFixedDecompressor;
1380
1381 impl FixedPerValueDecompressor for FallbackFixedDecompressor {
1382 fn decompress(
1383 &self,
1384 data: FixedWidthDataBlock,
1385 num_rows: u64,
1386 ) -> crate::Result<DataBlock> {
1387 assert_eq!(data.num_values, num_rows);
1388 Ok(DataBlock::FixedWidth(FixedWidthDataBlock {
1389 data: LanceBuffer::from(vec![7_u8; num_rows as usize * 4]),
1390 bits_per_value: 32,
1391 num_values: num_rows,
1392 block_info: BlockInfo::new(),
1393 }))
1394 }
1395
1396 fn bits_per_value(&self) -> u64 {
1397 u64::MAX - 7
1400 }
1401 }
1402
1403 let num_rows = 2;
1404 let decoded = decode_fixed_fullzip_no_levels(
1405 Arc::new(FallbackFixedDecompressor),
1406 vec![FullZipDecodeTaskItem {
1407 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
1408 data: LanceBuffer::from(vec![0_u8; num_rows * 4]),
1409 bits_per_value: 32,
1410 num_values: num_rows as u64,
1411 block_info: BlockInfo::new(),
1412 }),
1413 rows_in_buf: num_rows as u64,
1414 }],
1415 num_rows,
1416 4,
1417 );
1418 let values = decoded.as_fixed_width_ref().unwrap();
1419 assert_eq!(values.num_values, num_rows as u64);
1420 assert_eq!(values.data.len(), num_rows * 4);
1421 }
1422
1423 #[test]
1424 fn test_fixed_fullzip_real_fsl_preallocates_exact_output_size() {
1425 let num_rows = 64;
1426 let dimension = 32;
1427 let items = Arc::new(Float32Array::from_iter_values(
1428 (0..num_rows * dimension).map(|value| value as f32),
1429 ));
1430 let item_field = Arc::new(ArrowField::new("item", DataType::Float32, false));
1431 let sample = FixedSizeListArray::new(item_field, dimension as i32, items, None);
1432
1433 let (data, compression) = PerValueCompressor::compress(
1434 &ValueEncoder::default(),
1435 DataBlock::from_array(sample.clone()),
1436 )
1437 .unwrap();
1438 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1439 panic!("expected fixed-size-list compression");
1440 };
1441 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref()).unwrap();
1442 let expected_size = num_rows * dimension * size_of::<f32>();
1443 assert_eq!(
1444 FixedPerValueDecompressor::decoded_size_bytes(&decompressor, num_rows as u64),
1445 Some(expected_size as u64)
1446 );
1447
1448 let decoded = decode_fixed_fullzip_no_levels(
1449 Arc::new(decompressor),
1450 vec![FullZipDecodeTaskItem {
1451 data,
1452 rows_in_buf: num_rows as u64,
1453 }],
1454 num_rows,
1455 dimension * size_of::<f32>(),
1456 );
1457 let fsl = decoded.as_fixed_size_list_ref().unwrap();
1458 let values = fsl.child.as_fixed_width_ref().unwrap();
1459 assert_eq!(values.data.len(), expected_size);
1460 assert_eq!(values.data.clone().into_buffer().capacity(), expected_size);
1461
1462 let decoded_array = make_array(
1463 decoded
1464 .into_arrow(sample.data_type().clone(), true)
1465 .unwrap(),
1466 );
1467 assert_eq!(decoded_array.as_ref(), &sample);
1468 }
1469
1470 #[test]
1471 fn test_fixed_fullzip_nullable_fsl_uses_fallback_end_to_end() {
1472 #[derive(Debug)]
1473 struct NullableFslDecompressor {
1474 inner: ValueDecompressor,
1475 }
1476
1477 impl FixedPerValueDecompressor for NullableFslDecompressor {
1478 fn decompress(
1479 &self,
1480 data: FixedWidthDataBlock,
1481 num_rows: u64,
1482 ) -> crate::Result<DataBlock> {
1483 FixedPerValueDecompressor::decompress(&self.inner, data, num_rows)
1484 }
1485
1486 fn bits_per_value(&self) -> u64 {
1487 u64::MAX - 7
1490 }
1491
1492 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
1493 FixedPerValueDecompressor::decoded_size_bytes(&self.inner, num_values)
1494 }
1495 }
1496
1497 let num_rows = 64;
1498 let items = Arc::new(UInt8Array::from_iter(
1499 (0..num_rows).map(|value| (value % 3 != 0).then_some(value as u8)),
1500 ));
1501 let item_field = Arc::new(ArrowField::new("item", DataType::UInt8, true));
1502 let sample = FixedSizeListArray::new(item_field, 1, items, None);
1503
1504 let (data, compression) = PerValueCompressor::compress(
1505 &ValueEncoder::default(),
1506 DataBlock::from_array(sample.clone()),
1507 )
1508 .unwrap();
1509 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1510 panic!("expected fixed-size-list compression");
1511 };
1512 let decompressor = NullableFslDecompressor {
1513 inner: ValueDecompressor::from_fsl(fsl.as_ref()).unwrap(),
1514 };
1515 assert_eq!(
1516 FixedPerValueDecompressor::decoded_size_bytes(&decompressor, num_rows as u64),
1517 None
1518 );
1519
1520 let decoded = decode_fixed_fullzip_no_levels(
1521 Arc::new(decompressor),
1522 vec![FullZipDecodeTaskItem {
1523 data,
1524 rows_in_buf: num_rows as u64,
1525 }],
1526 num_rows,
1527 2,
1528 );
1529 let decoded_array = make_array(
1530 decoded
1531 .into_arrow(sample.data_type().clone(), true)
1532 .unwrap(),
1533 );
1534 assert_eq!(decoded_array.as_ref(), &sample);
1535 }
1536
1537 #[test]
1538 fn test_slice_batch_data_and_rebase_offsets_u32() {
1539 let data = LanceBuffer::copy_slice(b"0123456789abcdefghij");
1540 let offsets = LanceBuffer::reinterpret_vec(vec![6_u32, 8_u32, 8_u32, 12_u32]);
1541
1542 let (sliced_data, normalized_offsets) =
1543 VariableFullZipDecoder::slice_batch_data_and_rebase_offsets(&data, &offsets, 32)
1544 .unwrap();
1545
1546 assert_eq!(sliced_data.as_ref(), b"6789ab");
1547 let normalized = normalized_offsets.borrow_to_typed_slice::<u32>();
1548 assert_eq!(normalized.as_ref(), &[0, 2, 2, 6]);
1549 }
1550
1551 #[test]
1552 fn test_slice_batch_data_and_rebase_offsets_u64() {
1553 let data = LanceBuffer::copy_slice(b"abcdefghijklmnopqrstuvwxyz");
1554 let offsets = LanceBuffer::reinterpret_vec(vec![10_u64, 12_u64, 16_u64, 20_u64]);
1555
1556 let (sliced_data, normalized_offsets) =
1557 VariableFullZipDecoder::slice_batch_data_and_rebase_offsets(&data, &offsets, 64)
1558 .unwrap();
1559
1560 assert_eq!(sliced_data.as_ref(), b"klmnopqrst");
1561 let normalized = normalized_offsets.borrow_to_typed_slice::<u64>();
1562 assert_eq!(normalized.as_ref(), &[0, 2, 6, 10]);
1563 }
1564
1565 #[test]
1566 fn test_slice_batch_data_and_rebase_offsets_rejects_invalid_offsets() {
1567 let data = LanceBuffer::copy_slice(b"abcd");
1568 let offsets = LanceBuffer::reinterpret_vec(vec![3_u32, 2_u32]);
1569
1570 let err = VariableFullZipDecoder::slice_batch_data_and_rebase_offsets(&data, &offsets, 32)
1571 .expect_err("offset end before start should error");
1572 assert!(err.to_string().contains("less than base"));
1573 }
1574
1575 #[tokio::test]
1576 async fn test_fullzip_initialize_is_lazy() {
1577 use futures::{FutureExt, future::BoxFuture};
1578 use std::ops::Range;
1579 use std::sync::Mutex;
1580
1581 #[derive(Debug, Clone)]
1582 struct RecordingScheduler {
1583 data: bytes::Bytes,
1584 requests: Arc<Mutex<Vec<Vec<Range<u64>>>>>,
1585 }
1586
1587 impl RecordingScheduler {
1588 fn new(data: bytes::Bytes) -> Self {
1589 Self {
1590 data,
1591 requests: Arc::new(Mutex::new(Vec::new())),
1592 }
1593 }
1594
1595 fn requests(&self) -> Vec<Vec<Range<u64>>> {
1596 self.requests.lock().unwrap().clone()
1597 }
1598 }
1599
1600 impl crate::EncodingsIo for RecordingScheduler {
1601 fn submit_request(
1602 &self,
1603 ranges: Vec<Range<u64>>,
1604 _priority: u64,
1605 ) -> BoxFuture<'static, crate::Result<Vec<bytes::Bytes>>> {
1606 self.requests.lock().unwrap().push(ranges.clone());
1607 let data = ranges
1608 .into_iter()
1609 .map(|range| self.data.slice(range.start as usize..range.end as usize))
1610 .collect::<Vec<_>>();
1611 std::future::ready(Ok(data)).boxed()
1612 }
1613 }
1614
1615 #[derive(Debug)]
1616 struct TestFixedDecompressor;
1617
1618 impl FixedPerValueDecompressor for TestFixedDecompressor {
1619 fn decompress(
1620 &self,
1621 _data: FixedWidthDataBlock,
1622 _num_rows: u64,
1623 ) -> crate::Result<DataBlock> {
1624 unimplemented!("Test decompressor")
1625 }
1626
1627 fn bits_per_value(&self) -> u64 {
1628 32
1629 }
1630 }
1631
1632 let io = Arc::new(RecordingScheduler::new(bytes::Bytes::from(vec![
1633 0;
1634 16 * 1024
1635 ])));
1636 let mut scheduler = FullZipScheduler {
1637 data_buf_position: 0,
1638 data_buf_size: 4096,
1639 rep_index: Some(FullZipRepIndexDetails {
1640 buf_position: 1000,
1641 bytes_per_value: 4,
1642 }),
1643 priority: 0,
1644 rows_in_page: 100,
1645 bits_per_offset: 32,
1646 details: Arc::new(FullZipDecodeDetails {
1647 value_decompressor: PerValueDecompressor::Fixed(Arc::new(TestFixedDecompressor)),
1648 def_meaning: Arc::new([crate::repdef::DefinitionInterpretation::NullableItem]),
1649 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 1),
1650 max_rep: 0,
1651 max_visible_def: 0,
1652 }),
1653 cached_state: None,
1654 enable_cache: false,
1655 };
1656
1657 let io_dyn: Arc<dyn crate::EncodingsIo> = io.clone();
1658 let initialization = scheduler.init_layout().unwrap();
1659 assert!(
1660 initialization.required_ranges().next().is_none(),
1661 "FullZip should not request any ranges when caching is disabled"
1662 );
1663 let buffers = initialization.with_buffers(vec![]).unwrap();
1664 let cached_data = scheduler.init_from_buffers(buffers, &io_dyn).await.unwrap();
1665
1666 assert!(
1667 cached_data
1668 .as_arc_any()
1669 .downcast_ref::<super::NoCachedPageData>()
1670 .is_some(),
1671 "FullZip initialize should not eagerly load repetition index data"
1672 );
1673 assert!(scheduler.cached_state.is_none());
1674 assert!(
1675 io.requests().is_empty(),
1676 "FullZip initialize should not issue any I/O"
1677 );
1678 }
1679
1680 #[tokio::test]
1681 async fn test_fullzip_read_source_slices_prefetched_page() {
1682 let page_start = 200_u64;
1683 let page_data = LanceBuffer::copy_slice(&[0, 1, 2, 3, 4, 5, 6, 7]);
1684 let source = FullZipReadSource::PrefetchedPage {
1685 base_offset: page_start,
1686 data: page_data,
1687 };
1688 let ranges = vec![
1689 page_start..(page_start + 3),
1690 (page_start + 4)..(page_start + 8),
1691 ];
1692 let mut data = source.fetch(&ranges, 0).await.unwrap();
1693 assert_eq!(data.pop_front().unwrap().as_ref(), &[0, 1, 2]);
1694 assert_eq!(data.pop_front().unwrap().as_ref(), &[4, 5, 6, 7]);
1695 }
1696
1697 #[tokio::test]
1698 async fn test_fullzip_initialize_caches_rep_index_when_enabled() {
1699 use futures::{FutureExt, future::BoxFuture};
1700 use std::ops::Range;
1701 use std::sync::Mutex;
1702
1703 #[derive(Debug, Clone)]
1704 struct RecordingScheduler {
1705 data: bytes::Bytes,
1706 requests: Arc<Mutex<Vec<Vec<Range<u64>>>>>,
1707 }
1708
1709 impl RecordingScheduler {
1710 fn new(data: bytes::Bytes) -> Self {
1711 Self {
1712 data,
1713 requests: Arc::new(Mutex::new(Vec::new())),
1714 }
1715 }
1716
1717 fn requests(&self) -> Vec<Vec<Range<u64>>> {
1718 self.requests.lock().unwrap().clone()
1719 }
1720 }
1721
1722 impl crate::EncodingsIo for RecordingScheduler {
1723 fn submit_request(
1724 &self,
1725 ranges: Vec<Range<u64>>,
1726 _priority: u64,
1727 ) -> BoxFuture<'static, crate::Result<Vec<bytes::Bytes>>> {
1728 self.requests.lock().unwrap().push(ranges.clone());
1729 let data = ranges
1730 .into_iter()
1731 .map(|range| self.data.slice(range.start as usize..range.end as usize))
1732 .collect::<Vec<_>>();
1733 std::future::ready(Ok(data)).boxed()
1734 }
1735 }
1736
1737 #[derive(Debug)]
1738 struct TestFixedDecompressor;
1739
1740 impl FixedPerValueDecompressor for TestFixedDecompressor {
1741 fn decompress(
1742 &self,
1743 _data: FixedWidthDataBlock,
1744 _num_rows: u64,
1745 ) -> crate::Result<DataBlock> {
1746 unimplemented!("Test decompressor")
1747 }
1748
1749 fn bits_per_value(&self) -> u64 {
1750 32
1751 }
1752 }
1753
1754 let rows_in_page = 100_u64;
1755 let bytes_per_value = 4_u64;
1756 let rep_start = 1000_u64;
1757 let rep_size = ((rows_in_page + 1) * bytes_per_value) as usize;
1758 let mut data = vec![0_u8; 16 * 1024];
1759 data[rep_start as usize..rep_start as usize + rep_size].fill(7);
1760 let io = Arc::new(RecordingScheduler::new(bytes::Bytes::from(data)));
1761
1762 let mut scheduler = FullZipScheduler {
1763 data_buf_position: 0,
1764 data_buf_size: 4096,
1765 rep_index: Some(FullZipRepIndexDetails {
1766 buf_position: rep_start,
1767 bytes_per_value,
1768 }),
1769 priority: 0,
1770 rows_in_page,
1771 bits_per_offset: 32,
1772 details: Arc::new(FullZipDecodeDetails {
1773 value_decompressor: PerValueDecompressor::Fixed(Arc::new(TestFixedDecompressor)),
1774 def_meaning: Arc::new([crate::repdef::DefinitionInterpretation::NullableItem]),
1775 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 1),
1776 max_rep: 0,
1777 max_visible_def: 0,
1778 }),
1779 cached_state: None,
1780 enable_cache: true,
1781 };
1782
1783 let io_dyn: Arc<dyn crate::EncodingsIo> = io.clone();
1784 let initialization = scheduler.init_layout().unwrap();
1785 let ranges = initialization
1786 .required_ranges()
1787 .cloned()
1788 .collect::<Vec<_>>();
1789 assert_eq!(
1790 ranges,
1791 vec![rep_start..(rep_start + (rows_in_page + 1) * bytes_per_value)]
1792 );
1793 let buffers = io_dyn.submit_request(ranges, 0).await.unwrap();
1796 let buffers = initialization.with_buffers(buffers).unwrap();
1797 let cached_data = scheduler.init_from_buffers(buffers, &io_dyn).await.unwrap();
1798 assert!(
1799 cached_data
1800 .clone()
1801 .as_arc_any()
1802 .downcast_ref::<FullZipCacheableState>()
1803 .is_some()
1804 );
1805 assert!(scheduler.cached_state.is_none());
1806 scheduler.try_load(&cached_data).unwrap();
1807 assert!(scheduler.cached_state.is_some());
1808 assert_eq!(
1809 io.requests(),
1810 vec![vec![
1811 rep_start..(rep_start + (rows_in_page + 1) * bytes_per_value)
1812 ]]
1813 );
1814 }
1815
1816 #[tokio::test]
1817 async fn test_fullzip_full_page_bypasses_rep_index_io() {
1818 use futures::{FutureExt, future::BoxFuture};
1819 use std::ops::Range;
1820 use std::sync::Mutex;
1821
1822 #[derive(Debug, Clone)]
1823 struct RecordingScheduler {
1824 data: bytes::Bytes,
1825 requests: Arc<Mutex<Vec<Vec<Range<u64>>>>>,
1826 }
1827
1828 impl RecordingScheduler {
1829 fn new(data: bytes::Bytes) -> Self {
1830 Self {
1831 data,
1832 requests: Arc::new(Mutex::new(Vec::new())),
1833 }
1834 }
1835
1836 fn requests(&self) -> Vec<Vec<Range<u64>>> {
1837 self.requests.lock().unwrap().clone()
1838 }
1839 }
1840
1841 impl crate::EncodingsIo for RecordingScheduler {
1842 fn submit_request(
1843 &self,
1844 ranges: Vec<Range<u64>>,
1845 _priority: u64,
1846 ) -> BoxFuture<'static, crate::Result<Vec<bytes::Bytes>>> {
1847 self.requests.lock().unwrap().push(ranges.clone());
1848 let data = ranges
1849 .into_iter()
1850 .map(|range| self.data.slice(range.start as usize..range.end as usize))
1851 .collect::<Vec<_>>();
1852 std::future::ready(Ok(data)).boxed()
1853 }
1854 }
1855
1856 #[derive(Debug)]
1857 struct TestFixedDecompressor;
1858
1859 impl FixedPerValueDecompressor for TestFixedDecompressor {
1860 fn decompress(
1861 &self,
1862 _data: FixedWidthDataBlock,
1863 _num_rows: u64,
1864 ) -> crate::Result<DataBlock> {
1865 unimplemented!("Test decompressor")
1866 }
1867
1868 fn bits_per_value(&self) -> u64 {
1869 32
1870 }
1871 }
1872
1873 let rows_in_page = 100_u64;
1874 let data_start = 256_u64;
1875 let data_size = 500_u64;
1876 let rep_start = 4096_u64;
1877 let bytes_per_value = 4_u64;
1878
1879 let mut bytes = vec![0_u8; 16 * 1024];
1880 for i in 0..=rows_in_page {
1881 let offset = (i * 5) as u32;
1882 let pos = rep_start as usize + (i * bytes_per_value) as usize;
1883 bytes[pos..pos + 4].copy_from_slice(&offset.to_le_bytes());
1884 }
1885 let io = Arc::new(RecordingScheduler::new(bytes::Bytes::from(bytes)));
1886
1887 let scheduler = FullZipScheduler {
1888 data_buf_position: data_start,
1889 data_buf_size: data_size,
1890 rep_index: Some(FullZipRepIndexDetails {
1891 buf_position: rep_start,
1892 bytes_per_value,
1893 }),
1894 priority: 0,
1895 rows_in_page,
1896 bits_per_offset: 32,
1897 details: Arc::new(FullZipDecodeDetails {
1898 value_decompressor: PerValueDecompressor::Fixed(Arc::new(TestFixedDecompressor)),
1899 def_meaning: Arc::new([crate::repdef::DefinitionInterpretation::NullableItem]),
1900 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 1),
1901 max_rep: 0,
1902 max_visible_def: 0,
1903 }),
1904 cached_state: None,
1905 enable_cache: false,
1906 };
1907
1908 let io_dyn: Arc<dyn crate::EncodingsIo> = io.clone();
1909 let tasks = scheduler
1910 .schedule_ranges_rep(
1911 &[0..rows_in_page],
1912 &io_dyn,
1913 FullZipRepIndexDetails {
1914 buf_position: rep_start,
1915 bytes_per_value,
1916 },
1917 )
1918 .unwrap();
1919
1920 let requests = io.requests();
1921 assert_eq!(requests.len(), 1);
1922 assert_eq!(requests[0], vec![data_start..(data_start + data_size)]);
1923
1924 let _ = tasks.into_iter().next().unwrap().decoder_fut.await.unwrap();
1925 let requests_after_await = io.requests();
1926 assert_eq!(
1927 requests_after_await.len(),
1928 1,
1929 "full page path should not issue rep-index I/O"
1930 );
1931 }
1932
1933 #[tokio::test]
1935 async fn test_fuzz_issue_4492_empty_rep_values() {
1936 use lance_datagen::{RowCount, Seed, array, gen_batch};
1937
1938 let seed = 1823859942947654717u64;
1939 let num_rows = 2741usize;
1940
1941 let batch_gen = gen_batch().with_seed(Seed::from(seed));
1943 let base_generator = array::rand_type(&DataType::FixedSizeBinary(32));
1944 let list_generator = array::rand_list_any(base_generator, false);
1945
1946 let batch = batch_gen
1947 .anon_col(list_generator)
1948 .into_batch_rows(RowCount::from(num_rows as u64))
1949 .unwrap();
1950
1951 let list_array = batch.column(0).clone();
1952
1953 let mut metadata = HashMap::new();
1955 metadata.insert(
1956 STRUCTURAL_ENCODING_META_KEY.to_string(),
1957 STRUCTURAL_ENCODING_MINIBLOCK.to_string(),
1958 );
1959
1960 let test_cases = TestCases::default()
1961 .with_structural_encodings()
1962 .with_batch_size(100)
1963 .with_range(0..num_rows.min(500) as u64)
1964 .with_indices(vec![0, num_rows as u64 / 2, (num_rows - 1) as u64]);
1965
1966 check_round_trip_encoding_of_data(vec![list_array], &test_cases, metadata).await
1967 }
1968
1969 fn list_of_vectors(lists: &[Option<Vec<Option<f32>>>]) -> arrow_array::ArrayRef {
1973 use arrow_buffer::{NullBuffer, OffsetBuffer, ScalarBuffer};
1974
1975 const DIMENSION: i32 = 128;
1976 let mut values = Vec::new();
1977 let mut vector_validity = Vec::new();
1978 let mut offsets = vec![0_i32];
1979 let mut list_validity = Vec::new();
1980 for list in lists {
1981 if let Some(items) = list {
1982 for item in items {
1983 values.extend(std::iter::repeat_n(item.unwrap_or(0.0), DIMENSION as usize));
1984 vector_validity.push(item.is_some());
1985 }
1986 }
1987 list_validity.push(list.is_some());
1988 offsets.push(vector_validity.len() as i32);
1989 }
1990 let vectors = FixedSizeListArray::new(
1991 Arc::new(ArrowField::new("item", DataType::Float32, true)),
1992 DIMENSION,
1993 Arc::new(Float32Array::from(values)),
1994 vector_validity
1995 .contains(&false)
1996 .then(|| NullBuffer::from(vector_validity)),
1997 );
1998 Arc::new(arrow_array::ListArray::new(
1999 Arc::new(ArrowField::new("item", vectors.data_type().clone(), true)),
2000 OffsetBuffer::new(ScalarBuffer::from(offsets)),
2001 Arc::new(vectors),
2002 Some(NullBuffer::from(list_validity)),
2003 ))
2004 }
2005
2006 #[tokio::test]
2009 async fn test_fullzip_list_read_across_pages_with_mixed_item_validity() {
2010 let all_valid_page = list_of_vectors(&[
2011 Some(vec![Some(1.0), Some(2.0), Some(3.0)]),
2012 None,
2013 Some(vec![Some(4.0), Some(5.0)]),
2014 ]);
2015 let null_vector_page = list_of_vectors(&[
2016 Some(vec![Some(6.0), None, Some(7.0)]),
2017 Some(vec![Some(8.0)]),
2018 ]);
2019 check_round_trip_encoding_of_data(
2020 vec![all_valid_page, null_vector_page],
2021 &TestCases::default()
2022 .with_structural_encodings()
2023 .with_page_sizes(vec![1])
2025 .with_indices(vec![2, 3, 4]),
2026 HashMap::new(),
2027 )
2028 .await;
2029 }
2030
2031 fn truncated_tail_details() -> std::sync::Arc<super::FullZipDecodeDetails> {
2032 use crate::compression::VariablePerValueDecompressor;
2033 use crate::encodings::physical::binary::VariableDecoder;
2034 use crate::repdef::{ControlWordParser, DefinitionInterpretation};
2035 use std::sync::Arc;
2036 Arc::new(super::FullZipDecodeDetails {
2037 value_decompressor: super::PerValueDecompressor::Variable(Arc::new(
2038 VariableDecoder::default(),
2039 )
2040 as Arc<dyn VariablePerValueDecompressor>),
2041 def_meaning: vec![DefinitionInterpretation::NullableItem].into(),
2042 ctrl_word_parser: ControlWordParser::new(0, 0),
2043 max_rep: 0,
2044 max_visible_def: 0,
2045 })
2046 }
2047
2048 fn decode_variable_full_zip(
2049 buf: Vec<u8>,
2050 bits_per_offset: u8,
2051 ) -> lance_core::Result<super::VariableFullZipDecoder> {
2052 use std::collections::VecDeque;
2053 let mut data = VecDeque::new();
2054 data.push_back(crate::buffer::LanceBuffer::from(buf));
2055 super::VariableFullZipDecoder::new(
2056 truncated_tail_details(),
2057 data,
2058 1,
2059 bits_per_offset,
2060 bits_per_offset,
2061 )
2062 }
2063
2064 #[test]
2066 fn variable_full_zip_wellformed_length_prefix() {
2067 assert!(decode_variable_full_zip(0u32.to_le_bytes().to_vec(), 32).is_ok());
2068 assert!(decode_variable_full_zip(0u64.to_le_bytes().to_vec(), 64).is_ok());
2069 }
2070
2071 #[test]
2080 fn variable_full_zip_truncated_length_prefix_is_corrupt_file() {
2081 use lance_core::Error;
2082
2083 for (bits, buf_len) in [(32u8, 3usize), (64u8, 4usize)] {
2084 let err = decode_variable_full_zip(vec![0xAA; buf_len], bits)
2085 .expect_err("a truncated length prefix must not decode");
2086 assert!(
2087 matches!(err, Error::CorruptFile { .. }),
2088 "expected CorruptFile for a {}-bit prefix with {} byte(s), got: {:?}",
2089 bits,
2090 buf_len,
2091 err
2092 );
2093 let msg = err.to_string();
2094 assert!(
2095 msg.contains("truncated length prefix"),
2096 "error should say what is wrong, got: {msg}"
2097 );
2098 }
2099 }
2100
2101 fn drain_fixed_full_zip(buf: Vec<u8>, bits_rep: u8, num_rows: u64) -> lance_core::Result<()> {
2106 use crate::compression::FixedPerValueDecompressor;
2107 use crate::decoder::StructuralPageDecoder;
2108 use crate::repdef::{ControlWordParser, DefinitionInterpretation};
2109 use std::collections::VecDeque;
2110 use std::sync::Arc;
2111
2112 #[derive(Debug)]
2113 struct UnusedFixedDecompressor;
2114
2115 impl FixedPerValueDecompressor for UnusedFixedDecompressor {
2116 fn decompress(&self, _: FixedWidthDataBlock, _: u64) -> crate::Result<DataBlock> {
2117 unreachable!("these cases fail while slicing, before any value is decoded")
2118 }
2119
2120 fn bits_per_value(&self) -> u64 {
2121 32
2122 }
2123 }
2124
2125 let details = Arc::new(super::FullZipDecodeDetails {
2126 value_decompressor: super::PerValueDecompressor::Fixed(Arc::new(
2127 UnusedFixedDecompressor,
2128 )),
2129 def_meaning: vec![DefinitionInterpretation::AllValidList].into(),
2130 ctrl_word_parser: ControlWordParser::new(bits_rep, 0),
2131 max_rep: 1,
2132 max_visible_def: 0,
2133 });
2134 let bytes_per_word = bits_rep.div_ceil(8) as usize;
2135
2136 let mut data = VecDeque::new();
2137 data.push_back(crate::buffer::LanceBuffer::from(buf));
2138 let mut decoder = super::FixedFullZipDecoder {
2139 details,
2140 data,
2141 offset_in_current: 0,
2142 bytes_per_value: 4,
2143 total_bytes_per_value: 4 + bytes_per_word,
2144 num_rows,
2145 };
2146 decoder.drain(num_rows).map(|_| ())
2147 }
2148
2149 #[test]
2151 fn fixed_full_zip_wellformed_page_slices() {
2152 assert!(drain_fixed_full_zip(vec![1, 0, 0, 0, 0, 1, 0, 0, 0, 0], 1, 2).is_ok());
2153 }
2154
2155 #[rstest::rstest]
2164 #[case::truncated_item(vec![1, 0, 0, 0, 0, 1, 0, 0, 0], 1, 2, "truncated item")]
2166 #[case::truncated_control_word(
2168 vec![1, 0, 0, 0, 0, 0, 1],
2169 9,
2170 2,
2171 "truncated control word"
2172 )]
2173 #[case::page_exhausted(vec![1, 0, 0, 0, 0, 1, 0, 0, 0, 0], 1, 3, "page data ran out")]
2175 fn fixed_full_zip_malformed_page_is_corrupt_file(
2176 #[case] buf: Vec<u8>,
2177 #[case] bits_rep: u8,
2178 #[case] num_rows: u64,
2179 #[case] expected_msg: &str,
2180 ) {
2181 use lance_core::Error;
2182
2183 let err = drain_fixed_full_zip(buf, bits_rep, num_rows)
2184 .expect_err("a malformed page must not slice");
2185 assert!(
2186 matches!(err, Error::CorruptFile { .. }),
2187 "expected CorruptFile, got: {err:?}"
2188 );
2189 let msg = err.to_string();
2190 assert!(
2191 msg.contains(expected_msg),
2192 "error should say what is wrong, got: {msg}"
2193 );
2194 }
2195
2196 #[test]
2202 fn variable_full_zip_oversized_item_length_is_corrupt_file() {
2203 use lance_core::Error;
2204
2205 for bits in [32u8, 64u8] {
2206 let prefix_width = bits as usize / 8;
2207 let mut buf = vec![0xFF; prefix_width];
2209 buf.push(0xAA);
2210 let err = decode_variable_full_zip(buf, bits)
2211 .expect_err("an oversized item length must not decode");
2212 assert!(
2213 matches!(err, Error::CorruptFile { .. }),
2214 "expected CorruptFile for an oversized {}-bit length, got: {:?}",
2215 bits,
2216 err
2217 );
2218 let msg = err.to_string();
2219 assert!(
2220 msg.contains("exceeds") && msg.contains("remaining"),
2221 "error should say what is wrong, got: {msg}"
2222 );
2223 }
2224 }
2225}