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