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 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(&self, chunk: DataBlock) -> Result<(MiniBlockCompressed, CompressiveEncoding)> {
475 match chunk {
476 DataBlock::FixedWidth(fixed_width) => {
477 let encoding = ProtobufUtils21::flat(fixed_width.bits_per_value, None);
478 Ok((Self::chunk_data(fixed_width)?, encoding))
479 }
480 DataBlock::FixedSizeList(_) => Self::miniblock_fsl(chunk),
481 _ => Err(Error::invalid_input_source(
482 format!(
483 "Cannot compress a data block of type {} with ValueEncoder",
484 chunk.name()
485 )
486 .into(),
487 )),
488 }
489 }
490}
491
492#[derive(Debug)]
493struct ValueFslDesc {
494 dimension: u64,
495 has_validity: bool,
496}
497
498#[derive(Debug)]
501pub struct ValueDecompressor {
502 bits_per_item: u64,
504 bits_per_value: u64,
509 items_per_value: u64,
511 layers: Vec<ValueFslDesc>,
512}
513
514impl ValueDecompressor {
515 pub fn from_flat(description: &pb21::Flat) -> Self {
516 Self {
517 bits_per_item: description.bits_per_value,
518 bits_per_value: description.bits_per_value,
519 items_per_value: 1,
520 layers: Vec::default(),
521 }
522 }
523
524 pub fn from_fsl(mut description: &pb21::FixedSizeList) -> Self {
525 let mut layers = Vec::new();
526 let mut cum_dim = 1;
527 let mut bytes_per_value = 0;
528 loop {
529 layers.push(ValueFslDesc {
530 has_validity: description.has_validity,
531 dimension: description.items_per_value,
532 });
533 cum_dim *= description.items_per_value;
534 if description.has_validity {
535 bytes_per_value += cum_dim.div_ceil(8);
536 }
537 match description
538 .values
539 .as_ref()
540 .unwrap()
541 .compression
542 .as_ref()
543 .unwrap()
544 {
545 Compression::FixedSizeList(inner) => {
546 description = inner;
547 }
548 Compression::Flat(flat) => {
549 let mut bits_per_value = bytes_per_value * 8;
550 bits_per_value += flat.bits_per_value * cum_dim;
551 return Self {
552 bits_per_item: flat.bits_per_value,
553 bits_per_value,
554 items_per_value: cum_dim,
555 layers,
556 };
557 }
558 _ => unreachable!(),
559 }
560 }
561 }
562
563 fn buffer_to_block(&self, data: LanceBuffer, num_values: u64) -> DataBlock {
564 DataBlock::FixedWidth(FixedWidthDataBlock {
565 bits_per_value: self.bits_per_item,
566 num_values,
567 data,
568 block_info: BlockInfo::new(),
569 })
570 }
571}
572
573impl BlockDecompressor for ValueDecompressor {
574 fn decompress(&self, data: LanceBuffer, num_values: u64) -> Result<DataBlock> {
575 let block = self.buffer_to_block(data, num_values);
576 assert_eq!(block.num_values(), num_values);
577 Ok(block)
578 }
579}
580
581impl MiniBlockDecompressor for ValueDecompressor {
582 fn decompress(&self, data: Vec<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
583 let num_items = num_values * self.items_per_value;
584 let mut buffer_iter = data.into_iter().rev();
585
586 let data_buf = buffer_iter.next().unwrap();
588 let items = self.buffer_to_block(data_buf, num_items);
589 let mut lists = items;
590
591 for layer in self.layers.iter().rev() {
592 if layer.has_validity {
593 let validity_buf = buffer_iter.next().unwrap();
594 lists = DataBlock::Nullable(NullableDataBlock {
595 data: Box::new(lists),
596 nulls: validity_buf,
597 block_info: BlockInfo::default(),
598 });
599 }
600 lists = DataBlock::FixedSizeList(FixedSizeListBlock {
601 child: Box::new(lists),
602 dimension: layer.dimension,
603 })
604 }
605
606 assert_eq!(lists.num_values(), num_values);
607 Ok(lists)
608 }
609}
610
611struct FslDecompressorValidityBuilder {
612 buffer: BooleanBufferBuilder,
613 bits_per_row: usize,
614 bytes_per_row: usize,
615}
616
617impl ValueDecompressor {
619 fn has_validity(&self) -> bool {
620 self.layers.iter().any(|layer| layer.has_validity)
621 }
622
623 fn simple_decompress(&self, data: FixedWidthDataBlock, num_rows: u64) -> DataBlock {
625 let mut cum_dim = 1;
626 for layer in &self.layers {
627 cum_dim *= layer.dimension;
628 }
629 debug_assert_eq!(self.bits_per_item, data.bits_per_value / cum_dim);
630 let mut block = DataBlock::FixedWidth(FixedWidthDataBlock {
631 bits_per_value: self.bits_per_item,
632 num_values: num_rows * cum_dim,
633 data: data.data,
634 block_info: BlockInfo::new(),
635 });
636 for layer in self.layers.iter().rev() {
637 block = DataBlock::FixedSizeList(FixedSizeListBlock {
638 child: Box::new(block),
639 dimension: layer.dimension,
640 });
641 }
642 debug_assert_eq!(num_rows, block.num_values());
643 block
644 }
645
646 fn unzip_decompress(&self, data: FixedWidthDataBlock, num_rows: usize) -> DataBlock {
648 assert_eq!(self.bits_per_item % 8, 0);
650 let bytes_per_item = self.bits_per_item / 8;
651 let mut buffer_builders = Vec::with_capacity(self.layers.len());
652 let mut cum_dim = 1;
653 let mut total_size_bytes = 0;
654 for layer in &self.layers {
656 cum_dim *= layer.dimension as usize;
657 if layer.has_validity {
658 let validity_size_bits = cum_dim;
659 let validity_size_bytes = validity_size_bits.div_ceil(8);
660 total_size_bytes += num_rows * validity_size_bytes;
661 buffer_builders.push(FslDecompressorValidityBuilder {
662 buffer: BooleanBufferBuilder::new(validity_size_bits * num_rows),
663 bits_per_row: cum_dim,
664 bytes_per_row: validity_size_bytes,
665 })
666 }
667 }
668 let num_items = num_rows * cum_dim;
669 let data_size = num_items * bytes_per_item as usize;
670 total_size_bytes += data_size;
671 let mut data_buffer = Vec::with_capacity(data_size);
672
673 assert_eq!(data.data.len(), total_size_bytes);
674
675 let bytes_per_value = bytes_per_item as usize;
676 let data_bytes_per_row = bytes_per_value * cum_dim;
677
678 let mut data_offset = 0;
680 while data_offset < total_size_bytes {
681 for builder in buffer_builders.iter_mut() {
682 let start = data_offset * 8;
683 let end = start + builder.bits_per_row;
684 builder.buffer.append_packed_range(start..end, &data.data);
685 data_offset += builder.bytes_per_row;
686 }
687 let end = data_offset + data_bytes_per_row;
688 data_buffer.extend_from_slice(&data.data[data_offset..end]);
689 data_offset += data_bytes_per_row;
690 }
691
692 let mut block = DataBlock::FixedWidth(FixedWidthDataBlock {
694 bits_per_value: self.bits_per_item,
695 num_values: num_items as u64,
696 data: LanceBuffer::from(data_buffer),
697 block_info: BlockInfo::new(),
698 });
699
700 let mut validity_bufs = buffer_builders
701 .into_iter()
702 .rev()
703 .map(|mut b| LanceBuffer::from(b.buffer.finish().into_inner()));
704 for layer in self.layers.iter().rev() {
705 if layer.has_validity {
706 let nullable = NullableDataBlock {
707 data: Box::new(block),
708 nulls: validity_bufs.next().unwrap(),
709 block_info: BlockInfo::new(),
710 };
711 block = DataBlock::Nullable(nullable);
712 }
713 block = DataBlock::FixedSizeList(FixedSizeListBlock {
714 child: Box::new(block),
715 dimension: layer.dimension,
716 });
717 }
718
719 assert_eq!(num_rows, block.num_values() as usize);
720
721 block
722 }
723}
724
725impl FixedPerValueDecompressor for ValueDecompressor {
726 fn decompress(&self, data: FixedWidthDataBlock, num_rows: u64) -> Result<DataBlock> {
727 if self.has_validity() {
728 Ok(self.unzip_decompress(data, num_rows as usize))
729 } else {
730 Ok(self.simple_decompress(data, num_rows))
731 }
732 }
733
734 fn bits_per_value(&self) -> u64 {
735 self.bits_per_value
736 }
737}
738
739impl PerValueCompressor for ValueEncoder {
740 fn compress(&self, data: DataBlock) -> Result<(PerValueDataBlock, CompressiveEncoding)> {
741 let (data, encoding) = match data {
742 DataBlock::FixedWidth(fixed_width) => {
743 let encoding = ProtobufUtils21::flat(fixed_width.bits_per_value, None);
744 (PerValueDataBlock::Fixed(fixed_width), encoding)
745 }
746 DataBlock::FixedSizeList(fixed_size_list) => Self::per_value_fsl(fixed_size_list),
747 _ => unimplemented!(
748 "Cannot compress block of type {} with ValueEncoder",
749 data.name()
750 ),
751 };
752 Ok((data, encoding))
753 }
754}
755
756#[cfg(test)]
758mod tests {
759 use std::{
760 collections::HashMap,
761 sync::{Arc, LazyLock},
762 };
763
764 use arrow_array::{
765 Array, ArrayRef, Decimal128Array, FixedSizeListArray, Int32Array, ListArray, UInt8Array,
766 make_array, new_null_array, types::UInt32Type,
767 };
768 use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
769 use arrow_schema::{DataType, Field, TimeUnit};
770 use lance_datagen::{ArrayGeneratorExt, Dimension, RowCount, array, gen_batch};
771
772 use crate::{
773 compression::{FixedPerValueDecompressor, MiniBlockDecompressor},
774 data::DataBlock,
775 encodings::{
776 logical::primitive::{
777 fullzip::{PerValueCompressor, PerValueDataBlock},
778 miniblock::MiniBlockCompressor,
779 },
780 physical::value::ValueDecompressor,
781 },
782 format::pb21::compressive_encoding::Compression,
783 testing::{
784 FnArrayGeneratorProvider, TestCases, check_basic_random,
785 check_round_trip_encoding_generated, check_round_trip_encoding_of_data,
786 },
787 version::LanceFileVersion,
788 };
789
790 use super::ValueEncoder;
791
792 const PRIMITIVE_TYPES: &[DataType] = &[
793 DataType::Null,
794 DataType::FixedSizeBinary(2),
795 DataType::Date32,
796 DataType::Date64,
797 DataType::Int8,
798 DataType::Int16,
799 DataType::Int32,
800 DataType::Int64,
801 DataType::UInt8,
802 DataType::UInt16,
803 DataType::UInt32,
804 DataType::UInt64,
805 DataType::Float16,
806 DataType::Float32,
807 DataType::Float64,
808 DataType::Decimal128(10, 10),
809 DataType::Decimal256(10, 10),
810 DataType::Timestamp(TimeUnit::Nanosecond, None),
811 DataType::Time32(TimeUnit::Second),
812 DataType::Time64(TimeUnit::Nanosecond),
813 DataType::Duration(TimeUnit::Second),
814 ];
818
819 #[test_log::test(tokio::test)]
820 async fn test_simple_value() {
821 let items = Arc::new(Int32Array::from(vec![
822 Some(0),
823 None,
824 Some(2),
825 Some(3),
826 Some(4),
827 Some(5),
828 ]));
829
830 let test_cases = TestCases::default()
831 .with_range(0..3)
832 .with_range(0..2)
833 .with_range(1..3)
834 .with_indices(vec![0, 1, 2])
835 .with_indices(vec![1])
836 .with_indices(vec![2])
837 .with_min_file_version(LanceFileVersion::V2_1);
838
839 check_round_trip_encoding_of_data(vec![items], &test_cases, HashMap::default()).await;
840 }
841
842 #[test_log::test(tokio::test)]
843 async fn test_simple_range() {
844 let items = Arc::new(Int32Array::from_iter(
845 (0..5000).map(|i| if i % 2 == 0 { Some(i) } else { None }),
846 ));
847
848 let test_cases = TestCases::default().with_min_file_version(LanceFileVersion::V2_1);
849
850 check_round_trip_encoding_of_data(vec![items], &test_cases, HashMap::default()).await;
851 }
852
853 #[test_log::test(tokio::test)]
854 async fn test_value_primitive() {
855 for data_type in PRIMITIVE_TYPES {
856 log::info!("Testing encoding for {:?}", data_type);
857 let field = Field::new("", data_type.clone(), false);
858 check_basic_random(field).await;
859 }
860 }
861
862 static LARGE_TYPES: LazyLock<Vec<DataType>> = LazyLock::new(|| {
863 vec![DataType::FixedSizeList(
864 Arc::new(Field::new("", DataType::Int32, false)),
865 128,
866 )]
867 });
868
869 #[test_log::test(tokio::test)]
870 async fn test_large_primitive() {
871 for data_type in LARGE_TYPES.iter() {
872 log::info!("Testing encoding for {:?}", data_type);
873 let field = Field::new("", data_type.clone(), false);
874 check_basic_random(field).await;
875 }
876 }
877
878 #[test_log::test(tokio::test)]
879 async fn test_decimal128_dictionary_encoding() {
880 let test_cases = TestCases::default().with_min_file_version(LanceFileVersion::V2_1);
881 let decimals: Vec<i32> = (0..100).collect();
882 let repeated_strings: Vec<_> = decimals
883 .iter()
884 .cycle()
885 .take(decimals.len() * 10000)
886 .map(|&v| Some(v as i128))
887 .collect();
888 let decimal_array = Arc::new(Decimal128Array::from(repeated_strings)) as ArrayRef;
889 check_round_trip_encoding_of_data(vec![decimal_array], &test_cases, HashMap::new()).await;
890 }
891
892 #[test_log::test(tokio::test)]
893 async fn test_miniblock_stress() {
894 let data1 = (0..100)
898 .map(|_| Arc::new(Int32Array::from_iter_values(0..100)) as Arc<dyn Array>)
899 .collect::<Vec<_>>();
900
901 let data2 = (0..100)
903 .map(|_| {
904 Arc::new(Int32Array::from_iter(
905 (0..100).map(|i| if i % 2 == 0 { Some(i) } else { None }),
906 )) as Arc<dyn Array>
907 })
908 .collect::<Vec<_>>();
909
910 let _data3 = (0..100)
913 .map(|chunk_idx| {
914 Arc::new(Int32Array::from_iter(
915 (0..100).map(|i| if chunk_idx < 50 { None } else { Some(i) }),
916 )) as Arc<dyn Array>
917 })
918 .collect::<Vec<_>>();
919
920 for data in [data1, data2 ] {
921 for batch_size in [10, 100, 1500, 15000] {
922 let test_cases = TestCases::default()
924 .with_page_sizes(vec![1000, 2000, 3000, 60000])
925 .with_batch_size(batch_size)
926 .with_min_file_version(LanceFileVersion::V2_1);
927
928 check_round_trip_encoding_of_data(data.clone(), &test_cases, HashMap::new()).await;
929 }
930 }
931 }
932
933 fn create_simple_fsl() -> FixedSizeListArray {
934 let items = Arc::new(Int32Array::from(vec![
936 Some(0),
937 Some(1),
938 Some(2),
939 Some(3),
940 None,
941 None,
942 None,
943 None,
944 Some(8),
945 Some(9),
946 None,
947 Some(11),
948 ]));
949 let items_field = Arc::new(Field::new("item", DataType::Int32, true));
950 let inner_list_nulls = BooleanBuffer::from(vec![true, false, false, false, true, true]);
951 let inner_list = Arc::new(FixedSizeListArray::new(
952 items_field.clone(),
953 2,
954 items,
955 Some(NullBuffer::new(inner_list_nulls)),
956 ));
957 let inner_list_field = Arc::new(Field::new(
958 "item",
959 DataType::FixedSizeList(items_field, 2),
960 true,
961 ));
962 FixedSizeListArray::new(inner_list_field, 2, inner_list, None)
963 }
964
965 #[test]
966 fn test_fsl_value_compression_miniblock() {
967 let sample_list = create_simple_fsl();
968
969 let starting_data = DataBlock::from_array(sample_list.clone());
970
971 let encoder = ValueEncoder::default();
972 let (data, compression) = MiniBlockCompressor::compress(&encoder, starting_data).unwrap();
973
974 assert_eq!(data.num_values, 3);
975 assert_eq!(data.data.len(), 3);
976 assert_eq!(data.chunks.len(), 1);
977 assert_eq!(data.chunks[0].buffer_sizes, vec![1, 2, 48]);
978 assert_eq!(data.chunks[0].log_num_values, 0);
979
980 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
981 panic!()
982 };
983
984 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref());
985
986 let decompressed =
987 MiniBlockDecompressor::decompress(&decompressor, data.data, data.num_values).unwrap();
988
989 let decompressed = make_array(
990 decompressed
991 .into_arrow(sample_list.data_type().clone(), true)
992 .unwrap(),
993 );
994
995 assert_eq!(decompressed.as_ref(), &sample_list);
996 }
997
998 fn wide_fixed_size_binary() -> ArrayRef {
999 let wide_value = vec![0xABu8; 5000];
1000 Arc::new(
1001 arrow_array::FixedSizeBinaryArray::try_from_sparse_iter_with_size(
1002 std::iter::repeat_n(Some(wide_value.as_slice()), 4),
1003 5000,
1004 )
1005 .unwrap(),
1006 )
1007 }
1008
1009 fn wide_fixed_size_list_bool() -> ArrayRef {
1010 let dimension = 4095;
1013 let values = arrow_array::BooleanArray::from(vec![false; dimension * 2]);
1014 let field = Arc::new(Field::new("item", DataType::Boolean, true));
1015 Arc::new(FixedSizeListArray::new(
1016 field,
1017 dimension as i32,
1018 Arc::new(values),
1019 None,
1020 ))
1021 }
1022
1023 #[rstest::rstest]
1024 #[case::fixed_size_binary(wide_fixed_size_binary(), 2)]
1025 #[case::fixed_size_list_bool(wide_fixed_size_list_bool(), 16)]
1026 fn test_wide_value_miniblock_returns_error(
1027 #[case] array: ArrayRef,
1028 #[case] expected_min_values: u64,
1029 ) {
1030 let starting_data = DataBlock::from_array(array);
1031
1032 let encoder = ValueEncoder::default();
1033 let result = MiniBlockCompressor::compress(&encoder, starting_data);
1034
1035 let err = result.expect_err("wide values should not be encodable as miniblock");
1036 assert!(
1037 matches!(err, lance_core::Error::InvalidInput { .. }),
1038 "expected InvalidInput, got {err:?}"
1039 );
1040 let msg = err.to_string();
1041 assert!(
1042 msg.contains("too wide for miniblock encoding"),
1043 "unexpected error message: {msg}"
1044 );
1045 assert!(
1046 msg.contains(&format!("{expected_min_values} values require")),
1047 "unexpected error message: {msg}"
1048 );
1049 }
1050
1051 #[test]
1052 fn test_fsl_value_compression_per_value() {
1053 let sample_list = create_simple_fsl();
1054
1055 let starting_data = DataBlock::from_array(sample_list.clone());
1056
1057 let encoder = ValueEncoder::default();
1058 let (data, compression) = PerValueCompressor::compress(&encoder, starting_data).unwrap();
1059
1060 let PerValueDataBlock::Fixed(data) = data else {
1061 panic!()
1062 };
1063
1064 assert_eq!(data.bits_per_value, 144);
1065 assert_eq!(data.num_values, 3);
1066 assert_eq!(data.data.len(), 18 * 3);
1067
1068 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1069 panic!()
1070 };
1071
1072 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref());
1073
1074 let num_values = data.num_values;
1075 let decompressed =
1076 FixedPerValueDecompressor::decompress(&decompressor, data, num_values).unwrap();
1077
1078 let decompressed = make_array(
1079 decompressed
1080 .into_arrow(sample_list.data_type().clone(), true)
1081 .unwrap(),
1082 );
1083
1084 assert_eq!(decompressed.as_ref(), &sample_list);
1085 }
1086
1087 #[test_log::test(tokio::test)]
1088 async fn test_fsl_all_null() {
1089 let items = new_null_array(&DataType::Int32, 12);
1090 let items_field = Arc::new(Field::new("item", DataType::Int32, true));
1091 let list_nulls = BooleanBuffer::from(vec![true, false, false, false, true, true]);
1092 let list_array =
1093 FixedSizeListArray::new(items_field, 2, items, Some(NullBuffer::new(list_nulls)));
1094
1095 let test_cases = TestCases::default().with_min_file_version(LanceFileVersion::V2_1);
1096
1097 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, HashMap::new())
1098 .await;
1099 }
1100
1101 #[test_log::test(tokio::test)]
1102 async fn regress_list_fsl() {
1103 let offsets = ScalarBuffer::<i32>::from(vec![0, 393, 755, 1156, 1536]);
1106 let data = UInt8Array::from(vec![0; 1536 * 16]);
1107 let fsl_field = Arc::new(Field::new("item", DataType::UInt8, true));
1108 let fsl = FixedSizeListArray::new(fsl_field, 16, Arc::new(data), None);
1109 let list_field = Arc::new(Field::new("item", fsl.data_type().clone(), false));
1110 let list_arr = ListArray::new(list_field, OffsetBuffer::new(offsets), Arc::new(fsl), None);
1111
1112 let test_cases = TestCases::default()
1113 .with_min_file_version(LanceFileVersion::V2_1)
1114 .with_batch_size(1);
1115
1116 check_round_trip_encoding_of_data(vec![Arc::new(list_arr)], &test_cases, HashMap::new())
1117 .await;
1118 }
1119
1120 fn create_random_fsl() -> Arc<dyn Array> {
1121 let inner = array::rand_type(&DataType::Int32).with_random_nulls(0.1);
1123 let list_one = array::cycle_vec(inner, Dimension::from(4)).with_random_nulls(0.1);
1124 let list_two = array::cycle_vec(list_one, Dimension::from(4)).with_random_nulls(0.1);
1125 let list_three = array::cycle_vec(list_two, Dimension::from(2));
1126
1127 let batch = gen_batch()
1129 .anon_col(list_three)
1130 .into_batch_rows(RowCount::from(8 * 1024))
1131 .unwrap();
1132 batch.column(0).clone()
1133 }
1134
1135 #[test]
1136 fn fsl_value_miniblock_stress() {
1137 let sample_array = create_random_fsl();
1138
1139 let starting_data = DataBlock::from_arrays(
1140 std::slice::from_ref(&sample_array),
1141 sample_array.len() as u64,
1142 );
1143
1144 let encoder = ValueEncoder::default();
1145 let (data, compression) = MiniBlockCompressor::compress(&encoder, starting_data).unwrap();
1146
1147 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1148 panic!()
1149 };
1150
1151 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref());
1152
1153 let decompressed =
1154 MiniBlockDecompressor::decompress(&decompressor, data.data, data.num_values).unwrap();
1155
1156 let decompressed = make_array(
1157 decompressed
1158 .into_arrow(sample_array.data_type().clone(), true)
1159 .unwrap(),
1160 );
1161
1162 assert_eq!(decompressed.as_ref(), sample_array.as_ref());
1163 }
1164
1165 #[test]
1166 fn fsl_value_per_value_stress() {
1167 let sample_array = create_random_fsl();
1168
1169 let starting_data = DataBlock::from_arrays(
1170 std::slice::from_ref(&sample_array),
1171 sample_array.len() as u64,
1172 );
1173
1174 let encoder = ValueEncoder::default();
1175 let (data, compression) = PerValueCompressor::compress(&encoder, starting_data).unwrap();
1176
1177 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1178 panic!()
1179 };
1180
1181 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref());
1182
1183 let PerValueDataBlock::Fixed(data) = data else {
1184 panic!()
1185 };
1186
1187 let num_values = data.num_values;
1188 let decompressed =
1189 FixedPerValueDecompressor::decompress(&decompressor, data, num_values).unwrap();
1190
1191 let decompressed = make_array(
1192 decompressed
1193 .into_arrow(sample_array.data_type().clone(), true)
1194 .unwrap(),
1195 );
1196
1197 assert_eq!(decompressed.as_ref(), sample_array.as_ref());
1198 }
1199
1200 #[test_log::test(tokio::test)]
1201 async fn test_fsl_nullable_items() {
1202 let datagen = Box::new(FnArrayGeneratorProvider::new(move || {
1203 lance_datagen::array::rand_vec_nullable::<UInt32Type>(Dimension::from(128), 0.5)
1204 }));
1205
1206 let field = Field::new(
1207 "",
1208 DataType::FixedSizeList(Arc::new(Field::new("item", DataType::UInt32, true)), 128),
1209 false,
1210 );
1211 check_round_trip_encoding_generated(field, datagen, TestCases::default()).await;
1212 }
1213
1214 #[test_log::test(tokio::test)]
1215 async fn test_value_encoding_verification() {
1216 use std::collections::HashMap;
1217
1218 let test_cases = TestCases::default()
1219 .with_expected_encoding("flat")
1220 .with_min_file_version(LanceFileVersion::V2_1);
1221
1222 let mut metadata_explicit = HashMap::new();
1226 metadata_explicit.insert("lance-encoding:compression".to_string(), "none".to_string());
1227 metadata_explicit.insert("lance-encoding:bss".to_string(), "off".to_string());
1228
1229 let arr_explicit =
1230 Arc::new(Int32Array::from((0..1000).collect::<Vec<i32>>())) as Arc<dyn Array>;
1231 check_round_trip_encoding_of_data(vec![arr_explicit], &test_cases, metadata_explicit).await;
1232
1233 let mut metadata = HashMap::new();
1237 metadata.insert("lance-encoding:bss".to_string(), "off".to_string());
1238
1239 let arr_fallback = Arc::new(Int32Array::from(
1240 (0..100).map(|i| i * 73 + 19).collect::<Vec<i32>>(),
1241 )) as Arc<dyn Array>;
1242 check_round_trip_encoding_of_data(vec![arr_fallback], &test_cases, metadata).await;
1243 }
1244
1245 #[test_log::test(tokio::test)]
1246 async fn test_mixed_page_validity() {
1247 let no_nulls = Arc::new(Int32Array::from_iter_values([1, 2]));
1248 let has_nulls = Arc::new(Int32Array::from_iter([Some(3), None, Some(5)]));
1249
1250 let test_cases = TestCases::default().with_page_sizes(vec![1]);
1251 check_round_trip_encoding_of_data(vec![no_nulls, has_nulls], &test_cases, HashMap::new())
1252 .await;
1253 }
1254}