1use std::{
5 any::Any,
6 collections::{HashMap, VecDeque},
7 env,
8 fmt::Debug,
9 iter,
10 ops::Range,
11 sync::Arc,
12 vec,
13};
14
15use crate::{
16 constants::{
17 STRUCTURAL_ENCODING_FULLZIP, STRUCTURAL_ENCODING_META_KEY, STRUCTURAL_ENCODING_MINIBLOCK,
18 },
19 data::DictionaryDataBlock,
20 encodings::logical::primitive::blob::{BlobDescriptionPageScheduler, BlobPageScheduler},
21 format::{
22 ProtobufUtils21,
23 pb21::{self, CompressiveEncoding, PageLayout, compressive_encoding::Compression},
24 },
25};
26use arrow_array::{Array, ArrayRef, PrimitiveArray, cast::AsArray, make_array, types::UInt64Type};
27use arrow_buffer::{BooleanBuffer, BooleanBufferBuilder, NullBuffer, ScalarBuffer};
28use arrow_schema::{DataType, Field as ArrowField};
29use bytes::Bytes;
30use futures::{FutureExt, TryStreamExt, future::BoxFuture, stream::FuturesOrdered};
31use itertools::Itertools;
32use lance_arrow::DataTypeExt;
33use lance_arrow::deepcopy::deep_copy_nulls;
34use lance_core::{
35 cache::{CacheKey, Context, DeepSizeOf},
36 error::{Error, LanceOptionExt},
37 utils::bit::pad_bytes,
38};
39use log::trace;
40
41use crate::encodings::logical::primitive::miniblock::MiniBlockChunk;
42use crate::utils::bytepack::ByteUnpacker;
43use crate::{
44 compression::{
45 BlockDecompressor, CompressionStrategy, DecompressionStrategy, MiniBlockDecompressor,
46 },
47 data::{AllNullDataBlock, DataBlock, VariableWidthBlock},
48 utils::bytepack::BytepackedIntegerEncoder,
49};
50use crate::{
51 compression::{FixedPerValueDecompressor, VariablePerValueDecompressor},
52 encodings::logical::primitive::fullzip::PerValueDataBlock,
53};
54use crate::{
55 encodings::logical::primitive::miniblock::MiniBlockCompressed,
56 statistics::{ComputeStat, GetStat, Stat},
57};
58use crate::{
59 repdef::{
60 CompositeRepDefUnraveler, ControlWordIterator, ControlWordParser, DefinitionInterpretation,
61 MiniBlockRepDefBudget, RepDefSlicer, SerializedRepDefs, build_control_word_iterator,
62 },
63 utils::accumulation::AccumulationQueue,
64};
65use lance_core::{Result, datatypes::Field, utils::tokio::spawn_cpu};
66
67use crate::constants::{
68 COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY, DICT_DIVISOR_META_KEY,
69 DICT_SIZE_RATIO_META_KEY, DICT_VALUES_COMPRESSION_ENV_VAR,
70 DICT_VALUES_COMPRESSION_LEVEL_ENV_VAR, DICT_VALUES_COMPRESSION_LEVEL_META_KEY,
71 DICT_VALUES_COMPRESSION_META_KEY,
72};
73use crate::version::LanceFileVersion;
74use crate::{
75 EncodingsIo,
76 buffer::LanceBuffer,
77 data::{BlockInfo, DataBlockBuilder, FixedWidthDataBlock},
78 decoder::{
79 ColumnInfo, DecodePageTask, DecodedArray, DecodedPage, FilterExpression, LoadedPageShard,
80 MessageType, PageEncoding, PageInfo, ScheduledScanLine, SchedulerContext,
81 StructuralDecodeArrayTask, StructuralFieldDecoder, StructuralFieldScheduler,
82 StructuralPageDecoder, StructuralSchedulingJob, UnloadedPageShard,
83 },
84 encoder::{
85 EncodeTask, EncodedColumn, EncodedPage, EncodingOptions, FieldEncoder, OutOfLineBuffers,
86 },
87 repdef::{LevelBuffer, RepDefBuilder, RepDefUnraveler},
88};
89
90pub mod blob;
91pub mod constant;
92pub mod dict;
93pub mod fullzip;
94pub mod miniblock;
95
96const FILL_BYTE: u8 = 0xFE;
97const DEFAULT_DICT_DIVISOR: u64 = 2;
98const DEFAULT_DICT_MAX_CARDINALITY: u64 = 100_000;
99const DEFAULT_DICT_SIZE_RATIO: f64 = 0.8;
100const DEFAULT_DICT_VALUES_COMPRESSION: &str = "lz4";
101
102struct PageLoadTask {
103 decoder_fut: BoxFuture<'static, Result<Box<dyn StructuralPageDecoder>>>,
104 num_rows: u64,
105}
106
107trait StructuralPageScheduler: std::fmt::Debug + Send {
110 fn initialize<'a>(
112 &'a mut self,
113 io: &Arc<dyn EncodingsIo>,
114 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>>;
115 fn load(&mut self, data: &Arc<dyn CachedPageData>);
117 fn schedule_ranges(
126 &self,
127 ranges: &[Range<u64>],
128 io: &Arc<dyn EncodingsIo>,
129 ) -> Result<Vec<PageLoadTask>>;
130}
131
132#[derive(Debug)]
134struct ChunkMeta {
135 num_values: u64,
136 chunk_size_bytes: u64,
137 offset_bytes: u64,
138}
139
140#[derive(Debug, Clone)]
142struct DecodedMiniBlockChunk {
143 rep: Option<ScalarBuffer<u16>>,
144 def: Option<ScalarBuffer<u16>>,
145 values: DataBlock,
146}
147
148#[derive(Debug)]
156struct DecodeMiniBlockTask {
157 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
158 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
159 value_decompressor: Arc<dyn MiniBlockDecompressor>,
160 dictionary_data: Option<Arc<DataBlock>>,
161 def_meaning: Arc<[DefinitionInterpretation]>,
162 num_buffers: u64,
163 max_visible_level: u16,
164 instructions: Vec<(ChunkDrainInstructions, LoadedChunk)>,
165 has_large_chunk: bool,
166}
167
168impl DecodeMiniBlockTask {
169 fn decode_levels(
170 rep_decompressor: &dyn BlockDecompressor,
171 levels: LanceBuffer,
172 num_levels: u16,
173 ) -> Result<ScalarBuffer<u16>> {
174 let rep = rep_decompressor.decompress(levels, num_levels as u64)?;
175 let rep = rep.as_fixed_width().unwrap();
176 debug_assert_eq!(rep.num_values, num_levels as u64);
177 debug_assert_eq!(rep.bits_per_value, 16);
178 Ok(rep.data.borrow_to_typed_slice::<u16>())
179 }
180
181 fn extend_levels(
188 range: Range<u64>,
189 levels: &mut Option<LevelBuffer>,
190 level_buf: &Option<impl AsRef<[u16]>>,
191 dest_offset: usize,
192 ) {
193 if let Some(level_buf) = level_buf {
194 if levels.is_none() {
195 let mut new_levels_vec =
198 LevelBuffer::with_capacity(dest_offset + (range.end - range.start) as usize);
199 new_levels_vec.extend(iter::repeat_n(0, dest_offset));
200 *levels = Some(new_levels_vec);
201 }
202 levels.as_mut().unwrap().extend(
203 level_buf.as_ref()[range.start as usize..range.end as usize]
204 .iter()
205 .copied(),
206 );
207 } else if let Some(levels) = levels {
208 let num_values = (range.end - range.start) as usize;
209 levels.extend(iter::repeat_n(0, num_values));
212 }
213 }
214
215 fn map_range(
252 range: Range<u64>,
253 rep: Option<&impl AsRef<[u16]>>,
254 def: Option<&impl AsRef<[u16]>>,
255 max_rep: u16,
256 max_visible_def: u16,
257 total_items: u64,
260 preamble_action: PreambleAction,
261 ) -> (Range<u64>, Range<u64>) {
262 if let Some(rep) = rep {
263 let mut rep = rep.as_ref();
264 let mut items_in_preamble = 0_u64;
267 let first_row_start = match preamble_action {
268 PreambleAction::Skip | PreambleAction::Take => {
269 let first_row_start = if let Some(def) = def.as_ref() {
270 let mut first_row_start = None;
271 for (idx, (rep, def)) in rep.iter().zip(def.as_ref()).enumerate() {
272 if *rep == max_rep {
273 first_row_start = Some(idx as u64);
274 break;
275 }
276 if *def <= max_visible_def {
277 items_in_preamble += 1;
278 }
279 }
280 first_row_start
281 } else {
282 let first_row_start =
283 rep.iter().position(|&r| r == max_rep).map(|r| r as u64);
284 items_in_preamble = first_row_start.unwrap_or(rep.len() as u64);
285 first_row_start
286 };
287 if first_row_start.is_none() {
290 assert!(preamble_action == PreambleAction::Take);
291 return (0..total_items, 0..rep.len() as u64);
292 }
293 let first_row_start = first_row_start.unwrap();
294 rep = &rep[first_row_start as usize..];
295 first_row_start
296 }
297 PreambleAction::Absent => {
298 debug_assert!(rep[0] == max_rep);
299 0
300 }
301 };
302
303 if range.start == range.end {
305 debug_assert!(preamble_action == PreambleAction::Take);
306 debug_assert!(items_in_preamble <= total_items);
307 return (0..items_in_preamble, 0..first_row_start);
308 }
309 assert!(range.start < range.end);
310
311 let mut rows_seen = 0;
312 let mut new_start = 0;
313 let mut new_levels_start = 0;
314
315 if let Some(def) = def {
316 let def = &def.as_ref()[first_row_start as usize..];
317
318 let mut lead_invis_seen = 0;
320
321 if range.start > 0 {
322 if def[0] > max_visible_def {
323 lead_invis_seen += 1;
324 }
325 for (idx, (rep, def)) in rep.iter().zip(def).skip(1).enumerate() {
326 if *rep == max_rep {
327 rows_seen += 1;
328 if rows_seen == range.start {
329 new_start = idx as u64 + 1 - lead_invis_seen;
330 new_levels_start = idx as u64 + 1;
331 break;
332 }
333 }
334 if *def > max_visible_def {
335 lead_invis_seen += 1;
336 }
337 }
338 }
339
340 rows_seen += 1;
341
342 let mut new_end = u64::MAX;
343 let mut new_levels_end = rep.len() as u64;
344 let new_start_is_visible = def[new_levels_start as usize] <= max_visible_def;
345 let mut tail_invis_seen = if new_start_is_visible { 0 } else { 1 };
346 for (idx, (rep, def)) in rep[(new_levels_start + 1) as usize..]
347 .iter()
348 .zip(&def[(new_levels_start + 1) as usize..])
349 .enumerate()
350 {
351 if *rep == max_rep {
352 rows_seen += 1;
353 if rows_seen == range.end + 1 {
354 new_end = idx as u64 + new_start + 1 - tail_invis_seen;
355 new_levels_end = idx as u64 + new_levels_start + 1;
356 break;
357 }
358 }
359 if *def > max_visible_def {
360 tail_invis_seen += 1;
361 }
362 }
363
364 if new_end == u64::MAX {
365 new_levels_end = rep.len() as u64;
366 let total_invis_seen = lead_invis_seen + tail_invis_seen;
367 new_end = rep.len() as u64 - total_invis_seen;
368 }
369
370 assert_ne!(new_end, u64::MAX);
371
372 if preamble_action == PreambleAction::Skip {
374 new_start += items_in_preamble;
375 new_end += items_in_preamble;
376 new_levels_start += first_row_start;
377 new_levels_end += first_row_start;
378 } else if preamble_action == PreambleAction::Take {
379 debug_assert_eq!(new_start, 0);
380 debug_assert_eq!(new_levels_start, 0);
381 new_end += items_in_preamble;
382 new_levels_end += first_row_start;
383 }
384
385 debug_assert!(new_end <= total_items);
386 (new_start..new_end, new_levels_start..new_levels_end)
387 } else {
388 if range.start > 0 {
394 for (idx, rep) in rep.iter().skip(1).enumerate() {
395 if *rep == max_rep {
396 rows_seen += 1;
397 if rows_seen == range.start {
398 new_start = idx as u64 + 1;
399 break;
400 }
401 }
402 }
403 }
404 let mut new_end = rep.len() as u64;
405 if range.end < total_items {
407 for (idx, rep) in rep[(new_start + 1) as usize..].iter().enumerate() {
408 if *rep == max_rep {
409 rows_seen += 1;
410 if rows_seen == range.end {
411 new_end = idx as u64 + new_start + 1;
412 break;
413 }
414 }
415 }
416 }
417
418 if preamble_action == PreambleAction::Skip {
420 new_start += first_row_start;
421 new_end += first_row_start;
422 } else if preamble_action == PreambleAction::Take {
423 debug_assert_eq!(new_start, 0);
424 new_end += first_row_start;
425 }
426
427 debug_assert!(new_end <= total_items);
428 (new_start..new_end, new_start..new_end)
429 }
430 } else {
431 (range.clone(), range)
434 }
435 }
436
437 fn read_buffer_sizes<const LARGE: bool>(
439 buf: &[u8],
440 offset: &mut usize,
441 num_buffers: u64,
442 ) -> Vec<u32> {
443 let read_size = if LARGE { 4 } else { 2 };
444 (0..num_buffers)
445 .map(|_| {
446 let bytes = &buf[*offset..*offset + read_size];
447 let size = if LARGE {
448 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
449 } else {
450 u16::from_le_bytes([bytes[0], bytes[1]]) as u32
452 };
453 *offset += read_size;
454 size
455 })
456 .collect()
457 }
458
459 fn decode_miniblock_chunk(
461 &self,
462 buf: &LanceBuffer,
463 items_in_chunk: u64,
464 ) -> Result<DecodedMiniBlockChunk> {
465 let mut offset = 0;
466 let num_levels = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
467 offset += 2;
468
469 let rep_size = if self.rep_decompressor.is_some() {
470 let rep_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
471 offset += 2;
472 Some(rep_size)
473 } else {
474 None
475 };
476 let def_size = if self.def_decompressor.is_some() {
477 let def_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
478 offset += 2;
479 Some(def_size)
480 } else {
481 None
482 };
483
484 let buffer_sizes = if self.has_large_chunk {
485 Self::read_buffer_sizes::<true>(buf, &mut offset, self.num_buffers)
486 } else {
487 Self::read_buffer_sizes::<false>(buf, &mut offset, self.num_buffers)
488 };
489
490 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
491
492 let rep = rep_size.map(|rep_size| {
493 let rep = buf.slice_with_length(offset, rep_size as usize);
494 offset += rep_size as usize;
495 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
496 rep
497 });
498
499 let def = def_size.map(|def_size| {
500 let def = buf.slice_with_length(offset, def_size as usize);
501 offset += def_size as usize;
502 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
503 def
504 });
505
506 let buffers = buffer_sizes
507 .into_iter()
508 .map(|buf_size| {
509 let buf = buf.slice_with_length(offset, buf_size as usize);
510 offset += buf_size as usize;
511 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
512 buf
513 })
514 .collect::<Vec<_>>();
515
516 let values = self
517 .value_decompressor
518 .decompress(buffers, items_in_chunk)?;
519
520 let rep = rep
521 .map(|rep| {
522 Self::decode_levels(
523 self.rep_decompressor.as_ref().unwrap().as_ref(),
524 rep,
525 num_levels,
526 )
527 })
528 .transpose()?;
529 let def = def
530 .map(|def| {
531 Self::decode_levels(
532 self.def_decompressor.as_ref().unwrap().as_ref(),
533 def,
534 num_levels,
535 )
536 })
537 .transpose()?;
538
539 Ok(DecodedMiniBlockChunk { rep, def, values })
540 }
541}
542
543impl DecodePageTask for DecodeMiniBlockTask {
544 fn decode(self: Box<Self>) -> Result<DecodedPage> {
545 let mut repbuf: Option<LevelBuffer> = None;
547 let mut defbuf: Option<LevelBuffer> = None;
548
549 let max_rep = self.def_meaning.iter().filter(|l| l.is_list()).count() as u16;
550
551 let estimated_size_bytes = self
553 .instructions
554 .iter()
555 .map(|(_, chunk)| chunk.data.len())
556 .sum::<usize>()
557 * 2;
558 let mut data_builder =
559 DataBlockBuilder::with_capacity_estimate(estimated_size_bytes as u64);
560
561 let mut level_offset = 0;
563
564 let needs_caching: Vec<bool> = self
566 .instructions
567 .windows(2)
568 .map(|w| w[0].1.chunk_idx == w[1].1.chunk_idx)
569 .chain(std::iter::once(false)) .collect();
571
572 let mut chunk_cache: Option<(usize, DecodedMiniBlockChunk)> = None;
574
575 for (idx, (instructions, chunk)) in self.instructions.iter().enumerate() {
577 let should_cache_this_chunk = needs_caching[idx];
578
579 let decoded_chunk = match &chunk_cache {
580 Some((cached_chunk_idx, cached_chunk)) if *cached_chunk_idx == chunk.chunk_idx => {
581 cached_chunk.clone()
583 }
584 _ => {
585 let decoded = self.decode_miniblock_chunk(&chunk.data, chunk.items_in_chunk)?;
587
588 if should_cache_this_chunk {
590 chunk_cache = Some((chunk.chunk_idx, decoded.clone()));
591 }
592 decoded
593 }
594 };
595
596 let DecodedMiniBlockChunk { rep, def, values } = decoded_chunk;
597
598 let row_range_start =
600 instructions.rows_to_skip + instructions.chunk_instructions.rows_to_skip;
601 let row_range_end = row_range_start + instructions.rows_to_take;
602
603 let (item_range, level_range) = Self::map_range(
605 row_range_start..row_range_end,
606 rep.as_ref(),
607 def.as_ref(),
608 max_rep,
609 self.max_visible_level,
610 chunk.items_in_chunk,
611 instructions.preamble_action,
612 );
613 if item_range.end - item_range.start > chunk.items_in_chunk {
614 return Err(lance_core::Error::internal(format!(
615 "Item range {:?} is greater than chunk items in chunk {:?}",
616 item_range, chunk.items_in_chunk
617 )));
618 }
619
620 Self::extend_levels(level_range.clone(), &mut repbuf, &rep, level_offset);
622 Self::extend_levels(level_range.clone(), &mut defbuf, &def, level_offset);
623 level_offset += (level_range.end - level_range.start) as usize;
624 data_builder.append(&values, item_range);
625 }
626
627 let mut data = data_builder.finish();
628
629 let unraveler =
630 RepDefUnraveler::new(repbuf, defbuf, self.def_meaning.clone(), data.num_values());
631
632 if let Some(dictionary) = &self.dictionary_data {
633 let DataBlock::FixedWidth(indices) = data else {
635 return Err(lance_core::Error::internal(format!(
636 "Expected FixedWidth DataBlock for dictionary indices, got {:?}",
637 data
638 )));
639 };
640 data = DataBlock::Dictionary(DictionaryDataBlock::from_parts(
641 indices,
642 dictionary.as_ref().clone(),
643 ));
644 }
645
646 Ok(DecodedPage {
647 data,
648 repdef: unraveler,
649 })
650 }
651}
652
653#[derive(Debug)]
656struct LoadedChunk {
657 data: LanceBuffer,
658 items_in_chunk: u64,
659 byte_range: Range<u64>,
660 chunk_idx: usize,
661}
662
663impl Clone for LoadedChunk {
664 fn clone(&self) -> Self {
665 Self {
666 data: self.data.clone(),
668 items_in_chunk: self.items_in_chunk,
669 byte_range: self.byte_range.clone(),
670 chunk_idx: self.chunk_idx,
671 }
672 }
673}
674
675#[derive(Debug)]
678struct MiniBlockDecoder {
679 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
680 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
681 value_decompressor: Arc<dyn MiniBlockDecompressor>,
682 def_meaning: Arc<[DefinitionInterpretation]>,
683 loaded_chunks: VecDeque<LoadedChunk>,
684 instructions: VecDeque<ChunkInstructions>,
685 offset_in_current_chunk: u64,
686 num_rows: u64,
687 num_buffers: u64,
688 dictionary: Option<Arc<DataBlock>>,
689 has_large_chunk: bool,
690}
691
692impl StructuralPageDecoder for MiniBlockDecoder {
695 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
696 let mut items_desired = num_rows;
697 let mut need_preamble = false;
698 let mut skip_in_chunk = self.offset_in_current_chunk;
699 let mut drain_instructions = Vec::new();
700 while items_desired > 0 || need_preamble {
701 let (instructions, consumed) = self
702 .instructions
703 .front()
704 .unwrap()
705 .drain_from_instruction(&mut items_desired, &mut need_preamble, &mut skip_in_chunk);
706
707 while self.loaded_chunks.front().unwrap().chunk_idx
708 != instructions.chunk_instructions.chunk_idx
709 {
710 self.loaded_chunks.pop_front();
711 }
712 drain_instructions.push((instructions, self.loaded_chunks.front().unwrap().clone()));
713 if consumed {
714 self.instructions.pop_front();
715 }
716 }
717 self.offset_in_current_chunk = skip_in_chunk;
720
721 let max_visible_level = self
722 .def_meaning
723 .iter()
724 .take_while(|l| !l.is_list())
725 .map(|l| l.num_def_levels())
726 .sum::<u16>();
727
728 Ok(Box::new(DecodeMiniBlockTask {
729 instructions: drain_instructions,
730 def_decompressor: self.def_decompressor.clone(),
731 rep_decompressor: self.rep_decompressor.clone(),
732 value_decompressor: self.value_decompressor.clone(),
733 dictionary_data: self.dictionary.clone(),
734 def_meaning: self.def_meaning.clone(),
735 num_buffers: self.num_buffers,
736 max_visible_level,
737 has_large_chunk: self.has_large_chunk,
738 }))
739 }
740
741 fn num_rows(&self) -> u64 {
742 self.num_rows
743 }
744}
745
746#[derive(Debug)]
747struct CachedComplexAllNullState {
748 rep: Option<ScalarBuffer<u16>>,
749 def: Option<ScalarBuffer<u16>>,
750}
751
752impl DeepSizeOf for CachedComplexAllNullState {
753 fn deep_size_of_children(&self, _ctx: &mut Context) -> usize {
754 self.rep.as_ref().map(|buf| buf.len() * 2).unwrap_or(0)
755 + self.def.as_ref().map(|buf| buf.len() * 2).unwrap_or(0)
756 }
757}
758
759impl CachedPageData for CachedComplexAllNullState {
760 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
761 self
762 }
763}
764
765#[derive(Debug)]
774pub struct ComplexAllNullScheduler {
775 buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
777 def_meaning: Arc<[DefinitionInterpretation]>,
778 repdef: Option<Arc<CachedComplexAllNullState>>,
779 max_rep: u16,
780 max_visible_level: u16,
781 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
782 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
783 num_rep_values: u64,
784 num_def_values: u64,
785}
786
787impl ComplexAllNullScheduler {
788 pub fn new(
789 buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
790 def_meaning: Arc<[DefinitionInterpretation]>,
791 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
792 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
793 num_rep_values: u64,
794 num_def_values: u64,
795 ) -> Self {
796 let max_rep = def_meaning.iter().filter(|l| l.is_list()).count() as u16;
797 let max_visible_level = def_meaning
798 .iter()
799 .take_while(|l| !l.is_list())
800 .map(|l| l.num_def_levels())
801 .sum::<u16>();
802 Self {
803 buffer_offsets_and_sizes,
804 def_meaning,
805 repdef: None,
806 max_rep,
807 max_visible_level,
808 rep_decompressor,
809 def_decompressor,
810 num_rep_values,
811 num_def_values,
812 }
813 }
814}
815
816impl StructuralPageScheduler for ComplexAllNullScheduler {
817 fn initialize<'a>(
818 &'a mut self,
819 io: &Arc<dyn EncodingsIo>,
820 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
821 let (rep_pos, rep_size) = self.buffer_offsets_and_sizes[0];
823 let (def_pos, def_size) = self.buffer_offsets_and_sizes[1];
824 let has_rep = rep_size > 0;
825 let has_def = def_size > 0;
826
827 let mut reads = Vec::with_capacity(2);
828 if has_rep {
829 reads.push(rep_pos..rep_pos + rep_size);
830 }
831 if has_def {
832 reads.push(def_pos..def_pos + def_size);
833 }
834
835 let data = io.submit_request(reads, 0);
836 let rep_decompressor = self.rep_decompressor.clone();
837 let def_decompressor = self.def_decompressor.clone();
838 let num_rep_values = self.num_rep_values;
839 let num_def_values = self.num_def_values;
840
841 async move {
842 let data = data.await?;
843 let mut data_iter = data.into_iter();
844
845 let decompress_levels = |compressed_bytes: Bytes,
846 decompressor: &Arc<dyn BlockDecompressor>,
847 num_values: u64,
848 level_type: &str|
849 -> Result<ScalarBuffer<u16>> {
850 let compressed_buffer = LanceBuffer::from_bytes(compressed_bytes, 1);
851 let decompressed = decompressor.decompress(compressed_buffer, num_values)?;
852 match decompressed {
853 DataBlock::FixedWidth(block) => {
854 if block.num_values != num_values {
855 return Err(Error::invalid_input_source(format!(
856 "Unexpected {} level count after decompression: expected {}, got {}",
857 level_type, num_values, block.num_values
858 )
859 .into()));
860 }
861 if block.bits_per_value != 16 {
862 return Err(Error::invalid_input_source(format!(
863 "Unexpected {} level bit width after decompression: expected 16, got {}",
864 level_type, block.bits_per_value
865 )
866 .into()));
867 }
868 Ok(block.data.borrow_to_typed_slice::<u16>())
869 }
870 _ => Err(Error::invalid_input_source(format!(
871 "Expected fixed-width data block for {} levels",
872 level_type
873 )
874 .into())),
875 }
876 };
877
878 let rep = if has_rep {
879 let rep = data_iter.next().unwrap();
880 if let Some(rep_decompressor) = rep_decompressor.as_ref() {
881 Some(decompress_levels(
882 rep,
883 rep_decompressor,
884 num_rep_values,
885 "repetition",
886 )?)
887 } else {
888 let rep = LanceBuffer::from_bytes(rep, 2);
889 let rep = rep.borrow_to_typed_slice::<u16>();
890 Some(rep)
891 }
892 } else {
893 None
894 };
895
896 let def = if has_def {
897 let def = data_iter.next().unwrap();
898 if let Some(def_decompressor) = def_decompressor.as_ref() {
899 Some(decompress_levels(
900 def,
901 def_decompressor,
902 num_def_values,
903 "definition",
904 )?)
905 } else {
906 let def = LanceBuffer::from_bytes(def, 2);
907 let def = def.borrow_to_typed_slice::<u16>();
908 Some(def)
909 }
910 } else {
911 None
912 };
913
914 let repdef = Arc::new(CachedComplexAllNullState { rep, def });
915
916 self.repdef = Some(repdef.clone());
917
918 Ok(repdef as Arc<dyn CachedPageData>)
919 }
920 .boxed()
921 }
922
923 fn load(&mut self, data: &Arc<dyn CachedPageData>) {
924 self.repdef = Some(
925 data.clone()
926 .as_arc_any()
927 .downcast::<CachedComplexAllNullState>()
928 .unwrap(),
929 );
930 }
931
932 fn schedule_ranges(
933 &self,
934 ranges: &[Range<u64>],
935 _io: &Arc<dyn EncodingsIo>,
936 ) -> Result<Vec<PageLoadTask>> {
937 let ranges = VecDeque::from_iter(ranges.iter().cloned());
938 let num_rows = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
939 let decoder = Box::new(ComplexAllNullPageDecoder {
940 ranges,
941 rep: self.repdef.as_ref().unwrap().rep.clone(),
942 def: self.repdef.as_ref().unwrap().def.clone(),
943 num_rows,
944 def_meaning: self.def_meaning.clone(),
945 max_rep: self.max_rep,
946 max_visible_level: self.max_visible_level,
947 cursor_row: 0,
948 cursor_level: 0,
949 }) as Box<dyn StructuralPageDecoder>;
950 let page_load_task = PageLoadTask {
951 decoder_fut: std::future::ready(Ok(decoder)).boxed(),
952 num_rows,
953 };
954 Ok(vec![page_load_task])
955 }
956}
957
958#[derive(Debug)]
959pub struct ComplexAllNullPageDecoder {
960 ranges: VecDeque<Range<u64>>,
961 rep: Option<ScalarBuffer<u16>>,
962 def: Option<ScalarBuffer<u16>>,
963 num_rows: u64,
964 def_meaning: Arc<[DefinitionInterpretation]>,
965 max_rep: u16,
966 max_visible_level: u16,
967 cursor_row: u64,
968 cursor_level: usize,
969}
970
971impl ComplexAllNullPageDecoder {
972 fn drain_ranges(&mut self, num_rows: u64) -> Vec<Range<u64>> {
973 let mut rows_desired = num_rows;
974 let mut ranges = Vec::with_capacity(self.ranges.len());
975 while rows_desired > 0 {
976 let front = self.ranges.front_mut().unwrap();
977 let avail = front.end - front.start;
978 if avail > rows_desired {
979 ranges.push(front.start..front.start + rows_desired);
980 front.start += rows_desired;
981 rows_desired = 0;
982 } else {
983 ranges.push(self.ranges.pop_front().unwrap());
984 rows_desired -= avail;
985 }
986 }
987 ranges
988 }
989
990 fn take_row(&mut self) -> Result<(Range<usize>, u64)> {
991 let start = self.cursor_level;
992 let end = if let Some(rep) = &self.rep {
993 if start >= rep.len() {
994 return Err(Error::internal(
995 "Invalid complex all-null layout: repetition buffer too short",
996 ));
997 }
998 if rep[start] != self.max_rep {
999 return Err(Error::internal(
1000 "Invalid complex all-null layout: row did not start at max repetition level",
1001 ));
1002 }
1003 let mut end = start + 1;
1004 while end < rep.len() && rep[end] != self.max_rep {
1005 end += 1;
1006 }
1007 end
1008 } else {
1009 start + 1
1010 };
1011
1012 let visible = if let Some(def) = &self.def {
1013 if end > def.len() {
1014 return Err(Error::internal(
1015 "Invalid complex all-null layout: definition buffer too short",
1016 ));
1017 }
1018 def[start..end]
1019 .iter()
1020 .filter(|d| **d <= self.max_visible_level)
1021 .count() as u64
1022 } else {
1023 (end - start) as u64
1024 };
1025
1026 self.cursor_level = end;
1027 self.cursor_row += 1;
1028 Ok((start..end, visible))
1029 }
1030
1031 fn skip_to_row(&mut self, target_row: u64) -> Result<()> {
1032 while self.cursor_row < target_row {
1033 self.take_row()?;
1034 }
1035 Ok(())
1036 }
1037}
1038
1039impl StructuralPageDecoder for ComplexAllNullPageDecoder {
1040 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
1041 let drained_ranges = self.drain_ranges(num_rows);
1042 let mut level_slices: Vec<Range<usize>> = Vec::new();
1043 let mut visible_items_total = 0;
1044
1045 for range in drained_ranges {
1046 self.skip_to_row(range.start)?;
1047 for _ in range.start..range.end {
1048 let (level_range, visible) = self.take_row()?;
1049 visible_items_total += visible;
1050 if let Some(last) = level_slices.last_mut()
1051 && last.end == level_range.start
1052 {
1053 last.end = level_range.end;
1054 continue;
1055 }
1056 level_slices.push(level_range);
1057 }
1058 }
1059
1060 Ok(Box::new(DecodeComplexAllNullTask {
1061 level_slices,
1062 visible_items_total,
1063 rep: self.rep.clone(),
1064 def: self.def.clone(),
1065 def_meaning: self.def_meaning.clone(),
1066 max_visible_level: self.max_visible_level,
1067 }))
1068 }
1069
1070 fn num_rows(&self) -> u64 {
1071 self.num_rows
1072 }
1073}
1074
1075#[derive(Debug)]
1078pub struct DecodeComplexAllNullTask {
1079 level_slices: Vec<Range<usize>>,
1080 visible_items_total: u64,
1081 rep: Option<ScalarBuffer<u16>>,
1082 def: Option<ScalarBuffer<u16>>,
1083 def_meaning: Arc<[DefinitionInterpretation]>,
1084 max_visible_level: u16,
1085}
1086
1087impl DecodeComplexAllNullTask {
1088 fn decode_level(&self, levels: &Option<ScalarBuffer<u16>>) -> Option<Vec<u16>> {
1089 levels.as_ref().map(|levels| {
1090 let num_levels = self
1091 .level_slices
1092 .iter()
1093 .map(|range| range.end - range.start)
1094 .sum();
1095 let mut referenced_levels = Vec::with_capacity(num_levels);
1096 for range in &self.level_slices {
1097 referenced_levels.extend(levels[range.start..range.end].iter().copied());
1098 }
1099 referenced_levels
1100 })
1101 }
1102}
1103
1104impl DecodePageTask for DecodeComplexAllNullTask {
1105 fn decode(self: Box<Self>) -> Result<DecodedPage> {
1106 let rep = self.decode_level(&self.rep);
1107 let def = self.decode_level(&self.def);
1108
1109 let num_values = if let Some(def) = &def {
1113 def.iter().filter(|&d| *d <= self.max_visible_level).count() as u64
1114 } else {
1115 self.visible_items_total
1116 };
1117
1118 let data = DataBlock::AllNull(AllNullDataBlock { num_values });
1119 let unraveler = RepDefUnraveler::new(rep, def, self.def_meaning, num_values);
1120 Ok(DecodedPage {
1121 data,
1122 repdef: unraveler,
1123 })
1124 }
1125}
1126
1127#[derive(Debug, Default)]
1132pub struct SimpleAllNullScheduler {}
1133
1134impl StructuralPageScheduler for SimpleAllNullScheduler {
1135 fn initialize<'a>(
1136 &'a mut self,
1137 _io: &Arc<dyn EncodingsIo>,
1138 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
1139 std::future::ready(Ok(Arc::new(NoCachedPageData) as Arc<dyn CachedPageData>)).boxed()
1140 }
1141
1142 fn load(&mut self, _cache: &Arc<dyn CachedPageData>) {}
1143
1144 fn schedule_ranges(
1145 &self,
1146 ranges: &[Range<u64>],
1147 _io: &Arc<dyn EncodingsIo>,
1148 ) -> Result<Vec<PageLoadTask>> {
1149 let num_rows = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
1150 let decoder =
1151 Box::new(SimpleAllNullPageDecoder { num_rows }) as Box<dyn StructuralPageDecoder>;
1152 let page_load_task = PageLoadTask {
1153 decoder_fut: std::future::ready(Ok(decoder)).boxed(),
1154 num_rows,
1155 };
1156 Ok(vec![page_load_task])
1157 }
1158}
1159
1160#[derive(Debug)]
1163struct SimpleAllNullDecodePageTask {
1164 num_values: u64,
1165}
1166impl DecodePageTask for SimpleAllNullDecodePageTask {
1167 fn decode(self: Box<Self>) -> Result<DecodedPage> {
1168 let unraveler = RepDefUnraveler::new(
1169 None,
1170 Some(vec![1; self.num_values as usize]),
1171 Arc::new([DefinitionInterpretation::NullableItem]),
1172 self.num_values,
1173 );
1174 Ok(DecodedPage {
1175 data: DataBlock::AllNull(AllNullDataBlock {
1176 num_values: self.num_values,
1177 }),
1178 repdef: unraveler,
1179 })
1180 }
1181}
1182
1183#[derive(Debug)]
1184pub struct SimpleAllNullPageDecoder {
1185 num_rows: u64,
1186}
1187
1188impl StructuralPageDecoder for SimpleAllNullPageDecoder {
1189 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
1190 Ok(Box::new(SimpleAllNullDecodePageTask {
1191 num_values: num_rows,
1192 }))
1193 }
1194
1195 fn num_rows(&self) -> u64 {
1196 self.num_rows
1197 }
1198}
1199
1200#[derive(Debug, Clone)]
1201struct MiniBlockSchedulerDictionary {
1202 dictionary_decompressor: Arc<dyn BlockDecompressor>,
1204 dictionary_buf_position_and_size: (u64, u64),
1205 dictionary_data_alignment: u64,
1206 num_dictionary_items: u64,
1207}
1208
1209#[derive(Debug)]
1211struct MiniBlockRepIndexBlock {
1212 first_row: u64,
1216 starts_including_trailer: u64,
1219 has_preamble: bool,
1221 has_trailer: bool,
1223}
1224
1225impl DeepSizeOf for MiniBlockRepIndexBlock {
1226 fn deep_size_of_children(&self, _context: &mut Context) -> usize {
1227 0
1228 }
1229}
1230
1231#[derive(Debug)]
1236struct MiniBlockRepIndex {
1237 blocks: Vec<MiniBlockRepIndexBlock>,
1238}
1239
1240impl DeepSizeOf for MiniBlockRepIndex {
1241 fn deep_size_of_children(&self, context: &mut Context) -> usize {
1242 self.blocks.deep_size_of_children(context)
1243 }
1244}
1245
1246impl MiniBlockRepIndex {
1247 pub fn default_from_chunks(chunks: &[ChunkMeta]) -> Self {
1252 let mut blocks = Vec::with_capacity(chunks.len());
1253 let mut offset: u64 = 0;
1254
1255 for c in chunks {
1256 blocks.push(MiniBlockRepIndexBlock {
1257 first_row: offset,
1258 starts_including_trailer: c.num_values,
1259 has_preamble: false,
1260 has_trailer: false,
1261 });
1262
1263 offset += c.num_values;
1264 }
1265
1266 Self { blocks }
1267 }
1268
1269 pub fn decode_from_bytes(rep_bytes: &[u8], stride: usize) -> Self {
1275 let buffer = crate::buffer::LanceBuffer::from(rep_bytes.to_vec());
1277 let u64_slice = buffer.borrow_to_typed_slice::<u64>();
1278 let n = u64_slice.len() / stride;
1279
1280 let mut blocks = Vec::with_capacity(n);
1281 let mut chunk_has_preamble = false;
1282 let mut offset: u64 = 0;
1283
1284 for i in 0..n {
1286 let base_idx = i * stride;
1287 let ends = u64_slice[base_idx];
1288 let partial = u64_slice[base_idx + 1];
1289
1290 let has_trailer = partial > 0;
1291 let starts_including_trailer =
1293 ends + (has_trailer as u64) - (chunk_has_preamble as u64);
1294
1295 blocks.push(MiniBlockRepIndexBlock {
1296 first_row: offset,
1297 starts_including_trailer,
1298 has_preamble: chunk_has_preamble,
1299 has_trailer,
1300 });
1301
1302 chunk_has_preamble = has_trailer;
1303 offset += starts_including_trailer;
1304 }
1305
1306 Self { blocks }
1307 }
1308}
1309
1310#[derive(Debug)]
1312struct MiniBlockCacheableState {
1313 chunk_meta: Vec<ChunkMeta>,
1315 rep_index: MiniBlockRepIndex,
1317 dictionary: Option<Arc<DataBlock>>,
1319}
1320
1321impl DeepSizeOf for MiniBlockCacheableState {
1322 fn deep_size_of_children(&self, context: &mut Context) -> usize {
1323 self.rep_index.deep_size_of_children(context)
1324 + self
1325 .dictionary
1326 .as_ref()
1327 .map(|dict| dict.data_size() as usize)
1328 .unwrap_or(0)
1329 }
1330}
1331
1332impl CachedPageData for MiniBlockCacheableState {
1333 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
1334 self
1335 }
1336}
1337
1338#[derive(Debug)]
1365pub struct MiniBlockScheduler {
1366 buffer_offsets_and_sizes: Vec<(u64, u64)>,
1368 priority: u64,
1369 items_in_page: u64,
1370 repetition_index_depth: u16,
1371 num_buffers: u64,
1372 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
1373 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
1374 value_decompressor: Arc<dyn MiniBlockDecompressor>,
1375 def_meaning: Arc<[DefinitionInterpretation]>,
1376 dictionary: Option<MiniBlockSchedulerDictionary>,
1377 page_meta: Option<Arc<MiniBlockCacheableState>>,
1379 has_large_chunk: bool,
1380}
1381
1382impl MiniBlockScheduler {
1383 fn try_new(
1384 buffer_offsets_and_sizes: &[(u64, u64)],
1385 priority: u64,
1386 items_in_page: u64,
1387 layout: &pb21::MiniBlockLayout,
1388 decompressors: &dyn DecompressionStrategy,
1389 ) -> Result<Self> {
1390 let rep_decompressor = layout
1391 .rep_compression
1392 .as_ref()
1393 .map(|rep_compression| {
1394 decompressors
1395 .create_block_decompressor(rep_compression)
1396 .map(Arc::from)
1397 })
1398 .transpose()?;
1399 let def_decompressor = layout
1400 .def_compression
1401 .as_ref()
1402 .map(|def_compression| {
1403 decompressors
1404 .create_block_decompressor(def_compression)
1405 .map(Arc::from)
1406 })
1407 .transpose()?;
1408 let def_meaning = layout
1409 .layers
1410 .iter()
1411 .map(|l| ProtobufUtils21::repdef_layer_to_def_interp(*l))
1412 .collect::<Vec<_>>();
1413 let value_decompressor = decompressors.create_miniblock_decompressor(
1414 layout.value_compression.as_ref().unwrap(),
1415 decompressors,
1416 )?;
1417
1418 let dictionary = if let Some(dictionary_encoding) = layout.dictionary.as_ref() {
1419 let num_dictionary_items = layout.num_dictionary_items;
1420 let dictionary_decompressor = decompressors
1421 .create_block_decompressor(dictionary_encoding)?
1422 .into();
1423 let dictionary_data_alignment = match dictionary_encoding.compression.as_ref().unwrap()
1424 {
1425 Compression::Variable(_) => 4,
1426 Compression::Flat(_) => 16,
1427 Compression::General(_) => 1,
1428 Compression::InlineBitpacking(_) | Compression::OutOfLineBitpacking(_) => {
1429 crate::encoder::MIN_PAGE_BUFFER_ALIGNMENT
1430 }
1431 _ => {
1432 return Err(Error::invalid_input_source(
1433 format!(
1434 "Unsupported mini-block dictionary encoding: {:?}",
1435 dictionary_encoding.compression.as_ref().unwrap()
1436 )
1437 .into(),
1438 ));
1439 }
1440 };
1441 Some(MiniBlockSchedulerDictionary {
1442 dictionary_decompressor,
1443 dictionary_buf_position_and_size: buffer_offsets_and_sizes[2],
1444 dictionary_data_alignment,
1445 num_dictionary_items,
1446 })
1447 } else {
1448 None
1449 };
1450
1451 Ok(Self {
1452 buffer_offsets_and_sizes: buffer_offsets_and_sizes.to_vec(),
1453 rep_decompressor,
1454 def_decompressor,
1455 value_decompressor: value_decompressor.into(),
1456 repetition_index_depth: layout.repetition_index_depth as u16,
1457 num_buffers: layout.num_buffers,
1458 priority,
1459 items_in_page,
1460 dictionary,
1461 def_meaning: def_meaning.into(),
1462 page_meta: None,
1463 has_large_chunk: layout.has_large_chunk,
1464 })
1465 }
1466
1467 fn lookup_chunks(&self, chunk_indices: &[usize]) -> Vec<LoadedChunk> {
1468 let page_meta = self.page_meta.as_ref().unwrap();
1469 chunk_indices
1470 .iter()
1471 .map(|&chunk_idx| {
1472 let chunk_meta = &page_meta.chunk_meta[chunk_idx];
1473 let bytes_start = chunk_meta.offset_bytes;
1474 let bytes_end = bytes_start + chunk_meta.chunk_size_bytes;
1475 LoadedChunk {
1476 byte_range: bytes_start..bytes_end,
1477 items_in_chunk: chunk_meta.num_values,
1478 chunk_idx,
1479 data: LanceBuffer::empty(),
1480 }
1481 })
1482 .collect()
1483 }
1484}
1485
1486#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1487enum PreambleAction {
1488 Take,
1489 Skip,
1490 Absent,
1491}
1492
1493#[derive(Clone, Debug, PartialEq, Eq)]
1528struct ChunkInstructions {
1529 chunk_idx: usize,
1531 preamble: PreambleAction,
1537 rows_to_skip: u64,
1541 rows_to_take: u64,
1544 take_trailer: bool,
1551}
1552
1553#[derive(Debug, PartialEq, Eq)]
1571struct ChunkDrainInstructions {
1572 chunk_instructions: ChunkInstructions,
1573 rows_to_skip: u64,
1574 rows_to_take: u64,
1575 preamble_action: PreambleAction,
1576}
1577
1578impl ChunkInstructions {
1579 fn schedule_instructions(
1585 rep_index: &MiniBlockRepIndex,
1586 user_ranges: &[Range<u64>],
1587 ) -> Vec<Self> {
1588 let mut chunk_instructions = Vec::with_capacity(user_ranges.len());
1592
1593 for user_range in user_ranges {
1594 let mut rows_needed = user_range.end - user_range.start;
1595 let mut need_preamble = false;
1596
1597 let mut block_index = match rep_index
1600 .blocks
1601 .binary_search_by_key(&user_range.start, |block| block.first_row)
1602 {
1603 Ok(idx) => {
1604 let mut idx = idx;
1607 while idx > 0 && rep_index.blocks[idx - 1].first_row == user_range.start {
1608 idx -= 1;
1609 }
1610 idx
1611 }
1612 Err(idx) => idx - 1,
1614 };
1615
1616 let mut to_skip = user_range.start - rep_index.blocks[block_index].first_row;
1617
1618 while rows_needed > 0 || need_preamble {
1619 if block_index >= rep_index.blocks.len() {
1621 log::warn!(
1622 "schedule_instructions inconsistency: block_index >= rep_index.blocks.len(), exiting early"
1623 );
1624 break;
1625 }
1626
1627 let chunk = &rep_index.blocks[block_index];
1628 let rows_avail = chunk.starts_including_trailer.saturating_sub(to_skip);
1629
1630 if rows_avail == 0 && to_skip == 0 {
1634 if chunk.has_preamble && need_preamble {
1636 chunk_instructions.push(Self {
1637 chunk_idx: block_index,
1638 preamble: PreambleAction::Take,
1639 rows_to_skip: 0,
1640 rows_to_take: 0,
1641 take_trailer: chunk.has_trailer,
1645 });
1646 if chunk.starts_including_trailer > 0
1650 || block_index == rep_index.blocks.len() - 1
1651 {
1652 need_preamble = false;
1653 }
1654 }
1655 block_index += 1;
1657 continue;
1658 }
1659
1660 if rows_avail == 0 && to_skip > 0 {
1664 to_skip -= chunk.starts_including_trailer;
1667 block_index += 1;
1668 continue;
1669 }
1670
1671 let rows_to_take = rows_avail.min(rows_needed);
1672 rows_needed -= rows_to_take;
1673
1674 let mut take_trailer = false;
1675 let preamble = if chunk.has_preamble {
1676 if need_preamble {
1677 PreambleAction::Take
1678 } else {
1679 PreambleAction::Skip
1680 }
1681 } else {
1682 PreambleAction::Absent
1683 };
1684
1685 if rows_to_take == rows_avail && chunk.has_trailer {
1687 take_trailer = true;
1688 need_preamble = true;
1689 } else {
1690 need_preamble = false;
1691 };
1692
1693 chunk_instructions.push(Self {
1694 preamble,
1695 chunk_idx: block_index,
1696 rows_to_skip: to_skip,
1697 rows_to_take,
1698 take_trailer,
1699 });
1700
1701 to_skip = 0;
1702 block_index += 1;
1703 }
1704 }
1705
1706 if user_ranges.len() > 1 {
1710 let mut merged_instructions = Vec::with_capacity(chunk_instructions.len());
1712 let mut instructions_iter = chunk_instructions.into_iter();
1713 merged_instructions.push(instructions_iter.next().unwrap());
1714 for instruction in instructions_iter {
1715 let last = merged_instructions.last_mut().unwrap();
1716 if last.chunk_idx == instruction.chunk_idx
1717 && last.rows_to_take + last.rows_to_skip == instruction.rows_to_skip
1718 {
1719 last.rows_to_take += instruction.rows_to_take;
1720 last.take_trailer |= instruction.take_trailer;
1721 } else {
1722 merged_instructions.push(instruction);
1723 }
1724 }
1725 merged_instructions
1726 } else {
1727 chunk_instructions
1728 }
1729 }
1730
1731 fn drain_from_instruction(
1732 &self,
1733 rows_desired: &mut u64,
1734 need_preamble: &mut bool,
1735 skip_in_chunk: &mut u64,
1736 ) -> (ChunkDrainInstructions, bool) {
1737 debug_assert!(!*need_preamble || *skip_in_chunk == 0);
1739 let rows_avail = self.rows_to_take - *skip_in_chunk;
1740 let has_preamble = self.preamble != PreambleAction::Absent;
1741 let preamble_action = match (*need_preamble, has_preamble) {
1742 (true, true) => PreambleAction::Take,
1743 (true, false) => panic!("Need preamble but there isn't one"),
1744 (false, true) => PreambleAction::Skip,
1745 (false, false) => PreambleAction::Absent,
1746 };
1747
1748 let rows_taking = if *rows_desired >= rows_avail {
1751 *need_preamble = self.take_trailer;
1759 rows_avail
1760 } else {
1761 *need_preamble = false;
1764 *rows_desired
1765 };
1766 let rows_skipped = *skip_in_chunk;
1767
1768 let consumed_chunk = if *rows_desired >= rows_avail {
1770 *rows_desired -= rows_avail;
1771 *skip_in_chunk = 0;
1772 true
1773 } else {
1774 *skip_in_chunk += *rows_desired;
1775 *rows_desired = 0;
1776 false
1777 };
1778
1779 (
1780 ChunkDrainInstructions {
1781 chunk_instructions: self.clone(),
1782 rows_to_skip: rows_skipped,
1783 rows_to_take: rows_taking,
1784 preamble_action,
1785 },
1786 consumed_chunk,
1787 )
1788 }
1789}
1790
1791enum Words {
1792 U16(ScalarBuffer<u16>),
1793 U32(ScalarBuffer<u32>),
1794}
1795
1796struct WordsIter<'a> {
1797 iter: Box<dyn Iterator<Item = u32> + 'a>,
1798}
1799
1800impl Words {
1801 pub fn len(&self) -> usize {
1802 match self {
1803 Self::U16(b) => b.len(),
1804 Self::U32(b) => b.len(),
1805 }
1806 }
1807
1808 pub fn iter(&self) -> WordsIter<'_> {
1809 match self {
1810 Self::U16(buf) => WordsIter {
1811 iter: Box::new(buf.iter().map(|&x| x as u32)),
1812 },
1813 Self::U32(buf) => WordsIter {
1814 iter: Box::new(buf.iter().copied()),
1815 },
1816 }
1817 }
1818
1819 pub fn from_bytes(bytes: Bytes, has_large_chunk: bool) -> Result<Self> {
1820 let bytes_per_value = if has_large_chunk { 4 } else { 2 };
1821 assert_eq!(bytes.len() % bytes_per_value, 0);
1822 let buffer = LanceBuffer::from_bytes(bytes, bytes_per_value as u64);
1823 if has_large_chunk {
1824 Ok(Self::U32(buffer.borrow_to_typed_slice::<u32>()))
1825 } else {
1826 Ok(Self::U16(buffer.borrow_to_typed_slice::<u16>()))
1827 }
1828 }
1829}
1830
1831impl<'a> Iterator for WordsIter<'a> {
1832 type Item = u32;
1833
1834 fn next(&mut self) -> Option<Self::Item> {
1835 self.iter.next()
1836 }
1837}
1838
1839impl StructuralPageScheduler for MiniBlockScheduler {
1840 fn initialize<'a>(
1841 &'a mut self,
1842 io: &Arc<dyn EncodingsIo>,
1843 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
1844 let (meta_buf_position, meta_buf_size) = self.buffer_offsets_and_sizes[0];
1848 let value_buf_position = self.buffer_offsets_and_sizes[1].0;
1849 let mut bufs_needed = 1;
1850 if self.dictionary.is_some() {
1851 bufs_needed += 1;
1852 }
1853 if self.repetition_index_depth > 0 {
1854 bufs_needed += 1;
1855 }
1856 let mut required_ranges = Vec::with_capacity(bufs_needed);
1857 required_ranges.push(meta_buf_position..meta_buf_position + meta_buf_size);
1858 if let Some(ref dictionary) = self.dictionary {
1859 required_ranges.push(
1860 dictionary.dictionary_buf_position_and_size.0
1861 ..dictionary.dictionary_buf_position_and_size.0
1862 + dictionary.dictionary_buf_position_and_size.1,
1863 );
1864 }
1865 if self.repetition_index_depth > 0 {
1866 let (rep_index_pos, rep_index_size) = self.buffer_offsets_and_sizes.last().unwrap();
1867 required_ranges.push(*rep_index_pos..*rep_index_pos + *rep_index_size);
1868 }
1869 let io_req = io.submit_request(required_ranges, 0);
1870
1871 async move {
1872 let mut buffers = io_req.await?.into_iter().fuse();
1873 let meta_bytes = buffers.next().unwrap();
1874 let dictionary_bytes = self.dictionary.as_ref().and_then(|_| buffers.next());
1875 let rep_index_bytes = buffers.next();
1876
1877 let words = Words::from_bytes(meta_bytes, self.has_large_chunk)?;
1879 let mut chunk_meta = Vec::with_capacity(words.len());
1880
1881 let mut rows_counter = 0;
1882 let mut offset_bytes = value_buf_position;
1883 for (word_idx, word) in words.iter().enumerate() {
1884 let log_num_values = word & 0x0F;
1885 let divided_bytes = word >> 4;
1886 let num_bytes = (divided_bytes as usize + 1) * MINIBLOCK_ALIGNMENT;
1887 debug_assert!(num_bytes > 0);
1888 let num_values = if word_idx < words.len() - 1 {
1889 debug_assert!(log_num_values > 0);
1890 1 << log_num_values
1891 } else {
1892 debug_assert!(
1893 log_num_values == 0
1894 || (1 << log_num_values) == (self.items_in_page - rows_counter)
1895 );
1896 self.items_in_page - rows_counter
1897 };
1898 rows_counter += num_values;
1899
1900 chunk_meta.push(ChunkMeta {
1901 num_values,
1902 chunk_size_bytes: num_bytes as u64,
1903 offset_bytes,
1904 });
1905 offset_bytes += num_bytes as u64;
1906 }
1907
1908 let rep_index = if let Some(rep_index_data) = rep_index_bytes {
1910 assert!(rep_index_data.len() % 8 == 0);
1911 let stride = self.repetition_index_depth as usize + 1;
1912 MiniBlockRepIndex::decode_from_bytes(&rep_index_data, stride)
1913 } else {
1914 MiniBlockRepIndex::default_from_chunks(&chunk_meta)
1915 };
1916
1917 let mut page_meta = MiniBlockCacheableState {
1918 chunk_meta,
1919 rep_index,
1920 dictionary: None,
1921 };
1922
1923 if let Some(ref mut dictionary) = self.dictionary {
1925 let dictionary_data = dictionary_bytes.unwrap();
1926 page_meta.dictionary =
1927 Some(Arc::new(dictionary.dictionary_decompressor.decompress(
1928 LanceBuffer::from_bytes(
1929 dictionary_data,
1930 dictionary.dictionary_data_alignment,
1931 ),
1932 dictionary.num_dictionary_items,
1933 )?));
1934 };
1935 let page_meta = Arc::new(page_meta);
1936 self.page_meta = Some(page_meta.clone());
1937 Ok(page_meta as Arc<dyn CachedPageData>)
1938 }
1939 .boxed()
1940 }
1941
1942 fn load(&mut self, data: &Arc<dyn CachedPageData>) {
1943 self.page_meta = Some(
1944 data.clone()
1945 .as_arc_any()
1946 .downcast::<MiniBlockCacheableState>()
1947 .unwrap(),
1948 );
1949 }
1950
1951 fn schedule_ranges(
1952 &self,
1953 ranges: &[Range<u64>],
1954 io: &Arc<dyn EncodingsIo>,
1955 ) -> Result<Vec<PageLoadTask>> {
1956 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
1957
1958 let page_meta = self.page_meta.as_ref().unwrap();
1959
1960 let chunk_instructions =
1961 ChunkInstructions::schedule_instructions(&page_meta.rep_index, ranges);
1962
1963 debug_assert_eq!(
1964 num_rows,
1965 chunk_instructions
1966 .iter()
1967 .map(|ci| ci.rows_to_take)
1968 .sum::<u64>()
1969 );
1970
1971 let chunks_needed = chunk_instructions
1972 .iter()
1973 .map(|ci| ci.chunk_idx)
1974 .unique()
1975 .collect::<Vec<_>>();
1976
1977 let mut loaded_chunks = self.lookup_chunks(&chunks_needed);
1978 let chunk_ranges = loaded_chunks
1979 .iter()
1980 .map(|c| c.byte_range.clone())
1981 .collect::<Vec<_>>();
1982 let loaded_chunk_data = io.submit_request(chunk_ranges, self.priority);
1983
1984 let rep_decompressor = self.rep_decompressor.clone();
1985 let def_decompressor = self.def_decompressor.clone();
1986 let value_decompressor = self.value_decompressor.clone();
1987 let num_buffers = self.num_buffers;
1988 let has_large_chunk = self.has_large_chunk;
1989 let dictionary = page_meta
1990 .dictionary
1991 .as_ref()
1992 .map(|dictionary| dictionary.clone());
1993 let def_meaning = self.def_meaning.clone();
1994
1995 let res = async move {
1996 let loaded_chunk_data = loaded_chunk_data.await?;
1997 for (loaded_chunk, chunk_data) in loaded_chunks.iter_mut().zip(loaded_chunk_data) {
1998 loaded_chunk.data = LanceBuffer::from_bytes(chunk_data, 1);
1999 }
2000
2001 Ok(Box::new(MiniBlockDecoder {
2002 rep_decompressor,
2003 def_decompressor,
2004 value_decompressor,
2005 def_meaning,
2006 loaded_chunks: VecDeque::from_iter(loaded_chunks),
2007 instructions: VecDeque::from(chunk_instructions),
2008 offset_in_current_chunk: 0,
2009 dictionary,
2010 num_rows,
2011 num_buffers,
2012 has_large_chunk,
2013 }) as Box<dyn StructuralPageDecoder>)
2014 }
2015 .boxed();
2016 let page_load_task = PageLoadTask {
2017 decoder_fut: res,
2018 num_rows,
2019 };
2020 Ok(vec![page_load_task])
2021 }
2022}
2023
2024#[derive(Debug, Clone, Copy)]
2025struct FullZipRepIndexDetails {
2026 buf_position: u64,
2027 bytes_per_value: u64, }
2029
2030#[derive(Debug)]
2031enum PerValueDecompressor {
2032 Fixed(Arc<dyn FixedPerValueDecompressor>),
2033 Variable(Arc<dyn VariablePerValueDecompressor>),
2034}
2035
2036#[derive(Debug)]
2037struct FullZipDecodeDetails {
2038 value_decompressor: PerValueDecompressor,
2039 def_meaning: Arc<[DefinitionInterpretation]>,
2040 ctrl_word_parser: ControlWordParser,
2041 max_rep: u16,
2042 max_visible_def: u16,
2043}
2044
2045#[derive(Debug, Clone)]
2057enum FullZipReadSource {
2058 Remote(Arc<dyn EncodingsIo>),
2060 PrefetchedPage { base_offset: u64, data: LanceBuffer },
2062}
2063
2064impl FullZipReadSource {
2065 fn fetch(
2069 &self,
2070 ranges: &[Range<u64>],
2071 priority: u64,
2072 ) -> BoxFuture<'static, Result<VecDeque<LanceBuffer>>> {
2073 match self {
2074 Self::Remote(io) => {
2075 let io = io.clone();
2076 let ranges = ranges.to_vec();
2077 async move {
2078 let data = io.submit_request(ranges, priority).await?;
2079 Ok(data
2080 .into_iter()
2081 .map(|bytes| LanceBuffer::from_bytes(bytes, 1))
2082 .collect::<VecDeque<_>>())
2083 }
2084 .boxed()
2085 }
2086 Self::PrefetchedPage { base_offset, data } => {
2087 let base_offset = *base_offset;
2088 let data = data.clone();
2089 let page_end = base_offset + data.len() as u64;
2090 std::future::ready(
2091 ranges
2092 .iter()
2093 .map(|range| {
2094 if range.start > range.end
2095 || range.start < base_offset
2096 || range.end > page_end
2097 {
2098 return Err(Error::internal(format!(
2099 "Requested range {:?} is outside page range {}..{}",
2100 range, base_offset, page_end
2101 )));
2102 }
2103 let start = (range.start - base_offset) as usize;
2104 let len = (range.end - range.start) as usize;
2105 Ok(data.slice_with_length(start, len))
2106 })
2107 .collect::<Result<VecDeque<_>>>(),
2108 )
2109 .boxed()
2110 }
2111 }
2112 }
2113}
2114
2115#[derive(Debug)]
2123pub struct FullZipScheduler {
2124 data_buf_position: u64,
2125 data_buf_size: u64,
2126 rep_index: Option<FullZipRepIndexDetails>,
2127 priority: u64,
2128 rows_in_page: u64,
2129 bits_per_offset: u8,
2130 details: Arc<FullZipDecodeDetails>,
2131 cached_state: Option<Arc<FullZipCacheableState>>,
2133 enable_cache: bool,
2135}
2136
2137impl FullZipScheduler {
2138 fn try_new(
2139 buffer_offsets_and_sizes: &[(u64, u64)],
2140 priority: u64,
2141 rows_in_page: u64,
2142 layout: &pb21::FullZipLayout,
2143 decompressors: &dyn DecompressionStrategy,
2144 ) -> Result<Self> {
2145 let (data_buf_position, data_buf_size) = buffer_offsets_and_sizes[0];
2146 let rep_index = buffer_offsets_and_sizes.get(1).map(|(pos, len)| {
2147 let num_reps = rows_in_page + 1;
2148 let bytes_per_rep = len / num_reps;
2149 debug_assert_eq!(len % num_reps, 0);
2150 debug_assert!(
2151 bytes_per_rep == 1
2152 || bytes_per_rep == 2
2153 || bytes_per_rep == 4
2154 || bytes_per_rep == 8
2155 );
2156 FullZipRepIndexDetails {
2157 buf_position: *pos,
2158 bytes_per_value: bytes_per_rep,
2159 }
2160 });
2161
2162 let value_decompressor = match layout.details {
2163 Some(pb21::full_zip_layout::Details::BitsPerValue(_)) => {
2164 let decompressor = decompressors.create_fixed_per_value_decompressor(
2165 layout.value_compression.as_ref().unwrap(),
2166 )?;
2167 PerValueDecompressor::Fixed(decompressor.into())
2168 }
2169 Some(pb21::full_zip_layout::Details::BitsPerOffset(_)) => {
2170 let decompressor = decompressors.create_variable_per_value_decompressor(
2171 layout.value_compression.as_ref().unwrap(),
2172 )?;
2173 PerValueDecompressor::Variable(decompressor.into())
2174 }
2175 None => {
2176 panic!("Full-zip layout must have a `details` field");
2177 }
2178 };
2179 let ctrl_word_parser = ControlWordParser::new(
2180 layout.bits_rep.try_into().unwrap(),
2181 layout.bits_def.try_into().unwrap(),
2182 );
2183 let def_meaning = layout
2184 .layers
2185 .iter()
2186 .map(|l| ProtobufUtils21::repdef_layer_to_def_interp(*l))
2187 .collect::<Vec<_>>();
2188
2189 let max_rep = def_meaning.iter().filter(|d| d.is_list()).count() as u16;
2190 let max_visible_def = def_meaning
2191 .iter()
2192 .filter(|d| !d.is_list())
2193 .map(|d| d.num_def_levels())
2194 .sum();
2195
2196 let bits_per_offset = match layout.details {
2197 Some(pb21::full_zip_layout::Details::BitsPerValue(_)) => 32,
2198 Some(pb21::full_zip_layout::Details::BitsPerOffset(bits_per_offset)) => {
2199 bits_per_offset as u8
2200 }
2201 None => panic!("Full-zip layout must have a `details` field"),
2202 };
2203
2204 let details = Arc::new(FullZipDecodeDetails {
2205 value_decompressor,
2206 def_meaning: def_meaning.into(),
2207 ctrl_word_parser,
2208 max_rep,
2209 max_visible_def,
2210 });
2211 Ok(Self {
2212 data_buf_position,
2213 data_buf_size,
2214 rep_index,
2215 details,
2216 priority,
2217 rows_in_page,
2218 bits_per_offset,
2219 cached_state: None,
2220 enable_cache: false,
2221 })
2222 }
2223
2224 fn covers_entire_page(ranges: &[Range<u64>], rows_in_page: u64) -> bool {
2225 if ranges.is_empty() {
2226 return false;
2227 }
2228 let mut expected_start = 0;
2229 for range in ranges {
2230 if range.start != expected_start || range.end > rows_in_page || range.end < range.start
2231 {
2232 return false;
2233 }
2234 expected_start = range.end;
2235 }
2236 expected_start == rows_in_page
2237 }
2238
2239 fn create_page_load_task(
2240 io_future: BoxFuture<'static, Result<Vec<Bytes>>>,
2241 num_rows: u64,
2242 details: Arc<FullZipDecodeDetails>,
2243 bits_per_offset: u8,
2244 ) -> PageLoadTask {
2245 let load_task = async move {
2246 let buffers = io_future.await?;
2247 let data = buffers
2248 .into_iter()
2249 .map(|bytes| LanceBuffer::from_bytes(bytes, 1))
2250 .collect::<VecDeque<_>>();
2251 Self::create_decoder(details, data, num_rows, bits_per_offset)
2252 }
2253 .boxed();
2254 PageLoadTask {
2255 decoder_fut: load_task,
2256 num_rows,
2257 }
2258 }
2259
2260 fn create_decoder(
2262 details: Arc<FullZipDecodeDetails>,
2263 data: VecDeque<LanceBuffer>,
2264 num_rows: u64,
2265 bits_per_offset: u8,
2266 ) -> Result<Box<dyn StructuralPageDecoder>> {
2267 match &details.value_decompressor {
2268 PerValueDecompressor::Fixed(decompressor) => {
2269 let bits_per_value = decompressor.bits_per_value();
2270 if bits_per_value % 8 != 0 {
2271 return Err(lance_core::Error::not_supported_source("Bit-packed full-zip encoding (non-byte-aligned values) is not yet implemented".into()));
2272 }
2273 let bytes_per_value = bits_per_value / 8;
2274 let total_bytes_per_value =
2275 bytes_per_value as usize + details.ctrl_word_parser.bytes_per_word();
2276 if total_bytes_per_value == 0 {
2277 return Err(lance_core::Error::internal(
2278 "Invalid encoding: per-row byte width must be greater than 0",
2279 ));
2280 }
2281 Ok(Box::new(FixedFullZipDecoder {
2282 details,
2283 data,
2284 num_rows,
2285 offset_in_current: 0,
2286 bytes_per_value: bytes_per_value as usize,
2287 total_bytes_per_value,
2288 }) as Box<dyn StructuralPageDecoder>)
2289 }
2290 PerValueDecompressor::Variable(_decompressor) => {
2291 Ok(Box::new(VariableFullZipDecoder::new(
2292 details,
2293 data,
2294 num_rows,
2295 bits_per_offset,
2296 bits_per_offset,
2297 )?))
2298 }
2299 }
2300 }
2301
2302 fn extract_byte_ranges_from_pairs(
2305 buffer: LanceBuffer,
2306 bytes_per_value: u64,
2307 data_buf_position: u64,
2308 ) -> Vec<Range<u64>> {
2309 ByteUnpacker::new(buffer, bytes_per_value as usize)
2310 .chunks(2)
2311 .into_iter()
2312 .map(|mut c| {
2313 let start = c.next().unwrap() + data_buf_position;
2314 let end = c.next().unwrap() + data_buf_position;
2315 start..end
2316 })
2317 .collect::<Vec<_>>()
2318 }
2319
2320 fn extract_byte_ranges_from_cached(
2323 buffer: &LanceBuffer,
2324 ranges: &[Range<u64>],
2325 bytes_per_value: u64,
2326 data_buf_position: u64,
2327 ) -> Vec<Range<u64>> {
2328 ranges
2329 .iter()
2330 .map(|r| {
2331 let start_offset = (r.start * bytes_per_value) as usize;
2332 let end_offset = (r.end * bytes_per_value) as usize;
2333
2334 let start_slice = &buffer[start_offset..start_offset + bytes_per_value as usize];
2335 let start_val =
2336 ByteUnpacker::new(start_slice.iter().copied(), bytes_per_value as usize)
2337 .next()
2338 .unwrap();
2339
2340 let end_slice = &buffer[end_offset..end_offset + bytes_per_value as usize];
2341 let end_val =
2342 ByteUnpacker::new(end_slice.iter().copied(), bytes_per_value as usize)
2343 .next()
2344 .unwrap();
2345
2346 (data_buf_position + start_val)..(data_buf_position + end_val)
2347 })
2348 .collect()
2349 }
2350
2351 fn compute_rep_index_ranges(
2353 ranges: &[Range<u64>],
2354 rep_index: &FullZipRepIndexDetails,
2355 ) -> Vec<Range<u64>> {
2356 ranges
2357 .iter()
2358 .flat_map(|r| {
2359 let first_val_start =
2360 rep_index.buf_position + (r.start * rep_index.bytes_per_value);
2361 let first_val_end = first_val_start + rep_index.bytes_per_value;
2362 let last_val_start = rep_index.buf_position + (r.end * rep_index.bytes_per_value);
2363 let last_val_end = last_val_start + rep_index.bytes_per_value;
2364 [first_val_start..first_val_end, last_val_start..last_val_end]
2365 })
2366 .collect()
2367 }
2368
2369 fn schedule_ranges_rep(
2371 &self,
2372 ranges: &[Range<u64>],
2373 io: &Arc<dyn EncodingsIo>,
2374 rep_index: FullZipRepIndexDetails,
2375 ) -> Result<Vec<PageLoadTask>> {
2376 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
2377 let data_buf_position = self.data_buf_position;
2378 let priority = self.priority;
2379 let details = self.details.clone();
2380 let bits_per_offset = self.bits_per_offset;
2381
2382 if Self::covers_entire_page(ranges, self.rows_in_page) {
2383 let full_range = self.data_buf_position..(self.data_buf_position + self.data_buf_size);
2384 let page_data = io.submit_single(full_range.clone(), priority);
2385 let load_task = async move {
2386 let page_data = page_data.await?;
2387 let source = FullZipReadSource::PrefetchedPage {
2388 base_offset: full_range.start,
2389 data: LanceBuffer::from_bytes(page_data, 1),
2390 };
2391 let read_ranges = vec![full_range];
2392 let data = source.fetch(&read_ranges, priority).await?;
2393 Self::create_decoder(details, data, num_rows, bits_per_offset)
2394 }
2395 .boxed();
2396 let page_load_task = PageLoadTask {
2397 decoder_fut: load_task,
2398 num_rows,
2399 };
2400 return Ok(vec![page_load_task]);
2401 }
2402
2403 if let Some(cached_state) = &self.cached_state {
2404 let byte_ranges = Self::extract_byte_ranges_from_cached(
2405 &cached_state.rep_index_buffer,
2406 ranges,
2407 rep_index.bytes_per_value,
2408 data_buf_position,
2409 );
2410 let io_future = io.submit_request(byte_ranges, priority);
2411 let page_load_task =
2412 Self::create_page_load_task(io_future, num_rows, details, bits_per_offset);
2413 return Ok(vec![page_load_task]);
2414 }
2415
2416 let rep_ranges = Self::compute_rep_index_ranges(ranges, &rep_index);
2417 let rep_data = io.submit_request(rep_ranges, priority);
2418 let io_clone = io.clone();
2419 let load_task = async move {
2420 let rep_data = rep_data.await?;
2421 let rep_buffer = LanceBuffer::concat(
2422 &rep_data
2423 .into_iter()
2424 .map(|d| LanceBuffer::from_bytes(d, 1))
2425 .collect::<Vec<_>>(),
2426 );
2427 let byte_ranges = Self::extract_byte_ranges_from_pairs(
2428 rep_buffer,
2429 rep_index.bytes_per_value,
2430 data_buf_position,
2431 );
2432 let source = FullZipReadSource::Remote(io_clone);
2433 let data = source.fetch(&byte_ranges, priority).await?;
2434 Self::create_decoder(details, data, num_rows, bits_per_offset)
2435 }
2436 .boxed();
2437 let page_load_task = PageLoadTask {
2438 decoder_fut: load_task,
2439 num_rows,
2440 };
2441 Ok(vec![page_load_task])
2442 }
2443
2444 fn schedule_ranges_simple(
2448 &self,
2449 ranges: &[Range<u64>],
2450 io: &Arc<dyn EncodingsIo>,
2451 ) -> Result<Vec<PageLoadTask>> {
2452 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
2454
2455 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor else {
2456 unreachable!()
2457 };
2458
2459 let bits_per_value = decompressor.bits_per_value();
2461 if !bits_per_value.is_multiple_of(8) {
2462 return Err(Error::invalid_input_source(
2463 format!(
2464 "Full-zip fixed-width values must be byte aligned, got {} bits per value",
2465 bits_per_value
2466 )
2467 .into(),
2468 ));
2469 }
2470 let bytes_per_value = bits_per_value / 8;
2471 let bytes_per_cw = self.details.ctrl_word_parser.bytes_per_word();
2472 let total_bytes_per_value = bytes_per_value + bytes_per_cw as u64;
2473 let byte_ranges = ranges
2474 .iter()
2475 .map(|r| {
2476 debug_assert!(r.end <= self.rows_in_page);
2477 let start = self.data_buf_position + r.start * total_bytes_per_value;
2478 let end = self.data_buf_position + r.end * total_bytes_per_value;
2479 start..end
2480 })
2481 .collect::<Vec<_>>();
2482
2483 let io_future = io.submit_request(byte_ranges, self.priority);
2484 let page_load_task = Self::create_page_load_task(
2485 io_future,
2486 num_rows,
2487 self.details.clone(),
2488 self.bits_per_offset,
2489 );
2490 Ok(vec![page_load_task])
2491 }
2492}
2493
2494#[derive(Debug)]
2496struct FullZipCacheableState {
2497 rep_index_buffer: LanceBuffer,
2499}
2500
2501impl DeepSizeOf for FullZipCacheableState {
2502 fn deep_size_of_children(&self, _context: &mut Context) -> usize {
2503 self.rep_index_buffer.len()
2504 }
2505}
2506
2507impl CachedPageData for FullZipCacheableState {
2508 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
2509 self
2510 }
2511}
2512
2513impl StructuralPageScheduler for FullZipScheduler {
2514 fn initialize<'a>(
2515 &'a mut self,
2516 io: &Arc<dyn EncodingsIo>,
2517 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
2518 if self.enable_cache
2519 && let Some(rep_index) = self.rep_index
2520 {
2521 let total_size = (self.rows_in_page + 1) * rep_index.bytes_per_value;
2522 let rep_index_range = rep_index.buf_position..(rep_index.buf_position + total_size);
2523 let io_clone = io.clone();
2524 return async move {
2525 let rep_index_data = io_clone.submit_request(vec![rep_index_range], 0).await?;
2526 let state = Arc::new(FullZipCacheableState {
2527 rep_index_buffer: LanceBuffer::from_bytes(rep_index_data[0].clone(), 1),
2528 });
2529 self.cached_state = Some(state.clone());
2530 Ok(state as Arc<dyn CachedPageData>)
2531 }
2532 .boxed();
2533 }
2534 std::future::ready(Ok(Arc::new(NoCachedPageData) as Arc<dyn CachedPageData>)).boxed()
2535 }
2536
2537 fn load(&mut self, cache: &Arc<dyn CachedPageData>) {
2541 if let Ok(cached_state) = cache
2543 .clone()
2544 .as_arc_any()
2545 .downcast::<FullZipCacheableState>()
2546 {
2547 self.cached_state = Some(cached_state);
2549 }
2550 }
2551
2552 fn schedule_ranges(
2553 &self,
2554 ranges: &[Range<u64>],
2555 io: &Arc<dyn EncodingsIo>,
2556 ) -> Result<Vec<PageLoadTask>> {
2557 if let Some(rep_index) = self.rep_index {
2558 self.schedule_ranges_rep(ranges, io, rep_index)
2559 } else {
2560 self.schedule_ranges_simple(ranges, io)
2561 }
2562 }
2563}
2564
2565#[derive(Debug)]
2573struct FixedFullZipDecoder {
2574 details: Arc<FullZipDecodeDetails>,
2575 data: VecDeque<LanceBuffer>,
2576 offset_in_current: usize,
2577 bytes_per_value: usize,
2578 total_bytes_per_value: usize,
2579 num_rows: u64,
2580}
2581
2582impl FixedFullZipDecoder {
2583 fn slice_next_task(&mut self, num_rows: u64) -> FullZipDecodeTaskItem {
2584 debug_assert!(num_rows > 0);
2585 let cur_buf = self.data.front_mut().unwrap();
2586 let start = self.offset_in_current;
2587 if self.details.ctrl_word_parser.has_rep() {
2588 let mut rows_started = 0;
2591 let mut num_items = 0;
2594 while self.offset_in_current < cur_buf.len() {
2595 let control = self.details.ctrl_word_parser.parse_desc(
2596 &cur_buf[self.offset_in_current..],
2597 self.details.max_rep,
2598 self.details.max_visible_def,
2599 );
2600 if control.is_new_row {
2601 if rows_started == num_rows {
2602 break;
2603 }
2604 rows_started += 1;
2605 }
2606 num_items += 1;
2607 if control.is_visible {
2608 self.offset_in_current += self.total_bytes_per_value;
2609 } else {
2610 self.offset_in_current += self.details.ctrl_word_parser.bytes_per_word();
2611 }
2612 }
2613
2614 let task_slice = cur_buf.slice_with_length(start, self.offset_in_current - start);
2615 if self.offset_in_current == cur_buf.len() {
2616 self.data.pop_front();
2617 self.offset_in_current = 0;
2618 }
2619
2620 FullZipDecodeTaskItem {
2621 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
2622 data: task_slice,
2623 bits_per_value: self.bytes_per_value as u64 * 8,
2624 num_values: num_items,
2625 block_info: BlockInfo::new(),
2626 }),
2627 rows_in_buf: rows_started,
2628 }
2629 } else {
2630 let cur_buf = self.data.front_mut().unwrap();
2633 let bytes_avail = cur_buf.len() - self.offset_in_current;
2634 let offset_in_cur = self.offset_in_current;
2635
2636 let bytes_needed = num_rows as usize * self.total_bytes_per_value;
2637 let mut rows_taken = num_rows;
2638 let task_slice = if bytes_needed >= bytes_avail {
2639 self.offset_in_current = 0;
2640 rows_taken = bytes_avail as u64 / self.total_bytes_per_value as u64;
2641 self.data
2642 .pop_front()
2643 .unwrap()
2644 .slice_with_length(offset_in_cur, bytes_avail)
2645 } else {
2646 self.offset_in_current += bytes_needed;
2647 cur_buf.slice_with_length(offset_in_cur, bytes_needed)
2648 };
2649 FullZipDecodeTaskItem {
2650 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
2651 data: task_slice,
2652 bits_per_value: self.bytes_per_value as u64 * 8,
2653 num_values: rows_taken,
2654 block_info: BlockInfo::new(),
2655 }),
2656 rows_in_buf: rows_taken,
2657 }
2658 }
2659 }
2660}
2661
2662impl StructuralPageDecoder for FixedFullZipDecoder {
2663 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
2664 let mut task_data = Vec::with_capacity(self.data.len());
2665 let mut remaining = num_rows;
2666 while remaining > 0 {
2667 let task_item = self.slice_next_task(remaining);
2668 remaining -= task_item.rows_in_buf;
2669 task_data.push(task_item);
2670 }
2671 Ok(Box::new(FixedFullZipDecodeTask {
2672 details: self.details.clone(),
2673 data: task_data,
2674 bytes_per_value: self.bytes_per_value,
2675 num_rows: num_rows as usize,
2676 }))
2677 }
2678
2679 fn num_rows(&self) -> u64 {
2680 self.num_rows
2681 }
2682}
2683
2684#[derive(Debug)]
2689struct VariableFullZipDecoder {
2690 details: Arc<FullZipDecodeDetails>,
2691 decompressor: Arc<dyn VariablePerValueDecompressor>,
2692 data: LanceBuffer,
2693 offsets: LanceBuffer,
2694 rep: ScalarBuffer<u16>,
2695 def: ScalarBuffer<u16>,
2696 repdef_starts: Vec<usize>,
2697 data_starts: Vec<usize>,
2698 offset_starts: Vec<usize>,
2699 visible_item_counts: Vec<u64>,
2700 bits_per_offset: u8,
2701 current_idx: usize,
2702 num_rows: u64,
2703}
2704
2705fn corrupt_file_named(name: &str, message: impl Into<String>) -> Error {
2706 Error::corrupt_file(name.into(), message)
2707}
2708
2709impl VariableFullZipDecoder {
2710 fn new(
2711 details: Arc<FullZipDecodeDetails>,
2712 data: VecDeque<LanceBuffer>,
2713 num_rows: u64,
2714 in_bits_per_length: u8,
2715 out_bits_per_offset: u8,
2716 ) -> Result<Self> {
2717 let decompressor = match details.value_decompressor {
2718 PerValueDecompressor::Variable(ref d) => d.clone(),
2719 _ => unreachable!(),
2720 };
2721
2722 assert_eq!(in_bits_per_length % 8, 0);
2723 assert!(out_bits_per_offset == 32 || out_bits_per_offset == 64);
2724
2725 let mut decoder = Self {
2726 details,
2727 decompressor,
2728 data: LanceBuffer::empty(),
2729 offsets: LanceBuffer::empty(),
2730 rep: LanceBuffer::empty().borrow_to_typed_slice(),
2731 def: LanceBuffer::empty().borrow_to_typed_slice(),
2732 bits_per_offset: out_bits_per_offset,
2733 repdef_starts: Vec::with_capacity(num_rows as usize + 1),
2734 data_starts: Vec::with_capacity(num_rows as usize + 1),
2735 offset_starts: Vec::with_capacity(num_rows as usize + 1),
2736 visible_item_counts: Vec::with_capacity(num_rows as usize + 1),
2737 current_idx: 0,
2738 num_rows,
2739 };
2740
2741 decoder.unzip(data, in_bits_per_length, out_bits_per_offset, num_rows)?;
2762
2763 Ok(decoder)
2764 }
2765
2766 fn slice_batch_data_and_rebase_offsets_typed<T>(
2767 data: &LanceBuffer,
2768 offsets: &LanceBuffer,
2769 ) -> Result<(LanceBuffer, LanceBuffer)>
2770 where
2771 T: arrow_buffer::ArrowNativeType
2772 + Copy
2773 + PartialOrd
2774 + std::ops::Sub<Output = T>
2775 + std::fmt::Display
2776 + TryInto<usize>,
2777 {
2778 let offsets_slice = offsets.borrow_to_typed_slice::<T>();
2779 let offsets_slice = offsets_slice.as_ref();
2780 if offsets_slice.is_empty() {
2781 return Err(Error::internal(
2782 "Variable offsets cannot be empty".to_string(),
2783 ));
2784 }
2785
2786 let base = offsets_slice[0];
2787 let end = *offsets_slice.last().unwrap();
2788 if end < base {
2789 return Err(Error::internal(format!(
2790 "Invalid variable offsets: end ({end}) is less than base ({base})"
2791 )));
2792 }
2793
2794 let data_start = base.try_into().map_err(|_| {
2795 Error::internal(format!("Variable offset ({base}) does not fit into usize"))
2796 })?;
2797 let data_end = end.try_into().map_err(|_| {
2798 Error::internal(format!("Variable offset ({end}) does not fit into usize"))
2799 })?;
2800 if data_end > data.len() {
2801 return Err(Error::internal(format!(
2802 "Invalid variable offsets: end ({data_end}) exceeds data len ({})",
2803 data.len()
2804 )));
2805 }
2806
2807 let mut rebased_offsets = Vec::with_capacity(offsets_slice.len());
2808 for &offset in offsets_slice {
2809 if offset < base {
2810 return Err(Error::internal(format!(
2811 "Invalid variable offsets: offset ({offset}) is less than base ({base})"
2812 )));
2813 }
2814 rebased_offsets.push(offset - base);
2815 }
2816
2817 let sliced_data = data.slice_with_length(data_start, data_end - data_start);
2818 let sliced_data = LanceBuffer::copy_slice(&sliced_data);
2820 let rebased_offsets = LanceBuffer::reinterpret_vec(rebased_offsets);
2821 Ok((sliced_data, rebased_offsets))
2822 }
2823
2824 fn slice_batch_data_and_rebase_offsets(
2825 data: &LanceBuffer,
2826 offsets: &LanceBuffer,
2827 bits_per_offset: u8,
2828 ) -> Result<(LanceBuffer, LanceBuffer)> {
2829 match bits_per_offset {
2830 32 => Self::slice_batch_data_and_rebase_offsets_typed::<u32>(data, offsets),
2831 64 => Self::slice_batch_data_and_rebase_offsets_typed::<u64>(data, offsets),
2832 _ => Err(Error::internal(format!(
2833 "Unsupported bits_per_offset={bits_per_offset}"
2834 ))),
2835 }
2836 }
2837
2838 fn parse_length(data: &[u8], bits_per_offset: u8) -> Result<u64> {
2845 let width = bits_per_offset as usize / 8;
2846 if data.len() < width {
2847 return Err(corrupt_file_named(
2848 "variable_full_zip",
2849 format!(
2850 "truncated length prefix: {} byte(s) remain in the page buffer but a \
2851 {}-bit length prefix requires {}",
2852 data.len(),
2853 bits_per_offset,
2854 width
2855 ),
2856 ));
2857 }
2858 Ok(match bits_per_offset {
2859 8 => data[0] as u64,
2860 16 => u16::from_le_bytes(data[..2].try_into().unwrap()) as u64,
2861 32 => u32::from_le_bytes(data[..4].try_into().unwrap()) as u64,
2862 64 => u64::from_le_bytes(data[..8].try_into().unwrap()),
2863 _ => unreachable!(),
2864 })
2865 }
2866
2867 fn unzip(
2868 &mut self,
2869 data: VecDeque<LanceBuffer>,
2870 in_bits_per_length: u8,
2871 out_bits_per_offset: u8,
2872 num_rows: u64,
2873 ) -> Result<()> {
2874 let mut rep = Vec::with_capacity(num_rows as usize);
2876 let mut def = Vec::with_capacity(num_rows as usize);
2877 let bytes_cw = self.details.ctrl_word_parser.bytes_per_word() * num_rows as usize;
2878
2879 let bytes_per_offset = out_bits_per_offset as usize / 8;
2882 let bytes_offsets = bytes_per_offset * (num_rows as usize + 1);
2883 let mut offsets_data = Vec::with_capacity(bytes_offsets);
2884
2885 let bytes_per_length = in_bits_per_length as usize / 8;
2886 let bytes_lengths = bytes_per_length * num_rows as usize;
2887
2888 let bytes_data = data.iter().map(|d| d.len()).sum::<usize>();
2889 let mut unzipped_data =
2892 Vec::with_capacity((bytes_data - bytes_cw).saturating_sub(bytes_lengths));
2893
2894 let mut current_offset = 0_u64;
2895 let mut visible_item_count = 0_u64;
2896 for databuf in data.into_iter() {
2897 let mut databuf = databuf.as_ref();
2898 while !databuf.is_empty() {
2899 let data_start = unzipped_data.len();
2900 let offset_start = offsets_data.len();
2901 let repdef_start = rep.len().max(def.len());
2904 let ctrl_desc = self.details.ctrl_word_parser.parse_desc(
2906 databuf,
2907 self.details.max_rep,
2908 self.details.max_visible_def,
2909 );
2910 self.details
2911 .ctrl_word_parser
2912 .parse(databuf, &mut rep, &mut def);
2913 databuf = &databuf[self.details.ctrl_word_parser.bytes_per_word()..];
2914
2915 if ctrl_desc.is_new_row {
2916 self.repdef_starts.push(repdef_start);
2917 self.data_starts.push(data_start);
2918 self.offset_starts.push(offset_start);
2919 self.visible_item_counts.push(visible_item_count);
2920 }
2921 if ctrl_desc.is_visible {
2922 visible_item_count += 1;
2923 if ctrl_desc.is_valid_item {
2924 let length = Self::parse_length(databuf, in_bits_per_length)?;
2925 match out_bits_per_offset {
2926 32 => offsets_data
2927 .extend_from_slice(&(current_offset as u32).to_le_bytes()),
2928 64 => offsets_data.extend_from_slice(¤t_offset.to_le_bytes()),
2929 _ => unreachable!(),
2930 };
2931 databuf = &databuf[bytes_per_offset..];
2932 unzipped_data.extend_from_slice(&databuf[..length as usize]);
2933 databuf = &databuf[length as usize..];
2934 current_offset += length;
2935 } else {
2936 match out_bits_per_offset {
2938 32 => offsets_data
2939 .extend_from_slice(&(current_offset as u32).to_le_bytes()),
2940 64 => offsets_data.extend_from_slice(¤t_offset.to_le_bytes()),
2941 _ => unreachable!(),
2942 }
2943 }
2944 }
2945 }
2946 }
2947 self.repdef_starts.push(rep.len().max(def.len()));
2948 self.data_starts.push(unzipped_data.len());
2949 self.offset_starts.push(offsets_data.len());
2950 self.visible_item_counts.push(visible_item_count);
2951 match out_bits_per_offset {
2952 32 => offsets_data.extend_from_slice(&(current_offset as u32).to_le_bytes()),
2953 64 => offsets_data.extend_from_slice(¤t_offset.to_le_bytes()),
2954 _ => unreachable!(),
2955 };
2956 self.rep = ScalarBuffer::from(rep);
2957 self.def = ScalarBuffer::from(def);
2958 self.data = LanceBuffer::from(unzipped_data);
2959 self.offsets = LanceBuffer::from(offsets_data);
2960 Ok(())
2961 }
2962}
2963
2964impl StructuralPageDecoder for VariableFullZipDecoder {
2965 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
2966 let start = self.current_idx;
2967 let end = start + num_rows as usize;
2968
2969 let offset_start = self.offset_starts[start];
2970 let offset_end = self.offset_starts[end] + (self.bits_per_offset as usize / 8);
2971 let offsets = self
2972 .offsets
2973 .slice_with_length(offset_start, offset_end - offset_start);
2974 let (data, offsets) =
2976 Self::slice_batch_data_and_rebase_offsets(&self.data, &offsets, self.bits_per_offset)?;
2977
2978 let repdef_start = self.repdef_starts[start];
2979 let repdef_end = self.repdef_starts[end];
2980 let rep = if self.rep.is_empty() {
2981 self.rep.clone()
2982 } else {
2983 self.rep.slice(repdef_start, repdef_end - repdef_start)
2984 };
2985 let def = if self.def.is_empty() {
2986 self.def.clone()
2987 } else {
2988 self.def.slice(repdef_start, repdef_end - repdef_start)
2989 };
2990
2991 let visible_item_counts_start = self.visible_item_counts[start];
2992 let visible_item_counts_end = self.visible_item_counts[end];
2993 let num_visible_items = visible_item_counts_end - visible_item_counts_start;
2994
2995 self.current_idx += num_rows as usize;
2996
2997 Ok(Box::new(VariableFullZipDecodeTask {
2998 details: self.details.clone(),
2999 decompressor: self.decompressor.clone(),
3000 data,
3001 offsets,
3002 bits_per_offset: self.bits_per_offset,
3003 num_visible_items,
3004 rep,
3005 def,
3006 }))
3007 }
3008
3009 fn num_rows(&self) -> u64 {
3010 self.num_rows
3011 }
3012}
3013
3014#[derive(Debug)]
3015struct VariableFullZipDecodeTask {
3016 details: Arc<FullZipDecodeDetails>,
3017 decompressor: Arc<dyn VariablePerValueDecompressor>,
3018 data: LanceBuffer,
3019 offsets: LanceBuffer,
3020 bits_per_offset: u8,
3021 num_visible_items: u64,
3022 rep: ScalarBuffer<u16>,
3023 def: ScalarBuffer<u16>,
3024}
3025
3026impl DecodePageTask for VariableFullZipDecodeTask {
3027 fn decode(self: Box<Self>) -> Result<DecodedPage> {
3028 let block = VariableWidthBlock {
3029 data: self.data,
3030 offsets: self.offsets,
3031 bits_per_offset: self.bits_per_offset,
3032 num_values: self.num_visible_items,
3033 block_info: BlockInfo::new(),
3034 };
3035 let decomopressed = self.decompressor.decompress(block)?;
3036 let rep = if self.rep.is_empty() {
3037 None
3038 } else {
3039 Some(self.rep.to_vec())
3040 };
3041 let def = if self.def.is_empty() {
3042 None
3043 } else {
3044 Some(self.def.to_vec())
3045 };
3046 let unraveler = RepDefUnraveler::new(
3047 rep,
3048 def,
3049 self.details.def_meaning.clone(),
3050 self.num_visible_items,
3051 );
3052 Ok(DecodedPage {
3053 data: decomopressed,
3054 repdef: unraveler,
3055 })
3056 }
3057}
3058
3059#[derive(Debug)]
3060struct FullZipDecodeTaskItem {
3061 data: PerValueDataBlock,
3062 rows_in_buf: u64,
3063}
3064
3065#[derive(Debug)]
3068struct FixedFullZipDecodeTask {
3069 details: Arc<FullZipDecodeDetails>,
3070 data: Vec<FullZipDecodeTaskItem>,
3071 num_rows: usize,
3072 bytes_per_value: usize,
3073}
3074
3075impl DecodePageTask for FixedFullZipDecodeTask {
3076 fn decode(self: Box<Self>) -> Result<DecodedPage> {
3077 let estimated_size_bytes = self
3079 .data
3080 .iter()
3081 .map(|task_item| task_item.data.data_size() as usize)
3082 .sum::<usize>()
3083 * 2;
3084 let mut data_builder =
3085 DataBlockBuilder::with_capacity_estimate(estimated_size_bytes as u64);
3086
3087 if self.details.ctrl_word_parser.bytes_per_word() == 0 {
3088 for task_item in self.data.into_iter() {
3092 let PerValueDataBlock::Fixed(fixed_data) = task_item.data else {
3093 unreachable!()
3094 };
3095 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor
3096 else {
3097 unreachable!()
3098 };
3099 debug_assert_eq!(fixed_data.num_values, task_item.rows_in_buf);
3100 let decompressed = decompressor.decompress(fixed_data, task_item.rows_in_buf)?;
3101 data_builder.append(&decompressed, 0..task_item.rows_in_buf);
3102 }
3103
3104 let unraveler = RepDefUnraveler::new(
3105 None,
3106 None,
3107 self.details.def_meaning.clone(),
3108 self.num_rows as u64,
3109 );
3110
3111 Ok(DecodedPage {
3112 data: data_builder.finish(),
3113 repdef: unraveler,
3114 })
3115 } else {
3116 let mut rep = Vec::with_capacity(self.num_rows);
3118 let mut def = Vec::with_capacity(self.num_rows);
3119
3120 for task_item in self.data.into_iter() {
3121 let PerValueDataBlock::Fixed(fixed_data) = task_item.data else {
3122 unreachable!()
3123 };
3124 let mut buf_slice = fixed_data.data.as_ref();
3125 let num_values = fixed_data.num_values as usize;
3126 let mut values = Vec::with_capacity(
3129 fixed_data.data.len()
3130 - (self.details.ctrl_word_parser.bytes_per_word() * num_values),
3131 );
3132 let mut visible_items = 0;
3133 for _ in 0..num_values {
3134 self.details
3136 .ctrl_word_parser
3137 .parse(buf_slice, &mut rep, &mut def);
3138 buf_slice = &buf_slice[self.details.ctrl_word_parser.bytes_per_word()..];
3139
3140 let is_visible = def
3141 .last()
3142 .map(|d| *d <= self.details.max_visible_def)
3143 .unwrap_or(true);
3144 if is_visible {
3145 values.extend_from_slice(buf_slice[..self.bytes_per_value].as_ref());
3147 buf_slice = &buf_slice[self.bytes_per_value..];
3148 visible_items += 1;
3149 }
3150 }
3151
3152 let values_buf = LanceBuffer::from(values);
3154 let fixed_data = FixedWidthDataBlock {
3155 bits_per_value: self.bytes_per_value as u64 * 8,
3156 block_info: BlockInfo::new(),
3157 data: values_buf,
3158 num_values: visible_items,
3159 };
3160 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor
3161 else {
3162 unreachable!()
3163 };
3164 let decompressed = decompressor.decompress(fixed_data, visible_items)?;
3165 data_builder.append(&decompressed, 0..visible_items);
3166 }
3167
3168 let repetition = if rep.is_empty() { None } else { Some(rep) };
3169 let definition = if def.is_empty() { None } else { Some(def) };
3170
3171 let unraveler = RepDefUnraveler::new(
3172 repetition,
3173 definition,
3174 self.details.def_meaning.clone(),
3175 self.num_rows as u64,
3176 );
3177 let data = data_builder.finish();
3178
3179 Ok(DecodedPage {
3180 data,
3181 repdef: unraveler,
3182 })
3183 }
3184 }
3185}
3186
3187#[derive(Debug)]
3188struct StructuralPrimitiveFieldSchedulingJob<'a> {
3189 scheduler: &'a StructuralPrimitiveFieldScheduler,
3190 ranges: Vec<Range<u64>>,
3191 page_idx: usize,
3192 range_idx: usize,
3193 global_row_offset: u64,
3194}
3195
3196impl<'a> StructuralPrimitiveFieldSchedulingJob<'a> {
3197 pub fn new(scheduler: &'a StructuralPrimitiveFieldScheduler, ranges: Vec<Range<u64>>) -> Self {
3198 Self {
3199 scheduler,
3200 ranges,
3201 page_idx: 0,
3202 range_idx: 0,
3203 global_row_offset: 0,
3204 }
3205 }
3206}
3207
3208impl StructuralSchedulingJob for StructuralPrimitiveFieldSchedulingJob<'_> {
3209 fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>> {
3210 if self.range_idx >= self.ranges.len() {
3211 return Ok(Vec::new());
3212 }
3213 let mut range = self.ranges[self.range_idx].clone();
3215 let priority = range.start;
3216
3217 let mut cur_page = &self.scheduler.page_schedulers[self.page_idx];
3218 trace!(
3219 "Current range is {:?} and current page has {} rows",
3220 range, cur_page.num_rows
3221 );
3222 while cur_page.num_rows + self.global_row_offset <= range.start {
3224 self.global_row_offset += cur_page.num_rows;
3225 self.page_idx += 1;
3226 trace!("Skipping entire page of {} rows", cur_page.num_rows);
3227 cur_page = &self.scheduler.page_schedulers[self.page_idx];
3228 }
3229
3230 let mut ranges_in_page = Vec::new();
3234 while cur_page.num_rows + self.global_row_offset > range.start {
3235 range.start = range.start.max(self.global_row_offset);
3236 let start_in_page = range.start - self.global_row_offset;
3237 let end_in_page = start_in_page + (range.end - range.start);
3238 let end_in_page = end_in_page.min(cur_page.num_rows);
3239 let last_in_range = (end_in_page + self.global_row_offset) >= range.end;
3240
3241 ranges_in_page.push(start_in_page..end_in_page);
3242 if last_in_range {
3243 self.range_idx += 1;
3244 if self.range_idx == self.ranges.len() {
3245 break;
3246 }
3247 range = self.ranges[self.range_idx].clone();
3248 } else {
3249 break;
3250 }
3251 }
3252
3253 trace!(
3254 "Scheduling {} rows across {} ranges from page with {} rows (priority={}, column_index={}, page_index={})",
3255 ranges_in_page.iter().map(|r| r.end - r.start).sum::<u64>(),
3256 ranges_in_page.len(),
3257 cur_page.num_rows,
3258 priority,
3259 self.scheduler.column_index,
3260 cur_page.page_index,
3261 );
3262
3263 self.global_row_offset += cur_page.num_rows;
3264 self.page_idx += 1;
3265
3266 let page_decoders = cur_page
3267 .scheduler
3268 .schedule_ranges(&ranges_in_page, context.io())?;
3269
3270 let cur_path = context.current_path();
3271 page_decoders
3272 .into_iter()
3273 .map(|page_load_task| {
3274 let cur_path = cur_path.clone();
3275 let page_decoder = page_load_task.decoder_fut;
3276 let unloaded_page = async move {
3277 let page_decoder = page_decoder.await?;
3278 Ok(LoadedPageShard {
3279 decoder: page_decoder,
3280 path: cur_path,
3281 })
3282 }
3283 .boxed();
3284 Ok(ScheduledScanLine {
3285 decoders: vec![MessageType::UnloadedPage(UnloadedPageShard(unloaded_page))],
3286 rows_scheduled: page_load_task.num_rows,
3287 })
3288 })
3289 .collect::<Result<Vec<_>>>()
3290 }
3291}
3292
3293#[derive(Debug)]
3294struct PageInfoAndScheduler {
3295 page_index: usize,
3296 num_rows: u64,
3297 scheduler: Box<dyn StructuralPageScheduler>,
3298}
3299
3300#[derive(Debug)]
3305pub struct StructuralPrimitiveFieldScheduler {
3306 page_schedulers: Vec<PageInfoAndScheduler>,
3307 column_index: u32,
3308 view_tag: String,
3314}
3315
3316impl StructuralPrimitiveFieldScheduler {
3317 pub fn try_new(
3318 column_info: &ColumnInfo,
3319 decompressors: &dyn DecompressionStrategy,
3320 cache_repetition_index: bool,
3321 target_field: &Field,
3322 ) -> Result<Self> {
3323 let page_schedulers = column_info
3324 .page_infos
3325 .iter()
3326 .enumerate()
3327 .map(|(page_index, page_info)| {
3328 Self::page_info_to_scheduler(
3329 page_info,
3330 page_index,
3331 decompressors,
3332 cache_repetition_index,
3333 target_field,
3334 )
3335 })
3336 .collect::<Result<Vec<_>>>()?;
3337 Ok(Self {
3338 page_schedulers,
3339 column_index: column_info.index,
3340 view_tag: format!("{:?}", target_field.data_type()),
3341 })
3342 }
3343
3344 fn page_layout_to_scheduler(
3345 page_info: &PageInfo,
3346 page_layout: &PageLayout,
3347 decompressors: &dyn DecompressionStrategy,
3348 cache_repetition_index: bool,
3349 target_field: &Field,
3350 ) -> Result<Box<dyn StructuralPageScheduler>> {
3351 use pb21::page_layout::Layout;
3352 Ok(match page_layout.layout.as_ref().expect_ok()? {
3353 Layout::MiniBlockLayout(mini_block) => Box::new(MiniBlockScheduler::try_new(
3354 &page_info.buffer_offsets_and_sizes,
3355 page_info.priority,
3356 mini_block.num_items,
3357 mini_block,
3358 decompressors,
3359 )?),
3360 Layout::FullZipLayout(full_zip) => {
3361 let mut scheduler = FullZipScheduler::try_new(
3362 &page_info.buffer_offsets_and_sizes,
3363 page_info.priority,
3364 page_info.num_rows,
3365 full_zip,
3366 decompressors,
3367 )?;
3368 scheduler.enable_cache = cache_repetition_index;
3369 Box::new(scheduler)
3370 }
3371 Layout::ConstantLayout(constant_layout) => {
3372 let def_meaning = constant_layout
3373 .layers
3374 .iter()
3375 .map(|l| ProtobufUtils21::repdef_layer_to_def_interp(*l))
3376 .collect::<Vec<_>>();
3377 let has_scalar_value = constant_layout.inline_value.is_some()
3378 || page_info.buffer_offsets_and_sizes.len() == 1
3379 || page_info.buffer_offsets_and_sizes.len() == 3;
3380 if has_scalar_value {
3381 Box::new(constant::ConstantPageScheduler::try_new(
3382 page_info.buffer_offsets_and_sizes.clone(),
3383 constant_layout.inline_value.clone(),
3384 target_field.data_type(),
3385 def_meaning.into(),
3386 )?) as Box<dyn StructuralPageScheduler>
3387 } else if def_meaning.len() == 1
3388 && def_meaning[0] == DefinitionInterpretation::NullableItem
3389 {
3390 Box::new(SimpleAllNullScheduler::default()) as Box<dyn StructuralPageScheduler>
3391 } else {
3392 let rep_decompressor = constant_layout
3393 .rep_compression
3394 .as_ref()
3395 .map(|encoding| decompressors.create_block_decompressor(encoding))
3396 .transpose()?
3397 .map(Arc::from);
3398
3399 let def_decompressor = constant_layout
3400 .def_compression
3401 .as_ref()
3402 .map(|encoding| decompressors.create_block_decompressor(encoding))
3403 .transpose()?
3404 .map(Arc::from);
3405
3406 Box::new(ComplexAllNullScheduler::new(
3407 page_info.buffer_offsets_and_sizes.clone(),
3408 def_meaning.into(),
3409 rep_decompressor,
3410 def_decompressor,
3411 constant_layout.num_rep_values,
3412 constant_layout.num_def_values,
3413 )) as Box<dyn StructuralPageScheduler>
3414 }
3415 }
3416 Layout::BlobLayout(blob) => {
3417 let inner_scheduler = Self::page_layout_to_scheduler(
3418 page_info,
3419 blob.inner_layout.as_ref().expect_ok()?.as_ref(),
3420 decompressors,
3421 cache_repetition_index,
3422 target_field,
3423 )?;
3424 let def_meaning = blob
3425 .layers
3426 .iter()
3427 .map(|l| ProtobufUtils21::repdef_layer_to_def_interp(*l))
3428 .collect::<Vec<_>>();
3429 if matches!(target_field.data_type(), DataType::Struct(_)) {
3430 Box::new(BlobDescriptionPageScheduler::new(
3432 inner_scheduler,
3433 def_meaning.into(),
3434 ))
3435 } else {
3436 Box::new(BlobPageScheduler::new(
3438 inner_scheduler,
3439 page_info.priority,
3440 page_info.num_rows,
3441 def_meaning.into(),
3442 ))
3443 }
3444 }
3445 })
3446 }
3447
3448 fn page_info_to_scheduler(
3449 page_info: &PageInfo,
3450 page_index: usize,
3451 decompressors: &dyn DecompressionStrategy,
3452 cache_repetition_index: bool,
3453 target_field: &Field,
3454 ) -> Result<PageInfoAndScheduler> {
3455 let page_layout = page_info.encoding.as_structural();
3456 let scheduler = Self::page_layout_to_scheduler(
3457 page_info,
3458 page_layout,
3459 decompressors,
3460 cache_repetition_index,
3461 target_field,
3462 )?;
3463 Ok(PageInfoAndScheduler {
3464 page_index,
3465 num_rows: page_info.num_rows,
3466 scheduler,
3467 })
3468 }
3469}
3470
3471pub trait CachedPageData: Any + Send + Sync + DeepSizeOf + 'static {
3472 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static>;
3473}
3474
3475pub struct NoCachedPageData;
3476
3477impl DeepSizeOf for NoCachedPageData {
3478 fn deep_size_of_children(&self, _ctx: &mut Context) -> usize {
3479 0
3480 }
3481}
3482impl CachedPageData for NoCachedPageData {
3483 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
3484 self
3485 }
3486}
3487
3488pub struct CachedFieldData {
3489 pages: Vec<Arc<dyn CachedPageData>>,
3490}
3491
3492impl DeepSizeOf for CachedFieldData {
3493 fn deep_size_of_children(&self, ctx: &mut Context) -> usize {
3494 self.pages.deep_size_of_children(ctx)
3495 }
3496}
3497
3498#[derive(Debug, Clone)]
3508pub struct FieldDataCacheKey {
3509 pub column_index: u32,
3510 pub view_tag: String,
3511}
3512
3513impl CacheKey for FieldDataCacheKey {
3514 type ValueType = CachedFieldData;
3515
3516 fn key(&self) -> std::borrow::Cow<'_, str> {
3517 format!("{}:{}", self.column_index, self.view_tag).into()
3518 }
3519
3520 fn type_name() -> &'static str {
3521 "FieldData"
3522 }
3523}
3524
3525impl StructuralFieldScheduler for StructuralPrimitiveFieldScheduler {
3526 fn initialize<'a>(
3527 &'a mut self,
3528 _filter: &'a FilterExpression,
3529 context: &'a SchedulerContext,
3530 ) -> BoxFuture<'a, Result<()>> {
3531 let cache_key = FieldDataCacheKey {
3532 column_index: self.column_index,
3533 view_tag: self.view_tag.clone(),
3534 };
3535 let cache = context.cache().clone();
3536
3537 async move {
3538 if let Some(cached_data) = cache.get_with_key(&cache_key).await {
3539 self.page_schedulers
3540 .iter_mut()
3541 .zip(cached_data.pages.iter())
3542 .for_each(|(page_scheduler, cached_data)| {
3543 page_scheduler.scheduler.load(cached_data);
3544 });
3545 return Ok(());
3546 }
3547
3548 let page_data = self
3549 .page_schedulers
3550 .iter_mut()
3551 .map(|s| s.scheduler.initialize(context.io()))
3552 .collect::<FuturesOrdered<_>>();
3553
3554 let page_data = page_data.try_collect::<Vec<_>>().await?;
3555 let cached_data = Arc::new(CachedFieldData { pages: page_data });
3556 cache.insert_with_key(&cache_key, cached_data).await;
3557 Ok(())
3558 }
3559 .boxed()
3560 }
3561
3562 fn schedule_ranges<'a>(
3563 &'a self,
3564 ranges: &[Range<u64>],
3565 _filter: &FilterExpression,
3566 ) -> Result<Box<dyn StructuralSchedulingJob + 'a>> {
3567 let ranges = ranges.to_vec();
3568 Ok(Box::new(StructuralPrimitiveFieldSchedulingJob::new(
3569 self, ranges,
3570 )))
3571 }
3572}
3573
3574#[derive(Debug)]
3577pub struct StructuralCompositeDecodeArrayTask {
3578 tasks: Vec<Box<dyn DecodePageTask>>,
3579 should_validate: bool,
3580 data_type: DataType,
3581}
3582
3583impl StructuralCompositeDecodeArrayTask {
3584 fn restore_validity(
3585 array: Arc<dyn Array>,
3586 unraveler: &mut CompositeRepDefUnraveler,
3587 ) -> Arc<dyn Array> {
3588 let validity = unraveler.unravel_validity(array.len());
3589 let Some(validity) = validity else {
3590 return array;
3591 };
3592 if array.data_type() == &DataType::Null {
3593 return array;
3595 }
3596 assert_eq!(validity.len(), array.len());
3597 make_array(unsafe {
3600 array
3601 .to_data()
3602 .into_builder()
3603 .nulls(Some(validity))
3604 .build_unchecked()
3605 })
3606 }
3607}
3608
3609impl StructuralDecodeArrayTask for StructuralCompositeDecodeArrayTask {
3610 fn decode(self: Box<Self>) -> Result<DecodedArray> {
3611 let mut arrays = Vec::with_capacity(self.tasks.len());
3612 let mut unravelers = Vec::with_capacity(self.tasks.len());
3613 let mut data_size = 0u64;
3614 for task in self.tasks {
3615 let decoded = task.decode()?;
3616 data_size += decoded.data.data_size();
3617 unravelers.push(decoded.repdef);
3618
3619 let array = make_array(
3620 decoded
3621 .data
3622 .into_arrow(self.data_type.clone(), self.should_validate)?,
3623 );
3624
3625 arrays.push(array);
3626 }
3627 let array_refs = arrays.iter().map(|arr| arr.as_ref()).collect::<Vec<_>>();
3628 let array = arrow_select::concat::concat(&array_refs)?;
3629 let mut repdef = CompositeRepDefUnraveler::new(unravelers);
3630
3631 let array = Self::restore_validity(array, &mut repdef);
3632
3633 Ok(DecodedArray {
3634 array,
3635 repdef,
3636 data_size,
3637 })
3638 }
3639}
3640
3641#[derive(Debug)]
3642pub struct StructuralPrimitiveFieldDecoder {
3643 field: Arc<ArrowField>,
3644 page_decoders: VecDeque<Box<dyn StructuralPageDecoder>>,
3645 should_validate: bool,
3646 rows_drained_in_current: u64,
3647}
3648
3649impl StructuralPrimitiveFieldDecoder {
3650 pub fn new(field: &Arc<ArrowField>, should_validate: bool) -> Self {
3651 Self {
3652 field: field.clone(),
3653 page_decoders: VecDeque::new(),
3654 should_validate,
3655 rows_drained_in_current: 0,
3656 }
3657 }
3658}
3659
3660impl StructuralFieldDecoder for StructuralPrimitiveFieldDecoder {
3661 fn accept_page(&mut self, child: LoadedPageShard) -> Result<()> {
3662 assert!(child.path.is_empty());
3663 self.page_decoders.push_back(child.decoder);
3664 Ok(())
3665 }
3666
3667 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>> {
3668 let mut remaining = num_rows;
3669 let mut tasks = Vec::new();
3670 while remaining > 0 {
3671 let queued_pages = self.page_decoders.len();
3672 let Some(cur_page) = self.page_decoders.front_mut() else {
3673 return Err(Error::internal(format!(
3674 "Primitive decoder missing page decoder while draining field '{}' (data_type={:?}, requested_rows={}, remaining_rows={}, rows_drained_in_current={}, queued_pages={})",
3675 self.field.name(),
3676 self.field.data_type(),
3677 num_rows,
3678 remaining,
3679 self.rows_drained_in_current,
3680 queued_pages
3681 )));
3682 };
3683 let num_in_page = cur_page.num_rows() - self.rows_drained_in_current;
3684 let to_take = num_in_page.min(remaining);
3685
3686 let task = cur_page.drain(to_take)?;
3687 tasks.push(task);
3688
3689 if to_take == num_in_page {
3690 self.page_decoders.pop_front();
3691 self.rows_drained_in_current = 0;
3692 } else {
3693 self.rows_drained_in_current += to_take;
3694 }
3695
3696 remaining -= to_take;
3697 }
3698 Ok(Box::new(StructuralCompositeDecodeArrayTask {
3699 tasks,
3700 should_validate: self.should_validate,
3701 data_type: self.field.data_type().clone(),
3702 }))
3703 }
3704
3705 fn data_type(&self) -> &DataType {
3706 self.field.data_type()
3707 }
3708}
3709
3710struct SerializedFullZip {
3712 values: LanceBuffer,
3714 repetition_index: Option<LanceBuffer>,
3716}
3717
3718const MINIBLOCK_ALIGNMENT: usize = 8;
3733
3734pub struct PrimitiveStructuralEncoder {
3761 accumulation_queue: AccumulationQueue,
3763
3764 keep_original_array: bool,
3765 support_large_chunk: bool,
3766 accumulated_repdefs: Vec<RepDefBuilder>,
3767 compression_strategy: Arc<dyn CompressionStrategy>,
3769 column_index: u32,
3770 field: Field,
3771 encoding_metadata: Arc<HashMap<String, String>>,
3772 version: LanceFileVersion,
3773}
3774
3775struct CompressedLevelsChunk {
3776 data: LanceBuffer,
3777 num_levels: u16,
3778}
3779
3780struct CompressedLevels {
3781 data: Vec<CompressedLevelsChunk>,
3782 compression: CompressiveEncoding,
3783 rep_index: Option<LanceBuffer>,
3784}
3785
3786struct SerializedMiniBlockPage {
3787 num_buffers: u64,
3788 data: LanceBuffer,
3789 metadata: LanceBuffer,
3790}
3791
3792#[derive(Debug, Clone, Copy)]
3793struct DictEncodingBudget {
3794 max_dict_entries: u32,
3795 max_encoded_size: usize,
3796}
3797
3798struct PrimitivePageData {
3800 arrays: Vec<ArrayRef>,
3802 repdef: SerializedRepDefs,
3804 row_number: u64,
3806 num_rows: u64,
3808 single_row_miniblock_repdef_levels: Option<u64>,
3810}
3811
3812#[derive(Clone)]
3817struct PrimitiveEncodeContext {
3818 column_idx: u32,
3820 field: Field,
3822 compression_strategy: Arc<dyn CompressionStrategy>,
3824 encoding_metadata: Arc<HashMap<String, String>>,
3826 support_large_chunk: bool,
3828 version: LanceFileVersion,
3830 is_simple_validity: bool,
3832 has_repdef_info: bool,
3834}
3835
3836impl PrimitiveStructuralEncoder {
3837 pub fn try_new(
3838 options: &EncodingOptions,
3839 compression_strategy: Arc<dyn CompressionStrategy>,
3840 column_index: u32,
3841 field: Field,
3842 encoding_metadata: Arc<HashMap<String, String>>,
3843 ) -> Result<Self> {
3844 Ok(Self {
3845 accumulation_queue: AccumulationQueue::new(
3846 options.cache_bytes_per_column,
3847 column_index,
3848 options.keep_original_array,
3849 ),
3850 support_large_chunk: options.support_large_chunk(),
3851 keep_original_array: options.keep_original_array,
3852 accumulated_repdefs: Vec::new(),
3853 column_index,
3854 compression_strategy,
3855 field,
3856 encoding_metadata,
3857 version: options.version,
3858 })
3859 }
3860
3861 fn is_narrow(data_block: &DataBlock) -> bool {
3869 const MINIBLOCK_MAX_BYTE_LENGTH_PER_VALUE: u64 = 256;
3870
3871 if let Some(max_len_array) = data_block.get_stat(Stat::MaxLength) {
3872 let max_len_array = max_len_array
3873 .as_any()
3874 .downcast_ref::<PrimitiveArray<UInt64Type>>()
3875 .unwrap();
3876 if max_len_array.value(0) < MINIBLOCK_MAX_BYTE_LENGTH_PER_VALUE {
3877 return true;
3878 }
3879 }
3880 false
3881 }
3882
3883 fn prefers_miniblock(
3884 data_block: &DataBlock,
3885 encoding_metadata: &HashMap<String, String>,
3886 ) -> bool {
3887 if let Some(user_requested) = encoding_metadata.get(STRUCTURAL_ENCODING_META_KEY) {
3889 return user_requested.to_lowercase() == STRUCTURAL_ENCODING_MINIBLOCK;
3890 }
3891 Self::is_narrow(data_block)
3893 }
3894
3895 fn prefers_fullzip(encoding_metadata: &HashMap<String, String>) -> bool {
3896 if let Some(user_requested) = encoding_metadata.get(STRUCTURAL_ENCODING_META_KEY) {
3900 return user_requested.to_lowercase() == STRUCTURAL_ENCODING_FULLZIP;
3901 }
3902 true
3903 }
3904
3905 fn serialize_miniblocks(
3952 miniblocks: MiniBlockCompressed,
3953 rep: Option<Vec<CompressedLevelsChunk>>,
3954 def: Option<Vec<CompressedLevelsChunk>>,
3955 support_large_chunk: bool,
3956 ) -> Result<SerializedMiniBlockPage> {
3957 let bytes_rep = rep
3958 .as_ref()
3959 .map(|rep| rep.iter().map(|r| r.data.len()).sum::<usize>())
3960 .unwrap_or(0);
3961 let bytes_def = def
3962 .as_ref()
3963 .map(|def| def.iter().map(|d| d.data.len()).sum::<usize>())
3964 .unwrap_or(0);
3965 let bytes_data = miniblocks.data.iter().map(|d| d.len()).sum::<usize>();
3966 let mut num_buffers = miniblocks.data.len();
3967 if rep.is_some() {
3968 num_buffers += 1;
3969 }
3970 if def.is_some() {
3971 num_buffers += 1;
3972 }
3973 let max_extra = 9 * num_buffers;
3975 let mut data_buffer = Vec::with_capacity(bytes_rep + bytes_def + bytes_data + max_extra);
3976 let chunk_size_bytes = if support_large_chunk { 4 } else { 2 };
3977 let mut meta_buffer = Vec::with_capacity(miniblocks.chunks.len() * chunk_size_bytes);
3978
3979 let mut rep_iter = rep.map(|r| r.into_iter());
3980 let mut def_iter = def.map(|d| d.into_iter());
3981
3982 let mut buffer_offsets = vec![0; miniblocks.data.len()];
3983 for chunk in miniblocks.chunks {
3984 let start_pos = data_buffer.len();
3985 debug_assert_eq!(start_pos % MINIBLOCK_ALIGNMENT, 0);
3987
3988 let rep = rep_iter.as_mut().map(|r| r.next().unwrap());
3989 let def = def_iter.as_mut().map(|d| d.next().unwrap());
3990
3991 let num_levels = rep
3993 .as_ref()
3994 .map(|r| r.num_levels)
3995 .unwrap_or(def.as_ref().map(|d| d.num_levels).unwrap_or(0));
3996 data_buffer.extend_from_slice(&num_levels.to_le_bytes());
3997
3998 if let Some(rep) = rep.as_ref() {
4000 let bytes_rep = u16::try_from(rep.data.len()).map_err(|_| {
4001 Error::internal(format!(
4002 "Repetition buffer size ({} bytes) too large",
4003 rep.data.len()
4004 ))
4005 })?;
4006 data_buffer.extend_from_slice(&bytes_rep.to_le_bytes());
4007 }
4008 if let Some(def) = def.as_ref() {
4009 let bytes_def = u16::try_from(def.data.len()).map_err(|_| {
4010 Error::internal(format!(
4011 "Definition buffer size ({} bytes) too large",
4012 def.data.len()
4013 ))
4014 })?;
4015 data_buffer.extend_from_slice(&bytes_def.to_le_bytes());
4016 }
4017
4018 if support_large_chunk {
4019 for &buffer_size in &chunk.buffer_sizes {
4020 data_buffer.extend_from_slice(&buffer_size.to_le_bytes());
4021 }
4022 } else {
4023 for &buffer_size in &chunk.buffer_sizes {
4024 let buffer_size = u16::try_from(buffer_size).map_err(|_| {
4025 Error::internal(format!(
4026 "Mini-block buffer size ({} bytes) too large for 16-bit metadata",
4027 buffer_size
4028 ))
4029 })?;
4030 data_buffer.extend_from_slice(&buffer_size.to_le_bytes());
4031 }
4032 }
4033
4034 let add_padding = |data_buffer: &mut Vec<u8>| {
4036 let pad = pad_bytes::<MINIBLOCK_ALIGNMENT>(data_buffer.len());
4037 data_buffer.extend(iter::repeat_n(FILL_BYTE, pad));
4038 };
4039 add_padding(&mut data_buffer);
4040
4041 if let Some(rep) = rep.as_ref() {
4043 data_buffer.extend_from_slice(&rep.data);
4044 add_padding(&mut data_buffer);
4045 }
4046 if let Some(def) = def.as_ref() {
4047 data_buffer.extend_from_slice(&def.data);
4048 add_padding(&mut data_buffer);
4049 }
4050 for (buffer_size, (buffer, buffer_offset)) in chunk
4051 .buffer_sizes
4052 .iter()
4053 .zip(miniblocks.data.iter().zip(buffer_offsets.iter_mut()))
4054 {
4055 let start = *buffer_offset;
4056 let end = start + *buffer_size as usize;
4057 *buffer_offset += *buffer_size as usize;
4058 data_buffer.extend_from_slice(&buffer[start..end]);
4059 add_padding(&mut data_buffer);
4060 }
4061
4062 let chunk_bytes = data_buffer.len() - start_pos;
4063 let max_chunk_size = if support_large_chunk {
4064 1_u64 << 31 } else {
4066 32 * 1024 };
4068 if chunk_bytes == 0 || chunk_bytes as u64 > max_chunk_size {
4069 return Err(Error::internal(format!(
4070 "Mini-block chunk size {} bytes exceeds the {} byte metadata limit",
4071 chunk_bytes, max_chunk_size
4072 )));
4073 }
4074 if chunk_bytes % MINIBLOCK_ALIGNMENT != 0 {
4075 return Err(Error::internal(format!(
4076 "Mini-block chunk size {} bytes is not aligned to {} bytes",
4077 chunk_bytes, MINIBLOCK_ALIGNMENT
4078 )));
4079 }
4080 if chunk.log_num_values > 15 {
4081 return Err(Error::internal(format!(
4082 "Mini-block log_num_values {} exceeds the 4-bit metadata limit",
4083 chunk.log_num_values
4084 )));
4085 }
4086 let divided_bytes = chunk_bytes / MINIBLOCK_ALIGNMENT;
4090 let divided_bytes_minus_one = (divided_bytes - 1) as u64;
4091
4092 let metadata = (divided_bytes_minus_one << 4) | chunk.log_num_values as u64;
4093 if support_large_chunk {
4094 meta_buffer.extend_from_slice(&(metadata as u32).to_le_bytes());
4095 } else {
4096 meta_buffer.extend_from_slice(&(metadata as u16).to_le_bytes());
4097 }
4098 }
4099
4100 let data_buffer = LanceBuffer::from(data_buffer);
4101 let metadata_buffer = LanceBuffer::from(meta_buffer);
4102
4103 Ok(SerializedMiniBlockPage {
4104 num_buffers: miniblocks.data.len() as u64,
4105 data: data_buffer,
4106 metadata: metadata_buffer,
4107 })
4108 }
4109
4110 fn compress_levels(
4115 mut levels: RepDefSlicer<'_>,
4116 num_elements: u64,
4117 compression_strategy: &dyn CompressionStrategy,
4118 chunks: &[MiniBlockChunk],
4119 max_rep: u16,
4121 ) -> Result<CompressedLevels> {
4122 let mut rep_index = if max_rep > 0 {
4123 Vec::with_capacity(chunks.len())
4124 } else {
4125 vec![]
4126 };
4127 let num_levels = levels.num_levels() as u64;
4129 let levels_buf = levels.all_levels().clone();
4130
4131 let mut fixed_width_block = FixedWidthDataBlock {
4132 data: levels_buf,
4133 bits_per_value: 16,
4134 num_values: num_levels,
4135 block_info: BlockInfo::new(),
4136 };
4137 fixed_width_block.compute_stat();
4139
4140 let levels_block = DataBlock::FixedWidth(fixed_width_block);
4141 let levels_field = Field::new_arrow("", DataType::UInt16, false)?;
4142 let (compressor, compressor_desc) =
4144 compression_strategy.create_block_compressor(&levels_field, &levels_block)?;
4145 let mut level_chunks = Vec::with_capacity(chunks.len());
4147 let mut values_counter = 0;
4148 for (chunk_idx, chunk) in chunks.iter().enumerate() {
4149 let chunk_num_values = chunk.num_values(values_counter, num_elements);
4150 debug_assert!(chunk_num_values > 0);
4151 values_counter += chunk_num_values;
4152 let chunk_levels = if chunk_idx < chunks.len() - 1 {
4153 levels.slice_next(chunk_num_values as usize)
4154 } else {
4155 levels.slice_rest()
4156 };
4157 let num_chunk_levels = (chunk_levels.len() / 2) as u64;
4158 if max_rep > 0 {
4159 let rep_values = chunk_levels.borrow_to_typed_slice::<u16>();
4169 let rep_values = rep_values.as_ref();
4170
4171 let mut num_rows = rep_values.iter().skip(1).filter(|v| **v == max_rep).count();
4174 let num_leftovers = if chunk_idx < chunks.len() - 1 {
4175 rep_values
4176 .iter()
4177 .rev()
4178 .position(|v| *v == max_rep)
4179 .map(|pos| pos + 1)
4181 .unwrap_or(rep_values.len())
4182 } else {
4183 0
4185 };
4186
4187 if chunk_idx != 0 && rep_values.first() == Some(&max_rep) {
4188 let rep_len = rep_index.len();
4192 if rep_index[rep_len - 1] != 0 {
4193 rep_index[rep_len - 2] += 1;
4195 rep_index[rep_len - 1] = 0;
4196 }
4197 }
4198
4199 if chunk_idx == chunks.len() - 1 {
4200 num_rows += 1;
4202 }
4203 rep_index.push(num_rows as u64);
4204 rep_index.push(num_leftovers as u64);
4205 }
4206 let mut chunk_fixed_width = FixedWidthDataBlock {
4207 data: chunk_levels,
4208 bits_per_value: 16,
4209 num_values: num_chunk_levels,
4210 block_info: BlockInfo::new(),
4211 };
4212 chunk_fixed_width.compute_stat();
4213 let chunk_levels_block = DataBlock::FixedWidth(chunk_fixed_width);
4214 let compressed_levels = compressor.compress(chunk_levels_block)?;
4215 let num_levels = u16::try_from(num_chunk_levels).map_err(|_| {
4216 Error::invalid_input_source(
4217 format!(
4218 "Mini-block cannot encode {} rep/def levels in one chunk. \
4219 This usually means a top-level row contains too much nested structure \
4220 for the current layout.",
4221 num_chunk_levels
4222 )
4223 .into(),
4224 )
4225 })?;
4226 level_chunks.push(CompressedLevelsChunk {
4227 data: compressed_levels,
4228 num_levels,
4229 });
4230 }
4231 debug_assert_eq!(levels.num_levels_remaining(), 0);
4232 let rep_index = if rep_index.is_empty() {
4233 None
4234 } else {
4235 Some(LanceBuffer::reinterpret_vec(rep_index))
4236 };
4237 Ok(CompressedLevels {
4238 data: level_chunks,
4239 compression: compressor_desc,
4240 rep_index,
4241 })
4242 }
4243
4244 fn encode_simple_all_null(
4245 column_idx: u32,
4246 num_rows: u64,
4247 row_number: u64,
4248 ) -> Result<EncodedPage> {
4249 let description =
4250 ProtobufUtils21::constant_layout(&[DefinitionInterpretation::NullableItem], None);
4251 Ok(EncodedPage {
4252 column_idx,
4253 data: vec![],
4254 description: PageEncoding::Structural(description),
4255 num_rows,
4256 row_number,
4257 })
4258 }
4259
4260 fn encode_complex_all_null_vals(
4261 data: &Arc<[u16]>,
4262 compression_strategy: &dyn CompressionStrategy,
4263 ) -> Result<(LanceBuffer, pb21::CompressiveEncoding)> {
4264 let buffer = LanceBuffer::reinterpret_slice(data.clone());
4265 let mut fixed_width_block = FixedWidthDataBlock {
4266 data: buffer,
4267 bits_per_value: 16,
4268 num_values: data.len() as u64,
4269 block_info: BlockInfo::new(),
4270 };
4271 fixed_width_block.compute_stat();
4272
4273 let levels_block = DataBlock::FixedWidth(fixed_width_block);
4274 let levels_field = Field::new_arrow("", DataType::UInt16, false)?;
4275 let (compressor, encoding) =
4276 compression_strategy.create_block_compressor(&levels_field, &levels_block)?;
4277 let compressed_buffer = compressor.compress(levels_block)?;
4278 Ok((compressed_buffer, encoding))
4279 }
4280
4281 fn encode_complex_all_null(
4285 column_idx: u32,
4286 repdef: crate::repdef::SerializedRepDefs,
4287 row_number: u64,
4288 num_rows: u64,
4289 version: LanceFileVersion,
4290 compression_strategy: &dyn CompressionStrategy,
4291 ) -> Result<EncodedPage> {
4292 if version.resolve() < LanceFileVersion::V2_2 {
4293 let rep_bytes = if let Some(rep) = repdef.repetition_levels.as_ref() {
4294 LanceBuffer::reinterpret_slice(rep.clone())
4295 } else {
4296 LanceBuffer::empty()
4297 };
4298
4299 let def_bytes = if let Some(def) = repdef.definition_levels.as_ref() {
4300 LanceBuffer::reinterpret_slice(def.clone())
4301 } else {
4302 LanceBuffer::empty()
4303 };
4304
4305 let description = ProtobufUtils21::constant_layout(&repdef.def_meaning, None);
4306 return Ok(EncodedPage {
4307 column_idx,
4308 data: vec![rep_bytes, def_bytes],
4309 description: PageEncoding::Structural(description),
4310 num_rows,
4311 row_number,
4312 });
4313 }
4314
4315 let (rep_bytes, rep_encoding, num_rep_values) = if let Some(rep) =
4316 repdef.repetition_levels.as_ref()
4317 {
4318 let num_values = rep.len() as u64;
4319 let (buffer, encoding) = Self::encode_complex_all_null_vals(rep, compression_strategy)?;
4320 (buffer, Some(encoding), num_values)
4321 } else {
4322 (LanceBuffer::empty(), None, 0)
4323 };
4324
4325 let (def_bytes, def_encoding, num_def_values) = if let Some(def) =
4326 repdef.definition_levels.as_ref()
4327 {
4328 let num_values = def.len() as u64;
4329 let (buffer, encoding) = Self::encode_complex_all_null_vals(def, compression_strategy)?;
4330 (buffer, Some(encoding), num_values)
4331 } else {
4332 (LanceBuffer::empty(), None, 0)
4333 };
4334
4335 let description = ProtobufUtils21::compressed_all_null_constant_layout(
4336 &repdef.def_meaning,
4337 rep_encoding,
4338 def_encoding,
4339 num_rep_values,
4340 num_def_values,
4341 );
4342 Ok(EncodedPage {
4343 column_idx,
4344 data: vec![rep_bytes, def_bytes],
4345 description: PageEncoding::Structural(description),
4346 num_rows,
4347 row_number,
4348 })
4349 }
4350
4351 fn leaf_validity(
4352 repdef: &crate::repdef::SerializedRepDefs,
4353 num_values: usize,
4354 ) -> Result<Option<BooleanBuffer>> {
4355 let rep = repdef
4356 .repetition_levels
4357 .as_ref()
4358 .map(|rep| rep.as_ref().to_vec());
4359 let def = repdef
4360 .definition_levels
4361 .as_ref()
4362 .map(|def| def.as_ref().to_vec());
4363 let mut unraveler = RepDefUnraveler::new(
4364 rep,
4365 def,
4366 repdef.def_meaning.clone().into(),
4367 num_values as u64,
4368 );
4369 if unraveler.is_all_valid() {
4370 return Ok(None);
4371 }
4372 let mut validity = BooleanBufferBuilder::new(num_values);
4373 unraveler.unravel_validity(&mut validity);
4374 Ok(Some(validity.finish()))
4375 }
4376
4377 fn is_constant_values(
4378 arrays: &[ArrayRef],
4379 scalar: &ArrayRef,
4380 validity: Option<&BooleanBuffer>,
4381 ) -> Result<bool> {
4382 debug_assert_eq!(scalar.len(), 1);
4383 debug_assert_eq!(scalar.null_count(), 0);
4384
4385 match scalar.data_type() {
4386 DataType::Boolean => {
4387 let mut global_idx = 0usize;
4388 let scalar_val = scalar.as_boolean().value(0);
4389 for arr in arrays {
4390 let bool_arr = arr.as_boolean();
4391 for i in 0..arr.len() {
4392 let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
4393 global_idx += 1;
4394 if !is_valid {
4395 continue;
4396 }
4397 if bool_arr.value(i) != scalar_val {
4398 return Ok(false);
4399 }
4400 }
4401 }
4402 Ok(true)
4403 }
4404 DataType::Utf8 => Self::is_constant_utf8::<i32>(arrays, scalar, validity),
4405 DataType::LargeUtf8 => Self::is_constant_utf8::<i64>(arrays, scalar, validity),
4406 DataType::Binary => Self::is_constant_binary::<i32>(arrays, scalar, validity),
4407 DataType::LargeBinary => Self::is_constant_binary::<i64>(arrays, scalar, validity),
4408 data_type => {
4409 let mut global_idx = 0usize;
4410 let Some(byte_width) = data_type.byte_width_opt() else {
4411 return Ok(false);
4412 };
4413 let scalar_data = scalar.to_data();
4414 if scalar_data.buffers().len() != 1 || !scalar_data.child_data().is_empty() {
4415 return Ok(false);
4416 }
4417 let scalar_bytes = scalar_data.buffers()[0].as_slice();
4418 if scalar_bytes.len() != byte_width {
4419 return Ok(false);
4420 }
4421
4422 for arr in arrays {
4423 let data = arr.to_data();
4424 if data.buffers().is_empty() {
4425 return Ok(false);
4426 }
4427 let buf = data.buffers()[0].as_slice();
4428 let base = data.offset();
4429 for i in 0..arr.len() {
4430 let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
4431 global_idx += 1;
4432 if !is_valid {
4433 continue;
4434 }
4435 let start = (base + i) * byte_width;
4436 if buf[start..start + byte_width] != scalar_bytes[..] {
4437 return Ok(false);
4438 }
4439 }
4440 }
4441 Ok(true)
4442 }
4443 }
4444 }
4445
4446 fn is_constant_utf8<O: arrow_array::OffsetSizeTrait>(
4447 arrays: &[ArrayRef],
4448 scalar: &ArrayRef,
4449 validity: Option<&BooleanBuffer>,
4450 ) -> Result<bool> {
4451 debug_assert_eq!(scalar.len(), 1);
4452 let scalar_val = scalar.as_string::<O>().value(0).as_bytes();
4453 let mut global_idx = 0usize;
4454 for arr in arrays {
4455 let str_arr = arr.as_string::<O>();
4456 for i in 0..arr.len() {
4457 let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
4458 global_idx += 1;
4459 if !is_valid {
4460 continue;
4461 }
4462 if str_arr.value(i).as_bytes() != scalar_val {
4463 return Ok(false);
4464 }
4465 }
4466 }
4467 Ok(true)
4468 }
4469
4470 fn is_constant_binary<O: arrow_array::OffsetSizeTrait>(
4471 arrays: &[ArrayRef],
4472 scalar: &ArrayRef,
4473 validity: Option<&BooleanBuffer>,
4474 ) -> Result<bool> {
4475 debug_assert_eq!(scalar.len(), 1);
4476 let scalar_val = scalar.as_binary::<O>().value(0);
4477 let mut global_idx = 0usize;
4478 for arr in arrays {
4479 let bin_arr = arr.as_binary::<O>();
4480 for i in 0..arr.len() {
4481 let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
4482 global_idx += 1;
4483 if !is_valid {
4484 continue;
4485 }
4486 if bin_arr.value(i) != scalar_val {
4487 return Ok(false);
4488 }
4489 }
4490 }
4491 Ok(true)
4492 }
4493
4494 fn find_constant_scalar(
4495 arrays: &[ArrayRef],
4496 validity: Option<&BooleanBuffer>,
4497 ) -> Result<Option<ArrayRef>> {
4498 if arrays.is_empty() {
4499 return Ok(None);
4500 }
4501
4502 let global_scalar_idx = if let Some(validity) = validity {
4503 let Some(idx) = (0..validity.len()).find(|&i| validity.value(i)) else {
4504 return Ok(None);
4505 };
4506 idx
4507 } else {
4508 0
4509 };
4510
4511 let mut idx_remaining = global_scalar_idx;
4512 let mut scalar_arr_idx = 0usize;
4513 while scalar_arr_idx < arrays.len() {
4514 let len = arrays[scalar_arr_idx].len();
4515 if idx_remaining < len {
4516 break;
4517 }
4518 idx_remaining -= len;
4519 scalar_arr_idx += 1;
4520 }
4521
4522 if scalar_arr_idx >= arrays.len() {
4523 return Ok(None);
4524 }
4525
4526 let scalar =
4527 lance_arrow::scalar::extract_scalar_value(&arrays[scalar_arr_idx], idx_remaining)?;
4528 if scalar.null_count() != 0 {
4529 return Ok(None);
4530 }
4531 if !Self::is_constant_values(arrays, &scalar, validity)? {
4532 return Ok(None);
4533 }
4534 Ok(Some(scalar))
4535 }
4536
4537 fn resolve_dict_values_compression_metadata(
4538 field_metadata: &HashMap<String, String>,
4539 env_compression: Option<String>,
4540 env_compression_level: Option<String>,
4541 ) -> HashMap<String, String> {
4542 let mut metadata = HashMap::new();
4543
4544 let compression = field_metadata
4545 .get(DICT_VALUES_COMPRESSION_META_KEY)
4546 .cloned()
4547 .or(env_compression)
4548 .unwrap_or_else(|| DEFAULT_DICT_VALUES_COMPRESSION.to_string());
4549 metadata.insert(COMPRESSION_META_KEY.to_string(), compression);
4550
4551 if let Some(compression_level) = field_metadata
4552 .get(DICT_VALUES_COMPRESSION_LEVEL_META_KEY)
4553 .cloned()
4554 .or(env_compression_level)
4555 {
4556 metadata.insert(COMPRESSION_LEVEL_META_KEY.to_string(), compression_level);
4557 }
4558
4559 metadata
4560 }
4561
4562 fn build_dict_values_compressor_field(field: &Field) -> Result<Field> {
4563 let mut dict_values_field = Field::new_arrow("", DataType::UInt16, false)?;
4568 dict_values_field.metadata = Self::resolve_dict_values_compression_metadata(
4569 &field.metadata,
4570 env::var(DICT_VALUES_COMPRESSION_ENV_VAR).ok(),
4571 env::var(DICT_VALUES_COMPRESSION_LEVEL_ENV_VAR).ok(),
4572 );
4573 Ok(dict_values_field)
4574 }
4575
4576 #[allow(clippy::too_many_arguments)]
4577 fn encode_miniblock(
4578 column_idx: u32,
4579 field: &Field,
4580 compression_strategy: &dyn CompressionStrategy,
4581 data: DataBlock,
4582 repdef: crate::repdef::SerializedRepDefs,
4583 row_number: u64,
4584 dictionary_data: Option<DataBlock>,
4585 num_rows: u64,
4586 support_large_chunk: bool,
4587 ) -> Result<EncodedPage> {
4588 if let DataBlock::AllNull(_null_block) = data {
4589 unreachable!()
4592 }
4593
4594 let num_items = data.num_values();
4595
4596 let compressor = compression_strategy.create_miniblock_compressor(field, &data)?;
4597 let (compressed_data, value_encoding) = compressor.compress(data)?;
4598
4599 let max_rep = repdef.def_meaning.iter().filter(|l| l.is_list()).count() as u16;
4600
4601 let mut compressed_rep = repdef
4602 .rep_slicer()
4603 .map(|rep_slicer| {
4604 Self::compress_levels(
4605 rep_slicer,
4606 num_items,
4607 compression_strategy,
4608 &compressed_data.chunks,
4609 max_rep,
4610 )
4611 })
4612 .transpose()?;
4613
4614 let (rep_index, rep_index_depth) =
4615 match compressed_rep.as_mut().and_then(|cr| cr.rep_index.as_mut()) {
4616 Some(rep_index) => (Some(rep_index.clone()), 1),
4617 None => (None, 0),
4618 };
4619
4620 let mut compressed_def = repdef
4621 .def_slicer()
4622 .map(|def_slicer| {
4623 Self::compress_levels(
4624 def_slicer,
4625 num_items,
4626 compression_strategy,
4627 &compressed_data.chunks,
4628 0,
4629 )
4630 })
4631 .transpose()?;
4632
4633 let rep_data = compressed_rep
4639 .as_mut()
4640 .map(|cr| std::mem::take(&mut cr.data));
4641 let def_data = compressed_def
4642 .as_mut()
4643 .map(|cd| std::mem::take(&mut cd.data));
4644
4645 let serialized =
4646 Self::serialize_miniblocks(compressed_data, rep_data, def_data, support_large_chunk)?;
4647
4648 let mut data = Vec::with_capacity(4);
4650 data.push(serialized.metadata);
4651 data.push(serialized.data);
4652
4653 if let Some(dictionary_data) = dictionary_data {
4654 let num_dictionary_items = dictionary_data.num_values();
4655 let dict_values_field = Self::build_dict_values_compressor_field(field)?;
4656
4657 let (compressor, dictionary_encoding) = compression_strategy
4658 .create_block_compressor(&dict_values_field, &dictionary_data)?;
4659 let dictionary_buffer = compressor.compress(dictionary_data)?;
4660
4661 data.push(dictionary_buffer);
4662 if let Some(rep_index) = rep_index {
4663 data.push(rep_index);
4664 }
4665
4666 let description = ProtobufUtils21::miniblock_layout(
4667 compressed_rep.map(|cr| cr.compression),
4668 compressed_def.map(|cd| cd.compression),
4669 value_encoding,
4670 rep_index_depth,
4671 serialized.num_buffers,
4672 Some((dictionary_encoding, num_dictionary_items)),
4673 &repdef.def_meaning,
4674 num_items,
4675 support_large_chunk,
4676 );
4677 Ok(EncodedPage {
4678 num_rows,
4679 column_idx,
4680 data,
4681 description: PageEncoding::Structural(description),
4682 row_number,
4683 })
4684 } else {
4685 let description = ProtobufUtils21::miniblock_layout(
4686 compressed_rep.map(|cr| cr.compression),
4687 compressed_def.map(|cd| cd.compression),
4688 value_encoding,
4689 rep_index_depth,
4690 serialized.num_buffers,
4691 None,
4692 &repdef.def_meaning,
4693 num_items,
4694 support_large_chunk,
4695 );
4696
4697 if let Some(rep_index) = rep_index {
4698 let view = rep_index.borrow_to_typed_slice::<u64>();
4699 let total = view.chunks_exact(2).map(|c| c[0]).sum::<u64>();
4700 debug_assert_eq!(total, num_rows);
4701
4702 data.push(rep_index);
4703 }
4704
4705 Ok(EncodedPage {
4706 num_rows,
4707 column_idx,
4708 data,
4709 description: PageEncoding::Structural(description),
4710 row_number,
4711 })
4712 }
4713 }
4714
4715 fn serialize_full_zip_fixed(
4717 fixed: FixedWidthDataBlock,
4718 mut repdef: ControlWordIterator,
4719 num_values: u64,
4720 ) -> Result<SerializedFullZip> {
4721 if !fixed.bits_per_value.is_multiple_of(8) {
4722 return Err(Error::invalid_input_source(
4723 format!(
4724 "Full-zip fixed-width values must be byte aligned, got {} bits per value",
4725 fixed.bits_per_value
4726 )
4727 .into(),
4728 ));
4729 }
4730
4731 let len = fixed.data.len() + repdef.bytes_per_word() * num_values as usize;
4732 let mut zipped_data = Vec::with_capacity(len);
4733
4734 let max_rep_index_val = if repdef.has_repetition() {
4735 len as u64
4736 } else {
4737 0
4739 };
4740 let mut rep_index_builder =
4741 BytepackedIntegerEncoder::with_capacity(num_values as usize + 1, max_rep_index_val);
4742
4743 let bytes_per_value = fixed.bits_per_value as usize / 8;
4744 let mut offset = 0;
4745
4746 if bytes_per_value == 0 {
4747 while let Some(control) = repdef.append_next(&mut zipped_data) {
4749 if control.is_new_row {
4750 debug_assert!(offset <= len);
4752 unsafe { rep_index_builder.append(offset as u64) };
4754 }
4755 offset = zipped_data.len();
4756 }
4757 } else {
4758 let mut data_iter = fixed.data.chunks_exact(bytes_per_value);
4760 while let Some(control) = repdef.append_next(&mut zipped_data) {
4761 if control.is_new_row {
4762 debug_assert!(offset <= len);
4764 unsafe { rep_index_builder.append(offset as u64) };
4766 }
4767 if control.is_visible {
4768 let value = data_iter.next().unwrap();
4769 zipped_data.extend_from_slice(value);
4770 }
4771 offset = zipped_data.len();
4772 }
4773 }
4774
4775 debug_assert_eq!(zipped_data.len(), len);
4776 unsafe {
4779 rep_index_builder.append(zipped_data.len() as u64);
4780 }
4781
4782 let zipped_data = LanceBuffer::from(zipped_data);
4783 let rep_index = rep_index_builder.into_data();
4784 let rep_index = if rep_index.is_empty() {
4785 None
4786 } else {
4787 Some(LanceBuffer::from(rep_index))
4788 };
4789 Ok(SerializedFullZip {
4790 values: zipped_data,
4791 repetition_index: rep_index,
4792 })
4793 }
4794
4795 fn serialize_full_zip_variable(
4799 variable: VariableWidthBlock,
4800 mut repdef: ControlWordIterator,
4801 num_items: u64,
4802 ) -> Result<SerializedFullZip> {
4803 let bytes_per_offset = variable.bits_per_offset as usize / 8;
4804 if !variable.bits_per_offset.is_multiple_of(8) {
4805 return Err(Error::invalid_input_source(
4806 format!(
4807 "Full-zip variable-width offsets must be byte aligned, got {} bits per offset",
4808 variable.bits_per_offset
4809 )
4810 .into(),
4811 ));
4812 }
4813 let len = variable.data.len()
4814 + repdef.bytes_per_word() * num_items as usize
4815 + bytes_per_offset * variable.num_values as usize;
4816 let mut buf = Vec::with_capacity(len);
4817
4818 let max_rep_index_val = len as u64;
4819 let mut rep_index_builder =
4820 BytepackedIntegerEncoder::with_capacity(num_items as usize + 1, max_rep_index_val);
4821
4822 match bytes_per_offset {
4824 4 => {
4825 let offs = variable.offsets.borrow_to_typed_slice::<u32>();
4826 let mut rep_offset = 0;
4827 let mut windows_iter = offs.as_ref().windows(2);
4828 while let Some(control) = repdef.append_next(&mut buf) {
4829 if control.is_new_row {
4830 debug_assert!(rep_offset <= len);
4832 unsafe { rep_index_builder.append(rep_offset as u64) };
4834 }
4835 if control.is_visible {
4836 let window = windows_iter.next().unwrap();
4837 if control.is_valid_item {
4838 buf.extend_from_slice(&(window[1] - window[0]).to_le_bytes());
4839 buf.extend_from_slice(
4840 &variable.data[window[0] as usize..window[1] as usize],
4841 );
4842 }
4843 }
4844 rep_offset = buf.len();
4845 }
4846 }
4847 8 => {
4848 let offs = variable.offsets.borrow_to_typed_slice::<u64>();
4849 let mut rep_offset = 0;
4850 let mut windows_iter = offs.as_ref().windows(2);
4851 while let Some(control) = repdef.append_next(&mut buf) {
4852 if control.is_new_row {
4853 debug_assert!(rep_offset <= len);
4855 unsafe { rep_index_builder.append(rep_offset as u64) };
4857 }
4858 if control.is_visible {
4859 let window = windows_iter.next().unwrap();
4860 if control.is_valid_item {
4861 buf.extend_from_slice(&(window[1] - window[0]).to_le_bytes());
4862 buf.extend_from_slice(
4863 &variable.data[window[0] as usize..window[1] as usize],
4864 );
4865 }
4866 }
4867 rep_offset = buf.len();
4868 }
4869 }
4870 _ => {
4871 return Err(Error::invalid_input_source(
4872 format!(
4873 "Full-zip variable-width offsets must be 32 or 64 bits, got {} bits",
4874 variable.bits_per_offset
4875 )
4876 .into(),
4877 ));
4878 }
4879 }
4880
4881 debug_assert!(buf.len() <= len);
4884 unsafe {
4887 rep_index_builder.append(buf.len() as u64);
4888 }
4889
4890 let zipped_data = LanceBuffer::from(buf);
4891 let rep_index = rep_index_builder.into_data();
4892 debug_assert!(!rep_index.is_empty());
4893 let rep_index = Some(LanceBuffer::from(rep_index));
4894 Ok(SerializedFullZip {
4895 values: zipped_data,
4896 repetition_index: rep_index,
4897 })
4898 }
4899
4900 fn serialize_full_zip(
4903 compressed_data: PerValueDataBlock,
4904 repdef: ControlWordIterator,
4905 num_items: u64,
4906 ) -> Result<SerializedFullZip> {
4907 match compressed_data {
4908 PerValueDataBlock::Fixed(fixed) => {
4909 Self::serialize_full_zip_fixed(fixed, repdef, num_items)
4910 }
4911 PerValueDataBlock::Variable(var) => {
4912 Self::serialize_full_zip_variable(var, repdef, num_items)
4913 }
4914 }
4915 }
4916
4917 fn expand_boolean_to_bytes(fixed: FixedWidthDataBlock) -> FixedWidthDataBlock {
4918 debug_assert_eq!(fixed.bits_per_value, 1);
4919 let num_values = fixed.num_values as usize;
4920 let bool_buf = BooleanBuffer::new(fixed.data.into_buffer(), 0, num_values);
4921 let expanded: Vec<u8> = (0..num_values).map(|i| bool_buf.value(i) as u8).collect();
4922 FixedWidthDataBlock {
4923 data: LanceBuffer::from(expanded),
4924 bits_per_value: 8,
4925 num_values: fixed.num_values,
4926 block_info: BlockInfo::new(),
4927 }
4928 }
4929
4930 fn encode_full_zip(
4931 column_idx: u32,
4932 field: &Field,
4933 compression_strategy: &dyn CompressionStrategy,
4934 data: DataBlock,
4935 repdef: crate::repdef::SerializedRepDefs,
4936 row_number: u64,
4937 num_lists: u64,
4938 ) -> Result<EncodedPage> {
4939 let max_rep = repdef
4940 .repetition_levels
4941 .as_ref()
4942 .map_or(0, |r| r.iter().max().copied().unwrap_or(0));
4943 let max_def = repdef
4944 .definition_levels
4945 .as_ref()
4946 .map_or(0, |d| d.iter().max().copied().unwrap_or(0));
4947
4948 let (num_items, num_visible_items) =
4952 if let Some(rep_levels) = repdef.repetition_levels.as_ref() {
4953 (rep_levels.len() as u64, data.num_values())
4956 } else {
4957 (data.num_values(), data.num_values())
4959 };
4960
4961 let max_visible_def = repdef.max_visible_level.unwrap_or(u16::MAX);
4962
4963 let repdef_iter = build_control_word_iterator(
4964 repdef.repetition_levels.as_deref(),
4965 max_rep,
4966 repdef.definition_levels.as_deref(),
4967 max_def,
4968 max_visible_def,
4969 num_items as usize,
4970 );
4971 let bits_rep = repdef_iter.bits_rep();
4972 let bits_def = repdef_iter.bits_def();
4973
4974 let data = match data {
4976 DataBlock::FixedWidth(fixed) if fixed.bits_per_value == 1 => {
4977 DataBlock::FixedWidth(Self::expand_boolean_to_bytes(fixed))
4978 }
4979 other => other,
4980 };
4981
4982 let compressor = compression_strategy.create_per_value(field, &data)?;
4983 let (compressed_data, value_encoding) = compressor.compress(data)?;
4984
4985 let description = match &compressed_data {
4986 PerValueDataBlock::Fixed(fixed) => ProtobufUtils21::fixed_full_zip_layout(
4987 bits_rep,
4988 bits_def,
4989 fixed.bits_per_value as u32,
4990 value_encoding,
4991 &repdef.def_meaning,
4992 num_items as u32,
4993 num_visible_items as u32,
4994 ),
4995 PerValueDataBlock::Variable(variable) => ProtobufUtils21::variable_full_zip_layout(
4996 bits_rep,
4997 bits_def,
4998 variable.bits_per_offset as u32,
4999 value_encoding,
5000 &repdef.def_meaning,
5001 num_items as u32,
5002 num_visible_items as u32,
5003 ),
5004 };
5005
5006 let zipped = Self::serialize_full_zip(compressed_data, repdef_iter, num_items)?;
5007
5008 let data = if let Some(repindex) = zipped.repetition_index {
5009 vec![zipped.values, repindex]
5010 } else {
5011 vec![zipped.values]
5012 };
5013
5014 Ok(EncodedPage {
5015 num_rows: num_lists,
5016 column_idx,
5017 data,
5018 description: PageEncoding::Structural(description),
5019 row_number,
5020 })
5021 }
5022
5023 fn should_dictionary_encode(
5024 data_block: &DataBlock,
5025 field: &Field,
5026 version: LanceFileVersion,
5027 ) -> Option<DictEncodingBudget> {
5028 const DEFAULT_SAMPLE_SIZE: usize = 4096;
5029 const DEFAULT_SAMPLE_UNIQUE_RATIO: f64 = 0.98;
5030
5031 match data_block {
5034 DataBlock::FixedWidth(fixed) => {
5035 if fixed.bits_per_value == 64 && version < LanceFileVersion::V2_2 {
5036 return None;
5037 }
5038 if fixed.bits_per_value != 64 && fixed.bits_per_value != 128 {
5039 return None;
5040 }
5041 if fixed.bits_per_value % 8 != 0 {
5042 return None;
5043 }
5044 }
5045 DataBlock::VariableWidth(var) => {
5046 if var.bits_per_offset != 32 && var.bits_per_offset != 64 {
5047 return None;
5048 }
5049 }
5050 _ => return None,
5051 }
5052
5053 let too_small = env::var("LANCE_ENCODING_DICT_TOO_SMALL")
5055 .ok()
5056 .and_then(|val| val.parse().ok())
5057 .unwrap_or(100);
5058 if data_block.num_values() < too_small {
5059 return None;
5060 }
5061
5062 let num_values = data_block.num_values();
5063
5064 let divisor: u64 = field
5067 .metadata
5068 .get(DICT_DIVISOR_META_KEY)
5069 .and_then(|val| val.parse().ok())
5070 .or_else(|| {
5071 env::var("LANCE_ENCODING_DICT_DIVISOR")
5072 .ok()
5073 .and_then(|val| val.parse().ok())
5074 })
5075 .unwrap_or(DEFAULT_DICT_DIVISOR);
5076
5077 let max_cardinality: u64 = env::var("LANCE_ENCODING_DICT_MAX_CARDINALITY")
5078 .ok()
5079 .and_then(|val| val.parse().ok())
5080 .unwrap_or(DEFAULT_DICT_MAX_CARDINALITY);
5081
5082 let threshold_cardinality = num_values
5083 .checked_div(divisor.max(1))
5084 .unwrap_or(0)
5085 .min(max_cardinality);
5086 if threshold_cardinality == 0 {
5087 return None;
5088 }
5089
5090 let threshold_ratio = field
5092 .metadata
5093 .get(DICT_SIZE_RATIO_META_KEY)
5094 .and_then(|val| val.parse::<f64>().ok())
5095 .or_else(|| {
5096 env::var("LANCE_ENCODING_DICT_SIZE_RATIO")
5097 .ok()
5098 .and_then(|val| val.parse().ok())
5099 })
5100 .unwrap_or(DEFAULT_DICT_SIZE_RATIO);
5101
5102 if threshold_ratio <= 0.0 || threshold_ratio > 1.0 {
5103 panic!(
5104 "Invalid parameter: dict-size-ratio is {} which is not in the range (0, 1].",
5105 threshold_ratio
5106 );
5107 }
5108
5109 let data_size = data_block.data_size();
5110 if data_size == 0 {
5111 return None;
5112 }
5113
5114 let max_encoded_size = (data_size as f64 * threshold_ratio) as u64;
5115 let max_encoded_size = usize::try_from(max_encoded_size).ok()?;
5116
5117 if let Some(sample_unique_ratio) =
5120 Self::sample_unique_ratio(data_block, DEFAULT_SAMPLE_SIZE)?
5121 {
5122 if sample_unique_ratio >= DEFAULT_SAMPLE_UNIQUE_RATIO {
5123 return None;
5124 }
5125
5126 let projected_cardinality = (sample_unique_ratio * num_values as f64).ceil() as u64;
5127 if projected_cardinality > threshold_cardinality {
5128 return None;
5129 }
5130 }
5131
5132 let max_dict_entries = u32::try_from(threshold_cardinality.min(i32::MAX as u64)).ok()?;
5133 Some(DictEncodingBudget {
5134 max_dict_entries,
5135 max_encoded_size,
5136 })
5137 }
5138
5139 fn sample_unique_ratio(data_block: &DataBlock, max_samples: usize) -> Option<Option<f64>> {
5147 use std::collections::HashSet;
5148
5149 const NUM_SAMPLE_BLOCKS: usize = 32;
5150 const MIN_RELIABLE_SAMPLES: usize = 1024;
5151
5152 let num_values = usize::try_from(data_block.num_values()).ok()?;
5153 if num_values == 0 {
5154 return Some(None);
5155 }
5156
5157 let sample_count = num_values.min(max_samples).max(1);
5158 if sample_count < MIN_RELIABLE_SAMPLES {
5159 return Some(None);
5160 }
5161
5162 let block_count = NUM_SAMPLE_BLOCKS.min(sample_count).min(num_values).max(1);
5163 let samples_per_block = (sample_count / block_count).max(1);
5164 let mut indices = Vec::with_capacity(sample_count);
5165 for block_idx in 0..block_count {
5166 let block_start = block_idx * num_values / block_count;
5167 let next_block_start = ((block_idx + 1) * num_values / block_count).min(num_values);
5168 let block_len = next_block_start.saturating_sub(block_start);
5169 let samples_in_block = samples_per_block.min(block_len);
5170 indices.extend((0..samples_in_block).map(|offset| block_start + offset));
5171 }
5172
5173 if indices.len() < MIN_RELIABLE_SAMPLES {
5174 return Some(None);
5175 }
5176
5177 let ratio = match data_block {
5178 DataBlock::FixedWidth(fixed) => match fixed.bits_per_value {
5179 64 => {
5180 let values = fixed.data.borrow_to_typed_slice::<u64>();
5181 let values = values.as_ref();
5182 let mut unique: HashSet<u64> =
5183 HashSet::with_capacity(indices.len().min(MIN_RELIABLE_SAMPLES));
5184 for idx in indices.iter().copied() {
5185 unique.insert(values.get(idx).copied()?);
5186 }
5187 unique.len() as f64 / indices.len() as f64
5188 }
5189 128 => {
5190 let values = fixed.data.borrow_to_typed_slice::<u128>();
5191 let values = values.as_ref();
5192 let mut unique: HashSet<u128> =
5193 HashSet::with_capacity(indices.len().min(MIN_RELIABLE_SAMPLES));
5194 for idx in indices.iter().copied() {
5195 unique.insert(values.get(idx).copied()?);
5196 }
5197 unique.len() as f64 / indices.len() as f64
5198 }
5199 _ => return Some(None),
5200 },
5201 DataBlock::VariableWidth(var) => {
5202 use xxhash_rust::xxh3::xxh3_64;
5203
5204 let mut unique: HashSet<u64> =
5206 HashSet::with_capacity(indices.len().min(MIN_RELIABLE_SAMPLES));
5207 match var.bits_per_offset {
5208 32 => {
5209 let offsets_ref = var.offsets.borrow_to_typed_slice::<u32>();
5210 let offsets: &[u32] = offsets_ref.as_ref();
5211 for i in indices.iter().copied() {
5212 let start = usize::try_from(*offsets.get(i)?).ok()?;
5213 let end = usize::try_from(*offsets.get(i + 1)?).ok()?;
5214 if start > end || end > var.data.len() {
5215 return None;
5216 }
5217 unique.insert(xxh3_64(&var.data[start..end]));
5218 }
5219 }
5220 64 => {
5221 let offsets_ref = var.offsets.borrow_to_typed_slice::<u64>();
5222 let offsets: &[u64] = offsets_ref.as_ref();
5223 for i in indices.iter().copied() {
5224 let start = usize::try_from(*offsets.get(i)?).ok()?;
5225 let end = usize::try_from(*offsets.get(i + 1)?).ok()?;
5226 if start > end || end > var.data.len() {
5227 return None;
5228 }
5229 unique.insert(xxh3_64(&var.data[start..end]));
5230 }
5231 }
5232 _ => return Some(None),
5233 }
5234 unique.len() as f64 / indices.len() as f64
5235 }
5236 _ => return Some(None),
5237 };
5238
5239 Some(Some(ratio))
5240 }
5241
5242 fn slice_repdef(repdef: &SerializedRepDefs, range: Range<usize>) -> SerializedRepDefs {
5243 let repetition_levels = repdef
5244 .repetition_levels
5245 .as_ref()
5246 .map(|levels| levels[range.clone()].to_vec());
5247 let definition_levels = repdef
5248 .definition_levels
5249 .as_ref()
5250 .map(|levels| levels[range].to_vec());
5251 SerializedRepDefs::new_with_fixed_size_list_levels(
5252 repetition_levels,
5253 definition_levels,
5254 repdef.def_meaning.clone(),
5255 repdef.has_fixed_size_list_levels(),
5256 )
5257 }
5258
5259 fn slice_arrays(
5260 arrays: &[ArrayRef],
5261 value_start: u64,
5262 num_values: u64,
5263 ) -> Result<Vec<ArrayRef>> {
5264 if num_values == 0 {
5265 return Ok(Vec::new());
5266 }
5267
5268 let mut values_to_skip = usize::try_from(value_start).map_err(|_| {
5269 Error::invalid_input(format!("Value start {} is too large", value_start))
5270 })?;
5271 let mut values_remaining = usize::try_from(num_values).map_err(|_| {
5272 Error::invalid_input(format!("Value count {} is too large", num_values))
5273 })?;
5274 let mut sliced = Vec::new();
5275
5276 for array in arrays {
5277 if values_to_skip >= array.len() {
5278 values_to_skip -= array.len();
5279 continue;
5280 }
5281
5282 let offset = values_to_skip;
5283 let len = (array.len() - offset).min(values_remaining);
5284 sliced.push(array.slice(offset, len));
5285 values_remaining -= len;
5286 values_to_skip = 0;
5287
5288 if values_remaining == 0 {
5289 break;
5290 }
5291 }
5292
5293 if values_remaining != 0 {
5294 return Err(Error::internal(format!(
5295 "Page split requested {} values starting at {}, but the page did not contain enough values",
5296 num_values, value_start
5297 )));
5298 }
5299
5300 Ok(sliced)
5301 }
5302
5303 fn split_pages_for_miniblock_repdef_budget(
5304 arrays: Vec<ArrayRef>,
5305 repdef: SerializedRepDefs,
5306 budget: MiniBlockRepDefBudget,
5307 row_number: u64,
5308 num_rows: u64,
5309 ) -> Result<Vec<PrimitivePageData>> {
5310 if budget == MiniBlockRepDefBudget::WithinBudget {
5311 return Ok(vec![PrimitivePageData {
5312 arrays,
5313 repdef,
5314 row_number,
5315 num_rows,
5316 single_row_miniblock_repdef_levels: None,
5317 }]);
5318 }
5319 if let MiniBlockRepDefBudget::SingleRowOverBudget(num_levels) = budget {
5320 return Ok(vec![PrimitivePageData {
5321 arrays,
5322 repdef,
5323 row_number,
5324 num_rows,
5325 single_row_miniblock_repdef_levels: Some(num_levels),
5326 }]);
5327 }
5328
5329 let MiniBlockRepDefBudget::RequiresPageSplit(splits) = budget else {
5330 unreachable!();
5331 };
5332
5333 let mut pages = Vec::with_capacity(splits.len());
5334 for split in splits {
5335 let arrays = Self::slice_arrays(&arrays, split.value_start, split.num_values)?;
5336 let repdef = Self::slice_repdef(&repdef, split.level_range);
5337 pages.push(PrimitivePageData {
5338 arrays,
5339 repdef,
5340 row_number: row_number + split.row_start,
5341 num_rows: split.num_rows,
5342 single_row_miniblock_repdef_levels: None,
5343 });
5344 }
5345 Ok(pages)
5346 }
5347
5348 fn encode_page(ctx: PrimitiveEncodeContext, page: PrimitivePageData) -> Result<EncodedPage> {
5349 let PrimitiveEncodeContext {
5350 column_idx,
5351 field,
5352 compression_strategy,
5353 encoding_metadata,
5354 support_large_chunk,
5355 version,
5356 is_simple_validity,
5357 has_repdef_info,
5358 } = ctx;
5359 let PrimitivePageData {
5360 arrays,
5361 repdef,
5362 row_number,
5363 num_rows,
5364 single_row_miniblock_repdef_levels,
5365 } = page;
5366 let num_values = arrays.iter().map(|arr| arr.len() as u64).sum();
5367
5368 if num_values == 0 {
5369 log::debug!(
5372 "Encoding column {} with {} items ({} rows) using complex-null layout",
5373 column_idx,
5374 num_values,
5375 num_rows
5376 );
5377 return Self::encode_complex_all_null(
5378 column_idx,
5379 repdef,
5380 row_number,
5381 num_rows,
5382 version,
5383 compression_strategy.as_ref(),
5384 );
5385 }
5386
5387 let leaf_validity = Self::leaf_validity(&repdef, num_values as usize)?;
5388 let all_null = leaf_validity
5389 .as_ref()
5390 .map(|validity| validity.count_set_bits() == 0)
5391 .unwrap_or(false);
5392
5393 if all_null {
5394 return if is_simple_validity {
5395 log::debug!(
5396 "Encoding column {} with {} items ({} rows) using simple-null layout",
5397 column_idx,
5398 num_values,
5399 num_rows
5400 );
5401 Self::encode_simple_all_null(column_idx, num_values, row_number)
5402 } else {
5403 log::debug!(
5404 "Encoding column {} with {} items ({} rows) using complex-null layout",
5405 column_idx,
5406 num_values,
5407 num_rows
5408 );
5409 Self::encode_complex_all_null(
5410 column_idx,
5411 repdef,
5412 row_number,
5413 num_rows,
5414 version,
5415 compression_strategy.as_ref(),
5416 )
5417 };
5418 }
5419
5420 if let DataType::Struct(fields) = &field.data_type()
5421 && fields.is_empty()
5422 {
5423 if has_repdef_info {
5424 return Err(Error::invalid_input_source(format!("Empty structs with rep/def information are not yet supported. The field {} is an empty struct that either has nulls or is in a list.", field.name).into()));
5425 }
5426 return Self::encode_simple_all_null(column_idx, num_values, row_number);
5429 }
5430
5431 let data_block = DataBlock::from_arrays(&arrays, num_values);
5432
5433 if version.resolve() >= LanceFileVersion::V2_2
5434 && let Some(scalar) = Self::find_constant_scalar(&arrays, leaf_validity.as_ref())?
5435 {
5436 log::debug!(
5437 "Encoding column {} with {} items ({} rows) using constant layout",
5438 column_idx,
5439 num_values,
5440 num_rows
5441 );
5442 return constant::encode_constant_page(
5443 column_idx, scalar, repdef, row_number, num_rows,
5444 );
5445 }
5446
5447 if let Some(num_levels) = single_row_miniblock_repdef_levels {
5448 let requested_encoding = encoding_metadata
5449 .get(STRUCTURAL_ENCODING_META_KEY)
5450 .map(|requested| requested.to_lowercase());
5451 let fullzip_error = match &data_block {
5452 DataBlock::FixedWidth(fixed) if !fixed.bits_per_value.is_multiple_of(8) => {
5453 Some(format!(
5454 "Full-zip fixed-width values must be byte aligned, got {} bits per value",
5455 fixed.bits_per_value
5456 ))
5457 }
5458 DataBlock::VariableWidth(variable)
5459 if !variable.bits_per_offset.is_multiple_of(8) =>
5460 {
5461 Some(format!(
5462 "Full-zip variable-width offsets must be byte aligned, got {} bits per offset",
5463 variable.bits_per_offset
5464 ))
5465 }
5466 DataBlock::VariableWidth(variable)
5467 if variable.bits_per_offset != 32 && variable.bits_per_offset != 64 =>
5468 {
5469 Some(format!(
5470 "Full-zip variable-width offsets must be 32 or 64 bits, got {} bits",
5471 variable.bits_per_offset
5472 ))
5473 }
5474 DataBlock::Struct(struct_data_block)
5475 if !struct_data_block.has_variable_width_child() =>
5476 {
5477 Some(
5478 "Full-zip packed struct requires at least one variable-width child"
5479 .to_string(),
5480 )
5481 }
5482 DataBlock::Dictionary(_) => {
5483 Some("Full-zip does not encode dictionary data blocks directly".to_string())
5484 }
5485 DataBlock::FixedSizeList(fsl) => match fsl.clone().try_into_flat() {
5486 Some(flat) if flat.bits_per_value.is_multiple_of(8) => None,
5487 Some(flat) => Some(format!(
5488 "Full-zip fixed-size-list values must be byte aligned after flattening, got {} bits per value",
5489 flat.bits_per_value
5490 )),
5491 None => Some(
5492 "Full-zip fixed-size-list capability requires a flat fixed-width child"
5493 .to_string(),
5494 ),
5495 },
5496 DataBlock::FixedWidth(_) | DataBlock::VariableWidth(_) | DataBlock::Struct(_) => {
5497 None
5498 }
5499 other => Some(format!(
5500 "Full-zip does not support value block type {}",
5501 other.name()
5502 )),
5503 };
5504 match requested_encoding.as_deref() {
5505 Some(STRUCTURAL_ENCODING_FULLZIP) => {
5506 if let Some(reason) = fullzip_error {
5507 return Err(Error::invalid_input_source(reason.into()));
5508 }
5509 return Self::encode_full_zip(
5510 column_idx,
5511 &field,
5512 compression_strategy.as_ref(),
5513 data_block,
5514 repdef,
5515 row_number,
5516 num_rows,
5517 );
5518 }
5519 Some(STRUCTURAL_ENCODING_MINIBLOCK) | None => {
5520 if requested_encoding.is_none() && fullzip_error.is_none() {
5521 log::debug!(
5522 "Encoding column {} with {} items using full-zip layout because mini-block cannot split the structural page",
5523 column_idx,
5524 num_values
5525 );
5526 return Self::encode_full_zip(
5527 column_idx,
5528 &field,
5529 compression_strategy.as_ref(),
5530 data_block,
5531 repdef,
5532 row_number,
5533 num_rows,
5534 );
5535 }
5536 return Err(Error::invalid_input_source(
5537 format!(
5538 "Mini-block cannot encode {} rep/def levels in one top-level row. \
5539 This usually means the row contains too much nested structure \
5540 for the current layout.",
5541 num_levels
5542 )
5543 .into(),
5544 ));
5545 }
5546 _ => {}
5547 }
5548 }
5549
5550 let requires_full_zip_packed_struct =
5551 if let DataBlock::Struct(ref struct_data_block) = data_block {
5552 struct_data_block.has_variable_width_child()
5553 } else {
5554 false
5555 };
5556
5557 if requires_full_zip_packed_struct {
5558 log::debug!(
5559 "Encoding column {} with {} items using full-zip packed struct layout",
5560 column_idx,
5561 num_values
5562 );
5563 return Self::encode_full_zip(
5564 column_idx,
5565 &field,
5566 compression_strategy.as_ref(),
5567 data_block,
5568 repdef,
5569 row_number,
5570 num_rows,
5571 );
5572 }
5573
5574 if let DataBlock::Dictionary(dict) = data_block {
5575 log::debug!(
5576 "Encoding column {} with {} items using dictionary encoding (already dictionary encoded)",
5577 column_idx,
5578 num_values
5579 );
5580 let (mut indices_data_block, dictionary_data_block) = dict.into_parts();
5581 indices_data_block.compute_stat();
5586 return Self::encode_miniblock(
5587 column_idx,
5588 &field,
5589 compression_strategy.as_ref(),
5590 indices_data_block,
5591 repdef,
5592 row_number,
5593 Some(dictionary_data_block),
5594 num_rows,
5595 support_large_chunk,
5596 );
5597 }
5598
5599 let dict_result = Self::should_dictionary_encode(&data_block, &field, version).and_then(|budget| {
5602 log::debug!(
5603 "Encoding column {} with {} items using dictionary encoding (mini-block layout)",
5604 column_idx,
5605 num_values
5606 );
5607 dict::dictionary_encode(
5608 &data_block,
5609 budget.max_dict_entries,
5610 budget.max_encoded_size,
5611 )
5612 });
5613
5614 if let Some((indices_data_block, dictionary_data_block)) = dict_result {
5615 Self::encode_miniblock(
5616 column_idx,
5617 &field,
5618 compression_strategy.as_ref(),
5619 indices_data_block,
5620 repdef,
5621 row_number,
5622 Some(dictionary_data_block),
5623 num_rows,
5624 support_large_chunk,
5625 )
5626 } else if Self::prefers_miniblock(&data_block, encoding_metadata.as_ref()) {
5627 log::debug!(
5628 "Encoding column {} with {} items using mini-block layout",
5629 column_idx,
5630 num_values
5631 );
5632 Self::encode_miniblock(
5633 column_idx,
5634 &field,
5635 compression_strategy.as_ref(),
5636 data_block,
5637 repdef,
5638 row_number,
5639 None,
5640 num_rows,
5641 support_large_chunk,
5642 )
5643 } else if Self::prefers_fullzip(encoding_metadata.as_ref()) {
5644 log::debug!(
5645 "Encoding column {} with {} items using full-zip layout",
5646 column_idx,
5647 num_values
5648 );
5649 Self::encode_full_zip(
5650 column_idx,
5651 &field,
5652 compression_strategy.as_ref(),
5653 data_block,
5654 repdef,
5655 row_number,
5656 num_rows,
5657 )
5658 } else {
5659 Err(Error::invalid_input_source(format!("Cannot determine structural encoding for field {}. This typically indicates an invalid value of the field metadata key {}", field.name, STRUCTURAL_ENCODING_META_KEY).into()))
5660 }
5661 }
5662
5663 fn do_flush(
5665 &mut self,
5666 arrays: Vec<ArrayRef>,
5667 repdefs: Vec<RepDefBuilder>,
5668 row_number: u64,
5669 num_rows: u64,
5670 ) -> Result<Vec<EncodeTask>> {
5671 let num_values = arrays.iter().map(|arr| arr.len() as u64).sum();
5672 let is_simple_validity = repdefs.iter().all(|rd| rd.is_simple_validity());
5673 let has_repdef_info = repdefs.iter().any(|rd| !rd.is_empty());
5674 let (repdef, miniblock_repdef_budget) =
5675 RepDefBuilder::serialize_with_miniblock_repdef_budget(
5676 repdefs,
5677 miniblock::max_repdef_levels_per_chunk,
5678 num_rows,
5679 num_values,
5680 )?;
5681 let pages = Self::split_pages_for_miniblock_repdef_budget(
5682 arrays,
5683 repdef,
5684 miniblock_repdef_budget,
5685 row_number,
5686 num_rows,
5687 )?;
5688
5689 let mut tasks = Vec::with_capacity(pages.len());
5690 let ctx = PrimitiveEncodeContext {
5691 column_idx: self.column_index,
5692 field: self.field.clone(),
5693 compression_strategy: self.compression_strategy.clone(),
5694 encoding_metadata: self.encoding_metadata.clone(),
5695 support_large_chunk: self.support_large_chunk,
5696 version: self.version,
5697 is_simple_validity,
5698 has_repdef_info,
5699 };
5700 for page in pages {
5701 let ctx = ctx.clone();
5702 let task = spawn_cpu(move || Self::encode_page(ctx, page)).boxed();
5703 tasks.push(task);
5704 }
5705 Ok(tasks)
5706 }
5707
5708 fn extract_validity_buf(
5709 array: Arc<dyn Array>,
5710 repdef: &mut RepDefBuilder,
5711 keep_original_array: bool,
5712 ) -> Result<Arc<dyn Array>> {
5713 if let Some(validity) = array.nulls() {
5714 if keep_original_array {
5715 repdef.add_validity_bitmap(validity.clone());
5716 } else {
5717 repdef.add_validity_bitmap(deep_copy_nulls(Some(validity)).unwrap());
5718 }
5719 let data_no_nulls = array.to_data().into_builder().nulls(None).build()?;
5720 Ok(make_array(data_no_nulls))
5721 } else {
5722 repdef.add_no_null(array.len());
5723 Ok(array)
5724 }
5725 }
5726
5727 fn extract_validity(
5728 mut array: Arc<dyn Array>,
5729 repdef: &mut RepDefBuilder,
5730 keep_original_array: bool,
5731 ) -> Result<Arc<dyn Array>> {
5732 match array.data_type() {
5733 DataType::Null => {
5734 repdef.add_validity_bitmap(NullBuffer::new(BooleanBuffer::new_unset(array.len())));
5735 Ok(array)
5736 }
5737 DataType::Dictionary(_, _) => {
5738 array = dict::normalize_dict_nulls(array)?;
5739 Self::extract_validity_buf(array, repdef, keep_original_array)
5740 }
5741 _ => Self::extract_validity_buf(array, repdef, keep_original_array),
5750 }
5751 }
5752}
5753
5754impl FieldEncoder for PrimitiveStructuralEncoder {
5755 fn maybe_encode(
5757 &mut self,
5758 array: ArrayRef,
5759 _external_buffers: &mut OutOfLineBuffers,
5760 mut repdef: RepDefBuilder,
5761 row_number: u64,
5762 num_rows: u64,
5763 ) -> Result<Vec<EncodeTask>> {
5764 let array = Self::extract_validity(array, &mut repdef, self.keep_original_array)?;
5765 self.accumulated_repdefs.push(repdef);
5766
5767 if let Some((arrays, row_number, num_rows)) =
5768 self.accumulation_queue.insert(array, row_number, num_rows)
5769 {
5770 let accumulated_repdefs = std::mem::take(&mut self.accumulated_repdefs);
5771 Ok(self.do_flush(arrays, accumulated_repdefs, row_number, num_rows)?)
5772 } else {
5773 Ok(vec![])
5774 }
5775 }
5776
5777 fn flush(&mut self, _external_buffers: &mut OutOfLineBuffers) -> Result<Vec<EncodeTask>> {
5779 if let Some((arrays, row_number, num_rows)) = self.accumulation_queue.flush() {
5780 let accumulated_repdefs = std::mem::take(&mut self.accumulated_repdefs);
5781 Ok(self.do_flush(arrays, accumulated_repdefs, row_number, num_rows)?)
5782 } else {
5783 Ok(vec![])
5784 }
5785 }
5786
5787 fn num_columns(&self) -> u32 {
5788 1
5789 }
5790
5791 fn finish(
5792 &mut self,
5793 _external_buffers: &mut OutOfLineBuffers,
5794 ) -> BoxFuture<'_, Result<Vec<crate::encoder::EncodedColumn>>> {
5795 std::future::ready(Ok(vec![EncodedColumn::default()])).boxed()
5796 }
5797}
5798
5799#[cfg(test)]
5800#[allow(clippy::single_range_in_vec_init)]
5801mod tests {
5802 use super::{
5803 ChunkInstructions, DataBlock, DecodeMiniBlockTask, FixedPerValueDecompressor,
5804 FixedWidthDataBlock, FullZipCacheableState, FullZipDecodeDetails, FullZipReadSource,
5805 FullZipRepIndexDetails, FullZipScheduler, MiniBlockChunk, MiniBlockCompressed,
5806 MiniBlockRepIndex, PerValueDecompressor, PreambleAction, StructuralPageScheduler,
5807 VariableFullZipDecoder,
5808 };
5809 use crate::buffer::LanceBuffer;
5810 use crate::compression::DefaultDecompressionStrategy;
5811 use crate::constants::{
5812 COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY, DICT_VALUES_COMPRESSION_LEVEL_META_KEY,
5813 DICT_VALUES_COMPRESSION_META_KEY, STRUCTURAL_ENCODING_META_KEY,
5814 STRUCTURAL_ENCODING_MINIBLOCK,
5815 };
5816 use crate::data::BlockInfo;
5817 use crate::decoder::{PageEncoding, StructuralFieldDecoder};
5818 use crate::encodings::logical::primitive::{
5819 ChunkDrainInstructions, PrimitiveStructuralEncoder, StructuralPrimitiveFieldDecoder,
5820 };
5821 use crate::format::ProtobufUtils21;
5822 use crate::format::pb21;
5823 use crate::format::pb21::compressive_encoding::Compression;
5824 use crate::repdef::build_control_word_iterator;
5825 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
5826 use crate::version::LanceFileVersion;
5827 use arrow_array::{ArrayRef, Int8Array, StringArray};
5828 use arrow_schema::{DataType, Field as ArrowField};
5829 use std::collections::HashMap;
5830 use std::{collections::VecDeque, sync::Arc};
5831
5832 #[test]
5833 fn test_is_narrow() {
5834 let int8_array = Int8Array::from(vec![1, 2, 3]);
5835 let array_ref: ArrayRef = Arc::new(int8_array);
5836 let block = DataBlock::from_array(array_ref);
5837
5838 assert!(PrimitiveStructuralEncoder::is_narrow(&block));
5839
5840 let string_array = StringArray::from(vec![Some("hello"), Some("world")]);
5841 let block = DataBlock::from_array(string_array);
5842 assert!(PrimitiveStructuralEncoder::is_narrow(&block));
5843
5844 let string_array = StringArray::from(vec![
5845 Some("hello world".repeat(100)),
5846 Some("world".to_string()),
5847 ]);
5848 let block = DataBlock::from_array(string_array);
5849 assert!((!PrimitiveStructuralEncoder::is_narrow(&block)));
5850 }
5851
5852 #[test]
5853 fn test_primitive_decoder_empty_page_queue_returns_error() {
5854 let field = Arc::new(ArrowField::new("vector", DataType::Float32, true));
5855 let mut decoder = StructuralPrimitiveFieldDecoder::new(&field, false);
5856
5857 let err = decoder.drain(1).unwrap_err();
5858 assert!(
5859 matches!(&err, lance_core::Error::Internal { .. }),
5860 "expected internal error, got: {err:?}"
5861 );
5862 let message = err.to_string();
5863 for expected in [
5864 "Primitive decoder missing page decoder",
5865 "field 'vector'",
5866 "data_type=Float32",
5867 "requested_rows=1",
5868 "remaining_rows=1",
5869 "rows_drained_in_current=0",
5870 "queued_pages=0",
5871 ] {
5872 assert!(
5873 message.contains(expected),
5874 "expected error to contain {expected:?}, got: {message}"
5875 );
5876 }
5877 }
5878
5879 #[test]
5880 fn test_fullzip_fixed_rejects_non_byte_aligned_values() {
5881 let fixed = FixedWidthDataBlock {
5882 data: LanceBuffer::from(vec![0_u8]),
5883 bits_per_value: 1,
5884 num_values: 8,
5885 block_info: BlockInfo::new(),
5886 };
5887 let repdef = build_control_word_iterator(None, 0, None, 0, u16::MAX, 8);
5888
5889 let Err(err) = PrimitiveStructuralEncoder::serialize_full_zip_fixed(fixed, repdef, 8)
5890 else {
5891 panic!("expected full-zip to reject 1-bit fixed-width values");
5892 };
5893 assert!(
5894 err.to_string().contains("byte aligned"),
5895 "unexpected error: {err}"
5896 );
5897 }
5898
5899 #[test]
5900 fn test_map_range() {
5901 let rep = Some(vec![1, 0, 0, 1, 0, 1, 1, 0, 0]);
5904 let def = Some(vec![0, 0, 0, 0, 0, 1, 0, 0, 0]);
5905 let max_visible_def = 0;
5906 let total_items = 8;
5907 let max_rep = 1;
5908
5909 let check = |range, expected_item_range, expected_level_range| {
5910 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
5911 range,
5912 rep.as_ref(),
5913 def.as_ref(),
5914 max_rep,
5915 max_visible_def,
5916 total_items,
5917 PreambleAction::Absent,
5918 );
5919 assert_eq!(item_range, expected_item_range);
5920 assert_eq!(level_range, expected_level_range);
5921 };
5922
5923 check(0..1, 0..3, 0..3);
5924 check(1..2, 3..5, 3..5);
5925 check(2..3, 5..5, 5..6);
5926 check(3..4, 5..8, 6..9);
5927 check(0..2, 0..5, 0..5);
5928 check(1..3, 3..5, 3..6);
5929 check(2..4, 5..8, 5..9);
5930 check(0..3, 0..5, 0..6);
5931 check(1..4, 3..8, 3..9);
5932 check(0..4, 0..8, 0..9);
5933
5934 let rep = Some(vec![1, 1, 0, 1]);
5937 let def = Some(vec![1, 0, 0, 0]);
5938 let max_visible_def = 0;
5939 let total_items = 3;
5940
5941 let check = |range, expected_item_range, expected_level_range| {
5942 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
5943 range,
5944 rep.as_ref(),
5945 def.as_ref(),
5946 max_rep,
5947 max_visible_def,
5948 total_items,
5949 PreambleAction::Absent,
5950 );
5951 assert_eq!(item_range, expected_item_range);
5952 assert_eq!(level_range, expected_level_range);
5953 };
5954
5955 check(0..1, 0..0, 0..1);
5956 check(1..2, 0..2, 1..3);
5957 check(2..3, 2..3, 3..4);
5958 check(0..2, 0..2, 0..3);
5959 check(1..3, 0..3, 1..4);
5960 check(0..3, 0..3, 0..4);
5961
5962 let rep = Some(vec![1, 1, 0, 1]);
5965 let def = Some(vec![0, 0, 0, 1]);
5966 let max_visible_def = 0;
5967 let total_items = 3;
5968
5969 let check = |range, expected_item_range, expected_level_range| {
5970 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
5971 range,
5972 rep.as_ref(),
5973 def.as_ref(),
5974 max_rep,
5975 max_visible_def,
5976 total_items,
5977 PreambleAction::Absent,
5978 );
5979 assert_eq!(item_range, expected_item_range);
5980 assert_eq!(level_range, expected_level_range);
5981 };
5982
5983 check(0..1, 0..1, 0..1);
5984 check(1..2, 1..3, 1..3);
5985 check(2..3, 3..3, 3..4);
5986 check(0..2, 0..3, 0..3);
5987 check(1..3, 1..3, 1..4);
5988 check(0..3, 0..3, 0..4);
5989
5990 let rep = Some(vec![1, 0, 1, 0, 1, 0]);
5993 let def: Option<&[u16]> = None;
5994 let max_visible_def = 0;
5995 let total_items = 6;
5996
5997 let check = |range, expected_item_range, expected_level_range| {
5998 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
5999 range,
6000 rep.as_ref(),
6001 def.as_ref(),
6002 max_rep,
6003 max_visible_def,
6004 total_items,
6005 PreambleAction::Absent,
6006 );
6007 assert_eq!(item_range, expected_item_range);
6008 assert_eq!(level_range, expected_level_range);
6009 };
6010
6011 check(0..1, 0..2, 0..2);
6012 check(1..2, 2..4, 2..4);
6013 check(2..3, 4..6, 4..6);
6014 check(0..2, 0..4, 0..4);
6015 check(1..3, 2..6, 2..6);
6016 check(0..3, 0..6, 0..6);
6017
6018 let rep: Option<&[u16]> = None;
6021 let def = Some(vec![0, 0, 1, 0]);
6022 let max_visible_def = 1;
6023 let total_items = 4;
6024
6025 let check = |range, expected_item_range, expected_level_range| {
6026 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6027 range,
6028 rep.as_ref(),
6029 def.as_ref(),
6030 max_rep,
6031 max_visible_def,
6032 total_items,
6033 PreambleAction::Absent,
6034 );
6035 assert_eq!(item_range, expected_item_range);
6036 assert_eq!(level_range, expected_level_range);
6037 };
6038
6039 check(0..1, 0..1, 0..1);
6040 check(1..2, 1..2, 1..2);
6041 check(2..3, 2..3, 2..3);
6042 check(0..2, 0..2, 0..2);
6043 check(1..3, 1..3, 1..3);
6044 check(0..3, 0..3, 0..3);
6045
6046 let rep = Some(vec![0, 1, 0, 1]);
6051 let def = Some(vec![0, 0, 0, 1]);
6052 let max_visible_def = 0;
6053 let total_items = 3;
6054
6055 let check = |range, expected_item_range, expected_level_range| {
6056 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6057 range,
6058 rep.as_ref(),
6059 def.as_ref(),
6060 max_rep,
6061 max_visible_def,
6062 total_items,
6063 PreambleAction::Take,
6064 );
6065 assert_eq!(item_range, expected_item_range);
6066 assert_eq!(level_range, expected_level_range);
6067 };
6068
6069 check(0..1, 0..3, 0..3);
6071 check(0..2, 0..3, 0..4);
6072
6073 let check = |range, expected_item_range, expected_level_range| {
6074 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6075 range,
6076 rep.as_ref(),
6077 def.as_ref(),
6078 max_rep,
6079 max_visible_def,
6080 total_items,
6081 PreambleAction::Skip,
6082 );
6083 assert_eq!(item_range, expected_item_range);
6084 assert_eq!(level_range, expected_level_range);
6085 };
6086
6087 check(0..1, 1..3, 1..3);
6088 check(1..2, 3..3, 3..4);
6089 check(0..2, 1..3, 1..4);
6090
6091 let rep = Some(vec![0, 1, 1, 0]);
6096 let def = Some(vec![0, 1, 0, 0]);
6097 let max_visible_def = 0;
6098 let total_items = 4;
6099
6100 let check = |range, expected_item_range, expected_level_range| {
6101 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6102 range,
6103 rep.as_ref(),
6104 def.as_ref(),
6105 max_rep,
6106 max_visible_def,
6107 total_items,
6108 PreambleAction::Take,
6109 );
6110 assert_eq!(item_range, expected_item_range);
6111 assert_eq!(level_range, expected_level_range);
6112 };
6113
6114 check(0..1, 0..1, 0..2);
6116 check(0..2, 0..3, 0..4);
6117
6118 let check = |range, expected_item_range, expected_level_range| {
6119 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6120 range,
6121 rep.as_ref(),
6122 def.as_ref(),
6123 max_rep,
6124 max_visible_def,
6125 total_items,
6126 PreambleAction::Skip,
6127 );
6128 assert_eq!(item_range, expected_item_range);
6129 assert_eq!(level_range, expected_level_range);
6130 };
6131
6132 check(0..1, 1..1, 1..2);
6134 check(1..2, 1..3, 2..4);
6135 check(0..2, 1..3, 1..4);
6136
6137 let rep = Some(vec![0, 1, 0, 1]);
6140 let def: Option<Vec<u16>> = None;
6141 let max_visible_def = 0;
6142 let total_items = 4;
6143
6144 let check = |range, expected_item_range, expected_level_range| {
6145 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6146 range,
6147 rep.as_ref(),
6148 def.as_ref(),
6149 max_rep,
6150 max_visible_def,
6151 total_items,
6152 PreambleAction::Take,
6153 );
6154 assert_eq!(item_range, expected_item_range);
6155 assert_eq!(level_range, expected_level_range);
6156 };
6157
6158 check(0..1, 0..3, 0..3);
6160 check(0..2, 0..4, 0..4);
6161
6162 let check = |range, expected_item_range, expected_level_range| {
6163 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6164 range,
6165 rep.as_ref(),
6166 def.as_ref(),
6167 max_rep,
6168 max_visible_def,
6169 total_items,
6170 PreambleAction::Skip,
6171 );
6172 assert_eq!(item_range, expected_item_range);
6173 assert_eq!(level_range, expected_level_range);
6174 };
6175
6176 check(0..1, 1..3, 1..3);
6177 check(1..2, 3..4, 3..4);
6178 check(0..2, 1..4, 1..4);
6179
6180 let rep = Some(vec![2, 1, 2, 0, 1, 2]);
6184 let def = Some(vec![0, 1, 2, 0, 0, 0]);
6185 let max_rep = 2;
6186 let max_visible_def = 0;
6187 let total_items = 4;
6188
6189 let check = |range, expected_item_range, expected_level_range| {
6190 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6191 range,
6192 rep.as_ref(),
6193 def.as_ref(),
6194 max_rep,
6195 max_visible_def,
6196 total_items,
6197 PreambleAction::Absent,
6198 );
6199 assert_eq!(item_range, expected_item_range);
6200 assert_eq!(level_range, expected_level_range);
6201 };
6202
6203 check(0..3, 0..4, 0..6);
6204 check(0..1, 0..1, 0..2);
6205 check(1..2, 1..3, 2..5);
6206 check(2..3, 3..4, 5..6);
6207
6208 let rep = Some(vec![0, 0, 1, 0, 1, 1]);
6210 let def = Some(vec![0, 1, 0, 0, 0, 0]);
6211 let max_rep = 1;
6212 let max_visible_def = 0;
6213 let total_items = 5;
6214
6215 let check = |range, expected_item_range, expected_level_range| {
6216 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6217 range,
6218 rep.as_ref(),
6219 def.as_ref(),
6220 max_rep,
6221 max_visible_def,
6222 total_items,
6223 PreambleAction::Take,
6224 );
6225 assert_eq!(item_range, expected_item_range);
6226 assert_eq!(level_range, expected_level_range);
6227 };
6228
6229 check(0..0, 0..1, 0..2);
6230 check(0..1, 0..3, 0..4);
6231 check(0..2, 0..4, 0..5);
6232
6233 let rep = Some(vec![0, 1, 0, 1, 0, 1, 0, 1]);
6236 let def = Some(vec![1, 0, 1, 1, 0, 0, 0, 0]);
6237 let max_rep = 1;
6238 let max_visible_def = 0;
6239 let total_items = 5;
6240
6241 let check = |range, expected_item_range, expected_level_range| {
6242 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
6243 range,
6244 rep.as_ref(),
6245 def.as_ref(),
6246 max_rep,
6247 max_visible_def,
6248 total_items,
6249 PreambleAction::Skip,
6250 );
6251 assert_eq!(item_range, expected_item_range);
6252 assert_eq!(level_range, expected_level_range);
6253 };
6254
6255 check(2..3, 2..4, 5..7);
6256 }
6257
6258 #[test]
6259 fn test_slice_batch_data_and_rebase_offsets_u32() {
6260 let data = LanceBuffer::copy_slice(b"0123456789abcdefghij");
6261 let offsets = LanceBuffer::reinterpret_vec(vec![6_u32, 8_u32, 8_u32, 12_u32]);
6262
6263 let (sliced_data, normalized_offsets) =
6264 VariableFullZipDecoder::slice_batch_data_and_rebase_offsets(&data, &offsets, 32)
6265 .unwrap();
6266
6267 assert_eq!(sliced_data.as_ref(), b"6789ab");
6268 let normalized = normalized_offsets.borrow_to_typed_slice::<u32>();
6269 assert_eq!(normalized.as_ref(), &[0, 2, 2, 6]);
6270 }
6271
6272 #[test]
6273 fn test_slice_batch_data_and_rebase_offsets_u64() {
6274 let data = LanceBuffer::copy_slice(b"abcdefghijklmnopqrstuvwxyz");
6275 let offsets = LanceBuffer::reinterpret_vec(vec![10_u64, 12_u64, 16_u64, 20_u64]);
6276
6277 let (sliced_data, normalized_offsets) =
6278 VariableFullZipDecoder::slice_batch_data_and_rebase_offsets(&data, &offsets, 64)
6279 .unwrap();
6280
6281 assert_eq!(sliced_data.as_ref(), b"klmnopqrst");
6282 let normalized = normalized_offsets.borrow_to_typed_slice::<u64>();
6283 assert_eq!(normalized.as_ref(), &[0, 2, 6, 10]);
6284 }
6285
6286 #[test]
6287 fn test_slice_batch_data_and_rebase_offsets_rejects_invalid_offsets() {
6288 let data = LanceBuffer::copy_slice(b"abcd");
6289 let offsets = LanceBuffer::reinterpret_vec(vec![3_u32, 2_u32]);
6290
6291 let err = VariableFullZipDecoder::slice_batch_data_and_rebase_offsets(&data, &offsets, 32)
6292 .expect_err("offset end before start should error");
6293 assert!(err.to_string().contains("less than base"));
6294 }
6295
6296 #[test]
6297 fn test_schedule_instructions() {
6298 let rep_data: Vec<u64> = vec![5, 2, 3, 0, 4, 7, 2, 0];
6300 let rep_bytes: Vec<u8> = rep_data.iter().flat_map(|v| v.to_le_bytes()).collect();
6301 let repetition_index = MiniBlockRepIndex::decode_from_bytes(&rep_bytes, 2);
6302
6303 let check = |user_ranges, expected_instructions| {
6304 let instructions =
6305 ChunkInstructions::schedule_instructions(&repetition_index, user_ranges);
6306 assert_eq!(instructions, expected_instructions);
6307 };
6308
6309 let expected_take_all = vec![
6311 ChunkInstructions {
6312 chunk_idx: 0,
6313 preamble: PreambleAction::Absent,
6314 rows_to_skip: 0,
6315 rows_to_take: 6,
6316 take_trailer: true,
6317 },
6318 ChunkInstructions {
6319 chunk_idx: 1,
6320 preamble: PreambleAction::Take,
6321 rows_to_skip: 0,
6322 rows_to_take: 2,
6323 take_trailer: false,
6324 },
6325 ChunkInstructions {
6326 chunk_idx: 2,
6327 preamble: PreambleAction::Absent,
6328 rows_to_skip: 0,
6329 rows_to_take: 5,
6330 take_trailer: true,
6331 },
6332 ChunkInstructions {
6333 chunk_idx: 3,
6334 preamble: PreambleAction::Take,
6335 rows_to_skip: 0,
6336 rows_to_take: 1,
6337 take_trailer: false,
6338 },
6339 ];
6340
6341 check(&[0..14], expected_take_all.clone());
6343
6344 check(
6346 &[
6347 0..1,
6348 1..2,
6349 2..3,
6350 3..4,
6351 4..5,
6352 5..6,
6353 6..7,
6354 7..8,
6355 8..9,
6356 9..10,
6357 10..11,
6358 11..12,
6359 12..13,
6360 13..14,
6361 ],
6362 expected_take_all,
6363 );
6364
6365 check(
6369 &[0..1, 3..4],
6370 vec![
6371 ChunkInstructions {
6372 chunk_idx: 0,
6373 preamble: PreambleAction::Absent,
6374 rows_to_skip: 0,
6375 rows_to_take: 1,
6376 take_trailer: false,
6377 },
6378 ChunkInstructions {
6379 chunk_idx: 0,
6380 preamble: PreambleAction::Absent,
6381 rows_to_skip: 3,
6382 rows_to_take: 1,
6383 take_trailer: false,
6384 },
6385 ],
6386 );
6387
6388 check(
6390 &[5..6],
6391 vec![
6392 ChunkInstructions {
6393 chunk_idx: 0,
6394 preamble: PreambleAction::Absent,
6395 rows_to_skip: 5,
6396 rows_to_take: 1,
6397 take_trailer: true,
6398 },
6399 ChunkInstructions {
6400 chunk_idx: 1,
6401 preamble: PreambleAction::Take,
6402 rows_to_skip: 0,
6403 rows_to_take: 0,
6404 take_trailer: false,
6405 },
6406 ],
6407 );
6408
6409 check(
6411 &[7..10],
6412 vec![
6413 ChunkInstructions {
6414 chunk_idx: 1,
6415 preamble: PreambleAction::Skip,
6416 rows_to_skip: 1,
6417 rows_to_take: 1,
6418 take_trailer: false,
6419 },
6420 ChunkInstructions {
6421 chunk_idx: 2,
6422 preamble: PreambleAction::Absent,
6423 rows_to_skip: 0,
6424 rows_to_take: 2,
6425 take_trailer: false,
6426 },
6427 ],
6428 );
6429 }
6430
6431 #[test]
6432 fn test_drain_instructions() {
6433 fn drain_from_instructions(
6434 instructions: &mut VecDeque<ChunkInstructions>,
6435 mut rows_desired: u64,
6436 need_preamble: &mut bool,
6437 skip_in_chunk: &mut u64,
6438 ) -> Vec<ChunkDrainInstructions> {
6439 let mut drain_instructions = Vec::with_capacity(instructions.len());
6441 while rows_desired > 0 || *need_preamble {
6442 let (next_instructions, consumed_chunk) = instructions
6443 .front()
6444 .unwrap()
6445 .drain_from_instruction(&mut rows_desired, need_preamble, skip_in_chunk);
6446 if consumed_chunk {
6447 instructions.pop_front();
6448 }
6449 drain_instructions.push(next_instructions);
6450 }
6451 drain_instructions
6452 }
6453
6454 let rep_data: Vec<u64> = vec![5, 2, 3, 0, 4, 7, 2, 0];
6456 let rep_bytes: Vec<u8> = rep_data.iter().flat_map(|v| v.to_le_bytes()).collect();
6457 let repetition_index = MiniBlockRepIndex::decode_from_bytes(&rep_bytes, 2);
6458 let user_ranges = vec![1..7, 10..14];
6459
6460 let scheduled = ChunkInstructions::schedule_instructions(&repetition_index, &user_ranges);
6462
6463 let mut to_drain = VecDeque::from(scheduled.clone());
6464
6465 let mut need_preamble = false;
6468 let mut skip_in_chunk = 0;
6469
6470 let next_batch =
6471 drain_from_instructions(&mut to_drain, 4, &mut need_preamble, &mut skip_in_chunk);
6472
6473 assert!(!need_preamble);
6474 assert_eq!(skip_in_chunk, 4);
6475 assert_eq!(
6476 next_batch,
6477 vec![ChunkDrainInstructions {
6478 chunk_instructions: scheduled[0].clone(),
6479 rows_to_take: 4,
6480 rows_to_skip: 0,
6481 preamble_action: PreambleAction::Absent,
6482 }]
6483 );
6484
6485 let next_batch =
6486 drain_from_instructions(&mut to_drain, 4, &mut need_preamble, &mut skip_in_chunk);
6487
6488 assert!(!need_preamble);
6489 assert_eq!(skip_in_chunk, 2);
6490
6491 assert_eq!(
6492 next_batch,
6493 vec![
6494 ChunkDrainInstructions {
6495 chunk_instructions: scheduled[0].clone(),
6496 rows_to_take: 1,
6497 rows_to_skip: 4,
6498 preamble_action: PreambleAction::Absent,
6499 },
6500 ChunkDrainInstructions {
6501 chunk_instructions: scheduled[1].clone(),
6502 rows_to_take: 1,
6503 rows_to_skip: 0,
6504 preamble_action: PreambleAction::Take,
6505 },
6506 ChunkDrainInstructions {
6507 chunk_instructions: scheduled[2].clone(),
6508 rows_to_take: 2,
6509 rows_to_skip: 0,
6510 preamble_action: PreambleAction::Absent,
6511 }
6512 ]
6513 );
6514
6515 let next_batch =
6516 drain_from_instructions(&mut to_drain, 2, &mut need_preamble, &mut skip_in_chunk);
6517
6518 assert!(!need_preamble);
6519 assert_eq!(skip_in_chunk, 0);
6520
6521 assert_eq!(
6522 next_batch,
6523 vec![
6524 ChunkDrainInstructions {
6525 chunk_instructions: scheduled[2].clone(),
6526 rows_to_take: 1,
6527 rows_to_skip: 2,
6528 preamble_action: PreambleAction::Absent,
6529 },
6530 ChunkDrainInstructions {
6531 chunk_instructions: scheduled[3].clone(),
6532 rows_to_take: 1,
6533 rows_to_skip: 0,
6534 preamble_action: PreambleAction::Take,
6535 },
6536 ]
6537 );
6538
6539 let rep_data: Vec<u64> = vec![5, 2, 3, 3, 20, 0];
6541 let rep_bytes: Vec<u8> = rep_data.iter().flat_map(|v| v.to_le_bytes()).collect();
6542 let repetition_index = MiniBlockRepIndex::decode_from_bytes(&rep_bytes, 2);
6543 let user_ranges = vec![0..28];
6544
6545 let scheduled = ChunkInstructions::schedule_instructions(&repetition_index, &user_ranges);
6547
6548 let mut to_drain = VecDeque::from(scheduled.clone());
6549
6550 let mut need_preamble = false;
6553 let mut skip_in_chunk = 0;
6554
6555 let next_batch =
6556 drain_from_instructions(&mut to_drain, 7, &mut need_preamble, &mut skip_in_chunk);
6557
6558 assert_eq!(
6559 next_batch,
6560 vec![
6561 ChunkDrainInstructions {
6562 chunk_instructions: scheduled[0].clone(),
6563 rows_to_take: 6,
6564 rows_to_skip: 0,
6565 preamble_action: PreambleAction::Absent,
6566 },
6567 ChunkDrainInstructions {
6568 chunk_instructions: scheduled[1].clone(),
6569 rows_to_take: 1,
6570 rows_to_skip: 0,
6571 preamble_action: PreambleAction::Take,
6572 },
6573 ]
6574 );
6575
6576 assert!(!need_preamble);
6577 assert_eq!(skip_in_chunk, 1);
6578
6579 let next_batch =
6582 drain_from_instructions(&mut to_drain, 2, &mut need_preamble, &mut skip_in_chunk);
6583
6584 assert_eq!(
6585 next_batch,
6586 vec![
6587 ChunkDrainInstructions {
6588 chunk_instructions: scheduled[1].clone(),
6589 rows_to_take: 2,
6590 rows_to_skip: 1,
6591 preamble_action: PreambleAction::Skip,
6592 },
6593 ChunkDrainInstructions {
6594 chunk_instructions: scheduled[2].clone(),
6595 rows_to_take: 0,
6596 rows_to_skip: 0,
6597 preamble_action: PreambleAction::Take,
6598 },
6599 ]
6600 );
6601
6602 assert!(!need_preamble);
6603 assert_eq!(skip_in_chunk, 0);
6604 }
6605
6606 #[tokio::test]
6607 async fn test_fullzip_initialize_is_lazy() {
6608 use futures::{FutureExt, future::BoxFuture};
6609 use std::ops::Range;
6610 use std::sync::Mutex;
6611
6612 #[derive(Debug, Clone)]
6613 struct RecordingScheduler {
6614 data: bytes::Bytes,
6615 requests: Arc<Mutex<Vec<Vec<Range<u64>>>>>,
6616 }
6617
6618 impl RecordingScheduler {
6619 fn new(data: bytes::Bytes) -> Self {
6620 Self {
6621 data,
6622 requests: Arc::new(Mutex::new(Vec::new())),
6623 }
6624 }
6625
6626 fn requests(&self) -> Vec<Vec<Range<u64>>> {
6627 self.requests.lock().unwrap().clone()
6628 }
6629 }
6630
6631 impl crate::EncodingsIo for RecordingScheduler {
6632 fn submit_request(
6633 &self,
6634 ranges: Vec<Range<u64>>,
6635 _priority: u64,
6636 ) -> BoxFuture<'static, crate::Result<Vec<bytes::Bytes>>> {
6637 self.requests.lock().unwrap().push(ranges.clone());
6638 let data = ranges
6639 .into_iter()
6640 .map(|range| self.data.slice(range.start as usize..range.end as usize))
6641 .collect::<Vec<_>>();
6642 std::future::ready(Ok(data)).boxed()
6643 }
6644 }
6645
6646 #[derive(Debug)]
6647 struct TestFixedDecompressor;
6648
6649 impl FixedPerValueDecompressor for TestFixedDecompressor {
6650 fn decompress(
6651 &self,
6652 _data: FixedWidthDataBlock,
6653 _num_rows: u64,
6654 ) -> crate::Result<DataBlock> {
6655 unimplemented!("Test decompressor")
6656 }
6657
6658 fn bits_per_value(&self) -> u64 {
6659 32
6660 }
6661 }
6662
6663 let io = Arc::new(RecordingScheduler::new(bytes::Bytes::from(vec![
6664 0;
6665 16 * 1024
6666 ])));
6667 let mut scheduler = FullZipScheduler {
6668 data_buf_position: 0,
6669 data_buf_size: 4096,
6670 rep_index: Some(FullZipRepIndexDetails {
6671 buf_position: 1000,
6672 bytes_per_value: 4,
6673 }),
6674 priority: 0,
6675 rows_in_page: 100,
6676 bits_per_offset: 32,
6677 details: Arc::new(FullZipDecodeDetails {
6678 value_decompressor: PerValueDecompressor::Fixed(Arc::new(TestFixedDecompressor)),
6679 def_meaning: Arc::new([crate::repdef::DefinitionInterpretation::NullableItem]),
6680 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 1),
6681 max_rep: 0,
6682 max_visible_def: 0,
6683 }),
6684 cached_state: None,
6685 enable_cache: false,
6686 };
6687
6688 let io_dyn: Arc<dyn crate::EncodingsIo> = io.clone();
6689 let cached_data = scheduler.initialize(&io_dyn).await.unwrap();
6690
6691 assert!(
6692 cached_data
6693 .as_arc_any()
6694 .downcast_ref::<super::NoCachedPageData>()
6695 .is_some(),
6696 "FullZip initialize should not eagerly load repetition index data"
6697 );
6698 assert!(scheduler.cached_state.is_none());
6699 assert!(
6700 io.requests().is_empty(),
6701 "FullZip initialize should not issue any I/O"
6702 );
6703 }
6704
6705 #[tokio::test]
6706 async fn test_fullzip_read_source_slices_prefetched_page() {
6707 let page_start = 200_u64;
6708 let page_data = LanceBuffer::copy_slice(&[0, 1, 2, 3, 4, 5, 6, 7]);
6709 let source = FullZipReadSource::PrefetchedPage {
6710 base_offset: page_start,
6711 data: page_data,
6712 };
6713 let ranges = vec![
6714 page_start..(page_start + 3),
6715 (page_start + 4)..(page_start + 8),
6716 ];
6717 let mut data = source.fetch(&ranges, 0).await.unwrap();
6718 assert_eq!(data.pop_front().unwrap().as_ref(), &[0, 1, 2]);
6719 assert_eq!(data.pop_front().unwrap().as_ref(), &[4, 5, 6, 7]);
6720 }
6721
6722 #[tokio::test]
6723 async fn test_fullzip_initialize_caches_rep_index_when_enabled() {
6724 use futures::{FutureExt, future::BoxFuture};
6725 use std::ops::Range;
6726 use std::sync::Mutex;
6727
6728 #[derive(Debug, Clone)]
6729 struct RecordingScheduler {
6730 data: bytes::Bytes,
6731 requests: Arc<Mutex<Vec<Vec<Range<u64>>>>>,
6732 }
6733
6734 impl RecordingScheduler {
6735 fn new(data: bytes::Bytes) -> Self {
6736 Self {
6737 data,
6738 requests: Arc::new(Mutex::new(Vec::new())),
6739 }
6740 }
6741
6742 fn requests(&self) -> Vec<Vec<Range<u64>>> {
6743 self.requests.lock().unwrap().clone()
6744 }
6745 }
6746
6747 impl crate::EncodingsIo for RecordingScheduler {
6748 fn submit_request(
6749 &self,
6750 ranges: Vec<Range<u64>>,
6751 _priority: u64,
6752 ) -> BoxFuture<'static, crate::Result<Vec<bytes::Bytes>>> {
6753 self.requests.lock().unwrap().push(ranges.clone());
6754 let data = ranges
6755 .into_iter()
6756 .map(|range| self.data.slice(range.start as usize..range.end as usize))
6757 .collect::<Vec<_>>();
6758 std::future::ready(Ok(data)).boxed()
6759 }
6760 }
6761
6762 #[derive(Debug)]
6763 struct TestFixedDecompressor;
6764
6765 impl FixedPerValueDecompressor for TestFixedDecompressor {
6766 fn decompress(
6767 &self,
6768 _data: FixedWidthDataBlock,
6769 _num_rows: u64,
6770 ) -> crate::Result<DataBlock> {
6771 unimplemented!("Test decompressor")
6772 }
6773
6774 fn bits_per_value(&self) -> u64 {
6775 32
6776 }
6777 }
6778
6779 let rows_in_page = 100_u64;
6780 let bytes_per_value = 4_u64;
6781 let rep_start = 1000_u64;
6782 let rep_size = ((rows_in_page + 1) * bytes_per_value) as usize;
6783 let mut data = vec![0_u8; 16 * 1024];
6784 data[rep_start as usize..rep_start as usize + rep_size].fill(7);
6785 let io = Arc::new(RecordingScheduler::new(bytes::Bytes::from(data)));
6786
6787 let mut scheduler = FullZipScheduler {
6788 data_buf_position: 0,
6789 data_buf_size: 4096,
6790 rep_index: Some(FullZipRepIndexDetails {
6791 buf_position: rep_start,
6792 bytes_per_value,
6793 }),
6794 priority: 0,
6795 rows_in_page,
6796 bits_per_offset: 32,
6797 details: Arc::new(FullZipDecodeDetails {
6798 value_decompressor: PerValueDecompressor::Fixed(Arc::new(TestFixedDecompressor)),
6799 def_meaning: Arc::new([crate::repdef::DefinitionInterpretation::NullableItem]),
6800 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 1),
6801 max_rep: 0,
6802 max_visible_def: 0,
6803 }),
6804 cached_state: None,
6805 enable_cache: true,
6806 };
6807
6808 let io_dyn: Arc<dyn crate::EncodingsIo> = io.clone();
6809 let cached_data = scheduler.initialize(&io_dyn).await.unwrap();
6810 assert!(
6811 cached_data
6812 .as_arc_any()
6813 .downcast_ref::<FullZipCacheableState>()
6814 .is_some()
6815 );
6816 assert!(scheduler.cached_state.is_some());
6817 assert_eq!(
6818 io.requests(),
6819 vec![vec![
6820 rep_start..(rep_start + (rows_in_page + 1) * bytes_per_value)
6821 ]]
6822 );
6823 }
6824
6825 #[tokio::test]
6826 async fn test_fullzip_full_page_bypasses_rep_index_io() {
6827 use futures::{FutureExt, future::BoxFuture};
6828 use std::ops::Range;
6829 use std::sync::Mutex;
6830
6831 #[derive(Debug, Clone)]
6832 struct RecordingScheduler {
6833 data: bytes::Bytes,
6834 requests: Arc<Mutex<Vec<Vec<Range<u64>>>>>,
6835 }
6836
6837 impl RecordingScheduler {
6838 fn new(data: bytes::Bytes) -> Self {
6839 Self {
6840 data,
6841 requests: Arc::new(Mutex::new(Vec::new())),
6842 }
6843 }
6844
6845 fn requests(&self) -> Vec<Vec<Range<u64>>> {
6846 self.requests.lock().unwrap().clone()
6847 }
6848 }
6849
6850 impl crate::EncodingsIo for RecordingScheduler {
6851 fn submit_request(
6852 &self,
6853 ranges: Vec<Range<u64>>,
6854 _priority: u64,
6855 ) -> BoxFuture<'static, crate::Result<Vec<bytes::Bytes>>> {
6856 self.requests.lock().unwrap().push(ranges.clone());
6857 let data = ranges
6858 .into_iter()
6859 .map(|range| self.data.slice(range.start as usize..range.end as usize))
6860 .collect::<Vec<_>>();
6861 std::future::ready(Ok(data)).boxed()
6862 }
6863 }
6864
6865 #[derive(Debug)]
6866 struct TestFixedDecompressor;
6867
6868 impl FixedPerValueDecompressor for TestFixedDecompressor {
6869 fn decompress(
6870 &self,
6871 _data: FixedWidthDataBlock,
6872 _num_rows: u64,
6873 ) -> crate::Result<DataBlock> {
6874 unimplemented!("Test decompressor")
6875 }
6876
6877 fn bits_per_value(&self) -> u64 {
6878 32
6879 }
6880 }
6881
6882 let rows_in_page = 100_u64;
6883 let data_start = 256_u64;
6884 let data_size = 500_u64;
6885 let rep_start = 4096_u64;
6886 let bytes_per_value = 4_u64;
6887
6888 let mut bytes = vec![0_u8; 16 * 1024];
6889 for i in 0..=rows_in_page {
6890 let offset = (i * 5) as u32;
6891 let pos = rep_start as usize + (i * bytes_per_value) as usize;
6892 bytes[pos..pos + 4].copy_from_slice(&offset.to_le_bytes());
6893 }
6894 let io = Arc::new(RecordingScheduler::new(bytes::Bytes::from(bytes)));
6895
6896 let scheduler = FullZipScheduler {
6897 data_buf_position: data_start,
6898 data_buf_size: data_size,
6899 rep_index: Some(FullZipRepIndexDetails {
6900 buf_position: rep_start,
6901 bytes_per_value,
6902 }),
6903 priority: 0,
6904 rows_in_page,
6905 bits_per_offset: 32,
6906 details: Arc::new(FullZipDecodeDetails {
6907 value_decompressor: PerValueDecompressor::Fixed(Arc::new(TestFixedDecompressor)),
6908 def_meaning: Arc::new([crate::repdef::DefinitionInterpretation::NullableItem]),
6909 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 1),
6910 max_rep: 0,
6911 max_visible_def: 0,
6912 }),
6913 cached_state: None,
6914 enable_cache: false,
6915 };
6916
6917 let io_dyn: Arc<dyn crate::EncodingsIo> = io.clone();
6918 let tasks = scheduler
6919 .schedule_ranges_rep(
6920 &[0..rows_in_page],
6921 &io_dyn,
6922 FullZipRepIndexDetails {
6923 buf_position: rep_start,
6924 bytes_per_value,
6925 },
6926 )
6927 .unwrap();
6928
6929 let requests = io.requests();
6930 assert_eq!(requests.len(), 1);
6931 assert_eq!(requests[0], vec![data_start..(data_start + data_size)]);
6932
6933 let _ = tasks.into_iter().next().unwrap().decoder_fut.await.unwrap();
6934 let requests_after_await = io.requests();
6935 assert_eq!(
6936 requests_after_await.len(),
6937 1,
6938 "full page path should not issue rep-index I/O"
6939 );
6940 }
6941
6942 #[tokio::test]
6944 async fn test_fuzz_issue_4492_empty_rep_values() {
6945 use lance_datagen::{RowCount, Seed, array, gen_batch};
6946
6947 let seed = 1823859942947654717u64;
6948 let num_rows = 2741usize;
6949
6950 let batch_gen = gen_batch().with_seed(Seed::from(seed));
6952 let base_generator = array::rand_type(&DataType::FixedSizeBinary(32));
6953 let list_generator = array::rand_list_any(base_generator, false);
6954
6955 let batch = batch_gen
6956 .anon_col(list_generator)
6957 .into_batch_rows(RowCount::from(num_rows as u64))
6958 .unwrap();
6959
6960 let list_array = batch.column(0).clone();
6961
6962 let mut metadata = HashMap::new();
6964 metadata.insert(
6965 STRUCTURAL_ENCODING_META_KEY.to_string(),
6966 STRUCTURAL_ENCODING_MINIBLOCK.to_string(),
6967 );
6968
6969 let test_cases = TestCases::default()
6970 .with_min_file_version(LanceFileVersion::V2_1)
6971 .with_batch_size(100)
6972 .with_range(0..num_rows.min(500) as u64)
6973 .with_indices(vec![0, num_rows as u64 / 2, (num_rows - 1) as u64]);
6974
6975 check_round_trip_encoding_of_data(vec![list_array], &test_cases, metadata).await
6976 }
6977
6978 async fn test_minichunk_size_helper(
6979 string_data: Vec<Option<String>>,
6980 minichunk_size: u64,
6981 file_version: LanceFileVersion,
6982 ) {
6983 use crate::constants::MINICHUNK_SIZE_META_KEY;
6984 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
6985 use arrow_array::{ArrayRef, StringArray};
6986 use std::sync::Arc;
6987
6988 let string_array: ArrayRef = Arc::new(StringArray::from(string_data));
6989
6990 let mut metadata = HashMap::new();
6991 metadata.insert(
6992 MINICHUNK_SIZE_META_KEY.to_string(),
6993 minichunk_size.to_string(),
6994 );
6995 metadata.insert(
6996 STRUCTURAL_ENCODING_META_KEY.to_string(),
6997 STRUCTURAL_ENCODING_MINIBLOCK.to_string(),
6998 );
6999
7000 let test_cases = TestCases::default()
7001 .with_min_file_version(file_version)
7002 .with_batch_size(1000);
7003
7004 check_round_trip_encoding_of_data(vec![string_array], &test_cases, metadata).await;
7005 }
7006
7007 #[tokio::test]
7008 async fn test_minichunk_size_roundtrip() {
7009 let mut string_data = Vec::new();
7011 for i in 0..100 {
7012 string_data.push(Some(format!("test_string_{}", i).repeat(50)));
7013 }
7014 test_minichunk_size_helper(string_data, 64, LanceFileVersion::V2_1).await;
7016 }
7017
7018 #[tokio::test]
7019 async fn test_minichunk_size_128kb_v2_2() {
7020 let mut string_data = Vec::new();
7022 for i in 0..10000 {
7024 string_data.push(Some(format!("test_string_{}", i).repeat(50)));
7025 }
7026 test_minichunk_size_helper(string_data, 128 * 1024, LanceFileVersion::V2_2).await;
7027 }
7028
7029 #[tokio::test]
7030 async fn test_binary_large_minichunk_size_over_max_miniblock_values() {
7031 let mut string_data = Vec::new();
7032 for i in 0..10000 {
7034 string_data.push(Some(format!("t_{}", i)));
7035 }
7036 test_minichunk_size_helper(string_data, 128 * 1024, LanceFileVersion::V2_2).await;
7037 }
7038
7039 #[tokio::test]
7040 async fn test_large_dictionary_general_compression() {
7041 use arrow_array::{ArrayRef, StringArray};
7042 use std::collections::HashMap;
7043 use std::sync::Arc;
7044
7045 let unique_values: Vec<String> = (0..100)
7048 .map(|i| format!("value_{:04}_{}", i, "x".repeat(500)))
7049 .collect();
7050
7051 let repeated_strings: Vec<_> = unique_values
7053 .iter()
7054 .cycle()
7055 .take(100_000)
7056 .map(|s| Some(s.as_str()))
7057 .collect();
7058
7059 let string_array = Arc::new(StringArray::from(repeated_strings)) as ArrayRef;
7060
7061 let test_cases = TestCases::default()
7063 .with_min_file_version(LanceFileVersion::V2_2)
7064 .with_verify_encoding(Arc::new(|cols: &[crate::encoder::EncodedColumn], _| {
7065 assert_eq!(cols.len(), 1);
7066 let col = &cols[0];
7067
7068 if let Some(PageEncoding::Structural(page_layout)) =
7070 &col.final_pages.first().map(|p| &p.description)
7071 && let Some(pb21::page_layout::Layout::MiniBlockLayout(mini_block)) =
7072 &page_layout.layout
7073 && let Some(dictionary_encoding) = &mini_block.dictionary
7074 {
7075 match dictionary_encoding.compression.as_ref() {
7076 Some(Compression::General(general)) => {
7077 let compression = general.compression.as_ref().unwrap();
7079 assert!(
7080 compression.scheme()
7081 == pb21::CompressionScheme::CompressionAlgorithmLz4
7082 || compression.scheme()
7083 == pb21::CompressionScheme::CompressionAlgorithmZstd,
7084 "Expected LZ4 or Zstd compression for large dictionary"
7085 );
7086 }
7087 _ => panic!("Expected General compression for large dictionary"),
7088 }
7089 }
7090 }));
7091
7092 check_round_trip_encoding_of_data(vec![string_array], &test_cases, HashMap::new()).await;
7093 }
7094
7095 fn dictionary_encoding_from_page(
7096 page: &crate::encoder::EncodedPage,
7097 ) -> &crate::format::pb21::CompressiveEncoding {
7098 let PageEncoding::Structural(layout) = &page.description else {
7099 panic!("Expected structural page encoding");
7100 };
7101 let pb21::page_layout::Layout::MiniBlockLayout(layout) = layout.layout.as_ref().unwrap()
7102 else {
7103 panic!("Expected mini-block layout");
7104 };
7105 layout
7106 .dictionary
7107 .as_ref()
7108 .unwrap_or_else(|| panic!("Expected dictionary encoding"))
7109 }
7110
7111 async fn encode_variable_dict_page(
7112 metadata: HashMap<String, String>,
7113 ) -> crate::encoder::EncodedPage {
7114 use arrow_array::types::Int32Type;
7115 use arrow_array::{ArrayRef, DictionaryArray, Int32Array, StringArray};
7116
7117 let values = Arc::new(StringArray::from(
7118 (0..128)
7119 .map(|i| format!("value_{i:04}_{}", "x".repeat(256)))
7120 .collect::<Vec<_>>(),
7121 )) as ArrayRef;
7122 let keys = Int32Array::from_iter_values((0..20_000).map(|i| i % 128));
7123 let dict_array =
7124 Arc::new(DictionaryArray::<Int32Type>::try_new(keys, values).unwrap()) as ArrayRef;
7125
7126 let field = arrow_schema::Field::new(
7127 "dict_col",
7128 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
7129 false,
7130 )
7131 .with_metadata(metadata);
7132
7133 encode_first_page(field, dict_array, LanceFileVersion::V2_2).await
7134 }
7135
7136 async fn encode_auto_fixed_dict_page(
7137 metadata: HashMap<String, String>,
7138 ) -> crate::encoder::EncodedPage {
7139 use arrow_array::{ArrayRef, Decimal128Array};
7140
7141 let values = (0..20_000)
7143 .map(|i| match i % 3 {
7144 0 => 10_i128,
7145 1 => 20_i128,
7146 _ => 30_i128,
7147 })
7148 .collect::<Vec<_>>();
7149 let decimal = Decimal128Array::from_iter_values(values)
7150 .with_precision_and_scale(38, 0)
7151 .unwrap();
7152 let decimal = Arc::new(decimal) as ArrayRef;
7153
7154 let mut field_metadata = metadata;
7155 field_metadata.insert(
7157 "lance-encoding:dict-size-ratio".to_string(),
7158 "0.99".to_string(),
7159 );
7160 let field = arrow_schema::Field::new("fixed_col", DataType::Decimal128(38, 0), false)
7161 .with_metadata(field_metadata);
7162
7163 encode_first_page(field, decimal, LanceFileVersion::V2_2).await
7164 }
7165
7166 #[tokio::test]
7167 async fn test_dict_values_general_compression_default_lz4_for_variable_dict_values() {
7168 let page = encode_variable_dict_page(HashMap::new()).await;
7169 let dictionary_encoding = dictionary_encoding_from_page(&page);
7170 let Some(Compression::General(general)) = dictionary_encoding.compression.as_ref() else {
7171 panic!("Expected General compression for dictionary values");
7172 };
7173 let compression = general.compression.as_ref().unwrap();
7174 assert_eq!(
7175 compression.scheme(),
7176 pb21::CompressionScheme::CompressionAlgorithmLz4
7177 );
7178 }
7179
7180 #[tokio::test]
7181 async fn test_dict_values_general_compression_default_lz4_for_fixed_dict_values() {
7182 let page = encode_auto_fixed_dict_page(HashMap::new()).await;
7183 let dictionary_encoding = dictionary_encoding_from_page(&page);
7184 let Some(Compression::General(general)) = dictionary_encoding.compression.as_ref() else {
7185 panic!("Expected General compression for dictionary values");
7186 };
7187 let compression = general.compression.as_ref().unwrap();
7188 assert_eq!(
7189 compression.scheme(),
7190 pb21::CompressionScheme::CompressionAlgorithmLz4
7191 );
7192 }
7193
7194 #[tokio::test]
7195 async fn test_dict_values_general_compression_zstd() {
7196 let mut metadata = HashMap::new();
7197 metadata.insert(
7198 DICT_VALUES_COMPRESSION_META_KEY.to_string(),
7199 "zstd".to_string(),
7200 );
7201 let page = encode_variable_dict_page(metadata).await;
7202 let dictionary_encoding = dictionary_encoding_from_page(&page);
7203 let Some(Compression::General(general)) = dictionary_encoding.compression.as_ref() else {
7204 panic!("Expected General compression for dictionary values");
7205 };
7206 let compression = general.compression.as_ref().unwrap();
7207 assert_eq!(
7208 compression.scheme(),
7209 pb21::CompressionScheme::CompressionAlgorithmZstd
7210 );
7211 }
7212
7213 #[tokio::test]
7214 async fn test_dict_values_general_compression_none() {
7215 let mut metadata = HashMap::new();
7216 metadata.insert(
7217 DICT_VALUES_COMPRESSION_META_KEY.to_string(),
7218 "none".to_string(),
7219 );
7220 let page = encode_variable_dict_page(metadata).await;
7221 let dictionary_encoding = dictionary_encoding_from_page(&page);
7222 assert!(
7223 !matches!(
7224 dictionary_encoding.compression.as_ref(),
7225 Some(Compression::General(_))
7226 ),
7227 "Expected dictionary values to avoid General compression"
7228 );
7229 }
7230
7231 #[test]
7232 fn test_resolve_dict_values_compression_metadata_defaults_to_lz4() {
7233 let metadata = PrimitiveStructuralEncoder::resolve_dict_values_compression_metadata(
7234 &HashMap::new(),
7235 None,
7236 None,
7237 );
7238 assert_eq!(metadata.get(COMPRESSION_META_KEY), Some(&"lz4".to_string()),);
7239 assert!(!metadata.contains_key(COMPRESSION_LEVEL_META_KEY));
7240 }
7241
7242 #[test]
7243 fn test_resolve_dict_values_compression_metadata_metadata_overrides_env() {
7244 let field_metadata = HashMap::from([
7245 (
7246 DICT_VALUES_COMPRESSION_META_KEY.to_string(),
7247 "none".to_string(),
7248 ),
7249 (
7250 DICT_VALUES_COMPRESSION_LEVEL_META_KEY.to_string(),
7251 "7".to_string(),
7252 ),
7253 ]);
7254 let metadata = PrimitiveStructuralEncoder::resolve_dict_values_compression_metadata(
7255 &field_metadata,
7256 Some("zstd".to_string()),
7257 Some("3".to_string()),
7258 );
7259 assert_eq!(
7260 metadata.get(COMPRESSION_META_KEY),
7261 Some(&"none".to_string()),
7262 );
7263 assert_eq!(
7264 metadata.get(COMPRESSION_LEVEL_META_KEY),
7265 Some(&"7".to_string()),
7266 );
7267 }
7268
7269 #[test]
7270 fn test_resolve_dict_values_compression_metadata_env_fallback() {
7271 let metadata = PrimitiveStructuralEncoder::resolve_dict_values_compression_metadata(
7272 &HashMap::new(),
7273 Some("zstd".to_string()),
7274 Some("9".to_string()),
7275 );
7276 assert_eq!(
7277 metadata.get(COMPRESSION_META_KEY),
7278 Some(&"zstd".to_string()),
7279 );
7280 assert_eq!(
7281 metadata.get(COMPRESSION_LEVEL_META_KEY),
7282 Some(&"9".to_string()),
7283 );
7284 }
7285
7286 #[tokio::test]
7287 async fn test_dictionary_encode_int64() {
7288 use crate::constants::{DICT_SIZE_RATIO_META_KEY, STRUCTURAL_ENCODING_META_KEY};
7289 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
7290 use crate::version::LanceFileVersion;
7291 use arrow_array::{ArrayRef, Int64Array};
7292 use std::collections::HashMap;
7293 use std::sync::Arc;
7294
7295 let values = (0..1000)
7297 .map(|i| match i % 3 {
7298 0 => 10i64,
7299 1 => 20i64,
7300 _ => 30i64,
7301 })
7302 .collect::<Vec<_>>();
7303 let array = Arc::new(Int64Array::from(values)) as ArrayRef;
7304
7305 let mut metadata = HashMap::new();
7306 metadata.insert(
7307 STRUCTURAL_ENCODING_META_KEY.to_string(),
7308 STRUCTURAL_ENCODING_MINIBLOCK.to_string(),
7309 );
7310 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.99".to_string());
7311
7312 let test_cases = TestCases::default()
7313 .with_min_file_version(LanceFileVersion::V2_2)
7314 .with_batch_size(1000)
7315 .with_range(0..1000)
7316 .with_indices(vec![0, 1, 10, 999])
7317 .with_expected_encoding("dictionary");
7318
7319 check_round_trip_encoding_of_data(vec![array], &test_cases, metadata).await;
7320 }
7321
7322 #[tokio::test]
7323 async fn test_dictionary_encode_float64() {
7324 use crate::constants::{DICT_SIZE_RATIO_META_KEY, STRUCTURAL_ENCODING_META_KEY};
7325 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
7326 use crate::version::LanceFileVersion;
7327 use arrow_array::{ArrayRef, Float64Array};
7328 use std::collections::HashMap;
7329 use std::sync::Arc;
7330
7331 let values = (0..1000)
7333 .map(|i| match i % 3 {
7334 0 => 0.1f64,
7335 1 => 0.2f64,
7336 _ => 0.3f64,
7337 })
7338 .collect::<Vec<_>>();
7339 let array = Arc::new(Float64Array::from(values)) as ArrayRef;
7340
7341 let mut metadata = HashMap::new();
7342 metadata.insert(
7343 STRUCTURAL_ENCODING_META_KEY.to_string(),
7344 STRUCTURAL_ENCODING_MINIBLOCK.to_string(),
7345 );
7346 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.99".to_string());
7347
7348 let test_cases = TestCases::default()
7349 .with_min_file_version(LanceFileVersion::V2_2)
7350 .with_batch_size(1000)
7351 .with_range(0..1000)
7352 .with_indices(vec![0, 1, 10, 999])
7353 .with_expected_encoding("dictionary");
7354
7355 check_round_trip_encoding_of_data(vec![array], &test_cases, metadata).await;
7356 }
7357
7358 #[test]
7359 fn test_miniblock_dictionary_out_of_line_bitpacking_decode() {
7360 let rows = 10_000;
7361 let unique_values = 2_000;
7362
7363 let dictionary_encoding =
7364 ProtobufUtils21::out_of_line_bitpacking(64, ProtobufUtils21::flat(11, None));
7365 let layout = pb21::MiniBlockLayout {
7366 rep_compression: None,
7367 def_compression: None,
7368 value_compression: Some(ProtobufUtils21::flat(64, None)),
7369 dictionary: Some(dictionary_encoding),
7370 num_dictionary_items: unique_values,
7371 layers: vec![pb21::RepDefLayer::RepdefAllValidItem as i32],
7372 num_buffers: 1,
7373 repetition_index_depth: 0,
7374 num_items: rows,
7375 has_large_chunk: false,
7376 };
7377
7378 let buffer_offsets_and_sizes = vec![(0, 0), (0, 0), (0, 0)];
7379 let scheduler = super::MiniBlockScheduler::try_new(
7380 &buffer_offsets_and_sizes,
7381 0,
7382 rows,
7383 &layout,
7384 &DefaultDecompressionStrategy::default(),
7385 )
7386 .unwrap();
7387
7388 let dictionary = scheduler.dictionary.unwrap();
7389 assert_eq!(dictionary.num_dictionary_items, unique_values);
7390 assert_eq!(
7391 dictionary.dictionary_data_alignment,
7392 crate::encoder::MIN_PAGE_BUFFER_ALIGNMENT
7393 );
7394 }
7395
7396 fn create_test_fixed_data_block(
7398 num_values: u64,
7399 cardinality: u64,
7400 bits_per_value: u64,
7401 ) -> DataBlock {
7402 assert!(cardinality > 0);
7403 assert!(cardinality <= num_values);
7404 let block_info = BlockInfo::default();
7405
7406 assert_eq!(bits_per_value % 8, 0);
7407 let data = match bits_per_value {
7408 32 => {
7409 let values = (0..num_values)
7410 .map(|i| (i % cardinality) as u32)
7411 .collect::<Vec<_>>();
7412 crate::buffer::LanceBuffer::reinterpret_vec(values)
7413 }
7414 64 => {
7415 let values = (0..num_values).map(|i| i % cardinality).collect::<Vec<_>>();
7416 crate::buffer::LanceBuffer::reinterpret_vec(values)
7417 }
7418 128 => {
7419 let values = (0..num_values)
7420 .map(|i| (i % cardinality) as u128)
7421 .collect::<Vec<_>>();
7422 crate::buffer::LanceBuffer::reinterpret_vec(values)
7423 }
7424 _ => unreachable!(),
7425 };
7426 DataBlock::FixedWidth(FixedWidthDataBlock {
7427 bits_per_value,
7428 data,
7429 num_values,
7430 block_info,
7431 })
7432 }
7433
7434 fn create_test_variable_width_block(num_values: u64, cardinality: u64) -> DataBlock {
7436 use arrow_array::StringArray;
7437
7438 assert!(cardinality <= num_values && cardinality > 0);
7439
7440 let mut values = Vec::with_capacity(num_values as usize);
7441 for i in 0..num_values {
7442 values.push(format!("value_{:016}", i % cardinality));
7443 }
7444
7445 let array = StringArray::from(values);
7446 DataBlock::from_array(Arc::new(array) as ArrayRef)
7447 }
7448
7449 fn create_sorted_string_array(num_values: u64, cardinality: u64) -> ArrayRef {
7450 use arrow_array::StringArray;
7451
7452 assert!(cardinality <= num_values && cardinality > 0);
7453
7454 let mut values = Vec::with_capacity(num_values as usize);
7455 for i in 0..num_values {
7456 let value_idx = i * cardinality / num_values;
7457 values.push(format!("value_{:016}", value_idx));
7458 }
7459
7460 Arc::new(StringArray::from(values)) as ArrayRef
7461 }
7462
7463 fn create_sorted_variable_width_block(num_values: u64, cardinality: u64) -> DataBlock {
7464 DataBlock::from_array(create_sorted_string_array(num_values, cardinality))
7465 }
7466
7467 #[test]
7468 fn test_should_dictionary_encode() {
7469 use crate::constants::DICT_SIZE_RATIO_META_KEY;
7470 use lance_core::datatypes::Field as LanceField;
7471
7472 let block = create_test_variable_width_block(1000, 10);
7474
7475 let mut metadata = HashMap::new();
7476 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
7477 let arrow_field =
7478 arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
7479 let field = LanceField::try_from(&arrow_field).unwrap();
7480
7481 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
7482 &block,
7483 &field,
7484 LanceFileVersion::V2_1,
7485 );
7486
7487 assert!(
7488 result.is_some(),
7489 "Should use dictionary encode based on size"
7490 );
7491 }
7492
7493 #[test]
7494 fn test_block_sampling_detects_low_cardinality_in_short_sorted_runs() {
7495 let sample_count: usize = 4096;
7496 let num_values: u64 = 200_000;
7497 let cardinality: u64 = 8_000;
7498 let run_length = num_values / cardinality;
7499 let stride = num_values as usize / sample_count;
7500 assert!(
7501 stride > run_length as usize,
7502 "test must construct the stride > run_length case"
7503 );
7504
7505 let block = create_sorted_variable_width_block(num_values, cardinality);
7506 let sample_unique_ratio =
7507 PrimitiveStructuralEncoder::sample_unique_ratio(&block, sample_count).unwrap();
7508
7509 assert!(
7510 sample_unique_ratio.is_some_and(|ratio| ratio < 0.98),
7511 "sorted low-cardinality data must not be classified as near-unique"
7512 );
7513 }
7514
7515 #[test]
7516 fn test_should_dictionary_encode_sorted_low_cardinality() {
7517 use crate::constants::DICT_SIZE_RATIO_META_KEY;
7518 use lance_core::datatypes::Field as LanceField;
7519
7520 let block = create_sorted_variable_width_block(200_000, 8_000);
7521
7522 let mut metadata = HashMap::new();
7523 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
7524 let arrow_field =
7525 arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
7526 let field = LanceField::try_from(&arrow_field).unwrap();
7527
7528 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
7529 &block,
7530 &field,
7531 LanceFileVersion::V2_2,
7532 );
7533
7534 assert!(
7535 result.is_some(),
7536 "sorted low-cardinality data should reach dictionary encoding"
7537 );
7538 }
7539
7540 #[test]
7541 fn test_should_not_dictionary_encode_sorted_high_cardinality_short_runs() {
7542 use crate::constants::DICT_SIZE_RATIO_META_KEY;
7543 use lance_core::datatypes::Field as LanceField;
7544
7545 let num_values = 200_002;
7546 let cardinality = 100_001;
7547 let block = create_sorted_variable_width_block(num_values, cardinality);
7548
7549 let mut metadata = HashMap::new();
7550 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
7551 let arrow_field =
7552 arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
7553 let field = LanceField::try_from(&arrow_field).unwrap();
7554
7555 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
7556 &block,
7557 &field,
7558 LanceFileVersion::V2_2,
7559 );
7560
7561 assert!(
7562 result.is_none(),
7563 "sorted high-cardinality short runs should not trigger a full dictionary probe"
7564 );
7565 }
7566
7567 #[tokio::test]
7568 async fn test_encode_sorted_low_cardinality_uses_dictionary_layout() {
7569 use crate::constants::DICT_SIZE_RATIO_META_KEY;
7570
7571 let mut metadata = HashMap::new();
7572 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
7573 let field = arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
7574 let array = create_sorted_string_array(200_000, 8_000);
7575
7576 let page = encode_first_page(field, array, LanceFileVersion::V2_2).await;
7577 let _ = dictionary_encoding_from_page(&page);
7578 }
7579
7580 #[test]
7581 fn test_should_not_dictionary_encode_unsupported_bits() {
7582 use crate::constants::DICT_SIZE_RATIO_META_KEY;
7583 use lance_core::datatypes::Field as LanceField;
7584
7585 let block = create_test_fixed_data_block(1000, 1000, 32);
7586
7587 let mut metadata = HashMap::new();
7588 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
7589 let arrow_field =
7590 arrow_schema::Field::new("test", DataType::Int32, false).with_metadata(metadata);
7591 let field = LanceField::try_from(&arrow_field).unwrap();
7592
7593 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
7594 &block,
7595 &field,
7596 LanceFileVersion::V2_1,
7597 );
7598
7599 assert!(
7600 result.is_none(),
7601 "Should not use dictionary encode for unsupported bit width"
7602 );
7603 }
7604
7605 #[test]
7606 fn test_should_not_dictionary_encode_near_unique_sample() {
7607 use crate::constants::DICT_SIZE_RATIO_META_KEY;
7608 use lance_core::datatypes::Field as LanceField;
7609
7610 let num_values = 5000;
7611 let block = create_test_variable_width_block(num_values, num_values);
7612
7613 let mut metadata = HashMap::new();
7614 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "1.0".to_string());
7615 let arrow_field =
7616 arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
7617 let field = LanceField::try_from(&arrow_field).unwrap();
7618
7619 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
7620 &block,
7621 &field,
7622 LanceFileVersion::V2_1,
7623 );
7624
7625 assert!(
7626 result.is_none(),
7627 "Should not probe dictionary encoding for near-unique data"
7628 );
7629 }
7630
7631 #[test]
7632 fn test_v2_1_miniblock_serializes_log_num_values_15() {
7633 let miniblocks = MiniBlockCompressed {
7634 data: vec![LanceBuffer::from(vec![1_u8; 16])],
7635 chunks: vec![
7636 MiniBlockChunk {
7637 buffer_sizes: vec![8],
7638 log_num_values: 15,
7639 },
7640 MiniBlockChunk {
7641 buffer_sizes: vec![8],
7642 log_num_values: 0,
7643 },
7644 ],
7645 num_values: 32_769,
7646 };
7647
7648 let serialized =
7649 PrimitiveStructuralEncoder::serialize_miniblocks(miniblocks, None, None, false)
7650 .unwrap();
7651
7652 let chunk_metadata = serialized.metadata.borrow_to_typed_slice::<u16>();
7653 assert_eq!(chunk_metadata.len(), 2);
7654 assert_eq!(
7655 chunk_metadata[0] & 0x0F,
7656 15,
7657 "V2.1 metadata should use all 4 bits for log_num_values"
7658 );
7659 }
7660
7661 async fn encode_first_page(
7662 field: arrow_schema::Field,
7663 array: ArrayRef,
7664 version: LanceFileVersion,
7665 ) -> crate::encoder::EncodedPage {
7666 use crate::encoder::{
7667 ColumnIndexSequence, EncodingOptions, MIN_PAGE_BUFFER_ALIGNMENT, OutOfLineBuffers,
7668 default_encoding_strategy,
7669 };
7670 use crate::repdef::RepDefBuilder;
7671
7672 let lance_field = lance_core::datatypes::Field::try_from(&field).unwrap();
7673 let encoding_strategy = default_encoding_strategy(version);
7674 let mut column_index_seq = ColumnIndexSequence::default();
7675 let encoding_options = EncodingOptions {
7676 cache_bytes_per_column: 1,
7677 max_page_bytes: 32 * 1024 * 1024,
7678 keep_original_array: true,
7679 buffer_alignment: MIN_PAGE_BUFFER_ALIGNMENT,
7680 version,
7681 };
7682
7683 let mut encoder = encoding_strategy
7684 .create_field_encoder(
7685 encoding_strategy.as_ref(),
7686 &lance_field,
7687 &mut column_index_seq,
7688 &encoding_options,
7689 )
7690 .unwrap();
7691
7692 let mut external_buffers = OutOfLineBuffers::new(0, MIN_PAGE_BUFFER_ALIGNMENT);
7693 let repdef = RepDefBuilder::default();
7694 let num_rows = array.len() as u64;
7695 let mut pages = Vec::new();
7696 for task in encoder
7697 .maybe_encode(array, &mut external_buffers, repdef, 0, num_rows)
7698 .unwrap()
7699 {
7700 pages.push(task.await.unwrap());
7701 }
7702 for task in encoder.flush(&mut external_buffers).unwrap() {
7703 pages.push(task.await.unwrap());
7704 }
7705 pages.into_iter().next().unwrap()
7706 }
7707
7708 #[tokio::test]
7709 async fn test_constant_layout_out_of_line_fixed_size_binary_v2_2() {
7710 use crate::format::pb21::page_layout::Layout;
7711
7712 let val = vec![0xABu8; 33];
7713 let arr: ArrayRef = Arc::new(
7714 arrow_array::FixedSizeBinaryArray::try_from_sparse_iter_with_size(
7715 std::iter::repeat_n(Some(val.as_slice()), 256),
7716 33,
7717 )
7718 .unwrap(),
7719 );
7720 let field = arrow_schema::Field::new("c", DataType::FixedSizeBinary(33), true);
7721 let page = encode_first_page(field, arr.clone(), LanceFileVersion::V2_2).await;
7722
7723 let PageEncoding::Structural(layout) = &page.description else {
7724 panic!("Expected structural encoding");
7725 };
7726 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
7727 panic!("Expected constant layout in slot 2");
7728 };
7729 assert!(layout.inline_value.is_none());
7730 assert_eq!(page.data.len(), 1);
7731
7732 let test_cases = TestCases::default()
7733 .with_min_file_version(LanceFileVersion::V2_2)
7734 .with_max_file_version(LanceFileVersion::V2_2)
7735 .with_page_sizes(vec![4096]);
7736 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
7737 }
7738
7739 #[tokio::test]
7740 async fn test_constant_layout_out_of_line_utf8_v2_2() {
7741 use crate::format::pb21::page_layout::Layout;
7742
7743 let arr: ArrayRef = Arc::new(arrow_array::StringArray::from_iter_values(
7744 std::iter::repeat_n("hello", 512),
7745 ));
7746 let field = arrow_schema::Field::new("c", DataType::Utf8, true);
7747 let page = encode_first_page(field, arr.clone(), LanceFileVersion::V2_2).await;
7748
7749 let PageEncoding::Structural(layout) = &page.description else {
7750 panic!("Expected structural encoding");
7751 };
7752 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
7753 panic!("Expected constant layout in slot 2");
7754 };
7755 assert!(layout.inline_value.is_none());
7756 assert_eq!(page.data.len(), 1);
7757
7758 let test_cases = TestCases::default()
7759 .with_min_file_version(LanceFileVersion::V2_2)
7760 .with_max_file_version(LanceFileVersion::V2_2)
7761 .with_page_sizes(vec![4096]);
7762 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
7763 }
7764
7765 #[tokio::test]
7766 async fn test_constant_layout_nullable_item_v2_2() {
7767 use crate::format::pb21::page_layout::Layout;
7768
7769 let arr: ArrayRef = Arc::new(arrow_array::Int32Array::from(vec![
7770 Some(7),
7771 None,
7772 Some(7),
7773 None,
7774 Some(7),
7775 ]));
7776 let field = arrow_schema::Field::new("c", DataType::Int32, true);
7777 let page = encode_first_page(field, arr.clone(), LanceFileVersion::V2_2).await;
7778
7779 let PageEncoding::Structural(layout) = &page.description else {
7780 panic!("Expected structural encoding");
7781 };
7782 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
7783 panic!("Expected constant layout in slot 2");
7784 };
7785 assert!(layout.inline_value.is_some());
7786 assert_eq!(page.data.len(), 2);
7787
7788 let test_cases = TestCases::default()
7789 .with_min_file_version(LanceFileVersion::V2_2)
7790 .with_max_file_version(LanceFileVersion::V2_2)
7791 .with_page_sizes(vec![4096]);
7792 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
7793 }
7794
7795 #[tokio::test]
7796 async fn test_constant_layout_list_repdef_v2_2() {
7797 use crate::format::pb21::page_layout::Layout;
7798 use arrow_array::builder::{Int32Builder, ListBuilder};
7799
7800 let mut builder = ListBuilder::new(Int32Builder::new());
7801 builder.values().append_value(7);
7802 builder.values().append_null();
7803 builder.values().append_value(7);
7804 builder.append(true);
7805
7806 builder.append(true);
7807
7808 builder.values().append_value(7);
7809 builder.append(true);
7810
7811 builder.append_null();
7812
7813 let arr: ArrayRef = Arc::new(builder.finish());
7814 let field = arrow_schema::Field::new(
7815 "c",
7816 DataType::List(Arc::new(arrow_schema::Field::new(
7817 "item",
7818 DataType::Int32,
7819 true,
7820 ))),
7821 true,
7822 );
7823 let page = encode_first_page(field, arr.clone(), LanceFileVersion::V2_2).await;
7824
7825 let PageEncoding::Structural(layout) = &page.description else {
7826 panic!("Expected structural encoding");
7827 };
7828 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
7829 panic!("Expected constant layout in slot 2");
7830 };
7831 assert!(layout.inline_value.is_some());
7832 assert_eq!(page.data.len(), 2);
7833
7834 let test_cases = TestCases::default()
7835 .with_min_file_version(LanceFileVersion::V2_2)
7836 .with_max_file_version(LanceFileVersion::V2_2)
7837 .with_page_sizes(vec![4096]);
7838 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
7839 }
7840
7841 #[tokio::test]
7842 async fn test_constant_layout_fixed_size_list_not_used_v2_2() {
7843 use crate::format::pb21::page_layout::Layout;
7844 use arrow_array::builder::{FixedSizeListBuilder, Int32Builder};
7845
7846 let mut builder = FixedSizeListBuilder::new(Int32Builder::new(), 3);
7847 for _ in 0..64 {
7848 builder.values().append_value(1);
7849 builder.values().append_null();
7850 builder.values().append_value(3);
7851 builder.append(true);
7852 }
7853 let arr: ArrayRef = Arc::new(builder.finish());
7854 let field = arrow_schema::Field::new(
7855 "c",
7856 DataType::FixedSizeList(
7857 Arc::new(arrow_schema::Field::new("item", DataType::Int32, true)),
7858 3,
7859 ),
7860 true,
7861 );
7862 let page = encode_first_page(field, arr.clone(), LanceFileVersion::V2_2).await;
7863
7864 if let PageEncoding::Structural(layout) = &page.description {
7865 assert!(
7866 !matches!(layout.layout.as_ref().unwrap(), Layout::ConstantLayout(_)),
7867 "FixedSizeList should not use constant layout yet"
7868 );
7869 }
7870
7871 let test_cases = TestCases::default()
7872 .with_min_file_version(LanceFileVersion::V2_2)
7873 .with_max_file_version(LanceFileVersion::V2_2)
7874 .with_page_sizes(vec![4096]);
7875 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
7876 }
7877
7878 #[tokio::test]
7879 async fn test_constant_layout_not_written_before_v2_2() {
7880 use crate::format::pb21::page_layout::Layout;
7881
7882 let arr: ArrayRef = Arc::new(arrow_array::Int32Array::from(vec![7; 1024]));
7883 let field = arrow_schema::Field::new("c", DataType::Int32, true);
7884 let page = encode_first_page(field, arr.clone(), LanceFileVersion::V2_1).await;
7885
7886 let PageEncoding::Structural(layout) = &page.description else {
7887 return;
7888 };
7889 assert!(
7890 !matches!(layout.layout.as_ref().unwrap(), Layout::ConstantLayout(_)),
7891 "Should not emit constant layout before v2.2"
7892 );
7893
7894 let test_cases = TestCases::default()
7895 .with_min_file_version(LanceFileVersion::V2_1)
7896 .with_max_file_version(LanceFileVersion::V2_1)
7897 .with_page_sizes(vec![4096]);
7898 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
7899 }
7900
7901 #[tokio::test]
7902 async fn test_all_null_constant_layout_still_works_v2_2() {
7903 use crate::format::pb21::page_layout::Layout;
7904
7905 let arr: ArrayRef = Arc::new(arrow_array::Int32Array::from(vec![None, None, None]));
7906 let field = arrow_schema::Field::new("c", DataType::Int32, true);
7907 let page = encode_first_page(field, arr.clone(), LanceFileVersion::V2_2).await;
7908
7909 let PageEncoding::Structural(layout) = &page.description else {
7910 panic!("Expected structural encoding");
7911 };
7912 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
7913 panic!("Expected layout in slot 2");
7914 };
7915 assert!(layout.inline_value.is_none());
7916 assert_eq!(page.data.len(), 0);
7917
7918 let test_cases = TestCases::default()
7919 .with_min_file_version(LanceFileVersion::V2_2)
7920 .with_max_file_version(LanceFileVersion::V2_2)
7921 .with_page_sizes(vec![4096]);
7922 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
7923 }
7924
7925 #[test]
7926 fn test_encode_decode_complex_all_null_vals_roundtrip() {
7927 use crate::compression::{
7928 DecompressionStrategy, DefaultCompressionStrategy, DefaultDecompressionStrategy,
7929 };
7930
7931 let values: Arc<[u16]> = Arc::from((0..2048).map(|i| (i % 5) as u16).collect::<Vec<u16>>());
7932
7933 let compression_strategy = DefaultCompressionStrategy::default();
7934 let decompression_strategy = DefaultDecompressionStrategy::default();
7935
7936 let (compressed_buf, encoding) = PrimitiveStructuralEncoder::encode_complex_all_null_vals(
7937 &values,
7938 &compression_strategy,
7939 )
7940 .unwrap();
7941
7942 let decompressor = decompression_strategy
7943 .create_block_decompressor(&encoding)
7944 .unwrap();
7945 let decompressed = decompressor
7946 .decompress(compressed_buf, values.len() as u64)
7947 .unwrap();
7948 let decompressed_fixed_width = decompressed.as_fixed_width().unwrap();
7949 assert_eq!(decompressed_fixed_width.num_values, values.len() as u64);
7950 assert_eq!(decompressed_fixed_width.bits_per_value, 16);
7951 let rep_result = decompressed_fixed_width.data.borrow_to_typed_slice::<u16>();
7952 assert_eq!(rep_result.as_ref(), values.as_ref());
7953 }
7954
7955 #[tokio::test]
7956 async fn test_complex_all_null_compression_gated_by_version() {
7957 use crate::format::pb21::page_layout::Layout;
7958 use arrow_array::ListArray;
7959
7960 let list_array = ListArray::from_iter_primitive::<arrow_array::types::Int32Type, _, _>(
7961 (0..1000).map(|i| if i % 2 == 0 { None } else { Some(vec![]) }),
7962 );
7963 let arr: ArrayRef = Arc::new(list_array);
7964 let field = arrow_schema::Field::new(
7965 "c",
7966 DataType::List(Arc::new(arrow_schema::Field::new(
7967 "item",
7968 DataType::Int32,
7969 true,
7970 ))),
7971 true,
7972 );
7973
7974 let page_v21 = encode_first_page(field.clone(), arr.clone(), LanceFileVersion::V2_1).await;
7975 let PageEncoding::Structural(layout_v21) = &page_v21.description else {
7976 panic!("Expected structural encoding");
7977 };
7978 let Layout::ConstantLayout(layout_v21) = layout_v21.layout.as_ref().unwrap() else {
7979 panic!("Expected constant layout");
7980 };
7981 assert!(layout_v21.rep_compression.is_none());
7982 assert!(layout_v21.def_compression.is_none());
7983 assert_eq!(layout_v21.num_rep_values, 0);
7984 assert_eq!(layout_v21.num_def_values, 0);
7985
7986 let page_v22 = encode_first_page(field, arr, LanceFileVersion::V2_2).await;
7987 let PageEncoding::Structural(layout_v22) = &page_v22.description else {
7988 panic!("Expected structural encoding");
7989 };
7990 let Layout::ConstantLayout(layout_v22) = layout_v22.layout.as_ref().unwrap() else {
7991 panic!("Expected constant layout");
7992 };
7993 assert!(layout_v22.def_compression.is_some());
7994 assert!(layout_v22.num_def_values > 0);
7995 }
7996
7997 #[tokio::test]
7998 async fn test_complex_all_null_round_trip() {
7999 use arrow_array::ListArray;
8000
8001 let list_array = ListArray::from_iter_primitive::<arrow_array::types::Int32Type, _, _>(
8002 (0..1000).map(|i| if i % 2 == 0 { None } else { Some(vec![]) }),
8003 );
8004
8005 let test_cases = TestCases::default().with_min_file_version(LanceFileVersion::V2_2);
8006 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, HashMap::new())
8007 .await;
8008 }
8009
8010 #[tokio::test]
8012 async fn test_sparse_boolean_list_roundtrip() {
8013 use arrow_array::builder::{BooleanBuilder, ListBuilder};
8014
8015 let mut list_builder = ListBuilder::new(BooleanBuilder::new());
8016 for i in 0..1000i32 {
8017 if i % 64 == 0 {
8018 list_builder.values().append_value(i % 128 == 0);
8020 list_builder.append(true);
8021 } else {
8022 list_builder.append(false);
8023 }
8024 }
8025 let list_array = Arc::new(list_builder.finish());
8026
8027 let test_cases = TestCases::default().with_min_file_version(LanceFileVersion::V2_1);
8028 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
8029 }
8030
8031 fn truncated_tail_details() -> std::sync::Arc<super::FullZipDecodeDetails> {
8032 use crate::compression::VariablePerValueDecompressor;
8033 use crate::encodings::physical::binary::VariableDecoder;
8034 use crate::repdef::{ControlWordParser, DefinitionInterpretation};
8035 use std::sync::Arc;
8036 Arc::new(super::FullZipDecodeDetails {
8037 value_decompressor: super::PerValueDecompressor::Variable(Arc::new(
8038 VariableDecoder::default(),
8039 )
8040 as Arc<dyn VariablePerValueDecompressor>),
8041 def_meaning: vec![DefinitionInterpretation::NullableItem].into(),
8042 ctrl_word_parser: ControlWordParser::new(0, 0),
8043 max_rep: 0,
8044 max_visible_def: 0,
8045 })
8046 }
8047
8048 fn decode_variable_full_zip(
8049 buf: Vec<u8>,
8050 bits_per_offset: u8,
8051 ) -> lance_core::Result<super::VariableFullZipDecoder> {
8052 use std::collections::VecDeque;
8053 let mut data = VecDeque::new();
8054 data.push_back(crate::buffer::LanceBuffer::from(buf));
8055 super::VariableFullZipDecoder::new(
8056 truncated_tail_details(),
8057 data,
8058 1,
8059 bits_per_offset,
8060 bits_per_offset,
8061 )
8062 }
8063
8064 #[test]
8073 fn variable_full_zip_truncated_length_prefix_is_corrupt_file() {
8074 use lance_core::Error;
8075
8076 for (bits, buf_len) in [(32u8, 3usize), (64u8, 4usize)] {
8077 let err = decode_variable_full_zip(vec![0xAA; buf_len], bits)
8078 .expect_err("a truncated length prefix must not decode");
8079 assert!(
8080 matches!(err, Error::CorruptFile { .. }),
8081 "expected CorruptFile for a {}-bit prefix with {} byte(s), got: {:?}",
8082 bits,
8083 buf_len,
8084 err
8085 );
8086 let msg = err.to_string();
8087 assert!(
8088 msg.contains("truncated length prefix"),
8089 "error should say what is wrong, got: {msg}"
8090 );
8091 }
8092 }
8093}