1use arrow_buffer::{BooleanBufferBuilder, bit_util};
5
6use crate::buffer::LanceBuffer;
7use crate::compression::{
8 BlockCompressor, BlockDecompressor, FixedPerValueDecompressor, MiniBlockDecompressor,
9 require_block_payload,
10};
11use crate::data::{
12 AllNullDataBlock, BlockInfo, DataBlock, FixedSizeListBlock, FixedWidthDataBlock,
13 NullableDataBlock,
14};
15use crate::encodings::logical::primitive::fullzip::{PerValueCompressor, PerValueDataBlock};
16use crate::encodings::logical::primitive::miniblock::{
17 MAX_MINIBLOCK_BYTES, MAX_MINIBLOCK_VALUES, MiniBlockChunk, MiniBlockCompressed,
18 MiniBlockCompressionContext, MiniBlockCompressor,
19};
20use crate::format::ProtobufUtils21;
21use crate::format::pb21::compressive_encoding::Compression;
22use crate::format::pb21::{self, CompressiveEncoding};
23
24use lance_core::{Error, Result};
25
26#[derive(Debug, Default)]
28pub struct ValueEncoder {}
29
30impl ValueEncoder {
31 fn find_log_vals_per_chunk(bytes_per_word: u64, values_per_word: u64) -> Result<(u64, u64)> {
33 let mut size_bytes = 2 * bytes_per_word;
34 let (mut log_num_vals, mut num_vals) = match values_per_word {
35 1 => (1, 2),
36 8 => (3, 8),
37 _ => unreachable!(),
38 };
39
40 if size_bytes >= MAX_MINIBLOCK_BYTES {
41 let num_values = 2 * values_per_word;
42 return Err(Error::invalid_input(format!(
43 "Value is too wide for miniblock encoding: {} values require {} bytes but a \
44 miniblock chunk is limited to {} bytes.",
45 num_values, size_bytes, MAX_MINIBLOCK_BYTES
46 )));
47 }
48
49 while 2 * size_bytes < MAX_MINIBLOCK_BYTES && 2 * num_vals <= *MAX_MINIBLOCK_VALUES {
50 log_num_vals += 1;
51 size_bytes *= 2;
52 num_vals *= 2;
53 }
54
55 Ok((log_num_vals, num_vals))
56 }
57
58 fn chunk_data(data: FixedWidthDataBlock) -> Result<MiniBlockCompressed> {
59 let (bytes_per_word, values_per_word) = if data.bits_per_value.is_multiple_of(8) {
64 (data.bits_per_value / 8, 1)
65 } else {
66 (data.bits_per_value, 8)
67 };
68
69 let (log_vals_per_chunk, vals_per_chunk) =
71 Self::find_log_vals_per_chunk(bytes_per_word, values_per_word)?;
72 let num_chunks = bit_util::ceil(data.num_values as usize, vals_per_chunk as usize);
73 debug_assert_eq!(vals_per_chunk % values_per_word, 0);
74 let bytes_per_chunk = bytes_per_word * (vals_per_chunk / values_per_word);
75 let bytes_per_chunk = u32::try_from(bytes_per_chunk).unwrap();
76 debug_assert!(bytes_per_chunk > 0);
77
78 let data_buffer = data.data;
79
80 let mut row_offset = 0;
81 let mut chunks = Vec::with_capacity(num_chunks);
82
83 let mut bytes_counter = 0;
84 loop {
85 if row_offset + vals_per_chunk <= data.num_values {
86 chunks.push(MiniBlockChunk {
88 log_num_values: log_vals_per_chunk as u8,
89 buffer_sizes: vec![bytes_per_chunk],
90 });
91 row_offset += vals_per_chunk;
92 bytes_counter += bytes_per_chunk as u64;
93 } else if row_offset < data.num_values {
94 let num_bytes = data_buffer.len() as u64 - bytes_counter;
96 let num_bytes = u32::try_from(num_bytes).unwrap();
97 chunks.push(MiniBlockChunk {
98 log_num_values: 0,
99 buffer_sizes: vec![num_bytes],
100 });
101 break;
102 } else {
103 break;
105 }
106 }
107
108 debug_assert_eq!(chunks.len(), num_chunks);
109
110 Ok(MiniBlockCompressed {
111 chunks,
112 data: vec![data_buffer],
113 num_values: data.num_values,
114 })
115 }
116}
117
118#[derive(Debug)]
119struct MiniblockFslLayer {
120 validity: Option<LanceBuffer>,
121 dimension: u64,
122}
123
124impl ValueEncoder {
141 fn make_fsl_encoding(layers: &[MiniblockFslLayer], bits_per_value: u64) -> CompressiveEncoding {
142 let mut encoding = ProtobufUtils21::flat(bits_per_value, None);
143 for layer in layers.iter().rev() {
144 let has_validity = layer.validity.is_some();
145 let dimension = layer.dimension;
146 encoding = ProtobufUtils21::fsl(dimension, has_validity, encoding);
147 }
148 encoding
149 }
150
151 fn extract_fsl_chunk(
152 data: &FixedWidthDataBlock,
153 layers: &[MiniblockFslLayer],
154 row_offset: usize,
155 num_rows: usize,
156 validity_buffers: &mut [Vec<u8>],
157 ) -> Vec<u32> {
158 let mut row_offset = row_offset;
159 let mut num_values = num_rows;
160 let mut buffer_counter = 0;
161 let mut buffer_sizes = Vec::with_capacity(validity_buffers.len() + 1);
162 for layer in layers {
163 row_offset *= layer.dimension as usize;
164 num_values *= layer.dimension as usize;
165 if let Some(validity) = &layer.validity {
166 let validity_slice = validity
167 .clone()
168 .bit_slice_le_with_length(row_offset, num_values);
169 validity_buffers[buffer_counter].extend_from_slice(&validity_slice);
170 buffer_sizes.push(validity_slice.len() as u32);
171 buffer_counter += 1;
172 }
173 }
174
175 let bits_in_chunk = data.bits_per_value * num_values as u64;
176 let bytes_in_chunk = bits_in_chunk.div_ceil(8);
177 let bytes_in_chunk = u32::try_from(bytes_in_chunk).unwrap();
178 debug_assert!(bytes_in_chunk > 0);
179 buffer_sizes.push(bytes_in_chunk);
180
181 buffer_sizes
182 }
183
184 fn chunk_fsl(
185 data: FixedWidthDataBlock,
186 layers: Vec<MiniblockFslLayer>,
187 num_rows: u64,
188 ) -> Result<(MiniBlockCompressed, CompressiveEncoding)> {
189 let mut ceil_bytes_validity = 0;
191 let mut cum_dim = 1;
192 let mut num_validity_buffers = 0;
193 for layer in &layers {
194 cum_dim *= layer.dimension;
195 if layer.validity.is_some() {
196 ceil_bytes_validity += cum_dim.div_ceil(8);
197 num_validity_buffers += 1;
198 }
199 }
200 let cum_bits_per_value = data.bits_per_value * cum_dim;
202 let (cum_bytes_per_word, vals_per_word) = if cum_bits_per_value.is_multiple_of(8) {
203 (cum_bits_per_value / 8, 1)
204 } else {
205 (cum_bits_per_value, 8)
206 };
207 let est_bytes_per_word = (ceil_bytes_validity * vals_per_word) + cum_bytes_per_word;
208 let (log_rows_per_chunk, rows_per_chunk) =
209 Self::find_log_vals_per_chunk(est_bytes_per_word, vals_per_word)?;
210
211 let num_chunks = num_rows.div_ceil(rows_per_chunk) as usize;
212
213 let mut chunks = Vec::with_capacity(num_chunks);
215 let mut validity_buffers: Vec<Vec<u8>> = Vec::with_capacity(num_validity_buffers);
216 cum_dim = 1;
217 for layer in &layers {
218 cum_dim *= layer.dimension;
219 if let Some(validity) = &layer.validity {
220 let layer_bytes_validity = cum_dim.div_ceil(8);
221 let validity_with_padding =
222 layer_bytes_validity as usize * num_chunks * rows_per_chunk as usize;
223 debug_assert!(validity_with_padding >= validity.len());
224 validity_buffers.push(Vec::with_capacity(
225 layer_bytes_validity as usize * num_chunks,
226 ));
227 }
228 }
229
230 let mut row_offset = 0;
232 while row_offset + rows_per_chunk <= num_rows {
233 let buffer_sizes = Self::extract_fsl_chunk(
234 &data,
235 &layers,
236 row_offset as usize,
237 rows_per_chunk as usize,
238 &mut validity_buffers,
239 );
240 row_offset += rows_per_chunk;
241 chunks.push(MiniBlockChunk {
242 log_num_values: log_rows_per_chunk as u8,
243 buffer_sizes,
244 })
245 }
246 let rows_in_chunk = num_rows - row_offset;
247 if rows_in_chunk > 0 {
248 let buffer_sizes = Self::extract_fsl_chunk(
249 &data,
250 &layers,
251 row_offset as usize,
252 rows_in_chunk as usize,
253 &mut validity_buffers,
254 );
255 chunks.push(MiniBlockChunk {
256 log_num_values: 0,
257 buffer_sizes,
258 });
259 }
260
261 let encoding = Self::make_fsl_encoding(&layers, data.bits_per_value);
262 let buffers = validity_buffers
264 .into_iter()
265 .map(LanceBuffer::from)
266 .chain(std::iter::once(data.data))
267 .collect::<Vec<_>>();
268
269 Ok((
270 MiniBlockCompressed {
271 chunks,
272 data: buffers,
273 num_values: num_rows,
274 },
275 encoding,
276 ))
277 }
278
279 fn miniblock_fsl(data: DataBlock) -> Result<(MiniBlockCompressed, CompressiveEncoding)> {
280 let num_rows = data.num_values();
281 let fsl = data.as_fixed_size_list().unwrap();
282 let mut layers = Vec::new();
283 let mut child = *fsl.child;
284 let mut cur_layer = MiniblockFslLayer {
285 validity: None,
286 dimension: fsl.dimension,
287 };
288 loop {
289 if let DataBlock::Nullable(nullable) = child {
290 cur_layer.validity = Some(nullable.nulls);
291 child = *nullable.data;
292 }
293 match child {
294 DataBlock::FixedSizeList(inner) => {
295 layers.push(cur_layer);
296 cur_layer = MiniblockFslLayer {
297 validity: None,
298 dimension: inner.dimension,
299 };
300 child = *inner.child;
301 }
302 DataBlock::FixedWidth(inner) => {
303 layers.push(cur_layer);
304 return Self::chunk_fsl(inner, layers, num_rows);
305 }
306 _ => unreachable!("Unexpected data block type in value encoder's miniblock_fsl"),
307 }
308 }
309 }
310}
311
312struct PerValueFslValidityIter {
313 buffer: LanceBuffer,
314 bits_per_row: usize,
315 offset: usize,
316}
317
318impl ValueEncoder {
323 fn fsl_to_encoding(fsl: &FixedSizeListBlock) -> CompressiveEncoding {
324 let mut inner = fsl.child.as_ref();
325 let mut has_validity = false;
326 if let DataBlock::Nullable(nullable) = inner {
327 has_validity = true;
328 inner = nullable.data.as_ref();
329 }
330 let inner_encoding = match inner {
331 DataBlock::AllNull(_) => {
335 has_validity = true;
336 ProtobufUtils21::constant(None)
337 }
338 DataBlock::FixedWidth(fixed_width) => {
339 ProtobufUtils21::flat(fixed_width.bits_per_value, None)
340 }
341 DataBlock::FixedSizeList(inner) => Self::fsl_to_encoding(inner),
342 _ => unreachable!(
343 "Unexpected data block type in value encoder's fsl_to_encoding: {}",
344 inner.name()
345 ),
346 };
347 ProtobufUtils21::fsl(fsl.dimension, has_validity, inner_encoding)
348 }
349
350 fn simple_per_value_fsl(fsl: FixedSizeListBlock) -> (PerValueDataBlock, CompressiveEncoding) {
351 let encoding = Self::fsl_to_encoding(&fsl);
353 let num_values = fsl.num_values();
354 let mut child = *fsl.child;
355 let mut cum_dim = 1;
356 loop {
357 cum_dim *= fsl.dimension;
358 match child {
359 DataBlock::Nullable(nullable) => {
360 child = *nullable.data;
361 }
362 DataBlock::FixedSizeList(inner) => {
363 child = *inner.child;
364 }
365 DataBlock::FixedWidth(inner) => {
366 let data = FixedWidthDataBlock {
367 bits_per_value: inner.bits_per_value * cum_dim,
368 num_values,
369 data: inner.data,
370 block_info: BlockInfo::new(),
371 };
372 return (PerValueDataBlock::Fixed(data), encoding);
373 }
374 _ => unreachable!(
375 "Unexpected data block type in value encoder's simple_per_value_fsl"
376 ),
377 }
378 }
379 }
380
381 fn nullable_per_value_fsl(fsl: FixedSizeListBlock) -> (PerValueDataBlock, CompressiveEncoding) {
382 let encoding = Self::fsl_to_encoding(&fsl);
384 let num_values = fsl.num_values();
385 let mut bytes_per_row = 0;
386 let mut cum_dim = 1;
387 let mut current = fsl;
388 let mut validity_iters: Vec<PerValueFslValidityIter> = Vec::new();
389 let data_bytes_per_row: usize;
390 let data_buffer: LanceBuffer;
391 loop {
392 cum_dim *= current.dimension;
393 let mut child = *current.child;
394 if let DataBlock::Nullable(nullable) = child {
395 bytes_per_row += cum_dim.div_ceil(8) as usize;
397 validity_iters.push(PerValueFslValidityIter {
398 buffer: nullable.nulls,
399 bits_per_row: cum_dim as usize,
400 offset: 0,
401 });
402 child = *nullable.data;
403 };
404 match child {
405 DataBlock::FixedSizeList(inner) => {
406 current = inner;
407 }
408 DataBlock::FixedWidth(fixed_width) => {
409 data_bytes_per_row =
410 (fixed_width.bits_per_value.div_ceil(8) * cum_dim) as usize;
411 bytes_per_row += data_bytes_per_row;
412 data_buffer = fixed_width.data;
413 break;
414 }
415 DataBlock::AllNull(_) => {
416 bytes_per_row += cum_dim.div_ceil(8) as usize;
420 validity_iters.push(PerValueFslValidityIter {
421 buffer: LanceBuffer::from(vec![
422 0u8;
423 cum_dim.div_ceil(8) as usize
424 * num_values as usize
425 ]),
426 bits_per_row: cum_dim as usize,
427 offset: 0,
428 });
429 data_bytes_per_row = 0;
430 data_buffer = LanceBuffer::empty();
431 break;
432 }
433 _ => unreachable!(
434 "Unexpected data block type in value encoder's nullable_per_value_fsl: {:?}",
435 child
436 ),
437 }
438 }
439
440 let bytes_needed = bytes_per_row * num_values as usize;
441 let mut zipped = Vec::with_capacity(bytes_needed);
442 let data_slice = &data_buffer;
443 for i in 0..num_values as usize {
445 for validity in validity_iters.iter_mut() {
446 let validity_slice = validity
447 .buffer
448 .bit_slice_le_with_length(validity.offset, validity.bits_per_row);
449 zipped.extend_from_slice(&validity_slice);
450 validity.offset += validity.bits_per_row;
451 }
452 let start = i * data_bytes_per_row;
453 let end = start + data_bytes_per_row;
454 zipped.extend_from_slice(&data_slice[start..end]);
455 }
456
457 let zipped = LanceBuffer::from(zipped);
458 let data = PerValueDataBlock::Fixed(FixedWidthDataBlock {
459 bits_per_value: bytes_per_row as u64 * 8,
460 num_values,
461 data: zipped,
462 block_info: BlockInfo::new(),
463 });
464 (data, encoding)
465 }
466
467 fn per_value_fsl(fsl: FixedSizeListBlock) -> (PerValueDataBlock, CompressiveEncoding) {
468 if !fsl.child.is_nullable() {
469 Self::simple_per_value_fsl(fsl)
470 } else {
471 Self::nullable_per_value_fsl(fsl)
472 }
473 }
474}
475
476impl BlockCompressor for ValueEncoder {
477 fn compress(&self, data: DataBlock) -> Result<(Option<LanceBuffer>, CompressiveEncoding)> {
478 let DataBlock::FixedWidth(fixed_width) = data else {
479 return Err(Error::invalid_input(format!(
480 "ValueEncoder cannot compress a {} block",
481 data.name()
482 )));
483 };
484 Ok((
485 Some(fixed_width.data),
486 ProtobufUtils21::flat(fixed_width.bits_per_value, None),
487 ))
488 }
489}
490
491impl MiniBlockCompressor for ValueEncoder {
492 fn compress(
493 &self,
494 _context: MiniBlockCompressionContext,
495 chunk: DataBlock,
496 ) -> Result<(MiniBlockCompressed, CompressiveEncoding)> {
497 match chunk {
498 DataBlock::FixedWidth(fixed_width) => {
499 let encoding = ProtobufUtils21::flat(fixed_width.bits_per_value, None);
500 Ok((Self::chunk_data(fixed_width)?, encoding))
501 }
502 DataBlock::FixedSizeList(_) => Self::miniblock_fsl(chunk),
503 _ => Err(Error::invalid_input_source(
504 format!(
505 "Cannot compress a data block of type {} with ValueEncoder",
506 chunk.name()
507 )
508 .into(),
509 )),
510 }
511 }
512}
513
514#[derive(Debug)]
515struct ValueFslDesc {
516 dimension: u64,
517 has_validity: bool,
518}
519
520#[derive(Debug)]
523pub struct ValueDecompressor {
524 bits_per_item: u64,
526 bits_per_value: u64,
531 items_per_value: u64,
533 layers: Vec<ValueFslDesc>,
534}
535
536impl ValueDecompressor {
537 pub fn from_flat(description: &pb21::Flat) -> Self {
538 Self {
539 bits_per_item: description.bits_per_value,
540 bits_per_value: description.bits_per_value,
541 items_per_value: 1,
542 layers: Vec::default(),
543 }
544 }
545
546 pub fn from_fsl(mut description: &pb21::FixedSizeList) -> Result<Self> {
547 let mut layers = Vec::new();
548 let mut cum_dim = 1;
549 let mut bytes_per_value = 0;
550 loop {
551 layers.push(ValueFslDesc {
552 has_validity: description.has_validity,
553 dimension: description.items_per_value,
554 });
555 cum_dim *= description.items_per_value;
556 if description.has_validity {
557 bytes_per_value += cum_dim.div_ceil(8);
558 }
559 let encoding = description
560 .values
561 .as_ref()
562 .ok_or_else(|| Error::invalid_input("FSL encoding missing inner values field"))?
563 .compression
564 .as_ref()
565 .ok_or_else(|| {
566 Error::invalid_input("FSL encoding missing inner compression field")
567 })?;
568 match encoding {
569 Compression::FixedSizeList(inner) => {
570 description = inner;
571 }
572 Compression::Flat(flat) => {
573 let mut bits_per_value = bytes_per_value * 8;
574 bits_per_value += flat.bits_per_value * cum_dim;
575 return Ok(Self {
576 bits_per_item: flat.bits_per_value,
577 bits_per_value,
578 items_per_value: cum_dim,
579 layers,
580 });
581 }
582 Compression::Constant(_) => {
585 return Ok(Self {
586 bits_per_item: 0,
587 bits_per_value: bytes_per_value * 8,
588 items_per_value: cum_dim,
589 layers,
590 });
591 }
592 _ => {
593 return Err(Error::invalid_input(format!(
594 "Unexpected inner encoding type in FSL descriptor: {:?}",
595 encoding
596 )));
597 }
598 }
599 }
600 }
601
602 fn buffer_to_block(&self, data: LanceBuffer, num_values: u64) -> DataBlock {
603 DataBlock::FixedWidth(FixedWidthDataBlock {
604 bits_per_value: self.bits_per_item,
605 num_values,
606 data,
607 block_info: BlockInfo::new(),
608 })
609 }
610}
611
612impl BlockDecompressor for ValueDecompressor {
613 fn decompress(&self, data: Option<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
614 let data = require_block_payload(data, "Flat block")?;
615 let block = self.buffer_to_block(data, num_values);
616 assert_eq!(block.num_values(), num_values);
617 Ok(block)
618 }
619}
620
621impl MiniBlockDecompressor for ValueDecompressor {
622 fn decompress(&self, data: Vec<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
623 let num_items = num_values * self.items_per_value;
624 let mut buffer_iter = data.into_iter().rev();
625
626 let data_buf = buffer_iter.next().unwrap();
628 let items = self.buffer_to_block(data_buf, num_items);
629 let mut lists = items;
630
631 for layer in self.layers.iter().rev() {
632 if layer.has_validity {
633 let validity_buf = buffer_iter.next().unwrap();
634 lists = DataBlock::Nullable(NullableDataBlock {
635 data: Box::new(lists),
636 nulls: validity_buf,
637 block_info: BlockInfo::default(),
638 });
639 }
640 lists = DataBlock::FixedSizeList(FixedSizeListBlock {
641 child: Box::new(lists),
642 dimension: layer.dimension,
643 })
644 }
645
646 assert_eq!(lists.num_values(), num_values);
647 Ok(lists)
648 }
649
650 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
651 if self.has_validity() {
652 return None;
653 }
654 num_values
655 .checked_mul(self.bits_per_value)
656 .map(|bits| bits.div_ceil(8))
657 }
658}
659
660struct FslDecompressorValidityBuilder {
661 buffer: BooleanBufferBuilder,
662 bits_per_row: usize,
663 bytes_per_row: usize,
664}
665
666impl ValueDecompressor {
668 fn has_validity(&self) -> bool {
669 self.layers.iter().any(|layer| layer.has_validity)
670 }
671
672 fn simple_decompress(&self, data: FixedWidthDataBlock, num_rows: u64) -> DataBlock {
674 let mut cum_dim = 1;
675 for layer in &self.layers {
676 cum_dim *= layer.dimension;
677 }
678 debug_assert_eq!(self.bits_per_item, data.bits_per_value / cum_dim);
679 let mut block = DataBlock::FixedWidth(FixedWidthDataBlock {
680 bits_per_value: self.bits_per_item,
681 num_values: num_rows * cum_dim,
682 data: data.data,
683 block_info: BlockInfo::new(),
684 });
685 for layer in self.layers.iter().rev() {
686 block = DataBlock::FixedSizeList(FixedSizeListBlock {
687 child: Box::new(block),
688 dimension: layer.dimension,
689 });
690 }
691 debug_assert_eq!(num_rows, block.num_values());
692 block
693 }
694
695 fn unzip_decompress(&self, data: FixedWidthDataBlock, num_rows: usize) -> DataBlock {
697 assert_eq!(self.bits_per_item % 8, 0);
699 let bytes_per_item = self.bits_per_item / 8;
700 let mut buffer_builders = Vec::with_capacity(self.layers.len());
701 let mut cum_dim = 1;
702 let mut total_size_bytes = 0;
703 for layer in &self.layers {
705 cum_dim *= layer.dimension as usize;
706 if layer.has_validity {
707 let validity_size_bits = cum_dim;
708 let validity_size_bytes = validity_size_bits.div_ceil(8);
709 total_size_bytes += num_rows * validity_size_bytes;
710 buffer_builders.push(FslDecompressorValidityBuilder {
711 buffer: BooleanBufferBuilder::new(validity_size_bits * num_rows),
712 bits_per_row: cum_dim,
713 bytes_per_row: validity_size_bytes,
714 })
715 }
716 }
717 let num_items = num_rows * cum_dim;
718 let data_size = num_items * bytes_per_item as usize;
719 total_size_bytes += data_size;
720 let mut data_buffer = Vec::with_capacity(data_size);
721
722 assert_eq!(data.data.len(), total_size_bytes);
723
724 let bytes_per_value = bytes_per_item as usize;
725 let data_bytes_per_row = bytes_per_value * cum_dim;
726
727 let mut data_offset = 0;
729 while data_offset < total_size_bytes {
730 for builder in buffer_builders.iter_mut() {
731 let start = data_offset * 8;
732 let end = start + builder.bits_per_row;
733 builder.buffer.append_packed_range(start..end, &data.data);
734 data_offset += builder.bytes_per_row;
735 }
736 let end = data_offset + data_bytes_per_row;
737 data_buffer.extend_from_slice(&data.data[data_offset..end]);
738 data_offset += data_bytes_per_row;
739 }
740
741 let mut block = if self.bits_per_item == 0 {
747 debug_assert!(data_buffer.is_empty());
748 DataBlock::AllNull(AllNullDataBlock {
749 num_values: num_items as u64,
750 })
751 } else {
752 DataBlock::FixedWidth(FixedWidthDataBlock {
753 bits_per_value: self.bits_per_item,
754 num_values: num_items as u64,
755 data: LanceBuffer::from(data_buffer),
756 block_info: BlockInfo::new(),
757 })
758 };
759
760 let mut validity_bufs = buffer_builders
761 .into_iter()
762 .rev()
763 .map(|mut b| LanceBuffer::from(b.buffer.finish().into_inner()));
764 for layer in self.layers.iter().rev() {
765 if layer.has_validity {
766 let nullable = NullableDataBlock {
767 data: Box::new(block),
768 nulls: validity_bufs.next().unwrap(),
769 block_info: BlockInfo::new(),
770 };
771 block = DataBlock::Nullable(nullable);
772 }
773 block = DataBlock::FixedSizeList(FixedSizeListBlock {
774 child: Box::new(block),
775 dimension: layer.dimension,
776 });
777 }
778
779 assert_eq!(num_rows, block.num_values() as usize);
780
781 block
782 }
783}
784
785impl FixedPerValueDecompressor for ValueDecompressor {
786 fn decompress(&self, data: FixedWidthDataBlock, num_rows: u64) -> Result<DataBlock> {
787 if self.has_validity() {
788 Ok(self.unzip_decompress(data, num_rows as usize))
789 } else {
790 Ok(self.simple_decompress(data, num_rows))
791 }
792 }
793
794 fn bits_per_value(&self) -> u64 {
795 self.bits_per_value
796 }
797
798 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
799 if self.has_validity() {
800 return None;
801 }
802 num_values
803 .checked_mul(self.bits_per_value)
804 .map(|bits| bits.div_ceil(8))
805 }
806}
807
808impl PerValueCompressor for ValueEncoder {
809 fn compress(&self, data: DataBlock) -> Result<(PerValueDataBlock, CompressiveEncoding)> {
810 let (data, encoding) = match data {
811 DataBlock::FixedWidth(fixed_width) => {
812 let encoding = ProtobufUtils21::flat(fixed_width.bits_per_value, None);
813 (PerValueDataBlock::Fixed(fixed_width), encoding)
814 }
815 DataBlock::FixedSizeList(fixed_size_list) => Self::per_value_fsl(fixed_size_list),
816 _ => unimplemented!(
817 "Cannot compress block of type {} with ValueEncoder",
818 data.name()
819 ),
820 };
821 Ok((data, encoding))
822 }
823}
824
825#[cfg(test)]
827mod tests {
828 use std::{collections::HashMap, sync::Arc};
829
830 use arrow_array::{
831 Array, ArrayRef, Decimal128Array, FixedSizeListArray, Int32Array, ListArray, UInt8Array,
832 make_array, new_null_array, types::UInt32Type,
833 };
834 use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
835 use arrow_schema::{DataType, Field, TimeUnit};
836 use lance_datagen::{ArrayGeneratorExt, Dimension, RowCount, Seed, array, gen_batch};
837
838 use crate::{
839 compression::{FixedPerValueDecompressor, MiniBlockDecompressor},
840 data::DataBlock,
841 encodings::{
842 logical::primitive::{
843 fullzip::{PerValueCompressor, PerValueDataBlock},
844 miniblock::{MiniBlockCompressionContext, MiniBlockCompressor},
845 },
846 physical::value::ValueDecompressor,
847 },
848 format::pb21::compressive_encoding::Compression,
849 testing::{
850 TestCases, TestEncoding, check_basic_random_case, check_round_trip_encoding_of_data,
851 },
852 };
853
854 use super::ValueEncoder;
855
856 fn miniblock_context() -> MiniBlockCompressionContext {
857 MiniBlockCompressionContext::new(0, true, true)
858 }
859
860 const PRIMITIVE_TYPES: &[DataType] = &[
861 DataType::Null,
862 DataType::FixedSizeBinary(2),
863 DataType::Date32,
864 DataType::Date64,
865 DataType::Int8,
866 DataType::Int16,
867 DataType::Int32,
868 DataType::Int64,
869 DataType::UInt8,
870 DataType::UInt16,
871 DataType::UInt32,
872 DataType::UInt64,
873 DataType::Float16,
874 DataType::Float32,
875 DataType::Float64,
876 DataType::Decimal128(10, 10),
877 DataType::Decimal256(10, 10),
878 DataType::Timestamp(TimeUnit::Nanosecond, None),
879 DataType::Time32(TimeUnit::Second),
880 DataType::Time64(TimeUnit::Nanosecond),
881 DataType::Duration(TimeUnit::Second),
882 ];
886
887 #[test_log::test(tokio::test)]
888 async fn test_simple_value() {
889 let items = Arc::new(Int32Array::from(vec![
890 Some(0),
891 None,
892 Some(2),
893 Some(3),
894 Some(4),
895 Some(5),
896 ]));
897
898 let test_cases = TestCases::default()
899 .with_range(0..3)
900 .with_range(0..2)
901 .with_range(1..3)
902 .with_indices(vec![0, 1, 2])
903 .with_indices(vec![1])
904 .with_indices(vec![2])
905 .with_structural_encodings();
906
907 check_round_trip_encoding_of_data(vec![items], &test_cases, HashMap::default()).await;
908 }
909
910 #[test_log::test(tokio::test)]
911 async fn test_simple_range() {
912 let items = Arc::new(Int32Array::from_iter(
913 (0..5000).map(|i| if i % 2 == 0 { Some(i) } else { None }),
914 ));
915
916 let test_cases = TestCases::default().with_structural_encodings();
917
918 check_round_trip_encoding_of_data(vec![items], &test_cases, HashMap::default()).await;
919 }
920
921 #[test_log::test(tokio::test)]
922 async fn test_value_primitive() {
923 const NUM_ROWS: u32 = 1025;
924
925 let test_cases = TestCases::default()
926 .with_batch_size(NUM_ROWS)
927 .with_page_sizes(vec![4096])
928 .with_expected_encoding("flat");
929 let value_metadata =
930 HashMap::from([("lance-encoding:compression".to_string(), "none".to_string())]);
931
932 for (seed, data_type) in PRIMITIVE_TYPES.iter().enumerate() {
933 log::info!("Testing encoding for {:?}", data_type);
934 let data = gen_batch()
935 .with_seed(Seed::from(seed as u64))
936 .anon_col(array::rand_type(data_type))
937 .into_batch_rows(RowCount::from(NUM_ROWS as u64))
938 .unwrap()
939 .column(0)
940 .clone();
941
942 check_round_trip_encoding_of_data(vec![data], &test_cases, value_metadata.clone())
943 .await;
944 }
945 }
946
947 #[rstest::rstest]
948 #[test_log::test(tokio::test)]
949 async fn test_large_primitive(
950 #[values(
951 TestEncoding::Array,
952 TestEncoding::StructuralU16,
953 TestEncoding::StructuralU32,
954 TestEncoding::StructuralSparse
955 )]
956 encoding: TestEncoding,
957 #[values(4096, 1024 * 1024)] page_size: u64,
958 #[values(false, true)] use_slicing: bool,
959 ) {
960 let data_type =
961 DataType::FixedSizeList(Arc::new(Field::new("", DataType::Int32, false)), 128);
962 let field = Field::new("", data_type, false);
963 check_basic_random_case(field, encoding, page_size, use_slicing).await;
964 }
965
966 #[rstest::rstest]
967 #[test_log::test(tokio::test)]
968 async fn test_decimal128_dictionary_encoding(
969 #[values(
970 TestEncoding::StructuralU16,
971 TestEncoding::StructuralU32,
972 TestEncoding::StructuralSparse
973 )]
974 encoding: TestEncoding,
975 ) {
976 let test_cases = TestCases::default()
977 .with_encoding(encoding)
978 .with_expected_encoding("dictionary");
979 let decimals: Vec<i32> = (0..100).collect();
980 let repeated_strings: Vec<_> = decimals
981 .iter()
982 .cycle()
983 .take(decimals.len() * 1000)
984 .map(|&v| Some(v as i128))
985 .collect();
986 let decimal_array = Arc::new(Decimal128Array::from(repeated_strings)) as ArrayRef;
987 check_round_trip_encoding_of_data(vec![decimal_array], &test_cases, HashMap::new()).await;
988 }
989
990 #[rstest::rstest]
991 #[test_log::test(tokio::test)]
992 async fn test_miniblock_stress(
993 #[values(false, true)] mixed_validity: bool,
994 #[values(10, 100, 1500, 15000)] batch_size: u32,
995 #[values(1000, 2000, 3000, 60000)] page_size: u64,
996 #[values(
997 TestEncoding::StructuralU16,
998 TestEncoding::StructuralU32,
999 TestEncoding::StructuralSparse
1000 )]
1001 encoding: TestEncoding,
1002 ) {
1003 let data = (0..100)
1007 .map(|_| {
1008 if mixed_validity {
1009 Arc::new(Int32Array::from_iter(
1010 (0..100).map(|i| if i % 2 == 0 { Some(i) } else { None }),
1011 )) as Arc<dyn Array>
1012 } else {
1013 Arc::new(Int32Array::from_iter_values(0..100)) as Arc<dyn Array>
1014 }
1015 })
1016 .collect::<Vec<_>>();
1017
1018 let test_cases = TestCases::default()
1019 .with_page_sizes(vec![page_size])
1020 .with_batch_size(batch_size)
1021 .with_encoding(encoding);
1022
1023 check_round_trip_encoding_of_data(data, &test_cases, HashMap::new()).await;
1024 }
1025
1026 fn create_simple_fsl() -> FixedSizeListArray {
1027 let items = Arc::new(Int32Array::from(vec![
1029 Some(0),
1030 Some(1),
1031 Some(2),
1032 Some(3),
1033 None,
1034 None,
1035 None,
1036 None,
1037 Some(8),
1038 Some(9),
1039 None,
1040 Some(11),
1041 ]));
1042 let items_field = Arc::new(Field::new("item", DataType::Int32, true));
1043 let inner_list_nulls = BooleanBuffer::from(vec![true, false, false, false, true, true]);
1044 let inner_list = Arc::new(FixedSizeListArray::new(
1045 items_field.clone(),
1046 2,
1047 items,
1048 Some(NullBuffer::new(inner_list_nulls)),
1049 ));
1050 let inner_list_field = Arc::new(Field::new(
1051 "item",
1052 DataType::FixedSizeList(items_field, 2),
1053 true,
1054 ));
1055 FixedSizeListArray::new(inner_list_field, 2, inner_list, None)
1056 }
1057
1058 #[test]
1059 fn test_fsl_value_compression_miniblock() {
1060 let sample_list = create_simple_fsl();
1061
1062 let starting_data = DataBlock::from_array(sample_list.clone());
1063
1064 let encoder = ValueEncoder::default();
1065 let (data, compression) =
1066 MiniBlockCompressor::compress(&encoder, miniblock_context(), starting_data).unwrap();
1067
1068 assert_eq!(data.num_values, 3);
1069 assert_eq!(data.data.len(), 3);
1070 assert_eq!(data.chunks.len(), 1);
1071 assert_eq!(data.chunks[0].buffer_sizes, vec![1, 2, 48]);
1072 assert_eq!(data.chunks[0].log_num_values, 0);
1073
1074 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1075 panic!()
1076 };
1077
1078 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref()).unwrap();
1079
1080 let decompressed =
1081 MiniBlockDecompressor::decompress(&decompressor, data.data, data.num_values).unwrap();
1082
1083 let decompressed = make_array(
1084 decompressed
1085 .into_arrow(sample_list.data_type().clone(), true)
1086 .unwrap(),
1087 );
1088
1089 assert_eq!(decompressed.as_ref(), &sample_list);
1090 }
1091
1092 fn wide_fixed_size_binary() -> ArrayRef {
1093 let wide_value = vec![0xABu8; 5000];
1094 Arc::new(
1095 arrow_array::FixedSizeBinaryArray::try_from_sparse_iter_with_size(
1096 std::iter::repeat_n(Some(wide_value.as_slice()), 4),
1097 5000,
1098 )
1099 .unwrap(),
1100 )
1101 }
1102
1103 fn wide_fixed_size_list_bool() -> ArrayRef {
1104 let dimension = 4095;
1107 let values = arrow_array::BooleanArray::from(vec![false; dimension * 2]);
1108 let field = Arc::new(Field::new("item", DataType::Boolean, true));
1109 Arc::new(FixedSizeListArray::new(
1110 field,
1111 dimension as i32,
1112 Arc::new(values),
1113 None,
1114 ))
1115 }
1116
1117 #[rstest::rstest]
1118 #[case::fixed_size_binary(wide_fixed_size_binary(), 2)]
1119 #[case::fixed_size_list_bool(wide_fixed_size_list_bool(), 16)]
1120 fn test_wide_value_miniblock_returns_error(
1121 #[case] array: ArrayRef,
1122 #[case] expected_min_values: u64,
1123 ) {
1124 let starting_data = DataBlock::from_array(array);
1125
1126 let encoder = ValueEncoder::default();
1127 let result = MiniBlockCompressor::compress(&encoder, miniblock_context(), starting_data);
1128
1129 let err = result.expect_err("wide values should not be encodable as miniblock");
1130 assert!(
1131 matches!(err, lance_core::Error::InvalidInput { .. }),
1132 "expected InvalidInput, got {err:?}"
1133 );
1134 let msg = err.to_string();
1135 assert!(
1136 msg.contains("too wide for miniblock encoding"),
1137 "unexpected error message: {msg}"
1138 );
1139 assert!(
1140 msg.contains(&format!("{expected_min_values} values require")),
1141 "unexpected error message: {msg}"
1142 );
1143 }
1144
1145 #[test]
1146 fn test_fsl_value_compression_per_value() {
1147 let sample_list = create_simple_fsl();
1148
1149 let starting_data = DataBlock::from_array(sample_list.clone());
1150
1151 let encoder = ValueEncoder::default();
1152 let (data, compression) = PerValueCompressor::compress(&encoder, starting_data).unwrap();
1153
1154 let PerValueDataBlock::Fixed(data) = data else {
1155 panic!()
1156 };
1157
1158 assert_eq!(data.bits_per_value, 144);
1159 assert_eq!(data.num_values, 3);
1160 assert_eq!(data.data.len(), 18 * 3);
1161
1162 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1163 panic!()
1164 };
1165
1166 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref()).unwrap();
1167
1168 let num_values = data.num_values;
1169 assert_eq!(
1170 FixedPerValueDecompressor::decoded_size_bytes(&decompressor, num_values),
1171 None,
1172 "nullable FSL output uses multiple buffers and requires the fallback estimate"
1173 );
1174 let decompressed =
1175 FixedPerValueDecompressor::decompress(&decompressor, data, num_values).unwrap();
1176
1177 let decompressed = make_array(
1178 decompressed
1179 .into_arrow(sample_list.data_type().clone(), true)
1180 .unwrap(),
1181 );
1182
1183 assert_eq!(decompressed.as_ref(), &sample_list);
1184 }
1185
1186 #[test_log::test(tokio::test)]
1187 async fn test_fsl_all_null() {
1188 let items = new_null_array(&DataType::Int32, 12);
1189 let items_field = Arc::new(Field::new("item", DataType::Int32, true));
1190 let list_nulls = BooleanBuffer::from(vec![true, false, false, false, true, true]);
1191 let list_array =
1192 FixedSizeListArray::new(items_field, 2, items, Some(NullBuffer::new(list_nulls)));
1193
1194 let test_cases = TestCases::default().with_structural_encodings();
1195
1196 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, HashMap::new())
1197 .await;
1198 }
1199
1200 #[test_log::test(tokio::test)]
1204 async fn test_fsl_nullable_child_all_null_no_outer_nulls() {
1205 let items = new_null_array(&DataType::Float32, 8);
1210 let items_field = Arc::new(Field::new("item", DataType::Float32, true));
1211 let fsl = FixedSizeListArray::new(items_field, 4, items, None);
1212
1213 let test_cases = TestCases::default().with_structural_encodings();
1214 check_round_trip_encoding_of_data(vec![Arc::new(fsl)], &test_cases, HashMap::new()).await;
1215 }
1216
1217 #[test_log::test(tokio::test)]
1218 async fn regress_list_fsl() {
1219 let offsets = ScalarBuffer::<i32>::from(vec![0, 393, 755, 1156, 1536]);
1222 let data = UInt8Array::from(vec![0; 1536 * 16]);
1223 let fsl_field = Arc::new(Field::new("item", DataType::UInt8, true));
1224 let fsl = FixedSizeListArray::new(fsl_field, 16, Arc::new(data), None);
1225 let list_field = Arc::new(Field::new("item", fsl.data_type().clone(), false));
1226 let list_arr = ListArray::new(list_field, OffsetBuffer::new(offsets), Arc::new(fsl), None);
1227
1228 let test_cases = TestCases::default()
1229 .with_structural_encodings()
1230 .with_batch_size(1);
1231
1232 check_round_trip_encoding_of_data(vec![Arc::new(list_arr)], &test_cases, HashMap::new())
1233 .await;
1234 }
1235
1236 fn create_random_fsl() -> Arc<dyn Array> {
1237 let inner = array::rand_type(&DataType::Int32).with_random_nulls(0.1);
1239 let list_one = array::cycle_vec(inner, Dimension::from(4)).with_random_nulls(0.1);
1240 let list_two = array::cycle_vec(list_one, Dimension::from(4)).with_random_nulls(0.1);
1241 let list_three = array::cycle_vec(list_two, Dimension::from(2));
1242
1243 let batch = gen_batch()
1245 .anon_col(list_three)
1246 .into_batch_rows(RowCount::from(8 * 1024))
1247 .unwrap();
1248 batch.column(0).clone()
1249 }
1250
1251 #[test]
1252 fn fsl_value_miniblock_stress() {
1253 let sample_array = create_random_fsl();
1254
1255 let starting_data = DataBlock::from_arrays(
1256 std::slice::from_ref(&sample_array),
1257 sample_array.len() as u64,
1258 );
1259
1260 let encoder = ValueEncoder::default();
1261 let (data, compression) =
1262 MiniBlockCompressor::compress(&encoder, miniblock_context(), starting_data).unwrap();
1263
1264 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1265 panic!()
1266 };
1267
1268 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref()).unwrap();
1269
1270 let decompressed =
1271 MiniBlockDecompressor::decompress(&decompressor, data.data, data.num_values).unwrap();
1272
1273 let decompressed = make_array(
1274 decompressed
1275 .into_arrow(sample_array.data_type().clone(), true)
1276 .unwrap(),
1277 );
1278
1279 assert_eq!(decompressed.as_ref(), sample_array.as_ref());
1280 }
1281
1282 #[test]
1283 fn fsl_value_per_value_stress() {
1284 let sample_array = create_random_fsl();
1285
1286 let starting_data = DataBlock::from_arrays(
1287 std::slice::from_ref(&sample_array),
1288 sample_array.len() as u64,
1289 );
1290
1291 let encoder = ValueEncoder::default();
1292 let (data, compression) = PerValueCompressor::compress(&encoder, starting_data).unwrap();
1293
1294 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1295 panic!()
1296 };
1297
1298 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref()).unwrap();
1299
1300 let PerValueDataBlock::Fixed(data) = data else {
1301 panic!()
1302 };
1303
1304 let num_values = data.num_values;
1305 let decompressed =
1306 FixedPerValueDecompressor::decompress(&decompressor, data, num_values).unwrap();
1307
1308 let decompressed = make_array(
1309 decompressed
1310 .into_arrow(sample_array.data_type().clone(), true)
1311 .unwrap(),
1312 );
1313
1314 assert_eq!(decompressed.as_ref(), sample_array.as_ref());
1315 }
1316
1317 #[rstest::rstest]
1318 #[test_log::test(tokio::test)]
1319 async fn test_fsl_nullable_items(
1320 #[values(
1321 TestEncoding::Array,
1322 TestEncoding::StructuralU16,
1323 TestEncoding::StructuralU32,
1324 TestEncoding::StructuralSparse
1325 )]
1326 encoding: TestEncoding,
1327 ) {
1328 let mut generator =
1329 gen_batch()
1330 .with_seed(Seed::from(0))
1331 .anon_col(array::rand_vec_nullable::<UInt32Type>(
1332 Dimension::from(128),
1333 0.5,
1334 ));
1335 generator.with_random_nulls(0.2);
1336 let source = generator
1337 .into_batch_rows(RowCount::from(1026))
1338 .unwrap()
1339 .column(0)
1340 .clone();
1341 let test_cases = TestCases::default()
1342 .with_page_sizes(vec![4096])
1343 .with_encoding(encoding)
1344 .with_batch_size(257)
1345 .with_range(510..515)
1346 .with_indices(vec![0, 511, 512, 1024]);
1347
1348 check_round_trip_encoding_of_data(
1349 vec![source.slice(1, 512), source.slice(513, 513)],
1350 &test_cases,
1351 HashMap::new(),
1352 )
1353 .await;
1354 }
1355
1356 #[test_log::test(tokio::test)]
1357 async fn test_value_encoding_verification() {
1358 use std::collections::HashMap;
1359
1360 let test_cases = TestCases::default()
1361 .with_expected_encoding("flat")
1362 .with_structural_encodings();
1363
1364 let mut metadata_explicit = HashMap::new();
1368 metadata_explicit.insert("lance-encoding:compression".to_string(), "none".to_string());
1369 metadata_explicit.insert("lance-encoding:bss".to_string(), "off".to_string());
1370
1371 let arr_explicit =
1372 Arc::new(Int32Array::from((0..1000).collect::<Vec<i32>>())) as Arc<dyn Array>;
1373 check_round_trip_encoding_of_data(vec![arr_explicit], &test_cases, metadata_explicit).await;
1374
1375 let mut metadata = HashMap::new();
1379 metadata.insert("lance-encoding:bss".to_string(), "off".to_string());
1380
1381 let arr_fallback = Arc::new(Int32Array::from(
1382 (0..100).map(|i| i * 73 + 19).collect::<Vec<i32>>(),
1383 )) as Arc<dyn Array>;
1384 check_round_trip_encoding_of_data(vec![arr_fallback], &test_cases, metadata).await;
1385 }
1386
1387 #[test_log::test(tokio::test)]
1388 async fn test_mixed_page_validity() {
1389 let no_nulls = Arc::new(Int32Array::from_iter_values([1, 2]));
1390 let has_nulls = Arc::new(Int32Array::from_iter([Some(3), None, Some(5)]));
1391
1392 let test_cases = TestCases::default().with_page_sizes(vec![1]);
1393 check_round_trip_encoding_of_data(vec![no_nulls, has_nulls], &test_cases, HashMap::new())
1394 .await;
1395 }
1396}