1use std::any::Any;
5use std::collections::VecDeque;
6use std::fmt::Debug;
7use std::ops::Range;
8use std::sync::Arc;
9use std::{iter, vec};
10
11use arrow_buffer::ScalarBuffer;
12use bytes::Bytes;
13use futures::FutureExt;
14use futures::future::BoxFuture;
15use itertools::Itertools;
16use lance_core::Result;
17use lance_core::cache::{Context, DeepSizeOf};
18use lance_core::error::Error;
19use lance_core::utils::bit::pad_bytes;
20
21use crate::EncodingsIo;
22use crate::buffer::LanceBuffer;
23use crate::compression::{BlockDecompressor, DecompressionStrategy, MiniBlockDecompressor};
24use crate::data::{DataBlock, DataBlockBuilder, DictionaryDataBlock};
25use crate::decoder::{DecodePageTask, DecodedPage, StructuralPageDecoder};
26use crate::format::metadata::compressive_encoding::Compression;
27use crate::format::{EncodingUtils, metadata};
28use crate::repdef::{DefinitionInterpretation, LevelBuffer, RepDefUnraveler, max_visible_level};
29
30use super::chunk_index::{
31 ItemCounts, MiniBlockChunkIndex, PrefixSums, RowMapping, parse_nested_rep,
32};
33use super::{
34 CachedPageData, LoadedChunk, MAX_LEGACY_RLE_LEVELS, MINIBLOCK_ALIGNMENT, PageInitialization,
35 PageInitializationBuffers, PageLoadTask, StructuralPageScheduler,
36};
37
38#[derive(Debug, Clone)]
40struct DecodedMiniBlockChunk {
41 rep: Option<ScalarBuffer<u16>>,
42 def: Option<ScalarBuffer<u16>>,
43 values: DataBlock,
44}
45
46#[derive(Debug)]
54struct DecodeMiniBlockTask {
55 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
56 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
57 value_decompressor: Arc<dyn MiniBlockDecompressor>,
58 dictionary_data: Option<Arc<DataBlock>>,
59 def_meaning: Arc<[DefinitionInterpretation]>,
60 num_buffers: u64,
61 max_visible_level: u16,
62 instructions: Vec<(ChunkDrainInstructions, LoadedChunk)>,
63 has_large_chunk: bool,
64}
65
66impl DecodeMiniBlockTask {
67 fn decoded_size_bytes(&self) -> Option<u64> {
68 if self.rep_decompressor.is_some() || self.def_decompressor.is_some() {
69 return None;
70 }
71 let num_values = self
72 .instructions
73 .iter()
74 .try_fold(0_u64, |total, (instruction, _)| {
75 total.checked_add(instruction.rows_to_take)
76 })?;
77 self.value_decompressor.decoded_size_bytes(num_values)
78 }
79
80 fn resolve_num_levels(
81 rep_decompressor: Option<&dyn BlockDecompressor>,
82 rep_levels: Option<&LanceBuffer>,
83 def_decompressor: Option<&dyn BlockDecompressor>,
84 def_levels: Option<&LanceBuffer>,
85 declared_num_levels: u16,
86 ) -> Result<u64> {
87 let rep_num_levels = match (rep_decompressor, rep_levels) {
88 (Some(decompressor), Some(levels)) => decompressor.infer_num_values(levels)?,
89 (None, None) => None,
90 _ => {
91 return Err(Error::invalid_input_source(
92 "miniblock repetition codec and payload presence disagree".into(),
93 ));
94 }
95 };
96 let def_num_levels = match (def_decompressor, def_levels) {
97 (Some(decompressor), Some(levels)) => decompressor.infer_num_values(levels)?,
98 (None, None) => None,
99 _ => {
100 return Err(Error::invalid_input_source(
101 "miniblock definition codec and payload presence disagree".into(),
102 ));
103 }
104 };
105
106 let inferred_num_levels = match (rep_num_levels, def_num_levels) {
107 (Some(rep_num_levels), Some(def_num_levels)) if rep_num_levels != def_num_levels => {
108 return Err(Error::invalid_input_source(
109 format!(
110 "miniblock structural streams disagree on the level count: repetition inferred {rep_num_levels}, definition inferred {def_num_levels}"
111 )
112 .into(),
113 ));
114 }
115 (Some(num_levels), _) | (_, Some(num_levels)) => num_levels,
116 (None, None) => return Ok(u64::from(declared_num_levels)),
117 };
118
119 let declared_num_levels = u64::from(declared_num_levels);
120 if inferred_num_levels == declared_num_levels {
121 return Ok(inferred_num_levels);
122 }
123 let u16_modulus = u64::from(u16::MAX) + 1;
124 if inferred_num_levels <= declared_num_levels
125 || inferred_num_levels % u16_modulus != declared_num_levels
126 {
127 return Err(Error::invalid_input_source(
128 format!(
129 "miniblock payload proves {inferred_num_levels} levels but the header declared {declared_num_levels}; the counts are not congruent modulo 65536"
130 )
131 .into(),
132 ));
133 }
134 if inferred_num_levels > MAX_LEGACY_RLE_LEVELS {
135 return Err(Error::invalid_input_source(
136 format!(
137 "miniblock payload proves {inferred_num_levels} levels, exceeding the legacy RLE payload bound of {MAX_LEGACY_RLE_LEVELS}"
138 )
139 .into(),
140 ));
141 }
142 Ok(inferred_num_levels)
143 }
144
145 fn decode_levels(
146 decompressor: &dyn BlockDecompressor,
147 levels: LanceBuffer,
148 expected_num_levels: u64,
149 ) -> Result<ScalarBuffer<u16>> {
150 let levels = decompressor.decompress(Some(levels), expected_num_levels)?;
151 let levels = levels.as_fixed_width().ok_or_else(|| {
152 Error::invalid_input_source(
153 "miniblock levels did not decode to fixed-width data".into(),
154 )
155 })?;
156 if levels.num_values != expected_num_levels {
157 return Err(Error::invalid_input_source(
158 format!(
159 "miniblock levels decoded {} values, expected {expected_num_levels}",
160 levels.num_values
161 )
162 .into(),
163 ));
164 }
165 if levels.bits_per_value != 16 {
166 return Err(Error::invalid_input_source(
167 format!(
168 "miniblock levels decoded {} bits per value, expected 16",
169 levels.bits_per_value
170 )
171 .into(),
172 ));
173 }
174 let expected_bytes = expected_num_levels.checked_mul(2).ok_or_else(|| {
175 Error::invalid_input_source(
176 format!("miniblock level byte count overflowed for {expected_num_levels} levels")
177 .into(),
178 )
179 })?;
180 if levels.data.len() as u64 != expected_bytes {
181 return Err(Error::invalid_input_source(
182 format!(
183 "miniblock levels decoded {} bytes, expected {expected_bytes}",
184 levels.data.len()
185 )
186 .into(),
187 ));
188 }
189 Ok(levels.data.borrow_to_typed_slice::<u16>())
190 }
191
192 fn extend_levels(
199 range: Range<u64>,
200 levels: &mut Option<LevelBuffer>,
201 level_buf: &Option<impl AsRef<[u16]>>,
202 dest_offset: usize,
203 ) {
204 if let Some(level_buf) = level_buf {
205 if levels.is_none() {
206 let mut new_levels_vec =
209 LevelBuffer::with_capacity(dest_offset + (range.end - range.start) as usize);
210 new_levels_vec.extend(iter::repeat_n(0, dest_offset));
211 *levels = Some(new_levels_vec);
212 }
213 levels.as_mut().unwrap().extend(
214 level_buf.as_ref()[range.start as usize..range.end as usize]
215 .iter()
216 .copied(),
217 );
218 } else if let Some(levels) = levels {
219 let num_values = (range.end - range.start) as usize;
220 levels.extend(iter::repeat_n(0, num_values));
223 }
224 }
225
226 fn map_range(
263 range: Range<u64>,
264 rep: Option<&impl AsRef<[u16]>>,
265 def: Option<&impl AsRef<[u16]>>,
266 max_rep: u16,
267 max_visible_def: u16,
268 total_items: u64,
271 preamble_action: PreambleAction,
272 ) -> (Range<u64>, Range<u64>) {
273 if let Some(rep) = rep {
274 let mut rep = rep.as_ref();
275 let mut items_in_preamble = 0_u64;
278 let first_row_start = match preamble_action {
279 PreambleAction::Skip | PreambleAction::Take => {
280 let first_row_start = if let Some(def) = def.as_ref() {
281 let mut first_row_start = None;
282 for (idx, (rep, def)) in rep.iter().zip(def.as_ref()).enumerate() {
283 if *rep == max_rep {
284 first_row_start = Some(idx as u64);
285 break;
286 }
287 if *def <= max_visible_def {
288 items_in_preamble += 1;
289 }
290 }
291 first_row_start
292 } else {
293 let first_row_start =
294 rep.iter().position(|&r| r == max_rep).map(|r| r as u64);
295 items_in_preamble = first_row_start.unwrap_or(rep.len() as u64);
296 first_row_start
297 };
298 if first_row_start.is_none() {
301 assert!(preamble_action == PreambleAction::Take);
302 return (0..total_items, 0..rep.len() as u64);
303 }
304 let first_row_start = first_row_start.unwrap();
305 rep = &rep[first_row_start as usize..];
306 first_row_start
307 }
308 PreambleAction::Absent => {
309 debug_assert!(rep[0] == max_rep);
310 0
311 }
312 };
313
314 if range.start == range.end {
316 debug_assert!(preamble_action == PreambleAction::Take);
317 debug_assert!(items_in_preamble <= total_items);
318 return (0..items_in_preamble, 0..first_row_start);
319 }
320 assert!(range.start < range.end);
321
322 let mut rows_seen = 0;
323 let mut new_start = 0;
324 let mut new_levels_start = 0;
325
326 if let Some(def) = def {
327 let def = &def.as_ref()[first_row_start as usize..];
328
329 let mut lead_invis_seen = 0;
331
332 if range.start > 0 {
333 if def[0] > max_visible_def {
334 lead_invis_seen += 1;
335 }
336 for (idx, (rep, def)) in rep.iter().zip(def).skip(1).enumerate() {
337 if *rep == max_rep {
338 rows_seen += 1;
339 if rows_seen == range.start {
340 new_start = idx as u64 + 1 - lead_invis_seen;
341 new_levels_start = idx as u64 + 1;
342 break;
343 }
344 }
345 if *def > max_visible_def {
346 lead_invis_seen += 1;
347 }
348 }
349 }
350
351 rows_seen += 1;
352
353 let mut new_end = u64::MAX;
354 let mut new_levels_end = rep.len() as u64;
355 let new_start_is_visible = def[new_levels_start as usize] <= max_visible_def;
356 let mut tail_invis_seen = if new_start_is_visible { 0 } else { 1 };
357 for (idx, (rep, def)) in rep[(new_levels_start + 1) as usize..]
358 .iter()
359 .zip(&def[(new_levels_start + 1) as usize..])
360 .enumerate()
361 {
362 if *rep == max_rep {
363 rows_seen += 1;
364 if rows_seen == range.end + 1 {
365 new_end = idx as u64 + new_start + 1 - tail_invis_seen;
366 new_levels_end = idx as u64 + new_levels_start + 1;
367 break;
368 }
369 }
370 if *def > max_visible_def {
371 tail_invis_seen += 1;
372 }
373 }
374
375 if new_end == u64::MAX {
376 new_levels_end = rep.len() as u64;
377 let total_invis_seen = lead_invis_seen + tail_invis_seen;
378 new_end = rep.len() as u64 - total_invis_seen;
379 }
380
381 assert_ne!(new_end, u64::MAX);
382
383 if preamble_action == PreambleAction::Skip {
385 new_start += items_in_preamble;
386 new_end += items_in_preamble;
387 new_levels_start += first_row_start;
388 new_levels_end += first_row_start;
389 } else if preamble_action == PreambleAction::Take {
390 debug_assert_eq!(new_start, 0);
391 debug_assert_eq!(new_levels_start, 0);
392 new_end += items_in_preamble;
393 new_levels_end += first_row_start;
394 }
395
396 debug_assert!(new_end <= total_items);
397 (new_start..new_end, new_levels_start..new_levels_end)
398 } else {
399 if range.start > 0 {
405 for (idx, rep) in rep.iter().skip(1).enumerate() {
406 if *rep == max_rep {
407 rows_seen += 1;
408 if rows_seen == range.start {
409 new_start = idx as u64 + 1;
410 break;
411 }
412 }
413 }
414 }
415 let mut new_end = rep.len() as u64;
416 if range.end < total_items {
418 for (idx, rep) in rep[(new_start + 1) as usize..].iter().enumerate() {
419 if *rep == max_rep {
420 rows_seen += 1;
421 if rows_seen == range.end {
422 new_end = idx as u64 + new_start + 1;
423 break;
424 }
425 }
426 }
427 }
428
429 if preamble_action == PreambleAction::Skip {
431 new_start += first_row_start;
432 new_end += first_row_start;
433 } else if preamble_action == PreambleAction::Take {
434 debug_assert_eq!(new_start, 0);
435 new_end += first_row_start;
436 }
437
438 debug_assert!(new_end <= total_items);
439 (new_start..new_end, new_start..new_end)
440 }
441 } else {
442 (range.clone(), range)
445 }
446 }
447
448 fn read_buffer_sizes<const LARGE: bool>(
450 buf: &[u8],
451 offset: &mut usize,
452 num_buffers: u64,
453 ) -> Vec<u32> {
454 let read_size = if LARGE { 4 } else { 2 };
455 (0..num_buffers)
456 .map(|_| {
457 let bytes = &buf[*offset..*offset + read_size];
458 let size = if LARGE {
459 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
460 } else {
461 u16::from_le_bytes([bytes[0], bytes[1]]) as u32
463 };
464 *offset += read_size;
465 size
466 })
467 .collect()
468 }
469
470 fn decode_miniblock_chunk(
472 &self,
473 buf: &LanceBuffer,
474 items_in_chunk: u64,
475 ) -> Result<DecodedMiniBlockChunk> {
476 let mut offset = 0;
477 let num_levels = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
478 offset += 2;
479
480 let rep_size = if self.rep_decompressor.is_some() {
481 let rep_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
482 offset += 2;
483 Some(rep_size)
484 } else {
485 None
486 };
487 let def_size = if self.def_decompressor.is_some() {
488 let def_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
489 offset += 2;
490 Some(def_size)
491 } else {
492 None
493 };
494
495 let buffer_sizes = if self.has_large_chunk {
496 Self::read_buffer_sizes::<true>(buf, &mut offset, self.num_buffers)
497 } else {
498 Self::read_buffer_sizes::<false>(buf, &mut offset, self.num_buffers)
499 };
500
501 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
502
503 let rep = rep_size.map(|rep_size| {
504 let rep = buf.slice_with_length(offset, rep_size as usize);
505 offset += rep_size as usize;
506 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
507 rep
508 });
509
510 let def = def_size.map(|def_size| {
511 let def = buf.slice_with_length(offset, def_size as usize);
512 offset += def_size as usize;
513 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
514 def
515 });
516
517 let num_levels = Self::resolve_num_levels(
518 self.rep_decompressor.as_deref(),
519 rep.as_ref(),
520 self.def_decompressor.as_deref(),
521 def.as_ref(),
522 num_levels,
523 )?;
524
525 let buffers = buffer_sizes
526 .into_iter()
527 .map(|buf_size| {
528 let buf = buf.slice_with_length(offset, buf_size as usize);
529 offset += buf_size as usize;
530 offset += pad_bytes::<MINIBLOCK_ALIGNMENT>(offset);
531 buf
532 })
533 .collect::<Vec<_>>();
534
535 let values = self
536 .value_decompressor
537 .decompress(buffers, items_in_chunk)?;
538
539 let rep = rep
540 .map(|rep| {
541 Self::decode_levels(
542 self.rep_decompressor.as_ref().unwrap().as_ref(),
543 rep,
544 num_levels,
545 )
546 })
547 .transpose()?;
548 let def = def
549 .map(|def| {
550 Self::decode_levels(
551 self.def_decompressor.as_ref().unwrap().as_ref(),
552 def,
553 num_levels,
554 )
555 })
556 .transpose()?;
557
558 if let (Some(rep), Some(def)) = (&rep, &def)
559 && rep.len() != def.len()
560 {
561 return Err(Error::invalid_input_source(
562 format!(
563 "miniblock structural streams decoded different level counts: repetition {}, definition {}",
564 rep.len(),
565 def.len()
566 )
567 .into(),
568 ));
569 }
570
571 Ok(DecodedMiniBlockChunk { rep, def, values })
572 }
573}
574
575impl DecodePageTask for DecodeMiniBlockTask {
576 fn decode(self: Box<Self>) -> Result<DecodedPage> {
577 let mut repbuf: Option<LevelBuffer> = None;
579 let mut defbuf: Option<LevelBuffer> = None;
580
581 let max_rep = self.def_meaning.iter().filter(|l| l.is_list()).count() as u16;
582
583 let estimated_size_bytes = self.decoded_size_bytes().unwrap_or_else(|| {
584 self.instructions
586 .iter()
587 .map(|(_, chunk)| chunk.data.len() as u64)
588 .sum::<u64>()
589 * 2
590 });
591 let mut data_builder = DataBlockBuilder::with_capacity_estimate(estimated_size_bytes);
592
593 let mut level_offset = 0;
595
596 let needs_caching: Vec<bool> = self
598 .instructions
599 .windows(2)
600 .map(|w| w[0].1.chunk_idx == w[1].1.chunk_idx)
601 .chain(std::iter::once(false)) .collect();
603
604 let mut chunk_cache: Option<(usize, DecodedMiniBlockChunk)> = None;
606
607 for (idx, (instructions, chunk)) in self.instructions.iter().enumerate() {
609 let should_cache_this_chunk = needs_caching[idx];
610
611 let decoded_chunk = match &chunk_cache {
612 Some((cached_chunk_idx, cached_chunk)) if *cached_chunk_idx == chunk.chunk_idx => {
613 cached_chunk.clone()
615 }
616 _ => {
617 let decoded = self.decode_miniblock_chunk(&chunk.data, chunk.items_in_chunk)?;
619
620 if should_cache_this_chunk {
622 chunk_cache = Some((chunk.chunk_idx, decoded.clone()));
623 }
624 decoded
625 }
626 };
627
628 let DecodedMiniBlockChunk { rep, def, values } = decoded_chunk;
629
630 let row_range_start =
632 instructions.rows_to_skip + instructions.chunk_instructions.rows_to_skip;
633 let row_range_end = row_range_start + instructions.rows_to_take;
634
635 let (item_range, level_range) = Self::map_range(
637 row_range_start..row_range_end,
638 rep.as_ref(),
639 def.as_ref(),
640 max_rep,
641 self.max_visible_level,
642 chunk.items_in_chunk,
643 instructions.preamble_action,
644 );
645 if item_range.end - item_range.start > chunk.items_in_chunk {
646 return Err(lance_core::Error::internal(format!(
647 "Item range {:?} is greater than chunk items in chunk {:?}",
648 item_range, chunk.items_in_chunk
649 )));
650 }
651
652 Self::extend_levels(level_range.clone(), &mut repbuf, &rep, level_offset);
654 Self::extend_levels(level_range.clone(), &mut defbuf, &def, level_offset);
655 level_offset += (level_range.end - level_range.start) as usize;
656 data_builder.append(&values, item_range)?;
657 }
658
659 let mut data = data_builder.finish();
660
661 let unraveler =
662 RepDefUnraveler::new(repbuf, defbuf, self.def_meaning.clone(), data.num_values());
663
664 if let Some(dictionary) = &self.dictionary_data {
665 let DataBlock::FixedWidth(indices) = data else {
667 return Err(lance_core::Error::internal(format!(
668 "Expected FixedWidth DataBlock for dictionary indices, got {:?}",
669 data
670 )));
671 };
672 data = DataBlock::Dictionary(DictionaryDataBlock::from_parts(
673 indices,
674 dictionary.as_ref().clone(),
675 ));
676 }
677
678 Ok(DecodedPage {
679 data,
680 repdef: unraveler,
681 })
682 }
683}
684
685#[derive(Debug)]
690struct MiniBlockDecoder {
691 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
692 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
693 value_decompressor: Arc<dyn MiniBlockDecompressor>,
694 def_meaning: Arc<[DefinitionInterpretation]>,
695 loaded_chunks: VecDeque<LoadedChunk>,
696 instructions: VecDeque<ChunkInstructions>,
697 offset_in_current_chunk: u64,
698 num_rows: u64,
699 num_buffers: u64,
700 dictionary: Option<Arc<DataBlock>>,
701 has_large_chunk: bool,
702}
703
704impl StructuralPageDecoder for MiniBlockDecoder {
707 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>> {
708 let mut items_desired = num_rows;
709 let mut need_preamble = false;
710 let mut skip_in_chunk = self.offset_in_current_chunk;
711 let mut drain_instructions = Vec::new();
712 while items_desired > 0 || need_preamble {
713 let (instructions, consumed) = self
714 .instructions
715 .front()
716 .unwrap()
717 .drain_from_instruction(&mut items_desired, &mut need_preamble, &mut skip_in_chunk);
718
719 while self.loaded_chunks.front().unwrap().chunk_idx
720 != instructions.chunk_instructions.chunk_idx
721 {
722 self.loaded_chunks.pop_front();
723 }
724 drain_instructions.push((instructions, self.loaded_chunks.front().unwrap().clone()));
725 if consumed {
726 self.instructions.pop_front();
727 }
728 }
729 self.offset_in_current_chunk = skip_in_chunk;
732
733 let max_visible_level = max_visible_level(&self.def_meaning);
734
735 Ok(Box::new(DecodeMiniBlockTask {
736 instructions: drain_instructions,
737 def_decompressor: self.def_decompressor.clone(),
738 rep_decompressor: self.rep_decompressor.clone(),
739 value_decompressor: self.value_decompressor.clone(),
740 dictionary_data: self.dictionary.clone(),
741 def_meaning: self.def_meaning.clone(),
742 num_buffers: self.num_buffers,
743 max_visible_level,
744 has_large_chunk: self.has_large_chunk,
745 }))
746 }
747
748 fn num_rows(&self) -> u64 {
749 self.num_rows
750 }
751}
752
753#[derive(Debug, Clone)]
754struct MiniBlockSchedulerDictionary {
755 dictionary_decompressor: Arc<dyn BlockDecompressor>,
757 dictionary_buf_position_and_size: (u64, u64),
758 dictionary_data_alignment: u64,
759 num_dictionary_items: u64,
760}
761
762#[derive(Debug)]
764struct MiniBlockCacheableState {
765 chunk_index: MiniBlockChunkIndex,
767 dictionary: Option<Arc<DataBlock>>,
769}
770
771impl DeepSizeOf for MiniBlockCacheableState {
772 fn deep_size_of_children(&self, context: &mut Context) -> usize {
773 self.chunk_index.deep_size_of_children(context)
774 + self
775 .dictionary
776 .as_ref()
777 .map(|dict| dict.data_size() as usize)
778 .unwrap_or(0)
779 }
780}
781
782impl CachedPageData for MiniBlockCacheableState {
783 fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
784 self
785 }
786}
787
788#[derive(Debug)]
815pub struct MiniBlockScheduler {
816 pub(super) buffer_offsets_and_sizes: Vec<(u64, u64)>,
818 priority: u64,
819 items_in_page: u64,
820 repetition_index_depth: u16,
821 num_buffers: u64,
822 rep_decompressor: Option<Arc<dyn BlockDecompressor>>,
823 def_decompressor: Option<Arc<dyn BlockDecompressor>>,
824 value_decompressor: Arc<dyn MiniBlockDecompressor>,
825 def_meaning: Arc<[DefinitionInterpretation]>,
826 dictionary: Option<MiniBlockSchedulerDictionary>,
827 page_meta: Option<Arc<MiniBlockCacheableState>>,
829 has_large_chunk: bool,
830}
831
832impl MiniBlockScheduler {
833 pub(super) fn try_new(
834 buffer_offsets_and_sizes: &[(u64, u64)],
835 priority: u64,
836 items_in_page: u64,
837 layout: &metadata::MiniBlockLayout,
838 decompressors: &dyn DecompressionStrategy,
839 ) -> Result<Self> {
840 let rep_decompressor = layout
841 .rep_compression
842 .as_ref()
843 .map(|rep_compression| {
844 decompressors
845 .create_block_decompressor(rep_compression)
846 .map(Arc::from)
847 })
848 .transpose()?;
849 let def_decompressor = layout
850 .def_compression
851 .as_ref()
852 .map(|def_compression| {
853 decompressors
854 .create_block_decompressor(def_compression)
855 .map(Arc::from)
856 })
857 .transpose()?;
858 let def_meaning = layout
859 .layers
860 .iter()
861 .map(|l| EncodingUtils::repdef_layer_to_def_interp(*l))
862 .collect::<Vec<_>>();
863 let value_decompressor = decompressors.create_miniblock_decompressor(
864 layout.value_compression.as_ref().unwrap(),
865 decompressors,
866 )?;
867
868 let dictionary = if let Some(dictionary_encoding) = layout.dictionary.as_ref() {
869 let num_dictionary_items = layout.num_dictionary_items;
870 let dictionary_decompressor = decompressors
871 .create_block_decompressor(dictionary_encoding)?
872 .into();
873 let dictionary_data_alignment = match dictionary_encoding.compression.as_ref().unwrap()
874 {
875 Compression::Variable(_) => 4,
876 Compression::Flat(_) => 16,
877 Compression::General(_) => 1,
878 Compression::FixedSizeList(_) => 1,
879 Compression::Rle(_) => 1,
884 Compression::InlineBitpacking(_) | Compression::OutOfLineBitpacking(_) => {
885 crate::encoder::MIN_PAGE_BUFFER_ALIGNMENT
886 }
887 _ => {
888 return Err(Error::invalid_input_source(
889 format!(
890 "Unsupported mini-block dictionary encoding: {:?}",
891 dictionary_encoding.compression.as_ref().unwrap()
892 )
893 .into(),
894 ));
895 }
896 };
897 Some(MiniBlockSchedulerDictionary {
898 dictionary_decompressor,
899 dictionary_buf_position_and_size: buffer_offsets_and_sizes[2],
900 dictionary_data_alignment,
901 num_dictionary_items,
902 })
903 } else {
904 None
905 };
906
907 Ok(Self {
908 buffer_offsets_and_sizes: buffer_offsets_and_sizes.to_vec(),
909 rep_decompressor,
910 def_decompressor,
911 value_decompressor: value_decompressor.into(),
912 repetition_index_depth: layout.repetition_index_depth as u16,
913 num_buffers: layout.num_buffers,
914 priority,
915 items_in_page,
916 dictionary,
917 def_meaning: def_meaning.into(),
918 page_meta: None,
919 has_large_chunk: layout.has_large_chunk,
920 })
921 }
922
923 fn lookup_chunks(&self, chunk_indices: &[usize]) -> Vec<LoadedChunk> {
924 let chunk_index = &self.page_meta.as_ref().unwrap().chunk_index;
925 chunk_indices
926 .iter()
927 .map(|&chunk_idx| LoadedChunk {
928 byte_range: chunk_index.byte_range(chunk_idx),
929 items_in_chunk: chunk_index.items_in_chunk(chunk_idx),
930 chunk_idx,
931 data: LanceBuffer::empty(),
932 })
933 .collect()
934 }
935}
936
937#[derive(Debug, PartialEq, Eq, Clone, Copy)]
938enum PreambleAction {
939 Take,
940 Skip,
941 Absent,
942}
943
944#[derive(Clone, Debug, PartialEq, Eq)]
979struct ChunkInstructions {
980 chunk_idx: usize,
982 preamble: PreambleAction,
988 rows_to_skip: u64,
992 rows_to_take: u64,
995 take_trailer: bool,
1002}
1003
1004#[derive(Debug, PartialEq, Eq)]
1022struct ChunkDrainInstructions {
1023 chunk_instructions: ChunkInstructions,
1024 rows_to_skip: u64,
1025 rows_to_take: u64,
1026 preamble_action: PreambleAction,
1027}
1028
1029impl ChunkInstructions {
1030 fn schedule_instructions(
1036 chunk_index: &MiniBlockChunkIndex,
1037 user_ranges: &[Range<u64>],
1038 ) -> Vec<Self> {
1039 let num_chunks = chunk_index.num_chunks();
1042 let mut chunk_instructions = Vec::with_capacity(user_ranges.len());
1046
1047 for user_range in user_ranges {
1048 let mut rows_needed = user_range.end - user_range.start;
1049 let mut need_preamble = false;
1050
1051 let mut block_index = chunk_index.find_chunk(user_range.start);
1054
1055 let mut to_skip = user_range.start - chunk_index.first_row(block_index);
1056
1057 while rows_needed > 0 || need_preamble {
1058 if block_index >= num_chunks {
1060 log::warn!(
1061 "schedule_instructions inconsistency: block_index >= num_chunks, exiting early"
1062 );
1063 break;
1064 }
1065
1066 let starts_including_trailer = chunk_index.rows_in_chunk(block_index);
1067 let has_preamble = chunk_index.has_preamble(block_index);
1068 let has_trailer = chunk_index.has_trailer(block_index);
1069 let rows_avail = starts_including_trailer.saturating_sub(to_skip);
1070
1071 if rows_avail == 0 && to_skip == 0 {
1075 if has_preamble && need_preamble {
1077 chunk_instructions.push(Self {
1078 chunk_idx: block_index,
1079 preamble: PreambleAction::Take,
1080 rows_to_skip: 0,
1081 rows_to_take: 0,
1082 take_trailer: has_trailer,
1086 });
1087 if starts_including_trailer > 0 || block_index == num_chunks - 1 {
1091 need_preamble = false;
1092 }
1093 }
1094 block_index += 1;
1096 continue;
1097 }
1098
1099 if rows_avail == 0 && to_skip > 0 {
1103 to_skip -= starts_including_trailer;
1106 block_index += 1;
1107 continue;
1108 }
1109
1110 let rows_to_take = rows_avail.min(rows_needed);
1111 rows_needed -= rows_to_take;
1112
1113 let mut take_trailer = false;
1114 let preamble = if has_preamble {
1115 if need_preamble {
1116 PreambleAction::Take
1117 } else {
1118 PreambleAction::Skip
1119 }
1120 } else {
1121 PreambleAction::Absent
1122 };
1123
1124 if rows_to_take == rows_avail && has_trailer {
1126 take_trailer = true;
1127 need_preamble = true;
1128 } else {
1129 need_preamble = false;
1130 };
1131
1132 chunk_instructions.push(Self {
1133 preamble,
1134 chunk_idx: block_index,
1135 rows_to_skip: to_skip,
1136 rows_to_take,
1137 take_trailer,
1138 });
1139
1140 to_skip = 0;
1141 block_index += 1;
1142 }
1143 }
1144
1145 if user_ranges.len() > 1 {
1149 let mut write = 0;
1153 for read in 1..chunk_instructions.len() {
1154 let merges = {
1155 let last = &chunk_instructions[write];
1156 let candidate = &chunk_instructions[read];
1157 last.chunk_idx == candidate.chunk_idx
1158 && last.rows_to_take + last.rows_to_skip == candidate.rows_to_skip
1159 };
1160 if merges {
1161 let rows_to_take = chunk_instructions[read].rows_to_take;
1162 let take_trailer = chunk_instructions[read].take_trailer;
1163 let last = &mut chunk_instructions[write];
1164 last.rows_to_take += rows_to_take;
1165 last.take_trailer |= take_trailer;
1166 } else {
1167 write += 1;
1168 if write != read {
1169 chunk_instructions.swap(write, read);
1170 }
1171 }
1172 }
1173 chunk_instructions.truncate(write + 1);
1174 }
1175 chunk_instructions
1176 }
1177
1178 fn drain_from_instruction(
1179 &self,
1180 rows_desired: &mut u64,
1181 need_preamble: &mut bool,
1182 skip_in_chunk: &mut u64,
1183 ) -> (ChunkDrainInstructions, bool) {
1184 debug_assert!(!*need_preamble || *skip_in_chunk == 0);
1186 let rows_avail = self.rows_to_take - *skip_in_chunk;
1187 let has_preamble = self.preamble != PreambleAction::Absent;
1188 let preamble_action = match (*need_preamble, has_preamble) {
1189 (true, true) => PreambleAction::Take,
1190 (true, false) => panic!("Need preamble but there isn't one"),
1191 (false, true) => PreambleAction::Skip,
1192 (false, false) => PreambleAction::Absent,
1193 };
1194
1195 let rows_taking = if *rows_desired >= rows_avail {
1198 *need_preamble = self.take_trailer;
1206 rows_avail
1207 } else {
1208 *need_preamble = false;
1211 *rows_desired
1212 };
1213 let rows_skipped = *skip_in_chunk;
1214
1215 let consumed_chunk = if *rows_desired >= rows_avail {
1217 *rows_desired -= rows_avail;
1218 *skip_in_chunk = 0;
1219 true
1220 } else {
1221 *skip_in_chunk += *rows_desired;
1222 *rows_desired = 0;
1223 false
1224 };
1225
1226 (
1227 ChunkDrainInstructions {
1228 chunk_instructions: self.clone(),
1229 rows_to_skip: rows_skipped,
1230 rows_to_take: rows_taking,
1231 preamble_action,
1232 },
1233 consumed_chunk,
1234 )
1235 }
1236}
1237
1238enum Words {
1239 U16(ScalarBuffer<u16>),
1240 U32(ScalarBuffer<u32>),
1241}
1242
1243struct WordsIter<'a> {
1244 iter: Box<dyn Iterator<Item = u32> + 'a>,
1245}
1246
1247impl Words {
1248 pub fn len(&self) -> usize {
1249 match self {
1250 Self::U16(b) => b.len(),
1251 Self::U32(b) => b.len(),
1252 }
1253 }
1254
1255 pub fn iter(&self) -> WordsIter<'_> {
1256 match self {
1257 Self::U16(buf) => WordsIter {
1258 iter: Box::new(buf.iter().map(|&x| x as u32)),
1259 },
1260 Self::U32(buf) => WordsIter {
1261 iter: Box::new(buf.iter().copied()),
1262 },
1263 }
1264 }
1265
1266 pub fn from_bytes(bytes: Bytes, has_large_chunk: bool) -> Result<Self> {
1267 let bytes_per_value = if has_large_chunk { 4 } else { 2 };
1268 assert_eq!(bytes.len() % bytes_per_value, 0);
1269 let buffer = LanceBuffer::from_bytes(bytes, bytes_per_value as u64);
1270 if has_large_chunk {
1271 Ok(Self::U32(buffer.borrow_to_typed_slice::<u32>()))
1272 } else {
1273 Ok(Self::U16(buffer.borrow_to_typed_slice::<u16>()))
1274 }
1275 }
1276}
1277
1278impl<'a> Iterator for WordsIter<'a> {
1279 type Item = u32;
1280
1281 fn next(&mut self) -> Option<Self::Item> {
1282 self.iter.next()
1283 }
1284}
1285
1286struct FlatValueCounts {
1291 logs: Vec<u8>,
1292 uniform: bool,
1293 values_per_chunk: u64,
1294 last_chunk_values: u64,
1295}
1296
1297fn analyze_value_counts(words: &Words, items_in_page: u64) -> Result<FlatValueCounts> {
1298 let num_chunks = words.len();
1299 let logs = words.iter().map(|w| (w & 0x0F) as u8).collect::<Vec<_>>();
1300 let mut counted = 0u64;
1301 for (chunk_index, &log) in logs.iter().take(num_chunks.saturating_sub(1)).enumerate() {
1302 if log == 0 {
1303 return Err(Error::corrupt_file_named(
1304 "miniblock_metadata",
1305 format!(
1306 "non-final chunk {chunk_index} of {num_chunks} has invalid log_num_values=0"
1307 ),
1308 ));
1309 }
1310 counted = counted.checked_add(1u64 << log).ok_or_else(|| {
1311 Error::corrupt_file_named(
1312 "miniblock_metadata",
1313 format!(
1314 "value count overflow at chunk {chunk_index}: counted_values={counted}, \
1315 log_num_values={log}, items_in_page={items_in_page}"
1316 ),
1317 )
1318 })?;
1319 }
1320 let last_chunk_values = items_in_page.checked_sub(counted).ok_or_else(|| {
1321 Error::corrupt_file_named(
1322 "miniblock_metadata",
1323 format!(
1324 "non-final chunks account for counted_values={counted}, exceeding \
1325 items_in_page={items_in_page}"
1326 ),
1327 )
1328 })?;
1329 if let Some(&last_log) = logs.last()
1330 && last_log != 0
1331 && (1u64 << last_log) != last_chunk_values
1332 {
1333 return Err(Error::corrupt_file_named(
1334 "miniblock_metadata",
1335 format!(
1336 "final chunk log_num_values={last_log} does not match \
1337 last_chunk_values={last_chunk_values}: counted_values={counted}, \
1338 items_in_page={items_in_page}"
1339 ),
1340 ));
1341 }
1342 let uniform = num_chunks <= 1 || logs[..num_chunks - 1].iter().all(|&log| log == logs[0]);
1343 let values_per_chunk = if num_chunks <= 1 {
1346 items_in_page.max(1)
1347 } else {
1348 1u64 << logs[0]
1349 };
1350 Ok(FlatValueCounts {
1351 logs,
1352 uniform,
1353 values_per_chunk,
1354 last_chunk_values,
1355 })
1356}
1357
1358fn flat_value_counts_iter(logs: &[u8], last_chunk_values: u64) -> impl Iterator<Item = u64> + '_ {
1361 let num_chunks = logs.len();
1362 (0..num_chunks).map(move |i| {
1363 if i + 1 < num_chunks {
1364 1u64 << logs[i]
1365 } else {
1366 last_chunk_values
1367 }
1368 })
1369}
1370
1371fn build_chunk_index(
1376 words: &Words,
1377 items_in_page: u64,
1378 base: u64,
1379 data_buf_size: u64,
1380 rep_index_bytes: Option<&[u8]>,
1381 repetition_index_depth: u16,
1382) -> Result<MiniBlockChunkIndex> {
1383 let num_chunks = words.len();
1384 let value_counts = analyze_value_counts(words, items_in_page)?;
1387
1388 let byte_starts = PrefixSums::from_deltas(
1391 words
1392 .iter()
1393 .map(|word| ((word >> 4) as u64 + 1) * MINIBLOCK_ALIGNMENT as u64),
1394 num_chunks,
1395 data_buf_size,
1396 );
1397
1398 let rows = if let Some(rep_index_data) = rep_index_bytes {
1401 assert!(rep_index_data.len() % 8 == 0);
1402 let stride = repetition_index_depth as usize + 1;
1403 let (row_starts, has_trailer) = parse_nested_rep(rep_index_data, stride);
1404 let item_counts = if value_counts.uniform {
1405 ItemCounts::Uniform {
1406 values_per_chunk: value_counts.values_per_chunk,
1407 last_chunk_values: value_counts.last_chunk_values,
1408 }
1409 } else {
1410 ItemCounts::PerChunkLog {
1411 logs: value_counts.logs,
1412 last_chunk_values: value_counts.last_chunk_values,
1413 }
1414 };
1415 RowMapping::Nested {
1416 row_starts,
1417 has_trailer,
1418 item_counts,
1419 }
1420 } else {
1421 if value_counts.uniform {
1422 RowMapping::UniformFlat {
1423 values_per_chunk: value_counts.values_per_chunk,
1424 last_chunk_values: value_counts.last_chunk_values,
1425 num_chunks,
1426 }
1427 } else {
1428 let value_starts = PrefixSums::from_deltas(
1429 flat_value_counts_iter(&value_counts.logs, value_counts.last_chunk_values),
1430 num_chunks,
1431 items_in_page,
1432 );
1433 RowMapping::Flat { value_starts }
1434 }
1435 };
1436
1437 Ok(MiniBlockChunkIndex::new(base, byte_starts, rows))
1438}
1439
1440impl StructuralPageScheduler for MiniBlockScheduler {
1441 fn init_layout(&self) -> Result<PageInitialization> {
1442 let (meta_buf_position, meta_buf_size) = self.buffer_offsets_and_sizes[0];
1446 let dictionary_range = self.dictionary.as_ref().map(|dictionary| {
1447 dictionary.dictionary_buf_position_and_size.0
1448 ..dictionary.dictionary_buf_position_and_size.0
1449 + dictionary.dictionary_buf_position_and_size.1
1450 });
1451 let repetition_index_range = if self.repetition_index_depth > 0 {
1452 let (rep_index_pos, rep_index_size) =
1453 self.buffer_offsets_and_sizes.last().ok_or_else(|| {
1454 Error::invalid_input_source(
1455 "Mini-block page has a repetition index but no buffers".into(),
1456 )
1457 })?;
1458 Some(*rep_index_pos..*rep_index_pos + *rep_index_size)
1459 } else {
1460 None
1461 };
1462 Ok(PageInitialization::new([
1463 Some(meta_buf_position..meta_buf_position + meta_buf_size),
1464 dictionary_range,
1465 repetition_index_range,
1466 ]))
1467 }
1468
1469 fn init_from_buffers<'a>(
1470 &'a mut self,
1471 buffers: PageInitializationBuffers,
1472 _io: &Arc<dyn EncodingsIo>,
1473 ) -> BoxFuture<'a, Result<Arc<dyn CachedPageData>>> {
1474 let base = self.buffer_offsets_and_sizes[1].0;
1475 let data_buf_size = self.buffer_offsets_and_sizes[1].1;
1476
1477 async move {
1478 let [meta_bytes, dictionary_bytes, rep_index_bytes] =
1479 buffers.try_into_array("mini-block")?;
1480 let meta_bytes = meta_bytes.ok_or_else(|| {
1481 Error::internal("Mini-block page initialization is missing chunk metadata")
1482 })?;
1483
1484 let words = Words::from_bytes(meta_bytes, self.has_large_chunk)?;
1485 let chunk_index = build_chunk_index(
1486 &words,
1487 self.items_in_page,
1488 base,
1489 data_buf_size,
1490 rep_index_bytes.as_deref(),
1491 self.repetition_index_depth,
1492 )?;
1493
1494 let dictionary = match (self.dictionary.as_ref(), dictionary_bytes) {
1496 (Some(dictionary), Some(dictionary_data)) => {
1497 Some(Arc::new(dictionary.dictionary_decompressor.decompress(
1498 Some(LanceBuffer::from_bytes(
1499 dictionary_data,
1500 dictionary.dictionary_data_alignment,
1501 )),
1502 dictionary.num_dictionary_items,
1503 )?))
1504 }
1505 (None, None) => None,
1506 _ => {
1507 return Err(Error::internal(
1508 "Mini-block page initialization dictionary slot does not match its layout",
1509 ));
1510 }
1511 };
1512
1513 let page_meta = Arc::new(MiniBlockCacheableState {
1514 chunk_index,
1515 dictionary,
1516 });
1517 Ok(page_meta as Arc<dyn CachedPageData>)
1518 }
1519 .boxed()
1520 }
1521
1522 fn try_load(&mut self, data: &Arc<dyn CachedPageData>) -> Result<()> {
1523 self.page_meta = Some(data.clone().as_arc_any().downcast().map_err(|_| {
1524 Error::invalid_input_source("Cached mini-block page data has an unexpected type".into())
1525 })?);
1526 Ok(())
1527 }
1528
1529 fn schedule_ranges(
1530 &self,
1531 ranges: &[Range<u64>],
1532 io: &Arc<dyn EncodingsIo>,
1533 ) -> Result<Vec<PageLoadTask>> {
1534 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
1535
1536 let page_meta = self.page_meta.as_ref().unwrap();
1537
1538 let chunk_instructions =
1539 ChunkInstructions::schedule_instructions(&page_meta.chunk_index, ranges);
1540
1541 debug_assert_eq!(
1542 num_rows,
1543 chunk_instructions
1544 .iter()
1545 .map(|ci| ci.rows_to_take)
1546 .sum::<u64>()
1547 );
1548
1549 let chunks_needed = chunk_instructions
1550 .iter()
1551 .map(|ci| ci.chunk_idx)
1552 .unique()
1553 .collect::<Vec<_>>();
1554
1555 let mut loaded_chunks = self.lookup_chunks(&chunks_needed);
1556 let chunk_ranges = loaded_chunks
1557 .iter()
1558 .map(|c| c.byte_range.clone())
1559 .collect::<Vec<_>>();
1560 let loaded_chunk_data = io.submit_request(chunk_ranges, self.priority);
1561
1562 let rep_decompressor = self.rep_decompressor.clone();
1563 let def_decompressor = self.def_decompressor.clone();
1564 let value_decompressor = self.value_decompressor.clone();
1565 let num_buffers = self.num_buffers;
1566 let has_large_chunk = self.has_large_chunk;
1567 let dictionary = page_meta
1568 .dictionary
1569 .as_ref()
1570 .map(|dictionary| dictionary.clone());
1571 let def_meaning = self.def_meaning.clone();
1572
1573 let res = async move {
1574 let loaded_chunk_data = loaded_chunk_data.await?;
1575 for (loaded_chunk, chunk_data) in loaded_chunks.iter_mut().zip(loaded_chunk_data) {
1576 loaded_chunk.data = LanceBuffer::from_bytes(chunk_data, 1);
1577 }
1578
1579 Ok(Box::new(MiniBlockDecoder {
1580 rep_decompressor,
1581 def_decompressor,
1582 value_decompressor,
1583 def_meaning,
1584 loaded_chunks: VecDeque::from_iter(loaded_chunks),
1585 instructions: VecDeque::from(chunk_instructions),
1586 offset_in_current_chunk: 0,
1587 dictionary,
1588 num_rows,
1589 num_buffers,
1590 has_large_chunk,
1591 }) as Box<dyn StructuralPageDecoder>)
1592 }
1593 .boxed();
1594 let page_load_task = PageLoadTask {
1595 decoder_fut: res,
1596 num_rows,
1597 };
1598 Ok(vec![page_load_task])
1599 }
1600}
1601
1602#[cfg(test)]
1603#[allow(clippy::single_range_in_vec_init)]
1604mod tests {
1605 use std::collections::VecDeque;
1606 use std::sync::Arc;
1607
1608 use bytes::Bytes;
1609 use lance_core::cache::{Context, DeepSizeOf};
1610 use rstest::rstest;
1611
1612 use crate::buffer::LanceBuffer;
1613 use crate::compression::{DefaultDecompressionStrategy, MiniBlockDecompressor};
1614 use crate::data::{BlockInfo, DataBlock, FixedWidthDataBlock};
1615 use crate::decoder::DecodePageTask;
1616 use crate::encodings::logical::primitive::chunk_index::{
1617 MiniBlockChunkIndex, PrefixSums, RowMapping,
1618 };
1619 use crate::encodings::logical::primitive::tests::encoded_u16_frame;
1620 use crate::encodings::logical::primitive::{LoadedChunk, MINIBLOCK_ALIGNMENT};
1621 use crate::encodings::physical::rle::{RleDecompressor, RunLengthWidth};
1622 use crate::encodings::physical::value::ValueDecompressor;
1623 use crate::format::{EncodingUtils, metadata};
1624
1625 use super::{
1626 ChunkDrainInstructions, ChunkInstructions, DecodeMiniBlockTask, PreambleAction, Words,
1627 build_chunk_index,
1628 };
1629
1630 #[test]
1631 fn test_miniblock_decode_uses_exact_fixed_width_output_size() {
1632 #[derive(Debug)]
1633 struct FixedWidthMiniBlockDecompressor;
1634
1635 impl MiniBlockDecompressor for FixedWidthMiniBlockDecompressor {
1636 fn decompress(
1637 &self,
1638 data: Vec<LanceBuffer>,
1639 num_values: u64,
1640 ) -> crate::Result<DataBlock> {
1641 assert_eq!(data.len(), 1);
1642 Ok(DataBlock::FixedWidth(FixedWidthDataBlock {
1643 data: data.into_iter().next().unwrap(),
1644 bits_per_value: 32,
1645 num_values,
1646 block_info: BlockInfo::new(),
1647 }))
1648 }
1649
1650 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
1651 num_values.checked_mul(4)
1652 }
1653 }
1654
1655 let num_rows = 512;
1656 let expected_size = num_rows * 4;
1657 let mut chunk_data = Vec::new();
1658 chunk_data.extend_from_slice(&0_u16.to_le_bytes());
1659 chunk_data.extend_from_slice(&(expected_size as u16).to_le_bytes());
1660 let header_padding =
1661 lance_core::utils::bit::pad_bytes::<{ super::MINIBLOCK_ALIGNMENT }>(chunk_data.len());
1662 chunk_data.resize(chunk_data.len() + header_padding, 0);
1663 chunk_data.resize(chunk_data.len() + expected_size as usize, 7);
1664
1665 let task = DecodeMiniBlockTask {
1666 rep_decompressor: None,
1667 def_decompressor: None,
1668 value_decompressor: Arc::new(FixedWidthMiniBlockDecompressor),
1669 dictionary_data: None,
1670 def_meaning: Arc::from([]),
1671 num_buffers: 1,
1672 max_visible_level: 0,
1673 instructions: vec![(
1674 ChunkDrainInstructions {
1675 chunk_instructions: ChunkInstructions {
1676 chunk_idx: 0,
1677 preamble: PreambleAction::Absent,
1678 rows_to_skip: 0,
1679 rows_to_take: num_rows,
1680 take_trailer: false,
1681 },
1682 rows_to_skip: 0,
1683 rows_to_take: num_rows,
1684 preamble_action: PreambleAction::Absent,
1685 },
1686 LoadedChunk {
1687 byte_range: 0..chunk_data.len() as u64,
1688 data: LanceBuffer::from(chunk_data),
1689 items_in_chunk: num_rows,
1690 chunk_idx: 0,
1691 },
1692 )],
1693 has_large_chunk: false,
1694 };
1695
1696 let decoded = Box::new(task).decode().unwrap();
1697 let values = decoded.data.as_fixed_width_ref().unwrap();
1698 assert_eq!(values.data.len(), expected_size as usize);
1699 assert_eq!(
1700 values.data.clone().into_buffer().capacity(),
1701 expected_size as usize
1702 );
1703 }
1704
1705 #[test]
1706 fn test_map_range() {
1707 let rep = Some(vec![1, 0, 0, 1, 0, 1, 1, 0, 0]);
1710 let def = Some(vec![0, 0, 0, 0, 0, 1, 0, 0, 0]);
1711 let max_visible_def = 0;
1712 let total_items = 8;
1713 let max_rep = 1;
1714
1715 let check = |range, expected_item_range, expected_level_range| {
1716 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1717 range,
1718 rep.as_ref(),
1719 def.as_ref(),
1720 max_rep,
1721 max_visible_def,
1722 total_items,
1723 PreambleAction::Absent,
1724 );
1725 assert_eq!(item_range, expected_item_range);
1726 assert_eq!(level_range, expected_level_range);
1727 };
1728
1729 check(0..1, 0..3, 0..3);
1730 check(1..2, 3..5, 3..5);
1731 check(2..3, 5..5, 5..6);
1732 check(3..4, 5..8, 6..9);
1733 check(0..2, 0..5, 0..5);
1734 check(1..3, 3..5, 3..6);
1735 check(2..4, 5..8, 5..9);
1736 check(0..3, 0..5, 0..6);
1737 check(1..4, 3..8, 3..9);
1738 check(0..4, 0..8, 0..9);
1739
1740 let rep = Some(vec![1, 1, 0, 1]);
1743 let def = Some(vec![1, 0, 0, 0]);
1744 let max_visible_def = 0;
1745 let total_items = 3;
1746
1747 let check = |range, expected_item_range, expected_level_range| {
1748 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1749 range,
1750 rep.as_ref(),
1751 def.as_ref(),
1752 max_rep,
1753 max_visible_def,
1754 total_items,
1755 PreambleAction::Absent,
1756 );
1757 assert_eq!(item_range, expected_item_range);
1758 assert_eq!(level_range, expected_level_range);
1759 };
1760
1761 check(0..1, 0..0, 0..1);
1762 check(1..2, 0..2, 1..3);
1763 check(2..3, 2..3, 3..4);
1764 check(0..2, 0..2, 0..3);
1765 check(1..3, 0..3, 1..4);
1766 check(0..3, 0..3, 0..4);
1767
1768 let rep = Some(vec![1, 1, 0, 1]);
1771 let def = Some(vec![0, 0, 0, 1]);
1772 let max_visible_def = 0;
1773 let total_items = 3;
1774
1775 let check = |range, expected_item_range, expected_level_range| {
1776 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1777 range,
1778 rep.as_ref(),
1779 def.as_ref(),
1780 max_rep,
1781 max_visible_def,
1782 total_items,
1783 PreambleAction::Absent,
1784 );
1785 assert_eq!(item_range, expected_item_range);
1786 assert_eq!(level_range, expected_level_range);
1787 };
1788
1789 check(0..1, 0..1, 0..1);
1790 check(1..2, 1..3, 1..3);
1791 check(2..3, 3..3, 3..4);
1792 check(0..2, 0..3, 0..3);
1793 check(1..3, 1..3, 1..4);
1794 check(0..3, 0..3, 0..4);
1795
1796 let rep = Some(vec![1, 0, 1, 0, 1, 0]);
1799 let def: Option<&[u16]> = None;
1800 let max_visible_def = 0;
1801 let total_items = 6;
1802
1803 let check = |range, expected_item_range, expected_level_range| {
1804 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1805 range,
1806 rep.as_ref(),
1807 def.as_ref(),
1808 max_rep,
1809 max_visible_def,
1810 total_items,
1811 PreambleAction::Absent,
1812 );
1813 assert_eq!(item_range, expected_item_range);
1814 assert_eq!(level_range, expected_level_range);
1815 };
1816
1817 check(0..1, 0..2, 0..2);
1818 check(1..2, 2..4, 2..4);
1819 check(2..3, 4..6, 4..6);
1820 check(0..2, 0..4, 0..4);
1821 check(1..3, 2..6, 2..6);
1822 check(0..3, 0..6, 0..6);
1823
1824 let rep: Option<&[u16]> = None;
1827 let def = Some(vec![0, 0, 1, 0]);
1828 let max_visible_def = 1;
1829 let total_items = 4;
1830
1831 let check = |range, expected_item_range, expected_level_range| {
1832 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1833 range,
1834 rep.as_ref(),
1835 def.as_ref(),
1836 max_rep,
1837 max_visible_def,
1838 total_items,
1839 PreambleAction::Absent,
1840 );
1841 assert_eq!(item_range, expected_item_range);
1842 assert_eq!(level_range, expected_level_range);
1843 };
1844
1845 check(0..1, 0..1, 0..1);
1846 check(1..2, 1..2, 1..2);
1847 check(2..3, 2..3, 2..3);
1848 check(0..2, 0..2, 0..2);
1849 check(1..3, 1..3, 1..3);
1850 check(0..3, 0..3, 0..3);
1851
1852 let rep = Some(vec![0, 1, 0, 1]);
1857 let def = Some(vec![0, 0, 0, 1]);
1858 let max_visible_def = 0;
1859 let total_items = 3;
1860
1861 let check = |range, expected_item_range, expected_level_range| {
1862 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1863 range,
1864 rep.as_ref(),
1865 def.as_ref(),
1866 max_rep,
1867 max_visible_def,
1868 total_items,
1869 PreambleAction::Take,
1870 );
1871 assert_eq!(item_range, expected_item_range);
1872 assert_eq!(level_range, expected_level_range);
1873 };
1874
1875 check(0..1, 0..3, 0..3);
1877 check(0..2, 0..3, 0..4);
1878
1879 let check = |range, expected_item_range, expected_level_range| {
1880 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1881 range,
1882 rep.as_ref(),
1883 def.as_ref(),
1884 max_rep,
1885 max_visible_def,
1886 total_items,
1887 PreambleAction::Skip,
1888 );
1889 assert_eq!(item_range, expected_item_range);
1890 assert_eq!(level_range, expected_level_range);
1891 };
1892
1893 check(0..1, 1..3, 1..3);
1894 check(1..2, 3..3, 3..4);
1895 check(0..2, 1..3, 1..4);
1896
1897 let rep = Some(vec![0, 1, 1, 0]);
1902 let def = Some(vec![0, 1, 0, 0]);
1903 let max_visible_def = 0;
1904 let total_items = 4;
1905
1906 let check = |range, expected_item_range, expected_level_range| {
1907 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1908 range,
1909 rep.as_ref(),
1910 def.as_ref(),
1911 max_rep,
1912 max_visible_def,
1913 total_items,
1914 PreambleAction::Take,
1915 );
1916 assert_eq!(item_range, expected_item_range);
1917 assert_eq!(level_range, expected_level_range);
1918 };
1919
1920 check(0..1, 0..1, 0..2);
1922 check(0..2, 0..3, 0..4);
1923
1924 let check = |range, expected_item_range, expected_level_range| {
1925 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1926 range,
1927 rep.as_ref(),
1928 def.as_ref(),
1929 max_rep,
1930 max_visible_def,
1931 total_items,
1932 PreambleAction::Skip,
1933 );
1934 assert_eq!(item_range, expected_item_range);
1935 assert_eq!(level_range, expected_level_range);
1936 };
1937
1938 check(0..1, 1..1, 1..2);
1940 check(1..2, 1..3, 2..4);
1941 check(0..2, 1..3, 1..4);
1942
1943 let rep = Some(vec![0, 1, 0, 1]);
1946 let def: Option<Vec<u16>> = None;
1947 let max_visible_def = 0;
1948 let total_items = 4;
1949
1950 let check = |range, expected_item_range, expected_level_range| {
1951 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1952 range,
1953 rep.as_ref(),
1954 def.as_ref(),
1955 max_rep,
1956 max_visible_def,
1957 total_items,
1958 PreambleAction::Take,
1959 );
1960 assert_eq!(item_range, expected_item_range);
1961 assert_eq!(level_range, expected_level_range);
1962 };
1963
1964 check(0..1, 0..3, 0..3);
1966 check(0..2, 0..4, 0..4);
1967
1968 let check = |range, expected_item_range, expected_level_range| {
1969 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1970 range,
1971 rep.as_ref(),
1972 def.as_ref(),
1973 max_rep,
1974 max_visible_def,
1975 total_items,
1976 PreambleAction::Skip,
1977 );
1978 assert_eq!(item_range, expected_item_range);
1979 assert_eq!(level_range, expected_level_range);
1980 };
1981
1982 check(0..1, 1..3, 1..3);
1983 check(1..2, 3..4, 3..4);
1984 check(0..2, 1..4, 1..4);
1985
1986 let rep = Some(vec![2, 1, 2, 0, 1, 2]);
1990 let def = Some(vec![0, 1, 2, 0, 0, 0]);
1991 let max_rep = 2;
1992 let max_visible_def = 0;
1993 let total_items = 4;
1994
1995 let check = |range, expected_item_range, expected_level_range| {
1996 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
1997 range,
1998 rep.as_ref(),
1999 def.as_ref(),
2000 max_rep,
2001 max_visible_def,
2002 total_items,
2003 PreambleAction::Absent,
2004 );
2005 assert_eq!(item_range, expected_item_range);
2006 assert_eq!(level_range, expected_level_range);
2007 };
2008
2009 check(0..3, 0..4, 0..6);
2010 check(0..1, 0..1, 0..2);
2011 check(1..2, 1..3, 2..5);
2012 check(2..3, 3..4, 5..6);
2013
2014 let rep = Some(vec![0, 0, 1, 0, 1, 1]);
2016 let def = Some(vec![0, 1, 0, 0, 0, 0]);
2017 let max_rep = 1;
2018 let max_visible_def = 0;
2019 let total_items = 5;
2020
2021 let check = |range, expected_item_range, expected_level_range| {
2022 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
2023 range,
2024 rep.as_ref(),
2025 def.as_ref(),
2026 max_rep,
2027 max_visible_def,
2028 total_items,
2029 PreambleAction::Take,
2030 );
2031 assert_eq!(item_range, expected_item_range);
2032 assert_eq!(level_range, expected_level_range);
2033 };
2034
2035 check(0..0, 0..1, 0..2);
2036 check(0..1, 0..3, 0..4);
2037 check(0..2, 0..4, 0..5);
2038
2039 let rep = Some(vec![0, 1, 0, 1, 0, 1, 0, 1]);
2042 let def = Some(vec![1, 0, 1, 1, 0, 0, 0, 0]);
2043 let max_rep = 1;
2044 let max_visible_def = 0;
2045 let total_items = 5;
2046
2047 let check = |range, expected_item_range, expected_level_range| {
2048 let (item_range, level_range) = DecodeMiniBlockTask::map_range(
2049 range,
2050 rep.as_ref(),
2051 def.as_ref(),
2052 max_rep,
2053 max_visible_def,
2054 total_items,
2055 PreambleAction::Skip,
2056 );
2057 assert_eq!(item_range, expected_item_range);
2058 assert_eq!(level_range, expected_level_range);
2059 };
2060
2061 check(2..3, 2..4, 5..7);
2062 }
2063
2064 #[test]
2065 fn test_schedule_instructions() {
2066 let rep_data: Vec<u64> = vec![5, 2, 3, 0, 4, 7, 2, 0];
2068 let rep_bytes: Vec<u8> = rep_data.iter().flat_map(|v| v.to_le_bytes()).collect();
2069 let chunk_index = MiniBlockChunkIndex::new_nested_for_test(&rep_bytes, 2);
2070
2071 let check = |user_ranges, expected_instructions| {
2072 let instructions = ChunkInstructions::schedule_instructions(&chunk_index, user_ranges);
2073 assert_eq!(instructions, expected_instructions);
2074 };
2075
2076 let expected_take_all = vec![
2078 ChunkInstructions {
2079 chunk_idx: 0,
2080 preamble: PreambleAction::Absent,
2081 rows_to_skip: 0,
2082 rows_to_take: 6,
2083 take_trailer: true,
2084 },
2085 ChunkInstructions {
2086 chunk_idx: 1,
2087 preamble: PreambleAction::Take,
2088 rows_to_skip: 0,
2089 rows_to_take: 2,
2090 take_trailer: false,
2091 },
2092 ChunkInstructions {
2093 chunk_idx: 2,
2094 preamble: PreambleAction::Absent,
2095 rows_to_skip: 0,
2096 rows_to_take: 5,
2097 take_trailer: true,
2098 },
2099 ChunkInstructions {
2100 chunk_idx: 3,
2101 preamble: PreambleAction::Take,
2102 rows_to_skip: 0,
2103 rows_to_take: 1,
2104 take_trailer: false,
2105 },
2106 ];
2107
2108 check(&[0..14], expected_take_all.clone());
2110
2111 check(
2113 &[
2114 0..1,
2115 1..2,
2116 2..3,
2117 3..4,
2118 4..5,
2119 5..6,
2120 6..7,
2121 7..8,
2122 8..9,
2123 9..10,
2124 10..11,
2125 11..12,
2126 12..13,
2127 13..14,
2128 ],
2129 expected_take_all,
2130 );
2131
2132 check(
2136 &[0..1, 3..4],
2137 vec![
2138 ChunkInstructions {
2139 chunk_idx: 0,
2140 preamble: PreambleAction::Absent,
2141 rows_to_skip: 0,
2142 rows_to_take: 1,
2143 take_trailer: false,
2144 },
2145 ChunkInstructions {
2146 chunk_idx: 0,
2147 preamble: PreambleAction::Absent,
2148 rows_to_skip: 3,
2149 rows_to_take: 1,
2150 take_trailer: false,
2151 },
2152 ],
2153 );
2154
2155 check(
2157 &[5..6],
2158 vec![
2159 ChunkInstructions {
2160 chunk_idx: 0,
2161 preamble: PreambleAction::Absent,
2162 rows_to_skip: 5,
2163 rows_to_take: 1,
2164 take_trailer: true,
2165 },
2166 ChunkInstructions {
2167 chunk_idx: 1,
2168 preamble: PreambleAction::Take,
2169 rows_to_skip: 0,
2170 rows_to_take: 0,
2171 take_trailer: false,
2172 },
2173 ],
2174 );
2175
2176 check(
2178 &[7..10],
2179 vec![
2180 ChunkInstructions {
2181 chunk_idx: 1,
2182 preamble: PreambleAction::Skip,
2183 rows_to_skip: 1,
2184 rows_to_take: 1,
2185 take_trailer: false,
2186 },
2187 ChunkInstructions {
2188 chunk_idx: 2,
2189 preamble: PreambleAction::Absent,
2190 rows_to_skip: 0,
2191 rows_to_take: 2,
2192 take_trailer: false,
2193 },
2194 ],
2195 );
2196 }
2197
2198 #[test]
2199 fn test_drain_instructions() {
2200 fn drain_from_instructions(
2201 instructions: &mut VecDeque<ChunkInstructions>,
2202 mut rows_desired: u64,
2203 need_preamble: &mut bool,
2204 skip_in_chunk: &mut u64,
2205 ) -> Vec<ChunkDrainInstructions> {
2206 let mut drain_instructions = Vec::with_capacity(instructions.len());
2208 while rows_desired > 0 || *need_preamble {
2209 let (next_instructions, consumed_chunk) = instructions
2210 .front()
2211 .unwrap()
2212 .drain_from_instruction(&mut rows_desired, need_preamble, skip_in_chunk);
2213 if consumed_chunk {
2214 instructions.pop_front();
2215 }
2216 drain_instructions.push(next_instructions);
2217 }
2218 drain_instructions
2219 }
2220
2221 let rep_data: Vec<u64> = vec![5, 2, 3, 0, 4, 7, 2, 0];
2223 let rep_bytes: Vec<u8> = rep_data.iter().flat_map(|v| v.to_le_bytes()).collect();
2224 let chunk_index = MiniBlockChunkIndex::new_nested_for_test(&rep_bytes, 2);
2225 let user_ranges = vec![1..7, 10..14];
2226
2227 let scheduled = ChunkInstructions::schedule_instructions(&chunk_index, &user_ranges);
2229
2230 let mut to_drain = VecDeque::from(scheduled.clone());
2231
2232 let mut need_preamble = false;
2235 let mut skip_in_chunk = 0;
2236
2237 let next_batch =
2238 drain_from_instructions(&mut to_drain, 4, &mut need_preamble, &mut skip_in_chunk);
2239
2240 assert!(!need_preamble);
2241 assert_eq!(skip_in_chunk, 4);
2242 assert_eq!(
2243 next_batch,
2244 vec![ChunkDrainInstructions {
2245 chunk_instructions: scheduled[0].clone(),
2246 rows_to_take: 4,
2247 rows_to_skip: 0,
2248 preamble_action: PreambleAction::Absent,
2249 }]
2250 );
2251
2252 let next_batch =
2253 drain_from_instructions(&mut to_drain, 4, &mut need_preamble, &mut skip_in_chunk);
2254
2255 assert!(!need_preamble);
2256 assert_eq!(skip_in_chunk, 2);
2257
2258 assert_eq!(
2259 next_batch,
2260 vec![
2261 ChunkDrainInstructions {
2262 chunk_instructions: scheduled[0].clone(),
2263 rows_to_take: 1,
2264 rows_to_skip: 4,
2265 preamble_action: PreambleAction::Absent,
2266 },
2267 ChunkDrainInstructions {
2268 chunk_instructions: scheduled[1].clone(),
2269 rows_to_take: 1,
2270 rows_to_skip: 0,
2271 preamble_action: PreambleAction::Take,
2272 },
2273 ChunkDrainInstructions {
2274 chunk_instructions: scheduled[2].clone(),
2275 rows_to_take: 2,
2276 rows_to_skip: 0,
2277 preamble_action: PreambleAction::Absent,
2278 }
2279 ]
2280 );
2281
2282 let next_batch =
2283 drain_from_instructions(&mut to_drain, 2, &mut need_preamble, &mut skip_in_chunk);
2284
2285 assert!(!need_preamble);
2286 assert_eq!(skip_in_chunk, 0);
2287
2288 assert_eq!(
2289 next_batch,
2290 vec![
2291 ChunkDrainInstructions {
2292 chunk_instructions: scheduled[2].clone(),
2293 rows_to_take: 1,
2294 rows_to_skip: 2,
2295 preamble_action: PreambleAction::Absent,
2296 },
2297 ChunkDrainInstructions {
2298 chunk_instructions: scheduled[3].clone(),
2299 rows_to_take: 1,
2300 rows_to_skip: 0,
2301 preamble_action: PreambleAction::Take,
2302 },
2303 ]
2304 );
2305
2306 let rep_data: Vec<u64> = vec![5, 2, 3, 3, 20, 0];
2308 let rep_bytes: Vec<u8> = rep_data.iter().flat_map(|v| v.to_le_bytes()).collect();
2309 let chunk_index = MiniBlockChunkIndex::new_nested_for_test(&rep_bytes, 2);
2310 let user_ranges = vec![0..28];
2311
2312 let scheduled = ChunkInstructions::schedule_instructions(&chunk_index, &user_ranges);
2314
2315 let mut to_drain = VecDeque::from(scheduled.clone());
2316
2317 let mut need_preamble = false;
2320 let mut skip_in_chunk = 0;
2321
2322 let next_batch =
2323 drain_from_instructions(&mut to_drain, 7, &mut need_preamble, &mut skip_in_chunk);
2324
2325 assert_eq!(
2326 next_batch,
2327 vec![
2328 ChunkDrainInstructions {
2329 chunk_instructions: scheduled[0].clone(),
2330 rows_to_take: 6,
2331 rows_to_skip: 0,
2332 preamble_action: PreambleAction::Absent,
2333 },
2334 ChunkDrainInstructions {
2335 chunk_instructions: scheduled[1].clone(),
2336 rows_to_take: 1,
2337 rows_to_skip: 0,
2338 preamble_action: PreambleAction::Take,
2339 },
2340 ]
2341 );
2342
2343 assert!(!need_preamble);
2344 assert_eq!(skip_in_chunk, 1);
2345
2346 let next_batch =
2349 drain_from_instructions(&mut to_drain, 2, &mut need_preamble, &mut skip_in_chunk);
2350
2351 assert_eq!(
2352 next_batch,
2353 vec![
2354 ChunkDrainInstructions {
2355 chunk_instructions: scheduled[1].clone(),
2356 rows_to_take: 2,
2357 rows_to_skip: 1,
2358 preamble_action: PreambleAction::Skip,
2359 },
2360 ChunkDrainInstructions {
2361 chunk_instructions: scheduled[2].clone(),
2362 rows_to_take: 0,
2363 rows_to_skip: 0,
2364 preamble_action: PreambleAction::Take,
2365 },
2366 ]
2367 );
2368
2369 assert!(!need_preamble);
2370 assert_eq!(skip_in_chunk, 0);
2371 }
2372
2373 fn words_from(entries: &[(u32, u32)]) -> (Words, u64) {
2376 let mut raw = Vec::with_capacity(entries.len() * 2);
2377 let mut total = 0u64;
2378 for &(log, num_bytes) in entries {
2379 assert!(num_bytes > 0 && num_bytes % MINIBLOCK_ALIGNMENT as u32 == 0);
2380 let divided = num_bytes / MINIBLOCK_ALIGNMENT as u32 - 1;
2381 let word = (divided << 4) | log;
2382 assert!(word <= u16::MAX as u32, "test word {word} exceeds u16");
2383 raw.extend_from_slice(&(word as u16).to_le_bytes());
2384 total += num_bytes as u64;
2385 }
2386 (Words::from_bytes(Bytes::from(raw), false).unwrap(), total)
2387 }
2388
2389 fn rep_bytes_from(values: &[u64]) -> Vec<u8> {
2390 values.iter().flat_map(|v| v.to_le_bytes()).collect()
2391 }
2392
2393 #[rstest]
2394 #[case::uniform_partial_last(&[(3, 16), (3, 24), (0, 8)], 19, "uniform_flat", 8, 3)]
2397 #[case::single_chunk(&[(0, 24)], 5, "uniform_flat", 5, 5)]
2399 #[case::exact_multiple(&[(3, 16), (3, 16)], 16, "uniform_flat", 8, 8)]
2401 #[case::non_uniform(&[(4, 16), (2, 16), (0, 8)], 21, "flat", 16, 1)]
2403 fn test_flat_detection(
2404 #[case] entries: &[(u32, u32)],
2405 #[case] items_in_page: u64,
2406 #[case] expected_kind: &str,
2407 #[case] expected_first_items: u64,
2408 #[case] expected_last_items: u64,
2409 ) {
2410 let base = 100u64;
2411 let (words, data_buf_size) = words_from(entries);
2412 let index = build_chunk_index(&words, items_in_page, base, data_buf_size, None, 0).unwrap();
2413
2414 assert_eq!(index.row_mapping_debug(), expected_kind);
2415 assert_eq!(index.num_chunks(), entries.len());
2416 assert_eq!(index.items_in_chunk(0), expected_first_items);
2417 assert_eq!(index.items_in_chunk(entries.len() - 1), expected_last_items);
2418
2419 let mut expected_start = base;
2421 for (i, &(_, num_bytes)) in entries.iter().enumerate() {
2422 let range = index.byte_range(i);
2423 assert_eq!(range.start, expected_start);
2424 assert_eq!(range.end - range.start, num_bytes as u64);
2425 expected_start = range.end;
2426 }
2427 assert_eq!(expected_start, base + data_buf_size);
2428
2429 let total_items: u64 = (0..index.num_chunks())
2431 .map(|i| index.items_in_chunk(i))
2432 .sum();
2433 assert_eq!(total_items, items_in_page);
2434 }
2435
2436 #[test]
2437 fn test_nested_detection_and_axes() {
2438 let rep = rep_bytes_from(&[5, 0, 4, 0, 3, 0]);
2440
2441 let (words, data_buf_size) = words_from(&[(2, 8), (2, 8), (0, 8)]);
2443 let index = build_chunk_index(&words, 10, 0, data_buf_size, Some(&rep), 1).unwrap();
2444 assert_eq!(index.row_mapping_debug(), "nested");
2445 assert_eq!(index.num_chunks(), 3);
2446 assert_eq!(index.first_row(0), 0);
2448 assert_eq!(index.rows_in_chunk(0), 5);
2449 assert_eq!(index.first_row(1), 5);
2450 assert_eq!(index.rows_in_chunk(1), 4);
2451 assert_eq!(index.first_row(2), 9);
2452 assert_eq!(index.rows_in_chunk(2), 3);
2453 assert_eq!(index.items_in_chunk(0), 4);
2455 assert_eq!(index.items_in_chunk(1), 4);
2456 assert_eq!(index.items_in_chunk(2), 2);
2457
2458 let (words_nu, dbs_nu) = words_from(&[(3, 8), (1, 8), (0, 8)]);
2460 let index_nu = build_chunk_index(&words_nu, 15, 0, dbs_nu, Some(&rep), 1).unwrap();
2461 assert_eq!(index_nu.row_mapping_debug(), "nested");
2462 assert_eq!(index_nu.items_in_chunk(0), 8);
2463 assert_eq!(index_nu.items_in_chunk(1), 2);
2464 assert_eq!(index_nu.items_in_chunk(2), 5);
2465 assert_eq!(index_nu.rows_in_chunk(0), 5);
2467 }
2468
2469 #[test]
2470 fn test_uniform_flat_matches_prefix_sum_flat() {
2471 let (words, data_buf_size) = words_from(&[(2, 8), (2, 8), (2, 8), (0, 8)]);
2473 let uniform = build_chunk_index(&words, 15, 0, data_buf_size, None, 0).unwrap();
2474 assert_eq!(uniform.row_mapping_debug(), "uniform_flat");
2475
2476 let byte_starts = PrefixSums::from_deltas([8u64, 8, 8, 8].into_iter(), 4, 32);
2478 let value_starts = PrefixSums::from_deltas([4u64, 4, 4, 3].into_iter(), 4, 15);
2479 let flat = MiniBlockChunkIndex::new(0, byte_starts, RowMapping::Flat { value_starts });
2480 assert_eq!(flat.row_mapping_debug(), "flat");
2481
2482 for i in 0..4 {
2484 assert_eq!(uniform.byte_range(i), flat.byte_range(i));
2485 assert_eq!(uniform.items_in_chunk(i), flat.items_in_chunk(i));
2486 }
2487
2488 let range_sets: Vec<Vec<std::ops::Range<u64>>> = vec![
2490 vec![0..15],
2491 vec![0..1],
2492 vec![7..8],
2493 vec![14..15],
2494 vec![3..10],
2495 vec![0..2, 5..6, 12..15],
2496 ];
2497 for ranges in &range_sets {
2498 let from_uniform = ChunkInstructions::schedule_instructions(&uniform, ranges);
2499 let from_flat = ChunkInstructions::schedule_instructions(&flat, ranges);
2500 assert_eq!(from_uniform, from_flat, "mismatch for ranges {ranges:?}");
2501 }
2502
2503 let full = ChunkInstructions::schedule_instructions(&uniform, &[0..15]);
2505 assert_eq!(full.len(), 4);
2506 for (i, inst) in full.iter().enumerate() {
2507 assert_eq!(inst.chunk_idx, i);
2508 assert_eq!(inst.preamble, PreambleAction::Absent);
2509 assert_eq!(inst.rows_to_skip, 0);
2510 assert!(!inst.take_trailer);
2511 }
2512 assert_eq!(full.iter().map(|i| i.rows_to_take).sum::<u64>(), 15);
2513 }
2514
2515 #[test]
2516 fn test_deep_size_per_variant_below_legacy() {
2517 const LEGACY_PER_CHUNK: usize = 48;
2520 let num_chunks = 3;
2521 let heap = |index: &MiniBlockChunkIndex| index.deep_size_of_children(&mut Context::new());
2522
2523 let (uniform_words, uniform_dbs) = words_from(&[(2, 8), (2, 8), (0, 8)]);
2524 let uniform = build_chunk_index(&uniform_words, 10, 0, uniform_dbs, None, 0).unwrap();
2525 assert_eq!(uniform.row_mapping_debug(), "uniform_flat");
2526 assert!(heap(&uniform) < LEGACY_PER_CHUNK * num_chunks);
2527
2528 let (flat_words, flat_dbs) = words_from(&[(3, 8), (1, 8), (0, 8)]);
2529 let flat = build_chunk_index(&flat_words, 11, 0, flat_dbs, None, 0).unwrap();
2530 assert_eq!(flat.row_mapping_debug(), "flat");
2531 assert!(heap(&flat) < LEGACY_PER_CHUNK * num_chunks);
2532 assert!(heap(&flat) > heap(&uniform));
2534
2535 let rep = rep_bytes_from(&[4, 0, 3, 0, 3, 0]);
2536 let (nested_words, nested_dbs) = words_from(&[(2, 8), (2, 8), (0, 8)]);
2537 let nested = build_chunk_index(&nested_words, 10, 0, nested_dbs, Some(&rep), 1).unwrap();
2538 assert_eq!(nested.row_mapping_debug(), "nested");
2539 assert!(heap(&nested) < LEGACY_PER_CHUNK * num_chunks);
2540 }
2541
2542 #[test]
2543 fn test_miniblock_dictionary_out_of_line_bitpacking_decode() {
2544 let rows = 10_000;
2545 let unique_values = 2_000;
2546
2547 let dictionary_encoding =
2548 EncodingUtils::out_of_line_bitpacking(64, EncodingUtils::flat(11, None));
2549 let layout = metadata::MiniBlockLayout {
2550 rep_compression: None,
2551 def_compression: None,
2552 value_compression: Some(EncodingUtils::flat(64, None)),
2553 dictionary: Some(dictionary_encoding),
2554 num_dictionary_items: unique_values,
2555 layers: vec![metadata::RepDefLayer::RepdefAllValidItem as i32],
2556 num_buffers: 1,
2557 repetition_index_depth: 0,
2558 num_items: rows,
2559 has_large_chunk: false,
2560 };
2561
2562 let buffer_offsets_and_sizes = vec![(0, 0), (0, 0), (0, 0)];
2563 let scheduler = super::MiniBlockScheduler::try_new(
2564 &buffer_offsets_and_sizes,
2565 0,
2566 rows,
2567 &layout,
2568 &DefaultDecompressionStrategy::default(),
2569 )
2570 .unwrap();
2571
2572 let dictionary = scheduler.dictionary.unwrap();
2573 assert_eq!(dictionary.num_dictionary_items, unique_values);
2574 assert_eq!(
2575 dictionary.dictionary_data_alignment,
2576 crate::encoder::MIN_PAGE_BUFFER_ALIGNMENT
2577 );
2578 }
2579
2580 #[test]
2581 fn test_miniblock_dictionary_rle_decode() {
2582 let rows = 10_000;
2583
2584 let dictionary_encoding =
2589 EncodingUtils::rle(EncodingUtils::flat(64, None), EncodingUtils::flat(8, None));
2590 let layout = metadata::MiniBlockLayout {
2591 rep_compression: None,
2592 def_compression: None,
2593 value_compression: Some(EncodingUtils::flat(64, None)),
2594 dictionary: Some(dictionary_encoding),
2595 num_dictionary_items: 1,
2596 layers: vec![metadata::RepDefLayer::RepdefAllValidItem as i32],
2597 num_buffers: 1,
2598 repetition_index_depth: 0,
2599 num_items: rows,
2600 has_large_chunk: false,
2601 };
2602
2603 let buffer_offsets_and_sizes = vec![(0, 0), (0, 0), (0, 0)];
2604 let scheduler = super::MiniBlockScheduler::try_new(
2605 &buffer_offsets_and_sizes,
2606 0,
2607 rows,
2608 &layout,
2609 &DefaultDecompressionStrategy::default(),
2610 )
2611 .unwrap();
2612
2613 let dictionary = scheduler.dictionary.unwrap();
2614 assert_eq!(dictionary.num_dictionary_items, 1);
2615 assert_eq!(dictionary.dictionary_data_alignment, 1);
2616 }
2617
2618 #[test]
2619 fn miniblock_levels_use_one_count_for_mixed_codecs() {
2620 let actual_num_levels = usize::from(u16::MAX) + 8;
2621 let levels = vec![1_u16; actual_num_levels];
2622 let rep_frame = encoded_u16_frame(&levels, RunLengthWidth::U8);
2623 let rep_decompressor = RleDecompressor::new(16);
2624 let def_frame = LanceBuffer::reinterpret_slice(Arc::from(levels.clone()));
2625 let def_decompressor = ValueDecompressor::from_flat(&metadata::Flat {
2626 bits_per_value: 16,
2627 data: None,
2628 });
2629
2630 let num_levels = DecodeMiniBlockTask::resolve_num_levels(
2631 Some(&rep_decompressor),
2632 Some(&rep_frame),
2633 Some(&def_decompressor),
2634 Some(&def_frame),
2635 7,
2636 )
2637 .unwrap();
2638 assert_eq!(num_levels, actual_num_levels as u64);
2639
2640 let rep =
2641 DecodeMiniBlockTask::decode_levels(&rep_decompressor, rep_frame, num_levels).unwrap();
2642 let def =
2643 DecodeMiniBlockTask::decode_levels(&def_decompressor, def_frame, num_levels).unwrap();
2644 assert_eq!(rep.as_ref(), levels);
2645 assert_eq!(def, rep);
2646 }
2647
2648 #[test]
2649 fn miniblock_levels_reject_non_wrapped_count_mismatch() {
2650 let frame = encoded_u16_frame(&[1_u16; 8], RunLengthWidth::U8);
2651 let decompressor = RleDecompressor::new(16);
2652
2653 let error = DecodeMiniBlockTask::resolve_num_levels(
2654 Some(&decompressor),
2655 Some(&frame),
2656 None,
2657 None,
2658 7,
2659 )
2660 .unwrap_err();
2661 assert!(matches!(error, lance_core::Error::InvalidInput { .. }));
2662 assert!(error.to_string().contains("not congruent modulo 65536"));
2663 }
2664
2665 #[test]
2666 fn miniblock_levels_reject_cross_stream_count_disagreement() {
2667 let rep_levels = vec![1_u16; usize::from(u16::MAX) + 8];
2668 let def_levels = vec![1_u16; rep_levels.len() + usize::from(u16::MAX) + 1];
2669 let rep_frame = encoded_u16_frame(&rep_levels, RunLengthWidth::U8);
2670 let def_frame = encoded_u16_frame(&def_levels, RunLengthWidth::U8);
2671 let decompressor = RleDecompressor::new(16);
2672
2673 let error = DecodeMiniBlockTask::resolve_num_levels(
2674 Some(&decompressor),
2675 Some(&rep_frame),
2676 Some(&decompressor),
2677 Some(&def_frame),
2678 7,
2679 )
2680 .unwrap_err();
2681 assert!(matches!(error, lance_core::Error::InvalidInput { .. }));
2682 assert!(
2683 error
2684 .to_string()
2685 .contains("structural streams disagree on the level count")
2686 );
2687 }
2688}