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 STRUCTURAL_ENCODING_SPARSE,
19 },
20 data::DictionaryDataBlock,
21 encodings::logical::primitive::blob::{BlobDescriptionPageScheduler, BlobPageScheduler},
22 format::{
23 ProtobufUtils21,
24 pb21::{self, CompressiveEncoding, PageLayout, compressive_encoding::Compression},
25 },
26};
27use arrow_array::{
28 Array, ArrayRef, PrimitiveArray, cast::AsArray, make_array, new_null_array, types::UInt64Type,
29};
30use arrow_buffer::{BooleanBuffer, BooleanBufferBuilder, NullBuffer, ScalarBuffer};
31use arrow_schema::{DataType, Field as ArrowField};
32use bytes::Bytes;
33use futures::{FutureExt, TryStreamExt, future::BoxFuture, stream::FuturesOrdered};
34use itertools::Itertools;
35use lance_arrow::DataTypeExt;
36use lance_arrow::deepcopy::deep_copy_nulls;
37use lance_core::{
38 cache::{CacheKey, CacheKeySchema, Context, DeepSizeOf, KeyBuilder},
39 error::{Error, LanceOptionExt},
40 utils::bit::pad_bytes,
41};
42use log::{debug, trace};
43
44use crate::encodings::logical::primitive::miniblock::MiniBlockChunk;
45use crate::encodings::physical::rle::{RleDecompressor, RleRuns};
46use crate::utils::bytepack::ByteUnpacker;
47use crate::{
48 compression::{
49 BlockDecompressor, CompressionStrategy, DecompressionStrategy, MiniBlockDecompressor,
50 compress_required_block, create_rle_decompressor,
51 },
52 data::{AllNullDataBlock, DataBlock, VariableWidthBlock},
53 utils::bytepack::BytepackedIntegerEncoder,
54};
55use crate::{
56 compression::{FixedPerValueDecompressor, VariablePerValueDecompressor},
57 encodings::logical::primitive::fullzip::PerValueDataBlock,
58};
59use crate::{
60 encodings::logical::primitive::miniblock::{MiniBlockCompressed, MiniBlockCompressionContext},
61 statistics::{ComputeStat, GetStat, Stat},
62};
63use crate::{
64 repdef::{
65 CompositeRepDefUnraveler, ControlWordIterator, ControlWordParser, DefinitionInterpretation,
66 MiniBlockRepDefBudget, NormalizedStructuralPlan, RepDefSlicer, SerializedRepDefs,
67 build_control_word_iterator,
68 },
69 utils::accumulation::AccumulationQueue,
70};
71use lance_core::{Result, datatypes::Field, utils::tokio::spawn_cpu};
72
73use crate::constants::{
74 COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY, DICT_DIVISOR_META_KEY,
75 DICT_SIZE_RATIO_META_KEY, DICT_VALUES_COMPRESSION_ENV_VAR,
76 DICT_VALUES_COMPRESSION_LEVEL_ENV_VAR, DICT_VALUES_COMPRESSION_LEVEL_META_KEY,
77 DICT_VALUES_COMPRESSION_META_KEY,
78};
79use crate::{
80 EncodingsIo,
81 buffer::LanceBuffer,
82 data::{BlockInfo, DataBlockBuilder, FixedWidthDataBlock},
83 decoder::{
84 ColumnInfo, DecodePageTask, DecodedArray, DecodedPage, FilterExpression, LoadedPageShard,
85 MessageType, PageEncoding, PageInfo, ScheduledScanLine, SchedulerContext,
86 StructuralDecodeArrayTask, StructuralFieldDecoder, StructuralFieldScheduler,
87 StructuralPageDecoder, StructuralSchedulingJob, UnloadedPageShard,
88 },
89 encoder::{
90 EncodeTask, EncodedColumn, EncodedPage, EncodingOptions, FieldEncoder, OutOfLineBuffers,
91 },
92 repdef::{LevelBuffer, RepDefBuilder, RepDefUnraveler},
93};
94
95pub mod blob;
96mod chunk_index;
97pub mod constant;
98pub mod dict;
99pub mod fullzip;
100mod layout;
101pub mod miniblock;
102pub(crate) mod sparse;
103
104use chunk_index::{ItemCounts, MiniBlockChunkIndex, PrefixSums, RowMapping, parse_nested_rep};
105
106const FILL_BYTE: u8 = 0xFE;
107const DEFAULT_DICT_DIVISOR: u64 = 2;
108const DEFAULT_DICT_MAX_CARDINALITY: u64 = 100_000;
109const DEFAULT_DICT_SIZE_RATIO: f64 = 0.8;
110const DEFAULT_DICT_VALUES_COMPRESSION: &str = "lz4";
111
112const MAX_LEGACY_RLE_LEVELS: u64 = ((u16::MAX as u64 - std::mem::size_of::<u64>() as u64)
117 / (std::mem::size_of::<u16>() as u64 + std::mem::size_of::<u8>() as u64))
118 * u8::MAX as u64;
119
120struct PageLoadTask {
121 decoder_fut: BoxFuture<'static, Result<Box<dyn StructuralPageDecoder>>>,
122 num_rows: u64,
123}
124
125trait StructuralPageScheduler: std::fmt::Debug + Send {
128 fn initialize<'a>(
130 &'a mut self,
131 io: &Arc<dyn EncodingsIo>,
132 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>>;
133 fn load(&mut self, data: &Arc<dyn CachedPageData>);
135 fn schedule_ranges(
144 &self,
145 ranges: &[Range<u64>],
146 io: &Arc<dyn EncodingsIo>,
147 ) -> Result<Vec<PageLoadTask>>;
148}
149
150#[derive(Debug)]
152struct ChunkMeta {
153 num_values: u64,
154 chunk_size_bytes: u64,
155 offset_bytes: u64,
156}
157
158#[derive(Debug, Clone)]
160struct DecodedMiniBlockChunk {
161 rep: Option<ScalarBuffer<u16>>,
162 def: Option<ScalarBuffer<u16>>,
163 values: DataBlock,
164}
165
166#[derive(Debug)]
174struct DecodeMiniBlockTask {
175 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
176 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
177 value_decompressor: Arc<dyn MiniBlockDecompressor>,
178 dictionary_data: Option<Arc<DataBlock>>,
179 def_meaning: Arc<[DefinitionInterpretation]>,
180 num_buffers: u64,
181 max_visible_level: u16,
182 instructions: Vec<(ChunkDrainInstructions, LoadedChunk)>,
183 has_large_chunk: bool,
184}
185
186impl DecodeMiniBlockTask {
187 fn decoded_size_bytes(&self) -> Option<u64> {
188 if self.rep_decompressor.is_some() || self.def_decompressor.is_some() {
189 return None;
190 }
191 let num_values = self
192 .instructions
193 .iter()
194 .try_fold(0_u64, |total, (instruction, _)| {
195 total.checked_add(instruction.rows_to_take)
196 })?;
197 self.value_decompressor.decoded_size_bytes(num_values)
198 }
199
200 fn resolve_num_levels(
201 rep_decompressor: Option<&dyn BlockDecompressor>,
202 rep_levels: Option<&LanceBuffer>,
203 def_decompressor: Option<&dyn BlockDecompressor>,
204 def_levels: Option<&LanceBuffer>,
205 declared_num_levels: u16,
206 ) -> Result<u64> {
207 let rep_num_levels = match (rep_decompressor, rep_levels) {
208 (Some(decompressor), Some(levels)) => decompressor.infer_num_values(levels)?,
209 (None, None) => None,
210 _ => {
211 return Err(Error::invalid_input_source(
212 "miniblock repetition codec and payload presence disagree".into(),
213 ));
214 }
215 };
216 let def_num_levels = match (def_decompressor, def_levels) {
217 (Some(decompressor), Some(levels)) => decompressor.infer_num_values(levels)?,
218 (None, None) => None,
219 _ => {
220 return Err(Error::invalid_input_source(
221 "miniblock definition codec and payload presence disagree".into(),
222 ));
223 }
224 };
225
226 let inferred_num_levels = match (rep_num_levels, def_num_levels) {
227 (Some(rep_num_levels), Some(def_num_levels)) if rep_num_levels != def_num_levels => {
228 return Err(Error::invalid_input_source(
229 format!(
230 "miniblock structural streams disagree on the level count: repetition inferred {rep_num_levels}, definition inferred {def_num_levels}"
231 )
232 .into(),
233 ));
234 }
235 (Some(num_levels), _) | (_, Some(num_levels)) => num_levels,
236 (None, None) => return Ok(u64::from(declared_num_levels)),
237 };
238
239 let declared_num_levels = u64::from(declared_num_levels);
240 if inferred_num_levels == declared_num_levels {
241 return Ok(inferred_num_levels);
242 }
243 let u16_modulus = u64::from(u16::MAX) + 1;
244 if inferred_num_levels <= declared_num_levels
245 || inferred_num_levels % u16_modulus != declared_num_levels
246 {
247 return Err(Error::invalid_input_source(
248 format!(
249 "miniblock payload proves {inferred_num_levels} levels but the header declared {declared_num_levels}; the counts are not congruent modulo 65536"
250 )
251 .into(),
252 ));
253 }
254 if inferred_num_levels > MAX_LEGACY_RLE_LEVELS {
255 return Err(Error::invalid_input_source(
256 format!(
257 "miniblock payload proves {inferred_num_levels} levels, exceeding the legacy RLE payload bound of {MAX_LEGACY_RLE_LEVELS}"
258 )
259 .into(),
260 ));
261 }
262 Ok(inferred_num_levels)
263 }
264
265 fn decode_levels(
266 decompressor: &dyn BlockDecompressor,
267 levels: LanceBuffer,
268 expected_num_levels: u64,
269 ) -> Result<ScalarBuffer<u16>> {
270 let levels = decompressor.decompress(Some(levels), expected_num_levels)?;
271 let levels = levels.as_fixed_width().ok_or_else(|| {
272 Error::invalid_input_source(
273 "miniblock levels did not decode to fixed-width data".into(),
274 )
275 })?;
276 if levels.num_values != expected_num_levels {
277 return Err(Error::invalid_input_source(
278 format!(
279 "miniblock levels decoded {} values, expected {expected_num_levels}",
280 levels.num_values
281 )
282 .into(),
283 ));
284 }
285 if levels.bits_per_value != 16 {
286 return Err(Error::invalid_input_source(
287 format!(
288 "miniblock levels decoded {} bits per value, expected 16",
289 levels.bits_per_value
290 )
291 .into(),
292 ));
293 }
294 let expected_bytes = expected_num_levels.checked_mul(2).ok_or_else(|| {
295 Error::invalid_input_source(
296 format!("miniblock level byte count overflowed for {expected_num_levels} levels")
297 .into(),
298 )
299 })?;
300 if levels.data.len() as u64 != expected_bytes {
301 return Err(Error::invalid_input_source(
302 format!(
303 "miniblock levels decoded {} bytes, expected {expected_bytes}",
304 levels.data.len()
305 )
306 .into(),
307 ));
308 }
309 Ok(levels.data.borrow_to_typed_slice::<u16>())
310 }
311
312 fn extend_levels(
319 range: Range<u64>,
320 levels: &mut Option<LevelBuffer>,
321 level_buf: &Option<impl AsRef<[u16]>>,
322 dest_offset: usize,
323 ) {
324 if let Some(level_buf) = level_buf {
325 if levels.is_none() {
326 let mut new_levels_vec =
329 LevelBuffer::with_capacity(dest_offset + (range.end - range.start) as usize);
330 new_levels_vec.extend(iter::repeat_n(0, dest_offset));
331 *levels = Some(new_levels_vec);
332 }
333 levels.as_mut().unwrap().extend(
334 level_buf.as_ref()[range.start as usize..range.end as usize]
335 .iter()
336 .copied(),
337 );
338 } else if let Some(levels) = levels {
339 let num_values = (range.end - range.start) as usize;
340 levels.extend(iter::repeat_n(0, num_values));
343 }
344 }
345
346 fn map_range(
383 range: Range<u64>,
384 rep: Option<&impl AsRef<[u16]>>,
385 def: Option<&impl AsRef<[u16]>>,
386 max_rep: u16,
387 max_visible_def: u16,
388 total_items: u64,
391 preamble_action: PreambleAction,
392 ) -> (Range<u64>, Range<u64>) {
393 if let Some(rep) = rep {
394 let mut rep = rep.as_ref();
395 let mut items_in_preamble = 0_u64;
398 let first_row_start = match preamble_action {
399 PreambleAction::Skip | PreambleAction::Take => {
400 let first_row_start = if let Some(def) = def.as_ref() {
401 let mut first_row_start = None;
402 for (idx, (rep, def)) in rep.iter().zip(def.as_ref()).enumerate() {
403 if *rep == max_rep {
404 first_row_start = Some(idx as u64);
405 break;
406 }
407 if *def <= max_visible_def {
408 items_in_preamble += 1;
409 }
410 }
411 first_row_start
412 } else {
413 let first_row_start =
414 rep.iter().position(|&r| r == max_rep).map(|r| r as u64);
415 items_in_preamble = first_row_start.unwrap_or(rep.len() as u64);
416 first_row_start
417 };
418 if first_row_start.is_none() {
421 assert!(preamble_action == PreambleAction::Take);
422 return (0..total_items, 0..rep.len() as u64);
423 }
424 let first_row_start = first_row_start.unwrap();
425 rep = &rep[first_row_start as usize..];
426 first_row_start
427 }
428 PreambleAction::Absent => {
429 debug_assert!(rep[0] == max_rep);
430 0
431 }
432 };
433
434 if range.start == range.end {
436 debug_assert!(preamble_action == PreambleAction::Take);
437 debug_assert!(items_in_preamble <= total_items);
438 return (0..items_in_preamble, 0..first_row_start);
439 }
440 assert!(range.start < range.end);
441
442 let mut rows_seen = 0;
443 let mut new_start = 0;
444 let mut new_levels_start = 0;
445
446 if let Some(def) = def {
447 let def = &def.as_ref()[first_row_start as usize..];
448
449 let mut lead_invis_seen = 0;
451
452 if range.start > 0 {
453 if def[0] > max_visible_def {
454 lead_invis_seen += 1;
455 }
456 for (idx, (rep, def)) in rep.iter().zip(def).skip(1).enumerate() {
457 if *rep == max_rep {
458 rows_seen += 1;
459 if rows_seen == range.start {
460 new_start = idx as u64 + 1 - lead_invis_seen;
461 new_levels_start = idx as u64 + 1;
462 break;
463 }
464 }
465 if *def > max_visible_def {
466 lead_invis_seen += 1;
467 }
468 }
469 }
470
471 rows_seen += 1;
472
473 let mut new_end = u64::MAX;
474 let mut new_levels_end = rep.len() as u64;
475 let new_start_is_visible = def[new_levels_start as usize] <= max_visible_def;
476 let mut tail_invis_seen = if new_start_is_visible { 0 } else { 1 };
477 for (idx, (rep, def)) in rep[(new_levels_start + 1) as usize..]
478 .iter()
479 .zip(&def[(new_levels_start + 1) as usize..])
480 .enumerate()
481 {
482 if *rep == max_rep {
483 rows_seen += 1;
484 if rows_seen == range.end + 1 {
485 new_end = idx as u64 + new_start + 1 - tail_invis_seen;
486 new_levels_end = idx as u64 + new_levels_start + 1;
487 break;
488 }
489 }
490 if *def > max_visible_def {
491 tail_invis_seen += 1;
492 }
493 }
494
495 if new_end == u64::MAX {
496 new_levels_end = rep.len() as u64;
497 let total_invis_seen = lead_invis_seen + tail_invis_seen;
498 new_end = rep.len() as u64 - total_invis_seen;
499 }
500
501 assert_ne!(new_end, u64::MAX);
502
503 if preamble_action == PreambleAction::Skip {
505 new_start += items_in_preamble;
506 new_end += items_in_preamble;
507 new_levels_start += first_row_start;
508 new_levels_end += first_row_start;
509 } else if preamble_action == PreambleAction::Take {
510 debug_assert_eq!(new_start, 0);
511 debug_assert_eq!(new_levels_start, 0);
512 new_end += items_in_preamble;
513 new_levels_end += first_row_start;
514 }
515
516 debug_assert!(new_end <= total_items);
517 (new_start..new_end, new_levels_start..new_levels_end)
518 } else {
519 if range.start > 0 {
525 for (idx, rep) in rep.iter().skip(1).enumerate() {
526 if *rep == max_rep {
527 rows_seen += 1;
528 if rows_seen == range.start {
529 new_start = idx as u64 + 1;
530 break;
531 }
532 }
533 }
534 }
535 let mut new_end = rep.len() as u64;
536 if range.end < total_items {
538 for (idx, rep) in rep[(new_start + 1) as usize..].iter().enumerate() {
539 if *rep == max_rep {
540 rows_seen += 1;
541 if rows_seen == range.end {
542 new_end = idx as u64 + new_start + 1;
543 break;
544 }
545 }
546 }
547 }
548
549 if preamble_action == PreambleAction::Skip {
551 new_start += first_row_start;
552 new_end += first_row_start;
553 } else if preamble_action == PreambleAction::Take {
554 debug_assert_eq!(new_start, 0);
555 new_end += first_row_start;
556 }
557
558 debug_assert!(new_end <= total_items);
559 (new_start..new_end, new_start..new_end)
560 }
561 } else {
562 (range.clone(), range)
565 }
566 }
567
568 fn read_buffer_sizes<const LARGE: bool>(
570 buf: &[u8],
571 offset: &mut usize,
572 num_buffers: u64,
573 ) -> Vec<u32> {
574 let read_size = if LARGE { 4 } else { 2 };
575 (0..num_buffers)
576 .map(|_| {
577 let bytes = &buf[*offset..*offset + read_size];
578 let size = if LARGE {
579 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
580 } else {
581 u16::from_le_bytes([bytes[0], bytes[1]]) as u32
583 };
584 *offset += read_size;
585 size
586 })
587 .collect()
588 }
589
590 fn decode_miniblock_chunk(
592 &self,
593 buf: &LanceBuffer,
594 items_in_chunk: u64,
595 ) -> Result<DecodedMiniBlockChunk> {
596 let mut offset = 0;
597 let num_levels = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
598 offset += 2;
599
600 let rep_size = if self.rep_decompressor.is_some() {
601 let rep_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
602 offset += 2;
603 Some(rep_size)
604 } else {
605 None
606 };
607 let def_size = if self.def_decompressor.is_some() {
608 let def_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
609 offset += 2;
610 Some(def_size)
611 } else {
612 None
613 };
614
615 let buffer_sizes = if self.has_large_chunk {
616 Self::read_buffer_sizes::<true>(buf, &mut offset, self.num_buffers)
617 } else {
618 Self::read_buffer_sizes::<false>(buf, &mut offset, self.num_buffers)
619 };
620
621 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
622
623 let rep = rep_size.map(|rep_size| {
624 let rep = buf.slice_with_length(offset, rep_size as usize);
625 offset += rep_size as usize;
626 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
627 rep
628 });
629
630 let def = def_size.map(|def_size| {
631 let def = buf.slice_with_length(offset, def_size as usize);
632 offset += def_size as usize;
633 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
634 def
635 });
636
637 let num_levels = Self::resolve_num_levels(
638 self.rep_decompressor.as_deref(),
639 rep.as_ref(),
640 self.def_decompressor.as_deref(),
641 def.as_ref(),
642 num_levels,
643 )?;
644
645 let buffers = buffer_sizes
646 .into_iter()
647 .map(|buf_size| {
648 let buf = buf.slice_with_length(offset, buf_size as usize);
649 offset += buf_size as usize;
650 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
651 buf
652 })
653 .collect::<Vec<_>>();
654
655 let values = self
656 .value_decompressor
657 .decompress(buffers, items_in_chunk)?;
658
659 let rep = rep
660 .map(|rep| {
661 Self::decode_levels(
662 self.rep_decompressor.as_ref().unwrap().as_ref(),
663 rep,
664 num_levels,
665 )
666 })
667 .transpose()?;
668 let def = def
669 .map(|def| {
670 Self::decode_levels(
671 self.def_decompressor.as_ref().unwrap().as_ref(),
672 def,
673 num_levels,
674 )
675 })
676 .transpose()?;
677
678 if let (Some(rep), Some(def)) = (&rep, &def)
679 && rep.len() != def.len()
680 {
681 return Err(Error::invalid_input_source(
682 format!(
683 "miniblock structural streams decoded different level counts: repetition {}, definition {}",
684 rep.len(),
685 def.len()
686 )
687 .into(),
688 ));
689 }
690
691 Ok(DecodedMiniBlockChunk { rep, def, values })
692 }
693}
694
695impl DecodePageTask for DecodeMiniBlockTask {
696 fn decode(self: Box<Self>) -> Result<DecodedPage> {
697 let mut repbuf: Option<LevelBuffer> = None;
699 let mut defbuf: Option<LevelBuffer> = None;
700
701 let max_rep = self.def_meaning.iter().filter(|l| l.is_list()).count() as u16;
702
703 let estimated_size_bytes = self.decoded_size_bytes().unwrap_or_else(|| {
704 self.instructions
706 .iter()
707 .map(|(_, chunk)| chunk.data.len() as u64)
708 .sum::<u64>()
709 * 2
710 });
711 let mut data_builder = DataBlockBuilder::with_capacity_estimate(estimated_size_bytes);
712
713 let mut level_offset = 0;
715
716 let needs_caching: Vec<bool> = self
718 .instructions
719 .windows(2)
720 .map(|w| w[0].1.chunk_idx == w[1].1.chunk_idx)
721 .chain(std::iter::once(false)) .collect();
723
724 let mut chunk_cache: Option<(usize, DecodedMiniBlockChunk)> = None;
726
727 for (idx, (instructions, chunk)) in self.instructions.iter().enumerate() {
729 let should_cache_this_chunk = needs_caching[idx];
730
731 let decoded_chunk = match &chunk_cache {
732 Some((cached_chunk_idx, cached_chunk)) if *cached_chunk_idx == chunk.chunk_idx => {
733 cached_chunk.clone()
735 }
736 _ => {
737 let decoded = self.decode_miniblock_chunk(&chunk.data, chunk.items_in_chunk)?;
739
740 if should_cache_this_chunk {
742 chunk_cache = Some((chunk.chunk_idx, decoded.clone()));
743 }
744 decoded
745 }
746 };
747
748 let DecodedMiniBlockChunk { rep, def, values } = decoded_chunk;
749
750 let row_range_start =
752 instructions.rows_to_skip + instructions.chunk_instructions.rows_to_skip;
753 let row_range_end = row_range_start + instructions.rows_to_take;
754
755 let (item_range, level_range) = Self::map_range(
757 row_range_start..row_range_end,
758 rep.as_ref(),
759 def.as_ref(),
760 max_rep,
761 self.max_visible_level,
762 chunk.items_in_chunk,
763 instructions.preamble_action,
764 );
765 if item_range.end - item_range.start > chunk.items_in_chunk {
766 return Err(lance_core::Error::internal(format!(
767 "Item range {:?} is greater than chunk items in chunk {:?}",
768 item_range, chunk.items_in_chunk
769 )));
770 }
771
772 Self::extend_levels(level_range.clone(), &mut repbuf, &rep, level_offset);
774 Self::extend_levels(level_range.clone(), &mut defbuf, &def, level_offset);
775 level_offset += (level_range.end - level_range.start) as usize;
776 data_builder.append(&values, item_range)?;
777 }
778
779 let mut data = data_builder.finish();
780
781 let unraveler =
782 RepDefUnraveler::new(repbuf, defbuf, self.def_meaning.clone(), data.num_values());
783
784 if let Some(dictionary) = &self.dictionary_data {
785 let DataBlock::FixedWidth(indices) = data else {
787 return Err(lance_core::Error::internal(format!(
788 "Expected FixedWidth DataBlock for dictionary indices, got {:?}",
789 data
790 )));
791 };
792 data = DataBlock::Dictionary(DictionaryDataBlock::from_parts(
793 indices,
794 dictionary.as_ref().clone(),
795 ));
796 }
797
798 Ok(DecodedPage {
799 data,
800 repdef: unraveler,
801 })
802 }
803}
804
805#[derive(Debug)]
808struct LoadedChunk {
809 data: LanceBuffer,
810 items_in_chunk: u64,
811 byte_range: Range<u64>,
812 chunk_idx: usize,
813}
814
815impl Clone for LoadedChunk {
816 fn clone(&self) -> Self {
817 Self {
818 data: self.data.clone(),
820 items_in_chunk: self.items_in_chunk,
821 byte_range: self.byte_range.clone(),
822 chunk_idx: self.chunk_idx,
823 }
824 }
825}
826
827#[derive(Debug)]
830struct MiniBlockDecoder {
831 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
832 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
833 value_decompressor: Arc<dyn MiniBlockDecompressor>,
834 def_meaning: Arc<[DefinitionInterpretation]>,
835 loaded_chunks: VecDeque<LoadedChunk>,
836 instructions: VecDeque<ChunkInstructions>,
837 offset_in_current_chunk: u64,
838 num_rows: u64,
839 num_buffers: u64,
840 dictionary: Option<Arc<DataBlock>>,
841 has_large_chunk: bool,
842}
843
844impl StructuralPageDecoder for MiniBlockDecoder {
847 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
848 let mut items_desired = num_rows;
849 let mut need_preamble = false;
850 let mut skip_in_chunk = self.offset_in_current_chunk;
851 let mut drain_instructions = Vec::new();
852 while items_desired > 0 || need_preamble {
853 let (instructions, consumed) = self
854 .instructions
855 .front()
856 .unwrap()
857 .drain_from_instruction(&mut items_desired, &mut need_preamble, &mut skip_in_chunk);
858
859 while self.loaded_chunks.front().unwrap().chunk_idx
860 != instructions.chunk_instructions.chunk_idx
861 {
862 self.loaded_chunks.pop_front();
863 }
864 drain_instructions.push((instructions, self.loaded_chunks.front().unwrap().clone()));
865 if consumed {
866 self.instructions.pop_front();
867 }
868 }
869 self.offset_in_current_chunk = skip_in_chunk;
872
873 let max_visible_level = self
874 .def_meaning
875 .iter()
876 .take_while(|l| !l.is_list())
877 .map(|l| l.num_def_levels())
878 .sum::<u16>();
879
880 Ok(Box::new(DecodeMiniBlockTask {
881 instructions: drain_instructions,
882 def_decompressor: self.def_decompressor.clone(),
883 rep_decompressor: self.rep_decompressor.clone(),
884 value_decompressor: self.value_decompressor.clone(),
885 dictionary_data: self.dictionary.clone(),
886 def_meaning: self.def_meaning.clone(),
887 num_buffers: self.num_buffers,
888 max_visible_level,
889 has_large_chunk: self.has_large_chunk,
890 }))
891 }
892
893 fn num_rows(&self) -> u64 {
894 self.num_rows
895 }
896}
897
898#[derive(Debug, Clone)]
902pub(crate) enum LevelCodec {
903 Uncompressed,
905 Rle(Arc<RleDecompressor>),
907 Block(Arc<dyn BlockDecompressor>),
910}
911
912impl LevelCodec {
913 fn try_new(
914 encoding: Option<&CompressiveEncoding>,
915 decompression_strategy: &dyn DecompressionStrategy,
916 ) -> Result<Self> {
917 match encoding {
918 None => Ok(Self::Uncompressed),
919 Some(encoding) => match encoding.compression.as_ref() {
920 Some(Compression::Rle(rle)) => Ok(Self::Rle(Arc::new(create_rle_decompressor(
921 rle,
922 decompression_strategy,
923 )?))),
924 _ => Ok(Self::Block(Arc::from(
925 decompression_strategy.create_block_decompressor(encoding)?,
926 ))),
927 },
928 }
929 }
930}
931
932#[derive(Debug)]
933enum RunEnds {
934 U16(Box<[u16]>),
935 U32(Box<[u32]>),
936 U64(Box<[u64]>),
937}
938
939impl RunEnds {
940 fn width_for(num_values: usize) -> usize {
941 if u16::try_from(num_values).is_ok() {
942 std::mem::size_of::<u16>()
943 } else if u32::try_from(num_values).is_ok() {
944 std::mem::size_of::<u32>()
945 } else {
946 std::mem::size_of::<u64>()
947 }
948 }
949
950 fn len(&self) -> usize {
951 match self {
952 Self::U16(ends) => ends.len(),
953 Self::U32(ends) => ends.len(),
954 Self::U64(ends) => ends.len(),
955 }
956 }
957
958 fn get(&self, run: usize) -> usize {
959 match self {
960 Self::U16(ends) => ends[run] as usize,
961 Self::U32(ends) => ends[run] as usize,
962 Self::U64(ends) => ends[run] as usize,
963 }
964 }
965
966 fn partition_point(&self, logical_index: usize) -> usize {
967 match self {
968 Self::U16(ends) => ends.partition_point(|&end| end as usize <= logical_index),
969 Self::U32(ends) => ends.partition_point(|&end| end as usize <= logical_index),
970 Self::U64(ends) => ends.partition_point(|&end| end as usize <= logical_index),
971 }
972 }
973
974 fn deep_size(&self) -> usize {
975 match self {
976 Self::U16(ends) => std::mem::size_of_val(ends.as_ref()),
977 Self::U32(ends) => std::mem::size_of_val(ends.as_ref()),
978 Self::U64(ends) => std::mem::size_of_val(ends.as_ref()),
979 }
980 }
981}
982
983enum RunEndsBuilder {
984 U16(Vec<u16>),
985 U32(Vec<u32>),
986 U64(Vec<u64>),
987}
988
989impl RunEndsBuilder {
990 fn with_capacity(num_values: usize, capacity: usize) -> Self {
991 if u16::try_from(num_values).is_ok() {
992 Self::U16(Vec::with_capacity(capacity))
993 } else if u32::try_from(num_values).is_ok() {
994 Self::U32(Vec::with_capacity(capacity))
995 } else {
996 Self::U64(Vec::with_capacity(capacity))
997 }
998 }
999
1000 fn push(&mut self, end: usize) -> Result<()> {
1001 match self {
1002 Self::U16(ends) => ends.push(
1003 u16::try_from(end)
1004 .map_err(|_| Error::internal(format!("Run end {end} does not fit in u16")))?,
1005 ),
1006 Self::U32(ends) => ends.push(
1007 u32::try_from(end)
1008 .map_err(|_| Error::internal(format!("Run end {end} does not fit in u32")))?,
1009 ),
1010 Self::U64(ends) => ends.push(end as u64),
1011 }
1012 Ok(())
1013 }
1014
1015 fn set_last(&mut self, end: usize) -> Result<()> {
1016 match self {
1017 Self::U16(ends) => {
1018 let last = ends.last_mut().ok_or_else(|| {
1019 Error::internal("Cannot extend an empty coalesced run buffer")
1020 })?;
1021 *last = u16::try_from(end)
1022 .map_err(|_| Error::internal(format!("Run end {end} does not fit in u16")))?;
1023 }
1024 Self::U32(ends) => {
1025 let last = ends.last_mut().ok_or_else(|| {
1026 Error::internal("Cannot extend an empty coalesced run buffer")
1027 })?;
1028 *last = u32::try_from(end)
1029 .map_err(|_| Error::internal(format!("Run end {end} does not fit in u32")))?;
1030 }
1031 Self::U64(ends) => {
1032 let last = ends.last_mut().ok_or_else(|| {
1033 Error::internal("Cannot extend an empty coalesced run buffer")
1034 })?;
1035 *last = end as u64;
1036 }
1037 }
1038 Ok(())
1039 }
1040
1041 fn finish(self) -> RunEnds {
1042 match self {
1043 Self::U16(ends) => RunEnds::U16(ends.into_boxed_slice()),
1044 Self::U32(ends) => RunEnds::U32(ends.into_boxed_slice()),
1045 Self::U64(ends) => RunEnds::U64(ends.into_boxed_slice()),
1046 }
1047 }
1048}
1049
1050#[derive(Debug)]
1051enum RunStorage {
1052 Physical(RleRuns),
1053 Coalesced { values: Box<[u16]>, ends: RunEnds },
1054}
1055
1056impl RunStorage {
1057 fn len(&self) -> usize {
1058 match self {
1059 Self::Physical(runs) => runs.num_values(),
1060 Self::Coalesced { ends, .. } => ends.get(ends.len() - 1),
1061 }
1062 }
1063
1064 fn num_runs(&self) -> usize {
1065 match self {
1066 Self::Physical(runs) => runs.num_runs(),
1067 Self::Coalesced { values, .. } => values.len(),
1068 }
1069 }
1070
1071 fn value(&self, run: usize) -> u16 {
1072 match self {
1073 Self::Physical(runs) => runs.value(run),
1074 Self::Coalesced { values, .. } => values[run],
1075 }
1076 }
1077
1078 fn first_value_above(&self, max: u16) -> Option<(usize, u16)> {
1079 (0..self.num_runs()).find_map(|run| {
1080 let value = self.value(run);
1081 (value > max).then_some((run, value))
1082 })
1083 }
1084
1085 fn seek(&self, position: &mut RunPosition, logical_index: usize) {
1086 if logical_index >= self.len() {
1087 *position = RunPosition {
1088 run: self.num_runs(),
1089 start: self.len(),
1090 end: self.len(),
1091 };
1092 return;
1093 }
1094
1095 match self {
1096 Self::Physical(runs) => {
1097 if position.run >= runs.num_runs()
1098 || position.end == 0
1099 || logical_index < position.start
1100 {
1101 *position = RunPosition {
1102 run: 0,
1103 start: 0,
1104 end: runs.length(0),
1105 };
1106 }
1107 while position.end <= logical_index {
1108 self.advance(position);
1109 }
1110 }
1111 Self::Coalesced { ends, .. } => {
1112 if logical_index < position.start || logical_index >= position.end {
1113 let run = ends.partition_point(logical_index);
1114 *position = RunPosition {
1115 run,
1116 start: if run == 0 { 0 } else { ends.get(run - 1) },
1117 end: ends.get(run),
1118 };
1119 }
1120 }
1121 }
1122 }
1123
1124 fn advance(&self, position: &mut RunPosition) {
1125 let next_run = position.run + 1;
1126 if next_run >= self.num_runs() {
1127 *position = RunPosition {
1128 run: self.num_runs(),
1129 start: self.len(),
1130 end: self.len(),
1131 };
1132 return;
1133 }
1134
1135 let start = position.end;
1136 position.run = next_run;
1137 position.start = start;
1138 position.end = match self {
1139 Self::Physical(runs) => start + runs.length(next_run),
1140 Self::Coalesced { ends, .. } => ends.get(next_run),
1141 };
1142 }
1143
1144 fn deep_size(&self) -> usize {
1145 match self {
1146 Self::Physical(runs) => runs.deep_size(),
1147 Self::Coalesced { values, ends } => {
1148 std::mem::size_of_val(values.as_ref()) + ends.deep_size()
1149 }
1150 }
1151 }
1152}
1153
1154#[derive(Debug, Clone)]
1160enum LazyLevels {
1161 Dense(ScalarBuffer<u16>),
1162 Runs(Arc<RunStorage>),
1163}
1164
1165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1166enum LevelPlan {
1167 Physical,
1168 Coalesced,
1169 Dense,
1170}
1171
1172#[derive(Debug, Default, Clone, Copy)]
1173struct RunPosition {
1174 run: usize,
1175 start: usize,
1176 end: usize,
1177}
1178
1179#[derive(Debug, Default, Clone, Copy)]
1185struct LevelCursor {
1186 level: usize,
1188 row: u64,
1190 run: RunPosition,
1192}
1193
1194impl LazyLevels {
1195 fn from_rle_runs(runs: RleRuns) -> Result<Self> {
1196 let plan = Self::select_plan(&runs);
1197 match plan {
1198 LevelPlan::Physical => Ok(Self::Runs(Arc::new(RunStorage::Physical(
1199 runs.into_owned(),
1200 )))),
1201 LevelPlan::Coalesced => Self::build_coalesced(runs),
1202 LevelPlan::Dense => Self::build_dense(runs),
1203 }
1204 }
1205
1206 fn select_plan(runs: &RleRuns) -> LevelPlan {
1209 if runs.num_values() == 0 {
1210 return LevelPlan::Dense;
1211 }
1212
1213 let run_storage_size = std::mem::size_of::<RunStorage>() as u128;
1214 let physical_size = run_storage_size + runs.owned_size() as u128;
1215 let coalesced_size = run_storage_size
1216 + (runs.coalesced_runs() as u128)
1217 * (std::mem::size_of::<u16>() + RunEnds::width_for(runs.num_values())) as u128;
1218 let dense_size = (runs.num_values() as u128) * std::mem::size_of::<u16>() as u128;
1219 [
1220 (physical_size, runs.num_runs(), 0usize, LevelPlan::Physical),
1221 (
1222 coalesced_size,
1223 runs.coalesced_runs(),
1224 1usize,
1225 LevelPlan::Coalesced,
1226 ),
1227 (dense_size, runs.num_values(), 2usize, LevelPlan::Dense),
1228 ]
1229 .into_iter()
1230 .min_by_key(|(size, traversal, priority, _)| (*size, *traversal, *priority))
1231 .map(|(_, _, _, plan)| plan)
1232 .unwrap_or(LevelPlan::Dense)
1233 }
1234
1235 fn build_coalesced(runs: RleRuns) -> Result<Self> {
1236 let mut values = Vec::with_capacity(runs.coalesced_runs());
1237 let mut ends = RunEndsBuilder::with_capacity(runs.num_values(), runs.coalesced_runs());
1238 let mut logical_end = 0usize;
1239 for (value, length) in runs.iter() {
1240 logical_end = logical_end
1241 .checked_add(length)
1242 .ok_or_else(|| Error::internal("Validated RLE run length sum overflowed usize"))?;
1243 if values.last().copied() == Some(value) {
1244 ends.set_last(logical_end)?;
1245 } else {
1246 values.push(value);
1247 ends.push(logical_end)?;
1248 }
1249 }
1250 Ok(Self::Runs(Arc::new(RunStorage::Coalesced {
1251 values: values.into_boxed_slice(),
1252 ends: ends.finish(),
1253 })))
1254 }
1255
1256 fn build_dense(runs: RleRuns) -> Result<Self> {
1257 let mut values = Vec::new();
1258 values.try_reserve_exact(runs.num_values()).map_err(|_| {
1259 Error::internal(format!(
1260 "Cannot allocate {} dense repetition/definition levels",
1261 runs.num_values()
1262 ))
1263 })?;
1264 for (value, length) in runs.iter() {
1265 values.resize(values.len() + length, value);
1266 }
1267 Ok(Self::Dense(ScalarBuffer::from(values)))
1268 }
1269
1270 fn len(&self) -> usize {
1271 match self {
1272 Self::Dense(buf) => buf.len(),
1273 Self::Runs(runs) => runs.len(),
1274 }
1275 }
1276
1277 fn validate_max(&self, level_type: &str, max: u16) -> Result<()> {
1278 let invalid = match self {
1279 Self::Dense(levels) => levels
1280 .iter()
1281 .enumerate()
1282 .find_map(|(index, &value)| (value > max).then_some(("index", index, value))),
1283 Self::Runs(runs) => runs
1284 .first_value_above(max)
1285 .map(|(run, value)| ("run", run, value)),
1286 };
1287 if let Some((position_type, position, value)) = invalid {
1288 return Err(Error::invalid_input_source(
1289 format!(
1290 "Invalid {level_type} level {value} at {position_type} {position}: maximum is {max}"
1291 )
1292 .into(),
1293 ));
1294 }
1295 Ok(())
1296 }
1297
1298 fn seek_row_start(
1305 &self,
1306 cursor: &mut LevelCursor,
1307 target_row: u64,
1308 max_rep: u16,
1309 ) -> Result<usize> {
1310 let mut need = target_row.checked_sub(cursor.row).ok_or_else(|| {
1311 Error::internal(format!(
1312 "Complex all-null row ranges are not sorted: target row {target_row} follows {}",
1313 cursor.row
1314 ))
1315 })?;
1316 if need == 0 {
1317 return Ok(cursor.level);
1318 }
1319 match self {
1320 Self::Dense(buf) => {
1321 let mut level = cursor.level;
1322 while need > 0 {
1323 if level >= buf.len() {
1324 return Err(Error::internal(
1325 "Invalid complex all-null layout: repetition buffer too short",
1326 ));
1327 }
1328 if buf[level] != max_rep {
1329 return Err(Error::internal(
1330 "Invalid complex all-null layout: row did not start at max repetition level",
1331 ));
1332 }
1333 level += 1;
1334 while level < buf.len() && buf[level] != max_rep {
1335 level += 1;
1336 }
1337 need -= 1;
1338 }
1339 cursor.level = level;
1340 cursor.row = target_row;
1341 Ok(level)
1342 }
1343 Self::Runs(runs) => {
1344 let mut level = cursor.level;
1345 let mut run = cursor.run;
1346 runs.seek(&mut run, level);
1347 while need > 0 {
1348 if run.run >= runs.num_runs() {
1349 return Err(Error::internal(
1350 "Invalid complex all-null layout: repetition buffer too short",
1351 ));
1352 }
1353 if runs.value(run.run) != max_rep {
1354 return Err(Error::internal(
1355 "Invalid complex all-null layout: row did not start at max repetition level",
1356 ));
1357 }
1358 let avail = (run.end - level) as u64;
1359 if need < avail {
1360 level += need as usize;
1362 need = 0;
1363 } else {
1364 need -= avail;
1367 runs.advance(&mut run);
1368 while run.run < runs.num_runs() && runs.value(run.run) != max_rep {
1369 runs.advance(&mut run);
1370 }
1371 level = if run.run < runs.num_runs() {
1372 run.start
1373 } else {
1374 self.len()
1375 };
1376 }
1377 }
1378 cursor.level = level;
1379 cursor.row = target_row;
1380 cursor.run = run;
1381 Ok(level)
1382 }
1383 }
1384 }
1385
1386 fn count_le_cursor(
1392 &self,
1393 run_cursor: &mut RunPosition,
1394 range: Range<usize>,
1395 max: u16,
1396 ) -> (u64, RunPosition) {
1397 if range.is_empty() {
1398 return (0, *run_cursor);
1399 }
1400 match self {
1401 Self::Dense(buf) => (
1402 buf[range].iter().filter(|&&d| d <= max).count() as u64,
1403 RunPosition::default(),
1404 ),
1405 Self::Runs(runs) => {
1406 runs.seek(run_cursor, range.start);
1408 let start = *run_cursor;
1409 let mut count = 0u64;
1410 let mut current = *run_cursor;
1411 while current.run < runs.num_runs() && current.start < range.end {
1412 if runs.value(current.run) <= max {
1413 let lo = current.start.max(range.start);
1414 let hi = current.end.min(range.end);
1415 count += (hi - lo) as u64;
1416 }
1417 if current.end >= range.end {
1418 break;
1419 }
1420 runs.advance(&mut current);
1421 }
1422 *run_cursor = current;
1425 (count, start)
1426 }
1427 }
1428 }
1429
1430 fn extend_into(&self, range: Range<usize>, run: RunPosition, out: &mut Vec<u16>) {
1431 if range.is_empty() {
1432 return;
1433 }
1434 match self {
1435 Self::Dense(buf) => out.extend_from_slice(&buf[range]),
1436 Self::Runs(runs) => {
1437 let mut current = run;
1438 runs.seek(&mut current, range.start);
1439 while current.run < runs.num_runs() && current.start < range.end {
1440 let lo = current.start.max(range.start);
1441 let hi = current.end.min(range.end);
1442 if hi > lo {
1443 out.resize(out.len() + (hi - lo), runs.value(current.run));
1444 }
1445 runs.advance(&mut current);
1446 }
1447 }
1448 }
1449 }
1450
1451 #[cfg(test)]
1452 fn deep_size(&self) -> usize {
1453 self.deep_size_of_children(&mut Context::new())
1454 }
1455}
1456
1457impl DeepSizeOf for LazyLevels {
1458 fn deep_size_of_children(&self, ctx: &mut Context) -> usize {
1459 match self {
1460 Self::Dense(buf) => buf.deep_size_of_children(ctx),
1461 Self::Runs(runs) => {
1462 let pointer = Arc::as_ptr(runs) as *const () as usize;
1463 if ctx.mark_seen(pointer) {
1464 std::mem::size_of_val(runs.as_ref()) + runs.deep_size()
1465 } else {
1466 0
1467 }
1468 }
1469 }
1470 }
1471}
1472
1473fn validate_complex_all_null_levels(
1474 rep: &Option<LazyLevels>,
1475 def: &Option<LazyLevels>,
1476 max_rep: u16,
1477 max_def: u16,
1478) -> Result<()> {
1479 if let Some(rep) = rep {
1480 rep.validate_max("repetition", max_rep)?;
1481 }
1482 if let Some(def) = def {
1483 def.validate_max("definition", max_def)?;
1484 }
1485 if let (Some(rep), Some(def)) = (rep, def)
1486 && rep.len() != def.len()
1487 {
1488 return Err(Error::invalid_input_source(
1489 format!(
1490 "Mismatched complex all-null level counts: repetition has {}, definition has {}",
1491 rep.len(),
1492 def.len()
1493 )
1494 .into(),
1495 ));
1496 }
1497 Ok(())
1498}
1499
1500fn expected_level_bytes(num_values: u64, level_type: &str) -> Result<usize> {
1501 usize::try_from(num_values)
1502 .ok()
1503 .and_then(|num_values| num_values.checked_mul(std::mem::size_of::<u16>()))
1504 .ok_or_else(|| {
1505 Error::invalid_input_source(
1506 format!("{level_type} level count {num_values} does not fit in memory").into(),
1507 )
1508 })
1509}
1510
1511fn dense_levels_from_block(
1512 decompressed: DataBlock,
1513 num_values: u64,
1514 level_type: &str,
1515) -> Result<LazyLevels> {
1516 let DataBlock::FixedWidth(block) = decompressed else {
1517 return Err(Error::invalid_input_source(
1518 format!("Expected fixed-width data block for {level_type} levels").into(),
1519 ));
1520 };
1521 if block.num_values != num_values {
1522 return Err(Error::invalid_input_source(
1523 format!(
1524 "Unexpected {level_type} level count after decompression: expected {num_values}, got {}",
1525 block.num_values
1526 )
1527 .into(),
1528 ));
1529 }
1530 if block.bits_per_value != 16 {
1531 return Err(Error::invalid_input_source(
1532 format!(
1533 "Unexpected {level_type} level bit width after decompression: expected 16, got {}",
1534 block.bits_per_value
1535 )
1536 .into(),
1537 ));
1538 }
1539 let expected_bytes = expected_level_bytes(num_values, level_type)?;
1540 if block.data.len() != expected_bytes {
1541 return Err(Error::invalid_input_source(
1542 format!(
1543 "Unexpected decompressed {level_type} level size: expected {expected_bytes} bytes for {num_values} values, got {}",
1544 block.data.len()
1545 )
1546 .into(),
1547 ));
1548 }
1549 Ok(LazyLevels::Dense(block.data.borrow_to_typed_slice::<u16>()))
1550}
1551
1552#[derive(Debug)]
1553struct CachedComplexAllNullState {
1554 rep: Option<LazyLevels>,
1555 def: Option<LazyLevels>,
1556}
1557
1558impl DeepSizeOf for CachedComplexAllNullState {
1559 fn deep_size_of_children(&self, ctx: &mut Context) -> usize {
1560 self.rep.deep_size_of_children(ctx) + self.def.deep_size_of_children(ctx)
1561 }
1562}
1563
1564impl CachedPageData for CachedComplexAllNullState {
1565 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
1566 self
1567 }
1568}
1569
1570#[derive(Debug)]
1579pub struct ComplexAllNullScheduler {
1580 buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
1582 def_meaning: Arc<[DefinitionInterpretation]>,
1583 repdef: Option<Arc<CachedComplexAllNullState>>,
1584 max_rep: u16,
1585 max_def: u16,
1586 max_visible_level: u16,
1587 rep_codec: LevelCodec,
1588 def_codec: LevelCodec,
1589 num_rep_values: u64,
1590 num_def_values: u64,
1591}
1592
1593impl ComplexAllNullScheduler {
1594 pub(crate) fn new(
1595 buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
1596 def_meaning: Arc<[DefinitionInterpretation]>,
1597 rep_codec: LevelCodec,
1598 def_codec: LevelCodec,
1599 num_rep_values: u64,
1600 num_def_values: u64,
1601 ) -> Self {
1602 let max_rep = def_meaning.iter().filter(|l| l.is_list()).count() as u16;
1603 let max_def = def_meaning
1604 .iter()
1605 .map(|meaning| meaning.num_def_levels())
1606 .sum::<u16>();
1607 let max_visible_level = def_meaning
1608 .iter()
1609 .take_while(|l| !l.is_list())
1610 .map(|l| l.num_def_levels())
1611 .sum::<u16>();
1612 Self {
1613 buffer_offsets_and_sizes,
1614 def_meaning,
1615 repdef: None,
1616 max_rep,
1617 max_def,
1618 max_visible_level,
1619 rep_codec,
1620 def_codec,
1621 num_rep_values,
1622 num_def_values,
1623 }
1624 }
1625}
1626
1627impl StructuralPageScheduler for ComplexAllNullScheduler {
1628 fn initialize<'a>(
1629 &'a mut self,
1630 io: &Arc<dyn EncodingsIo>,
1631 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
1632 let (rep_pos, rep_size) = self.buffer_offsets_and_sizes[0];
1634 let (def_pos, def_size) = self.buffer_offsets_and_sizes[1];
1635 let has_rep = rep_size > 0;
1636 let has_def = def_size > 0;
1637
1638 let mut reads = Vec::with_capacity(2);
1639 if has_rep {
1640 reads.push(rep_pos..rep_pos + rep_size);
1641 }
1642 if has_def {
1643 reads.push(def_pos..def_pos + def_size);
1644 }
1645
1646 let data = io.submit_request(reads, 0);
1647 let rep_codec = self.rep_codec.clone();
1648 let def_codec = self.def_codec.clone();
1649 let num_rep_values = self.num_rep_values;
1650 let num_def_values = self.num_def_values;
1651 let max_rep = self.max_rep;
1652 let max_def = self.max_def;
1653
1654 async move {
1655 let data = data.await?;
1656 let mut data_iter = data.into_iter();
1657
1658 let build_levels = |compressed_bytes: Bytes,
1661 codec: &LevelCodec,
1662 num_values: u64,
1663 level_type: &str|
1664 -> Result<LazyLevels> {
1665 match codec {
1666 LevelCodec::Uncompressed => {
1667 if num_values == 0 {
1668 if !compressed_bytes
1669 .len()
1670 .is_multiple_of(std::mem::size_of::<u16>())
1671 {
1672 return Err(Error::invalid_input_source(
1673 format!(
1674 "Unexpected uncompressed {level_type} level size: {} bytes is not divisible by {}",
1675 compressed_bytes.len(),
1676 std::mem::size_of::<u16>()
1677 )
1678 .into(),
1679 ));
1680 }
1681 } else {
1682 let expected_bytes = expected_level_bytes(num_values, level_type)?;
1683 if compressed_bytes.len() != expected_bytes {
1684 return Err(Error::invalid_input_source(
1685 format!(
1686 "Unexpected uncompressed {level_type} level size: expected {expected_bytes} bytes for {num_values} values, got {}",
1687 compressed_bytes.len()
1688 )
1689 .into(),
1690 ));
1691 }
1692 }
1693 let buffer = LanceBuffer::from_bytes(compressed_bytes, 2);
1694 Ok(LazyLevels::Dense(buffer.borrow_to_typed_slice::<u16>()))
1695 }
1696 LevelCodec::Rle(decompressor) => {
1697 let frame = LanceBuffer::from_bytes(compressed_bytes, 1);
1698 let runs = decompressor.decode_u16_runs(frame, num_values)?;
1699 LazyLevels::from_rle_runs(runs)
1700 }
1701 LevelCodec::Block(decompressor) => {
1702 let frame = LanceBuffer::from_bytes(compressed_bytes, 1);
1703 let decompressed = decompressor.decompress(Some(frame), num_values)?;
1704 dense_levels_from_block(decompressed, num_values, level_type)
1705 }
1706 }
1707 };
1708
1709 let rep = if has_rep {
1710 let rep = data_iter.next().unwrap();
1711 Some(build_levels(rep, &rep_codec, num_rep_values, "repetition")?)
1712 } else {
1713 None
1714 };
1715
1716 let def = if has_def {
1717 let def = data_iter.next().unwrap();
1718 Some(build_levels(def, &def_codec, num_def_values, "definition")?)
1719 } else {
1720 None
1721 };
1722
1723 validate_complex_all_null_levels(&rep, &def, max_rep, max_def)?;
1724 let repdef = Arc::new(CachedComplexAllNullState { rep, def });
1725
1726 self.repdef = Some(repdef.clone());
1727
1728 Ok(repdef as Arc<dyn CachedPageData>)
1729 }
1730 .boxed()
1731 }
1732
1733 fn load(&mut self, data: &Arc<dyn CachedPageData>) {
1734 self.repdef = Some(
1735 data.clone()
1736 .as_arc_any()
1737 .downcast::<CachedComplexAllNullState>()
1738 .unwrap(),
1739 );
1740 }
1741
1742 fn schedule_ranges(
1743 &self,
1744 ranges: &[Range<u64>],
1745 _io: &Arc<dyn EncodingsIo>,
1746 ) -> Result<Vec<PageLoadTask>> {
1747 let ranges = VecDeque::from_iter(ranges.iter().cloned());
1748 let num_rows = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
1749 let decoder = Box::new(ComplexAllNullPageDecoder {
1750 ranges,
1751 rep: self.repdef.as_ref().unwrap().rep.clone(),
1752 def: self.repdef.as_ref().unwrap().def.clone(),
1753 num_rows,
1754 def_meaning: self.def_meaning.clone(),
1755 max_rep: self.max_rep,
1756 max_visible_level: self.max_visible_level,
1757 rep_cursor: LevelCursor::default(),
1758 def_run_cursor: RunPosition::default(),
1759 }) as Box<dyn StructuralPageDecoder>;
1760 let page_load_task = PageLoadTask {
1761 decoder_fut: std::future::ready(Ok(decoder)).boxed(),
1762 num_rows,
1763 };
1764 Ok(vec![page_load_task])
1765 }
1766}
1767
1768#[derive(Debug)]
1769pub struct ComplexAllNullPageDecoder {
1770 ranges: VecDeque<Range<u64>>,
1771 rep: Option<LazyLevels>,
1772 def: Option<LazyLevels>,
1773 num_rows: u64,
1774 def_meaning: Arc<[DefinitionInterpretation]>,
1775 max_rep: u16,
1776 max_visible_level: u16,
1777 rep_cursor: LevelCursor,
1779 def_run_cursor: RunPosition,
1781}
1782
1783impl ComplexAllNullPageDecoder {
1784 fn drain_ranges(&mut self, num_rows: u64) -> Vec<Range<u64>> {
1785 let mut rows_desired = num_rows;
1786 let mut ranges = Vec::with_capacity(self.ranges.len());
1787 while rows_desired > 0 {
1788 let front = self.ranges.front_mut().unwrap();
1789 let avail = front.end - front.start;
1790 if avail > rows_desired {
1791 ranges.push(front.start..front.start + rows_desired);
1792 front.start += rows_desired;
1793 rows_desired = 0;
1794 } else {
1795 ranges.push(self.ranges.pop_front().unwrap());
1796 rows_desired -= avail;
1797 }
1798 }
1799 ranges
1800 }
1801
1802 fn seek_row_start(&mut self, target_row: u64) -> Result<usize> {
1805 match &self.rep {
1806 Some(rep) => rep.seek_row_start(&mut self.rep_cursor, target_row, self.max_rep),
1807 None => {
1808 self.rep_cursor.row = target_row;
1810 self.rep_cursor.level = target_row as usize;
1811 Ok(target_row as usize)
1812 }
1813 }
1814 }
1815
1816 fn count_visible(&mut self, levels: Range<usize>) -> Result<(u64, RunPosition)> {
1819 match &self.def {
1820 Some(def) => {
1821 if levels.end > def.len() {
1822 return Err(Error::internal(
1823 "Invalid complex all-null layout: definition buffer too short",
1824 ));
1825 }
1826 Ok(def.count_le_cursor(&mut self.def_run_cursor, levels, self.max_visible_level))
1827 }
1828 None => Ok(((levels.end - levels.start) as u64, RunPosition::default())),
1829 }
1830 }
1831}
1832
1833impl StructuralPageDecoder for ComplexAllNullPageDecoder {
1834 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
1835 let drained_ranges = self.drain_ranges(num_rows);
1836 let mut level_slices: Vec<LevelSlice> = Vec::with_capacity(drained_ranges.len());
1837 let mut visible_items_total = 0;
1838
1839 for range in drained_ranges {
1844 let level_start = self.seek_row_start(range.start)?;
1845 let rep_run = self.rep_cursor.run;
1846 let level_end = self.seek_row_start(range.end)?;
1847 let (visible_items, def_run) = self.count_visible(level_start..level_end)?;
1848 visible_items_total += visible_items;
1849 if let Some(last) = level_slices.last_mut()
1850 && last.range.end == level_start
1851 {
1852 last.range.end = level_end;
1853 } else {
1854 level_slices.push(LevelSlice {
1855 range: level_start..level_end,
1856 rep_run,
1857 def_run,
1858 });
1859 }
1860 }
1861
1862 Ok(Box::new(DecodeComplexAllNullTask {
1863 level_slices,
1864 visible_items_total,
1865 rep: self.rep.clone(),
1866 def: self.def.clone(),
1867 def_meaning: self.def_meaning.clone(),
1868 max_visible_level: self.max_visible_level,
1869 }))
1870 }
1871
1872 fn num_rows(&self) -> u64 {
1873 self.num_rows
1874 }
1875}
1876
1877#[derive(Debug, Clone)]
1880struct LevelSlice {
1881 range: Range<usize>,
1882 rep_run: RunPosition,
1883 def_run: RunPosition,
1884}
1885
1886#[derive(Clone, Copy)]
1887enum LevelKind {
1888 Repetition,
1889 Definition,
1890}
1891
1892impl LevelSlice {
1893 fn run(&self, kind: LevelKind) -> RunPosition {
1894 match kind {
1895 LevelKind::Repetition => self.rep_run,
1896 LevelKind::Definition => self.def_run,
1897 }
1898 }
1899}
1900
1901#[derive(Debug)]
1902pub struct DecodeComplexAllNullTask {
1903 level_slices: Vec<LevelSlice>,
1904 visible_items_total: u64,
1905 rep: Option<LazyLevels>,
1906 def: Option<LazyLevels>,
1907 def_meaning: Arc<[DefinitionInterpretation]>,
1908 max_visible_level: u16,
1909}
1910
1911impl DecodeComplexAllNullTask {
1912 fn decode_level(&self, levels: &Option<LazyLevels>, kind: LevelKind) -> Option<Vec<u16>> {
1913 levels.as_ref().map(|levels| {
1914 let num_levels = self
1915 .level_slices
1916 .iter()
1917 .map(|slice| slice.range.end - slice.range.start)
1918 .sum();
1919 let mut referenced_levels = Vec::with_capacity(num_levels);
1920 for slice in &self.level_slices {
1921 levels.extend_into(slice.range.clone(), slice.run(kind), &mut referenced_levels);
1922 }
1923 referenced_levels
1924 })
1925 }
1926}
1927
1928impl DecodePageTask for DecodeComplexAllNullTask {
1929 fn decode(self: Box<Self>) -> Result<DecodedPage> {
1930 let rep = self.decode_level(&self.rep, LevelKind::Repetition);
1931 let def = self.decode_level(&self.def, LevelKind::Definition);
1932
1933 let num_values = if let Some(def) = &def {
1937 def.iter().filter(|&d| *d <= self.max_visible_level).count() as u64
1938 } else {
1939 self.visible_items_total
1940 };
1941
1942 let data = DataBlock::AllNull(AllNullDataBlock { num_values });
1943 let unraveler = RepDefUnraveler::new(rep, def, self.def_meaning, num_values);
1944 Ok(DecodedPage {
1945 data,
1946 repdef: unraveler,
1947 })
1948 }
1949}
1950
1951#[derive(Debug, Default)]
1956pub struct SimpleAllNullScheduler {}
1957
1958impl StructuralPageScheduler for SimpleAllNullScheduler {
1959 fn initialize<'a>(
1960 &'a mut self,
1961 _io: &Arc<dyn EncodingsIo>,
1962 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
1963 std::future::ready(Ok(Arc::new(NoCachedPageData) as Arc<dyn CachedPageData>)).boxed()
1964 }
1965
1966 fn load(&mut self, _cache: &Arc<dyn CachedPageData>) {}
1967
1968 fn schedule_ranges(
1969 &self,
1970 ranges: &[Range<u64>],
1971 _io: &Arc<dyn EncodingsIo>,
1972 ) -> Result<Vec<PageLoadTask>> {
1973 let num_rows = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
1974 let decoder =
1975 Box::new(SimpleAllNullPageDecoder { num_rows }) as Box<dyn StructuralPageDecoder>;
1976 let page_load_task = PageLoadTask {
1977 decoder_fut: std::future::ready(Ok(decoder)).boxed(),
1978 num_rows,
1979 };
1980 Ok(vec![page_load_task])
1981 }
1982}
1983
1984#[derive(Debug)]
1987struct SimpleAllNullDecodePageTask {
1988 num_values: u64,
1989}
1990impl DecodePageTask for SimpleAllNullDecodePageTask {
1991 fn decode(self: Box<Self>) -> Result<DecodedPage> {
1992 let unraveler = RepDefUnraveler::new(
1993 None,
1994 Some(vec![1; self.num_values as usize]),
1995 Arc::new([DefinitionInterpretation::NullableItem]),
1996 self.num_values,
1997 );
1998 Ok(DecodedPage {
1999 data: DataBlock::AllNull(AllNullDataBlock {
2000 num_values: self.num_values,
2001 }),
2002 repdef: unraveler,
2003 })
2004 }
2005}
2006
2007#[derive(Debug)]
2008pub struct SimpleAllNullPageDecoder {
2009 num_rows: u64,
2010}
2011
2012impl StructuralPageDecoder for SimpleAllNullPageDecoder {
2013 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
2014 Ok(Box::new(SimpleAllNullDecodePageTask {
2015 num_values: num_rows,
2016 }))
2017 }
2018
2019 fn num_rows(&self) -> u64 {
2020 self.num_rows
2021 }
2022}
2023
2024#[derive(Debug, Clone)]
2025struct MiniBlockSchedulerDictionary {
2026 dictionary_decompressor: Arc<dyn BlockDecompressor>,
2028 dictionary_buf_position_and_size: (u64, u64),
2029 dictionary_data_alignment: u64,
2030 num_dictionary_items: u64,
2031}
2032
2033#[derive(Debug)]
2035struct MiniBlockCacheableState {
2036 chunk_index: MiniBlockChunkIndex,
2038 dictionary: Option<Arc<DataBlock>>,
2040}
2041
2042impl DeepSizeOf for MiniBlockCacheableState {
2043 fn deep_size_of_children(&self, context: &mut Context) -> usize {
2044 self.chunk_index.deep_size_of_children(context)
2045 + self
2046 .dictionary
2047 .as_ref()
2048 .map(|dict| dict.data_size() as usize)
2049 .unwrap_or(0)
2050 }
2051}
2052
2053impl CachedPageData for MiniBlockCacheableState {
2054 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
2055 self
2056 }
2057}
2058
2059#[derive(Debug)]
2086pub struct MiniBlockScheduler {
2087 buffer_offsets_and_sizes: Vec<(u64, u64)>,
2089 priority: u64,
2090 items_in_page: u64,
2091 repetition_index_depth: u16,
2092 num_buffers: u64,
2093 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
2094 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
2095 value_decompressor: Arc<dyn MiniBlockDecompressor>,
2096 def_meaning: Arc<[DefinitionInterpretation]>,
2097 dictionary: Option<MiniBlockSchedulerDictionary>,
2098 page_meta: Option<Arc<MiniBlockCacheableState>>,
2100 has_large_chunk: bool,
2101}
2102
2103impl MiniBlockScheduler {
2104 fn try_new(
2105 buffer_offsets_and_sizes: &[(u64, u64)],
2106 priority: u64,
2107 items_in_page: u64,
2108 layout: &pb21::MiniBlockLayout,
2109 decompressors: &dyn DecompressionStrategy,
2110 ) -> Result<Self> {
2111 let rep_decompressor = layout
2112 .rep_compression
2113 .as_ref()
2114 .map(|rep_compression| {
2115 decompressors
2116 .create_block_decompressor(rep_compression)
2117 .map(Arc::from)
2118 })
2119 .transpose()?;
2120 let def_decompressor = layout
2121 .def_compression
2122 .as_ref()
2123 .map(|def_compression| {
2124 decompressors
2125 .create_block_decompressor(def_compression)
2126 .map(Arc::from)
2127 })
2128 .transpose()?;
2129 let def_meaning = layout
2130 .layers
2131 .iter()
2132 .map(|l| ProtobufUtils21::repdef_layer_to_def_interp(*l))
2133 .collect::<Vec<_>>();
2134 let value_decompressor = decompressors.create_miniblock_decompressor(
2135 layout.value_compression.as_ref().unwrap(),
2136 decompressors,
2137 )?;
2138
2139 let dictionary = if let Some(dictionary_encoding) = layout.dictionary.as_ref() {
2140 let num_dictionary_items = layout.num_dictionary_items;
2141 let dictionary_decompressor = decompressors
2142 .create_block_decompressor(dictionary_encoding)?
2143 .into();
2144 let dictionary_data_alignment = match dictionary_encoding.compression.as_ref().unwrap()
2145 {
2146 Compression::Variable(_) => 4,
2147 Compression::Flat(_) => 16,
2148 Compression::General(_) => 1,
2149 Compression::InlineBitpacking(_) | Compression::OutOfLineBitpacking(_) => {
2150 crate::encoder::MIN_PAGE_BUFFER_ALIGNMENT
2151 }
2152 _ => {
2153 return Err(Error::invalid_input_source(
2154 format!(
2155 "Unsupported mini-block dictionary encoding: {:?}",
2156 dictionary_encoding.compression.as_ref().unwrap()
2157 )
2158 .into(),
2159 ));
2160 }
2161 };
2162 Some(MiniBlockSchedulerDictionary {
2163 dictionary_decompressor,
2164 dictionary_buf_position_and_size: buffer_offsets_and_sizes[2],
2165 dictionary_data_alignment,
2166 num_dictionary_items,
2167 })
2168 } else {
2169 None
2170 };
2171
2172 Ok(Self {
2173 buffer_offsets_and_sizes: buffer_offsets_and_sizes.to_vec(),
2174 rep_decompressor,
2175 def_decompressor,
2176 value_decompressor: value_decompressor.into(),
2177 repetition_index_depth: layout.repetition_index_depth as u16,
2178 num_buffers: layout.num_buffers,
2179 priority,
2180 items_in_page,
2181 dictionary,
2182 def_meaning: def_meaning.into(),
2183 page_meta: None,
2184 has_large_chunk: layout.has_large_chunk,
2185 })
2186 }
2187
2188 fn lookup_chunks(&self, chunk_indices: &[usize]) -> Vec<LoadedChunk> {
2189 let chunk_index = &self.page_meta.as_ref().unwrap().chunk_index;
2190 chunk_indices
2191 .iter()
2192 .map(|&chunk_idx| LoadedChunk {
2193 byte_range: chunk_index.byte_range(chunk_idx),
2194 items_in_chunk: chunk_index.items_in_chunk(chunk_idx),
2195 chunk_idx,
2196 data: LanceBuffer::empty(),
2197 })
2198 .collect()
2199 }
2200}
2201
2202#[derive(Debug, PartialEq, Eq, Clone, Copy)]
2203enum PreambleAction {
2204 Take,
2205 Skip,
2206 Absent,
2207}
2208
2209#[derive(Clone, Debug, PartialEq, Eq)]
2244struct ChunkInstructions {
2245 chunk_idx: usize,
2247 preamble: PreambleAction,
2253 rows_to_skip: u64,
2257 rows_to_take: u64,
2260 take_trailer: bool,
2267}
2268
2269#[derive(Debug, PartialEq, Eq)]
2287struct ChunkDrainInstructions {
2288 chunk_instructions: ChunkInstructions,
2289 rows_to_skip: u64,
2290 rows_to_take: u64,
2291 preamble_action: PreambleAction,
2292}
2293
2294impl ChunkInstructions {
2295 fn schedule_instructions(
2301 chunk_index: &MiniBlockChunkIndex,
2302 user_ranges: &[Range<u64>],
2303 ) -> Vec<Self> {
2304 let num_chunks = chunk_index.num_chunks();
2307 let mut chunk_instructions = Vec::with_capacity(user_ranges.len());
2311
2312 for user_range in user_ranges {
2313 let mut rows_needed = user_range.end - user_range.start;
2314 let mut need_preamble = false;
2315
2316 let mut block_index = chunk_index.find_chunk(user_range.start);
2319
2320 let mut to_skip = user_range.start - chunk_index.first_row(block_index);
2321
2322 while rows_needed > 0 || need_preamble {
2323 if block_index >= num_chunks {
2325 log::warn!(
2326 "schedule_instructions inconsistency: block_index >= num_chunks, exiting early"
2327 );
2328 break;
2329 }
2330
2331 let starts_including_trailer = chunk_index.rows_in_chunk(block_index);
2332 let has_preamble = chunk_index.has_preamble(block_index);
2333 let has_trailer = chunk_index.has_trailer(block_index);
2334 let rows_avail = starts_including_trailer.saturating_sub(to_skip);
2335
2336 if rows_avail == 0 && to_skip == 0 {
2340 if has_preamble && need_preamble {
2342 chunk_instructions.push(Self {
2343 chunk_idx: block_index,
2344 preamble: PreambleAction::Take,
2345 rows_to_skip: 0,
2346 rows_to_take: 0,
2347 take_trailer: has_trailer,
2351 });
2352 if starts_including_trailer > 0 || block_index == num_chunks - 1 {
2356 need_preamble = false;
2357 }
2358 }
2359 block_index += 1;
2361 continue;
2362 }
2363
2364 if rows_avail == 0 && to_skip > 0 {
2368 to_skip -= starts_including_trailer;
2371 block_index += 1;
2372 continue;
2373 }
2374
2375 let rows_to_take = rows_avail.min(rows_needed);
2376 rows_needed -= rows_to_take;
2377
2378 let mut take_trailer = false;
2379 let preamble = if has_preamble {
2380 if need_preamble {
2381 PreambleAction::Take
2382 } else {
2383 PreambleAction::Skip
2384 }
2385 } else {
2386 PreambleAction::Absent
2387 };
2388
2389 if rows_to_take == rows_avail && has_trailer {
2391 take_trailer = true;
2392 need_preamble = true;
2393 } else {
2394 need_preamble = false;
2395 };
2396
2397 chunk_instructions.push(Self {
2398 preamble,
2399 chunk_idx: block_index,
2400 rows_to_skip: to_skip,
2401 rows_to_take,
2402 take_trailer,
2403 });
2404
2405 to_skip = 0;
2406 block_index += 1;
2407 }
2408 }
2409
2410 if user_ranges.len() > 1 {
2414 let mut write = 0;
2418 for read in 1..chunk_instructions.len() {
2419 let merges = {
2420 let last = &chunk_instructions[write];
2421 let candidate = &chunk_instructions[read];
2422 last.chunk_idx == candidate.chunk_idx
2423 && last.rows_to_take + last.rows_to_skip == candidate.rows_to_skip
2424 };
2425 if merges {
2426 let rows_to_take = chunk_instructions[read].rows_to_take;
2427 let take_trailer = chunk_instructions[read].take_trailer;
2428 let last = &mut chunk_instructions[write];
2429 last.rows_to_take += rows_to_take;
2430 last.take_trailer |= take_trailer;
2431 } else {
2432 write += 1;
2433 if write != read {
2434 chunk_instructions.swap(write, read);
2435 }
2436 }
2437 }
2438 chunk_instructions.truncate(write + 1);
2439 }
2440 chunk_instructions
2441 }
2442
2443 fn drain_from_instruction(
2444 &self,
2445 rows_desired: &mut u64,
2446 need_preamble: &mut bool,
2447 skip_in_chunk: &mut u64,
2448 ) -> (ChunkDrainInstructions, bool) {
2449 debug_assert!(!*need_preamble || *skip_in_chunk == 0);
2451 let rows_avail = self.rows_to_take - *skip_in_chunk;
2452 let has_preamble = self.preamble != PreambleAction::Absent;
2453 let preamble_action = match (*need_preamble, has_preamble) {
2454 (true, true) => PreambleAction::Take,
2455 (true, false) => panic!("Need preamble but there isn't one"),
2456 (false, true) => PreambleAction::Skip,
2457 (false, false) => PreambleAction::Absent,
2458 };
2459
2460 let rows_taking = if *rows_desired >= rows_avail {
2463 *need_preamble = self.take_trailer;
2471 rows_avail
2472 } else {
2473 *need_preamble = false;
2476 *rows_desired
2477 };
2478 let rows_skipped = *skip_in_chunk;
2479
2480 let consumed_chunk = if *rows_desired >= rows_avail {
2482 *rows_desired -= rows_avail;
2483 *skip_in_chunk = 0;
2484 true
2485 } else {
2486 *skip_in_chunk += *rows_desired;
2487 *rows_desired = 0;
2488 false
2489 };
2490
2491 (
2492 ChunkDrainInstructions {
2493 chunk_instructions: self.clone(),
2494 rows_to_skip: rows_skipped,
2495 rows_to_take: rows_taking,
2496 preamble_action,
2497 },
2498 consumed_chunk,
2499 )
2500 }
2501}
2502
2503enum Words {
2504 U16(ScalarBuffer<u16>),
2505 U32(ScalarBuffer<u32>),
2506}
2507
2508struct WordsIter<'a> {
2509 iter: Box<dyn Iterator<Item = u32> + 'a>,
2510}
2511
2512impl Words {
2513 pub fn len(&self) -> usize {
2514 match self {
2515 Self::U16(b) => b.len(),
2516 Self::U32(b) => b.len(),
2517 }
2518 }
2519
2520 pub fn iter(&self) -> WordsIter<'_> {
2521 match self {
2522 Self::U16(buf) => WordsIter {
2523 iter: Box::new(buf.iter().map(|&x| x as u32)),
2524 },
2525 Self::U32(buf) => WordsIter {
2526 iter: Box::new(buf.iter().copied()),
2527 },
2528 }
2529 }
2530
2531 pub fn from_bytes(bytes: Bytes, has_large_chunk: bool) -> Result<Self> {
2532 let bytes_per_value = if has_large_chunk { 4 } else { 2 };
2533 assert_eq!(bytes.len() % bytes_per_value, 0);
2534 let buffer = LanceBuffer::from_bytes(bytes, bytes_per_value as u64);
2535 if has_large_chunk {
2536 Ok(Self::U32(buffer.borrow_to_typed_slice::<u32>()))
2537 } else {
2538 Ok(Self::U16(buffer.borrow_to_typed_slice::<u16>()))
2539 }
2540 }
2541}
2542
2543impl<'a> Iterator for WordsIter<'a> {
2544 type Item = u32;
2545
2546 fn next(&mut self) -> Option<Self::Item> {
2547 self.iter.next()
2548 }
2549}
2550
2551struct FlatValueCounts {
2556 logs: Vec<u8>,
2557 uniform: bool,
2558 values_per_chunk: u64,
2559 last_chunk_values: u64,
2560}
2561
2562fn analyze_value_counts(words: &Words, items_in_page: u64) -> Result<FlatValueCounts> {
2563 let num_chunks = words.len();
2564 let logs = words.iter().map(|w| (w & 0x0F) as u8).collect::<Vec<_>>();
2565 let mut counted = 0u64;
2566 for (chunk_index, &log) in logs.iter().take(num_chunks.saturating_sub(1)).enumerate() {
2567 if log == 0 {
2568 return Err(Error::corrupt_file_named(
2569 "miniblock_metadata",
2570 format!(
2571 "non-final chunk {chunk_index} of {num_chunks} has invalid log_num_values=0"
2572 ),
2573 ));
2574 }
2575 counted = counted.checked_add(1u64 << log).ok_or_else(|| {
2576 Error::corrupt_file_named(
2577 "miniblock_metadata",
2578 format!(
2579 "value count overflow at chunk {chunk_index}: counted_values={counted}, \
2580 log_num_values={log}, items_in_page={items_in_page}"
2581 ),
2582 )
2583 })?;
2584 }
2585 let last_chunk_values = items_in_page.checked_sub(counted).ok_or_else(|| {
2586 Error::corrupt_file_named(
2587 "miniblock_metadata",
2588 format!(
2589 "non-final chunks account for counted_values={counted}, exceeding \
2590 items_in_page={items_in_page}"
2591 ),
2592 )
2593 })?;
2594 if let Some(&last_log) = logs.last()
2595 && last_log != 0
2596 && (1u64 << last_log) != last_chunk_values
2597 {
2598 return Err(Error::corrupt_file_named(
2599 "miniblock_metadata",
2600 format!(
2601 "final chunk log_num_values={last_log} does not match \
2602 last_chunk_values={last_chunk_values}: counted_values={counted}, \
2603 items_in_page={items_in_page}"
2604 ),
2605 ));
2606 }
2607 let uniform = num_chunks <= 1 || logs[..num_chunks - 1].iter().all(|&log| log == logs[0]);
2608 let values_per_chunk = if num_chunks <= 1 {
2611 items_in_page.max(1)
2612 } else {
2613 1u64 << logs[0]
2614 };
2615 Ok(FlatValueCounts {
2616 logs,
2617 uniform,
2618 values_per_chunk,
2619 last_chunk_values,
2620 })
2621}
2622
2623fn flat_value_counts_iter(logs: &[u8], last_chunk_values: u64) -> impl Iterator<Item = u64> + '_ {
2626 let num_chunks = logs.len();
2627 (0..num_chunks).map(move |i| {
2628 if i + 1 < num_chunks {
2629 1u64 << logs[i]
2630 } else {
2631 last_chunk_values
2632 }
2633 })
2634}
2635
2636fn build_chunk_index(
2641 words: &Words,
2642 items_in_page: u64,
2643 base: u64,
2644 data_buf_size: u64,
2645 rep_index_bytes: Option<&[u8]>,
2646 repetition_index_depth: u16,
2647) -> Result<MiniBlockChunkIndex> {
2648 let num_chunks = words.len();
2649 let value_counts = analyze_value_counts(words, items_in_page)?;
2652
2653 let byte_starts = PrefixSums::from_deltas(
2656 words
2657 .iter()
2658 .map(|word| ((word >> 4) as u64 + 1) * MINIBLOCK_ALIGNMENT as u64),
2659 num_chunks,
2660 data_buf_size,
2661 );
2662
2663 let rows = if let Some(rep_index_data) = rep_index_bytes {
2666 assert!(rep_index_data.len() % 8 == 0);
2667 let stride = repetition_index_depth as usize + 1;
2668 let (row_starts, has_trailer) = parse_nested_rep(rep_index_data, stride);
2669 let item_counts = if value_counts.uniform {
2670 ItemCounts::Uniform {
2671 values_per_chunk: value_counts.values_per_chunk,
2672 last_chunk_values: value_counts.last_chunk_values,
2673 }
2674 } else {
2675 ItemCounts::PerChunkLog {
2676 logs: value_counts.logs,
2677 last_chunk_values: value_counts.last_chunk_values,
2678 }
2679 };
2680 RowMapping::Nested {
2681 row_starts,
2682 has_trailer,
2683 item_counts,
2684 }
2685 } else {
2686 if value_counts.uniform {
2687 RowMapping::UniformFlat {
2688 values_per_chunk: value_counts.values_per_chunk,
2689 last_chunk_values: value_counts.last_chunk_values,
2690 num_chunks,
2691 }
2692 } else {
2693 let value_starts = PrefixSums::from_deltas(
2694 flat_value_counts_iter(&value_counts.logs, value_counts.last_chunk_values),
2695 num_chunks,
2696 items_in_page,
2697 );
2698 RowMapping::Flat { value_starts }
2699 }
2700 };
2701
2702 Ok(MiniBlockChunkIndex::new(base, byte_starts, rows))
2703}
2704
2705impl StructuralPageScheduler for MiniBlockScheduler {
2706 fn initialize<'a>(
2707 &'a mut self,
2708 io: &Arc<dyn EncodingsIo>,
2709 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
2710 let (meta_buf_position, meta_buf_size) = self.buffer_offsets_and_sizes[0];
2714 let base = self.buffer_offsets_and_sizes[1].0;
2715 let data_buf_size = self.buffer_offsets_and_sizes[1].1;
2716 let mut bufs_needed = 1;
2717 if self.dictionary.is_some() {
2718 bufs_needed += 1;
2719 }
2720 if self.repetition_index_depth > 0 {
2721 bufs_needed += 1;
2722 }
2723 let mut required_ranges = Vec::with_capacity(bufs_needed);
2724 required_ranges.push(meta_buf_position..meta_buf_position + meta_buf_size);
2725 if let Some(ref dictionary) = self.dictionary {
2726 required_ranges.push(
2727 dictionary.dictionary_buf_position_and_size.0
2728 ..dictionary.dictionary_buf_position_and_size.0
2729 + dictionary.dictionary_buf_position_and_size.1,
2730 );
2731 }
2732 if self.repetition_index_depth > 0 {
2733 let (rep_index_pos, rep_index_size) = self.buffer_offsets_and_sizes.last().unwrap();
2734 required_ranges.push(*rep_index_pos..*rep_index_pos + *rep_index_size);
2735 }
2736 let io_req = io.submit_request(required_ranges, 0);
2737
2738 async move {
2739 let mut buffers = io_req.await?.into_iter().fuse();
2740 let meta_bytes = buffers.next().unwrap();
2741 let dictionary_bytes = self.dictionary.as_ref().and_then(|_| buffers.next());
2742 let rep_index_bytes = buffers.next();
2743
2744 let words = Words::from_bytes(meta_bytes, self.has_large_chunk)?;
2745 let chunk_index = build_chunk_index(
2746 &words,
2747 self.items_in_page,
2748 base,
2749 data_buf_size,
2750 rep_index_bytes.as_deref(),
2751 self.repetition_index_depth,
2752 )?;
2753
2754 let dictionary = if let Some(ref mut dictionary) = self.dictionary {
2756 let dictionary_data = dictionary_bytes.unwrap();
2757 Some(Arc::new(dictionary.dictionary_decompressor.decompress(
2758 Some(LanceBuffer::from_bytes(
2759 dictionary_data,
2760 dictionary.dictionary_data_alignment,
2761 )),
2762 dictionary.num_dictionary_items,
2763 )?))
2764 } else {
2765 None
2766 };
2767
2768 let page_meta = Arc::new(MiniBlockCacheableState {
2769 chunk_index,
2770 dictionary,
2771 });
2772 self.page_meta = Some(page_meta.clone());
2773 Ok(page_meta as Arc<dyn CachedPageData>)
2774 }
2775 .boxed()
2776 }
2777
2778 fn load(&mut self, data: &Arc<dyn CachedPageData>) {
2779 self.page_meta = Some(
2780 data.clone()
2781 .as_arc_any()
2782 .downcast::<MiniBlockCacheableState>()
2783 .unwrap(),
2784 );
2785 }
2786
2787 fn schedule_ranges(
2788 &self,
2789 ranges: &[Range<u64>],
2790 io: &Arc<dyn EncodingsIo>,
2791 ) -> Result<Vec<PageLoadTask>> {
2792 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
2793
2794 let page_meta = self.page_meta.as_ref().unwrap();
2795
2796 let chunk_instructions =
2797 ChunkInstructions::schedule_instructions(&page_meta.chunk_index, ranges);
2798
2799 debug_assert_eq!(
2800 num_rows,
2801 chunk_instructions
2802 .iter()
2803 .map(|ci| ci.rows_to_take)
2804 .sum::<u64>()
2805 );
2806
2807 let chunks_needed = chunk_instructions
2808 .iter()
2809 .map(|ci| ci.chunk_idx)
2810 .unique()
2811 .collect::<Vec<_>>();
2812
2813 let mut loaded_chunks = self.lookup_chunks(&chunks_needed);
2814 let chunk_ranges = loaded_chunks
2815 .iter()
2816 .map(|c| c.byte_range.clone())
2817 .collect::<Vec<_>>();
2818 let loaded_chunk_data = io.submit_request(chunk_ranges, self.priority);
2819
2820 let rep_decompressor = self.rep_decompressor.clone();
2821 let def_decompressor = self.def_decompressor.clone();
2822 let value_decompressor = self.value_decompressor.clone();
2823 let num_buffers = self.num_buffers;
2824 let has_large_chunk = self.has_large_chunk;
2825 let dictionary = page_meta
2826 .dictionary
2827 .as_ref()
2828 .map(|dictionary| dictionary.clone());
2829 let def_meaning = self.def_meaning.clone();
2830
2831 let res = async move {
2832 let loaded_chunk_data = loaded_chunk_data.await?;
2833 for (loaded_chunk, chunk_data) in loaded_chunks.iter_mut().zip(loaded_chunk_data) {
2834 loaded_chunk.data = LanceBuffer::from_bytes(chunk_data, 1);
2835 }
2836
2837 Ok(Box::new(MiniBlockDecoder {
2838 rep_decompressor,
2839 def_decompressor,
2840 value_decompressor,
2841 def_meaning,
2842 loaded_chunks: VecDeque::from_iter(loaded_chunks),
2843 instructions: VecDeque::from(chunk_instructions),
2844 offset_in_current_chunk: 0,
2845 dictionary,
2846 num_rows,
2847 num_buffers,
2848 has_large_chunk,
2849 }) as Box<dyn StructuralPageDecoder>)
2850 }
2851 .boxed();
2852 let page_load_task = PageLoadTask {
2853 decoder_fut: res,
2854 num_rows,
2855 };
2856 Ok(vec![page_load_task])
2857 }
2858}
2859
2860#[derive(Debug, Clone, Copy)]
2861struct FullZipRepIndexDetails {
2862 buf_position: u64,
2863 bytes_per_value: u64, }
2865
2866#[derive(Debug)]
2867enum PerValueDecompressor {
2868 Fixed(Arc<dyn FixedPerValueDecompressor>),
2869 Variable(Arc<dyn VariablePerValueDecompressor>),
2870}
2871
2872#[derive(Debug)]
2873struct FullZipDecodeDetails {
2874 value_decompressor: PerValueDecompressor,
2875 def_meaning: Arc<[DefinitionInterpretation]>,
2876 ctrl_word_parser: ControlWordParser,
2877 max_rep: u16,
2878 max_visible_def: u16,
2879}
2880
2881#[derive(Debug, Clone)]
2893enum FullZipReadSource {
2894 Remote(Arc<dyn EncodingsIo>),
2896 PrefetchedPage { base_offset: u64, data: LanceBuffer },
2898}
2899
2900impl FullZipReadSource {
2901 fn fetch(
2905 &self,
2906 ranges: &[Range<u64>],
2907 priority: u64,
2908 ) -> BoxFuture<'static, Result<VecDeque<LanceBuffer>>> {
2909 match self {
2910 Self::Remote(io) => {
2911 let io = io.clone();
2912 let ranges = ranges.to_vec();
2913 async move {
2914 let data = io.submit_request(ranges, priority).await?;
2915 Ok(data
2916 .into_iter()
2917 .map(|bytes| LanceBuffer::from_bytes(bytes, 1))
2918 .collect::<VecDeque<_>>())
2919 }
2920 .boxed()
2921 }
2922 Self::PrefetchedPage { base_offset, data } => {
2923 let base_offset = *base_offset;
2924 let data = data.clone();
2925 let page_end = base_offset + data.len() as u64;
2926 std::future::ready(
2927 ranges
2928 .iter()
2929 .map(|range| {
2930 if range.start > range.end
2931 || range.start < base_offset
2932 || range.end > page_end
2933 {
2934 return Err(Error::internal(format!(
2935 "Requested range {:?} is outside page range {}..{}",
2936 range, base_offset, page_end
2937 )));
2938 }
2939 let start = (range.start - base_offset) as usize;
2940 let len = (range.end - range.start) as usize;
2941 Ok(data.slice_with_length(start, len))
2942 })
2943 .collect::<Result<VecDeque<_>>>(),
2944 )
2945 .boxed()
2946 }
2947 }
2948 }
2949}
2950
2951#[derive(Debug)]
2959pub struct FullZipScheduler {
2960 data_buf_position: u64,
2961 data_buf_size: u64,
2962 rep_index: Option<FullZipRepIndexDetails>,
2963 priority: u64,
2964 rows_in_page: u64,
2965 bits_per_offset: u8,
2966 details: Arc<FullZipDecodeDetails>,
2967 cached_state: Option<Arc<FullZipCacheableState>>,
2969 enable_cache: bool,
2971}
2972
2973impl FullZipScheduler {
2974 fn try_new(
2975 buffer_offsets_and_sizes: &[(u64, u64)],
2976 priority: u64,
2977 rows_in_page: u64,
2978 layout: &pb21::FullZipLayout,
2979 decompressors: &dyn DecompressionStrategy,
2980 ) -> Result<Self> {
2981 let (data_buf_position, data_buf_size) = buffer_offsets_and_sizes[0];
2982 let rep_index = buffer_offsets_and_sizes.get(1).map(|(pos, len)| {
2983 let num_reps = rows_in_page + 1;
2984 let bytes_per_rep = len / num_reps;
2985 debug_assert_eq!(len % num_reps, 0);
2986 debug_assert!(
2987 bytes_per_rep == 1
2988 || bytes_per_rep == 2
2989 || bytes_per_rep == 4
2990 || bytes_per_rep == 8
2991 );
2992 FullZipRepIndexDetails {
2993 buf_position: *pos,
2994 bytes_per_value: bytes_per_rep,
2995 }
2996 });
2997
2998 let value_decompressor = match layout.details {
2999 Some(pb21::full_zip_layout::Details::BitsPerValue(_)) => {
3000 let decompressor = decompressors.create_fixed_per_value_decompressor(
3001 layout.value_compression.as_ref().unwrap(),
3002 )?;
3003 PerValueDecompressor::Fixed(decompressor.into())
3004 }
3005 Some(pb21::full_zip_layout::Details::BitsPerOffset(_)) => {
3006 let decompressor = decompressors.create_variable_per_value_decompressor(
3007 layout.value_compression.as_ref().unwrap(),
3008 )?;
3009 PerValueDecompressor::Variable(decompressor.into())
3010 }
3011 None => {
3012 panic!("Full-zip layout must have a `details` field");
3013 }
3014 };
3015 let ctrl_word_parser = ControlWordParser::new(
3016 layout.bits_rep.try_into().unwrap(),
3017 layout.bits_def.try_into().unwrap(),
3018 );
3019 let def_meaning = layout
3020 .layers
3021 .iter()
3022 .map(|l| ProtobufUtils21::repdef_layer_to_def_interp(*l))
3023 .collect::<Vec<_>>();
3024
3025 let max_rep = def_meaning.iter().filter(|d| d.is_list()).count() as u16;
3026 let max_visible_def = def_meaning
3027 .iter()
3028 .filter(|d| !d.is_list())
3029 .map(|d| d.num_def_levels())
3030 .sum();
3031
3032 let bits_per_offset = match layout.details {
3033 Some(pb21::full_zip_layout::Details::BitsPerValue(_)) => 32,
3034 Some(pb21::full_zip_layout::Details::BitsPerOffset(bits_per_offset)) => {
3035 bits_per_offset as u8
3036 }
3037 None => panic!("Full-zip layout must have a `details` field"),
3038 };
3039
3040 let details = Arc::new(FullZipDecodeDetails {
3041 value_decompressor,
3042 def_meaning: def_meaning.into(),
3043 ctrl_word_parser,
3044 max_rep,
3045 max_visible_def,
3046 });
3047 Ok(Self {
3048 data_buf_position,
3049 data_buf_size,
3050 rep_index,
3051 details,
3052 priority,
3053 rows_in_page,
3054 bits_per_offset,
3055 cached_state: None,
3056 enable_cache: false,
3057 })
3058 }
3059
3060 fn covers_entire_page(ranges: &[Range<u64>], rows_in_page: u64) -> bool {
3061 if ranges.is_empty() {
3062 return false;
3063 }
3064 let mut expected_start = 0;
3065 for range in ranges {
3066 if range.start != expected_start || range.end > rows_in_page || range.end < range.start
3067 {
3068 return false;
3069 }
3070 expected_start = range.end;
3071 }
3072 expected_start == rows_in_page
3073 }
3074
3075 fn create_page_load_task(
3076 io_future: BoxFuture<'static, Result<Vec<Bytes>>>,
3077 num_rows: u64,
3078 details: Arc<FullZipDecodeDetails>,
3079 bits_per_offset: u8,
3080 ) -> PageLoadTask {
3081 let load_task = async move {
3082 let buffers = io_future.await?;
3083 let data = buffers
3084 .into_iter()
3085 .map(|bytes| LanceBuffer::from_bytes(bytes, 1))
3086 .collect::<VecDeque<_>>();
3087 Self::create_decoder(details, data, num_rows, bits_per_offset)
3088 }
3089 .boxed();
3090 PageLoadTask {
3091 decoder_fut: load_task,
3092 num_rows,
3093 }
3094 }
3095
3096 fn create_decoder(
3098 details: Arc<FullZipDecodeDetails>,
3099 data: VecDeque<LanceBuffer>,
3100 num_rows: u64,
3101 bits_per_offset: u8,
3102 ) -> Result<Box<dyn StructuralPageDecoder>> {
3103 match &details.value_decompressor {
3104 PerValueDecompressor::Fixed(decompressor) => {
3105 let bits_per_value = decompressor.bits_per_value();
3106 if bits_per_value % 8 != 0 {
3107 return Err(lance_core::Error::not_supported_source("Bit-packed full-zip encoding (non-byte-aligned values) is not yet implemented".into()));
3108 }
3109 let bytes_per_value = bits_per_value / 8;
3110 let total_bytes_per_value =
3111 bytes_per_value as usize + details.ctrl_word_parser.bytes_per_word();
3112 Ok(Box::new(FixedFullZipDecoder {
3117 details,
3118 data,
3119 num_rows,
3120 offset_in_current: 0,
3121 bytes_per_value: bytes_per_value as usize,
3122 total_bytes_per_value,
3123 }) as Box<dyn StructuralPageDecoder>)
3124 }
3125 PerValueDecompressor::Variable(_decompressor) => {
3126 Ok(Box::new(VariableFullZipDecoder::new(
3127 details,
3128 data,
3129 num_rows,
3130 bits_per_offset,
3131 bits_per_offset,
3132 )?))
3133 }
3134 }
3135 }
3136
3137 fn extract_byte_ranges_from_pairs(
3140 buffer: LanceBuffer,
3141 bytes_per_value: u64,
3142 data_buf_position: u64,
3143 ) -> Vec<Range<u64>> {
3144 ByteUnpacker::new(buffer, bytes_per_value as usize)
3145 .chunks(2)
3146 .into_iter()
3147 .map(|mut c| {
3148 let start = c.next().unwrap() + data_buf_position;
3149 let end = c.next().unwrap() + data_buf_position;
3150 start..end
3151 })
3152 .collect::<Vec<_>>()
3153 }
3154
3155 fn extract_byte_ranges_from_cached(
3158 buffer: &LanceBuffer,
3159 ranges: &[Range<u64>],
3160 bytes_per_value: u64,
3161 data_buf_position: u64,
3162 ) -> Vec<Range<u64>> {
3163 ranges
3164 .iter()
3165 .map(|r| {
3166 let start_offset = (r.start * bytes_per_value) as usize;
3167 let end_offset = (r.end * bytes_per_value) as usize;
3168
3169 let start_slice = &buffer[start_offset..start_offset + bytes_per_value as usize];
3170 let start_val =
3171 ByteUnpacker::new(start_slice.iter().copied(), bytes_per_value as usize)
3172 .next()
3173 .unwrap();
3174
3175 let end_slice = &buffer[end_offset..end_offset + bytes_per_value as usize];
3176 let end_val =
3177 ByteUnpacker::new(end_slice.iter().copied(), bytes_per_value as usize)
3178 .next()
3179 .unwrap();
3180
3181 (data_buf_position + start_val)..(data_buf_position + end_val)
3182 })
3183 .collect()
3184 }
3185
3186 fn compute_rep_index_ranges(
3188 ranges: &[Range<u64>],
3189 rep_index: &FullZipRepIndexDetails,
3190 ) -> Vec<Range<u64>> {
3191 ranges
3192 .iter()
3193 .flat_map(|r| {
3194 let first_val_start =
3195 rep_index.buf_position + (r.start * rep_index.bytes_per_value);
3196 let first_val_end = first_val_start + rep_index.bytes_per_value;
3197 let last_val_start = rep_index.buf_position + (r.end * rep_index.bytes_per_value);
3198 let last_val_end = last_val_start + rep_index.bytes_per_value;
3199 [first_val_start..first_val_end, last_val_start..last_val_end]
3200 })
3201 .collect()
3202 }
3203
3204 fn schedule_ranges_rep(
3206 &self,
3207 ranges: &[Range<u64>],
3208 io: &Arc<dyn EncodingsIo>,
3209 rep_index: FullZipRepIndexDetails,
3210 ) -> Result<Vec<PageLoadTask>> {
3211 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
3212 let data_buf_position = self.data_buf_position;
3213 let priority = self.priority;
3214 let details = self.details.clone();
3215 let bits_per_offset = self.bits_per_offset;
3216
3217 if Self::covers_entire_page(ranges, self.rows_in_page) {
3218 let full_range = self.data_buf_position..(self.data_buf_position + self.data_buf_size);
3219 let page_data = io.submit_single(full_range.clone(), priority);
3220 let load_task = async move {
3221 let page_data = page_data.await?;
3222 let source = FullZipReadSource::PrefetchedPage {
3223 base_offset: full_range.start,
3224 data: LanceBuffer::from_bytes(page_data, 1),
3225 };
3226 let read_ranges = vec![full_range];
3227 let data = source.fetch(&read_ranges, priority).await?;
3228 Self::create_decoder(details, data, num_rows, bits_per_offset)
3229 }
3230 .boxed();
3231 let page_load_task = PageLoadTask {
3232 decoder_fut: load_task,
3233 num_rows,
3234 };
3235 return Ok(vec![page_load_task]);
3236 }
3237
3238 if let Some(cached_state) = &self.cached_state {
3239 let byte_ranges = Self::extract_byte_ranges_from_cached(
3240 &cached_state.rep_index_buffer,
3241 ranges,
3242 rep_index.bytes_per_value,
3243 data_buf_position,
3244 );
3245 let io_future = io.submit_request(byte_ranges, priority);
3246 let page_load_task =
3247 Self::create_page_load_task(io_future, num_rows, details, bits_per_offset);
3248 return Ok(vec![page_load_task]);
3249 }
3250
3251 let rep_ranges = Self::compute_rep_index_ranges(ranges, &rep_index);
3252 let rep_data = io.submit_request(rep_ranges, priority);
3253 let io_clone = io.clone();
3254 let load_task = async move {
3255 let rep_data = rep_data.await?;
3256 let rep_buffer = LanceBuffer::concat(
3257 &rep_data
3258 .into_iter()
3259 .map(|d| LanceBuffer::from_bytes(d, 1))
3260 .collect::<Vec<_>>(),
3261 );
3262 let byte_ranges = Self::extract_byte_ranges_from_pairs(
3263 rep_buffer,
3264 rep_index.bytes_per_value,
3265 data_buf_position,
3266 );
3267 let source = FullZipReadSource::Remote(io_clone);
3268 let data = source.fetch(&byte_ranges, priority).await?;
3269 Self::create_decoder(details, data, num_rows, bits_per_offset)
3270 }
3271 .boxed();
3272 let page_load_task = PageLoadTask {
3273 decoder_fut: load_task,
3274 num_rows,
3275 };
3276 Ok(vec![page_load_task])
3277 }
3278
3279 fn schedule_ranges_simple(
3283 &self,
3284 ranges: &[Range<u64>],
3285 io: &Arc<dyn EncodingsIo>,
3286 ) -> Result<Vec<PageLoadTask>> {
3287 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
3289
3290 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor else {
3291 unreachable!()
3292 };
3293
3294 let bits_per_value = decompressor.bits_per_value();
3296 if !bits_per_value.is_multiple_of(8) {
3297 return Err(Error::invalid_input_source(
3298 format!(
3299 "Full-zip fixed-width values must be byte aligned, got {} bits per value",
3300 bits_per_value
3301 )
3302 .into(),
3303 ));
3304 }
3305 let bytes_per_value = bits_per_value / 8;
3306 let bytes_per_cw = self.details.ctrl_word_parser.bytes_per_word();
3307 let total_bytes_per_value = bytes_per_value + bytes_per_cw as u64;
3308 let byte_ranges = ranges
3309 .iter()
3310 .map(|r| {
3311 debug_assert!(r.end <= self.rows_in_page);
3312 let start = self.data_buf_position + r.start * total_bytes_per_value;
3313 let end = self.data_buf_position + r.end * total_bytes_per_value;
3314 start..end
3315 })
3316 .collect::<Vec<_>>();
3317
3318 let io_future = io.submit_request(byte_ranges, self.priority);
3319 let page_load_task = Self::create_page_load_task(
3320 io_future,
3321 num_rows,
3322 self.details.clone(),
3323 self.bits_per_offset,
3324 );
3325 Ok(vec![page_load_task])
3326 }
3327}
3328
3329#[derive(Debug)]
3331struct FullZipCacheableState {
3332 rep_index_buffer: LanceBuffer,
3334}
3335
3336impl DeepSizeOf for FullZipCacheableState {
3337 fn deep_size_of_children(&self, _context: &mut Context) -> usize {
3338 self.rep_index_buffer.len()
3339 }
3340}
3341
3342impl CachedPageData for FullZipCacheableState {
3343 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
3344 self
3345 }
3346}
3347
3348impl StructuralPageScheduler for FullZipScheduler {
3349 fn initialize<'a>(
3350 &'a mut self,
3351 io: &Arc<dyn EncodingsIo>,
3352 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
3353 if self.enable_cache
3354 && let Some(rep_index) = self.rep_index
3355 {
3356 let total_size = (self.rows_in_page + 1) * rep_index.bytes_per_value;
3357 let rep_index_range = rep_index.buf_position..(rep_index.buf_position + total_size);
3358 let io_clone = io.clone();
3359 return async move {
3360 let rep_index_data = io_clone.submit_request(vec![rep_index_range], 0).await?;
3361 let state = Arc::new(FullZipCacheableState {
3362 rep_index_buffer: LanceBuffer::from_bytes(rep_index_data[0].clone(), 1),
3363 });
3364 self.cached_state = Some(state.clone());
3365 Ok(state as Arc<dyn CachedPageData>)
3366 }
3367 .boxed();
3368 }
3369 std::future::ready(Ok(Arc::new(NoCachedPageData) as Arc<dyn CachedPageData>)).boxed()
3370 }
3371
3372 fn load(&mut self, cache: &Arc<dyn CachedPageData>) {
3376 if let Ok(cached_state) = cache
3378 .clone()
3379 .as_arc_any()
3380 .downcast::<FullZipCacheableState>()
3381 {
3382 self.cached_state = Some(cached_state);
3384 }
3385 }
3386
3387 fn schedule_ranges(
3388 &self,
3389 ranges: &[Range<u64>],
3390 io: &Arc<dyn EncodingsIo>,
3391 ) -> Result<Vec<PageLoadTask>> {
3392 if let Some(rep_index) = self.rep_index {
3393 self.schedule_ranges_rep(ranges, io, rep_index)
3394 } else {
3395 self.schedule_ranges_simple(ranges, io)
3396 }
3397 }
3398}
3399
3400#[derive(Debug)]
3408struct FixedFullZipDecoder {
3409 details: Arc<FullZipDecodeDetails>,
3410 data: VecDeque<LanceBuffer>,
3411 offset_in_current: usize,
3412 bytes_per_value: usize,
3413 total_bytes_per_value: usize,
3414 num_rows: u64,
3415}
3416
3417impl FixedFullZipDecoder {
3418 fn slice_next_task(&mut self, num_rows: u64) -> FullZipDecodeTaskItem {
3419 debug_assert!(num_rows > 0);
3420 let cur_buf = self.data.front_mut().unwrap();
3421 let start = self.offset_in_current;
3422 if self.details.ctrl_word_parser.has_rep() {
3423 let mut rows_started = 0;
3426 let mut num_items = 0;
3429 while self.offset_in_current < cur_buf.len() {
3430 let control = self.details.ctrl_word_parser.parse_desc(
3431 &cur_buf[self.offset_in_current..],
3432 self.details.max_rep,
3433 self.details.max_visible_def,
3434 );
3435 if control.is_new_row {
3436 if rows_started == num_rows {
3437 break;
3438 }
3439 rows_started += 1;
3440 }
3441 num_items += 1;
3442 if control.is_visible {
3443 self.offset_in_current += self.total_bytes_per_value;
3444 } else {
3445 self.offset_in_current += self.details.ctrl_word_parser.bytes_per_word();
3446 }
3447 }
3448
3449 let task_slice = cur_buf.slice_with_length(start, self.offset_in_current - start);
3450 if self.offset_in_current == cur_buf.len() {
3451 self.data.pop_front();
3452 self.offset_in_current = 0;
3453 }
3454
3455 FullZipDecodeTaskItem {
3456 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
3457 data: task_slice,
3458 bits_per_value: self.bytes_per_value as u64 * 8,
3459 num_values: num_items,
3460 block_info: BlockInfo::new(),
3461 }),
3462 rows_in_buf: rows_started,
3463 }
3464 } else {
3465 let cur_buf = self.data.front_mut().unwrap();
3468 let bytes_avail = cur_buf.len() - self.offset_in_current;
3469 let offset_in_cur = self.offset_in_current;
3470
3471 let bytes_needed = num_rows as usize * self.total_bytes_per_value;
3472 let mut rows_taken = num_rows;
3473 let task_slice = if bytes_needed >= bytes_avail {
3474 self.offset_in_current = 0;
3475 rows_taken = bytes_avail as u64 / self.total_bytes_per_value as u64;
3476 self.data
3477 .pop_front()
3478 .unwrap()
3479 .slice_with_length(offset_in_cur, bytes_avail)
3480 } else {
3481 self.offset_in_current += bytes_needed;
3482 cur_buf.slice_with_length(offset_in_cur, bytes_needed)
3483 };
3484 FullZipDecodeTaskItem {
3485 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
3486 data: task_slice,
3487 bits_per_value: self.bytes_per_value as u64 * 8,
3488 num_values: rows_taken,
3489 block_info: BlockInfo::new(),
3490 }),
3491 rows_in_buf: rows_taken,
3492 }
3493 }
3494 }
3495}
3496
3497impl StructuralPageDecoder for FixedFullZipDecoder {
3498 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
3499 if self.total_bytes_per_value == 0 {
3500 return Ok(Box::new(FixedFullZipDecodeTask {
3503 details: self.details.clone(),
3504 data: vec![FullZipDecodeTaskItem {
3505 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
3506 data: LanceBuffer::empty(),
3507 bits_per_value: 0,
3508 num_values: num_rows,
3509 block_info: BlockInfo::new(),
3510 }),
3511 rows_in_buf: num_rows,
3512 }],
3513 bytes_per_value: 0,
3514 num_rows: num_rows as usize,
3515 }));
3516 }
3517 let mut task_data = Vec::with_capacity(self.data.len());
3518 let mut remaining = num_rows;
3519 while remaining > 0 {
3520 let task_item = self.slice_next_task(remaining);
3521 remaining -= task_item.rows_in_buf;
3522 task_data.push(task_item);
3523 }
3524 Ok(Box::new(FixedFullZipDecodeTask {
3525 details: self.details.clone(),
3526 data: task_data,
3527 bytes_per_value: self.bytes_per_value,
3528 num_rows: num_rows as usize,
3529 }))
3530 }
3531
3532 fn num_rows(&self) -> u64 {
3533 self.num_rows
3534 }
3535}
3536
3537#[derive(Debug)]
3542struct VariableFullZipDecoder {
3543 details: Arc<FullZipDecodeDetails>,
3544 decompressor: Arc<dyn VariablePerValueDecompressor>,
3545 data: LanceBuffer,
3546 offsets: LanceBuffer,
3547 rep: ScalarBuffer<u16>,
3548 def: ScalarBuffer<u16>,
3549 repdef_starts: Vec<usize>,
3550 data_starts: Vec<usize>,
3551 offset_starts: Vec<usize>,
3552 visible_item_counts: Vec<u64>,
3553 bits_per_offset: u8,
3554 current_idx: usize,
3555 num_rows: u64,
3556}
3557
3558impl VariableFullZipDecoder {
3559 fn new(
3560 details: Arc<FullZipDecodeDetails>,
3561 data: VecDeque<LanceBuffer>,
3562 num_rows: u64,
3563 in_bits_per_length: u8,
3564 out_bits_per_offset: u8,
3565 ) -> Result<Self> {
3566 let decompressor = match details.value_decompressor {
3567 PerValueDecompressor::Variable(ref d) => d.clone(),
3568 _ => unreachable!(),
3569 };
3570
3571 assert_eq!(in_bits_per_length % 8, 0);
3572 assert!(out_bits_per_offset == 32 || out_bits_per_offset == 64);
3573
3574 let mut decoder = Self {
3575 details,
3576 decompressor,
3577 data: LanceBuffer::empty(),
3578 offsets: LanceBuffer::empty(),
3579 rep: LanceBuffer::empty().borrow_to_typed_slice(),
3580 def: LanceBuffer::empty().borrow_to_typed_slice(),
3581 bits_per_offset: out_bits_per_offset,
3582 repdef_starts: Vec::with_capacity(num_rows as usize + 1),
3583 data_starts: Vec::with_capacity(num_rows as usize + 1),
3584 offset_starts: Vec::with_capacity(num_rows as usize + 1),
3585 visible_item_counts: Vec::with_capacity(num_rows as usize + 1),
3586 current_idx: 0,
3587 num_rows,
3588 };
3589
3590 decoder.unzip(data, in_bits_per_length, out_bits_per_offset, num_rows)?;
3611
3612 Ok(decoder)
3613 }
3614
3615 fn slice_batch_data_and_rebase_offsets_typed<T>(
3616 data: &LanceBuffer,
3617 offsets: &LanceBuffer,
3618 ) -> Result<(LanceBuffer, LanceBuffer)>
3619 where
3620 T: arrow_buffer::ArrowNativeType
3621 + Copy
3622 + PartialOrd
3623 + std::ops::Sub<Output = T>
3624 + std::fmt::Display
3625 + TryInto<usize>,
3626 {
3627 let offsets_slice = offsets.borrow_to_typed_slice::<T>();
3628 let offsets_slice = offsets_slice.as_ref();
3629 if offsets_slice.is_empty() {
3630 return Err(Error::internal(
3631 "Variable offsets cannot be empty".to_string(),
3632 ));
3633 }
3634
3635 let base = offsets_slice[0];
3636 let end = *offsets_slice.last().unwrap();
3637 if end < base {
3638 return Err(Error::internal(format!(
3639 "Invalid variable offsets: end ({end}) is less than base ({base})"
3640 )));
3641 }
3642
3643 let data_start = base.try_into().map_err(|_| {
3644 Error::internal(format!("Variable offset ({base}) does not fit into usize"))
3645 })?;
3646 let data_end = end.try_into().map_err(|_| {
3647 Error::internal(format!("Variable offset ({end}) does not fit into usize"))
3648 })?;
3649 if data_end > data.len() {
3650 return Err(Error::internal(format!(
3651 "Invalid variable offsets: end ({data_end}) exceeds data len ({})",
3652 data.len()
3653 )));
3654 }
3655
3656 let mut rebased_offsets = Vec::with_capacity(offsets_slice.len());
3657 for &offset in offsets_slice {
3658 if offset < base {
3659 return Err(Error::internal(format!(
3660 "Invalid variable offsets: offset ({offset}) is less than base ({base})"
3661 )));
3662 }
3663 rebased_offsets.push(offset - base);
3664 }
3665
3666 let sliced_data = data.slice_with_length(data_start, data_end - data_start);
3667 let sliced_data = LanceBuffer::copy_slice(&sliced_data);
3669 let rebased_offsets = LanceBuffer::reinterpret_vec(rebased_offsets);
3670 Ok((sliced_data, rebased_offsets))
3671 }
3672
3673 fn slice_batch_data_and_rebase_offsets(
3674 data: &LanceBuffer,
3675 offsets: &LanceBuffer,
3676 bits_per_offset: u8,
3677 ) -> Result<(LanceBuffer, LanceBuffer)> {
3678 match bits_per_offset {
3679 32 => Self::slice_batch_data_and_rebase_offsets_typed::<u32>(data, offsets),
3680 64 => Self::slice_batch_data_and_rebase_offsets_typed::<u64>(data, offsets),
3681 _ => Err(Error::internal(format!(
3682 "Unsupported bits_per_offset={bits_per_offset}"
3683 ))),
3684 }
3685 }
3686
3687 fn parse_length(data: &[u8], bits_per_offset: u8) -> Result<u64> {
3694 let width = bits_per_offset as usize / 8;
3695 if data.len() < width {
3696 return Err(Error::corrupt_file_named(
3697 "variable_full_zip",
3698 format!(
3699 "truncated length prefix: {} byte(s) remain in the page buffer but a \
3700 {}-bit length prefix requires {}",
3701 data.len(),
3702 bits_per_offset,
3703 width
3704 ),
3705 ));
3706 }
3707 Ok(match bits_per_offset {
3708 8 => data[0] as u64,
3709 16 => u16::from_le_bytes(data[..2].try_into().unwrap()) as u64,
3710 32 => u32::from_le_bytes(data[..4].try_into().unwrap()) as u64,
3711 64 => u64::from_le_bytes(data[..8].try_into().unwrap()),
3712 _ => unreachable!(),
3713 })
3714 }
3715
3716 fn unzip(
3717 &mut self,
3718 data: VecDeque<LanceBuffer>,
3719 in_bits_per_length: u8,
3720 out_bits_per_offset: u8,
3721 num_rows: u64,
3722 ) -> Result<()> {
3723 let mut rep = Vec::with_capacity(num_rows as usize);
3725 let mut def = Vec::with_capacity(num_rows as usize);
3726 let bytes_cw = self.details.ctrl_word_parser.bytes_per_word() * num_rows as usize;
3727
3728 let bytes_per_offset = out_bits_per_offset as usize / 8;
3731 let bytes_offsets = bytes_per_offset * (num_rows as usize + 1);
3732 let mut offsets_data = Vec::with_capacity(bytes_offsets);
3733
3734 let bytes_per_length = in_bits_per_length as usize / 8;
3735 let bytes_lengths = bytes_per_length * num_rows as usize;
3736
3737 let bytes_data = data.iter().map(|d| d.len()).sum::<usize>();
3738 let mut unzipped_data =
3741 Vec::with_capacity((bytes_data - bytes_cw).saturating_sub(bytes_lengths));
3742
3743 let mut current_offset = 0_u64;
3744 let mut visible_item_count = 0_u64;
3745 for databuf in data.into_iter() {
3746 let mut databuf = databuf.as_ref();
3747 while !databuf.is_empty() {
3748 let data_start = unzipped_data.len();
3749 let offset_start = offsets_data.len();
3750 let repdef_start = rep.len().max(def.len());
3753 let ctrl_desc = self.details.ctrl_word_parser.parse_desc(
3755 databuf,
3756 self.details.max_rep,
3757 self.details.max_visible_def,
3758 );
3759 self.details
3760 .ctrl_word_parser
3761 .parse(databuf, &mut rep, &mut def);
3762 databuf = &databuf[self.details.ctrl_word_parser.bytes_per_word()..];
3763
3764 if ctrl_desc.is_new_row {
3765 self.repdef_starts.push(repdef_start);
3766 self.data_starts.push(data_start);
3767 self.offset_starts.push(offset_start);
3768 self.visible_item_counts.push(visible_item_count);
3769 }
3770 if ctrl_desc.is_visible {
3771 visible_item_count += 1;
3772 if ctrl_desc.is_valid_item {
3773 let length = Self::parse_length(databuf, in_bits_per_length)?;
3774 match out_bits_per_offset {
3775 32 => offsets_data
3776 .extend_from_slice(&(current_offset as u32).to_le_bytes()),
3777 64 => offsets_data.extend_from_slice(¤t_offset.to_le_bytes()),
3778 _ => unreachable!(),
3779 };
3780 databuf = &databuf[bytes_per_offset..];
3781 unzipped_data.extend_from_slice(&databuf[..length as usize]);
3782 databuf = &databuf[length as usize..];
3783 current_offset += length;
3784 } else {
3785 match out_bits_per_offset {
3787 32 => offsets_data
3788 .extend_from_slice(&(current_offset as u32).to_le_bytes()),
3789 64 => offsets_data.extend_from_slice(¤t_offset.to_le_bytes()),
3790 _ => unreachable!(),
3791 }
3792 }
3793 }
3794 }
3795 }
3796 self.repdef_starts.push(rep.len().max(def.len()));
3797 self.data_starts.push(unzipped_data.len());
3798 self.offset_starts.push(offsets_data.len());
3799 self.visible_item_counts.push(visible_item_count);
3800 match out_bits_per_offset {
3801 32 => offsets_data.extend_from_slice(&(current_offset as u32).to_le_bytes()),
3802 64 => offsets_data.extend_from_slice(¤t_offset.to_le_bytes()),
3803 _ => unreachable!(),
3804 };
3805 self.rep = ScalarBuffer::from(rep);
3806 self.def = ScalarBuffer::from(def);
3807 self.data = LanceBuffer::from(unzipped_data);
3808 self.offsets = LanceBuffer::from(offsets_data);
3809 Ok(())
3810 }
3811}
3812
3813impl StructuralPageDecoder for VariableFullZipDecoder {
3814 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
3815 let start = self.current_idx;
3816 let end = start + num_rows as usize;
3817
3818 let offset_start = self.offset_starts[start];
3819 let offset_end = self.offset_starts[end] + (self.bits_per_offset as usize / 8);
3820 let offsets = self
3821 .offsets
3822 .slice_with_length(offset_start, offset_end - offset_start);
3823 let (data, offsets) =
3825 Self::slice_batch_data_and_rebase_offsets(&self.data, &offsets, self.bits_per_offset)?;
3826
3827 let repdef_start = self.repdef_starts[start];
3828 let repdef_end = self.repdef_starts[end];
3829 let rep = if self.rep.is_empty() {
3830 self.rep.clone()
3831 } else {
3832 self.rep.slice(repdef_start, repdef_end - repdef_start)
3833 };
3834 let def = if self.def.is_empty() {
3835 self.def.clone()
3836 } else {
3837 self.def.slice(repdef_start, repdef_end - repdef_start)
3838 };
3839
3840 let visible_item_counts_start = self.visible_item_counts[start];
3841 let visible_item_counts_end = self.visible_item_counts[end];
3842 let num_visible_items = visible_item_counts_end - visible_item_counts_start;
3843
3844 self.current_idx += num_rows as usize;
3845
3846 Ok(Box::new(VariableFullZipDecodeTask {
3847 details: self.details.clone(),
3848 decompressor: self.decompressor.clone(),
3849 data,
3850 offsets,
3851 bits_per_offset: self.bits_per_offset,
3852 num_visible_items,
3853 rep,
3854 def,
3855 }))
3856 }
3857
3858 fn num_rows(&self) -> u64 {
3859 self.num_rows
3860 }
3861}
3862
3863#[derive(Debug)]
3864struct VariableFullZipDecodeTask {
3865 details: Arc<FullZipDecodeDetails>,
3866 decompressor: Arc<dyn VariablePerValueDecompressor>,
3867 data: LanceBuffer,
3868 offsets: LanceBuffer,
3869 bits_per_offset: u8,
3870 num_visible_items: u64,
3871 rep: ScalarBuffer<u16>,
3872 def: ScalarBuffer<u16>,
3873}
3874
3875impl DecodePageTask for VariableFullZipDecodeTask {
3876 fn decode(self: Box<Self>) -> Result<DecodedPage> {
3877 let block = VariableWidthBlock {
3878 data: self.data,
3879 offsets: self.offsets,
3880 bits_per_offset: self.bits_per_offset,
3881 num_values: self.num_visible_items,
3882 block_info: BlockInfo::new(),
3883 };
3884 let decomopressed = self.decompressor.decompress(block)?;
3885 let rep = if self.rep.is_empty() {
3886 None
3887 } else {
3888 Some(self.rep.to_vec())
3889 };
3890 let def = if self.def.is_empty() {
3891 None
3892 } else {
3893 Some(self.def.to_vec())
3894 };
3895 let unraveler = RepDefUnraveler::new(
3896 rep,
3897 def,
3898 self.details.def_meaning.clone(),
3899 self.num_visible_items,
3900 );
3901 Ok(DecodedPage {
3902 data: decomopressed,
3903 repdef: unraveler,
3904 })
3905 }
3906}
3907
3908#[derive(Debug)]
3909struct FullZipDecodeTaskItem {
3910 data: PerValueDataBlock,
3911 rows_in_buf: u64,
3912}
3913
3914#[derive(Debug)]
3917struct FixedFullZipDecodeTask {
3918 details: Arc<FullZipDecodeDetails>,
3919 data: Vec<FullZipDecodeTaskItem>,
3920 num_rows: usize,
3921 bytes_per_value: usize,
3922}
3923
3924impl DecodePageTask for FixedFullZipDecodeTask {
3925 fn decode(self: Box<Self>) -> Result<DecodedPage> {
3926 let estimated_size_bytes = if self.details.ctrl_word_parser.bytes_per_word() == 0 {
3927 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor else {
3928 return Err(Error::internal(
3929 "FixedFullZipDecodeTask requires a fixed-width decompressor",
3930 ));
3931 };
3932 decompressor
3933 .decoded_size_bytes(self.num_rows as u64)
3934 .unwrap_or_else(|| {
3935 self.data
3936 .iter()
3937 .map(|task_item| task_item.data.data_size())
3938 .sum::<u64>()
3939 * 2
3940 })
3941 } else {
3942 self.data
3945 .iter()
3946 .map(|task_item| task_item.data.data_size())
3947 .sum::<u64>()
3948 * 2
3949 };
3950 let mut data_builder = DataBlockBuilder::with_capacity_estimate(estimated_size_bytes);
3951
3952 if self.details.ctrl_word_parser.bytes_per_word() == 0 {
3953 for task_item in self.data.into_iter() {
3957 let PerValueDataBlock::Fixed(fixed_data) = task_item.data else {
3958 unreachable!()
3959 };
3960 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor
3961 else {
3962 unreachable!()
3963 };
3964 debug_assert_eq!(fixed_data.num_values, task_item.rows_in_buf);
3965 let decompressed = decompressor.decompress(fixed_data, task_item.rows_in_buf)?;
3966 data_builder.append(&decompressed, 0..task_item.rows_in_buf)?;
3967 }
3968
3969 let unraveler = RepDefUnraveler::new(
3970 None,
3971 None,
3972 self.details.def_meaning.clone(),
3973 self.num_rows as u64,
3974 );
3975
3976 Ok(DecodedPage {
3977 data: data_builder.finish(),
3978 repdef: unraveler,
3979 })
3980 } else {
3981 let mut rep = Vec::with_capacity(self.num_rows);
3983 let mut def = Vec::with_capacity(self.num_rows);
3984
3985 for task_item in self.data.into_iter() {
3986 let PerValueDataBlock::Fixed(fixed_data) = task_item.data else {
3987 unreachable!()
3988 };
3989 let mut buf_slice = fixed_data.data.as_ref();
3990 let num_values = fixed_data.num_values as usize;
3991 let mut values = Vec::with_capacity(
3994 fixed_data.data.len()
3995 - (self.details.ctrl_word_parser.bytes_per_word() * num_values),
3996 );
3997 let mut visible_items = 0;
3998 for _ in 0..num_values {
3999 self.details
4001 .ctrl_word_parser
4002 .parse(buf_slice, &mut rep, &mut def);
4003 buf_slice = &buf_slice[self.details.ctrl_word_parser.bytes_per_word()..];
4004
4005 let is_visible = def
4006 .last()
4007 .map(|d| *d <= self.details.max_visible_def)
4008 .unwrap_or(true);
4009 if is_visible {
4010 values.extend_from_slice(buf_slice[..self.bytes_per_value].as_ref());
4012 buf_slice = &buf_slice[self.bytes_per_value..];
4013 visible_items += 1;
4014 }
4015 }
4016
4017 let values_buf = LanceBuffer::from(values);
4019 let fixed_data = FixedWidthDataBlock {
4020 bits_per_value: self.bytes_per_value as u64 * 8,
4021 block_info: BlockInfo::new(),
4022 data: values_buf,
4023 num_values: visible_items,
4024 };
4025 let PerValueDecompressor::Fixed(decompressor) = &self.details.value_decompressor
4026 else {
4027 unreachable!()
4028 };
4029 let decompressed = decompressor.decompress(fixed_data, visible_items)?;
4030 data_builder.append(&decompressed, 0..visible_items)?;
4031 }
4032
4033 let repetition = if rep.is_empty() { None } else { Some(rep) };
4034 let definition = if def.is_empty() { None } else { Some(def) };
4035
4036 let unraveler = RepDefUnraveler::new(
4037 repetition,
4038 definition,
4039 self.details.def_meaning.clone(),
4040 self.num_rows as u64,
4041 );
4042 let data = data_builder.finish();
4043
4044 Ok(DecodedPage {
4045 data,
4046 repdef: unraveler,
4047 })
4048 }
4049 }
4050}
4051
4052#[derive(Debug)]
4053struct StructuralPrimitiveFieldSchedulingJob<'a> {
4054 scheduler: &'a StructuralPrimitiveFieldScheduler,
4055 ranges: Vec<Range<u64>>,
4056 page_idx: usize,
4057 range_idx: usize,
4058 global_row_offset: u64,
4059}
4060
4061impl<'a> StructuralPrimitiveFieldSchedulingJob<'a> {
4062 pub fn new(scheduler: &'a StructuralPrimitiveFieldScheduler, ranges: Vec<Range<u64>>) -> Self {
4063 Self {
4064 scheduler,
4065 ranges,
4066 page_idx: 0,
4067 range_idx: 0,
4068 global_row_offset: 0,
4069 }
4070 }
4071}
4072
4073impl StructuralSchedulingJob for StructuralPrimitiveFieldSchedulingJob<'_> {
4074 fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>> {
4075 if self.range_idx >= self.ranges.len() {
4076 return Ok(Vec::new());
4077 }
4078 let mut range = self.ranges[self.range_idx].clone();
4080 let priority = range.start;
4081
4082 let mut cur_page = &self.scheduler.page_schedulers[self.page_idx];
4083 trace!(
4084 "Current range is {:?} and current page has {} rows",
4085 range, cur_page.num_rows
4086 );
4087 while cur_page.num_rows + self.global_row_offset <= range.start {
4089 self.global_row_offset += cur_page.num_rows;
4090 self.page_idx += 1;
4091 trace!("Skipping entire page of {} rows", cur_page.num_rows);
4092 cur_page = &self.scheduler.page_schedulers[self.page_idx];
4093 }
4094
4095 let mut ranges_in_page = Vec::new();
4099 while cur_page.num_rows + self.global_row_offset > range.start {
4100 range.start = range.start.max(self.global_row_offset);
4101 let start_in_page = range.start - self.global_row_offset;
4102 let end_in_page = start_in_page + (range.end - range.start);
4103 let end_in_page = end_in_page.min(cur_page.num_rows);
4104 let last_in_range = (end_in_page + self.global_row_offset) >= range.end;
4105
4106 ranges_in_page.push(start_in_page..end_in_page);
4107 if last_in_range {
4108 self.range_idx += 1;
4109 if self.range_idx == self.ranges.len() {
4110 break;
4111 }
4112 range = self.ranges[self.range_idx].clone();
4113 } else {
4114 break;
4115 }
4116 }
4117
4118 trace!(
4119 "Scheduling {} rows across {} ranges from page with {} rows (priority={}, column_index={}, page_index={})",
4120 ranges_in_page.iter().map(|r| r.end - r.start).sum::<u64>(),
4121 ranges_in_page.len(),
4122 cur_page.num_rows,
4123 priority,
4124 self.scheduler.column_index,
4125 cur_page.page_index,
4126 );
4127
4128 self.global_row_offset += cur_page.num_rows;
4129 self.page_idx += 1;
4130
4131 let page_decoders = cur_page
4132 .scheduler
4133 .schedule_ranges(&ranges_in_page, context.io())?;
4134
4135 let cur_path = context.current_path();
4136 page_decoders
4137 .into_iter()
4138 .map(|page_load_task| {
4139 let cur_path = cur_path.clone();
4140 let page_decoder = page_load_task.decoder_fut;
4141 let unloaded_page = async move {
4142 let page_decoder = page_decoder.await?;
4143 Ok(LoadedPageShard {
4144 decoder: page_decoder,
4145 path: cur_path,
4146 })
4147 }
4148 .boxed();
4149 Ok(ScheduledScanLine {
4150 decoders: vec![MessageType::UnloadedPage(UnloadedPageShard(unloaded_page))],
4151 rows_scheduled: page_load_task.num_rows,
4152 })
4153 })
4154 .collect::<Result<Vec<_>>>()
4155 }
4156}
4157
4158#[derive(Debug)]
4159struct PageInfoAndScheduler {
4160 page_index: usize,
4161 num_rows: u64,
4162 scheduler: Box<dyn StructuralPageScheduler>,
4163}
4164
4165#[derive(Debug)]
4170pub struct StructuralPrimitiveFieldScheduler {
4171 page_schedulers: Vec<PageInfoAndScheduler>,
4172 column_index: u32,
4173 view_tag: String,
4179}
4180
4181impl StructuralPrimitiveFieldScheduler {
4182 pub fn try_new(
4183 column_info: &ColumnInfo,
4184 decompressors: &dyn DecompressionStrategy,
4185 cache_repetition_index: bool,
4186 target_field: &Field,
4187 ) -> Result<Self> {
4188 let page_schedulers = column_info
4189 .page_infos
4190 .iter()
4191 .enumerate()
4192 .map(|(page_index, page_info)| {
4193 Self::page_info_to_scheduler(
4194 page_info,
4195 page_index,
4196 decompressors,
4197 cache_repetition_index,
4198 target_field,
4199 )
4200 })
4201 .collect::<Result<Vec<_>>>()?;
4202 Ok(Self {
4203 page_schedulers,
4204 column_index: column_info.index,
4205 view_tag: format!("{:?}", target_field.data_type()),
4206 })
4207 }
4208
4209 fn page_layout_to_scheduler(
4210 page_info: &PageInfo,
4211 page_layout: &PageLayout,
4212 decompressors: &dyn DecompressionStrategy,
4213 cache_repetition_index: bool,
4214 target_field: &Field,
4215 ) -> Result<Box<dyn StructuralPageScheduler>> {
4216 use pb21::page_layout::Layout;
4217 Ok(match page_layout.layout.as_ref().expect_ok()? {
4218 Layout::MiniBlockLayout(mini_block) => Box::new(MiniBlockScheduler::try_new(
4219 &page_info.buffer_offsets_and_sizes,
4220 page_info.priority,
4221 mini_block.num_items,
4222 mini_block,
4223 decompressors,
4224 )?),
4225 Layout::SparseLayout(sparse_layout) => {
4226 Box::new(sparse::SparseStructuralScheduler::try_new(
4227 &page_info.buffer_offsets_and_sizes,
4228 page_info.priority,
4229 page_info.num_rows,
4230 target_field.data_type(),
4231 sparse_layout,
4232 decompressors,
4233 )?)
4234 }
4235 Layout::FullZipLayout(full_zip) => {
4236 let mut scheduler = FullZipScheduler::try_new(
4237 &page_info.buffer_offsets_and_sizes,
4238 page_info.priority,
4239 page_info.num_rows,
4240 full_zip,
4241 decompressors,
4242 )?;
4243 scheduler.enable_cache = cache_repetition_index;
4244 Box::new(scheduler)
4245 }
4246 Layout::ConstantLayout(constant_layout) => {
4247 let def_meaning = constant_layout
4248 .layers
4249 .iter()
4250 .map(|l| ProtobufUtils21::repdef_layer_to_def_interp(*l))
4251 .collect::<Vec<_>>();
4252 let has_scalar_value = constant_layout.inline_value.is_some()
4253 || page_info.buffer_offsets_and_sizes.len() == 1
4254 || page_info.buffer_offsets_and_sizes.len() == 3;
4255 if has_scalar_value {
4256 Box::new(constant::ConstantPageScheduler::try_new(
4257 page_info.buffer_offsets_and_sizes.clone(),
4258 constant_layout.inline_value.clone(),
4259 target_field.data_type(),
4260 def_meaning.into(),
4261 )?) as Box<dyn StructuralPageScheduler>
4262 } else if def_meaning.len() == 1
4263 && def_meaning[0] == DefinitionInterpretation::NullableItem
4264 {
4265 Box::new(SimpleAllNullScheduler::default()) as Box<dyn StructuralPageScheduler>
4266 } else {
4267 let rep_codec = LevelCodec::try_new(
4270 constant_layout.rep_compression.as_ref(),
4271 decompressors,
4272 )?;
4273 let def_codec = LevelCodec::try_new(
4274 constant_layout.def_compression.as_ref(),
4275 decompressors,
4276 )?;
4277
4278 Box::new(ComplexAllNullScheduler::new(
4279 page_info.buffer_offsets_and_sizes.clone(),
4280 def_meaning.into(),
4281 rep_codec,
4282 def_codec,
4283 constant_layout.num_rep_values,
4284 constant_layout.num_def_values,
4285 )) as Box<dyn StructuralPageScheduler>
4286 }
4287 }
4288 Layout::BlobLayout(blob) => {
4289 let inner_scheduler = Self::page_layout_to_scheduler(
4290 page_info,
4291 blob.inner_layout.as_ref().expect_ok()?.as_ref(),
4292 decompressors,
4293 cache_repetition_index,
4294 target_field,
4295 )?;
4296 let def_meaning = blob
4297 .layers
4298 .iter()
4299 .map(|l| ProtobufUtils21::repdef_layer_to_def_interp(*l))
4300 .collect::<Vec<_>>();
4301 if matches!(target_field.data_type(), DataType::Struct(_)) {
4302 Box::new(BlobDescriptionPageScheduler::new(
4304 inner_scheduler,
4305 def_meaning.into(),
4306 ))
4307 } else {
4308 Box::new(BlobPageScheduler::new(
4310 inner_scheduler,
4311 page_info.priority,
4312 page_info.num_rows,
4313 def_meaning.into(),
4314 ))
4315 }
4316 }
4317 })
4318 }
4319
4320 fn page_info_to_scheduler(
4321 page_info: &PageInfo,
4322 page_index: usize,
4323 decompressors: &dyn DecompressionStrategy,
4324 cache_repetition_index: bool,
4325 target_field: &Field,
4326 ) -> Result<PageInfoAndScheduler> {
4327 let page_layout = page_info.encoding.as_structural();
4328 let scheduler = Self::page_layout_to_scheduler(
4329 page_info,
4330 page_layout,
4331 decompressors,
4332 cache_repetition_index,
4333 target_field,
4334 )?;
4335 Ok(PageInfoAndScheduler {
4336 page_index,
4337 num_rows: page_info.num_rows,
4338 scheduler,
4339 })
4340 }
4341}
4342
4343pub trait CachedPageData: Any + Send + Sync + DeepSizeOf + 'static {
4344 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static>;
4345}
4346
4347pub struct NoCachedPageData;
4348
4349impl DeepSizeOf for NoCachedPageData {
4350 fn deep_size_of_children(&self, _ctx: &mut Context) -> usize {
4351 0
4352 }
4353}
4354impl CachedPageData for NoCachedPageData {
4355 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
4356 self
4357 }
4358}
4359
4360pub struct CachedFieldData {
4361 pages: Vec<Arc<dyn CachedPageData>>,
4362}
4363
4364impl DeepSizeOf for CachedFieldData {
4365 fn deep_size_of_children(&self, ctx: &mut Context) -> usize {
4366 self.pages.deep_size_of_children(ctx)
4367 }
4368}
4369
4370#[derive(Debug, Clone)]
4380pub struct FieldDataCacheKey {
4381 pub column_index: u32,
4382 pub view_tag: String,
4383}
4384
4385impl CacheKey for FieldDataCacheKey {
4386 type ValueType = CachedFieldData;
4387
4388 fn key(&self) -> std::borrow::Cow<'_, str> {
4389 format!("{}:{}", self.column_index, self.view_tag).into()
4390 }
4391
4392 fn type_name() -> &'static str {
4393 "FieldData"
4394 }
4395
4396 fn schema() -> CacheKeySchema {
4397 CacheKeySchema::new("lance.encoding.logical.primitive.field-data-key", 1)
4398 }
4399
4400 fn write_key(&self, builder: &mut KeyBuilder) {
4401 builder.write_u32(self.column_index);
4402 builder.write_str(&self.view_tag);
4403 }
4404}
4405
4406impl StructuralFieldScheduler for StructuralPrimitiveFieldScheduler {
4407 fn initialize<'a>(
4408 &'a mut self,
4409 _filter: &'a FilterExpression,
4410 context: &'a SchedulerContext,
4411 ) -> BoxFuture<'a, Result<()>> {
4412 let cache_key = FieldDataCacheKey {
4413 column_index: self.column_index,
4414 view_tag: self.view_tag.clone(),
4415 };
4416 let cache = context.cache().clone();
4417
4418 async move {
4419 if let Some(cached_data) = cache.get_with_key(&cache_key).await {
4420 self.page_schedulers
4421 .iter_mut()
4422 .zip(cached_data.pages.iter())
4423 .for_each(|(page_scheduler, cached_data)| {
4424 page_scheduler.scheduler.load(cached_data);
4425 });
4426 return Ok(());
4427 }
4428
4429 let page_data = self
4430 .page_schedulers
4431 .iter_mut()
4432 .map(|s| s.scheduler.initialize(context.io()))
4433 .collect::<FuturesOrdered<_>>();
4434
4435 let page_data = page_data.try_collect::<Vec<_>>().await?;
4436 let cached_data = Arc::new(CachedFieldData { pages: page_data });
4437 cache.insert_with_key(&cache_key, cached_data).await;
4438 Ok(())
4439 }
4440 .boxed()
4441 }
4442
4443 fn schedule_ranges<'a>(
4444 &'a self,
4445 ranges: &[Range<u64>],
4446 _filter: &FilterExpression,
4447 ) -> Result<Box<dyn StructuralSchedulingJob + 'a>> {
4448 let ranges = ranges.to_vec();
4449 Ok(Box::new(StructuralPrimitiveFieldSchedulingJob::new(
4450 self, ranges,
4451 )))
4452 }
4453}
4454
4455#[derive(Debug)]
4458pub struct StructuralCompositeDecodeArrayTask {
4459 tasks: Vec<Box<dyn DecodePageTask>>,
4460 should_validate: bool,
4461 data_type: DataType,
4462}
4463
4464impl StructuralCompositeDecodeArrayTask {
4465 fn restore_validity(
4466 array: Arc<dyn Array>,
4467 unraveler: &mut CompositeRepDefUnraveler,
4468 ) -> Result<Arc<dyn Array>> {
4469 let validity = unraveler.unravel_validity(array.len())?;
4470 let Some(validity) = validity else {
4471 return Ok(array);
4472 };
4473 if array.data_type() == &DataType::Null {
4474 return Ok(array);
4476 }
4477 if validity.len() != array.len() {
4478 return Err(Error::invalid_input_source(
4479 format!(
4480 "Structural validity has {} entries for an array with {} values",
4481 validity.len(),
4482 array.len()
4483 )
4484 .into(),
4485 ));
4486 }
4487 Ok(make_array(unsafe {
4490 array
4491 .to_data()
4492 .into_builder()
4493 .nulls(Some(validity))
4494 .build_unchecked()
4495 }))
4496 }
4497}
4498
4499impl StructuralDecodeArrayTask for StructuralCompositeDecodeArrayTask {
4500 fn decode(self: Box<Self>) -> Result<DecodedArray> {
4501 let mut arrays = Vec::with_capacity(self.tasks.len());
4502 let mut unravelers = Vec::with_capacity(self.tasks.len());
4503 let mut data_size = 0u64;
4504 for task in self.tasks {
4505 let decoded = task.decode()?;
4506 data_size += decoded.data.data_size();
4507 unravelers.push(decoded.repdef);
4508
4509 let array = make_array(
4510 decoded
4511 .data
4512 .into_arrow(self.data_type.clone(), self.should_validate)?,
4513 );
4514
4515 arrays.push(array);
4516 }
4517 let array_refs = arrays.iter().map(|arr| arr.as_ref()).collect::<Vec<_>>();
4518 let array = arrow_select::concat::concat(&array_refs)?;
4519 let mut repdef = CompositeRepDefUnraveler::new(unravelers);
4520
4521 let array = Self::restore_validity(array, &mut repdef)?;
4522
4523 Ok(DecodedArray {
4524 array,
4525 repdef,
4526 data_size,
4527 })
4528 }
4529}
4530
4531#[derive(Debug)]
4532pub struct StructuralPrimitiveFieldDecoder {
4533 field: Arc<ArrowField>,
4534 page_decoders: VecDeque<Box<dyn StructuralPageDecoder>>,
4535 should_validate: bool,
4536 rows_drained_in_current: u64,
4537}
4538
4539impl StructuralPrimitiveFieldDecoder {
4540 pub fn new(field: &Arc<ArrowField>, should_validate: bool) -> Self {
4541 Self {
4542 field: field.clone(),
4543 page_decoders: VecDeque::new(),
4544 should_validate,
4545 rows_drained_in_current: 0,
4546 }
4547 }
4548}
4549
4550impl StructuralFieldDecoder for StructuralPrimitiveFieldDecoder {
4551 fn accept_page(&mut self, child: LoadedPageShard) -> Result<()> {
4552 assert!(child.path.is_empty());
4553 self.page_decoders.push_back(child.decoder);
4554 Ok(())
4555 }
4556
4557 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>> {
4558 let mut remaining = num_rows;
4559 let mut tasks = Vec::new();
4560 while remaining > 0 {
4561 let queued_pages = self.page_decoders.len();
4562 let Some(cur_page) = self.page_decoders.front_mut() else {
4563 return Err(Error::internal(format!(
4564 "Primitive decoder missing page decoder while draining field '{}' (data_type={:?}, requested_rows={}, remaining_rows={}, rows_drained_in_current={}, queued_pages={})",
4565 self.field.name(),
4566 self.field.data_type(),
4567 num_rows,
4568 remaining,
4569 self.rows_drained_in_current,
4570 queued_pages
4571 )));
4572 };
4573 let num_in_page = cur_page.num_rows() - self.rows_drained_in_current;
4574 let to_take = num_in_page.min(remaining);
4575
4576 let task = cur_page.drain(to_take)?;
4577 tasks.push(task);
4578
4579 if to_take == num_in_page {
4580 self.page_decoders.pop_front();
4581 self.rows_drained_in_current = 0;
4582 } else {
4583 self.rows_drained_in_current += to_take;
4584 }
4585
4586 remaining -= to_take;
4587 }
4588 Ok(Box::new(StructuralCompositeDecodeArrayTask {
4589 tasks,
4590 should_validate: self.should_validate,
4591 data_type: self.field.data_type().clone(),
4592 }))
4593 }
4594
4595 fn data_type(&self) -> &DataType {
4596 self.field.data_type()
4597 }
4598}
4599
4600struct SerializedFullZip {
4602 values: LanceBuffer,
4604 repetition_index: Option<LanceBuffer>,
4606}
4607
4608const MINIBLOCK_ALIGNMENT: usize = 8;
4623
4624#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4651pub(crate) enum MiniblockChunkSize {
4652 U16,
4653 U32,
4654}
4655
4656#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4657enum ComplexNullEncoding {
4658 RawLevels,
4659 CompressedLevels,
4660}
4661
4662#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4663enum FixedWidthDictionaryEncoding {
4664 Exclude64Bit,
4665 Include64Bit,
4666}
4667
4668trait PrimitivePageEncodingBehavior: Send + Sync + Debug {
4669 fn validate_field(&self, _field: &Field, _metadata: &HashMap<String, String>) -> Result<()> {
4670 Ok(())
4671 }
4672
4673 fn try_plan_pages(
4674 &self,
4675 _ctx: &PrimitivePlanContext<'_>,
4676 _arrays: &[ArrayRef],
4677 _normalized: &NormalizedStructuralPlan,
4678 _row_number: u64,
4679 _num_rows: u64,
4680 _num_values: u64,
4681 ) -> Result<Option<Vec<PrimitivePageData>>> {
4682 Ok(None)
4683 }
4684
4685 fn try_encode_page(
4686 &self,
4687 _ctx: &PrimitiveEncodeContext,
4688 page: PrimitivePageData,
4689 ) -> Result<PrimitiveEncodeAttempt> {
4690 Ok(PrimitiveEncodeAttempt::Unhandled(page))
4691 }
4692}
4693
4694#[derive(Debug, Clone)]
4697pub struct PrimitivePageEncoding {
4698 behavior: Arc<dyn PrimitivePageEncodingBehavior>,
4699}
4700
4701impl PrimitivePageEncoding {
4702 pub fn reject_sparse() -> Self {
4704 Self {
4705 behavior: Arc::new(RejectSparsePrimitiveEncoding),
4706 }
4707 }
4708
4709 pub fn constant() -> Self {
4711 Self {
4712 behavior: Arc::new(ConstantPrimitiveEncoding),
4713 }
4714 }
4715
4716 pub fn sparse(compression: Arc<dyn CompressionStrategy>) -> Self {
4718 Self {
4719 behavior: Arc::new(SparsePrimitiveEncoding { compression }),
4720 }
4721 }
4722
4723 pub fn dense_u16(compression: Arc<dyn CompressionStrategy>) -> Self {
4725 Self {
4726 behavior: Arc::new(DenseU16PrimitiveEncoding { compression }),
4727 }
4728 }
4729
4730 pub fn dense_u32(compression: Arc<dyn CompressionStrategy>) -> Self {
4732 Self {
4733 behavior: Arc::new(DenseU32PrimitiveEncoding { compression }),
4734 }
4735 }
4736}
4737
4738#[derive(Debug)]
4739struct RejectSparsePrimitiveEncoding;
4740
4741#[derive(Debug)]
4742struct ConstantPrimitiveEncoding;
4743
4744#[derive(Debug)]
4745struct SparsePrimitiveEncoding {
4746 compression: Arc<dyn CompressionStrategy>,
4747}
4748
4749#[derive(Debug)]
4750struct DenseU16PrimitiveEncoding {
4751 compression: Arc<dyn CompressionStrategy>,
4752}
4753
4754#[derive(Debug)]
4755struct DenseU32PrimitiveEncoding {
4756 compression: Arc<dyn CompressionStrategy>,
4757}
4758
4759pub struct PrimitiveStructuralEncoder {
4760 accumulation_queue: AccumulationQueue,
4762
4763 keep_original_array: bool,
4764 accumulated_repdefs: Vec<RepDefBuilder>,
4765 page_encodings: Arc<[PrimitivePageEncoding]>,
4766 column_index: u32,
4767 field: Field,
4768 encoding_metadata: Arc<HashMap<String, String>>,
4769}
4770
4771struct CompressedLevelsChunk {
4772 data: LanceBuffer,
4773 num_levels: u16,
4774}
4775
4776struct CompressedLevels {
4777 data: Vec<CompressedLevelsChunk>,
4778 compression: CompressiveEncoding,
4779 rep_index: Option<LanceBuffer>,
4780}
4781
4782struct SerializedMiniBlockPage {
4783 num_buffers: u64,
4784 data: LanceBuffer,
4785 metadata: LanceBuffer,
4786}
4787
4788#[derive(Debug, Clone, Copy)]
4789struct DictEncodingBudget {
4790 max_dict_entries: u32,
4791 max_encoded_size: usize,
4792}
4793
4794enum PrimitivePageStructure {
4795 Dense {
4796 repdef: SerializedRepDefs,
4797 single_row_miniblock_repdef_levels: Option<u64>,
4798 },
4799 Sparse {
4800 plan: sparse::SparseStructuralPlan,
4801 prepared_values: Option<sparse::writer::PreparedSparseValues>,
4802 },
4803}
4804
4805struct PrimitivePageData {
4807 arrays: Vec<ArrayRef>,
4809 structure: PrimitivePageStructure,
4811 row_number: u64,
4813 num_rows: u64,
4815}
4816
4817struct PrimitivePlanContext<'a> {
4818 column_idx: u32,
4819 field: &'a Field,
4820 encoding_metadata: &'a HashMap<String, String>,
4821}
4822
4823enum PrimitiveEncodeAttempt {
4824 Encoded(EncodedPage),
4825 Unhandled(PrimitivePageData),
4826}
4827
4828#[derive(Clone)]
4833struct PrimitiveEncodeContext {
4834 column_idx: u32,
4836 field: Field,
4837 encoding_metadata: Arc<HashMap<String, String>>,
4838 is_simple_validity: bool,
4839 has_repdef_info: bool,
4840}
4841
4842impl PrimitiveStructuralEncoder {
4843 pub fn try_new(
4844 options: &EncodingOptions,
4845 page_encodings: Arc<[PrimitivePageEncoding]>,
4846 column_index: u32,
4847 field: Field,
4848 encoding_metadata: Arc<HashMap<String, String>>,
4849 ) -> Result<Self> {
4850 for page_encoding in page_encodings.iter() {
4851 page_encoding
4852 .behavior
4853 .validate_field(&field, &encoding_metadata)?;
4854 }
4855 Ok(Self {
4856 accumulation_queue: AccumulationQueue::new(
4857 options.cache_bytes_per_column,
4858 column_index,
4859 options.keep_original_array,
4860 ),
4861 keep_original_array: options.keep_original_array,
4862 accumulated_repdefs: Vec::new(),
4863 column_index,
4864 page_encodings,
4865 field,
4866 encoding_metadata,
4867 })
4868 }
4869
4870 fn encode_page(
4871 page_encodings: &[PrimitivePageEncoding],
4872 ctx: &PrimitiveEncodeContext,
4873 mut page: PrimitivePageData,
4874 ) -> Result<EncodedPage> {
4875 for page_encoding in page_encodings {
4876 match page_encoding.behavior.try_encode_page(ctx, page)? {
4877 PrimitiveEncodeAttempt::Encoded(page) => return Ok(page),
4878 PrimitiveEncodeAttempt::Unhandled(unhandled) => page = unhandled,
4879 }
4880 }
4881 Err(Error::invalid_input_source(
4882 format!(
4883 "No primitive page encoding atom supports field '{}'",
4884 ctx.field.name
4885 )
4886 .into(),
4887 ))
4888 }
4889
4890 fn is_narrow(data_block: &DataBlock) -> bool {
4898 const MINIBLOCK_MAX_BYTE_LENGTH_PER_VALUE: u64 = 256;
4899
4900 if let Some(max_len_array) = data_block.get_stat(Stat::MaxLength) {
4901 let max_len_array = max_len_array
4902 .as_any()
4903 .downcast_ref::<PrimitiveArray<UInt64Type>>()
4904 .unwrap();
4905 if max_len_array.value(0) < MINIBLOCK_MAX_BYTE_LENGTH_PER_VALUE {
4906 return true;
4907 }
4908 }
4909 false
4910 }
4911
4912 fn prefers_miniblock(
4913 data_block: &DataBlock,
4914 encoding_metadata: &HashMap<String, String>,
4915 ) -> bool {
4916 if let Some(user_requested) = encoding_metadata.get(STRUCTURAL_ENCODING_META_KEY) {
4918 return user_requested.to_lowercase() == STRUCTURAL_ENCODING_MINIBLOCK;
4919 }
4920 Self::is_narrow(data_block)
4922 }
4923
4924 fn prefers_fullzip(encoding_metadata: &HashMap<String, String>) -> bool {
4925 if let Some(user_requested) = encoding_metadata.get(STRUCTURAL_ENCODING_META_KEY) {
4929 return user_requested.to_lowercase() == STRUCTURAL_ENCODING_FULLZIP;
4930 }
4931 true
4932 }
4933
4934 fn serialize_miniblocks(
4981 miniblocks: MiniBlockCompressed,
4982 rep: Option<Vec<CompressedLevelsChunk>>,
4983 def: Option<Vec<CompressedLevelsChunk>>,
4984 miniblock_chunk_size: MiniblockChunkSize,
4985 ) -> Result<SerializedMiniBlockPage> {
4986 let bytes_rep = rep
4987 .as_ref()
4988 .map(|rep| rep.iter().map(|r| r.data.len()).sum::<usize>())
4989 .unwrap_or(0);
4990 let bytes_def = def
4991 .as_ref()
4992 .map(|def| def.iter().map(|d| d.data.len()).sum::<usize>())
4993 .unwrap_or(0);
4994 let bytes_data = miniblocks.data.iter().map(|d| d.len()).sum::<usize>();
4995 let mut num_buffers = miniblocks.data.len();
4996 if rep.is_some() {
4997 num_buffers += 1;
4998 }
4999 if def.is_some() {
5000 num_buffers += 1;
5001 }
5002 let max_extra = 9 * num_buffers;
5004 let mut data_buffer = Vec::with_capacity(bytes_rep + bytes_def + bytes_data + max_extra);
5005 let chunk_size_bytes = match miniblock_chunk_size {
5006 MiniblockChunkSize::U16 => 2,
5007 MiniblockChunkSize::U32 => 4,
5008 };
5009 let mut meta_buffer = Vec::with_capacity(miniblocks.chunks.len() * chunk_size_bytes);
5010
5011 let mut rep_iter = rep.map(|r| r.into_iter());
5012 let mut def_iter = def.map(|d| d.into_iter());
5013
5014 let mut buffer_offsets = vec![0; miniblocks.data.len()];
5015 for chunk in miniblocks.chunks {
5016 let start_pos = data_buffer.len();
5017 debug_assert_eq!(start_pos % MINIBLOCK_ALIGNMENT, 0);
5019
5020 let rep = rep_iter.as_mut().map(|r| r.next().unwrap());
5021 let def = def_iter.as_mut().map(|d| d.next().unwrap());
5022
5023 let num_levels = rep
5025 .as_ref()
5026 .map(|r| r.num_levels)
5027 .unwrap_or(def.as_ref().map(|d| d.num_levels).unwrap_or(0));
5028 data_buffer.extend_from_slice(&num_levels.to_le_bytes());
5029
5030 if let Some(rep) = rep.as_ref() {
5032 let bytes_rep = u16::try_from(rep.data.len()).map_err(|_| {
5033 Error::internal(format!(
5034 "Repetition buffer size ({} bytes) too large",
5035 rep.data.len()
5036 ))
5037 })?;
5038 data_buffer.extend_from_slice(&bytes_rep.to_le_bytes());
5039 }
5040 if let Some(def) = def.as_ref() {
5041 let bytes_def = u16::try_from(def.data.len()).map_err(|_| {
5042 Error::internal(format!(
5043 "Definition buffer size ({} bytes) too large",
5044 def.data.len()
5045 ))
5046 })?;
5047 data_buffer.extend_from_slice(&bytes_def.to_le_bytes());
5048 }
5049
5050 if miniblock_chunk_size == MiniblockChunkSize::U32 {
5051 for &buffer_size in &chunk.buffer_sizes {
5052 data_buffer.extend_from_slice(&buffer_size.to_le_bytes());
5053 }
5054 } else {
5055 for &buffer_size in &chunk.buffer_sizes {
5056 let buffer_size = u16::try_from(buffer_size).map_err(|_| {
5057 Error::internal(format!(
5058 "Mini-block buffer size ({} bytes) too large for 16-bit metadata",
5059 buffer_size
5060 ))
5061 })?;
5062 data_buffer.extend_from_slice(&buffer_size.to_le_bytes());
5063 }
5064 }
5065
5066 let add_padding = |data_buffer: &mut Vec<u8>| {
5068 let pad = pad_bytes::<MINIBLOCK_ALIGNMENT>(data_buffer.len());
5069 data_buffer.extend(iter::repeat_n(FILL_BYTE, pad));
5070 };
5071 add_padding(&mut data_buffer);
5072
5073 if let Some(rep) = rep.as_ref() {
5075 data_buffer.extend_from_slice(&rep.data);
5076 add_padding(&mut data_buffer);
5077 }
5078 if let Some(def) = def.as_ref() {
5079 data_buffer.extend_from_slice(&def.data);
5080 add_padding(&mut data_buffer);
5081 }
5082 for (buffer_size, (buffer, buffer_offset)) in chunk
5083 .buffer_sizes
5084 .iter()
5085 .zip(miniblocks.data.iter().zip(buffer_offsets.iter_mut()))
5086 {
5087 let start = *buffer_offset;
5088 let end = start + *buffer_size as usize;
5089 *buffer_offset += *buffer_size as usize;
5090 data_buffer.extend_from_slice(&buffer[start..end]);
5091 add_padding(&mut data_buffer);
5092 }
5093
5094 let chunk_bytes = data_buffer.len() - start_pos;
5095 let max_chunk_size = match miniblock_chunk_size {
5096 MiniblockChunkSize::U16 => 32 * 1024,
5097 MiniblockChunkSize::U32 => 1_u64 << 31,
5098 };
5099 if chunk_bytes == 0 || chunk_bytes as u64 > max_chunk_size {
5100 return Err(Error::internal(format!(
5101 "Mini-block chunk size {} bytes exceeds the {} byte metadata limit",
5102 chunk_bytes, max_chunk_size
5103 )));
5104 }
5105 if chunk_bytes % MINIBLOCK_ALIGNMENT != 0 {
5106 return Err(Error::internal(format!(
5107 "Mini-block chunk size {} bytes is not aligned to {} bytes",
5108 chunk_bytes, MINIBLOCK_ALIGNMENT
5109 )));
5110 }
5111 if chunk.log_num_values > 15 {
5112 return Err(Error::internal(format!(
5113 "Mini-block log_num_values {} exceeds the 4-bit metadata limit",
5114 chunk.log_num_values
5115 )));
5116 }
5117 let divided_bytes = chunk_bytes / MINIBLOCK_ALIGNMENT;
5121 let divided_bytes_minus_one = (divided_bytes - 1) as u64;
5122
5123 let metadata = (divided_bytes_minus_one << 4) | chunk.log_num_values as u64;
5124 if miniblock_chunk_size == MiniblockChunkSize::U32 {
5125 meta_buffer.extend_from_slice(&(metadata as u32).to_le_bytes());
5126 } else {
5127 meta_buffer.extend_from_slice(&(metadata as u16).to_le_bytes());
5128 }
5129 }
5130
5131 let data_buffer = LanceBuffer::from(data_buffer);
5132 let metadata_buffer = LanceBuffer::from(meta_buffer);
5133
5134 Ok(SerializedMiniBlockPage {
5135 num_buffers: miniblocks.data.len() as u64,
5136 data: data_buffer,
5137 metadata: metadata_buffer,
5138 })
5139 }
5140
5141 fn compress_levels(
5146 mut levels: RepDefSlicer<'_>,
5147 num_elements: u64,
5148 compression_strategy: &dyn CompressionStrategy,
5149 chunks: &[MiniBlockChunk],
5150 max_rep: u16,
5152 ) -> Result<CompressedLevels> {
5153 let mut rep_index = if max_rep > 0 {
5154 Vec::with_capacity(chunks.len())
5155 } else {
5156 vec![]
5157 };
5158 let num_levels = levels.num_levels() as u64;
5160 let levels_buf = levels.all_levels().clone();
5161
5162 let mut fixed_width_block = FixedWidthDataBlock {
5163 data: levels_buf,
5164 bits_per_value: 16,
5165 num_values: num_levels,
5166 block_info: BlockInfo::new(),
5167 };
5168 fixed_width_block.compute_stat();
5170
5171 let levels_block = DataBlock::FixedWidth(fixed_width_block);
5172 let levels_field = Field::new_arrow("", DataType::UInt16, false)?;
5173 let compressor =
5175 compression_strategy.create_block_compressor(&levels_field, &levels_block)?;
5176 let mut compressor_desc = None;
5177 let mut level_chunks = Vec::with_capacity(chunks.len());
5179 let mut values_counter = 0;
5180 for (chunk_idx, chunk) in chunks.iter().enumerate() {
5181 let chunk_num_values = chunk.num_values(values_counter, num_elements);
5182 debug_assert!(chunk_num_values > 0);
5183 values_counter += chunk_num_values;
5184 let chunk_levels = if chunk_idx < chunks.len() - 1 {
5185 levels.slice_next(chunk_num_values as usize)
5186 } else {
5187 levels.slice_rest()
5188 };
5189 let num_chunk_levels = (chunk_levels.len() / 2) as u64;
5190 if max_rep > 0 {
5191 let rep_values = chunk_levels.borrow_to_typed_slice::<u16>();
5201 let rep_values = rep_values.as_ref();
5202
5203 let mut num_rows = rep_values.iter().skip(1).filter(|v| **v == max_rep).count();
5206 let num_leftovers = if chunk_idx < chunks.len() - 1 {
5207 rep_values
5208 .iter()
5209 .rev()
5210 .position(|v| *v == max_rep)
5211 .map(|pos| pos + 1)
5213 .unwrap_or(rep_values.len())
5214 } else {
5215 0
5217 };
5218
5219 if chunk_idx != 0 && rep_values.first() == Some(&max_rep) {
5220 let rep_len = rep_index.len();
5224 if rep_index[rep_len - 1] != 0 {
5225 rep_index[rep_len - 2] += 1;
5227 rep_index[rep_len - 1] = 0;
5228 }
5229 }
5230
5231 if chunk_idx == chunks.len() - 1 {
5232 num_rows += 1;
5234 }
5235 rep_index.push(num_rows as u64);
5236 rep_index.push(num_leftovers as u64);
5237 }
5238 let mut chunk_fixed_width = FixedWidthDataBlock {
5239 data: chunk_levels,
5240 bits_per_value: 16,
5241 num_values: num_chunk_levels,
5242 block_info: BlockInfo::new(),
5243 };
5244 chunk_fixed_width.compute_stat();
5245 let chunk_levels_block = DataBlock::FixedWidth(chunk_fixed_width);
5246 let (compressed_levels, chunk_compressor_desc) =
5247 compressor.compress(chunk_levels_block)?;
5248 if let Some(compressor_desc) = compressor_desc.as_ref() {
5249 if compressor_desc != &chunk_compressor_desc {
5250 return Err(Error::internal(
5251 "Rep/def block compressor changed encoding between chunks".to_string(),
5252 ));
5253 }
5254 } else {
5255 compressor_desc = Some(chunk_compressor_desc);
5256 }
5257 let compressed_levels = compressed_levels.ok_or_else(|| {
5258 Error::internal(
5259 "Rep/def block compressor selected a metadata-only codec".to_string(),
5260 )
5261 })?;
5262 let num_levels = u16::try_from(num_chunk_levels).map_err(|_| {
5263 Error::invalid_input_source(
5264 format!(
5265 "Mini-block cannot encode {} rep/def levels in one chunk. \
5266 This usually means a top-level row contains too much nested structure \
5267 for the current layout.",
5268 num_chunk_levels
5269 )
5270 .into(),
5271 )
5272 })?;
5273 level_chunks.push(CompressedLevelsChunk {
5274 data: compressed_levels,
5275 num_levels,
5276 });
5277 }
5278 debug_assert_eq!(levels.num_levels_remaining(), 0);
5279 let rep_index = if rep_index.is_empty() {
5280 None
5281 } else {
5282 Some(LanceBuffer::reinterpret_vec(rep_index))
5283 };
5284 Ok(CompressedLevels {
5285 data: level_chunks,
5286 compression: compressor_desc.ok_or_else(|| {
5287 Error::internal("Rep/def compression produced no chunks".to_string())
5288 })?,
5289 rep_index,
5290 })
5291 }
5292
5293 fn encode_simple_all_null(
5294 column_idx: u32,
5295 num_rows: u64,
5296 row_number: u64,
5297 ) -> Result<EncodedPage> {
5298 let description =
5299 ProtobufUtils21::constant_layout(&[DefinitionInterpretation::NullableItem], None);
5300 Ok(EncodedPage {
5301 column_idx,
5302 data: vec![],
5303 description: PageEncoding::Structural(description),
5304 num_rows,
5305 row_number,
5306 })
5307 }
5308
5309 fn encode_complex_all_null_vals(
5310 data: &Arc<[u16]>,
5311 compression_strategy: &dyn CompressionStrategy,
5312 ) -> Result<(LanceBuffer, pb21::CompressiveEncoding)> {
5313 let buffer = LanceBuffer::reinterpret_slice(data.clone());
5314 let mut fixed_width_block = FixedWidthDataBlock {
5315 data: buffer,
5316 bits_per_value: 16,
5317 num_values: data.len() as u64,
5318 block_info: BlockInfo::new(),
5319 };
5320 fixed_width_block.compute_stat();
5321
5322 let levels_block = DataBlock::FixedWidth(fixed_width_block);
5323 let levels_field = Field::new_arrow("", DataType::UInt16, false)?;
5324 let (compressed_buffer, encoding) =
5325 compress_required_block(compression_strategy, &levels_field, levels_block)?;
5326 Ok((compressed_buffer, encoding))
5327 }
5328
5329 fn encode_complex_all_null(
5333 column_idx: u32,
5334 repdef: crate::repdef::SerializedRepDefs,
5335 row_number: u64,
5336 num_rows: u64,
5337 complex_null_encoding: ComplexNullEncoding,
5338 compression_strategy: &dyn CompressionStrategy,
5339 ) -> Result<EncodedPage> {
5340 if complex_null_encoding == ComplexNullEncoding::RawLevels {
5341 let rep_bytes = if let Some(rep) = repdef.repetition_levels.as_ref() {
5342 LanceBuffer::reinterpret_slice(rep.clone())
5343 } else {
5344 LanceBuffer::empty()
5345 };
5346
5347 let def_bytes = if let Some(def) = repdef.definition_levels.as_ref() {
5348 LanceBuffer::reinterpret_slice(def.clone())
5349 } else {
5350 LanceBuffer::empty()
5351 };
5352
5353 let description = ProtobufUtils21::constant_layout(&repdef.def_meaning, None);
5354 return Ok(EncodedPage {
5355 column_idx,
5356 data: vec![rep_bytes, def_bytes],
5357 description: PageEncoding::Structural(description),
5358 num_rows,
5359 row_number,
5360 });
5361 }
5362
5363 let (rep_bytes, rep_encoding, num_rep_values) = if let Some(rep) =
5364 repdef.repetition_levels.as_ref()
5365 {
5366 let num_values = rep.len() as u64;
5367 let (buffer, encoding) = Self::encode_complex_all_null_vals(rep, compression_strategy)?;
5368 (buffer, Some(encoding), num_values)
5369 } else {
5370 (LanceBuffer::empty(), None, 0)
5371 };
5372
5373 let (def_bytes, def_encoding, num_def_values) = if let Some(def) =
5374 repdef.definition_levels.as_ref()
5375 {
5376 let num_values = def.len() as u64;
5377 let (buffer, encoding) = Self::encode_complex_all_null_vals(def, compression_strategy)?;
5378 (buffer, Some(encoding), num_values)
5379 } else {
5380 (LanceBuffer::empty(), None, 0)
5381 };
5382
5383 let description = ProtobufUtils21::compressed_all_null_constant_layout(
5384 &repdef.def_meaning,
5385 rep_encoding,
5386 def_encoding,
5387 num_rep_values,
5388 num_def_values,
5389 );
5390 Ok(EncodedPage {
5391 column_idx,
5392 data: vec![rep_bytes, def_bytes],
5393 description: PageEncoding::Structural(description),
5394 num_rows,
5395 row_number,
5396 })
5397 }
5398
5399 fn leaf_validity(
5400 repdef: &crate::repdef::SerializedRepDefs,
5401 num_values: usize,
5402 ) -> Result<Option<BooleanBuffer>> {
5403 let rep = repdef
5404 .repetition_levels
5405 .as_ref()
5406 .map(|rep| rep.as_ref().to_vec());
5407 let def = repdef
5408 .definition_levels
5409 .as_ref()
5410 .map(|def| def.as_ref().to_vec());
5411 let mut unraveler = RepDefUnraveler::new(
5412 rep,
5413 def,
5414 repdef.def_meaning.clone().into(),
5415 num_values as u64,
5416 );
5417 if unraveler.is_all_valid() {
5418 return Ok(None);
5419 }
5420 let mut validity = BooleanBufferBuilder::new(num_values);
5421 unraveler.unravel_validity(&mut validity)?;
5422 Ok(Some(validity.finish()))
5423 }
5424
5425 fn is_constant_values(
5426 arrays: &[ArrayRef],
5427 scalar: &ArrayRef,
5428 validity: Option<&BooleanBuffer>,
5429 ) -> Result<bool> {
5430 debug_assert_eq!(scalar.len(), 1);
5431 debug_assert_eq!(scalar.null_count(), 0);
5432
5433 match scalar.data_type() {
5434 DataType::Boolean => {
5435 let mut global_idx = 0usize;
5436 let scalar_val = scalar.as_boolean().value(0);
5437 for arr in arrays {
5438 let bool_arr = arr.as_boolean();
5439 for i in 0..arr.len() {
5440 let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
5441 global_idx += 1;
5442 if !is_valid {
5443 continue;
5444 }
5445 if bool_arr.value(i) != scalar_val {
5446 return Ok(false);
5447 }
5448 }
5449 }
5450 Ok(true)
5451 }
5452 DataType::Utf8 => Self::is_constant_utf8::<i32>(arrays, scalar, validity),
5453 DataType::LargeUtf8 => Self::is_constant_utf8::<i64>(arrays, scalar, validity),
5454 DataType::Binary => Self::is_constant_binary::<i32>(arrays, scalar, validity),
5455 DataType::LargeBinary => Self::is_constant_binary::<i64>(arrays, scalar, validity),
5456 data_type => {
5457 let mut global_idx = 0usize;
5458 let Some(byte_width) = data_type.byte_width_opt() else {
5459 return Ok(false);
5460 };
5461 let scalar_data = scalar.to_data();
5462 if scalar_data.buffers().len() != 1 || !scalar_data.child_data().is_empty() {
5463 return Ok(false);
5464 }
5465 let scalar_bytes = scalar_data.buffers()[0].as_slice();
5466 if scalar_bytes.len() != byte_width {
5467 return Ok(false);
5468 }
5469
5470 for arr in arrays {
5471 let data = arr.to_data();
5472 if data.buffers().is_empty() {
5473 return Ok(false);
5474 }
5475 let buf = data.buffers()[0].as_slice();
5476 let base = data.offset();
5477 for i in 0..arr.len() {
5478 let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
5479 global_idx += 1;
5480 if !is_valid {
5481 continue;
5482 }
5483 let start = (base + i) * byte_width;
5484 if buf[start..start + byte_width] != scalar_bytes[..] {
5485 return Ok(false);
5486 }
5487 }
5488 }
5489 Ok(true)
5490 }
5491 }
5492 }
5493
5494 fn is_constant_utf8<O: arrow_array::OffsetSizeTrait>(
5495 arrays: &[ArrayRef],
5496 scalar: &ArrayRef,
5497 validity: Option<&BooleanBuffer>,
5498 ) -> Result<bool> {
5499 debug_assert_eq!(scalar.len(), 1);
5500 let scalar_val = scalar.as_string::<O>().value(0).as_bytes();
5501 let mut global_idx = 0usize;
5502 for arr in arrays {
5503 let str_arr = arr.as_string::<O>();
5504 for i in 0..arr.len() {
5505 let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
5506 global_idx += 1;
5507 if !is_valid {
5508 continue;
5509 }
5510 if str_arr.value(i).as_bytes() != scalar_val {
5511 return Ok(false);
5512 }
5513 }
5514 }
5515 Ok(true)
5516 }
5517
5518 fn is_constant_binary<O: arrow_array::OffsetSizeTrait>(
5519 arrays: &[ArrayRef],
5520 scalar: &ArrayRef,
5521 validity: Option<&BooleanBuffer>,
5522 ) -> Result<bool> {
5523 debug_assert_eq!(scalar.len(), 1);
5524 let scalar_val = scalar.as_binary::<O>().value(0);
5525 let mut global_idx = 0usize;
5526 for arr in arrays {
5527 let bin_arr = arr.as_binary::<O>();
5528 for i in 0..arr.len() {
5529 let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
5530 global_idx += 1;
5531 if !is_valid {
5532 continue;
5533 }
5534 if bin_arr.value(i) != scalar_val {
5535 return Ok(false);
5536 }
5537 }
5538 }
5539 Ok(true)
5540 }
5541
5542 fn find_constant_scalar(
5543 arrays: &[ArrayRef],
5544 validity: Option<&BooleanBuffer>,
5545 ) -> Result<Option<ArrayRef>> {
5546 if arrays.is_empty() {
5547 return Ok(None);
5548 }
5549
5550 let global_scalar_idx = if let Some(validity) = validity {
5551 let Some(idx) = (0..validity.len()).find(|&i| validity.value(i)) else {
5552 return Ok(None);
5553 };
5554 idx
5555 } else {
5556 0
5557 };
5558
5559 let mut idx_remaining = global_scalar_idx;
5560 let mut scalar_arr_idx = 0usize;
5561 while scalar_arr_idx < arrays.len() {
5562 let len = arrays[scalar_arr_idx].len();
5563 if idx_remaining < len {
5564 break;
5565 }
5566 idx_remaining -= len;
5567 scalar_arr_idx += 1;
5568 }
5569
5570 if scalar_arr_idx >= arrays.len() {
5571 return Ok(None);
5572 }
5573
5574 let scalar =
5575 lance_arrow::scalar::extract_scalar_value(&arrays[scalar_arr_idx], idx_remaining)?;
5576 if scalar.null_count() != 0 {
5577 return Ok(None);
5578 }
5579 if !Self::is_constant_values(arrays, &scalar, validity)? {
5580 return Ok(None);
5581 }
5582 Ok(Some(scalar))
5583 }
5584
5585 fn resolve_dict_values_compression_metadata(
5586 field_metadata: &HashMap<String, String>,
5587 env_compression: Option<String>,
5588 env_compression_level: Option<String>,
5589 ) -> HashMap<String, String> {
5590 let mut metadata = HashMap::new();
5591
5592 let compression = field_metadata
5593 .get(DICT_VALUES_COMPRESSION_META_KEY)
5594 .cloned()
5595 .or(env_compression)
5596 .unwrap_or_else(|| DEFAULT_DICT_VALUES_COMPRESSION.to_string());
5597 metadata.insert(COMPRESSION_META_KEY.to_string(), compression);
5598
5599 if let Some(compression_level) = field_metadata
5600 .get(DICT_VALUES_COMPRESSION_LEVEL_META_KEY)
5601 .cloned()
5602 .or(env_compression_level)
5603 {
5604 metadata.insert(COMPRESSION_LEVEL_META_KEY.to_string(), compression_level);
5605 }
5606
5607 metadata
5608 }
5609
5610 fn build_dict_values_compressor_field(field: &Field) -> Result<Field> {
5611 let mut dict_values_field = Field::new_arrow("", DataType::UInt16, false)?;
5616 dict_values_field.metadata = Self::resolve_dict_values_compression_metadata(
5617 &field.metadata,
5618 env::var(DICT_VALUES_COMPRESSION_ENV_VAR).ok(),
5619 env::var(DICT_VALUES_COMPRESSION_LEVEL_ENV_VAR).ok(),
5620 );
5621 Ok(dict_values_field)
5622 }
5623
5624 #[allow(clippy::too_many_arguments)]
5625 fn encode_miniblock(
5626 column_idx: u32,
5627 field: &Field,
5628 compression_strategy: &dyn CompressionStrategy,
5629 data: DataBlock,
5630 repdef: crate::repdef::SerializedRepDefs,
5631 row_number: u64,
5632 dictionary_data: Option<DataBlock>,
5633 num_rows: u64,
5634 miniblock_chunk_size: MiniblockChunkSize,
5635 ) -> Result<EncodedPage> {
5636 if let DataBlock::AllNull(_null_block) = data {
5637 unreachable!()
5640 }
5641
5642 let num_items = data.num_values();
5643
5644 let compressor = compression_strategy.create_miniblock_compressor(field, &data)?;
5645 let common_chunk_buffers =
5646 u64::from(repdef.rep_slicer().is_some()) + u64::from(repdef.def_slicer().is_some());
5647 let support_large_chunk = miniblock_chunk_size == MiniblockChunkSize::U32;
5648 let compression_context =
5649 MiniBlockCompressionContext::new(common_chunk_buffers, support_large_chunk, true);
5650 let (compressed_data, value_encoding) = compressor.compress(compression_context, data)?;
5651
5652 let max_rep = repdef.def_meaning.iter().filter(|l| l.is_list()).count() as u16;
5653
5654 let mut compressed_rep = repdef
5655 .rep_slicer()
5656 .map(|rep_slicer| {
5657 Self::compress_levels(
5658 rep_slicer,
5659 num_items,
5660 compression_strategy,
5661 &compressed_data.chunks,
5662 max_rep,
5663 )
5664 })
5665 .transpose()?;
5666
5667 let (rep_index, rep_index_depth) =
5668 match compressed_rep.as_mut().and_then(|cr| cr.rep_index.as_mut()) {
5669 Some(rep_index) => (Some(rep_index.clone()), 1),
5670 None => (None, 0),
5671 };
5672
5673 let mut compressed_def = repdef
5674 .def_slicer()
5675 .map(|def_slicer| {
5676 Self::compress_levels(
5677 def_slicer,
5678 num_items,
5679 compression_strategy,
5680 &compressed_data.chunks,
5681 0,
5682 )
5683 })
5684 .transpose()?;
5685
5686 let rep_data = compressed_rep
5692 .as_mut()
5693 .map(|cr| std::mem::take(&mut cr.data));
5694 let def_data = compressed_def
5695 .as_mut()
5696 .map(|cd| std::mem::take(&mut cd.data));
5697
5698 let serialized =
5699 Self::serialize_miniblocks(compressed_data, rep_data, def_data, miniblock_chunk_size)?;
5700 let has_large_chunk = miniblock_chunk_size == MiniblockChunkSize::U32;
5701
5702 let mut data = Vec::with_capacity(4);
5704 data.push(serialized.metadata);
5705 data.push(serialized.data);
5706
5707 if let Some(dictionary_data) = dictionary_data {
5708 let num_dictionary_items = dictionary_data.num_values();
5709 let dict_values_field = Self::build_dict_values_compressor_field(field)?;
5710
5711 let (dictionary_buffer, dictionary_encoding) =
5712 compress_required_block(compression_strategy, &dict_values_field, dictionary_data)?;
5713
5714 data.push(dictionary_buffer);
5715 if let Some(rep_index) = rep_index {
5716 data.push(rep_index);
5717 }
5718
5719 let description = ProtobufUtils21::miniblock_layout(
5720 compressed_rep.map(|cr| cr.compression),
5721 compressed_def.map(|cd| cd.compression),
5722 value_encoding,
5723 rep_index_depth,
5724 serialized.num_buffers,
5725 Some((dictionary_encoding, num_dictionary_items)),
5726 &repdef.def_meaning,
5727 num_items,
5728 has_large_chunk,
5729 );
5730 Ok(EncodedPage {
5731 num_rows,
5732 column_idx,
5733 data,
5734 description: PageEncoding::Structural(description),
5735 row_number,
5736 })
5737 } else {
5738 let description = ProtobufUtils21::miniblock_layout(
5739 compressed_rep.map(|cr| cr.compression),
5740 compressed_def.map(|cd| cd.compression),
5741 value_encoding,
5742 rep_index_depth,
5743 serialized.num_buffers,
5744 None,
5745 &repdef.def_meaning,
5746 num_items,
5747 has_large_chunk,
5748 );
5749
5750 if let Some(rep_index) = rep_index {
5751 let view = rep_index.borrow_to_typed_slice::<u64>();
5752 let total = view.chunks_exact(2).map(|c| c[0]).sum::<u64>();
5753 debug_assert_eq!(total, num_rows);
5754
5755 data.push(rep_index);
5756 }
5757
5758 Ok(EncodedPage {
5759 num_rows,
5760 column_idx,
5761 data,
5762 description: PageEncoding::Structural(description),
5763 row_number,
5764 })
5765 }
5766 }
5767
5768 fn serialize_full_zip_fixed(
5770 fixed: FixedWidthDataBlock,
5771 mut repdef: ControlWordIterator,
5772 num_values: u64,
5773 ) -> Result<SerializedFullZip> {
5774 if !fixed.bits_per_value.is_multiple_of(8) {
5775 return Err(Error::invalid_input_source(
5776 format!(
5777 "Full-zip fixed-width values must be byte aligned, got {} bits per value",
5778 fixed.bits_per_value
5779 )
5780 .into(),
5781 ));
5782 }
5783
5784 let len = fixed.data.len() + repdef.bytes_per_word() * num_values as usize;
5785 let mut zipped_data = Vec::with_capacity(len);
5786
5787 let max_rep_index_val = if repdef.has_repetition() {
5788 len as u64
5789 } else {
5790 0
5792 };
5793 let mut rep_index_builder =
5794 BytepackedIntegerEncoder::with_capacity(num_values as usize + 1, max_rep_index_val);
5795
5796 let bytes_per_value = fixed.bits_per_value as usize / 8;
5797 let mut offset = 0;
5798
5799 if bytes_per_value == 0 {
5800 while let Some(control) = repdef.append_next(&mut zipped_data) {
5802 if control.is_new_row {
5803 debug_assert!(offset <= len);
5805 rep_index_builder.append_trusted(offset as u64);
5806 }
5807 offset = zipped_data.len();
5808 }
5809 } else {
5810 let mut data_iter = fixed.data.chunks_exact(bytes_per_value);
5812 while let Some(control) = repdef.append_next(&mut zipped_data) {
5813 if control.is_new_row {
5814 debug_assert!(offset <= len);
5816 rep_index_builder.append_trusted(offset as u64);
5817 }
5818 if control.is_visible {
5819 let value = data_iter.next().unwrap();
5820 zipped_data.extend_from_slice(value);
5821 }
5822 offset = zipped_data.len();
5823 }
5824 }
5825
5826 debug_assert_eq!(zipped_data.len(), len);
5827 rep_index_builder.append_trusted(zipped_data.len() as u64);
5829
5830 let zipped_data = LanceBuffer::from(zipped_data);
5831 let rep_index = rep_index_builder.into_data();
5832 let rep_index = if rep_index.is_empty() {
5833 None
5834 } else {
5835 Some(LanceBuffer::from(rep_index))
5836 };
5837 Ok(SerializedFullZip {
5838 values: zipped_data,
5839 repetition_index: rep_index,
5840 })
5841 }
5842
5843 fn serialize_full_zip_variable(
5847 variable: VariableWidthBlock,
5848 mut repdef: ControlWordIterator,
5849 num_items: u64,
5850 ) -> Result<SerializedFullZip> {
5851 let bytes_per_offset = variable.bits_per_offset as usize / 8;
5852 if !variable.bits_per_offset.is_multiple_of(8) {
5853 return Err(Error::invalid_input_source(
5854 format!(
5855 "Full-zip variable-width offsets must be byte aligned, got {} bits per offset",
5856 variable.bits_per_offset
5857 )
5858 .into(),
5859 ));
5860 }
5861 let len = variable.data.len()
5862 + repdef.bytes_per_word() * num_items as usize
5863 + bytes_per_offset * variable.num_values as usize;
5864 let mut buf = Vec::with_capacity(len);
5865
5866 let max_rep_index_val = len as u64;
5867 let mut rep_index_builder =
5868 BytepackedIntegerEncoder::with_capacity(num_items as usize + 1, max_rep_index_val);
5869
5870 match bytes_per_offset {
5872 4 => {
5873 let offs = variable.offsets.borrow_to_typed_slice::<u32>();
5874 let mut rep_offset = 0;
5875 let mut windows_iter = offs.as_ref().windows(2);
5876 while let Some(control) = repdef.append_next(&mut buf) {
5877 if control.is_new_row {
5878 debug_assert!(rep_offset <= len);
5880 rep_index_builder.append_trusted(rep_offset as u64);
5881 }
5882 if control.is_visible {
5883 let window = windows_iter.next().unwrap();
5884 if control.is_valid_item {
5885 buf.extend_from_slice(&(window[1] - window[0]).to_le_bytes());
5886 buf.extend_from_slice(
5887 &variable.data[window[0] as usize..window[1] as usize],
5888 );
5889 }
5890 }
5891 rep_offset = buf.len();
5892 }
5893 }
5894 8 => {
5895 let offs = variable.offsets.borrow_to_typed_slice::<u64>();
5896 let mut rep_offset = 0;
5897 let mut windows_iter = offs.as_ref().windows(2);
5898 while let Some(control) = repdef.append_next(&mut buf) {
5899 if control.is_new_row {
5900 debug_assert!(rep_offset <= len);
5902 rep_index_builder.append_trusted(rep_offset as u64);
5903 }
5904 if control.is_visible {
5905 let window = windows_iter.next().unwrap();
5906 if control.is_valid_item {
5907 buf.extend_from_slice(&(window[1] - window[0]).to_le_bytes());
5908 buf.extend_from_slice(
5909 &variable.data[window[0] as usize..window[1] as usize],
5910 );
5911 }
5912 }
5913 rep_offset = buf.len();
5914 }
5915 }
5916 _ => {
5917 return Err(Error::invalid_input_source(
5918 format!(
5919 "Full-zip variable-width offsets must be 32 or 64 bits, got {} bits",
5920 variable.bits_per_offset
5921 )
5922 .into(),
5923 ));
5924 }
5925 }
5926
5927 debug_assert!(buf.len() <= len);
5930 rep_index_builder.append_trusted(buf.len() as u64);
5932
5933 let zipped_data = LanceBuffer::from(buf);
5934 let rep_index = rep_index_builder.into_data();
5935 debug_assert!(!rep_index.is_empty());
5936 let rep_index = Some(LanceBuffer::from(rep_index));
5937 Ok(SerializedFullZip {
5938 values: zipped_data,
5939 repetition_index: rep_index,
5940 })
5941 }
5942
5943 fn serialize_full_zip(
5946 compressed_data: PerValueDataBlock,
5947 repdef: ControlWordIterator,
5948 num_items: u64,
5949 ) -> Result<SerializedFullZip> {
5950 match compressed_data {
5951 PerValueDataBlock::Fixed(fixed) => {
5952 Self::serialize_full_zip_fixed(fixed, repdef, num_items)
5953 }
5954 PerValueDataBlock::Variable(var) => {
5955 Self::serialize_full_zip_variable(var, repdef, num_items)
5956 }
5957 }
5958 }
5959
5960 fn expand_boolean_to_bytes(fixed: FixedWidthDataBlock) -> FixedWidthDataBlock {
5961 debug_assert_eq!(fixed.bits_per_value, 1);
5962 let num_values = fixed.num_values as usize;
5963 let bool_buf = BooleanBuffer::new(fixed.data.into_buffer(), 0, num_values);
5964 let expanded: Vec<u8> = (0..num_values).map(|i| bool_buf.value(i) as u8).collect();
5965 FixedWidthDataBlock {
5966 data: LanceBuffer::from(expanded),
5967 bits_per_value: 8,
5968 num_values: fixed.num_values,
5969 block_info: BlockInfo::new(),
5970 }
5971 }
5972
5973 fn encode_full_zip(
5974 column_idx: u32,
5975 field: &Field,
5976 compression_strategy: &dyn CompressionStrategy,
5977 data: DataBlock,
5978 repdef: crate::repdef::SerializedRepDefs,
5979 row_number: u64,
5980 num_lists: u64,
5981 ) -> Result<EncodedPage> {
5982 let max_rep = repdef
5983 .repetition_levels
5984 .as_ref()
5985 .map_or(0, |r| r.iter().max().copied().unwrap_or(0));
5986 let max_def = repdef
5987 .definition_levels
5988 .as_ref()
5989 .map_or(0, |d| d.iter().max().copied().unwrap_or(0));
5990
5991 let (num_items, num_visible_items) =
5995 if let Some(rep_levels) = repdef.repetition_levels.as_ref() {
5996 (rep_levels.len() as u64, data.num_values())
5999 } else {
6000 (data.num_values(), data.num_values())
6002 };
6003
6004 let max_visible_def = repdef.max_visible_level.unwrap_or(u16::MAX);
6005
6006 let repdef_iter = build_control_word_iterator(
6007 repdef.repetition_levels.as_deref(),
6008 max_rep,
6009 repdef.definition_levels.as_deref(),
6010 max_def,
6011 max_visible_def,
6012 num_items as usize,
6013 );
6014 let bits_rep = repdef_iter.bits_rep();
6015 let bits_def = repdef_iter.bits_def();
6016
6017 let data = match data {
6019 DataBlock::FixedWidth(fixed) if fixed.bits_per_value == 1 => {
6020 DataBlock::FixedWidth(Self::expand_boolean_to_bytes(fixed))
6021 }
6022 other => other,
6023 };
6024
6025 let compressor = compression_strategy.create_per_value(field, &data)?;
6026 let (compressed_data, value_encoding) = compressor.compress(data)?;
6027
6028 let description = match &compressed_data {
6029 PerValueDataBlock::Fixed(fixed) => ProtobufUtils21::fixed_full_zip_layout(
6030 bits_rep,
6031 bits_def,
6032 fixed.bits_per_value as u32,
6033 value_encoding,
6034 &repdef.def_meaning,
6035 num_items as u32,
6036 num_visible_items as u32,
6037 ),
6038 PerValueDataBlock::Variable(variable) => ProtobufUtils21::variable_full_zip_layout(
6039 bits_rep,
6040 bits_def,
6041 variable.bits_per_offset as u32,
6042 value_encoding,
6043 &repdef.def_meaning,
6044 num_items as u32,
6045 num_visible_items as u32,
6046 ),
6047 };
6048
6049 let zipped = Self::serialize_full_zip(compressed_data, repdef_iter, num_items)?;
6050
6051 let data = if let Some(repindex) = zipped.repetition_index {
6052 vec![zipped.values, repindex]
6053 } else {
6054 vec![zipped.values]
6055 };
6056
6057 Ok(EncodedPage {
6058 num_rows: num_lists,
6059 column_idx,
6060 data,
6061 description: PageEncoding::Structural(description),
6062 row_number,
6063 })
6064 }
6065
6066 fn should_dictionary_encode(
6067 data_block: &DataBlock,
6068 field: &Field,
6069 fixed_width_dictionary_encoding: FixedWidthDictionaryEncoding,
6070 ) -> Option<DictEncodingBudget> {
6071 const DEFAULT_SAMPLE_SIZE: usize = 4096;
6072 const DEFAULT_SAMPLE_UNIQUE_RATIO: f64 = 0.98;
6073
6074 match data_block {
6077 DataBlock::FixedWidth(fixed) => {
6078 if fixed.bits_per_value == 64
6079 && fixed_width_dictionary_encoding == FixedWidthDictionaryEncoding::Exclude64Bit
6080 {
6081 return None;
6082 }
6083 if fixed.bits_per_value != 64 && fixed.bits_per_value != 128 {
6084 return None;
6085 }
6086 if fixed.bits_per_value % 8 != 0 {
6087 return None;
6088 }
6089 }
6090 DataBlock::VariableWidth(var) => {
6091 if var.bits_per_offset != 32 && var.bits_per_offset != 64 {
6092 return None;
6093 }
6094 }
6095 _ => return None,
6096 }
6097
6098 let too_small = env::var("LANCE_ENCODING_DICT_TOO_SMALL")
6100 .ok()
6101 .and_then(|val| val.parse().ok())
6102 .unwrap_or(100);
6103 if data_block.num_values() < too_small {
6104 return None;
6105 }
6106
6107 let num_values = data_block.num_values();
6108
6109 let divisor: u64 = field
6112 .metadata
6113 .get(DICT_DIVISOR_META_KEY)
6114 .and_then(|val| val.parse().ok())
6115 .or_else(|| {
6116 env::var("LANCE_ENCODING_DICT_DIVISOR")
6117 .ok()
6118 .and_then(|val| val.parse().ok())
6119 })
6120 .unwrap_or(DEFAULT_DICT_DIVISOR);
6121
6122 let max_cardinality: u64 = env::var("LANCE_ENCODING_DICT_MAX_CARDINALITY")
6123 .ok()
6124 .and_then(|val| val.parse().ok())
6125 .unwrap_or(DEFAULT_DICT_MAX_CARDINALITY);
6126
6127 let threshold_cardinality = num_values
6128 .checked_div(divisor.max(1))
6129 .unwrap_or(0)
6130 .min(max_cardinality);
6131 if threshold_cardinality == 0 {
6132 return None;
6133 }
6134
6135 let threshold_ratio = field
6137 .metadata
6138 .get(DICT_SIZE_RATIO_META_KEY)
6139 .and_then(|val| val.parse::<f64>().ok())
6140 .or_else(|| {
6141 env::var("LANCE_ENCODING_DICT_SIZE_RATIO")
6142 .ok()
6143 .and_then(|val| val.parse().ok())
6144 })
6145 .unwrap_or(DEFAULT_DICT_SIZE_RATIO);
6146
6147 if threshold_ratio <= 0.0 || threshold_ratio > 1.0 {
6148 panic!(
6149 "Invalid parameter: dict-size-ratio is {} which is not in the range (0, 1].",
6150 threshold_ratio
6151 );
6152 }
6153
6154 let data_size = data_block.data_size();
6155 if data_size == 0 {
6156 return None;
6157 }
6158
6159 let max_encoded_size = (data_size as f64 * threshold_ratio) as u64;
6160 let max_encoded_size = usize::try_from(max_encoded_size).ok()?;
6161
6162 if let Some(sample_unique_ratio) =
6165 Self::sample_unique_ratio(data_block, DEFAULT_SAMPLE_SIZE)?
6166 {
6167 if sample_unique_ratio >= DEFAULT_SAMPLE_UNIQUE_RATIO {
6168 return None;
6169 }
6170
6171 let projected_cardinality = (sample_unique_ratio * num_values as f64).ceil() as u64;
6172 if projected_cardinality > threshold_cardinality {
6173 return None;
6174 }
6175 }
6176
6177 let max_dict_entries = u32::try_from(threshold_cardinality.min(i32::MAX as u64)).ok()?;
6178 Some(DictEncodingBudget {
6179 max_dict_entries,
6180 max_encoded_size,
6181 })
6182 }
6183
6184 fn sample_unique_ratio(data_block: &DataBlock, max_samples: usize) -> Option<Option<f64>> {
6192 use std::collections::HashSet;
6193
6194 const NUM_SAMPLE_BLOCKS: usize = 32;
6195 const MIN_RELIABLE_SAMPLES: usize = 1024;
6196
6197 let num_values = usize::try_from(data_block.num_values()).ok()?;
6198 if num_values == 0 {
6199 return Some(None);
6200 }
6201
6202 let sample_count = num_values.min(max_samples).max(1);
6203 if sample_count < MIN_RELIABLE_SAMPLES {
6204 return Some(None);
6205 }
6206
6207 let block_count = NUM_SAMPLE_BLOCKS.min(sample_count).min(num_values).max(1);
6208 let samples_per_block = (sample_count / block_count).max(1);
6209 let mut indices = Vec::with_capacity(sample_count);
6210 for block_idx in 0..block_count {
6211 let block_start = block_idx * num_values / block_count;
6212 let next_block_start = ((block_idx + 1) * num_values / block_count).min(num_values);
6213 let block_len = next_block_start.saturating_sub(block_start);
6214 let samples_in_block = samples_per_block.min(block_len);
6215 indices.extend((0..samples_in_block).map(|offset| block_start + offset));
6216 }
6217
6218 if indices.len() < MIN_RELIABLE_SAMPLES {
6219 return Some(None);
6220 }
6221
6222 let ratio = match data_block {
6223 DataBlock::FixedWidth(fixed) => match fixed.bits_per_value {
6224 64 => {
6225 let values = fixed.data.borrow_to_typed_slice::<u64>();
6226 let values = values.as_ref();
6227 let mut unique: HashSet<u64> =
6228 HashSet::with_capacity(indices.len().min(MIN_RELIABLE_SAMPLES));
6229 for idx in indices.iter().copied() {
6230 unique.insert(values.get(idx).copied()?);
6231 }
6232 unique.len() as f64 / indices.len() as f64
6233 }
6234 128 => {
6235 let values = fixed.data.borrow_to_typed_slice::<u128>();
6236 let values = values.as_ref();
6237 let mut unique: HashSet<u128> =
6238 HashSet::with_capacity(indices.len().min(MIN_RELIABLE_SAMPLES));
6239 for idx in indices.iter().copied() {
6240 unique.insert(values.get(idx).copied()?);
6241 }
6242 unique.len() as f64 / indices.len() as f64
6243 }
6244 _ => return Some(None),
6245 },
6246 DataBlock::VariableWidth(var) => {
6247 use xxhash_rust::xxh3::xxh3_64;
6248
6249 let mut unique: HashSet<u64> =
6251 HashSet::with_capacity(indices.len().min(MIN_RELIABLE_SAMPLES));
6252 match var.bits_per_offset {
6253 32 => {
6254 let offsets_ref = var.offsets.borrow_to_typed_slice::<u32>();
6255 let offsets: &[u32] = offsets_ref.as_ref();
6256 for i in indices.iter().copied() {
6257 let start = usize::try_from(*offsets.get(i)?).ok()?;
6258 let end = usize::try_from(*offsets.get(i + 1)?).ok()?;
6259 if start > end || end > var.data.len() {
6260 return None;
6261 }
6262 unique.insert(xxh3_64(&var.data[start..end]));
6263 }
6264 }
6265 64 => {
6266 let offsets_ref = var.offsets.borrow_to_typed_slice::<u64>();
6267 let offsets: &[u64] = offsets_ref.as_ref();
6268 for i in indices.iter().copied() {
6269 let start = usize::try_from(*offsets.get(i)?).ok()?;
6270 let end = usize::try_from(*offsets.get(i + 1)?).ok()?;
6271 if start > end || end > var.data.len() {
6272 return None;
6273 }
6274 unique.insert(xxh3_64(&var.data[start..end]));
6275 }
6276 }
6277 _ => return Some(None),
6278 }
6279 unique.len() as f64 / indices.len() as f64
6280 }
6281 _ => return Some(None),
6282 };
6283
6284 Some(Some(ratio))
6285 }
6286
6287 fn slice_repdef(repdef: &SerializedRepDefs, range: Range<usize>) -> SerializedRepDefs {
6288 let repetition_levels = repdef
6289 .repetition_levels
6290 .as_ref()
6291 .map(|levels| levels[range.clone()].to_vec());
6292 let definition_levels = repdef
6293 .definition_levels
6294 .as_ref()
6295 .map(|levels| levels[range].to_vec());
6296 SerializedRepDefs::new_with_fixed_size_list_levels(
6297 repetition_levels,
6298 definition_levels,
6299 repdef.def_meaning.clone(),
6300 repdef.has_fixed_size_list_levels(),
6301 )
6302 }
6303
6304 fn slice_arrays(
6305 arrays: &[ArrayRef],
6306 value_start: u64,
6307 num_values: u64,
6308 ) -> Result<Vec<ArrayRef>> {
6309 if num_values == 0 {
6310 return Ok(Vec::new());
6311 }
6312
6313 let mut values_to_skip = usize::try_from(value_start).map_err(|_| {
6314 Error::invalid_input(format!("Value start {} is too large", value_start))
6315 })?;
6316 let mut values_remaining = usize::try_from(num_values).map_err(|_| {
6317 Error::invalid_input(format!("Value count {} is too large", num_values))
6318 })?;
6319 let mut sliced = Vec::new();
6320
6321 for array in arrays {
6322 if values_to_skip >= array.len() {
6323 values_to_skip -= array.len();
6324 continue;
6325 }
6326
6327 let offset = values_to_skip;
6328 let len = (array.len() - offset).min(values_remaining);
6329 sliced.push(array.slice(offset, len));
6330 values_remaining -= len;
6331 values_to_skip = 0;
6332
6333 if values_remaining == 0 {
6334 break;
6335 }
6336 }
6337
6338 if values_remaining != 0 {
6339 return Err(Error::internal(format!(
6340 "Page split requested {} values starting at {}, but the page did not contain enough values",
6341 num_values, value_start
6342 )));
6343 }
6344
6345 Ok(sliced)
6346 }
6347
6348 fn split_pages_for_miniblock_repdef_budget(
6349 arrays: Vec<ArrayRef>,
6350 repdef: SerializedRepDefs,
6351 budget: MiniBlockRepDefBudget,
6352 row_number: u64,
6353 num_rows: u64,
6354 ) -> Result<Vec<PrimitivePageData>> {
6355 if budget == MiniBlockRepDefBudget::WithinBudget {
6356 return Ok(vec![PrimitivePageData {
6357 arrays,
6358 structure: PrimitivePageStructure::Dense {
6359 repdef,
6360 single_row_miniblock_repdef_levels: None,
6361 },
6362 row_number,
6363 num_rows,
6364 }]);
6365 }
6366 if let MiniBlockRepDefBudget::SingleRowOverBudget(num_levels) = budget {
6367 return Ok(vec![PrimitivePageData {
6368 arrays,
6369 structure: PrimitivePageStructure::Dense {
6370 repdef,
6371 single_row_miniblock_repdef_levels: Some(num_levels),
6372 },
6373 row_number,
6374 num_rows,
6375 }]);
6376 }
6377
6378 let MiniBlockRepDefBudget::RequiresPageSplit(splits) = budget else {
6379 unreachable!();
6380 };
6381
6382 let mut pages = Vec::with_capacity(splits.len());
6383 for split in splits {
6384 let arrays = Self::slice_arrays(&arrays, split.value_start, split.num_values)?;
6385 let repdef = Self::slice_repdef(&repdef, split.level_range);
6386 pages.push(PrimitivePageData {
6387 arrays,
6388 structure: PrimitivePageStructure::Dense {
6389 repdef,
6390 single_row_miniblock_repdef_levels: None,
6391 },
6392 row_number: row_number + split.row_start,
6393 num_rows: split.num_rows,
6394 });
6395 }
6396 Ok(pages)
6397 }
6398
6399 fn encode_dense_page(
6400 ctx: PrimitiveEncodeContext,
6401 page: PrimitivePageData,
6402 compression_strategy: Arc<dyn CompressionStrategy>,
6403 miniblock_chunk_size: MiniblockChunkSize,
6404 complex_null_encoding: ComplexNullEncoding,
6405 fixed_width_dictionary_encoding: FixedWidthDictionaryEncoding,
6406 ) -> Result<EncodedPage> {
6407 let PrimitiveEncodeContext {
6408 column_idx,
6409 field,
6410 encoding_metadata,
6411 is_simple_validity,
6412 has_repdef_info,
6413 } = ctx;
6414 let PrimitivePageData {
6415 arrays,
6416 structure,
6417 row_number,
6418 num_rows,
6419 } = page;
6420 let num_values = arrays.iter().map(|arr| arr.len() as u64).sum();
6421
6422 let (repdef, single_row_miniblock_repdef_levels) = match structure {
6423 PrimitivePageStructure::Dense {
6424 repdef,
6425 single_row_miniblock_repdef_levels,
6426 } => (repdef, single_row_miniblock_repdef_levels),
6427 PrimitivePageStructure::Sparse { .. } => {
6428 unreachable!("dense atom received sparse page")
6429 }
6430 };
6431
6432 if num_values == 0 {
6433 log::debug!(
6436 "Encoding column {} with {} items ({} rows) using complex-null layout",
6437 column_idx,
6438 num_values,
6439 num_rows
6440 );
6441 return Self::encode_complex_all_null(
6442 column_idx,
6443 repdef,
6444 row_number,
6445 num_rows,
6446 complex_null_encoding,
6447 compression_strategy.as_ref(),
6448 );
6449 }
6450
6451 let leaf_validity = Self::leaf_validity(&repdef, num_values as usize)?;
6452 let all_null = leaf_validity
6453 .as_ref()
6454 .map(|validity| validity.count_set_bits() == 0)
6455 .unwrap_or(false);
6456
6457 if all_null {
6458 return if is_simple_validity {
6459 log::debug!(
6460 "Encoding column {} with {} items ({} rows) using simple-null layout",
6461 column_idx,
6462 num_values,
6463 num_rows
6464 );
6465 Self::encode_simple_all_null(column_idx, num_values, row_number)
6466 } else {
6467 log::debug!(
6468 "Encoding column {} with {} items ({} rows) using complex-null layout",
6469 column_idx,
6470 num_values,
6471 num_rows
6472 );
6473 Self::encode_complex_all_null(
6474 column_idx,
6475 repdef,
6476 row_number,
6477 num_rows,
6478 complex_null_encoding,
6479 compression_strategy.as_ref(),
6480 )
6481 };
6482 }
6483
6484 if let DataType::Struct(fields) = &field.data_type()
6485 && fields.is_empty()
6486 {
6487 if has_repdef_info {
6488 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()));
6489 }
6490 return Self::encode_simple_all_null(column_idx, num_values, row_number);
6493 }
6494
6495 let data_block = DataBlock::from_arrays(&arrays, num_values);
6496
6497 if let Some(num_levels) = single_row_miniblock_repdef_levels {
6498 let requested_encoding = encoding_metadata
6499 .get(STRUCTURAL_ENCODING_META_KEY)
6500 .map(|requested| requested.to_lowercase());
6501 let fullzip_error = match &data_block {
6502 DataBlock::FixedWidth(fixed)
6504 if fixed.bits_per_value != 1 && !fixed.bits_per_value.is_multiple_of(8) =>
6505 {
6506 Some(format!(
6507 "Full-zip fixed-width values must be byte aligned, got {} bits per value",
6508 fixed.bits_per_value
6509 ))
6510 }
6511 DataBlock::VariableWidth(variable)
6512 if !variable.bits_per_offset.is_multiple_of(8) =>
6513 {
6514 Some(format!(
6515 "Full-zip variable-width offsets must be byte aligned, got {} bits per offset",
6516 variable.bits_per_offset
6517 ))
6518 }
6519 DataBlock::VariableWidth(variable)
6520 if variable.bits_per_offset != 32 && variable.bits_per_offset != 64 =>
6521 {
6522 Some(format!(
6523 "Full-zip variable-width offsets must be 32 or 64 bits, got {} bits",
6524 variable.bits_per_offset
6525 ))
6526 }
6527 DataBlock::Dictionary(_) => {
6528 Some("Full-zip does not encode dictionary data blocks directly".to_string())
6529 }
6530 DataBlock::FixedSizeList(fsl) => match fsl.clone().try_into_flat() {
6531 Some(flat) if flat.bits_per_value.is_multiple_of(8) => None,
6532 Some(flat) => Some(format!(
6533 "Full-zip fixed-size-list values must be byte aligned after flattening, got {} bits per value",
6534 flat.bits_per_value
6535 )),
6536 None => Some(
6537 "Full-zip fixed-size-list capability requires a flat fixed-width child"
6538 .to_string(),
6539 ),
6540 },
6541 DataBlock::FixedWidth(_) | DataBlock::VariableWidth(_) | DataBlock::Struct(_) => {
6542 None
6543 }
6544 other => Some(format!(
6545 "Full-zip does not support value block type {}",
6546 other.name()
6547 )),
6548 };
6549 match requested_encoding.as_deref() {
6550 Some(STRUCTURAL_ENCODING_FULLZIP) => {
6551 if let Some(reason) = fullzip_error {
6552 return Err(Error::invalid_input_source(reason.into()));
6553 }
6554 return Self::encode_full_zip(
6555 column_idx,
6556 &field,
6557 compression_strategy.as_ref(),
6558 data_block,
6559 repdef,
6560 row_number,
6561 num_rows,
6562 );
6563 }
6564 Some(STRUCTURAL_ENCODING_MINIBLOCK) | None => {
6565 if requested_encoding.is_none() && fullzip_error.is_none() {
6566 log::debug!(
6567 "Encoding column {} with {} items using full-zip layout because mini-block cannot split the structural page",
6568 column_idx,
6569 num_values
6570 );
6571 return Self::encode_full_zip(
6572 column_idx,
6573 &field,
6574 compression_strategy.as_ref(),
6575 data_block,
6576 repdef,
6577 row_number,
6578 num_rows,
6579 );
6580 }
6581 return Err(Error::invalid_input_source(
6582 format!(
6583 "Mini-block cannot encode {} rep/def levels in one top-level row. \
6584 This usually means the row contains too much nested structure \
6585 for the current layout.",
6586 num_levels
6587 )
6588 .into(),
6589 ));
6590 }
6591 _ => {}
6592 }
6593 }
6594
6595 let requires_full_zip_packed_struct =
6596 if let DataBlock::Struct(ref struct_data_block) = data_block {
6597 struct_data_block.has_variable_width_child()
6598 } else {
6599 false
6600 };
6601
6602 if requires_full_zip_packed_struct {
6603 log::debug!(
6604 "Encoding column {} with {} items using full-zip packed struct layout",
6605 column_idx,
6606 num_values
6607 );
6608 return Self::encode_full_zip(
6609 column_idx,
6610 &field,
6611 compression_strategy.as_ref(),
6612 data_block,
6613 repdef,
6614 row_number,
6615 num_rows,
6616 );
6617 }
6618
6619 if let DataBlock::Dictionary(dict) = data_block {
6620 log::debug!(
6621 "Encoding column {} with {} items using dictionary encoding (already dictionary encoded)",
6622 column_idx,
6623 num_values
6624 );
6625 let (mut indices_data_block, dictionary_data_block) = dict.into_parts();
6626 indices_data_block.compute_stat();
6631 return Self::encode_miniblock(
6632 column_idx,
6633 &field,
6634 compression_strategy.as_ref(),
6635 indices_data_block,
6636 repdef,
6637 row_number,
6638 Some(dictionary_data_block),
6639 num_rows,
6640 miniblock_chunk_size,
6641 );
6642 }
6643
6644 let dict_result = Self::should_dictionary_encode(
6647 &data_block,
6648 &field,
6649 fixed_width_dictionary_encoding,
6650 )
6651 .and_then(|budget| {
6652 log::debug!(
6653 "Encoding column {} with {} items using dictionary encoding (mini-block layout)",
6654 column_idx,
6655 num_values
6656 );
6657 dict::dictionary_encode(
6658 &data_block,
6659 budget.max_dict_entries,
6660 budget.max_encoded_size,
6661 )
6662 });
6663
6664 if let Some((indices_data_block, dictionary_data_block)) = dict_result {
6665 Self::encode_miniblock(
6666 column_idx,
6667 &field,
6668 compression_strategy.as_ref(),
6669 indices_data_block,
6670 repdef,
6671 row_number,
6672 Some(dictionary_data_block),
6673 num_rows,
6674 miniblock_chunk_size,
6675 )
6676 } else if Self::prefers_miniblock(&data_block, encoding_metadata.as_ref()) {
6677 log::debug!(
6678 "Encoding column {} with {} items using mini-block layout",
6679 column_idx,
6680 num_values
6681 );
6682 Self::encode_miniblock(
6683 column_idx,
6684 &field,
6685 compression_strategy.as_ref(),
6686 data_block,
6687 repdef,
6688 row_number,
6689 None,
6690 num_rows,
6691 miniblock_chunk_size,
6692 )
6693 } else if Self::prefers_fullzip(encoding_metadata.as_ref()) {
6694 log::debug!(
6695 "Encoding column {} with {} items using full-zip layout",
6696 column_idx,
6697 num_values
6698 );
6699 Self::encode_full_zip(
6700 column_idx,
6701 &field,
6702 compression_strategy.as_ref(),
6703 data_block,
6704 repdef,
6705 row_number,
6706 num_rows,
6707 )
6708 } else {
6709 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()))
6710 }
6711 }
6712
6713 fn rebuild_empty_dictionary_chunks(arrays: Vec<ArrayRef>) -> Result<Vec<ArrayRef>> {
6720 if !arrays.iter().any(|array| {
6721 array
6722 .as_any_dictionary_opt()
6723 .is_some_and(|dictionary| dictionary.values().is_empty())
6724 }) {
6725 return Ok(arrays);
6726 }
6727
6728 let zeroed = |data_type: &DataType, len: usize| {
6729 new_null_array(data_type, len)
6730 .to_data()
6731 .into_builder()
6732 .nulls(None)
6733 .build()
6734 .map(make_array)
6735 };
6736 let rebuild =
6737 |keys: Vec<ArrayRef>, data_type: &DataType, values: ArrayRef| -> Result<ArrayRef> {
6738 let keys = keys.iter().map(|keys| keys.as_ref()).collect::<Vec<_>>();
6739 let keys = arrow_select::concat::concat(&keys)?;
6740 let data = keys
6741 .to_data()
6742 .into_builder()
6743 .data_type(data_type.clone())
6744 .child_data(vec![values.to_data()])
6745 .build()?;
6746 Ok(make_array(data))
6747 };
6748
6749 let mut rebuilt = Vec::with_capacity(arrays.len());
6750 let mut pending_keys = Vec::with_capacity(arrays.len());
6751 let mut empty_data_type = None;
6752 for array in arrays {
6753 let Some(dictionary) = array.as_any_dictionary_opt() else {
6754 return Err(Error::invalid_input_source(
6755 "Cannot mix dictionary and non-dictionary chunks".into(),
6756 ));
6757 };
6758 if dictionary.values().is_empty() {
6759 pending_keys.push(zeroed(dictionary.keys().data_type(), array.len())?);
6760 empty_data_type.get_or_insert_with(|| array.data_type().clone());
6761 } else if pending_keys.is_empty() {
6762 rebuilt.push(array);
6763 } else {
6764 pending_keys.push(make_array(dictionary.keys().to_data()));
6765 rebuilt.push(rebuild(
6766 std::mem::take(&mut pending_keys),
6767 array.data_type(),
6768 dictionary.values().clone(),
6769 )?);
6770 }
6771 }
6772
6773 if pending_keys.is_empty() {
6774 return Ok(rebuilt);
6775 }
6776 if let Some(array) = rebuilt.pop() {
6777 let Some(dictionary) = array.as_any_dictionary_opt() else {
6778 return Err(Error::invalid_input_source(
6779 "Cannot mix dictionary and non-dictionary chunks".into(),
6780 ));
6781 };
6782 let mut keys = Vec::with_capacity(pending_keys.len() + 1);
6783 keys.push(make_array(dictionary.keys().to_data()));
6784 keys.append(&mut pending_keys);
6785 rebuilt.push(rebuild(
6786 keys,
6787 array.data_type(),
6788 dictionary.values().clone(),
6789 )?);
6790 } else {
6791 let data_type = empty_data_type.ok_or_else(|| {
6792 Error::internal("Missing data type for an empty dictionary chunk")
6793 })?;
6794 let DataType::Dictionary(_, value_type) = &data_type else {
6795 return Err(Error::internal(format!(
6796 "Expected dictionary data type, got {data_type}"
6797 )));
6798 };
6799 rebuilt.push(rebuild(pending_keys, &data_type, zeroed(value_type, 1)?)?);
6800 }
6801 Ok(rebuilt)
6802 }
6803
6804 fn do_flush(
6806 &mut self,
6807 arrays: Vec<ArrayRef>,
6808 repdefs: Vec<RepDefBuilder>,
6809 row_number: u64,
6810 num_rows: u64,
6811 ) -> Result<Vec<EncodeTask>> {
6812 let arrays = Self::rebuild_empty_dictionary_chunks(arrays)?;
6813 DataBlock::validate_arrays(&arrays, &self.field.name)?;
6814 let num_values = arrays.iter().map(|arr| arr.len() as u64).sum();
6815 let is_simple_validity = repdefs.iter().all(|rd| rd.is_simple_validity());
6816 let has_repdef_info = repdefs.iter().any(|rd| !rd.is_empty());
6817 let normalized = RepDefBuilder::normalize(repdefs);
6818 let plan_ctx = PrimitivePlanContext {
6819 column_idx: self.column_index,
6820 field: &self.field,
6821 encoding_metadata: &self.encoding_metadata,
6822 };
6823 let mut pages = None;
6824 for page_encoding in self.page_encodings.iter() {
6825 if let Some(planned) = page_encoding.behavior.try_plan_pages(
6826 &plan_ctx,
6827 &arrays,
6828 &normalized,
6829 row_number,
6830 num_rows,
6831 num_values,
6832 )? {
6833 pages = Some(planned);
6834 break;
6835 }
6836 }
6837 let pages = pages.ok_or_else(|| {
6838 Error::invalid_input_source(
6839 format!(
6840 "No primitive page planner supports field '{}'",
6841 self.field.name
6842 )
6843 .into(),
6844 )
6845 })?;
6846
6847 let mut tasks = Vec::with_capacity(pages.len());
6848 let ctx = PrimitiveEncodeContext {
6849 column_idx: self.column_index,
6850 field: self.field.clone(),
6851 encoding_metadata: self.encoding_metadata.clone(),
6852 is_simple_validity,
6853 has_repdef_info,
6854 };
6855 for page in pages {
6856 let ctx = ctx.clone();
6857 let page_encodings = self.page_encodings.clone();
6858 let task =
6859 spawn_cpu(move || Self::encode_page(page_encodings.as_ref(), &ctx, page)).boxed();
6860 tasks.push(task);
6861 }
6862 Ok(tasks)
6863 }
6864
6865 fn extract_validity_buf(
6866 array: Arc<dyn Array>,
6867 repdef: &mut RepDefBuilder,
6868 keep_original_array: bool,
6869 ) -> Result<Arc<dyn Array>> {
6870 if let Some(validity) = array.nulls() {
6871 if keep_original_array {
6872 repdef.add_validity_bitmap(validity.clone());
6873 } else {
6874 repdef.add_validity_bitmap(deep_copy_nulls(Some(validity)).unwrap());
6875 }
6876 if let Some(dictionary) = array.as_any_dictionary_opt()
6880 && dictionary.values().is_empty()
6881 {
6882 return Ok(array);
6883 }
6884 let data_no_nulls = array.to_data().into_builder().nulls(None).build()?;
6885 Ok(make_array(data_no_nulls))
6886 } else {
6887 repdef.add_no_null(array.len());
6888 Ok(array)
6889 }
6890 }
6891
6892 fn extract_validity(
6893 mut array: Arc<dyn Array>,
6894 repdef: &mut RepDefBuilder,
6895 keep_original_array: bool,
6896 ) -> Result<Arc<dyn Array>> {
6897 match array.data_type() {
6898 DataType::Null => {
6899 repdef.add_validity_bitmap(NullBuffer::new(BooleanBuffer::new_unset(array.len())));
6900 Ok(array)
6901 }
6902 DataType::Dictionary(_, _) => {
6903 array = dict::normalize_dict_nulls(array)?;
6904 array = dict::clear_out_of_range_null_keys(array)?;
6905 Self::extract_validity_buf(array, repdef, keep_original_array)
6906 }
6907 _ => Self::extract_validity_buf(array, repdef, keep_original_array),
6916 }
6917 }
6918}
6919
6920impl PrimitivePageEncodingBehavior for RejectSparsePrimitiveEncoding {
6921 fn validate_field(&self, field: &Field, metadata: &HashMap<String, String>) -> Result<()> {
6922 if metadata
6923 .get(STRUCTURAL_ENCODING_META_KEY)
6924 .is_some_and(|requested| requested.eq_ignore_ascii_case(STRUCTURAL_ENCODING_SPARSE))
6925 {
6926 return Err(Error::invalid_input_source(
6927 format!(
6928 "Field '{}' requests sparse structural encoding, which is not enabled by the selected file format",
6929 field.name
6930 )
6931 .into(),
6932 ));
6933 }
6934 Ok(())
6935 }
6936}
6937
6938fn plan_dense_primitive_pages(
6939 arrays: &[ArrayRef],
6940 normalized: &NormalizedStructuralPlan,
6941 row_number: u64,
6942 num_rows: u64,
6943 num_values: u64,
6944) -> Result<Vec<PrimitivePageData>> {
6945 let (repdef, miniblock_repdef_budget) = normalized.serialize_with_miniblock_repdef_budget(
6946 miniblock::max_repdef_levels_per_chunk,
6947 num_rows,
6948 num_values,
6949 )?;
6950 PrimitiveStructuralEncoder::split_pages_for_miniblock_repdef_budget(
6951 arrays.to_vec(),
6952 repdef,
6953 miniblock_repdef_budget,
6954 row_number,
6955 num_rows,
6956 )
6957}
6958
6959impl PrimitivePageEncodingBehavior for DenseU16PrimitiveEncoding {
6960 fn try_plan_pages(
6961 &self,
6962 _ctx: &PrimitivePlanContext<'_>,
6963 arrays: &[ArrayRef],
6964 normalized: &NormalizedStructuralPlan,
6965 row_number: u64,
6966 num_rows: u64,
6967 num_values: u64,
6968 ) -> Result<Option<Vec<PrimitivePageData>>> {
6969 Ok(Some(plan_dense_primitive_pages(
6970 arrays, normalized, row_number, num_rows, num_values,
6971 )?))
6972 }
6973
6974 fn try_encode_page(
6975 &self,
6976 ctx: &PrimitiveEncodeContext,
6977 page: PrimitivePageData,
6978 ) -> Result<PrimitiveEncodeAttempt> {
6979 if !matches!(&page.structure, PrimitivePageStructure::Dense { .. }) {
6980 return Ok(PrimitiveEncodeAttempt::Unhandled(page));
6981 }
6982 Ok(PrimitiveEncodeAttempt::Encoded(
6983 PrimitiveStructuralEncoder::encode_dense_page(
6984 ctx.clone(),
6985 page,
6986 self.compression.clone(),
6987 MiniblockChunkSize::U16,
6988 ComplexNullEncoding::RawLevels,
6989 FixedWidthDictionaryEncoding::Exclude64Bit,
6990 )?,
6991 ))
6992 }
6993}
6994
6995impl PrimitivePageEncodingBehavior for DenseU32PrimitiveEncoding {
6996 fn try_plan_pages(
6997 &self,
6998 _ctx: &PrimitivePlanContext<'_>,
6999 arrays: &[ArrayRef],
7000 normalized: &NormalizedStructuralPlan,
7001 row_number: u64,
7002 num_rows: u64,
7003 num_values: u64,
7004 ) -> Result<Option<Vec<PrimitivePageData>>> {
7005 Ok(Some(plan_dense_primitive_pages(
7006 arrays, normalized, row_number, num_rows, num_values,
7007 )?))
7008 }
7009
7010 fn try_encode_page(
7011 &self,
7012 ctx: &PrimitiveEncodeContext,
7013 page: PrimitivePageData,
7014 ) -> Result<PrimitiveEncodeAttempt> {
7015 if !matches!(&page.structure, PrimitivePageStructure::Dense { .. }) {
7016 return Ok(PrimitiveEncodeAttempt::Unhandled(page));
7017 }
7018 Ok(PrimitiveEncodeAttempt::Encoded(
7019 PrimitiveStructuralEncoder::encode_dense_page(
7020 ctx.clone(),
7021 page,
7022 self.compression.clone(),
7023 MiniblockChunkSize::U32,
7024 ComplexNullEncoding::CompressedLevels,
7025 FixedWidthDictionaryEncoding::Include64Bit,
7026 )?,
7027 ))
7028 }
7029}
7030
7031impl PrimitivePageEncodingBehavior for SparsePrimitiveEncoding {
7032 fn try_plan_pages(
7033 &self,
7034 ctx: &PrimitivePlanContext<'_>,
7035 arrays: &[ArrayRef],
7036 normalized: &NormalizedStructuralPlan,
7037 row_number: u64,
7038 num_rows: u64,
7039 num_values: u64,
7040 ) -> Result<Option<Vec<PrimitivePageData>>> {
7041 let requested_encoding = ctx.encoding_metadata.get(STRUCTURAL_ENCODING_META_KEY);
7042 let requests_sparse = requested_encoding
7043 .is_some_and(|requested| requested.eq_ignore_ascii_case(STRUCTURAL_ENCODING_SPARSE));
7044 if requests_sparse {
7045 let plan = sparse::writer::plan(normalized, num_values)?;
7046 if sparse::writer::uses_constant_layout(&plan, ctx.field) {
7047 return Ok(None);
7048 }
7049 return Ok(Some(vec![PrimitivePageData {
7050 arrays: arrays.to_vec(),
7051 structure: PrimitivePageStructure::Sparse {
7052 plan,
7053 prepared_values: None,
7054 },
7055 row_number,
7056 num_rows,
7057 }]));
7058 }
7059
7060 let (_, miniblock_repdef_budget) = normalized.serialize_with_miniblock_repdef_budget(
7061 miniblock::max_repdef_levels_per_chunk,
7062 num_rows,
7063 num_values,
7064 )?;
7065 let automatic_sparse = layout::select_automatic_sparse(
7066 requested_encoding.map(String::as_str),
7067 &miniblock_repdef_budget,
7068 || {
7069 let data = DataBlock::from_arrays(arrays, num_values);
7070 if !sparse::writer::supports_value_block(&data) {
7071 return Ok(None);
7072 }
7073 let prepared_values = match sparse::writer::prepare_values(
7074 ctx.field,
7075 self.compression.as_ref(),
7076 data,
7077 MiniblockChunkSize::U32,
7078 ) {
7079 Ok(prepared_values) => prepared_values,
7080 Err(error) => {
7081 debug!(
7082 "Keeping column {} on its dense structural path because sparse value preparation is unavailable: {}",
7083 ctx.column_idx, error
7084 );
7085 return Ok(None);
7086 }
7087 };
7088 let plan = sparse::writer::plan(normalized, num_values)?;
7089 if sparse::writer::uses_constant_layout(&plan, ctx.field) {
7090 return Ok(None);
7091 }
7092 Ok(Some((plan, prepared_values)))
7093 },
7094 )?;
7095 Ok(automatic_sparse.map(|(plan, prepared_values)| {
7096 vec![PrimitivePageData {
7097 arrays: arrays.to_vec(),
7098 structure: PrimitivePageStructure::Sparse {
7099 plan,
7100 prepared_values: Some(prepared_values),
7101 },
7102 row_number,
7103 num_rows,
7104 }]
7105 }))
7106 }
7107
7108 fn try_encode_page(
7109 &self,
7110 ctx: &PrimitiveEncodeContext,
7111 page: PrimitivePageData,
7112 ) -> Result<PrimitiveEncodeAttempt> {
7113 if !matches!(&page.structure, PrimitivePageStructure::Sparse { .. }) {
7114 return Ok(PrimitiveEncodeAttempt::Unhandled(page));
7115 }
7116 let PrimitivePageData {
7117 arrays,
7118 structure:
7119 PrimitivePageStructure::Sparse {
7120 plan,
7121 prepared_values,
7122 },
7123 row_number,
7124 num_rows,
7125 } = page
7126 else {
7127 unreachable!()
7128 };
7129 let num_values = arrays.iter().map(|array| array.len() as u64).sum();
7130 log::debug!(
7131 "Encoding column {} with {} visible items ({} rows) using sparse layout",
7132 ctx.column_idx,
7133 num_values,
7134 num_rows
7135 );
7136 Ok(PrimitiveEncodeAttempt::Encoded(
7137 sparse::writer::encode_page(
7138 ctx.column_idx,
7139 &ctx.field,
7140 self.compression.as_ref(),
7141 prepared_values.map_or_else(
7142 || {
7143 sparse::writer::SparseValueInput::Unprepared(DataBlock::from_arrays(
7144 &arrays, num_values,
7145 ))
7146 },
7147 sparse::writer::SparseValueInput::Prepared,
7148 ),
7149 plan,
7150 row_number,
7151 num_rows,
7152 MiniblockChunkSize::U32,
7153 )?,
7154 ))
7155 }
7156}
7157
7158impl PrimitivePageEncodingBehavior for ConstantPrimitiveEncoding {
7159 fn try_encode_page(
7160 &self,
7161 ctx: &PrimitiveEncodeContext,
7162 page: PrimitivePageData,
7163 ) -> Result<PrimitiveEncodeAttempt> {
7164 let PrimitivePageStructure::Dense { repdef, .. } = &page.structure else {
7165 return Ok(PrimitiveEncodeAttempt::Unhandled(page));
7166 };
7167 let num_values: u64 = page.arrays.iter().map(|array| array.len() as u64).sum();
7168 if num_values == 0 {
7169 return Ok(PrimitiveEncodeAttempt::Unhandled(page));
7170 }
7171 let leaf_validity = PrimitiveStructuralEncoder::leaf_validity(repdef, num_values as usize)?;
7172 if leaf_validity
7173 .as_ref()
7174 .is_some_and(|validity| validity.count_set_bits() == 0)
7175 || matches!(ctx.field.data_type(), DataType::Struct(fields) if fields.is_empty())
7176 {
7177 return Ok(PrimitiveEncodeAttempt::Unhandled(page));
7178 }
7179 let Some(scalar) =
7180 PrimitiveStructuralEncoder::find_constant_scalar(&page.arrays, leaf_validity.as_ref())?
7181 else {
7182 return Ok(PrimitiveEncodeAttempt::Unhandled(page));
7183 };
7184 let PrimitivePageData {
7185 structure: PrimitivePageStructure::Dense { repdef, .. },
7186 row_number,
7187 num_rows,
7188 ..
7189 } = page
7190 else {
7191 unreachable!()
7192 };
7193 log::debug!(
7194 "Encoding column {} with {} items ({} rows) using constant layout",
7195 ctx.column_idx,
7196 num_values,
7197 num_rows
7198 );
7199 Ok(PrimitiveEncodeAttempt::Encoded(
7200 constant::encode_constant_page(ctx.column_idx, scalar, repdef, row_number, num_rows)?,
7201 ))
7202 }
7203}
7204
7205impl FieldEncoder for PrimitiveStructuralEncoder {
7206 fn maybe_encode(
7208 &mut self,
7209 array: ArrayRef,
7210 _external_buffers: &mut OutOfLineBuffers,
7211 mut repdef: RepDefBuilder,
7212 row_number: u64,
7213 num_rows: u64,
7214 ) -> Result<Vec<EncodeTask>> {
7215 let array = Self::extract_validity(array, &mut repdef, self.keep_original_array)?;
7216 self.accumulated_repdefs.push(repdef);
7217
7218 if let Some((arrays, row_number, num_rows)) =
7219 self.accumulation_queue.insert(array, row_number, num_rows)
7220 {
7221 let accumulated_repdefs = std::mem::take(&mut self.accumulated_repdefs);
7222 Ok(self.do_flush(arrays, accumulated_repdefs, row_number, num_rows)?)
7223 } else {
7224 Ok(vec![])
7225 }
7226 }
7227
7228 fn flush(&mut self, _external_buffers: &mut OutOfLineBuffers) -> Result<Vec<EncodeTask>> {
7230 if let Some((arrays, row_number, num_rows)) = self.accumulation_queue.flush() {
7231 let accumulated_repdefs = std::mem::take(&mut self.accumulated_repdefs);
7232 Ok(self.do_flush(arrays, accumulated_repdefs, row_number, num_rows)?)
7233 } else {
7234 Ok(vec![])
7235 }
7236 }
7237
7238 fn num_columns(&self) -> u32 {
7239 1
7240 }
7241
7242 fn finish(
7243 &mut self,
7244 _external_buffers: &mut OutOfLineBuffers,
7245 ) -> BoxFuture<'_, Result<Vec<crate::encoder::EncodedColumn>>> {
7246 std::future::ready(Ok(vec![EncodedColumn::default()])).boxed()
7247 }
7248}
7249
7250#[cfg(test)]
7251#[allow(clippy::single_range_in_vec_init)]
7252mod tests {
7253 use super::{
7254 ChunkInstructions, DataBlock, DecodeMiniBlockTask, DecodePageTask, FixedFullZipDecodeTask,
7255 FixedPerValueDecompressor, FixedWidthDataBlock, FixedWidthDictionaryEncoding,
7256 FullZipCacheableState, FullZipDecodeDetails, FullZipDecodeTaskItem, FullZipReadSource,
7257 FullZipRepIndexDetails, FullZipScheduler, LazyLevels, LevelCodec, LevelCursor, LevelPlan,
7258 MiniBlockChunk, MiniBlockChunkIndex, MiniBlockCompressed, MiniblockChunkSize,
7259 PerValueDataBlock, PerValueDecompressor, PreambleAction, RunEndsBuilder, RunPosition,
7260 RunStorage, StructuralPageScheduler, VariableFullZipDecoder, dense_levels_from_block,
7261 validate_complex_all_null_levels,
7262 };
7263 use crate::buffer::LanceBuffer;
7264 use crate::compression::{
7265 BlockCompressor, DefaultDecompressionStrategy, MiniBlockDecompressor,
7266 };
7267 use crate::constants::{
7268 COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY, DICT_VALUES_COMPRESSION_LEVEL_META_KEY,
7269 DICT_VALUES_COMPRESSION_META_KEY, STRUCTURAL_ENCODING_META_KEY,
7270 STRUCTURAL_ENCODING_MINIBLOCK,
7271 };
7272 use crate::data::BlockInfo;
7273 use crate::decoder::{PageEncoding, StructuralFieldDecoder};
7274 use crate::encodings::logical::primitive::fullzip::PerValueCompressor;
7275 use crate::encodings::logical::primitive::{
7276 ChunkDrainInstructions, LoadedChunk, PrimitiveStructuralEncoder,
7277 StructuralPrimitiveFieldDecoder,
7278 };
7279 use crate::encodings::physical::rle::{RleDecompressor, RleEncoder, RleRuns, RunLengthWidth};
7280 use crate::encodings::physical::value::{ValueDecompressor, ValueEncoder};
7281 use crate::format::ProtobufUtils21;
7282 use crate::format::pb21;
7283 use crate::format::pb21::compressive_encoding::Compression;
7284 use crate::repdef::build_control_word_iterator;
7285 use crate::testing::TestEncoding;
7286 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
7287 use arrow_array::{
7288 Array, ArrayRef, DictionaryArray, FixedSizeListArray, Float32Array, Int8Array,
7289 PrimitiveArray, StringArray, UInt8Array, make_array, new_null_array, types::Int32Type,
7290 };
7291 use arrow_buffer::{BooleanBuffer, NullBuffer, ScalarBuffer};
7292 use arrow_schema::{DataType, Field as ArrowField};
7293 use std::collections::HashMap;
7294 use std::{collections::VecDeque, sync::Arc};
7295
7296 #[test]
7297 fn test_is_narrow() {
7298 let int8_array = Int8Array::from(vec![1, 2, 3]);
7299 let array_ref: ArrayRef = Arc::new(int8_array);
7300 let block = DataBlock::from_array(array_ref);
7301
7302 assert!(PrimitiveStructuralEncoder::is_narrow(&block));
7303
7304 let string_array = StringArray::from(vec![Some("hello"), Some("world")]);
7305 let block = DataBlock::from_array(string_array);
7306 assert!(PrimitiveStructuralEncoder::is_narrow(&block));
7307
7308 let string_array = StringArray::from(vec![
7309 Some("hello world".repeat(100)),
7310 Some("world".to_string()),
7311 ]);
7312 let block = DataBlock::from_array(string_array);
7313 assert!((!PrimitiveStructuralEncoder::is_narrow(&block)));
7314 }
7315
7316 fn valued_dictionary() -> ArrayRef {
7317 Arc::new(
7318 DictionaryArray::<Int32Type>::try_new(
7319 PrimitiveArray::<Int32Type>::from(vec![0]),
7320 Arc::new(StringArray::from(vec!["a"])),
7321 )
7322 .unwrap(),
7323 )
7324 }
7325
7326 fn empty_dictionary() -> ArrayRef {
7327 new_null_array(
7328 &DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
7329 1,
7330 )
7331 }
7332
7333 fn null_valued_dictionary() -> ArrayRef {
7334 Arc::new(
7335 DictionaryArray::<Int32Type>::try_new(
7336 PrimitiveArray::<Int32Type>::from(vec![0]),
7337 Arc::new(StringArray::from(vec![None::<&str>])),
7338 )
7339 .unwrap(),
7340 )
7341 }
7342
7343 fn sliced_empty_dictionary_with_hidden_key_payload() -> ArrayRef {
7344 let keys = PrimitiveArray::<Int32Type>::new(
7345 ScalarBuffer::from(vec![5, 7, 9]),
7346 Some(NullBuffer::new(BooleanBuffer::new_unset(3))),
7347 );
7348 let values = Arc::new(StringArray::from(Vec::<&str>::new())) as ArrayRef;
7349 let dictionary = DictionaryArray::<Int32Type>::try_new(keys, values).unwrap();
7350 Arc::new(dictionary.slice(1, 1))
7351 }
7352
7353 #[rstest::rstest]
7354 #[case::empty_after_value(vec![valued_dictionary(), empty_dictionary()])]
7355 #[case::empty_before_value(vec![empty_dictionary(), valued_dictionary()])]
7356 #[case::null_value_after_value(vec![valued_dictionary(), null_valued_dictionary()])]
7357 #[case::hidden_sliced_after_value(vec![
7358 valued_dictionary(),
7359 sliced_empty_dictionary_with_hidden_key_payload(),
7360 ])]
7361 #[tokio::test]
7362 async fn test_mixed_valued_and_all_null_dictionary_chunks(#[case] dictionaries: Vec<ArrayRef>) {
7363 check_round_trip_encoding_of_data(
7364 dictionaries,
7365 &TestCases::default()
7366 .with_structural_encodings()
7367 .with_page_sizes(vec![4096]),
7368 HashMap::new(),
7369 )
7370 .await;
7371 }
7372
7373 #[tokio::test]
7374 async fn test_sliced_empty_dictionary_with_hidden_key_payload() {
7375 check_round_trip_encoding_of_data(
7376 vec![sliced_empty_dictionary_with_hidden_key_payload()],
7377 &TestCases::default()
7378 .with_structural_encodings()
7379 .with_page_sizes(vec![4096]),
7380 HashMap::new(),
7381 )
7382 .await;
7383 }
7384
7385 #[test]
7386 fn test_primitive_decoder_empty_page_queue_returns_error() {
7387 let field = Arc::new(ArrowField::new("vector", DataType::Float32, true));
7388 let mut decoder = StructuralPrimitiveFieldDecoder::new(&field, false);
7389
7390 let err = decoder.drain(1).unwrap_err();
7391 assert!(
7392 matches!(&err, lance_core::Error::Internal { .. }),
7393 "expected internal error, got: {err:?}"
7394 );
7395 let message = err.to_string();
7396 for expected in [
7397 "Primitive decoder missing page decoder",
7398 "field 'vector'",
7399 "data_type=Float32",
7400 "requested_rows=1",
7401 "remaining_rows=1",
7402 "rows_drained_in_current=0",
7403 "queued_pages=0",
7404 ] {
7405 assert!(
7406 message.contains(expected),
7407 "expected error to contain {expected:?}, got: {message}"
7408 );
7409 }
7410 }
7411
7412 #[test]
7413 fn test_fullzip_fixed_rejects_non_byte_aligned_values() {
7414 let fixed = FixedWidthDataBlock {
7415 data: LanceBuffer::from(vec![0_u8]),
7416 bits_per_value: 1,
7417 num_values: 8,
7418 block_info: BlockInfo::new(),
7419 };
7420 let repdef = build_control_word_iterator(None, 0, None, 0, u16::MAX, 8);
7421
7422 let Err(err) = PrimitiveStructuralEncoder::serialize_full_zip_fixed(fixed, repdef, 8)
7423 else {
7424 panic!("expected full-zip to reject 1-bit fixed-width values");
7425 };
7426 assert!(
7427 err.to_string().contains("byte aligned"),
7428 "unexpected error: {err}"
7429 );
7430 }
7431
7432 fn decode_fixed_fullzip_no_levels(
7433 decompressor: Arc<dyn FixedPerValueDecompressor>,
7434 data: Vec<FullZipDecodeTaskItem>,
7435 num_rows: usize,
7436 bytes_per_value: usize,
7437 ) -> DataBlock {
7438 Box::new(FixedFullZipDecodeTask {
7439 details: Arc::new(FullZipDecodeDetails {
7440 value_decompressor: PerValueDecompressor::Fixed(decompressor),
7441 def_meaning: Arc::from([]),
7442 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 0),
7443 max_rep: 0,
7444 max_visible_def: u16::MAX,
7445 }),
7446 data,
7447 num_rows,
7448 bytes_per_value,
7449 })
7450 .decode()
7451 .unwrap()
7452 .data
7453 }
7454
7455 #[test]
7456 fn test_fixed_fullzip_decode_preallocates_exact_output_size() {
7457 #[derive(Debug)]
7458 struct IdentityFixedDecompressor;
7459
7460 impl FixedPerValueDecompressor for IdentityFixedDecompressor {
7461 fn decompress(
7462 &self,
7463 data: FixedWidthDataBlock,
7464 num_rows: u64,
7465 ) -> crate::Result<DataBlock> {
7466 assert_eq!(data.num_values, num_rows);
7467 Ok(DataBlock::FixedWidth(data))
7468 }
7469
7470 fn bits_per_value(&self) -> u64 {
7471 32
7472 }
7473
7474 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
7475 num_values.checked_mul(4)
7476 }
7477 }
7478
7479 let make_item = |num_rows: u64| FullZipDecodeTaskItem {
7480 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
7481 data: LanceBuffer::from(vec![7_u8; num_rows as usize * 4]),
7482 bits_per_value: 32,
7483 num_values: num_rows,
7484 block_info: BlockInfo::new(),
7485 }),
7486 rows_in_buf: num_rows,
7487 };
7488
7489 let num_rows = 512;
7490 let decoded = decode_fixed_fullzip_no_levels(
7491 Arc::new(IdentityFixedDecompressor),
7492 vec![make_item(128), make_item(384)],
7493 num_rows,
7494 4,
7495 );
7496 let values = decoded.as_fixed_width_ref().unwrap();
7497 let expected_size = num_rows * 4;
7498 assert_eq!(values.data.len(), expected_size);
7499 assert_eq!(values.data.clone().into_buffer().capacity(), expected_size);
7500 }
7501
7502 #[test]
7503 fn test_fixed_fullzip_decode_falls_back_when_output_size_is_not_exact() {
7504 #[derive(Debug)]
7505 struct FallbackFixedDecompressor;
7506
7507 impl FixedPerValueDecompressor for FallbackFixedDecompressor {
7508 fn decompress(
7509 &self,
7510 data: FixedWidthDataBlock,
7511 num_rows: u64,
7512 ) -> crate::Result<DataBlock> {
7513 assert_eq!(data.num_values, num_rows);
7514 Ok(DataBlock::FixedWidth(FixedWidthDataBlock {
7515 data: LanceBuffer::from(vec![7_u8; num_rows as usize * 4]),
7516 bits_per_value: 32,
7517 num_values: num_rows,
7518 block_info: BlockInfo::new(),
7519 }))
7520 }
7521
7522 fn bits_per_value(&self) -> u64 {
7523 u64::MAX - 7
7526 }
7527 }
7528
7529 let num_rows = 2;
7530 let decoded = decode_fixed_fullzip_no_levels(
7531 Arc::new(FallbackFixedDecompressor),
7532 vec![FullZipDecodeTaskItem {
7533 data: PerValueDataBlock::Fixed(FixedWidthDataBlock {
7534 data: LanceBuffer::from(vec![0_u8; num_rows * 4]),
7535 bits_per_value: 32,
7536 num_values: num_rows as u64,
7537 block_info: BlockInfo::new(),
7538 }),
7539 rows_in_buf: num_rows as u64,
7540 }],
7541 num_rows,
7542 4,
7543 );
7544 let values = decoded.as_fixed_width_ref().unwrap();
7545 assert_eq!(values.num_values, num_rows as u64);
7546 assert_eq!(values.data.len(), num_rows * 4);
7547 }
7548
7549 #[test]
7550 fn test_fixed_fullzip_real_fsl_preallocates_exact_output_size() {
7551 let num_rows = 64;
7552 let dimension = 32;
7553 let items = Arc::new(Float32Array::from_iter_values(
7554 (0..num_rows * dimension).map(|value| value as f32),
7555 ));
7556 let item_field = Arc::new(ArrowField::new("item", DataType::Float32, false));
7557 let sample = FixedSizeListArray::new(item_field, dimension as i32, items, None);
7558
7559 let (data, compression) = PerValueCompressor::compress(
7560 &ValueEncoder::default(),
7561 DataBlock::from_array(sample.clone()),
7562 )
7563 .unwrap();
7564 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
7565 panic!("expected fixed-size-list compression");
7566 };
7567 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref()).unwrap();
7568 let expected_size = num_rows * dimension * size_of::<f32>();
7569 assert_eq!(
7570 FixedPerValueDecompressor::decoded_size_bytes(&decompressor, num_rows as u64),
7571 Some(expected_size as u64)
7572 );
7573
7574 let decoded = decode_fixed_fullzip_no_levels(
7575 Arc::new(decompressor),
7576 vec![FullZipDecodeTaskItem {
7577 data,
7578 rows_in_buf: num_rows as u64,
7579 }],
7580 num_rows,
7581 dimension * size_of::<f32>(),
7582 );
7583 let fsl = decoded.as_fixed_size_list_ref().unwrap();
7584 let values = fsl.child.as_fixed_width_ref().unwrap();
7585 assert_eq!(values.data.len(), expected_size);
7586 assert_eq!(values.data.clone().into_buffer().capacity(), expected_size);
7587
7588 let decoded_array = make_array(
7589 decoded
7590 .into_arrow(sample.data_type().clone(), true)
7591 .unwrap(),
7592 );
7593 assert_eq!(decoded_array.as_ref(), &sample);
7594 }
7595
7596 #[test]
7597 fn test_fixed_fullzip_nullable_fsl_uses_fallback_end_to_end() {
7598 #[derive(Debug)]
7599 struct NullableFslDecompressor {
7600 inner: ValueDecompressor,
7601 }
7602
7603 impl FixedPerValueDecompressor for NullableFslDecompressor {
7604 fn decompress(
7605 &self,
7606 data: FixedWidthDataBlock,
7607 num_rows: u64,
7608 ) -> crate::Result<DataBlock> {
7609 FixedPerValueDecompressor::decompress(&self.inner, data, num_rows)
7610 }
7611
7612 fn bits_per_value(&self) -> u64 {
7613 u64::MAX - 7
7616 }
7617
7618 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
7619 FixedPerValueDecompressor::decoded_size_bytes(&self.inner, num_values)
7620 }
7621 }
7622
7623 let num_rows = 64;
7624 let items = Arc::new(UInt8Array::from_iter(
7625 (0..num_rows).map(|value| (value % 3 != 0).then_some(value as u8)),
7626 ));
7627 let item_field = Arc::new(ArrowField::new("item", DataType::UInt8, true));
7628 let sample = FixedSizeListArray::new(item_field, 1, items, None);
7629
7630 let (data, compression) = PerValueCompressor::compress(
7631 &ValueEncoder::default(),
7632 DataBlock::from_array(sample.clone()),
7633 )
7634 .unwrap();
7635 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
7636 panic!("expected fixed-size-list compression");
7637 };
7638 let decompressor = NullableFslDecompressor {
7639 inner: ValueDecompressor::from_fsl(fsl.as_ref()).unwrap(),
7640 };
7641 assert_eq!(
7642 FixedPerValueDecompressor::decoded_size_bytes(&decompressor, num_rows as u64),
7643 None
7644 );
7645
7646 let decoded = decode_fixed_fullzip_no_levels(
7647 Arc::new(decompressor),
7648 vec![FullZipDecodeTaskItem {
7649 data,
7650 rows_in_buf: num_rows as u64,
7651 }],
7652 num_rows,
7653 2,
7654 );
7655 let decoded_array = make_array(
7656 decoded
7657 .into_arrow(sample.data_type().clone(), true)
7658 .unwrap(),
7659 );
7660 assert_eq!(decoded_array.as_ref(), &sample);
7661 }
7662
7663 #[test]
7664 fn test_miniblock_decode_uses_exact_fixed_width_output_size() {
7665 #[derive(Debug)]
7666 struct FixedWidthMiniBlockDecompressor;
7667
7668 impl MiniBlockDecompressor for FixedWidthMiniBlockDecompressor {
7669 fn decompress(
7670 &self,
7671 data: Vec<LanceBuffer>,
7672 num_values: u64,
7673 ) -> crate::Result<DataBlock> {
7674 assert_eq!(data.len(), 1);
7675 Ok(DataBlock::FixedWidth(FixedWidthDataBlock {
7676 data: data.into_iter().next().unwrap(),
7677 bits_per_value: 32,
7678 num_values,
7679 block_info: BlockInfo::new(),
7680 }))
7681 }
7682
7683 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
7684 num_values.checked_mul(4)
7685 }
7686 }
7687
7688 let num_rows = 512;
7689 let expected_size = num_rows * 4;
7690 let mut chunk_data = Vec::new();
7691 chunk_data.extend_from_slice(&0_u16.to_le_bytes());
7692 chunk_data.extend_from_slice(&(expected_size as u16).to_le_bytes());
7693 let header_padding =
7694 lance_core::utils::bit::pad_bytes::<{ super::MINIBLOCK_ALIGNMENT }>(chunk_data.len());
7695 chunk_data.resize(chunk_data.len() + header_padding, 0);
7696 chunk_data.resize(chunk_data.len() + expected_size as usize, 7);
7697
7698 let task = DecodeMiniBlockTask {
7699 rep_decompressor: None,
7700 def_decompressor: None,
7701 value_decompressor: Arc::new(FixedWidthMiniBlockDecompressor),
7702 dictionary_data: None,
7703 def_meaning: Arc::from([]),
7704 num_buffers: 1,
7705 max_visible_level: 0,
7706 instructions: vec![(
7707 ChunkDrainInstructions {
7708 chunk_instructions: ChunkInstructions {
7709 chunk_idx: 0,
7710 preamble: PreambleAction::Absent,
7711 rows_to_skip: 0,
7712 rows_to_take: num_rows,
7713 take_trailer: false,
7714 },
7715 rows_to_skip: 0,
7716 rows_to_take: num_rows,
7717 preamble_action: PreambleAction::Absent,
7718 },
7719 LoadedChunk {
7720 byte_range: 0..chunk_data.len() as u64,
7721 data: LanceBuffer::from(chunk_data),
7722 items_in_chunk: num_rows,
7723 chunk_idx: 0,
7724 },
7725 )],
7726 has_large_chunk: false,
7727 };
7728
7729 let decoded = Box::new(task).decode().unwrap();
7730 let values = decoded.data.as_fixed_width_ref().unwrap();
7731 assert_eq!(values.data.len(), expected_size as usize);
7732 assert_eq!(
7733 values.data.clone().into_buffer().capacity(),
7734 expected_size as usize
7735 );
7736 }
7737
7738 #[test]
7739 fn test_map_range() {
7740 let rep = Some(vec![1, 0, 0, 1, 0, 1, 1, 0, 0]);
7743 let def = Some(vec![0, 0, 0, 0, 0, 1, 0, 0, 0]);
7744 let max_visible_def = 0;
7745 let total_items = 8;
7746 let max_rep = 1;
7747
7748 let check = |range, expected_item_range, expected_level_range| {
7749 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7750 range,
7751 rep.as_ref(),
7752 def.as_ref(),
7753 max_rep,
7754 max_visible_def,
7755 total_items,
7756 PreambleAction::Absent,
7757 );
7758 assert_eq!(item_range, expected_item_range);
7759 assert_eq!(level_range, expected_level_range);
7760 };
7761
7762 check(0..1, 0..3, 0..3);
7763 check(1..2, 3..5, 3..5);
7764 check(2..3, 5..5, 5..6);
7765 check(3..4, 5..8, 6..9);
7766 check(0..2, 0..5, 0..5);
7767 check(1..3, 3..5, 3..6);
7768 check(2..4, 5..8, 5..9);
7769 check(0..3, 0..5, 0..6);
7770 check(1..4, 3..8, 3..9);
7771 check(0..4, 0..8, 0..9);
7772
7773 let rep = Some(vec![1, 1, 0, 1]);
7776 let def = Some(vec![1, 0, 0, 0]);
7777 let max_visible_def = 0;
7778 let total_items = 3;
7779
7780 let check = |range, expected_item_range, expected_level_range| {
7781 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7782 range,
7783 rep.as_ref(),
7784 def.as_ref(),
7785 max_rep,
7786 max_visible_def,
7787 total_items,
7788 PreambleAction::Absent,
7789 );
7790 assert_eq!(item_range, expected_item_range);
7791 assert_eq!(level_range, expected_level_range);
7792 };
7793
7794 check(0..1, 0..0, 0..1);
7795 check(1..2, 0..2, 1..3);
7796 check(2..3, 2..3, 3..4);
7797 check(0..2, 0..2, 0..3);
7798 check(1..3, 0..3, 1..4);
7799 check(0..3, 0..3, 0..4);
7800
7801 let rep = Some(vec![1, 1, 0, 1]);
7804 let def = Some(vec![0, 0, 0, 1]);
7805 let max_visible_def = 0;
7806 let total_items = 3;
7807
7808 let check = |range, expected_item_range, expected_level_range| {
7809 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7810 range,
7811 rep.as_ref(),
7812 def.as_ref(),
7813 max_rep,
7814 max_visible_def,
7815 total_items,
7816 PreambleAction::Absent,
7817 );
7818 assert_eq!(item_range, expected_item_range);
7819 assert_eq!(level_range, expected_level_range);
7820 };
7821
7822 check(0..1, 0..1, 0..1);
7823 check(1..2, 1..3, 1..3);
7824 check(2..3, 3..3, 3..4);
7825 check(0..2, 0..3, 0..3);
7826 check(1..3, 1..3, 1..4);
7827 check(0..3, 0..3, 0..4);
7828
7829 let rep = Some(vec![1, 0, 1, 0, 1, 0]);
7832 let def: Option<&[u16]> = None;
7833 let max_visible_def = 0;
7834 let total_items = 6;
7835
7836 let check = |range, expected_item_range, expected_level_range| {
7837 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7838 range,
7839 rep.as_ref(),
7840 def.as_ref(),
7841 max_rep,
7842 max_visible_def,
7843 total_items,
7844 PreambleAction::Absent,
7845 );
7846 assert_eq!(item_range, expected_item_range);
7847 assert_eq!(level_range, expected_level_range);
7848 };
7849
7850 check(0..1, 0..2, 0..2);
7851 check(1..2, 2..4, 2..4);
7852 check(2..3, 4..6, 4..6);
7853 check(0..2, 0..4, 0..4);
7854 check(1..3, 2..6, 2..6);
7855 check(0..3, 0..6, 0..6);
7856
7857 let rep: Option<&[u16]> = None;
7860 let def = Some(vec![0, 0, 1, 0]);
7861 let max_visible_def = 1;
7862 let total_items = 4;
7863
7864 let check = |range, expected_item_range, expected_level_range| {
7865 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7866 range,
7867 rep.as_ref(),
7868 def.as_ref(),
7869 max_rep,
7870 max_visible_def,
7871 total_items,
7872 PreambleAction::Absent,
7873 );
7874 assert_eq!(item_range, expected_item_range);
7875 assert_eq!(level_range, expected_level_range);
7876 };
7877
7878 check(0..1, 0..1, 0..1);
7879 check(1..2, 1..2, 1..2);
7880 check(2..3, 2..3, 2..3);
7881 check(0..2, 0..2, 0..2);
7882 check(1..3, 1..3, 1..3);
7883 check(0..3, 0..3, 0..3);
7884
7885 let rep = Some(vec![0, 1, 0, 1]);
7890 let def = Some(vec![0, 0, 0, 1]);
7891 let max_visible_def = 0;
7892 let total_items = 3;
7893
7894 let check = |range, expected_item_range, expected_level_range| {
7895 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7896 range,
7897 rep.as_ref(),
7898 def.as_ref(),
7899 max_rep,
7900 max_visible_def,
7901 total_items,
7902 PreambleAction::Take,
7903 );
7904 assert_eq!(item_range, expected_item_range);
7905 assert_eq!(level_range, expected_level_range);
7906 };
7907
7908 check(0..1, 0..3, 0..3);
7910 check(0..2, 0..3, 0..4);
7911
7912 let check = |range, expected_item_range, expected_level_range| {
7913 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7914 range,
7915 rep.as_ref(),
7916 def.as_ref(),
7917 max_rep,
7918 max_visible_def,
7919 total_items,
7920 PreambleAction::Skip,
7921 );
7922 assert_eq!(item_range, expected_item_range);
7923 assert_eq!(level_range, expected_level_range);
7924 };
7925
7926 check(0..1, 1..3, 1..3);
7927 check(1..2, 3..3, 3..4);
7928 check(0..2, 1..3, 1..4);
7929
7930 let rep = Some(vec![0, 1, 1, 0]);
7935 let def = Some(vec![0, 1, 0, 0]);
7936 let max_visible_def = 0;
7937 let total_items = 4;
7938
7939 let check = |range, expected_item_range, expected_level_range| {
7940 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7941 range,
7942 rep.as_ref(),
7943 def.as_ref(),
7944 max_rep,
7945 max_visible_def,
7946 total_items,
7947 PreambleAction::Take,
7948 );
7949 assert_eq!(item_range, expected_item_range);
7950 assert_eq!(level_range, expected_level_range);
7951 };
7952
7953 check(0..1, 0..1, 0..2);
7955 check(0..2, 0..3, 0..4);
7956
7957 let check = |range, expected_item_range, expected_level_range| {
7958 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7959 range,
7960 rep.as_ref(),
7961 def.as_ref(),
7962 max_rep,
7963 max_visible_def,
7964 total_items,
7965 PreambleAction::Skip,
7966 );
7967 assert_eq!(item_range, expected_item_range);
7968 assert_eq!(level_range, expected_level_range);
7969 };
7970
7971 check(0..1, 1..1, 1..2);
7973 check(1..2, 1..3, 2..4);
7974 check(0..2, 1..3, 1..4);
7975
7976 let rep = Some(vec![0, 1, 0, 1]);
7979 let def: Option<Vec<u16>> = None;
7980 let max_visible_def = 0;
7981 let total_items = 4;
7982
7983 let check = |range, expected_item_range, expected_level_range| {
7984 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
7985 range,
7986 rep.as_ref(),
7987 def.as_ref(),
7988 max_rep,
7989 max_visible_def,
7990 total_items,
7991 PreambleAction::Take,
7992 );
7993 assert_eq!(item_range, expected_item_range);
7994 assert_eq!(level_range, expected_level_range);
7995 };
7996
7997 check(0..1, 0..3, 0..3);
7999 check(0..2, 0..4, 0..4);
8000
8001 let check = |range, expected_item_range, expected_level_range| {
8002 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
8003 range,
8004 rep.as_ref(),
8005 def.as_ref(),
8006 max_rep,
8007 max_visible_def,
8008 total_items,
8009 PreambleAction::Skip,
8010 );
8011 assert_eq!(item_range, expected_item_range);
8012 assert_eq!(level_range, expected_level_range);
8013 };
8014
8015 check(0..1, 1..3, 1..3);
8016 check(1..2, 3..4, 3..4);
8017 check(0..2, 1..4, 1..4);
8018
8019 let rep = Some(vec![2, 1, 2, 0, 1, 2]);
8023 let def = Some(vec![0, 1, 2, 0, 0, 0]);
8024 let max_rep = 2;
8025 let max_visible_def = 0;
8026 let total_items = 4;
8027
8028 let check = |range, expected_item_range, expected_level_range| {
8029 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
8030 range,
8031 rep.as_ref(),
8032 def.as_ref(),
8033 max_rep,
8034 max_visible_def,
8035 total_items,
8036 PreambleAction::Absent,
8037 );
8038 assert_eq!(item_range, expected_item_range);
8039 assert_eq!(level_range, expected_level_range);
8040 };
8041
8042 check(0..3, 0..4, 0..6);
8043 check(0..1, 0..1, 0..2);
8044 check(1..2, 1..3, 2..5);
8045 check(2..3, 3..4, 5..6);
8046
8047 let rep = Some(vec![0, 0, 1, 0, 1, 1]);
8049 let def = Some(vec![0, 1, 0, 0, 0, 0]);
8050 let max_rep = 1;
8051 let max_visible_def = 0;
8052 let total_items = 5;
8053
8054 let check = |range, expected_item_range, expected_level_range| {
8055 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
8056 range,
8057 rep.as_ref(),
8058 def.as_ref(),
8059 max_rep,
8060 max_visible_def,
8061 total_items,
8062 PreambleAction::Take,
8063 );
8064 assert_eq!(item_range, expected_item_range);
8065 assert_eq!(level_range, expected_level_range);
8066 };
8067
8068 check(0..0, 0..1, 0..2);
8069 check(0..1, 0..3, 0..4);
8070 check(0..2, 0..4, 0..5);
8071
8072 let rep = Some(vec![0, 1, 0, 1, 0, 1, 0, 1]);
8075 let def = Some(vec![1, 0, 1, 1, 0, 0, 0, 0]);
8076 let max_rep = 1;
8077 let max_visible_def = 0;
8078 let total_items = 5;
8079
8080 let check = |range, expected_item_range, expected_level_range| {
8081 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
8082 range,
8083 rep.as_ref(),
8084 def.as_ref(),
8085 max_rep,
8086 max_visible_def,
8087 total_items,
8088 PreambleAction::Skip,
8089 );
8090 assert_eq!(item_range, expected_item_range);
8091 assert_eq!(level_range, expected_level_range);
8092 };
8093
8094 check(2..3, 2..4, 5..7);
8095 }
8096
8097 #[test]
8098 fn test_slice_batch_data_and_rebase_offsets_u32() {
8099 let data = LanceBuffer::copy_slice(b"0123456789abcdefghij");
8100 let offsets = LanceBuffer::reinterpret_vec(vec![6_u32, 8_u32, 8_u32, 12_u32]);
8101
8102 let (sliced_data, normalized_offsets) =
8103 VariableFullZipDecoder::slice_batch_data_and_rebase_offsets(&data, &offsets, 32)
8104 .unwrap();
8105
8106 assert_eq!(sliced_data.as_ref(), b"6789ab");
8107 let normalized = normalized_offsets.borrow_to_typed_slice::<u32>();
8108 assert_eq!(normalized.as_ref(), &[0, 2, 2, 6]);
8109 }
8110
8111 #[test]
8112 fn test_slice_batch_data_and_rebase_offsets_u64() {
8113 let data = LanceBuffer::copy_slice(b"abcdefghijklmnopqrstuvwxyz");
8114 let offsets = LanceBuffer::reinterpret_vec(vec![10_u64, 12_u64, 16_u64, 20_u64]);
8115
8116 let (sliced_data, normalized_offsets) =
8117 VariableFullZipDecoder::slice_batch_data_and_rebase_offsets(&data, &offsets, 64)
8118 .unwrap();
8119
8120 assert_eq!(sliced_data.as_ref(), b"klmnopqrst");
8121 let normalized = normalized_offsets.borrow_to_typed_slice::<u64>();
8122 assert_eq!(normalized.as_ref(), &[0, 2, 6, 10]);
8123 }
8124
8125 #[test]
8126 fn test_slice_batch_data_and_rebase_offsets_rejects_invalid_offsets() {
8127 let data = LanceBuffer::copy_slice(b"abcd");
8128 let offsets = LanceBuffer::reinterpret_vec(vec![3_u32, 2_u32]);
8129
8130 let err = VariableFullZipDecoder::slice_batch_data_and_rebase_offsets(&data, &offsets, 32)
8131 .expect_err("offset end before start should error");
8132 assert!(err.to_string().contains("less than base"));
8133 }
8134
8135 #[test]
8136 fn test_schedule_instructions() {
8137 let rep_data: Vec<u64> = vec![5, 2, 3, 0, 4, 7, 2, 0];
8139 let rep_bytes: Vec<u8> = rep_data.iter().flat_map(|v| v.to_le_bytes()).collect();
8140 let chunk_index = MiniBlockChunkIndex::new_nested_for_test(&rep_bytes, 2);
8141
8142 let check = |user_ranges, expected_instructions| {
8143 let instructions = ChunkInstructions::schedule_instructions(&chunk_index, user_ranges);
8144 assert_eq!(instructions, expected_instructions);
8145 };
8146
8147 let expected_take_all = vec![
8149 ChunkInstructions {
8150 chunk_idx: 0,
8151 preamble: PreambleAction::Absent,
8152 rows_to_skip: 0,
8153 rows_to_take: 6,
8154 take_trailer: true,
8155 },
8156 ChunkInstructions {
8157 chunk_idx: 1,
8158 preamble: PreambleAction::Take,
8159 rows_to_skip: 0,
8160 rows_to_take: 2,
8161 take_trailer: false,
8162 },
8163 ChunkInstructions {
8164 chunk_idx: 2,
8165 preamble: PreambleAction::Absent,
8166 rows_to_skip: 0,
8167 rows_to_take: 5,
8168 take_trailer: true,
8169 },
8170 ChunkInstructions {
8171 chunk_idx: 3,
8172 preamble: PreambleAction::Take,
8173 rows_to_skip: 0,
8174 rows_to_take: 1,
8175 take_trailer: false,
8176 },
8177 ];
8178
8179 check(&[0..14], expected_take_all.clone());
8181
8182 check(
8184 &[
8185 0..1,
8186 1..2,
8187 2..3,
8188 3..4,
8189 4..5,
8190 5..6,
8191 6..7,
8192 7..8,
8193 8..9,
8194 9..10,
8195 10..11,
8196 11..12,
8197 12..13,
8198 13..14,
8199 ],
8200 expected_take_all,
8201 );
8202
8203 check(
8207 &[0..1, 3..4],
8208 vec![
8209 ChunkInstructions {
8210 chunk_idx: 0,
8211 preamble: PreambleAction::Absent,
8212 rows_to_skip: 0,
8213 rows_to_take: 1,
8214 take_trailer: false,
8215 },
8216 ChunkInstructions {
8217 chunk_idx: 0,
8218 preamble: PreambleAction::Absent,
8219 rows_to_skip: 3,
8220 rows_to_take: 1,
8221 take_trailer: false,
8222 },
8223 ],
8224 );
8225
8226 check(
8228 &[5..6],
8229 vec![
8230 ChunkInstructions {
8231 chunk_idx: 0,
8232 preamble: PreambleAction::Absent,
8233 rows_to_skip: 5,
8234 rows_to_take: 1,
8235 take_trailer: true,
8236 },
8237 ChunkInstructions {
8238 chunk_idx: 1,
8239 preamble: PreambleAction::Take,
8240 rows_to_skip: 0,
8241 rows_to_take: 0,
8242 take_trailer: false,
8243 },
8244 ],
8245 );
8246
8247 check(
8249 &[7..10],
8250 vec![
8251 ChunkInstructions {
8252 chunk_idx: 1,
8253 preamble: PreambleAction::Skip,
8254 rows_to_skip: 1,
8255 rows_to_take: 1,
8256 take_trailer: false,
8257 },
8258 ChunkInstructions {
8259 chunk_idx: 2,
8260 preamble: PreambleAction::Absent,
8261 rows_to_skip: 0,
8262 rows_to_take: 2,
8263 take_trailer: false,
8264 },
8265 ],
8266 );
8267 }
8268
8269 #[test]
8270 fn test_drain_instructions() {
8271 fn drain_from_instructions(
8272 instructions: &mut VecDeque<ChunkInstructions>,
8273 mut rows_desired: u64,
8274 need_preamble: &mut bool,
8275 skip_in_chunk: &mut u64,
8276 ) -> Vec<ChunkDrainInstructions> {
8277 let mut drain_instructions = Vec::with_capacity(instructions.len());
8279 while rows_desired > 0 || *need_preamble {
8280 let (next_instructions, consumed_chunk) = instructions
8281 .front()
8282 .unwrap()
8283 .drain_from_instruction(&mut rows_desired, need_preamble, skip_in_chunk);
8284 if consumed_chunk {
8285 instructions.pop_front();
8286 }
8287 drain_instructions.push(next_instructions);
8288 }
8289 drain_instructions
8290 }
8291
8292 let rep_data: Vec<u64> = vec![5, 2, 3, 0, 4, 7, 2, 0];
8294 let rep_bytes: Vec<u8> = rep_data.iter().flat_map(|v| v.to_le_bytes()).collect();
8295 let chunk_index = MiniBlockChunkIndex::new_nested_for_test(&rep_bytes, 2);
8296 let user_ranges = vec![1..7, 10..14];
8297
8298 let scheduled = ChunkInstructions::schedule_instructions(&chunk_index, &user_ranges);
8300
8301 let mut to_drain = VecDeque::from(scheduled.clone());
8302
8303 let mut need_preamble = false;
8306 let mut skip_in_chunk = 0;
8307
8308 let next_batch =
8309 drain_from_instructions(&mut to_drain, 4, &mut need_preamble, &mut skip_in_chunk);
8310
8311 assert!(!need_preamble);
8312 assert_eq!(skip_in_chunk, 4);
8313 assert_eq!(
8314 next_batch,
8315 vec![ChunkDrainInstructions {
8316 chunk_instructions: scheduled[0].clone(),
8317 rows_to_take: 4,
8318 rows_to_skip: 0,
8319 preamble_action: PreambleAction::Absent,
8320 }]
8321 );
8322
8323 let next_batch =
8324 drain_from_instructions(&mut to_drain, 4, &mut need_preamble, &mut skip_in_chunk);
8325
8326 assert!(!need_preamble);
8327 assert_eq!(skip_in_chunk, 2);
8328
8329 assert_eq!(
8330 next_batch,
8331 vec![
8332 ChunkDrainInstructions {
8333 chunk_instructions: scheduled[0].clone(),
8334 rows_to_take: 1,
8335 rows_to_skip: 4,
8336 preamble_action: PreambleAction::Absent,
8337 },
8338 ChunkDrainInstructions {
8339 chunk_instructions: scheduled[1].clone(),
8340 rows_to_take: 1,
8341 rows_to_skip: 0,
8342 preamble_action: PreambleAction::Take,
8343 },
8344 ChunkDrainInstructions {
8345 chunk_instructions: scheduled[2].clone(),
8346 rows_to_take: 2,
8347 rows_to_skip: 0,
8348 preamble_action: PreambleAction::Absent,
8349 }
8350 ]
8351 );
8352
8353 let next_batch =
8354 drain_from_instructions(&mut to_drain, 2, &mut need_preamble, &mut skip_in_chunk);
8355
8356 assert!(!need_preamble);
8357 assert_eq!(skip_in_chunk, 0);
8358
8359 assert_eq!(
8360 next_batch,
8361 vec![
8362 ChunkDrainInstructions {
8363 chunk_instructions: scheduled[2].clone(),
8364 rows_to_take: 1,
8365 rows_to_skip: 2,
8366 preamble_action: PreambleAction::Absent,
8367 },
8368 ChunkDrainInstructions {
8369 chunk_instructions: scheduled[3].clone(),
8370 rows_to_take: 1,
8371 rows_to_skip: 0,
8372 preamble_action: PreambleAction::Take,
8373 },
8374 ]
8375 );
8376
8377 let rep_data: Vec<u64> = vec![5, 2, 3, 3, 20, 0];
8379 let rep_bytes: Vec<u8> = rep_data.iter().flat_map(|v| v.to_le_bytes()).collect();
8380 let chunk_index = MiniBlockChunkIndex::new_nested_for_test(&rep_bytes, 2);
8381 let user_ranges = vec![0..28];
8382
8383 let scheduled = ChunkInstructions::schedule_instructions(&chunk_index, &user_ranges);
8385
8386 let mut to_drain = VecDeque::from(scheduled.clone());
8387
8388 let mut need_preamble = false;
8391 let mut skip_in_chunk = 0;
8392
8393 let next_batch =
8394 drain_from_instructions(&mut to_drain, 7, &mut need_preamble, &mut skip_in_chunk);
8395
8396 assert_eq!(
8397 next_batch,
8398 vec![
8399 ChunkDrainInstructions {
8400 chunk_instructions: scheduled[0].clone(),
8401 rows_to_take: 6,
8402 rows_to_skip: 0,
8403 preamble_action: PreambleAction::Absent,
8404 },
8405 ChunkDrainInstructions {
8406 chunk_instructions: scheduled[1].clone(),
8407 rows_to_take: 1,
8408 rows_to_skip: 0,
8409 preamble_action: PreambleAction::Take,
8410 },
8411 ]
8412 );
8413
8414 assert!(!need_preamble);
8415 assert_eq!(skip_in_chunk, 1);
8416
8417 let next_batch =
8420 drain_from_instructions(&mut to_drain, 2, &mut need_preamble, &mut skip_in_chunk);
8421
8422 assert_eq!(
8423 next_batch,
8424 vec![
8425 ChunkDrainInstructions {
8426 chunk_instructions: scheduled[1].clone(),
8427 rows_to_take: 2,
8428 rows_to_skip: 1,
8429 preamble_action: PreambleAction::Skip,
8430 },
8431 ChunkDrainInstructions {
8432 chunk_instructions: scheduled[2].clone(),
8433 rows_to_take: 0,
8434 rows_to_skip: 0,
8435 preamble_action: PreambleAction::Take,
8436 },
8437 ]
8438 );
8439
8440 assert!(!need_preamble);
8441 assert_eq!(skip_in_chunk, 0);
8442 }
8443
8444 use super::chunk_index::{PrefixSums, RowMapping};
8445 use super::{MINIBLOCK_ALIGNMENT, Words, build_chunk_index};
8446 use bytes::Bytes;
8447 use lance_core::cache::{Context, DeepSizeOf};
8448 use rstest::rstest;
8449
8450 fn words_from(entries: &[(u32, u32)]) -> (Words, u64) {
8453 let mut raw = Vec::with_capacity(entries.len() * 2);
8454 let mut total = 0u64;
8455 for &(log, num_bytes) in entries {
8456 assert!(num_bytes > 0 && num_bytes % MINIBLOCK_ALIGNMENT as u32 == 0);
8457 let divided = num_bytes / MINIBLOCK_ALIGNMENT as u32 - 1;
8458 let word = (divided << 4) | log;
8459 assert!(word <= u16::MAX as u32, "test word {word} exceeds u16");
8460 raw.extend_from_slice(&(word as u16).to_le_bytes());
8461 total += num_bytes as u64;
8462 }
8463 (Words::from_bytes(Bytes::from(raw), false).unwrap(), total)
8464 }
8465
8466 fn rep_bytes_from(values: &[u64]) -> Vec<u8> {
8467 values.iter().flat_map(|v| v.to_le_bytes()).collect()
8468 }
8469
8470 #[rstest]
8471 #[case::uniform_partial_last(&[(3, 16), (3, 24), (0, 8)], 19, "uniform_flat", 8, 3)]
8474 #[case::single_chunk(&[(0, 24)], 5, "uniform_flat", 5, 5)]
8476 #[case::exact_multiple(&[(3, 16), (3, 16)], 16, "uniform_flat", 8, 8)]
8478 #[case::non_uniform(&[(4, 16), (2, 16), (0, 8)], 21, "flat", 16, 1)]
8480 fn test_flat_detection(
8481 #[case] entries: &[(u32, u32)],
8482 #[case] items_in_page: u64,
8483 #[case] expected_kind: &str,
8484 #[case] expected_first_items: u64,
8485 #[case] expected_last_items: u64,
8486 ) {
8487 let base = 100u64;
8488 let (words, data_buf_size) = words_from(entries);
8489 let index = build_chunk_index(&words, items_in_page, base, data_buf_size, None, 0).unwrap();
8490
8491 assert_eq!(index.row_mapping_debug(), expected_kind);
8492 assert_eq!(index.num_chunks(), entries.len());
8493 assert_eq!(index.items_in_chunk(0), expected_first_items);
8494 assert_eq!(index.items_in_chunk(entries.len() - 1), expected_last_items);
8495
8496 let mut expected_start = base;
8498 for (i, &(_, num_bytes)) in entries.iter().enumerate() {
8499 let range = index.byte_range(i);
8500 assert_eq!(range.start, expected_start);
8501 assert_eq!(range.end - range.start, num_bytes as u64);
8502 expected_start = range.end;
8503 }
8504 assert_eq!(expected_start, base + data_buf_size);
8505
8506 let total_items: u64 = (0..index.num_chunks())
8508 .map(|i| index.items_in_chunk(i))
8509 .sum();
8510 assert_eq!(total_items, items_in_page);
8511 }
8512
8513 #[test]
8514 fn test_nested_detection_and_axes() {
8515 let rep = rep_bytes_from(&[5, 0, 4, 0, 3, 0]);
8517
8518 let (words, data_buf_size) = words_from(&[(2, 8), (2, 8), (0, 8)]);
8520 let index = build_chunk_index(&words, 10, 0, data_buf_size, Some(&rep), 1).unwrap();
8521 assert_eq!(index.row_mapping_debug(), "nested");
8522 assert_eq!(index.num_chunks(), 3);
8523 assert_eq!(index.first_row(0), 0);
8525 assert_eq!(index.rows_in_chunk(0), 5);
8526 assert_eq!(index.first_row(1), 5);
8527 assert_eq!(index.rows_in_chunk(1), 4);
8528 assert_eq!(index.first_row(2), 9);
8529 assert_eq!(index.rows_in_chunk(2), 3);
8530 assert_eq!(index.items_in_chunk(0), 4);
8532 assert_eq!(index.items_in_chunk(1), 4);
8533 assert_eq!(index.items_in_chunk(2), 2);
8534
8535 let (words_nu, dbs_nu) = words_from(&[(3, 8), (1, 8), (0, 8)]);
8537 let index_nu = build_chunk_index(&words_nu, 15, 0, dbs_nu, Some(&rep), 1).unwrap();
8538 assert_eq!(index_nu.row_mapping_debug(), "nested");
8539 assert_eq!(index_nu.items_in_chunk(0), 8);
8540 assert_eq!(index_nu.items_in_chunk(1), 2);
8541 assert_eq!(index_nu.items_in_chunk(2), 5);
8542 assert_eq!(index_nu.rows_in_chunk(0), 5);
8544 }
8545
8546 #[test]
8547 fn test_uniform_flat_matches_prefix_sum_flat() {
8548 let (words, data_buf_size) = words_from(&[(2, 8), (2, 8), (2, 8), (0, 8)]);
8550 let uniform = build_chunk_index(&words, 15, 0, data_buf_size, None, 0).unwrap();
8551 assert_eq!(uniform.row_mapping_debug(), "uniform_flat");
8552
8553 let byte_starts = PrefixSums::from_deltas([8u64, 8, 8, 8].into_iter(), 4, 32);
8555 let value_starts = PrefixSums::from_deltas([4u64, 4, 4, 3].into_iter(), 4, 15);
8556 let flat = MiniBlockChunkIndex::new(0, byte_starts, RowMapping::Flat { value_starts });
8557 assert_eq!(flat.row_mapping_debug(), "flat");
8558
8559 for i in 0..4 {
8561 assert_eq!(uniform.byte_range(i), flat.byte_range(i));
8562 assert_eq!(uniform.items_in_chunk(i), flat.items_in_chunk(i));
8563 }
8564
8565 let range_sets: Vec<Vec<std::ops::Range<u64>>> = vec![
8567 vec![0..15],
8568 vec![0..1],
8569 vec![7..8],
8570 vec![14..15],
8571 vec![3..10],
8572 vec![0..2, 5..6, 12..15],
8573 ];
8574 for ranges in &range_sets {
8575 let from_uniform = ChunkInstructions::schedule_instructions(&uniform, ranges);
8576 let from_flat = ChunkInstructions::schedule_instructions(&flat, ranges);
8577 assert_eq!(from_uniform, from_flat, "mismatch for ranges {ranges:?}");
8578 }
8579
8580 let full = ChunkInstructions::schedule_instructions(&uniform, &[0..15]);
8582 assert_eq!(full.len(), 4);
8583 for (i, inst) in full.iter().enumerate() {
8584 assert_eq!(inst.chunk_idx, i);
8585 assert_eq!(inst.preamble, PreambleAction::Absent);
8586 assert_eq!(inst.rows_to_skip, 0);
8587 assert!(!inst.take_trailer);
8588 }
8589 assert_eq!(full.iter().map(|i| i.rows_to_take).sum::<u64>(), 15);
8590 }
8591
8592 #[test]
8593 fn test_deep_size_per_variant_below_legacy() {
8594 const LEGACY_PER_CHUNK: usize = 48;
8597 let num_chunks = 3;
8598 let heap = |index: &MiniBlockChunkIndex| index.deep_size_of_children(&mut Context::new());
8599
8600 let (uniform_words, uniform_dbs) = words_from(&[(2, 8), (2, 8), (0, 8)]);
8601 let uniform = build_chunk_index(&uniform_words, 10, 0, uniform_dbs, None, 0).unwrap();
8602 assert_eq!(uniform.row_mapping_debug(), "uniform_flat");
8603 assert!(heap(&uniform) < LEGACY_PER_CHUNK * num_chunks);
8604
8605 let (flat_words, flat_dbs) = words_from(&[(3, 8), (1, 8), (0, 8)]);
8606 let flat = build_chunk_index(&flat_words, 11, 0, flat_dbs, None, 0).unwrap();
8607 assert_eq!(flat.row_mapping_debug(), "flat");
8608 assert!(heap(&flat) < LEGACY_PER_CHUNK * num_chunks);
8609 assert!(heap(&flat) > heap(&uniform));
8611
8612 let rep = rep_bytes_from(&[4, 0, 3, 0, 3, 0]);
8613 let (nested_words, nested_dbs) = words_from(&[(2, 8), (2, 8), (0, 8)]);
8614 let nested = build_chunk_index(&nested_words, 10, 0, nested_dbs, Some(&rep), 1).unwrap();
8615 assert_eq!(nested.row_mapping_debug(), "nested");
8616 assert!(heap(&nested) < LEGACY_PER_CHUNK * num_chunks);
8617 }
8618
8619 #[tokio::test]
8620 async fn test_fullzip_initialize_is_lazy() {
8621 use futures::{FutureExt, future::BoxFuture};
8622 use std::ops::Range;
8623 use std::sync::Mutex;
8624
8625 #[derive(Debug, Clone)]
8626 struct RecordingScheduler {
8627 data: bytes::Bytes,
8628 requests: Arc<Mutex<Vec<Vec<Range<u64>>>>>,
8629 }
8630
8631 impl RecordingScheduler {
8632 fn new(data: bytes::Bytes) -> Self {
8633 Self {
8634 data,
8635 requests: Arc::new(Mutex::new(Vec::new())),
8636 }
8637 }
8638
8639 fn requests(&self) -> Vec<Vec<Range<u64>>> {
8640 self.requests.lock().unwrap().clone()
8641 }
8642 }
8643
8644 impl crate::EncodingsIo for RecordingScheduler {
8645 fn submit_request(
8646 &self,
8647 ranges: Vec<Range<u64>>,
8648 _priority: u64,
8649 ) -> BoxFuture<'static, crate::Result<Vec<bytes::Bytes>>> {
8650 self.requests.lock().unwrap().push(ranges.clone());
8651 let data = ranges
8652 .into_iter()
8653 .map(|range| self.data.slice(range.start as usize..range.end as usize))
8654 .collect::<Vec<_>>();
8655 std::future::ready(Ok(data)).boxed()
8656 }
8657 }
8658
8659 #[derive(Debug)]
8660 struct TestFixedDecompressor;
8661
8662 impl FixedPerValueDecompressor for TestFixedDecompressor {
8663 fn decompress(
8664 &self,
8665 _data: FixedWidthDataBlock,
8666 _num_rows: u64,
8667 ) -> crate::Result<DataBlock> {
8668 unimplemented!("Test decompressor")
8669 }
8670
8671 fn bits_per_value(&self) -> u64 {
8672 32
8673 }
8674 }
8675
8676 let io = Arc::new(RecordingScheduler::new(bytes::Bytes::from(vec![
8677 0;
8678 16 * 1024
8679 ])));
8680 let mut scheduler = FullZipScheduler {
8681 data_buf_position: 0,
8682 data_buf_size: 4096,
8683 rep_index: Some(FullZipRepIndexDetails {
8684 buf_position: 1000,
8685 bytes_per_value: 4,
8686 }),
8687 priority: 0,
8688 rows_in_page: 100,
8689 bits_per_offset: 32,
8690 details: Arc::new(FullZipDecodeDetails {
8691 value_decompressor: PerValueDecompressor::Fixed(Arc::new(TestFixedDecompressor)),
8692 def_meaning: Arc::new([crate::repdef::DefinitionInterpretation::NullableItem]),
8693 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 1),
8694 max_rep: 0,
8695 max_visible_def: 0,
8696 }),
8697 cached_state: None,
8698 enable_cache: false,
8699 };
8700
8701 let io_dyn: Arc<dyn crate::EncodingsIo> = io.clone();
8702 let cached_data = scheduler.initialize(&io_dyn).await.unwrap();
8703
8704 assert!(
8705 cached_data
8706 .as_arc_any()
8707 .downcast_ref::<super::NoCachedPageData>()
8708 .is_some(),
8709 "FullZip initialize should not eagerly load repetition index data"
8710 );
8711 assert!(scheduler.cached_state.is_none());
8712 assert!(
8713 io.requests().is_empty(),
8714 "FullZip initialize should not issue any I/O"
8715 );
8716 }
8717
8718 #[tokio::test]
8719 async fn test_fullzip_read_source_slices_prefetched_page() {
8720 let page_start = 200_u64;
8721 let page_data = LanceBuffer::copy_slice(&[0, 1, 2, 3, 4, 5, 6, 7]);
8722 let source = FullZipReadSource::PrefetchedPage {
8723 base_offset: page_start,
8724 data: page_data,
8725 };
8726 let ranges = vec![
8727 page_start..(page_start + 3),
8728 (page_start + 4)..(page_start + 8),
8729 ];
8730 let mut data = source.fetch(&ranges, 0).await.unwrap();
8731 assert_eq!(data.pop_front().unwrap().as_ref(), &[0, 1, 2]);
8732 assert_eq!(data.pop_front().unwrap().as_ref(), &[4, 5, 6, 7]);
8733 }
8734
8735 #[tokio::test]
8736 async fn test_fullzip_initialize_caches_rep_index_when_enabled() {
8737 use futures::{FutureExt, future::BoxFuture};
8738 use std::ops::Range;
8739 use std::sync::Mutex;
8740
8741 #[derive(Debug, Clone)]
8742 struct RecordingScheduler {
8743 data: bytes::Bytes,
8744 requests: Arc<Mutex<Vec<Vec<Range<u64>>>>>,
8745 }
8746
8747 impl RecordingScheduler {
8748 fn new(data: bytes::Bytes) -> Self {
8749 Self {
8750 data,
8751 requests: Arc::new(Mutex::new(Vec::new())),
8752 }
8753 }
8754
8755 fn requests(&self) -> Vec<Vec<Range<u64>>> {
8756 self.requests.lock().unwrap().clone()
8757 }
8758 }
8759
8760 impl crate::EncodingsIo for RecordingScheduler {
8761 fn submit_request(
8762 &self,
8763 ranges: Vec<Range<u64>>,
8764 _priority: u64,
8765 ) -> BoxFuture<'static, crate::Result<Vec<bytes::Bytes>>> {
8766 self.requests.lock().unwrap().push(ranges.clone());
8767 let data = ranges
8768 .into_iter()
8769 .map(|range| self.data.slice(range.start as usize..range.end as usize))
8770 .collect::<Vec<_>>();
8771 std::future::ready(Ok(data)).boxed()
8772 }
8773 }
8774
8775 #[derive(Debug)]
8776 struct TestFixedDecompressor;
8777
8778 impl FixedPerValueDecompressor for TestFixedDecompressor {
8779 fn decompress(
8780 &self,
8781 _data: FixedWidthDataBlock,
8782 _num_rows: u64,
8783 ) -> crate::Result<DataBlock> {
8784 unimplemented!("Test decompressor")
8785 }
8786
8787 fn bits_per_value(&self) -> u64 {
8788 32
8789 }
8790 }
8791
8792 let rows_in_page = 100_u64;
8793 let bytes_per_value = 4_u64;
8794 let rep_start = 1000_u64;
8795 let rep_size = ((rows_in_page + 1) * bytes_per_value) as usize;
8796 let mut data = vec![0_u8; 16 * 1024];
8797 data[rep_start as usize..rep_start as usize + rep_size].fill(7);
8798 let io = Arc::new(RecordingScheduler::new(bytes::Bytes::from(data)));
8799
8800 let mut scheduler = FullZipScheduler {
8801 data_buf_position: 0,
8802 data_buf_size: 4096,
8803 rep_index: Some(FullZipRepIndexDetails {
8804 buf_position: rep_start,
8805 bytes_per_value,
8806 }),
8807 priority: 0,
8808 rows_in_page,
8809 bits_per_offset: 32,
8810 details: Arc::new(FullZipDecodeDetails {
8811 value_decompressor: PerValueDecompressor::Fixed(Arc::new(TestFixedDecompressor)),
8812 def_meaning: Arc::new([crate::repdef::DefinitionInterpretation::NullableItem]),
8813 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 1),
8814 max_rep: 0,
8815 max_visible_def: 0,
8816 }),
8817 cached_state: None,
8818 enable_cache: true,
8819 };
8820
8821 let io_dyn: Arc<dyn crate::EncodingsIo> = io.clone();
8822 let cached_data = scheduler.initialize(&io_dyn).await.unwrap();
8823 assert!(
8824 cached_data
8825 .as_arc_any()
8826 .downcast_ref::<FullZipCacheableState>()
8827 .is_some()
8828 );
8829 assert!(scheduler.cached_state.is_some());
8830 assert_eq!(
8831 io.requests(),
8832 vec![vec![
8833 rep_start..(rep_start + (rows_in_page + 1) * bytes_per_value)
8834 ]]
8835 );
8836 }
8837
8838 #[tokio::test]
8839 async fn test_fullzip_full_page_bypasses_rep_index_io() {
8840 use futures::{FutureExt, future::BoxFuture};
8841 use std::ops::Range;
8842 use std::sync::Mutex;
8843
8844 #[derive(Debug, Clone)]
8845 struct RecordingScheduler {
8846 data: bytes::Bytes,
8847 requests: Arc<Mutex<Vec<Vec<Range<u64>>>>>,
8848 }
8849
8850 impl RecordingScheduler {
8851 fn new(data: bytes::Bytes) -> Self {
8852 Self {
8853 data,
8854 requests: Arc::new(Mutex::new(Vec::new())),
8855 }
8856 }
8857
8858 fn requests(&self) -> Vec<Vec<Range<u64>>> {
8859 self.requests.lock().unwrap().clone()
8860 }
8861 }
8862
8863 impl crate::EncodingsIo for RecordingScheduler {
8864 fn submit_request(
8865 &self,
8866 ranges: Vec<Range<u64>>,
8867 _priority: u64,
8868 ) -> BoxFuture<'static, crate::Result<Vec<bytes::Bytes>>> {
8869 self.requests.lock().unwrap().push(ranges.clone());
8870 let data = ranges
8871 .into_iter()
8872 .map(|range| self.data.slice(range.start as usize..range.end as usize))
8873 .collect::<Vec<_>>();
8874 std::future::ready(Ok(data)).boxed()
8875 }
8876 }
8877
8878 #[derive(Debug)]
8879 struct TestFixedDecompressor;
8880
8881 impl FixedPerValueDecompressor for TestFixedDecompressor {
8882 fn decompress(
8883 &self,
8884 _data: FixedWidthDataBlock,
8885 _num_rows: u64,
8886 ) -> crate::Result<DataBlock> {
8887 unimplemented!("Test decompressor")
8888 }
8889
8890 fn bits_per_value(&self) -> u64 {
8891 32
8892 }
8893 }
8894
8895 let rows_in_page = 100_u64;
8896 let data_start = 256_u64;
8897 let data_size = 500_u64;
8898 let rep_start = 4096_u64;
8899 let bytes_per_value = 4_u64;
8900
8901 let mut bytes = vec![0_u8; 16 * 1024];
8902 for i in 0..=rows_in_page {
8903 let offset = (i * 5) as u32;
8904 let pos = rep_start as usize + (i * bytes_per_value) as usize;
8905 bytes[pos..pos + 4].copy_from_slice(&offset.to_le_bytes());
8906 }
8907 let io = Arc::new(RecordingScheduler::new(bytes::Bytes::from(bytes)));
8908
8909 let scheduler = FullZipScheduler {
8910 data_buf_position: data_start,
8911 data_buf_size: data_size,
8912 rep_index: Some(FullZipRepIndexDetails {
8913 buf_position: rep_start,
8914 bytes_per_value,
8915 }),
8916 priority: 0,
8917 rows_in_page,
8918 bits_per_offset: 32,
8919 details: Arc::new(FullZipDecodeDetails {
8920 value_decompressor: PerValueDecompressor::Fixed(Arc::new(TestFixedDecompressor)),
8921 def_meaning: Arc::new([crate::repdef::DefinitionInterpretation::NullableItem]),
8922 ctrl_word_parser: crate::repdef::ControlWordParser::new(0, 1),
8923 max_rep: 0,
8924 max_visible_def: 0,
8925 }),
8926 cached_state: None,
8927 enable_cache: false,
8928 };
8929
8930 let io_dyn: Arc<dyn crate::EncodingsIo> = io.clone();
8931 let tasks = scheduler
8932 .schedule_ranges_rep(
8933 &[0..rows_in_page],
8934 &io_dyn,
8935 FullZipRepIndexDetails {
8936 buf_position: rep_start,
8937 bytes_per_value,
8938 },
8939 )
8940 .unwrap();
8941
8942 let requests = io.requests();
8943 assert_eq!(requests.len(), 1);
8944 assert_eq!(requests[0], vec![data_start..(data_start + data_size)]);
8945
8946 let _ = tasks.into_iter().next().unwrap().decoder_fut.await.unwrap();
8947 let requests_after_await = io.requests();
8948 assert_eq!(
8949 requests_after_await.len(),
8950 1,
8951 "full page path should not issue rep-index I/O"
8952 );
8953 }
8954
8955 #[tokio::test]
8957 async fn test_fuzz_issue_4492_empty_rep_values() {
8958 use lance_datagen::{RowCount, Seed, array, gen_batch};
8959
8960 let seed = 1823859942947654717u64;
8961 let num_rows = 2741usize;
8962
8963 let batch_gen = gen_batch().with_seed(Seed::from(seed));
8965 let base_generator = array::rand_type(&DataType::FixedSizeBinary(32));
8966 let list_generator = array::rand_list_any(base_generator, false);
8967
8968 let batch = batch_gen
8969 .anon_col(list_generator)
8970 .into_batch_rows(RowCount::from(num_rows as u64))
8971 .unwrap();
8972
8973 let list_array = batch.column(0).clone();
8974
8975 let mut metadata = HashMap::new();
8977 metadata.insert(
8978 STRUCTURAL_ENCODING_META_KEY.to_string(),
8979 STRUCTURAL_ENCODING_MINIBLOCK.to_string(),
8980 );
8981
8982 let test_cases = TestCases::default()
8983 .with_structural_encodings()
8984 .with_batch_size(100)
8985 .with_range(0..num_rows.min(500) as u64)
8986 .with_indices(vec![0, num_rows as u64 / 2, (num_rows - 1) as u64]);
8987
8988 check_round_trip_encoding_of_data(vec![list_array], &test_cases, metadata).await
8989 }
8990
8991 async fn test_minichunk_size_helper(
8992 string_data: Vec<Option<String>>,
8993 minichunk_size: u64,
8994 encodings: &[TestEncoding],
8995 ) {
8996 use crate::constants::MINICHUNK_SIZE_META_KEY;
8997 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
8998 use arrow_array::{ArrayRef, StringArray};
8999 use std::sync::Arc;
9000
9001 let string_array: ArrayRef = Arc::new(StringArray::from(string_data));
9002
9003 let mut metadata = HashMap::new();
9004 metadata.insert(
9005 MINICHUNK_SIZE_META_KEY.to_string(),
9006 minichunk_size.to_string(),
9007 );
9008 metadata.insert(
9009 STRUCTURAL_ENCODING_META_KEY.to_string(),
9010 STRUCTURAL_ENCODING_MINIBLOCK.to_string(),
9011 );
9012
9013 let test_cases = TestCases::default()
9014 .with_encodings(encodings.iter().copied())
9015 .with_batch_size(1000);
9016
9017 check_round_trip_encoding_of_data(vec![string_array], &test_cases, metadata).await;
9018 }
9019
9020 #[tokio::test]
9021 async fn test_minichunk_size_roundtrip() {
9022 let mut string_data = Vec::new();
9024 for i in 0..100 {
9025 string_data.push(Some(format!("test_string_{}", i).repeat(50)));
9026 }
9027 test_minichunk_size_helper(
9029 string_data,
9030 64,
9031 &[
9032 TestEncoding::StructuralU16,
9033 TestEncoding::StructuralU32,
9034 TestEncoding::StructuralSparse,
9035 ],
9036 )
9037 .await;
9038 }
9039
9040 #[tokio::test]
9041 async fn test_minichunk_size_128kb_v2_2() {
9042 let mut string_data = Vec::new();
9044 for i in 0..10000 {
9046 string_data.push(Some(format!("test_string_{}", i).repeat(50)));
9047 }
9048 test_minichunk_size_helper(
9049 string_data,
9050 128 * 1024,
9051 &[TestEncoding::StructuralU32, TestEncoding::StructuralSparse],
9052 )
9053 .await;
9054 }
9055
9056 #[tokio::test]
9057 async fn test_binary_large_minichunk_size_over_max_miniblock_values() {
9058 let mut string_data = Vec::new();
9059 for i in 0..10000 {
9061 string_data.push(Some(format!("t_{}", i)));
9062 }
9063 test_minichunk_size_helper(
9064 string_data,
9065 128 * 1024,
9066 &[TestEncoding::StructuralU32, TestEncoding::StructuralSparse],
9067 )
9068 .await;
9069 }
9070
9071 #[tokio::test]
9072 async fn test_large_dictionary_general_compression() {
9073 use arrow_array::{ArrayRef, StringArray};
9074 use std::collections::HashMap;
9075 use std::sync::Arc;
9076
9077 let unique_values: Vec<String> = (0..100)
9080 .map(|i| format!("value_{:04}_{}", i, "x".repeat(500)))
9081 .collect();
9082
9083 let repeated_strings: Vec<_> = unique_values
9085 .iter()
9086 .cycle()
9087 .take(10_000)
9088 .map(|s| Some(s.as_str()))
9089 .collect();
9090
9091 let string_array = Arc::new(StringArray::from(repeated_strings)) as ArrayRef;
9092
9093 let test_cases = TestCases::default()
9095 .with_u32_structural_encodings()
9096 .with_verify_encoding(Arc::new(|cols: &[crate::encoder::EncodedColumn], _| {
9097 assert_eq!(cols.len(), 1);
9098 let col = &cols[0];
9099
9100 if let Some(PageEncoding::Structural(page_layout)) =
9102 &col.final_pages.first().map(|p| &p.description)
9103 && let Some(pb21::page_layout::Layout::MiniBlockLayout(mini_block)) =
9104 &page_layout.layout
9105 && let Some(dictionary_encoding) = &mini_block.dictionary
9106 {
9107 match dictionary_encoding.compression.as_ref() {
9108 Some(Compression::General(general)) => {
9109 let compression = general.compression.as_ref().unwrap();
9111 assert!(
9112 compression.scheme()
9113 == pb21::CompressionScheme::CompressionAlgorithmLz4
9114 || compression.scheme()
9115 == pb21::CompressionScheme::CompressionAlgorithmZstd,
9116 "Expected LZ4 or Zstd compression for large dictionary"
9117 );
9118 }
9119 _ => panic!("Expected General compression for large dictionary"),
9120 }
9121 }
9122 }));
9123
9124 check_round_trip_encoding_of_data(vec![string_array], &test_cases, HashMap::new()).await;
9125 }
9126
9127 fn dictionary_encoding_from_page(
9128 page: &crate::encoder::EncodedPage,
9129 ) -> &crate::format::pb21::CompressiveEncoding {
9130 let PageEncoding::Structural(layout) = &page.description else {
9131 panic!("Expected structural page encoding");
9132 };
9133 let pb21::page_layout::Layout::MiniBlockLayout(layout) = layout.layout.as_ref().unwrap()
9134 else {
9135 panic!("Expected mini-block layout");
9136 };
9137 layout
9138 .dictionary
9139 .as_ref()
9140 .unwrap_or_else(|| panic!("Expected dictionary encoding"))
9141 }
9142
9143 async fn encode_variable_dict_page(
9144 metadata: HashMap<String, String>,
9145 ) -> crate::encoder::EncodedPage {
9146 use arrow_array::types::Int32Type;
9147 use arrow_array::{ArrayRef, DictionaryArray, Int32Array, StringArray};
9148
9149 let values = Arc::new(StringArray::from(
9150 (0..128)
9151 .map(|i| format!("value_{i:04}_{}", "x".repeat(256)))
9152 .collect::<Vec<_>>(),
9153 )) as ArrayRef;
9154 let keys = Int32Array::from_iter_values((0..20_000).map(|i| i % 128));
9155 let dict_array =
9156 Arc::new(DictionaryArray::<Int32Type>::try_new(keys, values).unwrap()) as ArrayRef;
9157
9158 let field = arrow_schema::Field::new(
9159 "dict_col",
9160 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
9161 false,
9162 )
9163 .with_metadata(metadata);
9164
9165 encode_first_page(field, dict_array, TestEncoding::StructuralU32).await
9166 }
9167
9168 async fn encode_auto_fixed_dict_page(
9169 metadata: HashMap<String, String>,
9170 ) -> crate::encoder::EncodedPage {
9171 use arrow_array::{ArrayRef, Decimal128Array};
9172
9173 let values = (0..20_000)
9175 .map(|i| match i % 3 {
9176 0 => 10_i128,
9177 1 => 20_i128,
9178 _ => 30_i128,
9179 })
9180 .collect::<Vec<_>>();
9181 let decimal = Decimal128Array::from_iter_values(values)
9182 .with_precision_and_scale(38, 0)
9183 .unwrap();
9184 let decimal = Arc::new(decimal) as ArrayRef;
9185
9186 let mut field_metadata = metadata;
9187 field_metadata.insert(
9189 "lance-encoding:dict-size-ratio".to_string(),
9190 "0.99".to_string(),
9191 );
9192 let field = arrow_schema::Field::new("fixed_col", DataType::Decimal128(38, 0), false)
9193 .with_metadata(field_metadata);
9194
9195 encode_first_page(field, decimal, TestEncoding::StructuralU32).await
9196 }
9197
9198 #[tokio::test]
9199 async fn test_dict_values_general_compression_default_lz4_for_variable_dict_values() {
9200 let page = encode_variable_dict_page(HashMap::new()).await;
9201 let dictionary_encoding = dictionary_encoding_from_page(&page);
9202 let Some(Compression::General(general)) = dictionary_encoding.compression.as_ref() else {
9203 panic!("Expected General compression for dictionary values");
9204 };
9205 let compression = general.compression.as_ref().unwrap();
9206 assert_eq!(
9207 compression.scheme(),
9208 pb21::CompressionScheme::CompressionAlgorithmLz4
9209 );
9210 }
9211
9212 #[tokio::test]
9213 async fn test_dict_values_general_compression_default_lz4_for_fixed_dict_values() {
9214 let page = encode_auto_fixed_dict_page(HashMap::new()).await;
9215 let dictionary_encoding = dictionary_encoding_from_page(&page);
9216 let Some(Compression::General(general)) = dictionary_encoding.compression.as_ref() else {
9217 panic!("Expected General compression for dictionary values");
9218 };
9219 let compression = general.compression.as_ref().unwrap();
9220 assert_eq!(
9221 compression.scheme(),
9222 pb21::CompressionScheme::CompressionAlgorithmLz4
9223 );
9224 }
9225
9226 #[tokio::test]
9227 async fn test_dict_values_general_compression_zstd() {
9228 let mut metadata = HashMap::new();
9229 metadata.insert(
9230 DICT_VALUES_COMPRESSION_META_KEY.to_string(),
9231 "zstd".to_string(),
9232 );
9233 let page = encode_variable_dict_page(metadata).await;
9234 let dictionary_encoding = dictionary_encoding_from_page(&page);
9235 let Some(Compression::General(general)) = dictionary_encoding.compression.as_ref() else {
9236 panic!("Expected General compression for dictionary values");
9237 };
9238 let compression = general.compression.as_ref().unwrap();
9239 assert_eq!(
9240 compression.scheme(),
9241 pb21::CompressionScheme::CompressionAlgorithmZstd
9242 );
9243 }
9244
9245 #[tokio::test]
9246 async fn test_dict_values_general_compression_none() {
9247 let mut metadata = HashMap::new();
9248 metadata.insert(
9249 DICT_VALUES_COMPRESSION_META_KEY.to_string(),
9250 "none".to_string(),
9251 );
9252 let page = encode_variable_dict_page(metadata).await;
9253 let dictionary_encoding = dictionary_encoding_from_page(&page);
9254 assert!(
9255 !matches!(
9256 dictionary_encoding.compression.as_ref(),
9257 Some(Compression::General(_))
9258 ),
9259 "Expected dictionary values to avoid General compression"
9260 );
9261 }
9262
9263 #[test]
9264 fn test_resolve_dict_values_compression_metadata_defaults_to_lz4() {
9265 let metadata = PrimitiveStructuralEncoder::resolve_dict_values_compression_metadata(
9266 &HashMap::new(),
9267 None,
9268 None,
9269 );
9270 assert_eq!(metadata.get(COMPRESSION_META_KEY), Some(&"lz4".to_string()),);
9271 assert!(!metadata.contains_key(COMPRESSION_LEVEL_META_KEY));
9272 }
9273
9274 #[test]
9275 fn test_resolve_dict_values_compression_metadata_metadata_overrides_env() {
9276 let field_metadata = HashMap::from([
9277 (
9278 DICT_VALUES_COMPRESSION_META_KEY.to_string(),
9279 "none".to_string(),
9280 ),
9281 (
9282 DICT_VALUES_COMPRESSION_LEVEL_META_KEY.to_string(),
9283 "7".to_string(),
9284 ),
9285 ]);
9286 let metadata = PrimitiveStructuralEncoder::resolve_dict_values_compression_metadata(
9287 &field_metadata,
9288 Some("zstd".to_string()),
9289 Some("3".to_string()),
9290 );
9291 assert_eq!(
9292 metadata.get(COMPRESSION_META_KEY),
9293 Some(&"none".to_string()),
9294 );
9295 assert_eq!(
9296 metadata.get(COMPRESSION_LEVEL_META_KEY),
9297 Some(&"7".to_string()),
9298 );
9299 }
9300
9301 #[test]
9302 fn test_resolve_dict_values_compression_metadata_env_fallback() {
9303 let metadata = PrimitiveStructuralEncoder::resolve_dict_values_compression_metadata(
9304 &HashMap::new(),
9305 Some("zstd".to_string()),
9306 Some("9".to_string()),
9307 );
9308 assert_eq!(
9309 metadata.get(COMPRESSION_META_KEY),
9310 Some(&"zstd".to_string()),
9311 );
9312 assert_eq!(
9313 metadata.get(COMPRESSION_LEVEL_META_KEY),
9314 Some(&"9".to_string()),
9315 );
9316 }
9317
9318 #[tokio::test]
9319 async fn test_dictionary_encode_int64() {
9320 use crate::constants::{DICT_SIZE_RATIO_META_KEY, STRUCTURAL_ENCODING_META_KEY};
9321 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
9322 use arrow_array::{ArrayRef, Int64Array};
9323 use std::collections::HashMap;
9324 use std::sync::Arc;
9325
9326 let values = (0..1000)
9328 .map(|i| match i % 3 {
9329 0 => 10i64,
9330 1 => 20i64,
9331 _ => 30i64,
9332 })
9333 .collect::<Vec<_>>();
9334 let array = Arc::new(Int64Array::from(values)) as ArrayRef;
9335
9336 let mut metadata = HashMap::new();
9337 metadata.insert(
9338 STRUCTURAL_ENCODING_META_KEY.to_string(),
9339 STRUCTURAL_ENCODING_MINIBLOCK.to_string(),
9340 );
9341 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.99".to_string());
9342
9343 let test_cases = TestCases::default()
9344 .with_u32_structural_encodings()
9345 .with_batch_size(1000)
9346 .with_range(0..1000)
9347 .with_indices(vec![0, 1, 10, 999])
9348 .with_expected_encoding("dictionary");
9349
9350 check_round_trip_encoding_of_data(vec![array], &test_cases, metadata).await;
9351 }
9352
9353 #[tokio::test]
9354 async fn test_dictionary_encode_float64() {
9355 use crate::constants::{DICT_SIZE_RATIO_META_KEY, STRUCTURAL_ENCODING_META_KEY};
9356 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
9357 use arrow_array::{ArrayRef, Float64Array};
9358 use std::collections::HashMap;
9359 use std::sync::Arc;
9360
9361 let values = (0..1000)
9363 .map(|i| match i % 3 {
9364 0 => 0.1f64,
9365 1 => 0.2f64,
9366 _ => 0.3f64,
9367 })
9368 .collect::<Vec<_>>();
9369 let array = Arc::new(Float64Array::from(values)) as ArrayRef;
9370
9371 let mut metadata = HashMap::new();
9372 metadata.insert(
9373 STRUCTURAL_ENCODING_META_KEY.to_string(),
9374 STRUCTURAL_ENCODING_MINIBLOCK.to_string(),
9375 );
9376 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.99".to_string());
9377
9378 let test_cases = TestCases::default()
9379 .with_u32_structural_encodings()
9380 .with_batch_size(1000)
9381 .with_range(0..1000)
9382 .with_indices(vec![0, 1, 10, 999])
9383 .with_expected_encoding("dictionary");
9384
9385 check_round_trip_encoding_of_data(vec![array], &test_cases, metadata).await;
9386 }
9387
9388 #[test]
9389 fn test_miniblock_dictionary_out_of_line_bitpacking_decode() {
9390 let rows = 10_000;
9391 let unique_values = 2_000;
9392
9393 let dictionary_encoding =
9394 ProtobufUtils21::out_of_line_bitpacking(64, ProtobufUtils21::flat(11, None));
9395 let layout = pb21::MiniBlockLayout {
9396 rep_compression: None,
9397 def_compression: None,
9398 value_compression: Some(ProtobufUtils21::flat(64, None)),
9399 dictionary: Some(dictionary_encoding),
9400 num_dictionary_items: unique_values,
9401 layers: vec![pb21::RepDefLayer::RepdefAllValidItem as i32],
9402 num_buffers: 1,
9403 repetition_index_depth: 0,
9404 num_items: rows,
9405 has_large_chunk: false,
9406 };
9407
9408 let buffer_offsets_and_sizes = vec![(0, 0), (0, 0), (0, 0)];
9409 let scheduler = super::MiniBlockScheduler::try_new(
9410 &buffer_offsets_and_sizes,
9411 0,
9412 rows,
9413 &layout,
9414 &DefaultDecompressionStrategy::default(),
9415 )
9416 .unwrap();
9417
9418 let dictionary = scheduler.dictionary.unwrap();
9419 assert_eq!(dictionary.num_dictionary_items, unique_values);
9420 assert_eq!(
9421 dictionary.dictionary_data_alignment,
9422 crate::encoder::MIN_PAGE_BUFFER_ALIGNMENT
9423 );
9424 }
9425
9426 fn create_test_fixed_data_block(
9428 num_values: u64,
9429 cardinality: u64,
9430 bits_per_value: u64,
9431 ) -> DataBlock {
9432 assert!(cardinality > 0);
9433 assert!(cardinality <= num_values);
9434 let block_info = BlockInfo::default();
9435
9436 assert_eq!(bits_per_value % 8, 0);
9437 let data = match bits_per_value {
9438 32 => {
9439 let values = (0..num_values)
9440 .map(|i| (i % cardinality) as u32)
9441 .collect::<Vec<_>>();
9442 crate::buffer::LanceBuffer::reinterpret_vec(values)
9443 }
9444 64 => {
9445 let values = (0..num_values).map(|i| i % cardinality).collect::<Vec<_>>();
9446 crate::buffer::LanceBuffer::reinterpret_vec(values)
9447 }
9448 128 => {
9449 let values = (0..num_values)
9450 .map(|i| (i % cardinality) as u128)
9451 .collect::<Vec<_>>();
9452 crate::buffer::LanceBuffer::reinterpret_vec(values)
9453 }
9454 _ => unreachable!(),
9455 };
9456 DataBlock::FixedWidth(FixedWidthDataBlock {
9457 bits_per_value,
9458 data,
9459 num_values,
9460 block_info,
9461 })
9462 }
9463
9464 fn create_test_variable_width_block(num_values: u64, cardinality: u64) -> DataBlock {
9466 use arrow_array::StringArray;
9467
9468 assert!(cardinality <= num_values && cardinality > 0);
9469
9470 let mut values = Vec::with_capacity(num_values as usize);
9471 for i in 0..num_values {
9472 values.push(format!("value_{:016}", i % cardinality));
9473 }
9474
9475 let array = StringArray::from(values);
9476 DataBlock::from_array(Arc::new(array) as ArrayRef)
9477 }
9478
9479 fn create_sorted_string_array(num_values: u64, cardinality: u64) -> ArrayRef {
9480 use arrow_array::StringArray;
9481
9482 assert!(cardinality <= num_values && cardinality > 0);
9483
9484 let mut values = Vec::with_capacity(num_values as usize);
9485 for i in 0..num_values {
9486 let value_idx = i * cardinality / num_values;
9487 values.push(format!("value_{:016}", value_idx));
9488 }
9489
9490 Arc::new(StringArray::from(values)) as ArrayRef
9491 }
9492
9493 fn create_sorted_variable_width_block(num_values: u64, cardinality: u64) -> DataBlock {
9494 DataBlock::from_array(create_sorted_string_array(num_values, cardinality))
9495 }
9496
9497 #[test]
9498 fn test_should_dictionary_encode() {
9499 use crate::constants::DICT_SIZE_RATIO_META_KEY;
9500 use lance_core::datatypes::Field as LanceField;
9501
9502 let block = create_test_variable_width_block(1000, 10);
9504
9505 let mut metadata = HashMap::new();
9506 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
9507 let arrow_field =
9508 arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
9509 let field = LanceField::try_from(&arrow_field).unwrap();
9510
9511 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
9512 &block,
9513 &field,
9514 FixedWidthDictionaryEncoding::Exclude64Bit,
9515 );
9516
9517 assert!(
9518 result.is_some(),
9519 "Should use dictionary encode based on size"
9520 );
9521 }
9522
9523 #[test]
9524 fn test_block_sampling_detects_low_cardinality_in_short_sorted_runs() {
9525 let sample_count: usize = 4096;
9526 let num_values: u64 = 200_000;
9527 let cardinality: u64 = 8_000;
9528 let run_length = num_values / cardinality;
9529 let stride = num_values as usize / sample_count;
9530 assert!(
9531 stride > run_length as usize,
9532 "test must construct the stride > run_length case"
9533 );
9534
9535 let block = create_sorted_variable_width_block(num_values, cardinality);
9536 let sample_unique_ratio =
9537 PrimitiveStructuralEncoder::sample_unique_ratio(&block, sample_count).unwrap();
9538
9539 assert!(
9540 sample_unique_ratio.is_some_and(|ratio| ratio < 0.98),
9541 "sorted low-cardinality data must not be classified as near-unique"
9542 );
9543 }
9544
9545 #[test]
9546 fn test_should_dictionary_encode_sorted_low_cardinality() {
9547 use crate::constants::DICT_SIZE_RATIO_META_KEY;
9548 use lance_core::datatypes::Field as LanceField;
9549
9550 let block = create_sorted_variable_width_block(200_000, 8_000);
9551
9552 let mut metadata = HashMap::new();
9553 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
9554 let arrow_field =
9555 arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
9556 let field = LanceField::try_from(&arrow_field).unwrap();
9557
9558 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
9559 &block,
9560 &field,
9561 FixedWidthDictionaryEncoding::Include64Bit,
9562 );
9563
9564 assert!(
9565 result.is_some(),
9566 "sorted low-cardinality data should reach dictionary encoding"
9567 );
9568 }
9569
9570 #[test]
9571 fn test_should_not_dictionary_encode_sorted_high_cardinality_short_runs() {
9572 use crate::constants::DICT_SIZE_RATIO_META_KEY;
9573 use lance_core::datatypes::Field as LanceField;
9574
9575 let num_values = 200_002;
9576 let cardinality = 100_001;
9577 let block = create_sorted_variable_width_block(num_values, cardinality);
9578
9579 let mut metadata = HashMap::new();
9580 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
9581 let arrow_field =
9582 arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
9583 let field = LanceField::try_from(&arrow_field).unwrap();
9584
9585 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
9586 &block,
9587 &field,
9588 FixedWidthDictionaryEncoding::Include64Bit,
9589 );
9590
9591 assert!(
9592 result.is_none(),
9593 "sorted high-cardinality short runs should not trigger a full dictionary probe"
9594 );
9595 }
9596
9597 #[tokio::test]
9598 async fn test_encode_sorted_low_cardinality_uses_dictionary_layout() {
9599 use crate::constants::DICT_SIZE_RATIO_META_KEY;
9600
9601 let mut metadata = HashMap::new();
9602 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
9603 let field = arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
9604 let array = create_sorted_string_array(200_000, 8_000);
9605
9606 let page = encode_first_page(field, array, TestEncoding::StructuralU32).await;
9607 let _ = dictionary_encoding_from_page(&page);
9608 }
9609
9610 #[test]
9611 fn test_should_not_dictionary_encode_unsupported_bits() {
9612 use crate::constants::DICT_SIZE_RATIO_META_KEY;
9613 use lance_core::datatypes::Field as LanceField;
9614
9615 let block = create_test_fixed_data_block(1000, 1000, 32);
9616
9617 let mut metadata = HashMap::new();
9618 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.8".to_string());
9619 let arrow_field =
9620 arrow_schema::Field::new("test", DataType::Int32, false).with_metadata(metadata);
9621 let field = LanceField::try_from(&arrow_field).unwrap();
9622
9623 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
9624 &block,
9625 &field,
9626 FixedWidthDictionaryEncoding::Exclude64Bit,
9627 );
9628
9629 assert!(
9630 result.is_none(),
9631 "Should not use dictionary encode for unsupported bit width"
9632 );
9633 }
9634
9635 #[test]
9636 fn test_should_not_dictionary_encode_near_unique_sample() {
9637 use crate::constants::DICT_SIZE_RATIO_META_KEY;
9638 use lance_core::datatypes::Field as LanceField;
9639
9640 let num_values = 5000;
9641 let block = create_test_variable_width_block(num_values, num_values);
9642
9643 let mut metadata = HashMap::new();
9644 metadata.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "1.0".to_string());
9645 let arrow_field =
9646 arrow_schema::Field::new("test", DataType::Utf8, false).with_metadata(metadata);
9647 let field = LanceField::try_from(&arrow_field).unwrap();
9648
9649 let result = PrimitiveStructuralEncoder::should_dictionary_encode(
9650 &block,
9651 &field,
9652 FixedWidthDictionaryEncoding::Exclude64Bit,
9653 );
9654
9655 assert!(
9656 result.is_none(),
9657 "Should not probe dictionary encoding for near-unique data"
9658 );
9659 }
9660
9661 #[test]
9662 fn test_v2_1_miniblock_serializes_log_num_values_15() {
9663 let miniblocks = MiniBlockCompressed {
9664 data: vec![LanceBuffer::from(vec![1_u8; 16])],
9665 chunks: vec![
9666 MiniBlockChunk {
9667 buffer_sizes: vec![8],
9668 log_num_values: 15,
9669 },
9670 MiniBlockChunk {
9671 buffer_sizes: vec![8],
9672 log_num_values: 0,
9673 },
9674 ],
9675 num_values: 32_769,
9676 };
9677
9678 let serialized = PrimitiveStructuralEncoder::serialize_miniblocks(
9679 miniblocks,
9680 None,
9681 None,
9682 MiniblockChunkSize::U16,
9683 )
9684 .unwrap();
9685
9686 let chunk_metadata = serialized.metadata.borrow_to_typed_slice::<u16>();
9687 assert_eq!(chunk_metadata.len(), 2);
9688 assert_eq!(
9689 chunk_metadata[0] & 0x0F,
9690 15,
9691 "V2.1 metadata should use all 4 bits for log_num_values"
9692 );
9693 }
9694
9695 async fn encode_first_page(
9696 field: arrow_schema::Field,
9697 array: ArrayRef,
9698 version: TestEncoding,
9699 ) -> crate::encoder::EncodedPage {
9700 use crate::repdef::RepDefBuilder;
9701 use crate::{
9702 encoder::{
9703 ColumnIndexSequence, EncodingOptions, MIN_PAGE_BUFFER_ALIGNMENT, OutOfLineBuffers,
9704 },
9705 testing::{create_test_field_encoder, test_encoding_strategy},
9706 };
9707
9708 let lance_field = lance_core::datatypes::Field::try_from(&field).unwrap();
9709 let encoding_strategy = test_encoding_strategy(version);
9710 let mut column_index_seq = ColumnIndexSequence::default();
9711 let encoding_options = EncodingOptions {
9712 cache_bytes_per_column: 1,
9713 max_page_bytes: 32 * 1024 * 1024,
9714 keep_original_array: true,
9715 buffer_alignment: MIN_PAGE_BUFFER_ALIGNMENT,
9716 };
9717
9718 let mut encoder = create_test_field_encoder(
9719 encoding_strategy.as_ref(),
9720 &lance_field,
9721 &mut column_index_seq,
9722 &encoding_options,
9723 )
9724 .unwrap();
9725
9726 let mut external_buffers = OutOfLineBuffers::new(0, MIN_PAGE_BUFFER_ALIGNMENT);
9727 let repdef = RepDefBuilder::default();
9728 let num_rows = array.len() as u64;
9729 let mut pages = Vec::new();
9730 for task in encoder
9731 .maybe_encode(array, &mut external_buffers, repdef, 0, num_rows)
9732 .unwrap()
9733 {
9734 pages.push(task.await.unwrap());
9735 }
9736 for task in encoder.flush(&mut external_buffers).unwrap() {
9737 pages.push(task.await.unwrap());
9738 }
9739 pages.into_iter().next().unwrap()
9740 }
9741
9742 #[tokio::test]
9743 async fn test_constant_layout_out_of_line_fixed_size_binary_v2_2() {
9744 use crate::format::pb21::page_layout::Layout;
9745
9746 let val = vec![0xABu8; 33];
9747 let arr: ArrayRef = Arc::new(
9748 arrow_array::FixedSizeBinaryArray::try_from_sparse_iter_with_size(
9749 std::iter::repeat_n(Some(val.as_slice()), 256),
9750 33,
9751 )
9752 .unwrap(),
9753 );
9754 let field = arrow_schema::Field::new("c", DataType::FixedSizeBinary(33), true);
9755 let page = encode_first_page(field, arr.clone(), TestEncoding::StructuralU32).await;
9756
9757 let PageEncoding::Structural(layout) = &page.description else {
9758 panic!("Expected structural encoding");
9759 };
9760 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
9761 panic!("Expected constant layout in slot 2");
9762 };
9763 assert!(layout.inline_value.is_none());
9764 assert_eq!(page.data.len(), 1);
9765
9766 let test_cases = TestCases::default()
9767 .with_encoding(TestEncoding::StructuralU32)
9768 .with_page_sizes(vec![4096]);
9769 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
9770 }
9771
9772 #[tokio::test]
9773 async fn test_constant_layout_out_of_line_utf8_v2_2() {
9774 use crate::format::pb21::page_layout::Layout;
9775
9776 let arr: ArrayRef = Arc::new(arrow_array::StringArray::from_iter_values(
9777 std::iter::repeat_n("hello", 512),
9778 ));
9779 let field = arrow_schema::Field::new("c", DataType::Utf8, true);
9780 let page = encode_first_page(field, arr.clone(), TestEncoding::StructuralU32).await;
9781
9782 let PageEncoding::Structural(layout) = &page.description else {
9783 panic!("Expected structural encoding");
9784 };
9785 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
9786 panic!("Expected constant layout in slot 2");
9787 };
9788 assert!(layout.inline_value.is_none());
9789 assert_eq!(page.data.len(), 1);
9790
9791 let test_cases = TestCases::default()
9792 .with_encoding(TestEncoding::StructuralU32)
9793 .with_page_sizes(vec![4096]);
9794 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
9795 }
9796
9797 #[tokio::test]
9798 async fn test_constant_layout_nullable_item_v2_2() {
9799 use crate::format::pb21::page_layout::Layout;
9800
9801 let arr: ArrayRef = Arc::new(arrow_array::Int32Array::from(vec![
9802 Some(7),
9803 None,
9804 Some(7),
9805 None,
9806 Some(7),
9807 ]));
9808 let field = arrow_schema::Field::new("c", DataType::Int32, true);
9809 let page = encode_first_page(field, arr.clone(), TestEncoding::StructuralU32).await;
9810
9811 let PageEncoding::Structural(layout) = &page.description else {
9812 panic!("Expected structural encoding");
9813 };
9814 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
9815 panic!("Expected constant layout in slot 2");
9816 };
9817 assert!(layout.inline_value.is_some());
9818 assert_eq!(page.data.len(), 2);
9819
9820 let test_cases = TestCases::default()
9821 .with_encoding(TestEncoding::StructuralU32)
9822 .with_page_sizes(vec![4096]);
9823 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
9824 }
9825
9826 #[tokio::test]
9827 async fn test_constant_layout_list_repdef_v2_2() {
9828 use crate::format::pb21::page_layout::Layout;
9829 use arrow_array::builder::{Int32Builder, ListBuilder};
9830
9831 let mut builder = ListBuilder::new(Int32Builder::new());
9832 builder.values().append_value(7);
9833 builder.values().append_null();
9834 builder.values().append_value(7);
9835 builder.append(true);
9836
9837 builder.append(true);
9838
9839 builder.values().append_value(7);
9840 builder.append(true);
9841
9842 builder.append_null();
9843
9844 let arr: ArrayRef = Arc::new(builder.finish());
9845 let field = arrow_schema::Field::new(
9846 "c",
9847 DataType::List(Arc::new(arrow_schema::Field::new(
9848 "item",
9849 DataType::Int32,
9850 true,
9851 ))),
9852 true,
9853 );
9854 let page = encode_first_page(field, arr.clone(), TestEncoding::StructuralU32).await;
9855
9856 let PageEncoding::Structural(layout) = &page.description else {
9857 panic!("Expected structural encoding");
9858 };
9859 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
9860 panic!("Expected constant layout in slot 2");
9861 };
9862 assert!(layout.inline_value.is_some());
9863 assert_eq!(page.data.len(), 2);
9864
9865 let test_cases = TestCases::default()
9866 .with_encoding(TestEncoding::StructuralU32)
9867 .with_page_sizes(vec![4096]);
9868 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
9869 }
9870
9871 #[tokio::test]
9872 async fn test_constant_layout_fixed_size_list_not_used_v2_2() {
9873 use crate::format::pb21::page_layout::Layout;
9874 use arrow_array::builder::{FixedSizeListBuilder, Int32Builder};
9875
9876 let mut builder = FixedSizeListBuilder::new(Int32Builder::new(), 3);
9877 for _ in 0..64 {
9878 builder.values().append_value(1);
9879 builder.values().append_null();
9880 builder.values().append_value(3);
9881 builder.append(true);
9882 }
9883 let arr: ArrayRef = Arc::new(builder.finish());
9884 let field = arrow_schema::Field::new(
9885 "c",
9886 DataType::FixedSizeList(
9887 Arc::new(arrow_schema::Field::new("item", DataType::Int32, true)),
9888 3,
9889 ),
9890 true,
9891 );
9892 let page = encode_first_page(field, arr.clone(), TestEncoding::StructuralU32).await;
9893
9894 if let PageEncoding::Structural(layout) = &page.description {
9895 assert!(
9896 !matches!(layout.layout.as_ref().unwrap(), Layout::ConstantLayout(_)),
9897 "FixedSizeList should not use constant layout yet"
9898 );
9899 }
9900
9901 let test_cases = TestCases::default()
9902 .with_encoding(TestEncoding::StructuralU32)
9903 .with_page_sizes(vec![4096]);
9904 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
9905 }
9906
9907 #[tokio::test]
9908 async fn test_constant_layout_not_written_before_v2_2() {
9909 use crate::format::pb21::page_layout::Layout;
9910
9911 let arr: ArrayRef = Arc::new(arrow_array::Int32Array::from(vec![7; 1024]));
9912 let field = arrow_schema::Field::new("c", DataType::Int32, true);
9913 let page = encode_first_page(field, arr.clone(), TestEncoding::StructuralU16).await;
9914
9915 let PageEncoding::Structural(layout) = &page.description else {
9916 return;
9917 };
9918 assert!(
9919 !matches!(layout.layout.as_ref().unwrap(), Layout::ConstantLayout(_)),
9920 "Should not emit constant layout before v2.2"
9921 );
9922
9923 let test_cases = TestCases::default()
9924 .with_encoding(TestEncoding::StructuralU16)
9925 .with_page_sizes(vec![4096]);
9926 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
9927 }
9928
9929 #[tokio::test]
9930 async fn test_all_null_constant_layout_still_works_v2_2() {
9931 use crate::format::pb21::page_layout::Layout;
9932
9933 let arr: ArrayRef = Arc::new(arrow_array::Int32Array::from(vec![None, None, None]));
9934 let field = arrow_schema::Field::new("c", DataType::Int32, true);
9935 let page = encode_first_page(field, arr.clone(), TestEncoding::StructuralU32).await;
9936
9937 let PageEncoding::Structural(layout) = &page.description else {
9938 panic!("Expected structural encoding");
9939 };
9940 let Layout::ConstantLayout(layout) = layout.layout.as_ref().unwrap() else {
9941 panic!("Expected layout in slot 2");
9942 };
9943 assert!(layout.inline_value.is_none());
9944 assert_eq!(page.data.len(), 0);
9945
9946 let test_cases = TestCases::default()
9947 .with_encoding(TestEncoding::StructuralU32)
9948 .with_page_sizes(vec![4096]);
9949 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
9950 }
9951
9952 fn hand_built_dictionary_with_out_of_range_null_keys() -> ArrayRef {
9953 use arrow_array::{DictionaryArray, Int32Array, types::Int32Type};
9954 use arrow_buffer::NullBuffer;
9955
9956 let keys = Int32Array::new(
9957 vec![0, 7, 7].into(),
9958 Some(NullBuffer::from(vec![true, false, false])),
9959 );
9960 let values = Arc::new(StringArray::from(vec!["a"]));
9961 Arc::new(DictionaryArray::<Int32Type>::try_new(keys, values).unwrap()) as ArrayRef
9962 }
9963
9964 fn concatenated_dictionary_with_out_of_range_null_keys() -> ArrayRef {
9965 use arrow_array::{builder::StringDictionaryBuilder, new_null_array, types::Int32Type};
9966
9967 let mut builder = StringDictionaryBuilder::<Int32Type>::new();
9968 builder.append_value("a");
9969 for _ in 0..7 {
9970 builder.append_null();
9971 }
9972 let valued = Arc::new(builder.finish()) as ArrayRef;
9973 let all_null = new_null_array(valued.data_type(), 8);
9974 arrow_select::concat::concat(&[valued.as_ref(), all_null.as_ref()]).unwrap()
9975 }
9976
9977 #[rstest::rstest]
9978 #[case::hand_built(hand_built_dictionary_with_out_of_range_null_keys())]
9979 #[case::concatenated(concatenated_dictionary_with_out_of_range_null_keys())]
9980 #[tokio::test]
9981 async fn test_dictionary_out_of_range_null_keys_round_trip(#[case] dictionary: ArrayRef) {
9982 let test_cases = TestCases::default()
9983 .with_encoding(TestEncoding::StructuralU32)
9984 .with_page_sizes(vec![4096]);
9985
9986 check_round_trip_encoding_of_data(vec![dictionary], &test_cases, HashMap::new()).await;
9987 }
9988
9989 #[test]
9990 fn test_encode_decode_complex_all_null_vals_roundtrip() {
9991 use crate::compression::{DecompressionStrategy, DefaultDecompressionStrategy};
9992
9993 let values: Arc<[u16]> = Arc::from((0..2048).map(|i| (i % 5) as u16).collect::<Vec<u16>>());
9994
9995 let compression_strategy = crate::testing::test_compression_strategy(
9996 TestEncoding::StructuralU16,
9997 crate::compression_config::CompressionParams::default(),
9998 );
9999 let decompression_strategy = DefaultDecompressionStrategy::default();
10000
10001 let (compressed_buf, encoding) = PrimitiveStructuralEncoder::encode_complex_all_null_vals(
10002 &values,
10003 compression_strategy.as_ref(),
10004 )
10005 .unwrap();
10006
10007 let decompressor = decompression_strategy
10008 .create_block_decompressor(&encoding)
10009 .unwrap();
10010 let decompressed = decompressor
10011 .decompress(Some(compressed_buf), values.len() as u64)
10012 .unwrap();
10013 let decompressed_fixed_width = decompressed.as_fixed_width().unwrap();
10014 assert_eq!(decompressed_fixed_width.num_values, values.len() as u64);
10015 assert_eq!(decompressed_fixed_width.bits_per_value, 16);
10016 let rep_result = decompressed_fixed_width.data.borrow_to_typed_slice::<u16>();
10017 assert_eq!(rep_result.as_ref(), values.as_ref());
10018 }
10019
10020 #[tokio::test]
10021 async fn test_complex_all_null_compression_gated_by_version() {
10022 use crate::format::pb21::page_layout::Layout;
10023 use arrow_array::ListArray;
10024
10025 let list_array = ListArray::from_iter_primitive::<arrow_array::types::Int32Type, _, _>(
10026 (0..1000).map(|i| if i % 2 == 0 { None } else { Some(vec![]) }),
10027 );
10028 let arr: ArrayRef = Arc::new(list_array);
10029 let field = arrow_schema::Field::new(
10030 "c",
10031 DataType::List(Arc::new(arrow_schema::Field::new(
10032 "item",
10033 DataType::Int32,
10034 true,
10035 ))),
10036 true,
10037 );
10038
10039 let page_v21 =
10040 encode_first_page(field.clone(), arr.clone(), TestEncoding::StructuralU16).await;
10041 let PageEncoding::Structural(layout_v21) = &page_v21.description else {
10042 panic!("Expected structural encoding");
10043 };
10044 let Layout::ConstantLayout(layout_v21) = layout_v21.layout.as_ref().unwrap() else {
10045 panic!("Expected constant layout");
10046 };
10047 assert!(layout_v21.rep_compression.is_none());
10048 assert!(layout_v21.def_compression.is_none());
10049 assert_eq!(layout_v21.num_rep_values, 0);
10050 assert_eq!(layout_v21.num_def_values, 0);
10051
10052 let page_v22 = encode_first_page(field, arr, TestEncoding::StructuralU32).await;
10053 let PageEncoding::Structural(layout_v22) = &page_v22.description else {
10054 panic!("Expected structural encoding");
10055 };
10056 let Layout::ConstantLayout(layout_v22) = layout_v22.layout.as_ref().unwrap() else {
10057 panic!("Expected constant layout");
10058 };
10059 assert!(layout_v22.def_compression.is_some());
10060 assert!(layout_v22.num_def_values > 0);
10061 }
10062
10063 #[tokio::test]
10064 async fn test_complex_all_null_round_trip() {
10065 use arrow_array::ListArray;
10066
10067 let list_array = ListArray::from_iter_primitive::<arrow_array::types::Int32Type, _, _>(
10068 (0..1000).map(|i| if i % 2 == 0 { None } else { Some(vec![]) }),
10069 );
10070
10071 let test_cases = TestCases::default().with_u32_structural_encodings();
10072 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, HashMap::new())
10073 .await;
10074 }
10075
10076 #[tokio::test]
10077 async fn test_complex_all_null_constant_def_round_trip() {
10078 use arrow_array::ListArray;
10079
10080 let list_array = ListArray::from_iter_primitive::<arrow_array::types::Int32Type, _, _>(
10083 (0..5000).map(|_| None::<Vec<Option<i32>>>),
10084 );
10085
10086 let test_cases = TestCases::default().with_u32_structural_encodings();
10087 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, HashMap::new())
10088 .await;
10089 }
10090
10091 fn encoded_u16_frame(levels: &[u16], run_length_width: RunLengthWidth) -> LanceBuffer {
10092 let block = DataBlock::FixedWidth(FixedWidthDataBlock {
10093 data: LanceBuffer::reinterpret_slice(Arc::from(levels)),
10094 bits_per_value: 16,
10095 num_values: levels.len() as u64,
10096 block_info: BlockInfo::new(),
10097 });
10098 BlockCompressor::compress(&RleEncoder::with_run_length_width(run_length_width), block)
10099 .unwrap()
10100 .0
10101 .unwrap()
10102 }
10103
10104 fn encoded_u16_runs(levels: &[u16], run_length_width: RunLengthWidth) -> RleRuns {
10105 let frame = encoded_u16_frame(levels, run_length_width);
10106 RleDecompressor::with_run_length_width(16, run_length_width)
10107 .decode_u16_runs(frame, levels.len() as u64)
10108 .unwrap()
10109 }
10110
10111 #[test]
10112 fn miniblock_levels_use_one_count_for_mixed_codecs() {
10113 let actual_num_levels = usize::from(u16::MAX) + 8;
10114 let levels = vec![1_u16; actual_num_levels];
10115 let rep_frame = encoded_u16_frame(&levels, RunLengthWidth::U8);
10116 let rep_decompressor = RleDecompressor::new(16);
10117 let def_frame = LanceBuffer::reinterpret_slice(Arc::from(levels.clone()));
10118 let def_decompressor = ValueDecompressor::from_flat(&pb21::Flat {
10119 bits_per_value: 16,
10120 data: None,
10121 });
10122
10123 let num_levels = DecodeMiniBlockTask::resolve_num_levels(
10124 Some(&rep_decompressor),
10125 Some(&rep_frame),
10126 Some(&def_decompressor),
10127 Some(&def_frame),
10128 7,
10129 )
10130 .unwrap();
10131 assert_eq!(num_levels, actual_num_levels as u64);
10132
10133 let rep =
10134 DecodeMiniBlockTask::decode_levels(&rep_decompressor, rep_frame, num_levels).unwrap();
10135 let def =
10136 DecodeMiniBlockTask::decode_levels(&def_decompressor, def_frame, num_levels).unwrap();
10137 assert_eq!(rep.as_ref(), levels);
10138 assert_eq!(def, rep);
10139 }
10140
10141 #[test]
10142 fn miniblock_levels_reject_non_wrapped_count_mismatch() {
10143 let frame = encoded_u16_frame(&[1_u16; 8], RunLengthWidth::U8);
10144 let decompressor = RleDecompressor::new(16);
10145
10146 let error = DecodeMiniBlockTask::resolve_num_levels(
10147 Some(&decompressor),
10148 Some(&frame),
10149 None,
10150 None,
10151 7,
10152 )
10153 .unwrap_err();
10154 assert!(matches!(error, lance_core::Error::InvalidInput { .. }));
10155 assert!(error.to_string().contains("not congruent modulo 65536"));
10156 }
10157
10158 #[test]
10159 fn miniblock_levels_reject_cross_stream_count_disagreement() {
10160 let rep_levels = vec![1_u16; usize::from(u16::MAX) + 8];
10161 let def_levels = vec![1_u16; rep_levels.len() + usize::from(u16::MAX) + 1];
10162 let rep_frame = encoded_u16_frame(&rep_levels, RunLengthWidth::U8);
10163 let def_frame = encoded_u16_frame(&def_levels, RunLengthWidth::U8);
10164 let decompressor = RleDecompressor::new(16);
10165
10166 let error = DecodeMiniBlockTask::resolve_num_levels(
10167 Some(&decompressor),
10168 Some(&rep_frame),
10169 Some(&decompressor),
10170 Some(&def_frame),
10171 7,
10172 )
10173 .unwrap_err();
10174 assert!(matches!(error, lance_core::Error::InvalidInput { .. }));
10175 assert!(
10176 error
10177 .to_string()
10178 .contains("structural streams disagree on the level count")
10179 );
10180 }
10181
10182 fn physical_levels(levels: &[u16]) -> LazyLevels {
10183 LazyLevels::Runs(Arc::new(RunStorage::Physical(
10184 encoded_u16_runs(levels, RunLengthWidth::U8).into_owned(),
10185 )))
10186 }
10187
10188 fn coalesced_levels(levels: &[u16]) -> LazyLevels {
10189 let mut values = Vec::new();
10190 let mut ends = RunEndsBuilder::with_capacity(levels.len(), levels.len());
10191 for (index, &value) in levels.iter().enumerate() {
10192 if values.last() == Some(&value) {
10193 ends.set_last(index + 1).unwrap();
10194 } else {
10195 values.push(value);
10196 ends.push(index + 1).unwrap();
10197 }
10198 }
10199 LazyLevels::Runs(Arc::new(RunStorage::Coalesced {
10200 values: values.into_boxed_slice(),
10201 ends: ends.finish(),
10202 }))
10203 }
10204
10205 #[test]
10206 fn lazy_levels_runs_match_dense() {
10207 let expanded: Vec<u16> = vec![3, 3, 1, 3, 3, 3, 0, 0];
10209 let coalesced = coalesced_levels(&expanded);
10210 let physical = physical_levels(&expanded);
10211 let dense = LazyLevels::Dense(ScalarBuffer::<u16>::from(expanded.clone()));
10212 let n = expanded.len();
10213
10214 assert_eq!(coalesced.len(), n);
10215 assert_eq!(physical.len(), n);
10216 assert_eq!(dense.len(), n);
10217
10218 let max_rep = 3u16;
10220 let row_starts: Vec<usize> = (0..n).filter(|&i| expanded[i] == max_rep).collect();
10221 for target in 0..=row_starts.len() as u64 {
10222 let want = row_starts.get(target as usize).copied().unwrap_or(n);
10223 for runs in [&coalesced, &physical] {
10224 let mut cursor = LevelCursor::default();
10225 assert_eq!(
10226 runs.seek_row_start(&mut cursor, target, max_rep).unwrap(),
10227 want,
10228 "seek_row_start({target})"
10229 );
10230 }
10231 let mut c_dense = LevelCursor::default();
10232 assert_eq!(
10233 dense.seek_row_start(&mut c_dense, target, max_rep).unwrap(),
10234 want
10235 );
10236 }
10237
10238 for start in 0..=n {
10241 for end in start..=n {
10242 for max in [0u16, 1, 2, 3] {
10243 let want = expanded[start..end].iter().filter(|&&d| d <= max).count() as u64;
10244 for runs in [&coalesced, &physical] {
10245 let mut cursor = RunPosition::default();
10246 assert_eq!(
10247 runs.count_le_cursor(&mut cursor, start..end, max).0,
10248 want,
10249 "count_le_cursor({start}..{end}, {max})"
10250 );
10251 }
10252 let mut d_cur = RunPosition::default();
10253 assert_eq!(dense.count_le_cursor(&mut d_cur, start..end, max).0, want);
10254 }
10255 for runs in [&coalesced, &physical] {
10256 let mut got = Vec::new();
10257 runs.extend_into(start..end, RunPosition::default(), &mut got);
10258 assert_eq!(
10259 got,
10260 expanded[start..end].to_vec(),
10261 "extend_into({start}..{end})"
10262 );
10263 }
10264 let mut got_dense = Vec::new();
10265 dense.extend_into(start..end, RunPosition::default(), &mut got_dense);
10266 assert_eq!(got_dense, expanded[start..end].to_vec());
10267 }
10268 }
10269 }
10270
10271 #[test]
10272 fn physical_run_hints_support_deferred_materialization() {
10273 let expanded: Vec<u16> = vec![3, 3, 1, 1, 2, 2, 0, 0];
10274 let physical = physical_levels(&expanded);
10275 let LazyLevels::Runs(runs) = &physical else {
10276 panic!("expected physical runs");
10277 };
10278 let mut first_hint = RunPosition::default();
10279 runs.seek(&mut first_hint, 2);
10280 let mut second_hint = RunPosition::default();
10281 runs.seek(&mut second_hint, 6);
10282
10283 let mut second = Vec::new();
10284 physical.extend_into(6..8, second_hint, &mut second);
10285 let mut first = Vec::new();
10286 physical.extend_into(2..4, first_hint, &mut first);
10287 assert_eq!(second, expanded[6..8]);
10288 assert_eq!(first, expanded[2..4]);
10289 }
10290
10291 mod complex_all_null_drain_parity {
10296 use std::ops::Range;
10297
10298 use arrow_buffer::ScalarBuffer;
10299 use proptest::prelude::*;
10300
10301 use super::super::{LazyLevels, LevelCursor, RunPosition};
10302 use super::{coalesced_levels, physical_levels};
10303 use crate::Result;
10304
10305 #[derive(Debug, Clone)]
10306 struct DrainInput {
10307 max_rep: u16,
10308 max_visible: u16,
10309 rep: Option<Vec<u16>>,
10310 def: Option<Vec<u16>>,
10311 ranges: Vec<Range<u64>>,
10312 }
10313
10314 fn dense_levels(levels: &[u16]) -> LazyLevels {
10315 LazyLevels::Dense(ScalarBuffer::from(levels.to_vec()))
10316 }
10317
10318 fn rle_levels(levels: &[u16]) -> LazyLevels {
10319 coalesced_levels(levels)
10320 }
10321
10322 fn seek(
10323 rep: Option<&LazyLevels>,
10324 cursor: &mut LevelCursor,
10325 row: u64,
10326 max_rep: u16,
10327 ) -> Result<usize> {
10328 match rep {
10329 Some(rep) => rep.seek_row_start(cursor, row, max_rep),
10330 None => {
10331 cursor.row = row;
10332 cursor.level = row as usize;
10333 Ok(row as usize)
10334 }
10335 }
10336 }
10337
10338 fn simulate_drain(
10341 rep: Option<&LazyLevels>,
10342 def: Option<&LazyLevels>,
10343 max_rep: u16,
10344 max_visible: u16,
10345 ranges: &[Range<u64>],
10346 ) -> Result<(Vec<Range<usize>>, u64)> {
10347 let mut rep_cursor = LevelCursor::default();
10348 let mut def_run_cursor = RunPosition::default();
10349 let mut slices: Vec<Range<usize>> = Vec::new();
10350 let mut visible = 0u64;
10351 for range in ranges {
10352 let level_start = seek(rep, &mut rep_cursor, range.start, max_rep)?;
10353 let level_end = seek(rep, &mut rep_cursor, range.end, max_rep)?;
10354 visible += match def {
10355 Some(def) => {
10356 def.count_le_cursor(
10357 &mut def_run_cursor,
10358 level_start..level_end,
10359 max_visible,
10360 )
10361 .0
10362 }
10363 None => (level_end - level_start) as u64,
10364 };
10365 match slices.last_mut() {
10366 Some(last) if last.end == level_start => last.end = level_end,
10367 _ => slices.push(level_start..level_end),
10368 }
10369 }
10370 Ok((slices, visible))
10371 }
10372
10373 fn reference_drain(
10375 rep: Option<&[u16]>,
10376 def: Option<&[u16]>,
10377 max_rep: u16,
10378 max_visible: u16,
10379 ranges: &[Range<u64>],
10380 ) -> (Vec<Range<usize>>, u64) {
10381 let total_levels = rep
10382 .map(|r| r.len())
10383 .or_else(|| def.map(|d| d.len()))
10384 .unwrap_or(0);
10385 let row_starts: Vec<usize> = match rep {
10387 Some(rep) => (0..rep.len()).filter(|&i| rep[i] == max_rep).collect(),
10388 None => (0..total_levels).collect(),
10389 };
10390 let level_of_row = |row: u64| {
10391 row_starts
10392 .get(row as usize)
10393 .copied()
10394 .unwrap_or(total_levels)
10395 };
10396
10397 let mut slices: Vec<Range<usize>> = Vec::new();
10398 let mut visible = 0u64;
10399 for range in ranges {
10400 let ls = level_of_row(range.start);
10401 let le = level_of_row(range.end);
10402 visible += match def {
10403 Some(def) => def[ls..le].iter().filter(|&&d| d <= max_visible).count() as u64,
10404 None => (le - ls) as u64,
10405 };
10406 match slices.last_mut() {
10407 Some(last) if last.end == ls => last.end = le,
10408 _ => slices.push(ls..le),
10409 }
10410 }
10411 (slices, visible)
10412 }
10413
10414 fn ranges_strategy(num_rows: u64) -> BoxedStrategy<Vec<Range<u64>>> {
10415 if num_rows == 0 {
10416 return Just(Vec::new()).boxed();
10417 }
10418 proptest::collection::vec((0u64..=3, 1u64..=4), 0..=8)
10421 .prop_map(move |pairs| {
10422 let mut ranges = Vec::new();
10423 let mut pos = 0u64;
10424 for (gap, len) in pairs {
10425 pos = pos.saturating_add(gap);
10426 if pos >= num_rows {
10427 break;
10428 }
10429 let end = (pos + len).min(num_rows);
10430 ranges.push(pos..end);
10431 pos = end;
10432 }
10433 ranges
10434 })
10435 .boxed()
10436 }
10437
10438 fn drain_input() -> impl Strategy<Value = DrainInput> {
10439 (
10440 1u16..=3,
10441 0u16..=3,
10442 any::<bool>(),
10443 any::<bool>(),
10444 1usize..=48,
10445 )
10446 .prop_flat_map(|(max_rep, max_visible, has_rep, has_def, len)| {
10447 let has_def = has_def || !has_rep;
10450 let rep = if has_rep {
10451 proptest::collection::vec(0u16..=max_rep, len)
10452 .prop_map(move |mut v| {
10453 v[0] = max_rep;
10455 Some(v)
10456 })
10457 .boxed()
10458 } else {
10459 Just(None).boxed()
10460 };
10461 let def = if has_def {
10462 proptest::collection::vec(0u16..=(max_visible + 2), len)
10463 .prop_map(Some)
10464 .boxed()
10465 } else {
10466 Just(None).boxed()
10467 };
10468 (Just(max_rep), Just(max_visible), rep, def)
10469 })
10470 .prop_flat_map(|(max_rep, max_visible, rep, def)| {
10471 let num_rows = match &rep {
10472 Some(rep) => rep.iter().filter(|&&v| v == max_rep).count() as u64,
10473 None => def.as_ref().map(|d| d.len() as u64).unwrap_or(0),
10474 };
10475 ranges_strategy(num_rows).prop_map(move |ranges| DrainInput {
10476 max_rep,
10477 max_visible,
10478 rep: rep.clone(),
10479 def: def.clone(),
10480 ranges,
10481 })
10482 })
10483 }
10484
10485 proptest! {
10486 #![proptest_config(ProptestConfig::with_cases(512))]
10487
10488 #[test]
10489 fn drain_matches_reference(input in drain_input()) {
10490 let DrainInput { max_rep, max_visible, rep, def, ranges } = input;
10491
10492 let reference =
10493 reference_drain(rep.as_deref(), def.as_deref(), max_rep, max_visible, &ranges);
10494
10495 let rep_dense = rep.as_deref().map(dense_levels);
10496 let def_dense = def.as_deref().map(dense_levels);
10497 let got_dense =
10498 simulate_drain(rep_dense.as_ref(), def_dense.as_ref(), max_rep, max_visible, &ranges)
10499 .unwrap();
10500 prop_assert_eq!(&got_dense, &reference, "dense form diverged from reference");
10501
10502 let rep_rle = rep.as_deref().map(rle_levels);
10503 let def_rle = def.as_deref().map(rle_levels);
10504 let got_rle =
10505 simulate_drain(rep_rle.as_ref(), def_rle.as_ref(), max_rep, max_visible, &ranges)
10506 .unwrap();
10507 prop_assert_eq!(&got_rle, &reference, "rle form diverged from reference");
10508
10509 let rep_physical = rep.as_deref().map(physical_levels);
10510 let def_physical = def.as_deref().map(physical_levels);
10511 let got_physical =
10512 simulate_drain(rep_physical.as_ref(), def_physical.as_ref(), max_rep, max_visible, &ranges)
10513 .unwrap();
10514 prop_assert_eq!(&got_physical, &reference, "physical form diverged from reference");
10515 }
10516 }
10517 }
10518
10519 #[test]
10520 fn lazy_levels_runs_are_compact() {
10521 let single_run = |n: usize| {
10522 let mut ends = RunEndsBuilder::with_capacity(n, 1);
10523 ends.push(n).unwrap();
10524 LazyLevels::Runs(Arc::new(RunStorage::Coalesced {
10525 values: vec![1u16].into_boxed_slice(),
10526 ends: ends.finish(),
10527 }))
10528 };
10529 assert_eq!(single_run(100).deep_size(), single_run(10_000).deep_size());
10531 assert!(single_run(10_000_000).deep_size() < 100);
10532 assert_eq!(single_run(10_000_000).len(), 10_000_000);
10533 assert_eq!(
10535 LazyLevels::Dense(ScalarBuffer::<u16>::from(vec![1u16; 1000])).deep_size(),
10536 2000
10537 );
10538 }
10539
10540 #[test]
10541 fn lazy_levels_selects_smallest_representation() {
10542 let runs = encoded_u16_runs(&[7u16; 10], RunLengthWidth::U8);
10543 assert_eq!(LazyLevels::select_plan(&runs), LevelPlan::Dense);
10544
10545 let equal_size: Vec<u16> = std::iter::repeat_n(0, 256)
10546 .chain(std::iter::repeat_n(1, 100))
10547 .chain(std::iter::repeat_n(2, 100))
10548 .collect();
10549 let runs = encoded_u16_runs(&equal_size, RunLengthWidth::U8);
10550 assert_eq!(LazyLevels::select_plan(&runs), LevelPlan::Coalesced);
10551
10552 let moderate_runs: Vec<u16> = (0..250)
10553 .flat_map(|run| std::iter::repeat_n((run % 2) as u16, 4))
10554 .collect();
10555 let runs = encoded_u16_runs(&moderate_runs, RunLengthWidth::U8);
10556 assert_eq!(LazyLevels::select_plan(&runs), LevelPlan::Physical);
10557
10558 let split_constant = vec![7u16; 5000];
10559 let runs = encoded_u16_runs(&split_constant, RunLengthWidth::U8);
10560 assert_eq!(LazyLevels::select_plan(&runs), LevelPlan::Coalesced);
10561
10562 let high_density: Vec<u16> = (0..70_000).map(|index| (index % 2) as u16).collect();
10563 let runs = encoded_u16_runs(&high_density, RunLengthWidth::U32);
10564 assert_eq!(LazyLevels::select_plan(&runs), LevelPlan::Dense);
10565 }
10566
10567 #[test]
10568 fn physical_runs_detach_from_large_encoded_frame() {
10569 let levels: Vec<u16> = (0..250)
10570 .flat_map(|run| std::iter::repeat_n((run % 2) as u16, 4))
10571 .collect();
10572 let frame = encoded_u16_frame(&levels, RunLengthWidth::U8);
10573 let frame_offset = 4096;
10574 let mut allocation = vec![0; frame_offset + frame.len() + 1_000_000];
10575 allocation[frame_offset..frame_offset + frame.len()].copy_from_slice(frame.as_ref());
10576 let frame = LanceBuffer::from(allocation).slice_with_length(frame_offset, frame.len());
10577 let runs = RleDecompressor::with_run_length_width(16, RunLengthWidth::U8)
10578 .decode_u16_runs(frame, levels.len() as u64)
10579 .unwrap();
10580
10581 assert_eq!(LazyLevels::select_plan(&runs), LevelPlan::Physical);
10582 let cached = LazyLevels::from_rle_runs(runs).unwrap();
10583 assert!(
10584 matches!(cached, LazyLevels::Runs(ref runs) if matches!(runs.as_ref(), RunStorage::Physical(_)))
10585 );
10586 assert_eq!(cached.len(), levels.len());
10587 assert!(cached.deep_size() < 4096);
10588 }
10589
10590 #[test]
10591 fn complex_all_null_levels_reject_invalid_values_and_lengths() {
10592 let invalid_levels = vec![0u16, 3];
10593 for levels in [
10594 LazyLevels::Dense(ScalarBuffer::from(invalid_levels.clone())),
10595 physical_levels(&invalid_levels),
10596 coalesced_levels(&invalid_levels),
10597 ] {
10598 let error = validate_complex_all_null_levels(&None, &Some(levels), 0, 2).unwrap_err();
10599 assert!(matches!(error, lance_core::Error::InvalidInput { .. }));
10600 assert!(error.to_string().contains("Invalid definition level 3"));
10601 }
10602
10603 let rep = Some(LazyLevels::Dense(ScalarBuffer::from(vec![0u16; 2])));
10604 let def = Some(LazyLevels::Dense(ScalarBuffer::from(vec![0u16])));
10605 let error = validate_complex_all_null_levels(&rep, &def, 0, 0).unwrap_err();
10606 assert!(matches!(error, lance_core::Error::InvalidInput { .. }));
10607 assert!(
10608 error
10609 .to_string()
10610 .contains("repetition has 2, definition has 1")
10611 );
10612 }
10613
10614 #[test]
10615 fn block_levels_reject_malformed_payload_size() {
10616 let block = DataBlock::FixedWidth(FixedWidthDataBlock {
10617 data: LanceBuffer::from(vec![0]),
10618 bits_per_value: 16,
10619 num_values: 1,
10620 block_info: BlockInfo::new(),
10621 });
10622 let error = dense_levels_from_block(block, 1, "definition").unwrap_err();
10623 assert!(matches!(error, lance_core::Error::InvalidInput { .. }));
10624 assert!(
10625 error
10626 .to_string()
10627 .contains("expected 2 bytes for 1 values, got 1")
10628 );
10629 }
10630
10631 #[test]
10632 fn complex_all_null_level_codec_validates_rle_metadata() {
10633 let encoding = pb21::CompressiveEncoding {
10634 compression: Some(Compression::Rle(Box::new(pb21::Rle {
10635 values: None,
10636 run_lengths: Some(Box::new(ProtobufUtils21::flat(8, None))),
10637 }))),
10638 };
10639
10640 let error = LevelCodec::try_new(Some(&encoding), &DefaultDecompressionStrategy::default())
10641 .unwrap_err();
10642 assert!(matches!(error, lance_core::Error::InvalidInput { .. }));
10643 assert!(
10644 error
10645 .to_string()
10646 .contains("RLE compression missing values encoding")
10647 );
10648 }
10649
10650 #[tokio::test]
10652 async fn test_sparse_boolean_list_roundtrip() {
10653 use arrow_array::builder::{BooleanBuilder, ListBuilder};
10654
10655 let mut list_builder = ListBuilder::new(BooleanBuilder::new());
10656 for i in 0..1000i32 {
10657 if i % 64 == 0 {
10658 list_builder.values().append_value(i % 128 == 0);
10660 list_builder.append(true);
10661 } else {
10662 list_builder.append(false);
10663 }
10664 }
10665 let list_array = Arc::new(list_builder.finish());
10666
10667 let test_cases = TestCases::default().with_structural_encodings();
10668 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
10669 }
10670
10671 fn truncated_tail_details() -> std::sync::Arc<super::FullZipDecodeDetails> {
10672 use crate::compression::VariablePerValueDecompressor;
10673 use crate::encodings::physical::binary::VariableDecoder;
10674 use crate::repdef::{ControlWordParser, DefinitionInterpretation};
10675 use std::sync::Arc;
10676 Arc::new(super::FullZipDecodeDetails {
10677 value_decompressor: super::PerValueDecompressor::Variable(Arc::new(
10678 VariableDecoder::default(),
10679 )
10680 as Arc<dyn VariablePerValueDecompressor>),
10681 def_meaning: vec![DefinitionInterpretation::NullableItem].into(),
10682 ctrl_word_parser: ControlWordParser::new(0, 0),
10683 max_rep: 0,
10684 max_visible_def: 0,
10685 })
10686 }
10687
10688 fn decode_variable_full_zip(
10689 buf: Vec<u8>,
10690 bits_per_offset: u8,
10691 ) -> lance_core::Result<super::VariableFullZipDecoder> {
10692 use std::collections::VecDeque;
10693 let mut data = VecDeque::new();
10694 data.push_back(crate::buffer::LanceBuffer::from(buf));
10695 super::VariableFullZipDecoder::new(
10696 truncated_tail_details(),
10697 data,
10698 1,
10699 bits_per_offset,
10700 bits_per_offset,
10701 )
10702 }
10703
10704 #[test]
10706 fn variable_full_zip_wellformed_length_prefix() {
10707 assert!(decode_variable_full_zip(0u32.to_le_bytes().to_vec(), 32).is_ok());
10708 assert!(decode_variable_full_zip(0u64.to_le_bytes().to_vec(), 64).is_ok());
10709 }
10710
10711 #[test]
10720 fn variable_full_zip_truncated_length_prefix_is_corrupt_file() {
10721 use lance_core::Error;
10722
10723 for (bits, buf_len) in [(32u8, 3usize), (64u8, 4usize)] {
10724 let err = decode_variable_full_zip(vec![0xAA; buf_len], bits)
10725 .expect_err("a truncated length prefix must not decode");
10726 assert!(
10727 matches!(err, Error::CorruptFile { .. }),
10728 "expected CorruptFile for a {}-bit prefix with {} byte(s), got: {:?}",
10729 bits,
10730 buf_len,
10731 err
10732 );
10733 let msg = err.to_string();
10734 assert!(
10735 msg.contains("truncated length prefix"),
10736 "error should say what is wrong, got: {msg}"
10737 );
10738 }
10739 }
10740}