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