Skip to main content

lance_encoding/encodings/physical/
value.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use 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/// A compression strategy that writes fixed-width data as-is (no compression)
25#[derive(Debug, Default)]
26pub struct ValueEncoder {}
27
28impl ValueEncoder {
29    /// Use the largest chunk we can smaller than 4KiB
30    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        // Usually there are X bytes per value.  However, when working with boolean
58        // or FSL<boolean> we might have some number of bits per value that isn't
59        // divisible by 8.  In this case, to avoid chunking in the middle of a byte
60        // we calculate how many 8-value words we can fit in a chunk.
61        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        // Aim for 4KiB chunks
68        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                // We can make a full chunk
85                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                // Final chunk, special values
93                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                // If we get here then all chunks were full chunks and we have no remainder chunk
102                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
122/// This impl deals with encoding FSL<FSL<...<FSL<FixedWidth>>>> data as a mini-block compressor.
123/// The tricky part of FSL data is that we want to include inner validity buffers (we don't want these
124/// to be part of the rep-def because that usually ends up being more expensive).
125///
126/// The resulting mini-block will, instead of having a single buffer, have X + 1 buffers where X is
127/// the number of FSL layers that contain validity.
128///
129/// In the simple case where there is no validity inside the FSL layers, all we are doing here is flattening
130/// the FSL layers into a single buffer.
131///
132/// Also: We don't allow a row to be broken across chunks.  This typically isn't too big of a deal since we
133/// are usually dealing with relatively small vectors if we are using mini-block.
134///
135/// Note: when we do have validity we have to make copies of the validity buffers because they are bit buffers
136/// and we need to bit slice them which requires copies or offsets.  Paying the price at write time to make
137/// the copies is better than paying the price at read time to do the bit slicing.
138impl 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        // Count size to calculate rows per chunk
188        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        // It's an estimate because validity buffers may have some padding bits
199        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        // Allocate buffers for validity, these will be slightly bigger than the input validity buffers
212        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        // Now go through and extract validity buffers
229        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        // Finally, add the data buffer
261        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
316/// In this section we deal with per-value encoding of FSL<FSL<...<FSL<FixedWidth>>>> data.
317///
318/// It's easier than mini-block.  All we need to do is flatten the FSL layers into a single buffer.
319/// This includes any validity buffers we encounter on the way.
320impl 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        // The simple case is zero-copy, we just return the flattened inner buffer
349        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        // If there are nullable inner values then we need to zip the validity with the values
380        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                // Each item will need this many bytes of validity prepended to it
393                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        // Hopefully values are pretty large so we don't iterate this loop _too_ many times
428        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/// A decompressor for fixed-width data that has
503/// been written, as-is, to disk in single contiguous array
504#[derive(Debug)]
505pub struct ValueDecompressor {
506    /// How many bits are in each inner-most item (e.g. FSL<Int32, 100> would be 32)
507    bits_per_item: u64,
508    /// How many bits are in each value (e.g. FSL<Int32, 100> would be 3200)
509    ///
510    /// This number is a little trickier to compute because we also have to include bytes
511    /// of any inner validity
512    bits_per_value: u64,
513    /// How many items are in each value (e.g. FSL<Int32, 100> would be 100)
514    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        // Always at least 1 buffer
591        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
630// Helper methods for per-value decompression
631impl ValueDecompressor {
632    fn has_validity(&self) -> bool {
633        self.layers.iter().any(|layer| layer.has_validity)
634    }
635
636    // If there is no validity then decompression is zero-copy, we just need to restore any FSL layers
637    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    // If there is validity then it has been zipped in with the values and we must unzip it
660    fn unzip_decompress(&self, data: FixedWidthDataBlock, num_rows: usize) -> DataBlock {
661        // No support for full-zip on per-value encodings
662        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        // First, go through the layers, setup our builders, allocate space
668        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        // Next, unzip
692        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        // Finally, restore the structure
706        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// public tests module because we share the PRIMITIVE_TYPES constant with fixed_size_list
779#[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        // The Interval type is supported by the reader but the writer works with Lance schema
840        // at the moment and Lance schema can't parse interval
841        // DataType::Interval(IntervalUnit::DayTime),
842    ];
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        // Tests for strange page sizes and batch sizes and validity scenarios for miniblock
920
921        // 10K integers, 100 per array, all valid
922        let data1 = (0..100)
923            .map(|_| Arc::new(Int32Array::from_iter_values(0..100)) as Arc<dyn Array>)
924            .collect::<Vec<_>>();
925
926        // Same as above but with mixed validity
927        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        // Same as above but with all null for first half then all valid
936        // TODO: Re-enable once the all-null path is complete
937        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 /*data3*/] {
946            for batch_size in [10, 100, 1500, 15000] {
947                // 40000 bytes of data
948                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        // [[0, 1], NULL], [NULL, NULL], [[8, 9], [NULL, 11]]
960        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        // A wide FSL<Boolean> is sub-byte, so it chunks eight values per word and the
1037        // smallest unit is 16 values rather than 2.
1038        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        // This regresses a case where rows are large lists that span multiple
1135        // mini-block chunks which gives us some all-premable mini-block chunks.
1136        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        // Several levels of def and multiple pages
1153        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        // Should be 256Ki rows ~ 1MiB of data
1159        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        // Test both explicit configuration and automatic fallback scenarios
1255        // 1. Test explicit "none" compression to force flat encoding
1256        // Also explicitly disable BSS to ensure value encoding is tested
1257        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        // 2. Test automatic fallback to flat encoding when bitpacking conditions aren't met
1266        // Use unique values to avoid RLE encoding
1267        // Explicitly disable BSS to ensure value encoding is tested
1268        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}