Skip to main content

lance_encoding/encodings/logical/primitive/
structural_encoder.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use std::collections::HashMap;
5use std::fmt::Debug;
6use std::ops::Range;
7use std::sync::Arc;
8use std::{env, iter, vec};
9
10use arrow_array::cast::AsArray;
11use arrow_array::types::UInt64Type;
12use arrow_array::{Array, ArrayRef, PrimitiveArray, make_array, new_null_array};
13use arrow_buffer::{BooleanBuffer, BooleanBufferBuilder, NullBuffer};
14use arrow_schema::DataType;
15use futures::FutureExt;
16use futures::future::BoxFuture;
17use lance_arrow::DataTypeExt;
18use lance_arrow::deepcopy::deep_copy_nulls;
19use lance_core::Result;
20use lance_core::datatypes::Field;
21use lance_core::error::Error;
22use lance_core::utils::bit::pad_bytes;
23use lance_core::utils::tokio::spawn_cpu;
24
25use crate::buffer::LanceBuffer;
26use crate::compression::{CompressionStrategy, FixedPerValueDecompressor, compress_required_block};
27use crate::constants::{
28    COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY, DICT_DIVISOR_META_KEY,
29    DICT_MAX_SIZE_META_KEY, DICT_SIZE_RATIO_META_KEY, DICT_VALUES_COMPRESSION_ENV_VAR,
30    DICT_VALUES_COMPRESSION_LEVEL_ENV_VAR, DICT_VALUES_COMPRESSION_LEVEL_META_KEY,
31    DICT_VALUES_COMPRESSION_META_KEY, STRUCTURAL_ENCODING_FULLZIP, STRUCTURAL_ENCODING_META_KEY,
32    STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_SPARSE,
33};
34use crate::data::{BlockInfo, DataBlock, FixedWidthDataBlock, VariableWidthBlock};
35use crate::decoder::PageEncoding;
36use crate::encoder::{
37    EncodeTask, EncodedColumn, EncodedPage, EncodingOptions, FieldEncoder, OutOfLineBuffers,
38};
39use crate::encodings::logical::primitive::fullzip::{PerValueCompressor, PerValueDataBlock};
40use crate::encodings::logical::primitive::miniblock::{
41    MiniBlockChunk, MiniBlockCompressed, MiniBlockCompressionContext,
42};
43use crate::encodings::physical::value::{ValueDecompressor, ValueEncoder};
44use crate::format::metadata::CompressiveEncoding;
45use crate::format::{EncodingUtils, metadata};
46use crate::repdef::{
47    ControlWordIterator, DefinitionInterpretation, MiniBlockRepDefBudget, NormalizedStructuralPlan,
48    RepDefBuilder, RepDefSlicer, RepDefUnraveler, SerializedRepDefs, build_control_word_iterator,
49};
50use crate::statistics::{ComputeStat, GetStat, Stat};
51use crate::utils::accumulation::AccumulationQueue;
52use crate::utils::bytepack::BytepackedIntegerEncoder;
53
54use super::{
55    DEFAULT_DICT_DIVISOR, DEFAULT_DICT_MAX_CARDINALITY, DEFAULT_DICT_MAX_SIZE_BYTES,
56    DEFAULT_DICT_SIZE_RATIO, DEFAULT_DICT_VALUES_COMPRESSION, FILL_BYTE, MINIBLOCK_ALIGNMENT,
57};
58use super::{constant, dict, miniblock, planar, sparse};
59
60/// The serialized representation of full-zip data
61struct SerializedFullZip {
62    /// The zipped values buffer
63    values: LanceBuffer,
64    /// The repetition index (only present if there is repetition)
65    repetition_index: Option<LanceBuffer>,
66}
67
68/// An encoder for primitive (leaf) arrays
69///
70/// This encoder is fairly complicated and follows a number of paths depending
71/// on the data.
72///
73/// First, we convert the validity & offsets information into repetition and
74/// definition levels.  Then we compress the data itself into a single buffer.
75///
76/// If the data is narrow then we encode the data in small chunks (each chunk
77/// should be a few disk sectors and contains a buffer of repetition, a buffer
78/// of definition, and a buffer of value data).  This approach is called
79/// "mini-block".  These mini-blocks are stored into a single data buffer.
80///
81/// If the data is wide then we zip together the repetition and definition value
82/// with the value data into a single buffer.  This approach is called "zipped".
83///
84/// If there is any repetition information then we create a repetition index
85///
86/// In addition, the compression process may create zero or more metadata buffers.
87/// For example, a dictionary compression will create dictionary metadata.  Any
88/// mini-block approach has a metadata buffer of block sizes.  This metadata is
89/// stored in a separate buffer on disk and read at initialization time.
90///
91/// TODO: We should concatenate metadata buffers from all pages into a single buffer
92/// at (roughly) the end of the file so there is, at most, one read per column of
93/// metadata per file.
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum MiniblockChunkSize {
96    U16,
97    U32,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub(super) enum ComplexNullEncoding {
102    RawLevels,
103    CompressedLevels,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub(super) enum DictionaryEncodingPolicy {
108    LegacyU16,
109    LegacyU32,
110    Planar,
111}
112
113pub(super) trait PrimitivePageEncodingBehavior: Send + Sync + Debug {
114    fn validate_field(&self, _field: &Field, _metadata: &HashMap<String, String>) -> Result<()> {
115        Ok(())
116    }
117
118    fn try_plan_pages(
119        &self,
120        _ctx: &PrimitivePlanContext<'_>,
121        _arrays: &[ArrayRef],
122        _normalized: &NormalizedStructuralPlan,
123        _row_number: u64,
124        _num_rows: u64,
125        _num_values: u64,
126    ) -> Result<Option<Vec<PrimitivePageData>>> {
127        Ok(None)
128    }
129
130    fn try_encode_page(
131        &self,
132        _ctx: &PrimitiveEncodeContext,
133        page: PrimitivePageData,
134    ) -> Result<PrimitiveEncodeAttempt> {
135        Ok(PrimitiveEncodeAttempt::Unhandled(page))
136    }
137}
138
139/// One executable primitive-page behavior selected by an exact file
140/// composition.
141#[derive(Debug, Clone)]
142pub struct PrimitivePageEncoding {
143    behavior: Arc<dyn PrimitivePageEncodingBehavior>,
144}
145
146impl PrimitivePageEncoding {
147    /// Encode 2.3 pages as independent structural and value planes.
148    ///
149    /// ```
150    /// # use std::sync::Arc;
151    /// # use lance_encoding::{compression::CompressionStrategy, encoder::structural::PrimitivePageEncoding};
152    /// # fn configure(compression: Arc<dyn CompressionStrategy>) {
153    /// let encoding = PrimitivePageEncoding::planar(compression);
154    /// # }
155    /// ```
156    pub fn planar(compression: Arc<dyn CompressionStrategy>) -> Self {
157        Self {
158            behavior: Arc::new(planar::PlanarPrimitiveEncoding { compression }),
159        }
160    }
161
162    /// Reject an explicit request for sparse structural encoding.
163    pub fn reject_sparse() -> Self {
164        Self {
165            behavior: Arc::new(RejectSparsePrimitiveEncoding),
166        }
167    }
168
169    /// Encode constant non-null values as a constant page when applicable.
170    pub fn constant() -> Self {
171        Self {
172            behavior: Arc::new(ConstantPrimitiveEncoding),
173        }
174    }
175
176    /// Encode dense pages with the original u16 miniblock grammar.
177    pub fn dense_u16(compression: Arc<dyn CompressionStrategy>) -> Self {
178        Self {
179            behavior: Arc::new(DenseU16PrimitiveEncoding { compression }),
180        }
181    }
182
183    /// Encode dense pages with the u32 miniblock grammar.
184    pub fn dense_u32(compression: Arc<dyn CompressionStrategy>) -> Self {
185        Self {
186            behavior: Arc::new(DenseU32PrimitiveEncoding { compression }),
187        }
188    }
189}
190
191#[derive(Debug)]
192struct RejectSparsePrimitiveEncoding;
193
194#[derive(Debug)]
195pub(super) struct ConstantPrimitiveEncoding;
196
197#[derive(Debug)]
198struct DenseU16PrimitiveEncoding {
199    compression: Arc<dyn CompressionStrategy>,
200}
201
202#[derive(Debug)]
203struct DenseU32PrimitiveEncoding {
204    compression: Arc<dyn CompressionStrategy>,
205}
206
207pub struct PrimitiveStructuralEncoder {
208    // Accumulates arrays until we have enough data to justify a disk page
209    accumulation_queue: AccumulationQueue,
210
211    keep_original_array: bool,
212    accumulated_repdefs: Vec<RepDefBuilder>,
213    page_encodings: Arc<[PrimitivePageEncoding]>,
214    column_index: u32,
215    field: Field,
216    encoding_metadata: Arc<HashMap<String, String>>,
217}
218
219struct CompressedLevelsChunk {
220    data: LanceBuffer,
221    num_levels: u16,
222}
223
224struct CompressedLevels {
225    data: Vec<CompressedLevelsChunk>,
226    compression: CompressiveEncoding,
227    rep_index: Option<LanceBuffer>,
228}
229
230struct SerializedMiniBlockPage {
231    num_buffers: u64,
232    data: LanceBuffer,
233    metadata: LanceBuffer,
234}
235
236#[derive(Debug, Clone, Copy)]
237pub(super) struct DictEncodingBudget {
238    pub(super) max_dict_entries: u32,
239    pub(super) max_encoded_size: usize,
240    /// Upper bound on the dictionary's value bytes; see `DICT_MAX_SIZE_META_KEY`.
241    pub(super) max_dict_bytes: usize,
242}
243
244pub(super) enum PrimitivePageStructure {
245    Dense {
246        repdef: SerializedRepDefs,
247        single_row_miniblock_repdef_levels: Option<u64>,
248    },
249    Sparse {
250        plan: sparse::SparseStructuralPlan,
251        constant: Option<Vec<u8>>,
252    },
253}
254
255// A primitive page after structural encoding selection and optional dense splitting.
256pub(super) struct PrimitivePageData {
257    // Arrow leaf arrays that contain this page's visible values.
258    pub(super) arrays: Vec<ArrayRef>,
259    // Structural representation aligned to this page.
260    pub(super) structure: PrimitivePageStructure,
261    // Top-level row number of the first row in this page.
262    pub(super) row_number: u64,
263    // Number of top-level rows in this page.
264    pub(super) num_rows: u64,
265}
266
267pub(super) struct PrimitivePlanContext<'a> {
268    pub(super) field: &'a Field,
269    pub(super) encoding_metadata: &'a HashMap<String, String>,
270}
271
272pub(super) enum PrimitiveEncodeAttempt {
273    Encoded(EncodedPage),
274    Unhandled(PrimitivePageData),
275}
276
277// Immutable encoder state shared by per-page encode tasks.
278//
279// Cloning this only clones Arc-backed configuration and field metadata.  Page data
280// stays in PrimitivePageData and is moved into exactly one task.
281#[derive(Clone)]
282pub(super) struct PrimitiveEncodeContext {
283    // Column being encoded.
284    pub(super) column_idx: u32,
285    pub(super) field: Field,
286    pub(super) encoding_metadata: Arc<HashMap<String, String>>,
287    pub(super) is_simple_validity: bool,
288    pub(super) has_repdef_info: bool,
289}
290
291impl PrimitiveStructuralEncoder {
292    pub fn try_new(
293        options: &EncodingOptions,
294        page_encodings: Arc<[PrimitivePageEncoding]>,
295        column_index: u32,
296        field: Field,
297        encoding_metadata: Arc<HashMap<String, String>>,
298    ) -> Result<Self> {
299        for page_encoding in page_encodings.iter() {
300            page_encoding
301                .behavior
302                .validate_field(&field, &encoding_metadata)?;
303        }
304        Ok(Self {
305            accumulation_queue: AccumulationQueue::new(
306                options.cache_bytes_per_column,
307                column_index,
308                options.keep_original_array,
309            ),
310            keep_original_array: options.keep_original_array,
311            accumulated_repdefs: Vec::new(),
312            column_index,
313            page_encodings,
314            field,
315            encoding_metadata,
316        })
317    }
318
319    fn encode_page(
320        page_encodings: &[PrimitivePageEncoding],
321        ctx: &PrimitiveEncodeContext,
322        mut page: PrimitivePageData,
323    ) -> Result<EncodedPage> {
324        for page_encoding in page_encodings {
325            match page_encoding.behavior.try_encode_page(ctx, page)? {
326                PrimitiveEncodeAttempt::Encoded(page) => return Ok(page),
327                PrimitiveEncodeAttempt::Unhandled(unhandled) => page = unhandled,
328            }
329        }
330        Err(Error::invalid_input_source(
331            format!(
332                "No primitive page encoding atom supports field '{}'",
333                ctx.field.name
334            )
335            .into(),
336        ))
337    }
338
339    // TODO: This is a heuristic we may need to tune at some point
340    //
341    // As data gets narrow then the "zipping" process gets too expensive
342    //   and we prefer mini-block
343    // As data gets wide then the # of values per block shrinks (very wide)
344    //   data doesn't even fit in a mini-block and the block overhead gets
345    //   too large and we prefer zipped.
346    pub(super) fn is_narrow(data_block: &DataBlock) -> bool {
347        const MINIBLOCK_MAX_BYTE_LENGTH_PER_VALUE: u64 = 256;
348
349        if let Some(max_len_array) = data_block.get_stat(Stat::MaxLength) {
350            let max_len_array = max_len_array
351                .as_any()
352                .downcast_ref::<PrimitiveArray<UInt64Type>>()
353                .unwrap();
354            if max_len_array.value(0) < MINIBLOCK_MAX_BYTE_LENGTH_PER_VALUE {
355                return true;
356            }
357        }
358        false
359    }
360
361    fn prefers_miniblock(
362        data_block: &DataBlock,
363        encoding_metadata: &HashMap<String, String>,
364    ) -> bool {
365        // If the user specifically requested miniblock then use it
366        if let Some(user_requested) = encoding_metadata.get(STRUCTURAL_ENCODING_META_KEY) {
367            return user_requested.to_lowercase() == STRUCTURAL_ENCODING_MINIBLOCK;
368        }
369        // Otherwise only use miniblock if it is narrow
370        Self::is_narrow(data_block)
371    }
372
373    fn prefers_fullzip(encoding_metadata: &HashMap<String, String>) -> bool {
374        // Fullzip is the backup option so the only reason we wouldn't use it is if the
375        // user specifically requested not to use it (in which case we're probably going
376        // to emit an error)
377        if let Some(user_requested) = encoding_metadata.get(STRUCTURAL_ENCODING_META_KEY) {
378            return user_requested.to_lowercase() == STRUCTURAL_ENCODING_FULLZIP;
379        }
380        true
381    }
382
383    // Converts value data, repetition levels, and definition levels into a single
384    // buffer of mini-blocks.  In addition, creates a buffer of mini-block metadata
385    // which tells us the size of each block.  Finally, if repetition is present then
386    // we also create a buffer for the repetition index.
387    //
388    // Each chunk is serialized as:
389    // | num_bufs (1 byte) | buf_lens (2 bytes per buffer) | P | buf0 | P | buf1 | ... | bufN | P |
390    //
391    // P - Padding inserted to ensure each buffer is 8-byte aligned and the buffer size is a multiple
392    //     of 8 bytes (so that the next chunk is 8-byte aligned).
393    //
394    // Each block has a u16 word of metadata.  The upper 12 bits contain the
395    // # of 8-byte words in the block (if the block does not fill the final word
396    // then up to 7 bytes of padding are added).  The lower 4 bits describe the log_2
397    // number of values (e.g. if there are 1024 then the lower 4 bits will be
398    // 0xA)  All blocks except the last must have power-of-two number of values.
399    // This not only makes metadata smaller but it makes decoding easier since
400    // batch sizes are typically a power of 2.  4 bits would allow us to express
401    // up to 32Ki values.
402    //
403    // This means blocks can have 1 to 32Ki values and 8 - 32Ki bytes.
404    //
405    // All metadata words are serialized (as little endian) into a single buffer
406    // of metadata values.
407    //
408    // If there is repetition then we also create a repetition index.  This is a
409    // single buffer of integer vectors (stored in row major order).  There is one
410    // entry for each chunk.  The size of the vector is based on the depth of random
411    // access we want to support.
412    //
413    // A vector of size 2 is the minimum and will support row-based random access (e.g.
414    // "take the 57th row").  A vector of size 3 will support 1 level of nested access
415    // (e.g. "take the 3rd item in the 57th row").  A vector of size 4 will support 2
416    // levels of nested access and so on.
417    //
418    // The first number in the vector is the number of top-level rows that complete in
419    // the chunk.  The second number is the number of second-level rows that complete
420    // after the final top-level row completed (or beginning of the chunk if no top-level
421    // row completes in the chunk).  And so on.  The final number in the vector is always
422    // the number of leftover items not covered by earlier entries in the vector.
423    //
424    // Currently we are limited to 0 levels of nested access but that will change in the
425    // future.
426    //
427    // The repetition index and the chunk metadata are read at initialization time and
428    // cached in memory.
429    fn serialize_miniblocks(
430        miniblocks: MiniBlockCompressed,
431        rep: Option<Vec<CompressedLevelsChunk>>,
432        def: Option<Vec<CompressedLevelsChunk>>,
433        miniblock_chunk_size: MiniblockChunkSize,
434    ) -> Result<SerializedMiniBlockPage> {
435        let bytes_rep = rep
436            .as_ref()
437            .map(|rep| rep.iter().map(|r| r.data.len()).sum::<usize>())
438            .unwrap_or(0);
439        let bytes_def = def
440            .as_ref()
441            .map(|def| def.iter().map(|d| d.data.len()).sum::<usize>())
442            .unwrap_or(0);
443        let bytes_data = miniblocks.data.iter().map(|d| d.len()).sum::<usize>();
444        let mut num_buffers = miniblocks.data.len();
445        if rep.is_some() {
446            num_buffers += 1;
447        }
448        if def.is_some() {
449            num_buffers += 1;
450        }
451        // 2 bytes for the length of each buffer and up to 7 bytes of padding per buffer
452        let max_extra = 9 * num_buffers;
453        let mut data_buffer = Vec::with_capacity(bytes_rep + bytes_def + bytes_data + max_extra);
454        let chunk_size_bytes = match miniblock_chunk_size {
455            MiniblockChunkSize::U16 => 2,
456            MiniblockChunkSize::U32 => 4,
457        };
458        let mut meta_buffer = Vec::with_capacity(miniblocks.chunks.len() * chunk_size_bytes);
459
460        let mut rep_iter = rep.map(|r| r.into_iter());
461        let mut def_iter = def.map(|d| d.into_iter());
462
463        let mut buffer_offsets = vec![0; miniblocks.data.len()];
464        for chunk in miniblocks.chunks {
465            let start_pos = data_buffer.len();
466            // Start of chunk should be aligned
467            debug_assert_eq!(start_pos % MINIBLOCK_ALIGNMENT, 0);
468
469            let rep = rep_iter.as_mut().map(|r| r.next().unwrap());
470            let def = def_iter.as_mut().map(|d| d.next().unwrap());
471
472            // Write the number of levels, or 0 if there is no rep/def
473            let num_levels = rep
474                .as_ref()
475                .map(|r| r.num_levels)
476                .unwrap_or(def.as_ref().map(|d| d.num_levels).unwrap_or(0));
477            data_buffer.extend_from_slice(&num_levels.to_le_bytes());
478
479            // Write the buffer lengths
480            if let Some(rep) = rep.as_ref() {
481                let bytes_rep = u16::try_from(rep.data.len()).map_err(|_| {
482                    Error::internal(format!(
483                        "Repetition buffer size ({} bytes) too large",
484                        rep.data.len()
485                    ))
486                })?;
487                data_buffer.extend_from_slice(&bytes_rep.to_le_bytes());
488            }
489            if let Some(def) = def.as_ref() {
490                let bytes_def = u16::try_from(def.data.len()).map_err(|_| {
491                    Error::internal(format!(
492                        "Definition buffer size ({} bytes) too large",
493                        def.data.len()
494                    ))
495                })?;
496                data_buffer.extend_from_slice(&bytes_def.to_le_bytes());
497            }
498
499            if miniblock_chunk_size == MiniblockChunkSize::U32 {
500                for &buffer_size in &chunk.buffer_sizes {
501                    data_buffer.extend_from_slice(&buffer_size.to_le_bytes());
502                }
503            } else {
504                for &buffer_size in &chunk.buffer_sizes {
505                    let buffer_size = u16::try_from(buffer_size).map_err(|_| {
506                        Error::internal(format!(
507                            "Mini-block buffer size ({} bytes) too large for 16-bit metadata",
508                            buffer_size
509                        ))
510                    })?;
511                    data_buffer.extend_from_slice(&buffer_size.to_le_bytes());
512                }
513            }
514
515            // Pad
516            let add_padding = |data_buffer: &mut Vec<u8>| {
517                let pad = pad_bytes::<MINIBLOCK_ALIGNMENT>(data_buffer.len());
518                data_buffer.extend(iter::repeat_n(FILL_BYTE, pad));
519            };
520            add_padding(&mut data_buffer);
521
522            // Write the buffers themselves
523            if let Some(rep) = rep.as_ref() {
524                data_buffer.extend_from_slice(&rep.data);
525                add_padding(&mut data_buffer);
526            }
527            if let Some(def) = def.as_ref() {
528                data_buffer.extend_from_slice(&def.data);
529                add_padding(&mut data_buffer);
530            }
531            for (buffer_size, (buffer, buffer_offset)) in chunk
532                .buffer_sizes
533                .iter()
534                .zip(miniblocks.data.iter().zip(buffer_offsets.iter_mut()))
535            {
536                let start = *buffer_offset;
537                let end = start + *buffer_size as usize;
538                *buffer_offset += *buffer_size as usize;
539                data_buffer.extend_from_slice(&buffer[start..end]);
540                add_padding(&mut data_buffer);
541            }
542
543            let chunk_bytes = data_buffer.len() - start_pos;
544            let max_chunk_size = match miniblock_chunk_size {
545                MiniblockChunkSize::U16 => 32 * 1024,
546                MiniblockChunkSize::U32 => 1_u64 << 31,
547            };
548            if chunk_bytes == 0 || chunk_bytes as u64 > max_chunk_size {
549                return Err(Error::internal(format!(
550                    "Mini-block chunk size {} bytes exceeds the {} byte metadata limit",
551                    chunk_bytes, max_chunk_size
552                )));
553            }
554            if chunk_bytes % MINIBLOCK_ALIGNMENT != 0 {
555                return Err(Error::internal(format!(
556                    "Mini-block chunk size {} bytes is not aligned to {} bytes",
557                    chunk_bytes, MINIBLOCK_ALIGNMENT
558                )));
559            }
560            if chunk.log_num_values > 15 {
561                return Err(Error::internal(format!(
562                    "Mini-block log_num_values {} exceeds the 4-bit metadata limit",
563                    chunk.log_num_values
564                )));
565            }
566            // We subtract 1 here from chunk_bytes because we want to be able to express
567            // a size of 32KiB and not (32Ki - 8)B which is what we'd get otherwise with
568            // 0xFFF
569            let divided_bytes = chunk_bytes / MINIBLOCK_ALIGNMENT;
570            let divided_bytes_minus_one = (divided_bytes - 1) as u64;
571
572            let metadata = (divided_bytes_minus_one << 4) | chunk.log_num_values as u64;
573            if miniblock_chunk_size == MiniblockChunkSize::U32 {
574                meta_buffer.extend_from_slice(&(metadata as u32).to_le_bytes());
575            } else {
576                meta_buffer.extend_from_slice(&(metadata as u16).to_le_bytes());
577            }
578        }
579
580        let data_buffer = LanceBuffer::from(data_buffer);
581        let metadata_buffer = LanceBuffer::from(meta_buffer);
582
583        Ok(SerializedMiniBlockPage {
584            num_buffers: miniblocks.data.len() as u64,
585            data: data_buffer,
586            metadata: metadata_buffer,
587        })
588    }
589
590    /// Compresses a buffer of levels into chunks
591    ///
592    /// If these are repetition levels then we also calculate the repetition index here (that
593    /// is the third return value)
594    fn compress_levels(
595        mut levels: RepDefSlicer<'_>,
596        num_elements: u64,
597        compression_strategy: &dyn CompressionStrategy,
598        chunks: &[MiniBlockChunk],
599        // This will be 0 if we are compressing def levels
600        max_rep: u16,
601    ) -> Result<CompressedLevels> {
602        let mut rep_index = if max_rep > 0 {
603            Vec::with_capacity(chunks.len())
604        } else {
605            vec![]
606        };
607        // Make the levels into a FixedWidth data block
608        let num_levels = levels.num_levels() as u64;
609        let levels_buf = levels.all_levels().clone();
610
611        let mut fixed_width_block = FixedWidthDataBlock {
612            data: levels_buf,
613            bits_per_value: 16,
614            num_values: num_levels,
615            block_info: BlockInfo::new(),
616        };
617        // Compute statistics to enable optimal compression for rep/def levels
618        fixed_width_block.compute_stat();
619
620        let levels_block = DataBlock::FixedWidth(fixed_width_block);
621        let levels_field = Field::new_arrow("", DataType::UInt16, false)?;
622        // Pick a block compressor
623        let compressor =
624            compression_strategy.create_block_compressor(&levels_field, &levels_block)?;
625        let mut compressor_desc = None;
626        // Compress blocks of levels (sized according to the chunks)
627        let mut level_chunks = Vec::with_capacity(chunks.len());
628        let mut values_counter = 0;
629        for (chunk_idx, chunk) in chunks.iter().enumerate() {
630            let chunk_num_values = chunk.num_values(values_counter, num_elements);
631            debug_assert!(chunk_num_values > 0);
632            values_counter += chunk_num_values;
633            let chunk_levels = if chunk_idx < chunks.len() - 1 {
634                levels.slice_next(chunk_num_values as usize)
635            } else {
636                levels.slice_rest()
637            };
638            let num_chunk_levels = (chunk_levels.len() / 2) as u64;
639            if max_rep > 0 {
640                // If max_rep > 0 then we are working with rep levels and we need
641                // to calculate the repetition index.  The repetition index for a
642                // chunk is currently 2 values (in the future it may be more).
643                //
644                // The first value is the number of rows that _finish_ in the
645                // chunk.
646                //
647                // The second value is the number of "leftovers" after the last
648                // finished row in the chunk.
649                let rep_values = chunk_levels.borrow_to_typed_slice::<u16>();
650                let rep_values = rep_values.as_ref();
651
652                // We skip 1 here because a max_rep at spot 0 doesn't count as a finished list (we
653                // will count it in the previous chunk)
654                let mut num_rows = rep_values.iter().skip(1).filter(|v| **v == max_rep).count();
655                let num_leftovers = if chunk_idx < chunks.len() - 1 {
656                    rep_values
657                        .iter()
658                        .rev()
659                        .position(|v| *v == max_rep)
660                        // # of leftovers includes the max_rep spot
661                        .map(|pos| pos + 1)
662                        .unwrap_or(rep_values.len())
663                } else {
664                    // Last chunk can't have leftovers
665                    0
666                };
667
668                if chunk_idx != 0 && rep_values.first() == Some(&max_rep) {
669                    // This chunk starts with a new row and so, if we thought we had leftovers
670                    // in the previous chunk, we were mistaken
671                    // TODO: Can use unchecked here
672                    let rep_len = rep_index.len();
673                    if rep_index[rep_len - 1] != 0 {
674                        // We thought we had leftovers but that was actually a full row
675                        rep_index[rep_len - 2] += 1;
676                        rep_index[rep_len - 1] = 0;
677                    }
678                }
679
680                if chunk_idx == chunks.len() - 1 {
681                    // The final list
682                    num_rows += 1;
683                }
684                rep_index.push(num_rows as u64);
685                rep_index.push(num_leftovers as u64);
686            }
687            let mut chunk_fixed_width = FixedWidthDataBlock {
688                data: chunk_levels,
689                bits_per_value: 16,
690                num_values: num_chunk_levels,
691                block_info: BlockInfo::new(),
692            };
693            chunk_fixed_width.compute_stat();
694            let chunk_levels_block = DataBlock::FixedWidth(chunk_fixed_width);
695            let (compressed_levels, chunk_compressor_desc) =
696                compressor.compress(chunk_levels_block)?;
697            if let Some(compressor_desc) = compressor_desc.as_ref() {
698                if compressor_desc != &chunk_compressor_desc {
699                    return Err(Error::internal(
700                        "Rep/def block compressor changed encoding between chunks".to_string(),
701                    ));
702                }
703            } else {
704                compressor_desc = Some(chunk_compressor_desc);
705            }
706            let compressed_levels = compressed_levels.ok_or_else(|| {
707                Error::internal(
708                    "Rep/def block compressor selected a metadata-only codec".to_string(),
709                )
710            })?;
711            let num_levels = u16::try_from(num_chunk_levels).map_err(|_| {
712                Error::invalid_input_source(
713                    format!(
714                        "Mini-block cannot encode {} rep/def levels in one chunk. \
715                         This usually means a top-level row contains too much nested structure \
716                         for the current layout.",
717                        num_chunk_levels
718                    )
719                    .into(),
720                )
721            })?;
722            level_chunks.push(CompressedLevelsChunk {
723                data: compressed_levels,
724                num_levels,
725            });
726        }
727        debug_assert_eq!(levels.num_levels_remaining(), 0);
728        let rep_index = if rep_index.is_empty() {
729            None
730        } else {
731            Some(LanceBuffer::reinterpret_vec(rep_index))
732        };
733        Ok(CompressedLevels {
734            data: level_chunks,
735            compression: compressor_desc.ok_or_else(|| {
736                Error::internal("Rep/def compression produced no chunks".to_string())
737            })?,
738            rep_index,
739        })
740    }
741
742    fn encode_simple_all_null(
743        column_idx: u32,
744        num_rows: u64,
745        row_number: u64,
746    ) -> Result<EncodedPage> {
747        let description =
748            EncodingUtils::constant_layout(&[DefinitionInterpretation::NullableItem], None);
749        Ok(EncodedPage {
750            column_idx,
751            data: vec![],
752            description: PageEncoding::Structural(description),
753            num_rows,
754            row_number,
755        })
756    }
757
758    fn encode_complex_all_null_vals(
759        data: &Arc<[u16]>,
760        compression_strategy: &dyn CompressionStrategy,
761    ) -> Result<(LanceBuffer, metadata::CompressiveEncoding)> {
762        let buffer = LanceBuffer::reinterpret_slice(data.clone());
763        let mut fixed_width_block = FixedWidthDataBlock {
764            data: buffer,
765            bits_per_value: 16,
766            num_values: data.len() as u64,
767            block_info: BlockInfo::new(),
768        };
769        fixed_width_block.compute_stat();
770
771        let levels_block = DataBlock::FixedWidth(fixed_width_block);
772        let levels_field = Field::new_arrow("", DataType::UInt16, false)?;
773        let (compressed_buffer, encoding) =
774            compress_required_block(compression_strategy, &levels_field, levels_block)?;
775        Ok((compressed_buffer, encoding))
776    }
777
778    // Encodes a page where all values are null but we have rep/def
779    // information that we need to store (e.g. to distinguish between
780    // different kinds of null)
781    fn encode_complex_all_null(
782        column_idx: u32,
783        repdef: crate::repdef::SerializedRepDefs,
784        row_number: u64,
785        num_rows: u64,
786        complex_null_encoding: ComplexNullEncoding,
787        compression_strategy: &dyn CompressionStrategy,
788    ) -> Result<EncodedPage> {
789        if complex_null_encoding == ComplexNullEncoding::RawLevels {
790            let rep_bytes = if let Some(rep) = repdef.repetition_levels.as_ref() {
791                LanceBuffer::reinterpret_slice(rep.clone())
792            } else {
793                LanceBuffer::empty()
794            };
795
796            let def_bytes = if let Some(def) = repdef.definition_levels.as_ref() {
797                LanceBuffer::reinterpret_slice(def.clone())
798            } else {
799                LanceBuffer::empty()
800            };
801
802            let description = EncodingUtils::constant_layout(&repdef.def_meaning, None);
803            return Ok(EncodedPage {
804                column_idx,
805                data: vec![rep_bytes, def_bytes],
806                description: PageEncoding::Structural(description),
807                num_rows,
808                row_number,
809            });
810        }
811
812        let (rep_bytes, rep_encoding, num_rep_values) = if let Some(rep) =
813            repdef.repetition_levels.as_ref()
814        {
815            let num_values = rep.len() as u64;
816            let (buffer, encoding) = Self::encode_complex_all_null_vals(rep, compression_strategy)?;
817            (buffer, Some(encoding), num_values)
818        } else {
819            (LanceBuffer::empty(), None, 0)
820        };
821
822        let (def_bytes, def_encoding, num_def_values) = if let Some(def) =
823            repdef.definition_levels.as_ref()
824        {
825            let num_values = def.len() as u64;
826            let (buffer, encoding) = Self::encode_complex_all_null_vals(def, compression_strategy)?;
827            (buffer, Some(encoding), num_values)
828        } else {
829            (LanceBuffer::empty(), None, 0)
830        };
831
832        let description = EncodingUtils::compressed_all_null_constant_layout(
833            &repdef.def_meaning,
834            rep_encoding,
835            def_encoding,
836            num_rep_values,
837            num_def_values,
838        );
839        Ok(EncodedPage {
840            column_idx,
841            data: vec![rep_bytes, def_bytes],
842            description: PageEncoding::Structural(description),
843            num_rows,
844            row_number,
845        })
846    }
847
848    pub(super) fn leaf_validity(
849        repdef: &crate::repdef::SerializedRepDefs,
850        num_values: usize,
851    ) -> Result<Option<BooleanBuffer>> {
852        let rep = repdef
853            .repetition_levels
854            .as_ref()
855            .map(|rep| rep.as_ref().to_vec());
856        let def = repdef
857            .definition_levels
858            .as_ref()
859            .map(|def| def.as_ref().to_vec());
860        let mut unraveler = RepDefUnraveler::new(
861            rep,
862            def,
863            repdef.def_meaning.clone().into(),
864            num_values as u64,
865        );
866        if unraveler.is_all_valid() {
867            return Ok(None);
868        }
869        let mut validity = BooleanBufferBuilder::new(num_values);
870        unraveler.unravel_validity(&mut validity)?;
871        Ok(Some(validity.finish()))
872    }
873
874    fn is_constant_values(
875        arrays: &[ArrayRef],
876        scalar: &ArrayRef,
877        validity: Option<&BooleanBuffer>,
878    ) -> Result<bool> {
879        debug_assert_eq!(scalar.len(), 1);
880        debug_assert_eq!(scalar.null_count(), 0);
881
882        match scalar.data_type() {
883            DataType::Boolean => {
884                let mut global_idx = 0usize;
885                let scalar_val = scalar.as_boolean().value(0);
886                for arr in arrays {
887                    let bool_arr = arr.as_boolean();
888                    for i in 0..arr.len() {
889                        let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
890                        global_idx += 1;
891                        if !is_valid {
892                            continue;
893                        }
894                        if bool_arr.value(i) != scalar_val {
895                            return Ok(false);
896                        }
897                    }
898                }
899                Ok(true)
900            }
901            DataType::Utf8 => Self::is_constant_utf8::<i32>(arrays, scalar, validity),
902            DataType::LargeUtf8 => Self::is_constant_utf8::<i64>(arrays, scalar, validity),
903            DataType::Binary => Self::is_constant_binary::<i32>(arrays, scalar, validity),
904            DataType::LargeBinary => Self::is_constant_binary::<i64>(arrays, scalar, validity),
905            data_type => {
906                let mut global_idx = 0usize;
907                let Some(byte_width) = data_type.byte_width_opt() else {
908                    return Ok(false);
909                };
910                let scalar_data = scalar.to_data();
911                if scalar_data.buffers().len() != 1 || !scalar_data.child_data().is_empty() {
912                    return Ok(false);
913                }
914                let scalar_bytes = scalar_data.buffers()[0].as_slice();
915                if scalar_bytes.len() != byte_width {
916                    return Ok(false);
917                }
918
919                for arr in arrays {
920                    let data = arr.to_data();
921                    if data.buffers().is_empty() {
922                        return Ok(false);
923                    }
924                    let buf = data.buffers()[0].as_slice();
925                    let base = data.offset();
926                    for i in 0..arr.len() {
927                        let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
928                        global_idx += 1;
929                        if !is_valid {
930                            continue;
931                        }
932                        let start = (base + i) * byte_width;
933                        if buf[start..start + byte_width] != scalar_bytes[..] {
934                            return Ok(false);
935                        }
936                    }
937                }
938                Ok(true)
939            }
940        }
941    }
942
943    fn is_constant_utf8<O: arrow_array::OffsetSizeTrait>(
944        arrays: &[ArrayRef],
945        scalar: &ArrayRef,
946        validity: Option<&BooleanBuffer>,
947    ) -> Result<bool> {
948        debug_assert_eq!(scalar.len(), 1);
949        let scalar_val = scalar.as_string::<O>().value(0).as_bytes();
950        let mut global_idx = 0usize;
951        for arr in arrays {
952            let str_arr = arr.as_string::<O>();
953            for i in 0..arr.len() {
954                let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
955                global_idx += 1;
956                if !is_valid {
957                    continue;
958                }
959                if str_arr.value(i).as_bytes() != scalar_val {
960                    return Ok(false);
961                }
962            }
963        }
964        Ok(true)
965    }
966
967    fn is_constant_binary<O: arrow_array::OffsetSizeTrait>(
968        arrays: &[ArrayRef],
969        scalar: &ArrayRef,
970        validity: Option<&BooleanBuffer>,
971    ) -> Result<bool> {
972        debug_assert_eq!(scalar.len(), 1);
973        let scalar_val = scalar.as_binary::<O>().value(0);
974        let mut global_idx = 0usize;
975        for arr in arrays {
976            let bin_arr = arr.as_binary::<O>();
977            for i in 0..arr.len() {
978                let is_valid = validity.map(|v| v.value(global_idx)).unwrap_or(true);
979                global_idx += 1;
980                if !is_valid {
981                    continue;
982                }
983                if bin_arr.value(i) != scalar_val {
984                    return Ok(false);
985                }
986            }
987        }
988        Ok(true)
989    }
990
991    pub(super) fn find_constant_scalar(
992        arrays: &[ArrayRef],
993        validity: Option<&BooleanBuffer>,
994    ) -> Result<Option<ArrayRef>> {
995        if arrays.is_empty() {
996            return Ok(None);
997        }
998
999        let global_scalar_idx = if let Some(validity) = validity {
1000            let Some(idx) = (0..validity.len()).find(|&i| validity.value(i)) else {
1001                return Ok(None);
1002            };
1003            idx
1004        } else {
1005            0
1006        };
1007
1008        let mut idx_remaining = global_scalar_idx;
1009        let mut scalar_arr_idx = 0usize;
1010        while scalar_arr_idx < arrays.len() {
1011            let len = arrays[scalar_arr_idx].len();
1012            if idx_remaining < len {
1013                break;
1014            }
1015            idx_remaining -= len;
1016            scalar_arr_idx += 1;
1017        }
1018
1019        if scalar_arr_idx >= arrays.len() {
1020            return Ok(None);
1021        }
1022
1023        let scalar =
1024            lance_arrow::scalar::extract_scalar_value(&arrays[scalar_arr_idx], idx_remaining)?;
1025        if scalar.null_count() != 0 {
1026            return Ok(None);
1027        }
1028        if !Self::is_constant_values(arrays, &scalar, validity)? {
1029            return Ok(None);
1030        }
1031        Ok(Some(scalar))
1032    }
1033
1034    fn resolve_dict_values_compression_metadata(
1035        field_metadata: &HashMap<String, String>,
1036        env_compression: Option<String>,
1037        env_compression_level: Option<String>,
1038    ) -> HashMap<String, String> {
1039        let mut metadata = HashMap::new();
1040
1041        let compression = field_metadata
1042            .get(DICT_VALUES_COMPRESSION_META_KEY)
1043            .cloned()
1044            .or(env_compression)
1045            .unwrap_or_else(|| DEFAULT_DICT_VALUES_COMPRESSION.to_string());
1046        metadata.insert(COMPRESSION_META_KEY.to_string(), compression);
1047
1048        if let Some(compression_level) = field_metadata
1049            .get(DICT_VALUES_COMPRESSION_LEVEL_META_KEY)
1050            .cloned()
1051            .or(env_compression_level)
1052        {
1053            metadata.insert(COMPRESSION_LEVEL_META_KEY.to_string(), compression_level);
1054        }
1055
1056        metadata
1057    }
1058
1059    pub(super) fn build_dict_values_compressor_field(field: &Field) -> Result<Field> {
1060        // This is an internal synthetic field used only to feed metadata into
1061        // `create_block_compressor` for dictionary values. The concrete type/name here
1062        // are not semantically meaningful; we rely on explicit metadata below to control
1063        // general compression selection for dictionary values.
1064        let mut dict_values_field = Field::new_arrow("", DataType::UInt16, false)?;
1065        dict_values_field.metadata = Self::resolve_dict_values_compression_metadata(
1066            &field.metadata,
1067            env::var(DICT_VALUES_COMPRESSION_ENV_VAR).ok(),
1068            env::var(DICT_VALUES_COMPRESSION_LEVEL_ENV_VAR).ok(),
1069        );
1070        Ok(dict_values_field)
1071    }
1072
1073    #[allow(clippy::too_many_arguments)]
1074    pub(super) fn encode_miniblock(
1075        column_idx: u32,
1076        field: &Field,
1077        compression_strategy: &dyn CompressionStrategy,
1078        data: DataBlock,
1079        repdef: crate::repdef::SerializedRepDefs,
1080        row_number: u64,
1081        dictionary_data: Option<DataBlock>,
1082        num_rows: u64,
1083        miniblock_chunk_size: MiniblockChunkSize,
1084    ) -> Result<EncodedPage> {
1085        if let DataBlock::AllNull(_null_block) = data {
1086            // We should not be using mini-block for all-null.  There are other structural
1087            // encodings for that.
1088            unreachable!()
1089        }
1090
1091        let num_items = data.num_values();
1092
1093        let compressor = if dictionary_data.is_some() {
1094            compression_strategy.create_dictionary_index_compressor(field, &data)?
1095        } else {
1096            compression_strategy.create_miniblock_compressor(field, &data)?
1097        };
1098        let common_chunk_buffers =
1099            u64::from(repdef.rep_slicer().is_some()) + u64::from(repdef.def_slicer().is_some());
1100        let support_large_chunk = miniblock_chunk_size == MiniblockChunkSize::U32;
1101        let compression_context =
1102            MiniBlockCompressionContext::new(common_chunk_buffers, support_large_chunk, true);
1103        let (compressed_data, value_encoding) = compressor.compress(compression_context, data)?;
1104
1105        let max_rep = repdef.def_meaning.iter().filter(|l| l.is_list()).count() as u16;
1106
1107        let mut compressed_rep = repdef
1108            .rep_slicer()
1109            .map(|rep_slicer| {
1110                Self::compress_levels(
1111                    rep_slicer,
1112                    num_items,
1113                    compression_strategy,
1114                    &compressed_data.chunks,
1115                    max_rep,
1116                )
1117            })
1118            .transpose()?;
1119
1120        let (rep_index, rep_index_depth) =
1121            match compressed_rep.as_mut().and_then(|cr| cr.rep_index.as_mut()) {
1122                Some(rep_index) => (Some(rep_index.clone()), 1),
1123                None => (None, 0),
1124            };
1125
1126        let mut compressed_def = repdef
1127            .def_slicer()
1128            .map(|def_slicer| {
1129                Self::compress_levels(
1130                    def_slicer,
1131                    num_items,
1132                    compression_strategy,
1133                    &compressed_data.chunks,
1134                    /*max_rep=*/ 0,
1135                )
1136            })
1137            .transpose()?;
1138
1139        // TODO: Parquet sparsely encodes values here.  We could do the same but
1140        // then we won't have log2 values per chunk.  This means more metadata
1141        // and potentially more decoder asymmetry.  However, it may be worth
1142        // investigating at some point
1143
1144        let rep_data = compressed_rep
1145            .as_mut()
1146            .map(|cr| std::mem::take(&mut cr.data));
1147        let def_data = compressed_def
1148            .as_mut()
1149            .map(|cd| std::mem::take(&mut cd.data));
1150
1151        let serialized =
1152            Self::serialize_miniblocks(compressed_data, rep_data, def_data, miniblock_chunk_size)?;
1153        let has_large_chunk = miniblock_chunk_size == MiniblockChunkSize::U32;
1154
1155        // Metadata, Data, Dictionary, (maybe) Repetition Index
1156        let mut data = Vec::with_capacity(4);
1157        data.push(serialized.metadata);
1158        data.push(serialized.data);
1159
1160        if let Some(dictionary_data) = dictionary_data {
1161            let num_dictionary_items = dictionary_data.num_values();
1162            let dict_values_field = Self::build_dict_values_compressor_field(field)?;
1163
1164            let (dictionary_buffer, dictionary_encoding) =
1165                compress_required_block(compression_strategy, &dict_values_field, dictionary_data)?;
1166
1167            data.push(dictionary_buffer);
1168            if let Some(rep_index) = rep_index {
1169                data.push(rep_index);
1170            }
1171
1172            let description = EncodingUtils::miniblock_layout(
1173                compressed_rep.map(|cr| cr.compression),
1174                compressed_def.map(|cd| cd.compression),
1175                value_encoding,
1176                rep_index_depth,
1177                serialized.num_buffers,
1178                Some((dictionary_encoding, num_dictionary_items)),
1179                &repdef.def_meaning,
1180                num_items,
1181                has_large_chunk,
1182            );
1183            Ok(EncodedPage {
1184                num_rows,
1185                column_idx,
1186                data,
1187                description: PageEncoding::Structural(description),
1188                row_number,
1189            })
1190        } else {
1191            let description = EncodingUtils::miniblock_layout(
1192                compressed_rep.map(|cr| cr.compression),
1193                compressed_def.map(|cd| cd.compression),
1194                value_encoding,
1195                rep_index_depth,
1196                serialized.num_buffers,
1197                None,
1198                &repdef.def_meaning,
1199                num_items,
1200                has_large_chunk,
1201            );
1202
1203            if let Some(rep_index) = rep_index {
1204                let view = rep_index.borrow_to_typed_slice::<u64>();
1205                let total = view.as_chunks::<2>().0.iter().map(|c| c[0]).sum::<u64>();
1206                debug_assert_eq!(total, num_rows);
1207
1208                data.push(rep_index);
1209            }
1210
1211            Ok(EncodedPage {
1212                num_rows,
1213                column_idx,
1214                data,
1215                description: PageEncoding::Structural(description),
1216                row_number,
1217            })
1218        }
1219    }
1220
1221    // For fixed-size data we encode < control word | data > for each value
1222    fn serialize_full_zip_fixed(
1223        fixed: FixedWidthDataBlock,
1224        mut repdef: ControlWordIterator,
1225        num_values: u64,
1226    ) -> Result<SerializedFullZip> {
1227        if !fixed.bits_per_value.is_multiple_of(8) {
1228            return Err(Error::invalid_input_source(
1229                format!(
1230                    "Full-zip fixed-width values must be byte aligned, got {} bits per value",
1231                    fixed.bits_per_value
1232                )
1233                .into(),
1234            ));
1235        }
1236
1237        let len = fixed.data.len() + repdef.bytes_per_word() * num_values as usize;
1238        let mut zipped_data = Vec::with_capacity(len);
1239
1240        let max_rep_index_val = if repdef.has_repetition() {
1241            len as u64
1242        } else {
1243            // Setting this to 0 means we won't write a repetition index
1244            0
1245        };
1246        let mut rep_index_builder =
1247            BytepackedIntegerEncoder::with_capacity(num_values as usize + 1, max_rep_index_val);
1248
1249        let bytes_per_value = fixed.bits_per_value as usize / 8;
1250        let mut offset = 0;
1251
1252        if bytes_per_value == 0 {
1253            // No data, just dump the repdef into the buffer
1254            while let Some(control) = repdef.append_next(&mut zipped_data) {
1255                if control.is_new_row {
1256                    // We have finished a row
1257                    debug_assert!(offset <= len);
1258                    rep_index_builder.append_trusted(offset as u64);
1259                }
1260                offset = zipped_data.len();
1261            }
1262        } else {
1263            // We have data, zip it with the repdef
1264            let mut data_iter = fixed.data.chunks_exact(bytes_per_value);
1265            while let Some(control) = repdef.append_next(&mut zipped_data) {
1266                if control.is_new_row {
1267                    // We have finished a row
1268                    debug_assert!(offset <= len);
1269                    rep_index_builder.append_trusted(offset as u64);
1270                }
1271                if control.is_visible {
1272                    let value = data_iter.next().unwrap();
1273                    zipped_data.extend_from_slice(value);
1274                }
1275                offset = zipped_data.len();
1276            }
1277        }
1278
1279        debug_assert_eq!(zipped_data.len(), len);
1280        // Put the final value in the rep index
1281        rep_index_builder.append_trusted(zipped_data.len() as u64);
1282
1283        let zipped_data = LanceBuffer::from(zipped_data);
1284        let rep_index = rep_index_builder.into_data();
1285        let rep_index = if rep_index.is_empty() {
1286            None
1287        } else {
1288            Some(LanceBuffer::from(rep_index))
1289        };
1290        Ok(SerializedFullZip {
1291            values: zipped_data,
1292            repetition_index: rep_index,
1293        })
1294    }
1295
1296    // For variable-size data we encode < control word | length | data > for each value
1297    //
1298    // In addition, we create a second buffer, the repetition index
1299    fn serialize_full_zip_variable(
1300        variable: VariableWidthBlock,
1301        mut repdef: ControlWordIterator,
1302        num_items: u64,
1303    ) -> Result<SerializedFullZip> {
1304        let bytes_per_offset = variable.bits_per_offset as usize / 8;
1305        if !variable.bits_per_offset.is_multiple_of(8) {
1306            return Err(Error::invalid_input_source(
1307                format!(
1308                    "Full-zip variable-width offsets must be byte aligned, got {} bits per offset",
1309                    variable.bits_per_offset
1310                )
1311                .into(),
1312            ));
1313        }
1314        let len = variable.data.len()
1315            + repdef.bytes_per_word() * num_items as usize
1316            + bytes_per_offset * variable.num_values as usize;
1317        let mut buf = Vec::with_capacity(len);
1318
1319        let max_rep_index_val = len as u64;
1320        let mut rep_index_builder =
1321            BytepackedIntegerEncoder::with_capacity(num_items as usize + 1, max_rep_index_val);
1322
1323        // TODO: byte pack the item lengths with varint encoding
1324        match bytes_per_offset {
1325            4 => {
1326                let offs = variable.offsets.borrow_to_typed_slice::<u32>();
1327                let mut rep_offset = 0;
1328                let mut windows_iter = offs.as_ref().windows(2);
1329                while let Some(control) = repdef.append_next(&mut buf) {
1330                    if control.is_new_row {
1331                        // We have finished a row
1332                        debug_assert!(rep_offset <= len);
1333                        rep_index_builder.append_trusted(rep_offset as u64);
1334                    }
1335                    if control.is_visible {
1336                        let window = windows_iter.next().unwrap();
1337                        if control.is_valid_item {
1338                            buf.extend_from_slice(&(window[1] - window[0]).to_le_bytes());
1339                            buf.extend_from_slice(
1340                                &variable.data[window[0] as usize..window[1] as usize],
1341                            );
1342                        }
1343                    }
1344                    rep_offset = buf.len();
1345                }
1346            }
1347            8 => {
1348                let offs = variable.offsets.borrow_to_typed_slice::<u64>();
1349                let mut rep_offset = 0;
1350                let mut windows_iter = offs.as_ref().windows(2);
1351                while let Some(control) = repdef.append_next(&mut buf) {
1352                    if control.is_new_row {
1353                        // We have finished a row
1354                        debug_assert!(rep_offset <= len);
1355                        rep_index_builder.append_trusted(rep_offset as u64);
1356                    }
1357                    if control.is_visible {
1358                        let window = windows_iter.next().unwrap();
1359                        if control.is_valid_item {
1360                            buf.extend_from_slice(&(window[1] - window[0]).to_le_bytes());
1361                            buf.extend_from_slice(
1362                                &variable.data[window[0] as usize..window[1] as usize],
1363                            );
1364                        }
1365                    }
1366                    rep_offset = buf.len();
1367                }
1368            }
1369            _ => {
1370                return Err(Error::invalid_input_source(
1371                    format!(
1372                        "Full-zip variable-width offsets must be 32 or 64 bits, got {} bits",
1373                        variable.bits_per_offset
1374                    )
1375                    .into(),
1376                ));
1377            }
1378        }
1379
1380        // We might have saved a few bytes by not copying lengths when the length was zero.  However,
1381        // if we are over `len` then we have a bug.
1382        debug_assert!(buf.len() <= len);
1383        // Put the final value in the rep index
1384        rep_index_builder.append_trusted(buf.len() as u64);
1385
1386        let zipped_data = LanceBuffer::from(buf);
1387        let rep_index = rep_index_builder.into_data();
1388        debug_assert!(!rep_index.is_empty());
1389        let rep_index = Some(LanceBuffer::from(rep_index));
1390        Ok(SerializedFullZip {
1391            values: zipped_data,
1392            repetition_index: rep_index,
1393        })
1394    }
1395
1396    /// Serializes data into a single buffer according to the full-zip format which zips
1397    /// together the repetition, definition, and value data into a single buffer.
1398    fn serialize_full_zip(
1399        compressed_data: PerValueDataBlock,
1400        repdef: ControlWordIterator,
1401        num_items: u64,
1402    ) -> Result<SerializedFullZip> {
1403        match compressed_data {
1404            PerValueDataBlock::Fixed(fixed) => {
1405                Self::serialize_full_zip_fixed(fixed, repdef, num_items)
1406            }
1407            PerValueDataBlock::Variable(var) => {
1408                Self::serialize_full_zip_variable(var, repdef, num_items)
1409            }
1410        }
1411    }
1412
1413    fn expand_boolean_to_bytes(fixed: FixedWidthDataBlock) -> FixedWidthDataBlock {
1414        debug_assert_eq!(fixed.bits_per_value, 1);
1415        let num_values = fixed.num_values as usize;
1416        let bool_buf = BooleanBuffer::new(fixed.data.into_buffer(), 0, num_values);
1417        let expanded: Vec<u8> = (0..num_values).map(|i| bool_buf.value(i) as u8).collect();
1418        FixedWidthDataBlock {
1419            data: LanceBuffer::from(expanded),
1420            bits_per_value: 8,
1421            num_values: fixed.num_values,
1422            block_info: BlockInfo::new(),
1423        }
1424    }
1425
1426    pub(super) fn encode_full_zip(
1427        column_idx: u32,
1428        field: &Field,
1429        compression_strategy: &dyn CompressionStrategy,
1430        data: DataBlock,
1431        repdef: crate::repdef::SerializedRepDefs,
1432        row_number: u64,
1433        num_lists: u64,
1434    ) -> Result<EncodedPage> {
1435        let max_rep = repdef
1436            .repetition_levels
1437            .as_ref()
1438            .map_or(0, |r| r.iter().max().copied().unwrap_or(0));
1439        let max_def = repdef
1440            .definition_levels
1441            .as_ref()
1442            .map_or(0, |d| d.iter().max().copied().unwrap_or(0));
1443
1444        // To handle FSL we just flatten
1445        // let data = data.flatten();
1446
1447        let (num_items, num_visible_items) =
1448            if let Some(rep_levels) = repdef.repetition_levels.as_ref() {
1449                // If there are rep levels there may be "invisible" items and we need to encode
1450                // rep_levels.len() things which might be larger than data.num_values()
1451                (rep_levels.len() as u64, data.num_values())
1452            } else {
1453                // If there are no rep levels then we encode data.num_values() things
1454                (data.num_values(), data.num_values())
1455            };
1456
1457        let max_visible_def = repdef.max_visible_level.unwrap_or(u16::MAX);
1458
1459        let repdef_iter = build_control_word_iterator(
1460            repdef.repetition_levels.as_deref(),
1461            max_rep,
1462            repdef.definition_levels.as_deref(),
1463            max_def,
1464            max_visible_def,
1465            num_items as usize,
1466        );
1467        let bits_rep = repdef_iter.bits_rep();
1468        let bits_def = repdef_iter.bits_def();
1469
1470        // Full-zip requires byte-aligned values; expand 1-bit booleans to 1 byte each.
1471        let data = match data {
1472            DataBlock::FixedWidth(fixed) if fixed.bits_per_value == 1 => {
1473                DataBlock::FixedWidth(Self::expand_boolean_to_bytes(fixed))
1474            }
1475            other => other,
1476        };
1477
1478        let compressor = compression_strategy.create_per_value(field, &data)?;
1479        let (compressed_data, value_encoding) = compressor.compress(data)?;
1480
1481        let description = match &compressed_data {
1482            PerValueDataBlock::Fixed(fixed) => EncodingUtils::fixed_full_zip_layout(
1483                bits_rep,
1484                bits_def,
1485                fixed.bits_per_value as u32,
1486                value_encoding,
1487                &repdef.def_meaning,
1488                num_items as u32,
1489                num_visible_items as u32,
1490            ),
1491            PerValueDataBlock::Variable(variable) => EncodingUtils::variable_full_zip_layout(
1492                bits_rep,
1493                bits_def,
1494                variable.bits_per_offset as u32,
1495                value_encoding,
1496                &repdef.def_meaning,
1497                num_items as u32,
1498                num_visible_items as u32,
1499            ),
1500        };
1501
1502        let zipped = Self::serialize_full_zip(compressed_data, repdef_iter, num_items)?;
1503
1504        let data = if let Some(repindex) = zipped.repetition_index {
1505            vec![zipped.values, repindex]
1506        } else {
1507            vec![zipped.values]
1508        };
1509
1510        Ok(EncodedPage {
1511            num_rows: num_lists,
1512            column_idx,
1513            data,
1514            description: PageEncoding::Structural(description),
1515            row_number,
1516        })
1517    }
1518
1519    pub(super) fn dictionary_min_values() -> u64 {
1520        env::var("LANCE_ENCODING_DICT_TOO_SMALL")
1521            .ok()
1522            .and_then(|val| val.parse().ok())
1523            .unwrap_or(100)
1524    }
1525
1526    pub(super) fn try_dictionary_encode(
1527        data: &DataBlock,
1528        field: &Field,
1529        policy: DictionaryEncodingPolicy,
1530        num_fallback_values: u64,
1531    ) -> Result<Option<(DataBlock, DataBlock)>> {
1532        if policy == DictionaryEncodingPolicy::Planar
1533            && let DataBlock::FixedSizeList(fsl) = data
1534        {
1535            if data.num_values() < Self::dictionary_min_values() {
1536                return Ok(None);
1537            }
1538            // The existing per-value representation retains nested dimensions and
1539            // child validity. Byte-aligned leaves permit exact whole-row keys.
1540            let mut child = fsl.child.as_ref();
1541            loop {
1542                child = match child {
1543                    DataBlock::FixedSizeList(inner) => inner.child.as_ref(),
1544                    DataBlock::Nullable(inner) => inner.data.as_ref(),
1545                    DataBlock::FixedWidth(inner) if inner.bits_per_value.is_multiple_of(8) => break,
1546                    DataBlock::AllNull(_) => break,
1547                    _ => return Ok(None),
1548                };
1549            }
1550            let (PerValueDataBlock::Fixed(values), encoding) =
1551                PerValueCompressor::compress(&ValueEncoder::default(), data.clone())?
1552            else {
1553                return Err(Error::internal(
1554                    "Fixed-size-list rows must have fixed width",
1555                ));
1556            };
1557            let block = DataBlock::FixedWidth(values.clone());
1558            let Some(mut budget) = Self::should_dictionary_encode(&block, field, policy) else {
1559                return Ok(None);
1560            };
1561            // Sparse zipped pages store only materialized rows. Dictionary indices
1562            // still cover every slot, so both indices and hidden dictionary items
1563            // must fit the budget for that compact payload, not the Arrow buffers.
1564            debug_assert!(num_fallback_values <= values.num_values);
1565            budget.max_encoded_size = (budget.max_encoded_size as u128
1566                * num_fallback_values as u128
1567                / values.num_values as u128) as usize;
1568            let Some((indices, items)) =
1569                dict::encode_fixed_rows(&values, budget.max_dict_entries, budget.max_encoded_size)
1570            else {
1571                return Ok(None);
1572            };
1573            let Some(metadata::compressive_encoding::Compression::FixedSizeList(description)) =
1574                encoding.compression
1575            else {
1576                return Err(Error::internal("Fixed-size-list row encoding is missing"));
1577            };
1578            let count = items.num_values;
1579            let items = FixedPerValueDecompressor::decompress(
1580                &ValueDecompressor::from_fsl(&description)?,
1581                items,
1582                count,
1583            )?;
1584            return Ok(Some((indices, items)));
1585        }
1586        Ok(
1587            Self::should_dictionary_encode(data, field, policy).and_then(|budget| {
1588                dict::dictionary_encode(
1589                    data,
1590                    budget.max_dict_entries,
1591                    budget.max_encoded_size,
1592                    budget.max_dict_bytes,
1593                )
1594            }),
1595        )
1596    }
1597
1598    pub(super) fn should_dictionary_encode(
1599        data_block: &DataBlock,
1600        field: &Field,
1601        dictionary_policy: DictionaryEncodingPolicy,
1602    ) -> Option<DictEncodingBudget> {
1603        const DEFAULT_SAMPLE_SIZE: usize = 4096;
1604        const DEFAULT_SAMPLE_UNIQUE_RATIO: f64 = 0.98;
1605
1606        // Since we only dictionary encode FixedWidth and VariableWidth blocks for now, we skip
1607        // estimating the size for other types.
1608        match data_block {
1609            DataBlock::FixedWidth(fixed) => {
1610                if fixed.bits_per_value == 64
1611                    && dictionary_policy == DictionaryEncodingPolicy::LegacyU16
1612                {
1613                    return None;
1614                }
1615                let vector = dictionary_policy == DictionaryEncodingPolicy::Planar
1616                    && matches!(field.data_type(), DataType::FixedSizeList(_, _));
1617                if !vector && fixed.bits_per_value != 64 && fixed.bits_per_value != 128 {
1618                    return None;
1619                }
1620                if fixed.bits_per_value % 8 != 0 {
1621                    return None;
1622                }
1623            }
1624            DataBlock::VariableWidth(var) => {
1625                if var.bits_per_offset != 32 && var.bits_per_offset != 64 {
1626                    return None;
1627                }
1628            }
1629            _ => return None,
1630        }
1631
1632        // Don't dictionary encode tiny arrays.
1633        let too_small = Self::dictionary_min_values();
1634        if data_block.num_values() < too_small {
1635            return None;
1636        }
1637
1638        let num_values = data_block.num_values();
1639
1640        // Bound actual dictionary construction by the configured cardinality and size budgets.
1641        let divisor: u64 = field
1642            .metadata
1643            .get(DICT_DIVISOR_META_KEY)
1644            .and_then(|val| val.parse().ok())
1645            .or_else(|| {
1646                env::var("LANCE_ENCODING_DICT_DIVISOR")
1647                    .ok()
1648                    .and_then(|val| val.parse().ok())
1649            })
1650            .unwrap_or(DEFAULT_DICT_DIVISOR);
1651
1652        let max_cardinality: u64 = env::var("LANCE_ENCODING_DICT_MAX_CARDINALITY")
1653            .ok()
1654            .and_then(|val| val.parse().ok())
1655            .unwrap_or(DEFAULT_DICT_MAX_CARDINALITY);
1656
1657        let threshold_cardinality = num_values
1658            .checked_div(divisor.max(1))
1659            .unwrap_or(0)
1660            .min(max_cardinality);
1661        if threshold_cardinality == 0 {
1662            return None;
1663        }
1664
1665        // Get size ratio from metadata or env var.
1666        let threshold_ratio = field
1667            .metadata
1668            .get(DICT_SIZE_RATIO_META_KEY)
1669            .and_then(|val| val.parse::<f64>().ok())
1670            .or_else(|| {
1671                env::var("LANCE_ENCODING_DICT_SIZE_RATIO")
1672                    .ok()
1673                    .and_then(|val| val.parse().ok())
1674            })
1675            .unwrap_or(DEFAULT_DICT_SIZE_RATIO);
1676
1677        if threshold_ratio <= 0.0 || threshold_ratio > 1.0 {
1678            panic!(
1679                "Invalid parameter: dict-size-ratio is {} which is not in the range (0, 1].",
1680                threshold_ratio
1681            );
1682        }
1683
1684        let data_size = data_block.data_size();
1685        if data_size == 0 {
1686            return None;
1687        }
1688
1689        let max_encoded_size = (data_size as f64 * threshold_ratio) as u64;
1690        let max_encoded_size = usize::try_from(max_encoded_size).ok()?;
1691
1692        let max_dict_bytes: usize = match field.metadata.get(DICT_MAX_SIZE_META_KEY) {
1693            Some(val) => val.parse().unwrap_or_else(|_| {
1694                panic!("Invalid parameter: dict-max-size is {val} which is not a byte count")
1695            }),
1696            None => DEFAULT_DICT_MAX_SIZE_BYTES,
1697        };
1698
1699        // A repeated sample can come from either a small dictionary or a high-cardinality
1700        // page with short runs. Its unique ratio cannot be extrapolated to the whole page.
1701        // Planar leaves that decision to bounded dictionary construction.
1702        if let Some(sample_unique_ratio) =
1703            Self::sample_unique_ratio(data_block, DEFAULT_SAMPLE_SIZE)?
1704        {
1705            if sample_unique_ratio >= DEFAULT_SAMPLE_UNIQUE_RATIO {
1706                return None;
1707            }
1708
1709            // Preserve the existing candidate selection for legacy writers.
1710            if dictionary_policy != DictionaryEncodingPolicy::Planar {
1711                let projected_cardinality = (sample_unique_ratio * num_values as f64).ceil() as u64;
1712                if projected_cardinality > threshold_cardinality {
1713                    return None;
1714                }
1715            }
1716        }
1717
1718        let max_dict_entries = u32::try_from(threshold_cardinality.min(i32::MAX as u64)).ok()?;
1719        Some(DictEncodingBudget {
1720            max_dict_entries,
1721            max_encoded_size,
1722            max_dict_bytes,
1723        })
1724    }
1725
1726    /// Samples whether a page looks near-unique before attempting dictionary encoding.
1727    ///
1728    /// The probe uses deterministic block sampling (not RNG sampling), which keeps
1729    /// the check cheap and reproducible across runs. The result is only a gate for
1730    /// whether we try dictionary encoding, not a cardinality statistic.
1731    /// Returns `Some(None)` when there are too few reliable samples or the block type does not
1732    /// support dictionary encoding. Returns `None` for malformed data.
1733    fn sample_unique_ratio(data_block: &DataBlock, max_samples: usize) -> Option<Option<f64>> {
1734        use std::collections::HashSet;
1735
1736        const NUM_SAMPLE_BLOCKS: usize = 32;
1737        const MIN_RELIABLE_SAMPLES: usize = 1024;
1738
1739        let num_values = usize::try_from(data_block.num_values()).ok()?;
1740        if num_values == 0 {
1741            return Some(None);
1742        }
1743
1744        let sample_count = num_values.min(max_samples).max(1);
1745        if sample_count < MIN_RELIABLE_SAMPLES {
1746            return Some(None);
1747        }
1748
1749        let block_count = NUM_SAMPLE_BLOCKS.min(sample_count).min(num_values).max(1);
1750        let samples_per_block = (sample_count / block_count).max(1);
1751        let mut indices = Vec::with_capacity(sample_count);
1752        for block_idx in 0..block_count {
1753            let block_start = block_idx * num_values / block_count;
1754            let next_block_start = ((block_idx + 1) * num_values / block_count).min(num_values);
1755            let block_len = next_block_start.saturating_sub(block_start);
1756            let samples_in_block = samples_per_block.min(block_len);
1757            indices.extend((0..samples_in_block).map(|offset| block_start + offset));
1758        }
1759
1760        if indices.len() < MIN_RELIABLE_SAMPLES {
1761            return Some(None);
1762        }
1763
1764        let ratio = match data_block {
1765            DataBlock::FixedWidth(fixed) => match fixed.bits_per_value {
1766                64 => {
1767                    let values = fixed.data.borrow_to_typed_slice::<u64>();
1768                    let values = values.as_ref();
1769                    let mut unique: HashSet<u64> =
1770                        HashSet::with_capacity(indices.len().min(MIN_RELIABLE_SAMPLES));
1771                    for idx in indices.iter().copied() {
1772                        unique.insert(values.get(idx).copied()?);
1773                    }
1774                    unique.len() as f64 / indices.len() as f64
1775                }
1776                128 => {
1777                    let values = fixed.data.borrow_to_typed_slice::<u128>();
1778                    let values = values.as_ref();
1779                    let mut unique: HashSet<u128> =
1780                        HashSet::with_capacity(indices.len().min(MIN_RELIABLE_SAMPLES));
1781                    for idx in indices.iter().copied() {
1782                        unique.insert(values.get(idx).copied()?);
1783                    }
1784                    unique.len() as f64 / indices.len() as f64
1785                }
1786                bits if bits > 0 && bits.is_multiple_of(8) => {
1787                    let width = usize::try_from(bits / 8).ok()?;
1788                    let mut unique = HashSet::with_capacity(indices.len());
1789                    for &index in &indices {
1790                        let start = index.checked_mul(width)?;
1791                        let end = start.checked_add(width)?;
1792                        unique.insert(xxhash_rust::xxh3::xxh3_64(fixed.data.get(start..end)?));
1793                    }
1794                    unique.len() as f64 / indices.len() as f64
1795                }
1796                _ => return Some(None),
1797            },
1798            DataBlock::VariableWidth(var) => {
1799                use xxhash_rust::xxh3::xxh3_64;
1800
1801                // Hash variable-width slices instead of storing borrowed slice keys.
1802                let mut unique: HashSet<u64> =
1803                    HashSet::with_capacity(indices.len().min(MIN_RELIABLE_SAMPLES));
1804                match var.bits_per_offset {
1805                    32 => {
1806                        let offsets_ref = var.offsets.borrow_to_typed_slice::<u32>();
1807                        let offsets: &[u32] = offsets_ref.as_ref();
1808                        for i in indices.iter().copied() {
1809                            let start = usize::try_from(*offsets.get(i)?).ok()?;
1810                            let end = usize::try_from(*offsets.get(i + 1)?).ok()?;
1811                            if start > end || end > var.data.len() {
1812                                return None;
1813                            }
1814                            unique.insert(xxh3_64(&var.data[start..end]));
1815                        }
1816                    }
1817                    64 => {
1818                        let offsets_ref = var.offsets.borrow_to_typed_slice::<u64>();
1819                        let offsets: &[u64] = offsets_ref.as_ref();
1820                        for i in indices.iter().copied() {
1821                            let start = usize::try_from(*offsets.get(i)?).ok()?;
1822                            let end = usize::try_from(*offsets.get(i + 1)?).ok()?;
1823                            if start > end || end > var.data.len() {
1824                                return None;
1825                            }
1826                            unique.insert(xxh3_64(&var.data[start..end]));
1827                        }
1828                    }
1829                    _ => return Some(None),
1830                }
1831                unique.len() as f64 / indices.len() as f64
1832            }
1833            _ => return Some(None),
1834        };
1835
1836        Some(Some(ratio))
1837    }
1838
1839    fn slice_repdef(repdef: &SerializedRepDefs, range: Range<usize>) -> SerializedRepDefs {
1840        let repetition_levels = repdef
1841            .repetition_levels
1842            .as_ref()
1843            .map(|levels| levels[range.clone()].to_vec());
1844        let definition_levels = repdef
1845            .definition_levels
1846            .as_ref()
1847            .map(|levels| levels[range].to_vec());
1848        SerializedRepDefs::new_with_fixed_size_list_levels(
1849            repetition_levels,
1850            definition_levels,
1851            repdef.def_meaning.clone(),
1852            repdef.has_fixed_size_list_levels(),
1853        )
1854    }
1855
1856    fn slice_arrays(
1857        arrays: &[ArrayRef],
1858        value_start: u64,
1859        num_values: u64,
1860    ) -> Result<Vec<ArrayRef>> {
1861        if num_values == 0 {
1862            return Ok(Vec::new());
1863        }
1864
1865        let mut values_to_skip = usize::try_from(value_start).map_err(|_| {
1866            Error::invalid_input(format!("Value start {} is too large", value_start))
1867        })?;
1868        let mut values_remaining = usize::try_from(num_values).map_err(|_| {
1869            Error::invalid_input(format!("Value count {} is too large", num_values))
1870        })?;
1871        let mut sliced = Vec::new();
1872
1873        for array in arrays {
1874            if values_to_skip >= array.len() {
1875                values_to_skip -= array.len();
1876                continue;
1877            }
1878
1879            let offset = values_to_skip;
1880            let len = (array.len() - offset).min(values_remaining);
1881            sliced.push(array.slice(offset, len));
1882            values_remaining -= len;
1883            values_to_skip = 0;
1884
1885            if values_remaining == 0 {
1886                break;
1887            }
1888        }
1889
1890        if values_remaining != 0 {
1891            return Err(Error::internal(format!(
1892                "Page split requested {} values starting at {}, but the page did not contain enough values",
1893                num_values, value_start
1894            )));
1895        }
1896
1897        Ok(sliced)
1898    }
1899
1900    pub(super) fn split_pages_for_miniblock_repdef_budget(
1901        arrays: Vec<ArrayRef>,
1902        repdef: SerializedRepDefs,
1903        budget: MiniBlockRepDefBudget,
1904        row_number: u64,
1905        num_rows: u64,
1906    ) -> Result<Vec<PrimitivePageData>> {
1907        if budget == MiniBlockRepDefBudget::WithinBudget {
1908            return Ok(vec![PrimitivePageData {
1909                arrays,
1910                structure: PrimitivePageStructure::Dense {
1911                    repdef,
1912                    single_row_miniblock_repdef_levels: None,
1913                },
1914                row_number,
1915                num_rows,
1916            }]);
1917        }
1918        if let MiniBlockRepDefBudget::SingleRowOverBudget(num_levels) = budget {
1919            return Ok(vec![PrimitivePageData {
1920                arrays,
1921                structure: PrimitivePageStructure::Dense {
1922                    repdef,
1923                    single_row_miniblock_repdef_levels: Some(num_levels),
1924                },
1925                row_number,
1926                num_rows,
1927            }]);
1928        }
1929
1930        let MiniBlockRepDefBudget::RequiresPageSplit(splits) = budget else {
1931            unreachable!();
1932        };
1933
1934        let mut pages = Vec::with_capacity(splits.len());
1935        for split in splits {
1936            let arrays = Self::slice_arrays(&arrays, split.value_start, split.num_values)?;
1937            let repdef = Self::slice_repdef(&repdef, split.level_range);
1938            pages.push(PrimitivePageData {
1939                arrays,
1940                structure: PrimitivePageStructure::Dense {
1941                    repdef,
1942                    single_row_miniblock_repdef_levels: None,
1943                },
1944                row_number: row_number + split.row_start,
1945                num_rows: split.num_rows,
1946            });
1947        }
1948        Ok(pages)
1949    }
1950
1951    pub(super) fn encode_dense_page(
1952        ctx: PrimitiveEncodeContext,
1953        page: PrimitivePageData,
1954        compression_strategy: Arc<dyn CompressionStrategy>,
1955        miniblock_chunk_size: MiniblockChunkSize,
1956        complex_null_encoding: ComplexNullEncoding,
1957        dictionary_policy: DictionaryEncodingPolicy,
1958    ) -> Result<EncodedPage> {
1959        let PrimitiveEncodeContext {
1960            column_idx,
1961            field,
1962            encoding_metadata,
1963            is_simple_validity,
1964            has_repdef_info,
1965        } = ctx;
1966        let PrimitivePageData {
1967            arrays,
1968            structure,
1969            row_number,
1970            num_rows,
1971        } = page;
1972        let num_values = arrays.iter().map(|arr| arr.len() as u64).sum();
1973
1974        let (repdef, single_row_miniblock_repdef_levels) = match structure {
1975            PrimitivePageStructure::Dense {
1976                repdef,
1977                single_row_miniblock_repdef_levels,
1978            } => (repdef, single_row_miniblock_repdef_levels),
1979            PrimitivePageStructure::Sparse { .. } => {
1980                unreachable!("dense atom received sparse page")
1981            }
1982        };
1983
1984        if num_values == 0 {
1985            // This page contains only structural events, such as empty/null list rows.
1986            // The existing complex-null layout stores the rep/def stream without value buffers.
1987            log::debug!(
1988                "Encoding column {} with {} items ({} rows) using complex-null layout",
1989                column_idx,
1990                num_values,
1991                num_rows
1992            );
1993            return Self::encode_complex_all_null(
1994                column_idx,
1995                repdef,
1996                row_number,
1997                num_rows,
1998                complex_null_encoding,
1999                compression_strategy.as_ref(),
2000            );
2001        }
2002
2003        let leaf_validity = Self::leaf_validity(&repdef, num_values as usize)?;
2004        let all_null = leaf_validity
2005            .as_ref()
2006            .map(|validity| validity.count_set_bits() == 0)
2007            .unwrap_or(false);
2008
2009        if all_null {
2010            return if is_simple_validity {
2011                log::debug!(
2012                    "Encoding column {} with {} items ({} rows) using simple-null layout",
2013                    column_idx,
2014                    num_values,
2015                    num_rows
2016                );
2017                Self::encode_simple_all_null(column_idx, num_values, row_number)
2018            } else {
2019                log::debug!(
2020                    "Encoding column {} with {} items ({} rows) using complex-null layout",
2021                    column_idx,
2022                    num_values,
2023                    num_rows
2024                );
2025                Self::encode_complex_all_null(
2026                    column_idx,
2027                    repdef,
2028                    row_number,
2029                    num_rows,
2030                    complex_null_encoding,
2031                    compression_strategy.as_ref(),
2032                )
2033            };
2034        }
2035
2036        if let DataType::Struct(fields) = &field.data_type()
2037            && fields.is_empty()
2038        {
2039            if has_repdef_info {
2040                return Err(Error::invalid_input_source(format!("Empty structs with rep/def information are not yet supported.  The field {} is an empty struct that either has nulls or is in a list.", field.name).into()));
2041            }
2042            // This is maybe a little confusing but the reader should never look at this anyways and it
2043            // seems like overkill to invent a new layout just for "empty structs".
2044            return Self::encode_simple_all_null(column_idx, num_values, row_number);
2045        }
2046
2047        let data_block = DataBlock::from_arrays(&arrays, num_values);
2048
2049        if let Some(num_levels) = single_row_miniblock_repdef_levels {
2050            let requested_encoding = encoding_metadata
2051                .get(STRUCTURAL_ENCODING_META_KEY)
2052                .map(|requested| requested.to_lowercase());
2053            let fullzip_error = match &data_block {
2054                // 1-bit booleans are widened to bytes inside `encode_full_zip`.
2055                DataBlock::FixedWidth(fixed)
2056                    if fixed.bits_per_value != 1 && !fixed.bits_per_value.is_multiple_of(8) =>
2057                {
2058                    Some(format!(
2059                        "Full-zip fixed-width values must be byte aligned, got {} bits per value",
2060                        fixed.bits_per_value
2061                    ))
2062                }
2063                DataBlock::VariableWidth(variable)
2064                    if !variable.bits_per_offset.is_multiple_of(8) =>
2065                {
2066                    Some(format!(
2067                        "Full-zip variable-width offsets must be byte aligned, got {} bits per offset",
2068                        variable.bits_per_offset
2069                    ))
2070                }
2071                DataBlock::VariableWidth(variable)
2072                    if variable.bits_per_offset != 32 && variable.bits_per_offset != 64 =>
2073                {
2074                    Some(format!(
2075                        "Full-zip variable-width offsets must be 32 or 64 bits, got {} bits",
2076                        variable.bits_per_offset
2077                    ))
2078                }
2079                DataBlock::Dictionary(_) => {
2080                    Some("Full-zip does not encode dictionary data blocks directly".to_string())
2081                }
2082                DataBlock::FixedSizeList(fsl) => match fsl.clone().try_into_flat() {
2083                    Some(flat) if flat.bits_per_value.is_multiple_of(8) => None,
2084                    Some(flat) => Some(format!(
2085                        "Full-zip fixed-size-list values must be byte aligned after flattening, got {} bits per value",
2086                        flat.bits_per_value
2087                    )),
2088                    None => Some(
2089                        "Full-zip fixed-size-list capability requires a flat fixed-width child"
2090                            .to_string(),
2091                    ),
2092                },
2093                DataBlock::FixedWidth(_) | DataBlock::VariableWidth(_) | DataBlock::Struct(_) => {
2094                    None
2095                }
2096                other => Some(format!(
2097                    "Full-zip does not support value block type {}",
2098                    other.name()
2099                )),
2100            };
2101            match requested_encoding.as_deref() {
2102                Some(STRUCTURAL_ENCODING_FULLZIP) => {
2103                    if let Some(reason) = fullzip_error {
2104                        return Err(Error::invalid_input_source(reason.into()));
2105                    }
2106                    return Self::encode_full_zip(
2107                        column_idx,
2108                        &field,
2109                        compression_strategy.as_ref(),
2110                        data_block,
2111                        repdef,
2112                        row_number,
2113                        num_rows,
2114                    );
2115                }
2116                Some(STRUCTURAL_ENCODING_MINIBLOCK) | None => {
2117                    if requested_encoding.is_none() && fullzip_error.is_none() {
2118                        log::debug!(
2119                            "Encoding column {} with {} items using full-zip layout because mini-block cannot split the structural page",
2120                            column_idx,
2121                            num_values
2122                        );
2123                        return Self::encode_full_zip(
2124                            column_idx,
2125                            &field,
2126                            compression_strategy.as_ref(),
2127                            data_block,
2128                            repdef,
2129                            row_number,
2130                            num_rows,
2131                        );
2132                    }
2133                    return Err(Error::invalid_input_source(
2134                        format!(
2135                            "Mini-block cannot encode {} rep/def levels in one top-level row. \
2136                             This usually means the row contains too much nested structure \
2137                             for the current layout.",
2138                            num_levels
2139                        )
2140                        .into(),
2141                    ));
2142                }
2143                _ => {}
2144            }
2145        }
2146
2147        let requires_full_zip_packed_struct =
2148            if let DataBlock::Struct(ref struct_data_block) = data_block {
2149                struct_data_block.has_variable_width_child()
2150            } else {
2151                false
2152            };
2153
2154        if requires_full_zip_packed_struct {
2155            log::debug!(
2156                "Encoding column {} with {} items using full-zip packed struct layout",
2157                column_idx,
2158                num_values
2159            );
2160            return Self::encode_full_zip(
2161                column_idx,
2162                &field,
2163                compression_strategy.as_ref(),
2164                data_block,
2165                repdef,
2166                row_number,
2167                num_rows,
2168            );
2169        }
2170
2171        if let DataBlock::Dictionary(dict) = data_block {
2172            log::debug!(
2173                "Encoding column {} with {} items using dictionary encoding (already dictionary encoded)",
2174                column_idx,
2175                num_values
2176            );
2177            let (mut indices_data_block, dictionary_data_block) = dict.into_parts();
2178            // TODO: https://github.com/lancedb/lance/issues/4809
2179            // If we compute stats on dictionary_data_block => panic.
2180            // If we don't compute stats on indices_data_block => panic.
2181            // This is messy.  Don't make me call compute_stat ever.
2182            indices_data_block.compute_stat();
2183            return Self::encode_miniblock(
2184                column_idx,
2185                &field,
2186                compression_strategy.as_ref(),
2187                indices_data_block,
2188                repdef,
2189                row_number,
2190                Some(dictionary_data_block),
2191                num_rows,
2192                miniblock_chunk_size,
2193            );
2194        }
2195
2196        // Try dictionary encoding first if applicable. If encoding aborts, fall back to the
2197        // preferred structural encoding.
2198        let dict_result =
2199            Self::try_dictionary_encode(&data_block, &field, dictionary_policy, num_values)?;
2200
2201        if let Some((indices_data_block, dictionary_data_block)) = dict_result {
2202            Self::encode_miniblock(
2203                column_idx,
2204                &field,
2205                compression_strategy.as_ref(),
2206                indices_data_block,
2207                repdef,
2208                row_number,
2209                Some(dictionary_data_block),
2210                num_rows,
2211                miniblock_chunk_size,
2212            )
2213        } else if Self::prefers_miniblock(&data_block, encoding_metadata.as_ref()) {
2214            log::debug!(
2215                "Encoding column {} with {} items using mini-block layout",
2216                column_idx,
2217                num_values
2218            );
2219            Self::encode_miniblock(
2220                column_idx,
2221                &field,
2222                compression_strategy.as_ref(),
2223                data_block,
2224                repdef,
2225                row_number,
2226                None,
2227                num_rows,
2228                miniblock_chunk_size,
2229            )
2230        } else if Self::prefers_fullzip(encoding_metadata.as_ref()) {
2231            log::debug!(
2232                "Encoding column {} with {} items using full-zip layout",
2233                column_idx,
2234                num_values
2235            );
2236            Self::encode_full_zip(
2237                column_idx,
2238                &field,
2239                compression_strategy.as_ref(),
2240                data_block,
2241                repdef,
2242                row_number,
2243                num_rows,
2244            )
2245        } else {
2246            Err(Error::invalid_input_source(format!("Cannot determine structural encoding for field {}.  This typically indicates an invalid value of the field metadata key {}", field.name, STRUCTURAL_ENCODING_META_KEY).into()))
2247        }
2248    }
2249
2250    /// Rebuilds dictionary chunks whose values array is empty.
2251    ///
2252    /// Such chunks are entirely null and retain their key validity until rep/def has been
2253    /// recorded.  At flush time their keys are zeroed and attached to a neighboring non-empty
2254    /// dictionary.  If every chunk is empty, one non-null placeholder value makes key zero valid.
2255    /// Rep/def retains the logical nullness, so these replacement keys are never exposed.
2256    fn rebuild_empty_dictionary_chunks(arrays: Vec<ArrayRef>) -> Result<Vec<ArrayRef>> {
2257        if !arrays.iter().any(|array| {
2258            array
2259                .as_any_dictionary_opt()
2260                .is_some_and(|dictionary| dictionary.values().is_empty())
2261        }) {
2262            return Ok(arrays);
2263        }
2264
2265        let zeroed = |data_type: &DataType, len: usize| {
2266            new_null_array(data_type, len)
2267                .to_data()
2268                .into_builder()
2269                .nulls(None)
2270                .build()
2271                .map(make_array)
2272        };
2273        let rebuild =
2274            |keys: Vec<ArrayRef>, data_type: &DataType, values: ArrayRef| -> Result<ArrayRef> {
2275                let keys = keys.iter().map(|keys| keys.as_ref()).collect::<Vec<_>>();
2276                let keys = arrow_select::concat::concat(&keys)?;
2277                let data = keys
2278                    .to_data()
2279                    .into_builder()
2280                    .data_type(data_type.clone())
2281                    .child_data(vec![values.to_data()])
2282                    .build()?;
2283                Ok(make_array(data))
2284            };
2285
2286        let mut rebuilt = Vec::with_capacity(arrays.len());
2287        let mut pending_keys = Vec::with_capacity(arrays.len());
2288        let mut empty_data_type = None;
2289        for array in arrays {
2290            let Some(dictionary) = array.as_any_dictionary_opt() else {
2291                return Err(Error::invalid_input_source(
2292                    "Cannot mix dictionary and non-dictionary chunks".into(),
2293                ));
2294            };
2295            if dictionary.values().is_empty() {
2296                pending_keys.push(zeroed(dictionary.keys().data_type(), array.len())?);
2297                empty_data_type.get_or_insert_with(|| array.data_type().clone());
2298            } else if pending_keys.is_empty() {
2299                rebuilt.push(array);
2300            } else {
2301                pending_keys.push(make_array(dictionary.keys().to_data()));
2302                rebuilt.push(rebuild(
2303                    std::mem::take(&mut pending_keys),
2304                    array.data_type(),
2305                    dictionary.values().clone(),
2306                )?);
2307            }
2308        }
2309
2310        if pending_keys.is_empty() {
2311            return Ok(rebuilt);
2312        }
2313        if let Some(array) = rebuilt.pop() {
2314            let Some(dictionary) = array.as_any_dictionary_opt() else {
2315                return Err(Error::invalid_input_source(
2316                    "Cannot mix dictionary and non-dictionary chunks".into(),
2317                ));
2318            };
2319            let mut keys = Vec::with_capacity(pending_keys.len() + 1);
2320            keys.push(make_array(dictionary.keys().to_data()));
2321            keys.append(&mut pending_keys);
2322            rebuilt.push(rebuild(
2323                keys,
2324                array.data_type(),
2325                dictionary.values().clone(),
2326            )?);
2327        } else {
2328            let data_type = empty_data_type.ok_or_else(|| {
2329                Error::internal("Missing data type for an empty dictionary chunk")
2330            })?;
2331            let DataType::Dictionary(_, value_type) = &data_type else {
2332                return Err(Error::internal(format!(
2333                    "Expected dictionary data type, got {data_type}"
2334                )));
2335            };
2336            rebuilt.push(rebuild(pending_keys, &data_type, zeroed(value_type, 1)?)?);
2337        }
2338        Ok(rebuilt)
2339    }
2340
2341    // Creates encode tasks, consuming all buffered data
2342    fn do_flush(
2343        &mut self,
2344        arrays: Vec<ArrayRef>,
2345        repdefs: Vec<RepDefBuilder>,
2346        row_number: u64,
2347        num_rows: u64,
2348    ) -> Result<Vec<EncodeTask>> {
2349        let arrays = Self::rebuild_empty_dictionary_chunks(arrays)?;
2350        DataBlock::validate_arrays(&arrays, &self.field.name)?;
2351        let num_values = arrays.iter().map(|arr| arr.len() as u64).sum();
2352        let is_simple_validity = repdefs.iter().all(|rd| rd.is_simple_validity());
2353        let has_repdef_info = repdefs.iter().any(|rd| !rd.is_empty());
2354        let normalized = RepDefBuilder::normalize(repdefs);
2355        let plan_ctx = PrimitivePlanContext {
2356            field: &self.field,
2357            encoding_metadata: &self.encoding_metadata,
2358        };
2359        let mut pages = None;
2360        for page_encoding in self.page_encodings.iter() {
2361            if let Some(planned) = page_encoding.behavior.try_plan_pages(
2362                &plan_ctx,
2363                &arrays,
2364                &normalized,
2365                row_number,
2366                num_rows,
2367                num_values,
2368            )? {
2369                pages = Some(planned);
2370                break;
2371            }
2372        }
2373        let pages = pages.ok_or_else(|| {
2374            Error::invalid_input_source(
2375                format!(
2376                    "No primitive page planner supports field '{}'",
2377                    self.field.name
2378                )
2379                .into(),
2380            )
2381        })?;
2382
2383        let mut tasks = Vec::with_capacity(pages.len());
2384        let ctx = PrimitiveEncodeContext {
2385            column_idx: self.column_index,
2386            field: self.field.clone(),
2387            encoding_metadata: self.encoding_metadata.clone(),
2388            is_simple_validity,
2389            has_repdef_info,
2390        };
2391        for page in pages {
2392            let ctx = ctx.clone();
2393            let page_encodings = self.page_encodings.clone();
2394            let task =
2395                spawn_cpu(move || Self::encode_page(page_encodings.as_ref(), &ctx, page)).boxed();
2396            tasks.push(task);
2397        }
2398        Ok(tasks)
2399    }
2400
2401    fn extract_validity_buf(
2402        array: Arc<dyn Array>,
2403        repdef: &mut RepDefBuilder,
2404        keep_original_array: bool,
2405    ) -> Result<Arc<dyn Array>> {
2406        if let Some(validity) = array.nulls() {
2407            if keep_original_array {
2408                repdef.add_validity_bitmap(validity.clone());
2409            } else {
2410                repdef.add_validity_bitmap(deep_copy_nulls(Some(validity)).unwrap());
2411            }
2412            // Empty dictionaries have no valid key payload.  Keep the validity until rep/def is
2413            // grouped with the buffered page; `do_flush` then rebuilds the keys against either a
2414            // neighboring values array or an all-empty-page placeholder.
2415            if let Some(dictionary) = array.as_any_dictionary_opt()
2416                && dictionary.values().is_empty()
2417            {
2418                return Ok(array);
2419            }
2420            let data_no_nulls = array.to_data().into_builder().nulls(None).build()?;
2421            Ok(make_array(data_no_nulls))
2422        } else {
2423            repdef.add_no_null(array.len());
2424            Ok(array)
2425        }
2426    }
2427
2428    fn extract_validity(
2429        mut array: Arc<dyn Array>,
2430        repdef: &mut RepDefBuilder,
2431        keep_original_array: bool,
2432    ) -> Result<Arc<dyn Array>> {
2433        match array.data_type() {
2434            DataType::Null => {
2435                repdef.add_validity_bitmap(NullBuffer::new(BooleanBuffer::new_unset(array.len())));
2436                Ok(array)
2437            }
2438            DataType::Dictionary(_, _) => {
2439                array = dict::normalize_dict_nulls(array)?;
2440                array = dict::clear_out_of_range_null_keys(array)?;
2441                Self::extract_validity_buf(array, repdef, keep_original_array)
2442            }
2443            // Extract our validity buf but NOT any child validity bufs. (they will be encoded in
2444            // as part of the values).  Note: for FSL we do not use repdef.add_fsl because we do
2445            // NOT want to increase the repdef depth.
2446            //
2447            // This would be quite catasrophic for something like vector embeddings.  Imagine we
2448            // had thousands of vectors and some were null but no vector contained null items.  If
2449            // we treated the vectors (primitive FSL) like we treat structural FSL we would end up
2450            // with a rep/def value for every single item in the vector.
2451            _ => Self::extract_validity_buf(array, repdef, keep_original_array),
2452        }
2453    }
2454}
2455
2456impl PrimitivePageEncodingBehavior for RejectSparsePrimitiveEncoding {
2457    fn validate_field(&self, field: &Field, metadata: &HashMap<String, String>) -> Result<()> {
2458        if metadata
2459            .get(STRUCTURAL_ENCODING_META_KEY)
2460            .is_some_and(|requested| requested.eq_ignore_ascii_case(STRUCTURAL_ENCODING_SPARSE))
2461        {
2462            return Err(Error::invalid_input_source(
2463                format!(
2464                    "Field '{}' requests sparse structural encoding, which is not enabled by the selected file format",
2465                    field.name
2466                )
2467                .into(),
2468            ));
2469        }
2470        Ok(())
2471    }
2472}
2473
2474fn plan_dense_primitive_pages(
2475    arrays: &[ArrayRef],
2476    normalized: &NormalizedStructuralPlan,
2477    row_number: u64,
2478    num_rows: u64,
2479    num_values: u64,
2480) -> Result<Vec<PrimitivePageData>> {
2481    let (repdef, miniblock_repdef_budget) = normalized.serialize_with_miniblock_repdef_budget(
2482        miniblock::max_repdef_levels_per_chunk,
2483        num_rows,
2484        num_values,
2485    )?;
2486    PrimitiveStructuralEncoder::split_pages_for_miniblock_repdef_budget(
2487        arrays.to_vec(),
2488        repdef,
2489        miniblock_repdef_budget,
2490        row_number,
2491        num_rows,
2492    )
2493}
2494
2495impl PrimitivePageEncodingBehavior for DenseU16PrimitiveEncoding {
2496    fn try_plan_pages(
2497        &self,
2498        _ctx: &PrimitivePlanContext<'_>,
2499        arrays: &[ArrayRef],
2500        normalized: &NormalizedStructuralPlan,
2501        row_number: u64,
2502        num_rows: u64,
2503        num_values: u64,
2504    ) -> Result<Option<Vec<PrimitivePageData>>> {
2505        Ok(Some(plan_dense_primitive_pages(
2506            arrays, normalized, row_number, num_rows, num_values,
2507        )?))
2508    }
2509
2510    fn try_encode_page(
2511        &self,
2512        ctx: &PrimitiveEncodeContext,
2513        page: PrimitivePageData,
2514    ) -> Result<PrimitiveEncodeAttempt> {
2515        if !matches!(&page.structure, PrimitivePageStructure::Dense { .. }) {
2516            return Ok(PrimitiveEncodeAttempt::Unhandled(page));
2517        }
2518        Ok(PrimitiveEncodeAttempt::Encoded(
2519            PrimitiveStructuralEncoder::encode_dense_page(
2520                ctx.clone(),
2521                page,
2522                self.compression.clone(),
2523                MiniblockChunkSize::U16,
2524                ComplexNullEncoding::RawLevels,
2525                DictionaryEncodingPolicy::LegacyU16,
2526            )?,
2527        ))
2528    }
2529}
2530
2531impl PrimitivePageEncodingBehavior for DenseU32PrimitiveEncoding {
2532    fn try_plan_pages(
2533        &self,
2534        _ctx: &PrimitivePlanContext<'_>,
2535        arrays: &[ArrayRef],
2536        normalized: &NormalizedStructuralPlan,
2537        row_number: u64,
2538        num_rows: u64,
2539        num_values: u64,
2540    ) -> Result<Option<Vec<PrimitivePageData>>> {
2541        Ok(Some(plan_dense_primitive_pages(
2542            arrays, normalized, row_number, num_rows, num_values,
2543        )?))
2544    }
2545
2546    fn try_encode_page(
2547        &self,
2548        ctx: &PrimitiveEncodeContext,
2549        page: PrimitivePageData,
2550    ) -> Result<PrimitiveEncodeAttempt> {
2551        if !matches!(&page.structure, PrimitivePageStructure::Dense { .. }) {
2552            return Ok(PrimitiveEncodeAttempt::Unhandled(page));
2553        }
2554        Ok(PrimitiveEncodeAttempt::Encoded(
2555            PrimitiveStructuralEncoder::encode_dense_page(
2556                ctx.clone(),
2557                page,
2558                self.compression.clone(),
2559                MiniblockChunkSize::U32,
2560                ComplexNullEncoding::CompressedLevels,
2561                DictionaryEncodingPolicy::LegacyU32,
2562            )?,
2563        ))
2564    }
2565}
2566
2567impl PrimitivePageEncodingBehavior for ConstantPrimitiveEncoding {
2568    fn try_encode_page(
2569        &self,
2570        ctx: &PrimitiveEncodeContext,
2571        page: PrimitivePageData,
2572    ) -> Result<PrimitiveEncodeAttempt> {
2573        let PrimitivePageStructure::Dense { repdef, .. } = &page.structure else {
2574            return Ok(PrimitiveEncodeAttempt::Unhandled(page));
2575        };
2576        let num_values: u64 = page.arrays.iter().map(|array| array.len() as u64).sum();
2577        if num_values == 0 {
2578            return Ok(PrimitiveEncodeAttempt::Unhandled(page));
2579        }
2580        let leaf_validity = PrimitiveStructuralEncoder::leaf_validity(repdef, num_values as usize)?;
2581        if leaf_validity
2582            .as_ref()
2583            .is_some_and(|validity| validity.count_set_bits() == 0)
2584            || matches!(ctx.field.data_type(), DataType::Struct(fields) if fields.is_empty())
2585        {
2586            return Ok(PrimitiveEncodeAttempt::Unhandled(page));
2587        }
2588        let Some(scalar) =
2589            PrimitiveStructuralEncoder::find_constant_scalar(&page.arrays, leaf_validity.as_ref())?
2590        else {
2591            return Ok(PrimitiveEncodeAttempt::Unhandled(page));
2592        };
2593        let PrimitivePageData {
2594            structure: PrimitivePageStructure::Dense { repdef, .. },
2595            row_number,
2596            num_rows,
2597            ..
2598        } = page
2599        else {
2600            unreachable!()
2601        };
2602        log::debug!(
2603            "Encoding column {} with {} items ({} rows) using constant layout",
2604            ctx.column_idx,
2605            num_values,
2606            num_rows
2607        );
2608        Ok(PrimitiveEncodeAttempt::Encoded(
2609            constant::encode_constant_page(ctx.column_idx, scalar, repdef, row_number, num_rows)?,
2610        ))
2611    }
2612}
2613
2614impl FieldEncoder for PrimitiveStructuralEncoder {
2615    // Buffers data, if there is enough to write a page then we create an encode task
2616    fn maybe_encode(
2617        &mut self,
2618        array: ArrayRef,
2619        _external_buffers: &mut OutOfLineBuffers,
2620        mut repdef: RepDefBuilder,
2621        row_number: u64,
2622        num_rows: u64,
2623    ) -> Result<Vec<EncodeTask>> {
2624        let array = Self::extract_validity(array, &mut repdef, self.keep_original_array)?;
2625        self.accumulated_repdefs.push(repdef);
2626
2627        if let Some((arrays, row_number, num_rows)) =
2628            self.accumulation_queue.insert(array, row_number, num_rows)
2629        {
2630            let accumulated_repdefs = std::mem::take(&mut self.accumulated_repdefs);
2631            Ok(self.do_flush(arrays, accumulated_repdefs, row_number, num_rows)?)
2632        } else {
2633            Ok(vec![])
2634        }
2635    }
2636
2637    // If there is any data left in the buffer then create an encode task from it
2638    fn flush(&mut self, _external_buffers: &mut OutOfLineBuffers) -> Result<Vec<EncodeTask>> {
2639        if let Some((arrays, row_number, num_rows)) = self.accumulation_queue.flush() {
2640            let accumulated_repdefs = std::mem::take(&mut self.accumulated_repdefs);
2641            Ok(self.do_flush(arrays, accumulated_repdefs, row_number, num_rows)?)
2642        } else {
2643            Ok(vec![])
2644        }
2645    }
2646
2647    fn num_columns(&self) -> u32 {
2648        1
2649    }
2650
2651    fn finish(
2652        &mut self,
2653        _external_buffers: &mut OutOfLineBuffers,
2654    ) -> BoxFuture<'_, Result<Vec<crate::encoder::EncodedColumn>>> {
2655        std::future::ready(Ok(vec![EncodedColumn::default()])).boxed()
2656    }
2657}
2658
2659#[cfg(test)]
2660#[allow(clippy::single_range_in_vec_init)]
2661mod tests;