1use 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
60struct SerializedFullZip {
62 values: LanceBuffer,
64 repetition_index: Option<LanceBuffer>,
66}
67
68#[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#[derive(Debug, Clone)]
142pub struct PrimitivePageEncoding {
143 behavior: Arc<dyn PrimitivePageEncodingBehavior>,
144}
145
146impl PrimitivePageEncoding {
147 pub fn planar(compression: Arc<dyn CompressionStrategy>) -> Self {
157 Self {
158 behavior: Arc::new(planar::PlanarPrimitiveEncoding { compression }),
159 }
160 }
161
162 pub fn reject_sparse() -> Self {
164 Self {
165 behavior: Arc::new(RejectSparsePrimitiveEncoding),
166 }
167 }
168
169 pub fn constant() -> Self {
171 Self {
172 behavior: Arc::new(ConstantPrimitiveEncoding),
173 }
174 }
175
176 pub fn dense_u16(compression: Arc<dyn CompressionStrategy>) -> Self {
178 Self {
179 behavior: Arc::new(DenseU16PrimitiveEncoding { compression }),
180 }
181 }
182
183 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 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 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
255pub(super) struct PrimitivePageData {
257 pub(super) arrays: Vec<ArrayRef>,
259 pub(super) structure: PrimitivePageStructure,
261 pub(super) row_number: u64,
263 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#[derive(Clone)]
282pub(super) struct PrimitiveEncodeContext {
283 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 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 let Some(user_requested) = encoding_metadata.get(STRUCTURAL_ENCODING_META_KEY) {
367 return user_requested.to_lowercase() == STRUCTURAL_ENCODING_MINIBLOCK;
368 }
369 Self::is_narrow(data_block)
371 }
372
373 fn prefers_fullzip(encoding_metadata: &HashMap<String, String>) -> bool {
374 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 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 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 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 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 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 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 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 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 fn compress_levels(
595 mut levels: RepDefSlicer<'_>,
596 num_elements: u64,
597 compression_strategy: &dyn CompressionStrategy,
598 chunks: &[MiniBlockChunk],
599 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 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 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 let compressor =
624 compression_strategy.create_block_compressor(&levels_field, &levels_block)?;
625 let mut compressor_desc = None;
626 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 let rep_values = chunk_levels.borrow_to_typed_slice::<u16>();
650 let rep_values = rep_values.as_ref();
651
652 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 .map(|pos| pos + 1)
662 .unwrap_or(rep_values.len())
663 } else {
664 0
666 };
667
668 if chunk_idx != 0 && rep_values.first() == Some(&max_rep) {
669 let rep_len = rep_index.len();
673 if rep_index[rep_len - 1] != 0 {
674 rep_index[rep_len - 2] += 1;
676 rep_index[rep_len - 1] = 0;
677 }
678 }
679
680 if chunk_idx == chunks.len() - 1 {
681 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 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 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 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 0,
1135 )
1136 })
1137 .transpose()?;
1138
1139 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 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 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 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 while let Some(control) = repdef.append_next(&mut zipped_data) {
1255 if control.is_new_row {
1256 debug_assert!(offset <= len);
1258 rep_index_builder.append_trusted(offset as u64);
1259 }
1260 offset = zipped_data.len();
1261 }
1262 } else {
1263 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 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 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 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 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 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 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 debug_assert!(buf.len() <= len);
1383 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 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 let (num_items, num_visible_items) =
1448 if let Some(rep_levels) = repdef.repetition_levels.as_ref() {
1449 (rep_levels.len() as u64, data.num_values())
1452 } else {
1453 (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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 _ => 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 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 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;