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 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
615 if self.has_validity() {
616 return None;
617 }
618 num_values
619 .checked_mul(self.bits_per_value)
620 .map(|bits| bits.div_ceil(8))
621 }
622}
623
624struct FslDecompressorValidityBuilder {
625 buffer: BooleanBufferBuilder,
626 bits_per_row: usize,
627 bytes_per_row: usize,
628}
629
630impl ValueDecompressor {
632 fn has_validity(&self) -> bool {
633 self.layers.iter().any(|layer| layer.has_validity)
634 }
635
636 fn simple_decompress(&self, data: FixedWidthDataBlock, num_rows: u64) -> DataBlock {
638 let mut cum_dim = 1;
639 for layer in &self.layers {
640 cum_dim *= layer.dimension;
641 }
642 debug_assert_eq!(self.bits_per_item, data.bits_per_value / cum_dim);
643 let mut block = DataBlock::FixedWidth(FixedWidthDataBlock {
644 bits_per_value: self.bits_per_item,
645 num_values: num_rows * cum_dim,
646 data: data.data,
647 block_info: BlockInfo::new(),
648 });
649 for layer in self.layers.iter().rev() {
650 block = DataBlock::FixedSizeList(FixedSizeListBlock {
651 child: Box::new(block),
652 dimension: layer.dimension,
653 });
654 }
655 debug_assert_eq!(num_rows, block.num_values());
656 block
657 }
658
659 fn unzip_decompress(&self, data: FixedWidthDataBlock, num_rows: usize) -> DataBlock {
661 assert_eq!(self.bits_per_item % 8, 0);
663 let bytes_per_item = self.bits_per_item / 8;
664 let mut buffer_builders = Vec::with_capacity(self.layers.len());
665 let mut cum_dim = 1;
666 let mut total_size_bytes = 0;
667 for layer in &self.layers {
669 cum_dim *= layer.dimension as usize;
670 if layer.has_validity {
671 let validity_size_bits = cum_dim;
672 let validity_size_bytes = validity_size_bits.div_ceil(8);
673 total_size_bytes += num_rows * validity_size_bytes;
674 buffer_builders.push(FslDecompressorValidityBuilder {
675 buffer: BooleanBufferBuilder::new(validity_size_bits * num_rows),
676 bits_per_row: cum_dim,
677 bytes_per_row: validity_size_bytes,
678 })
679 }
680 }
681 let num_items = num_rows * cum_dim;
682 let data_size = num_items * bytes_per_item as usize;
683 total_size_bytes += data_size;
684 let mut data_buffer = Vec::with_capacity(data_size);
685
686 assert_eq!(data.data.len(), total_size_bytes);
687
688 let bytes_per_value = bytes_per_item as usize;
689 let data_bytes_per_row = bytes_per_value * cum_dim;
690
691 let mut data_offset = 0;
693 while data_offset < total_size_bytes {
694 for builder in buffer_builders.iter_mut() {
695 let start = data_offset * 8;
696 let end = start + builder.bits_per_row;
697 builder.buffer.append_packed_range(start..end, &data.data);
698 data_offset += builder.bytes_per_row;
699 }
700 let end = data_offset + data_bytes_per_row;
701 data_buffer.extend_from_slice(&data.data[data_offset..end]);
702 data_offset += data_bytes_per_row;
703 }
704
705 let mut block = DataBlock::FixedWidth(FixedWidthDataBlock {
707 bits_per_value: self.bits_per_item,
708 num_values: num_items as u64,
709 data: LanceBuffer::from(data_buffer),
710 block_info: BlockInfo::new(),
711 });
712
713 let mut validity_bufs = buffer_builders
714 .into_iter()
715 .rev()
716 .map(|mut b| LanceBuffer::from(b.buffer.finish().into_inner()));
717 for layer in self.layers.iter().rev() {
718 if layer.has_validity {
719 let nullable = NullableDataBlock {
720 data: Box::new(block),
721 nulls: validity_bufs.next().unwrap(),
722 block_info: BlockInfo::new(),
723 };
724 block = DataBlock::Nullable(nullable);
725 }
726 block = DataBlock::FixedSizeList(FixedSizeListBlock {
727 child: Box::new(block),
728 dimension: layer.dimension,
729 });
730 }
731
732 assert_eq!(num_rows, block.num_values() as usize);
733
734 block
735 }
736}
737
738impl FixedPerValueDecompressor for ValueDecompressor {
739 fn decompress(&self, data: FixedWidthDataBlock, num_rows: u64) -> Result<DataBlock> {
740 if self.has_validity() {
741 Ok(self.unzip_decompress(data, num_rows as usize))
742 } else {
743 Ok(self.simple_decompress(data, num_rows))
744 }
745 }
746
747 fn bits_per_value(&self) -> u64 {
748 self.bits_per_value
749 }
750
751 fn decoded_size_bytes(&self, num_values: u64) -> Option<u64> {
752 if self.has_validity() {
753 return None;
754 }
755 num_values
756 .checked_mul(self.bits_per_value)
757 .map(|bits| bits.div_ceil(8))
758 }
759}
760
761impl PerValueCompressor for ValueEncoder {
762 fn compress(&self, data: DataBlock) -> Result<(PerValueDataBlock, CompressiveEncoding)> {
763 let (data, encoding) = match data {
764 DataBlock::FixedWidth(fixed_width) => {
765 let encoding = ProtobufUtils21::flat(fixed_width.bits_per_value, None);
766 (PerValueDataBlock::Fixed(fixed_width), encoding)
767 }
768 DataBlock::FixedSizeList(fixed_size_list) => Self::per_value_fsl(fixed_size_list),
769 _ => unimplemented!(
770 "Cannot compress block of type {} with ValueEncoder",
771 data.name()
772 ),
773 };
774 Ok((data, encoding))
775 }
776}
777
778#[cfg(test)]
780mod tests {
781 use std::{
782 collections::HashMap,
783 sync::{Arc, LazyLock},
784 };
785
786 use arrow_array::{
787 Array, ArrayRef, Decimal128Array, FixedSizeListArray, Int32Array, ListArray, UInt8Array,
788 make_array, new_null_array, types::UInt32Type,
789 };
790 use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
791 use arrow_schema::{DataType, Field, TimeUnit};
792 use lance_datagen::{ArrayGeneratorExt, Dimension, RowCount, array, gen_batch};
793
794 use crate::{
795 compression::{FixedPerValueDecompressor, MiniBlockDecompressor},
796 data::DataBlock,
797 encodings::{
798 logical::primitive::{
799 fullzip::{PerValueCompressor, PerValueDataBlock},
800 miniblock::{MiniBlockCompressionContext, MiniBlockCompressor},
801 },
802 physical::value::ValueDecompressor,
803 },
804 format::pb21::compressive_encoding::Compression,
805 testing::{
806 FnArrayGeneratorProvider, TestCases, check_basic_random,
807 check_round_trip_encoding_generated, check_round_trip_encoding_of_data,
808 },
809 };
810
811 use super::ValueEncoder;
812
813 fn miniblock_context() -> MiniBlockCompressionContext {
814 MiniBlockCompressionContext::new(0, true, true)
815 }
816
817 const PRIMITIVE_TYPES: &[DataType] = &[
818 DataType::Null,
819 DataType::FixedSizeBinary(2),
820 DataType::Date32,
821 DataType::Date64,
822 DataType::Int8,
823 DataType::Int16,
824 DataType::Int32,
825 DataType::Int64,
826 DataType::UInt8,
827 DataType::UInt16,
828 DataType::UInt32,
829 DataType::UInt64,
830 DataType::Float16,
831 DataType::Float32,
832 DataType::Float64,
833 DataType::Decimal128(10, 10),
834 DataType::Decimal256(10, 10),
835 DataType::Timestamp(TimeUnit::Nanosecond, None),
836 DataType::Time32(TimeUnit::Second),
837 DataType::Time64(TimeUnit::Nanosecond),
838 DataType::Duration(TimeUnit::Second),
839 ];
843
844 #[test_log::test(tokio::test)]
845 async fn test_simple_value() {
846 let items = Arc::new(Int32Array::from(vec![
847 Some(0),
848 None,
849 Some(2),
850 Some(3),
851 Some(4),
852 Some(5),
853 ]));
854
855 let test_cases = TestCases::default()
856 .with_range(0..3)
857 .with_range(0..2)
858 .with_range(1..3)
859 .with_indices(vec![0, 1, 2])
860 .with_indices(vec![1])
861 .with_indices(vec![2])
862 .with_structural_encodings();
863
864 check_round_trip_encoding_of_data(vec![items], &test_cases, HashMap::default()).await;
865 }
866
867 #[test_log::test(tokio::test)]
868 async fn test_simple_range() {
869 let items = Arc::new(Int32Array::from_iter(
870 (0..5000).map(|i| if i % 2 == 0 { Some(i) } else { None }),
871 ));
872
873 let test_cases = TestCases::default().with_structural_encodings();
874
875 check_round_trip_encoding_of_data(vec![items], &test_cases, HashMap::default()).await;
876 }
877
878 #[test_log::test(tokio::test)]
879 async fn test_value_primitive() {
880 for data_type in PRIMITIVE_TYPES {
881 log::info!("Testing encoding for {:?}", data_type);
882 let field = Field::new("", data_type.clone(), false);
883 check_basic_random(field).await;
884 }
885 }
886
887 static LARGE_TYPES: LazyLock<Vec<DataType>> = LazyLock::new(|| {
888 vec![DataType::FixedSizeList(
889 Arc::new(Field::new("", DataType::Int32, false)),
890 128,
891 )]
892 });
893
894 #[test_log::test(tokio::test)]
895 async fn test_large_primitive() {
896 for data_type in LARGE_TYPES.iter() {
897 log::info!("Testing encoding for {:?}", data_type);
898 let field = Field::new("", data_type.clone(), false);
899 check_basic_random(field).await;
900 }
901 }
902
903 #[test_log::test(tokio::test)]
904 async fn test_decimal128_dictionary_encoding() {
905 let test_cases = TestCases::default().with_structural_encodings();
906 let decimals: Vec<i32> = (0..100).collect();
907 let repeated_strings: Vec<_> = decimals
908 .iter()
909 .cycle()
910 .take(decimals.len() * 10000)
911 .map(|&v| Some(v as i128))
912 .collect();
913 let decimal_array = Arc::new(Decimal128Array::from(repeated_strings)) as ArrayRef;
914 check_round_trip_encoding_of_data(vec![decimal_array], &test_cases, HashMap::new()).await;
915 }
916
917 #[test_log::test(tokio::test)]
918 async fn test_miniblock_stress() {
919 let data1 = (0..100)
923 .map(|_| Arc::new(Int32Array::from_iter_values(0..100)) as Arc<dyn Array>)
924 .collect::<Vec<_>>();
925
926 let data2 = (0..100)
928 .map(|_| {
929 Arc::new(Int32Array::from_iter(
930 (0..100).map(|i| if i % 2 == 0 { Some(i) } else { None }),
931 )) as Arc<dyn Array>
932 })
933 .collect::<Vec<_>>();
934
935 let _data3 = (0..100)
938 .map(|chunk_idx| {
939 Arc::new(Int32Array::from_iter(
940 (0..100).map(|i| if chunk_idx < 50 { None } else { Some(i) }),
941 )) as Arc<dyn Array>
942 })
943 .collect::<Vec<_>>();
944
945 for data in [data1, data2 ] {
946 for batch_size in [10, 100, 1500, 15000] {
947 let test_cases = TestCases::default()
949 .with_page_sizes(vec![1000, 2000, 3000, 60000])
950 .with_batch_size(batch_size)
951 .with_structural_encodings();
952
953 check_round_trip_encoding_of_data(data.clone(), &test_cases, HashMap::new()).await;
954 }
955 }
956 }
957
958 fn create_simple_fsl() -> FixedSizeListArray {
959 let items = Arc::new(Int32Array::from(vec![
961 Some(0),
962 Some(1),
963 Some(2),
964 Some(3),
965 None,
966 None,
967 None,
968 None,
969 Some(8),
970 Some(9),
971 None,
972 Some(11),
973 ]));
974 let items_field = Arc::new(Field::new("item", DataType::Int32, true));
975 let inner_list_nulls = BooleanBuffer::from(vec![true, false, false, false, true, true]);
976 let inner_list = Arc::new(FixedSizeListArray::new(
977 items_field.clone(),
978 2,
979 items,
980 Some(NullBuffer::new(inner_list_nulls)),
981 ));
982 let inner_list_field = Arc::new(Field::new(
983 "item",
984 DataType::FixedSizeList(items_field, 2),
985 true,
986 ));
987 FixedSizeListArray::new(inner_list_field, 2, inner_list, None)
988 }
989
990 #[test]
991 fn test_fsl_value_compression_miniblock() {
992 let sample_list = create_simple_fsl();
993
994 let starting_data = DataBlock::from_array(sample_list.clone());
995
996 let encoder = ValueEncoder::default();
997 let (data, compression) =
998 MiniBlockCompressor::compress(&encoder, miniblock_context(), starting_data).unwrap();
999
1000 assert_eq!(data.num_values, 3);
1001 assert_eq!(data.data.len(), 3);
1002 assert_eq!(data.chunks.len(), 1);
1003 assert_eq!(data.chunks[0].buffer_sizes, vec![1, 2, 48]);
1004 assert_eq!(data.chunks[0].log_num_values, 0);
1005
1006 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1007 panic!()
1008 };
1009
1010 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref());
1011
1012 let decompressed =
1013 MiniBlockDecompressor::decompress(&decompressor, data.data, data.num_values).unwrap();
1014
1015 let decompressed = make_array(
1016 decompressed
1017 .into_arrow(sample_list.data_type().clone(), true)
1018 .unwrap(),
1019 );
1020
1021 assert_eq!(decompressed.as_ref(), &sample_list);
1022 }
1023
1024 fn wide_fixed_size_binary() -> ArrayRef {
1025 let wide_value = vec![0xABu8; 5000];
1026 Arc::new(
1027 arrow_array::FixedSizeBinaryArray::try_from_sparse_iter_with_size(
1028 std::iter::repeat_n(Some(wide_value.as_slice()), 4),
1029 5000,
1030 )
1031 .unwrap(),
1032 )
1033 }
1034
1035 fn wide_fixed_size_list_bool() -> ArrayRef {
1036 let dimension = 4095;
1039 let values = arrow_array::BooleanArray::from(vec![false; dimension * 2]);
1040 let field = Arc::new(Field::new("item", DataType::Boolean, true));
1041 Arc::new(FixedSizeListArray::new(
1042 field,
1043 dimension as i32,
1044 Arc::new(values),
1045 None,
1046 ))
1047 }
1048
1049 #[rstest::rstest]
1050 #[case::fixed_size_binary(wide_fixed_size_binary(), 2)]
1051 #[case::fixed_size_list_bool(wide_fixed_size_list_bool(), 16)]
1052 fn test_wide_value_miniblock_returns_error(
1053 #[case] array: ArrayRef,
1054 #[case] expected_min_values: u64,
1055 ) {
1056 let starting_data = DataBlock::from_array(array);
1057
1058 let encoder = ValueEncoder::default();
1059 let result = MiniBlockCompressor::compress(&encoder, miniblock_context(), starting_data);
1060
1061 let err = result.expect_err("wide values should not be encodable as miniblock");
1062 assert!(
1063 matches!(err, lance_core::Error::InvalidInput { .. }),
1064 "expected InvalidInput, got {err:?}"
1065 );
1066 let msg = err.to_string();
1067 assert!(
1068 msg.contains("too wide for miniblock encoding"),
1069 "unexpected error message: {msg}"
1070 );
1071 assert!(
1072 msg.contains(&format!("{expected_min_values} values require")),
1073 "unexpected error message: {msg}"
1074 );
1075 }
1076
1077 #[test]
1078 fn test_fsl_value_compression_per_value() {
1079 let sample_list = create_simple_fsl();
1080
1081 let starting_data = DataBlock::from_array(sample_list.clone());
1082
1083 let encoder = ValueEncoder::default();
1084 let (data, compression) = PerValueCompressor::compress(&encoder, starting_data).unwrap();
1085
1086 let PerValueDataBlock::Fixed(data) = data else {
1087 panic!()
1088 };
1089
1090 assert_eq!(data.bits_per_value, 144);
1091 assert_eq!(data.num_values, 3);
1092 assert_eq!(data.data.len(), 18 * 3);
1093
1094 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1095 panic!()
1096 };
1097
1098 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref());
1099
1100 let num_values = data.num_values;
1101 assert_eq!(
1102 FixedPerValueDecompressor::decoded_size_bytes(&decompressor, num_values),
1103 None,
1104 "nullable FSL output uses multiple buffers and requires the fallback estimate"
1105 );
1106 let decompressed =
1107 FixedPerValueDecompressor::decompress(&decompressor, data, num_values).unwrap();
1108
1109 let decompressed = make_array(
1110 decompressed
1111 .into_arrow(sample_list.data_type().clone(), true)
1112 .unwrap(),
1113 );
1114
1115 assert_eq!(decompressed.as_ref(), &sample_list);
1116 }
1117
1118 #[test_log::test(tokio::test)]
1119 async fn test_fsl_all_null() {
1120 let items = new_null_array(&DataType::Int32, 12);
1121 let items_field = Arc::new(Field::new("item", DataType::Int32, true));
1122 let list_nulls = BooleanBuffer::from(vec![true, false, false, false, true, true]);
1123 let list_array =
1124 FixedSizeListArray::new(items_field, 2, items, Some(NullBuffer::new(list_nulls)));
1125
1126 let test_cases = TestCases::default().with_structural_encodings();
1127
1128 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, HashMap::new())
1129 .await;
1130 }
1131
1132 #[test_log::test(tokio::test)]
1133 async fn regress_list_fsl() {
1134 let offsets = ScalarBuffer::<i32>::from(vec![0, 393, 755, 1156, 1536]);
1137 let data = UInt8Array::from(vec![0; 1536 * 16]);
1138 let fsl_field = Arc::new(Field::new("item", DataType::UInt8, true));
1139 let fsl = FixedSizeListArray::new(fsl_field, 16, Arc::new(data), None);
1140 let list_field = Arc::new(Field::new("item", fsl.data_type().clone(), false));
1141 let list_arr = ListArray::new(list_field, OffsetBuffer::new(offsets), Arc::new(fsl), None);
1142
1143 let test_cases = TestCases::default()
1144 .with_structural_encodings()
1145 .with_batch_size(1);
1146
1147 check_round_trip_encoding_of_data(vec![Arc::new(list_arr)], &test_cases, HashMap::new())
1148 .await;
1149 }
1150
1151 fn create_random_fsl() -> Arc<dyn Array> {
1152 let inner = array::rand_type(&DataType::Int32).with_random_nulls(0.1);
1154 let list_one = array::cycle_vec(inner, Dimension::from(4)).with_random_nulls(0.1);
1155 let list_two = array::cycle_vec(list_one, Dimension::from(4)).with_random_nulls(0.1);
1156 let list_three = array::cycle_vec(list_two, Dimension::from(2));
1157
1158 let batch = gen_batch()
1160 .anon_col(list_three)
1161 .into_batch_rows(RowCount::from(8 * 1024))
1162 .unwrap();
1163 batch.column(0).clone()
1164 }
1165
1166 #[test]
1167 fn fsl_value_miniblock_stress() {
1168 let sample_array = create_random_fsl();
1169
1170 let starting_data = DataBlock::from_arrays(
1171 std::slice::from_ref(&sample_array),
1172 sample_array.len() as u64,
1173 );
1174
1175 let encoder = ValueEncoder::default();
1176 let (data, compression) =
1177 MiniBlockCompressor::compress(&encoder, miniblock_context(), starting_data).unwrap();
1178
1179 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1180 panic!()
1181 };
1182
1183 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref());
1184
1185 let decompressed =
1186 MiniBlockDecompressor::decompress(&decompressor, data.data, data.num_values).unwrap();
1187
1188 let decompressed = make_array(
1189 decompressed
1190 .into_arrow(sample_array.data_type().clone(), true)
1191 .unwrap(),
1192 );
1193
1194 assert_eq!(decompressed.as_ref(), sample_array.as_ref());
1195 }
1196
1197 #[test]
1198 fn fsl_value_per_value_stress() {
1199 let sample_array = create_random_fsl();
1200
1201 let starting_data = DataBlock::from_arrays(
1202 std::slice::from_ref(&sample_array),
1203 sample_array.len() as u64,
1204 );
1205
1206 let encoder = ValueEncoder::default();
1207 let (data, compression) = PerValueCompressor::compress(&encoder, starting_data).unwrap();
1208
1209 let Compression::FixedSizeList(fsl) = compression.compression.unwrap() else {
1210 panic!()
1211 };
1212
1213 let decompressor = ValueDecompressor::from_fsl(fsl.as_ref());
1214
1215 let PerValueDataBlock::Fixed(data) = data else {
1216 panic!()
1217 };
1218
1219 let num_values = data.num_values;
1220 let decompressed =
1221 FixedPerValueDecompressor::decompress(&decompressor, data, num_values).unwrap();
1222
1223 let decompressed = make_array(
1224 decompressed
1225 .into_arrow(sample_array.data_type().clone(), true)
1226 .unwrap(),
1227 );
1228
1229 assert_eq!(decompressed.as_ref(), sample_array.as_ref());
1230 }
1231
1232 #[test_log::test(tokio::test)]
1233 async fn test_fsl_nullable_items() {
1234 let datagen = Box::new(FnArrayGeneratorProvider::new(move || {
1235 lance_datagen::array::rand_vec_nullable::<UInt32Type>(Dimension::from(128), 0.5)
1236 }));
1237
1238 let field = Field::new(
1239 "",
1240 DataType::FixedSizeList(Arc::new(Field::new("item", DataType::UInt32, true)), 128),
1241 false,
1242 );
1243 check_round_trip_encoding_generated(field, datagen, TestCases::default()).await;
1244 }
1245
1246 #[test_log::test(tokio::test)]
1247 async fn test_value_encoding_verification() {
1248 use std::collections::HashMap;
1249
1250 let test_cases = TestCases::default()
1251 .with_expected_encoding("flat")
1252 .with_structural_encodings();
1253
1254 let mut metadata_explicit = HashMap::new();
1258 metadata_explicit.insert("lance-encoding:compression".to_string(), "none".to_string());
1259 metadata_explicit.insert("lance-encoding:bss".to_string(), "off".to_string());
1260
1261 let arr_explicit =
1262 Arc::new(Int32Array::from((0..1000).collect::<Vec<i32>>())) as Arc<dyn Array>;
1263 check_round_trip_encoding_of_data(vec![arr_explicit], &test_cases, metadata_explicit).await;
1264
1265 let mut metadata = HashMap::new();
1269 metadata.insert("lance-encoding:bss".to_string(), "off".to_string());
1270
1271 let arr_fallback = Arc::new(Int32Array::from(
1272 (0..100).map(|i| i * 73 + 19).collect::<Vec<i32>>(),
1273 )) as Arc<dyn Array>;
1274 check_round_trip_encoding_of_data(vec![arr_fallback], &test_cases, metadata).await;
1275 }
1276
1277 #[test_log::test(tokio::test)]
1278 async fn test_mixed_page_validity() {
1279 let no_nulls = Arc::new(Int32Array::from_iter_values([1, 2]));
1280 let has_nulls = Arc::new(Int32Array::from_iter([Some(3), None, Some(5)]));
1281
1282 let test_cases = TestCases::default().with_page_sizes(vec![1]);
1283 check_round_trip_encoding_of_data(vec![no_nulls, has_nulls], &test_cases, HashMap::new())
1284 .await;
1285 }
1286}