1use std::{convert::TryInto, sync::Arc};
13
14use arrow_array::types::UInt64Type;
15
16use lance_core::{Error, Result, datatypes::Field};
17
18use crate::{
19 buffer::LanceBuffer,
20 compression::{
21 CompressionStrategy, FixedPerValueDecompressor, MiniBlockDecompressor,
22 VariablePerValueDecompressor,
23 },
24 data::{
25 BlockInfo, DataBlock, DataBlockBuilder, FixedWidthDataBlock, StructDataBlock,
26 VariableWidthBlock,
27 },
28 encodings::logical::primitive::{
29 fullzip::{PerValueCompressor, PerValueDataBlock},
30 miniblock::{MiniBlockCompressed, MiniBlockCompressionContext, MiniBlockCompressor},
31 },
32 format::{
33 ProtobufUtils21,
34 pb21::{CompressiveEncoding, PackedStruct, compressive_encoding::Compression},
35 },
36 statistics::{GetStat, Stat},
37};
38
39use super::value::{ValueDecompressor, ValueEncoder};
40
41fn struct_data_block_to_fixed_width_data_block(
45 struct_data_block: StructDataBlock,
46 bits_per_values: &[u64],
47) -> DataBlock {
48 let data_size = struct_data_block.expect_single_stat::<UInt64Type>(Stat::DataSize);
49 let mut output = Vec::with_capacity(data_size as usize);
50 let num_values = struct_data_block.children[0].num_values();
51
52 for i in 0..num_values as usize {
53 for (j, child) in struct_data_block.children.iter().enumerate() {
54 let bytes_per_value = (bits_per_values[j] / 8) as usize;
55 let this_data = child
56 .as_fixed_width_ref()
57 .unwrap()
58 .data
59 .slice_with_length(bytes_per_value * i, bytes_per_value);
60 output.extend_from_slice(&this_data);
61 }
62 }
63
64 DataBlock::FixedWidth(FixedWidthDataBlock {
65 bits_per_value: bits_per_values.iter().copied().sum(),
66 data: LanceBuffer::from(output),
67 num_values,
68 block_info: BlockInfo::default(),
69 })
70}
71
72#[derive(Debug, Default)]
73pub struct PackedStructFixedWidthMiniBlockEncoder {}
74
75impl MiniBlockCompressor for PackedStructFixedWidthMiniBlockEncoder {
76 fn compress(
77 &self,
78 context: MiniBlockCompressionContext,
79 data: DataBlock,
80 ) -> Result<(MiniBlockCompressed, CompressiveEncoding)> {
81 match data {
82 DataBlock::Struct(struct_data_block) => {
83 let bits_per_values = struct_data_block.children.iter().map(|data_block| data_block.as_fixed_width_ref().unwrap().bits_per_value).collect::<Vec<_>>();
84
85 let data_block = struct_data_block_to_fixed_width_data_block(struct_data_block, &bits_per_values);
87
88 let value_miniblock_compressor = Box::new(ValueEncoder::default()) as Box<dyn MiniBlockCompressor>;
90 let (value_miniblock_compressed, value_array_encoding) =
91 value_miniblock_compressor.compress(context, data_block)?;
92
93 Ok((
94 value_miniblock_compressed,
95 ProtobufUtils21::packed_struct(value_array_encoding, bits_per_values),
96 ))
97 }
98 _ => Err(Error::invalid_input_source(format!(
99 "Cannot compress a data block of type {} with PackedStructFixedWidthBlockEncoder",
100 data.name()
101 )
102 .into())),
103 }
104 }
105}
106
107#[derive(Debug)]
108pub struct PackedStructFixedWidthMiniBlockDecompressor {
109 bits_per_values: Vec<u64>,
110 array_encoding: Box<dyn MiniBlockDecompressor>,
111}
112
113impl PackedStructFixedWidthMiniBlockDecompressor {
114 pub fn new(description: &PackedStruct) -> Self {
115 let array_encoding: Box<dyn MiniBlockDecompressor> = match description
116 .values
117 .as_ref()
118 .unwrap()
119 .compression
120 .as_ref()
121 .unwrap()
122 {
123 Compression::Flat(flat) => Box::new(ValueDecompressor::from_flat(flat)),
124 _ => panic!(
125 "Currently only `ArrayEncoding::Flat` is supported in packed struct encoding in Lance 2.1."
126 ),
127 };
128 Self {
129 bits_per_values: description.bits_per_value.clone(),
130 array_encoding,
131 }
132 }
133}
134
135impl MiniBlockDecompressor for PackedStructFixedWidthMiniBlockDecompressor {
136 fn decompress(&self, data: Vec<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
137 assert_eq!(data.len(), 1);
138 let encoded_data_block = self.array_encoding.decompress(data, num_values)?;
139 let DataBlock::FixedWidth(encoded_data_block) = encoded_data_block else {
140 panic!("ValueDecompressor should output FixedWidth DataBlock")
141 };
142
143 let bytes_per_values = self
144 .bits_per_values
145 .iter()
146 .map(|bits_per_value| *bits_per_value as usize / 8)
147 .collect::<Vec<_>>();
148
149 assert!(encoded_data_block.bits_per_value % 8 == 0);
150 let encoded_bytes_per_row = (encoded_data_block.bits_per_value / 8) as usize;
151
152 let mut prefix_sum = vec![0; self.bits_per_values.len()];
154 for i in 0..(self.bits_per_values.len() - 1) {
155 prefix_sum[i + 1] = prefix_sum[i] + bytes_per_values[i];
156 }
157
158 let mut children_data_block = vec![];
159 for i in 0..self.bits_per_values.len() {
160 let child_buf_size = bytes_per_values[i] * num_values as usize;
161 let mut child_buf: Vec<u8> = Vec::with_capacity(child_buf_size);
162
163 for j in 0..num_values as usize {
164 let this_value = encoded_data_block.data.slice_with_length(
166 prefix_sum[i] + (j * encoded_bytes_per_row),
167 bytes_per_values[i],
168 );
169
170 child_buf.extend_from_slice(&this_value);
171 }
172
173 let child = DataBlock::FixedWidth(FixedWidthDataBlock {
174 data: LanceBuffer::from(child_buf),
175 bits_per_value: self.bits_per_values[i],
176 num_values,
177 block_info: BlockInfo::default(),
178 });
179 children_data_block.push(child);
180 }
181 Ok(DataBlock::Struct(StructDataBlock {
182 children: children_data_block,
183 block_info: BlockInfo::default(),
184 validity: None,
185 }))
186 }
187}
188
189#[derive(Debug)]
190enum VariablePackedFieldData {
191 Fixed {
192 block: FixedWidthDataBlock,
193 },
194 Variable {
195 block: VariableWidthBlock,
196 bits_per_length: u64,
197 },
198}
199
200impl VariablePackedFieldData {
201 fn append_row_bytes(&self, row_idx: usize, output: &mut Vec<u8>) -> Result<()> {
202 match self {
203 Self::Fixed { block } => {
204 let bits_per_value = block.bits_per_value;
205 if bits_per_value % 8 != 0 {
206 return Err(Error::invalid_input(
207 "Packed struct variable encoding requires byte-aligned fixed-width children",
208 ));
209 }
210 let bytes_per_value = (bits_per_value / 8) as usize;
211 let start = row_idx
212 .checked_mul(bytes_per_value)
213 .ok_or_else(|| Error::invalid_input("Packed struct row size overflow"))?;
214 let end = start + bytes_per_value;
215 let data = block.data.as_ref();
216 if end > data.len() {
217 return Err(Error::invalid_input(
218 "Packed struct fixed child out of bounds",
219 ));
220 }
221 output.extend_from_slice(&data[start..end]);
222 Ok(())
223 }
224 Self::Variable {
225 block,
226 bits_per_length,
227 } => {
228 if bits_per_length % 8 != 0 {
229 return Err(Error::invalid_input(
230 "Packed struct variable children must have byte-aligned length prefixes",
231 ));
232 }
233 let prefix_bytes = (*bits_per_length / 8) as usize;
234 if !(prefix_bytes == 4 || prefix_bytes == 8) {
235 return Err(Error::invalid_input(
236 "Packed struct variable children must use 32 or 64-bit length prefixes",
237 ));
238 }
239 match block.bits_per_offset {
240 32 => {
241 let offsets = block.offsets.borrow_to_typed_slice::<u32>();
242 let start = offsets[row_idx] as usize;
243 let end = offsets[row_idx + 1] as usize;
244 if end > block.data.len() {
245 return Err(Error::invalid_input(
246 "Packed struct variable child offsets out of bounds",
247 ));
248 }
249 let len = (end - start) as u32;
250 if prefix_bytes != std::mem::size_of::<u32>() {
251 return Err(Error::invalid_input(
252 "Packed struct variable child length prefix mismatch",
253 ));
254 }
255 output.extend_from_slice(&len.to_le_bytes());
256 output.extend_from_slice(&block.data[start..end]);
257 Ok(())
258 }
259 64 => {
260 let offsets = block.offsets.borrow_to_typed_slice::<u64>();
261 let start = offsets[row_idx] as usize;
262 let end = offsets[row_idx + 1] as usize;
263 if end > block.data.len() {
264 return Err(Error::invalid_input(
265 "Packed struct variable child offsets out of bounds",
266 ));
267 }
268 let len = (end - start) as u64;
269 if prefix_bytes != std::mem::size_of::<u64>() {
270 return Err(Error::invalid_input(
271 "Packed struct variable child length prefix mismatch",
272 ));
273 }
274 output.extend_from_slice(&len.to_le_bytes());
275 output.extend_from_slice(&block.data[start..end]);
276 Ok(())
277 }
278 _ => Err(Error::invalid_input(
279 "Packed struct variable child must use 32 or 64-bit offsets",
280 )),
281 }
282 }
283 }
284 }
285}
286
287#[derive(Debug)]
288pub struct PackedStructVariablePerValueEncoder {
289 strategy: Arc<dyn CompressionStrategy>,
290 fields: Vec<Field>,
291}
292
293impl PackedStructVariablePerValueEncoder {
294 pub fn new(strategy: Arc<dyn CompressionStrategy>, fields: Vec<Field>) -> Self {
295 Self { strategy, fields }
296 }
297}
298
299impl PerValueCompressor for PackedStructVariablePerValueEncoder {
300 fn compress(&self, data: DataBlock) -> Result<(PerValueDataBlock, CompressiveEncoding)> {
301 let DataBlock::Struct(struct_block) = data else {
302 return Err(Error::invalid_input(
303 "Packed struct encoder requires Struct data block",
304 ));
305 };
306
307 if struct_block.children.is_empty() {
308 return Err(Error::invalid_input(
309 "Packed struct encoder requires at least one child field",
310 ));
311 }
312 if struct_block.children.len() != self.fields.len() {
313 return Err(Error::invalid_input(
314 "Struct field metadata does not match number of children",
315 ));
316 }
317
318 let num_values = struct_block.children[0].num_values();
319 for child in struct_block.children.iter() {
320 if child.num_values() != num_values {
321 return Err(Error::invalid_input(
322 "Packed struct children must have matching value counts",
323 ));
324 }
325 }
326
327 let mut field_data = Vec::with_capacity(self.fields.len());
328 let mut field_metadata = Vec::with_capacity(self.fields.len());
329
330 for (field, child_block) in self.fields.iter().zip(struct_block.children) {
331 let compressor = self.strategy.create_per_value(field, &child_block)?;
332 let (compressed, encoding) = compressor.compress(child_block)?;
333 match compressed {
334 PerValueDataBlock::Fixed(block) => {
335 field_metadata.push(ProtobufUtils21::packed_struct_field_fixed(
336 encoding,
337 block.bits_per_value,
338 ));
339 field_data.push(VariablePackedFieldData::Fixed { block });
340 }
341 PerValueDataBlock::Variable(block) => {
342 let bits_per_length = block.bits_per_offset as u64;
343 field_metadata.push(ProtobufUtils21::packed_struct_field_variable(
344 encoding,
345 bits_per_length,
346 ));
347 field_data.push(VariablePackedFieldData::Variable {
348 block,
349 bits_per_length,
350 });
351 }
352 }
353 }
354
355 let mut row_data: Vec<u8> = Vec::new();
356 let mut row_offsets: Vec<u64> = Vec::with_capacity(num_values as usize + 1);
357 row_offsets.push(0);
358 let mut total_bytes: usize = 0;
359 let mut max_row_len: usize = 0;
360 for row in 0..num_values as usize {
361 let start = row_data.len();
362 for field in &field_data {
363 field.append_row_bytes(row, &mut row_data)?;
364 }
365 let end = row_data.len();
366 let row_len = end - start;
367 max_row_len = max_row_len.max(row_len);
368 total_bytes = total_bytes
369 .checked_add(row_len)
370 .ok_or_else(|| Error::invalid_input("Packed struct row data size overflow"))?;
371 row_offsets.push(end as u64);
372 }
373 debug_assert_eq!(total_bytes, row_data.len());
374
375 let use_u32_offsets = total_bytes <= u32::MAX as usize && max_row_len <= u32::MAX as usize;
376 let bits_per_offset = if use_u32_offsets { 32 } else { 64 };
377 let offsets_buffer = if use_u32_offsets {
378 let offsets_u32 = row_offsets
379 .iter()
380 .map(|&offset| offset as u32)
381 .collect::<Vec<_>>();
382 LanceBuffer::reinterpret_vec(offsets_u32)
383 } else {
384 LanceBuffer::reinterpret_vec(row_offsets)
385 };
386
387 let data_block = VariableWidthBlock {
388 data: LanceBuffer::from(row_data),
389 bits_per_offset,
390 offsets: offsets_buffer,
391 num_values,
392 block_info: BlockInfo::new(),
393 };
394
395 Ok((
396 PerValueDataBlock::Variable(data_block),
397 ProtobufUtils21::packed_struct_variable(field_metadata),
398 ))
399 }
400}
401
402#[derive(Debug)]
403pub(crate) enum VariablePackedStructFieldKind {
404 Fixed {
405 bits_per_value: u64,
406 decompressor: Arc<dyn FixedPerValueDecompressor>,
407 },
408 Variable {
409 bits_per_length: u64,
410 decompressor: Arc<dyn VariablePerValueDecompressor>,
411 },
412}
413
414#[derive(Debug)]
415pub(crate) struct VariablePackedStructFieldDecoder {
416 pub(crate) kind: VariablePackedStructFieldKind,
417}
418
419#[derive(Debug)]
420pub struct PackedStructVariablePerValueDecompressor {
421 fields: Vec<VariablePackedStructFieldDecoder>,
422}
423
424impl PackedStructVariablePerValueDecompressor {
425 pub(crate) fn new(fields: Vec<VariablePackedStructFieldDecoder>) -> Self {
426 Self { fields }
427 }
428}
429
430enum FieldAccumulator {
431 Fixed {
432 builder: DataBlockBuilder,
433 bits_per_value: u64,
434 empty_value: DataBlock,
435 },
436 Variable32 {
437 builder: DataBlockBuilder,
438 empty_value: DataBlock,
439 },
440 Variable64 {
441 builder: DataBlockBuilder,
442 empty_value: DataBlock,
443 },
444}
445
446impl FieldAccumulator {
447 fn append_empty(&mut self) {
451 match self {
452 Self::Fixed {
453 builder,
454 empty_value,
455 ..
456 } => builder.append(empty_value, 0..1),
457 Self::Variable32 {
458 builder,
459 empty_value,
460 } => builder.append(empty_value, 0..1),
461 Self::Variable64 {
462 builder,
463 empty_value,
464 } => builder.append(empty_value, 0..1),
465 }
466 }
467}
468
469impl VariablePerValueDecompressor for PackedStructVariablePerValueDecompressor {
470 fn decompress(&self, data: VariableWidthBlock) -> Result<DataBlock> {
471 let num_values = data.num_values;
472 let offsets_u64 = match data.bits_per_offset {
473 32 => data
474 .offsets
475 .borrow_to_typed_slice::<u32>()
476 .iter()
477 .map(|v| *v as u64)
478 .collect::<Vec<_>>(),
479 64 => data
480 .offsets
481 .borrow_to_typed_slice::<u64>()
482 .as_ref()
483 .to_vec(),
484 _ => {
485 return Err(Error::invalid_input(
486 "Packed struct row offsets must be 32 or 64 bits",
487 ));
488 }
489 };
490
491 if offsets_u64.len() != num_values as usize + 1 {
492 return Err(Error::invalid_input(
493 "Packed struct row offsets length mismatch",
494 ));
495 }
496
497 let mut accumulators = Vec::with_capacity(self.fields.len());
498 for field in &self.fields {
499 match &field.kind {
500 VariablePackedStructFieldKind::Fixed { bits_per_value, .. } => {
501 if bits_per_value % 8 != 0 {
502 return Err(Error::invalid_input(
503 "Packed struct fixed child must be byte-aligned",
504 ));
505 }
506 let bytes_per_value = bits_per_value.checked_div(8).ok_or_else(|| {
507 Error::invalid_input("Invalid bits per value for packed struct field")
508 })?;
509 let estimate = bytes_per_value.checked_mul(num_values).ok_or_else(|| {
510 Error::invalid_input("Packed struct fixed child allocation overflow")
511 })?;
512 let empty_value = DataBlock::FixedWidth(FixedWidthDataBlock {
513 data: LanceBuffer::from(vec![0_u8; bytes_per_value as usize]),
514 bits_per_value: *bits_per_value,
515 num_values: 1,
516 block_info: BlockInfo::new(),
517 });
518 accumulators.push(FieldAccumulator::Fixed {
519 builder: DataBlockBuilder::with_capacity_estimate(estimate),
520 bits_per_value: *bits_per_value,
521 empty_value,
522 });
523 }
524 VariablePackedStructFieldKind::Variable {
525 bits_per_length, ..
526 } => match bits_per_length {
527 32 => accumulators.push(FieldAccumulator::Variable32 {
528 builder: DataBlockBuilder::with_capacity_estimate(data.data.len() as u64),
529 empty_value: DataBlock::VariableWidth(VariableWidthBlock {
530 data: LanceBuffer::empty(),
531 bits_per_offset: 32,
532 offsets: LanceBuffer::reinterpret_vec(vec![0_u32, 0_u32]),
533 num_values: 1,
534 block_info: BlockInfo::new(),
535 }),
536 }),
537 64 => accumulators.push(FieldAccumulator::Variable64 {
538 builder: DataBlockBuilder::with_capacity_estimate(data.data.len() as u64),
539 empty_value: DataBlock::VariableWidth(VariableWidthBlock {
540 data: LanceBuffer::empty(),
541 bits_per_offset: 64,
542 offsets: LanceBuffer::reinterpret_vec(vec![0_u64, 0_u64]),
543 num_values: 1,
544 block_info: BlockInfo::new(),
545 }),
546 }),
547 _ => {
548 return Err(Error::invalid_input(
549 "Packed struct variable child must use 32 or 64-bit length prefixes",
550 ));
551 }
552 },
553 }
554 }
555
556 for row_idx in 0..num_values as usize {
557 let row_start = offsets_u64[row_idx] as usize;
558 let row_end = offsets_u64[row_idx + 1] as usize;
559 if row_end > data.data.len() || row_start > row_end {
560 return Err(Error::invalid_input(
561 "Packed struct row bounds exceed buffer",
562 ));
563 }
564 if row_start == row_end {
565 for accumulator in accumulators.iter_mut() {
566 accumulator.append_empty();
567 }
568 continue;
569 }
570 let mut cursor = row_start;
571 for (field, accumulator) in self.fields.iter().zip(accumulators.iter_mut()) {
572 match (&field.kind, accumulator) {
573 (
574 VariablePackedStructFieldKind::Fixed { bits_per_value, .. },
575 FieldAccumulator::Fixed {
576 builder,
577 bits_per_value: acc_bits,
578 ..
579 },
580 ) => {
581 debug_assert_eq!(bits_per_value, acc_bits);
582 let bytes_per_value = (bits_per_value / 8) as usize;
583 let end = cursor + bytes_per_value;
584 if end > row_end {
585 return Err(Error::invalid_input(
586 "Packed struct fixed child exceeds row bounds",
587 ));
588 }
589 let value_block = DataBlock::FixedWidth(FixedWidthDataBlock {
590 data: LanceBuffer::from(data.data[cursor..end].to_vec()),
591 bits_per_value: *bits_per_value,
592 num_values: 1,
593 block_info: BlockInfo::new(),
594 });
595 builder.append(&value_block, 0..1);
596 cursor = end;
597 }
598 (
599 VariablePackedStructFieldKind::Variable {
600 bits_per_length, ..
601 },
602 FieldAccumulator::Variable32 { builder, .. },
603 ) => {
604 if *bits_per_length != 32 {
605 return Err(Error::invalid_input(
606 "Packed struct length prefix size mismatch",
607 ));
608 }
609 let end = cursor + std::mem::size_of::<u32>();
610 if end > row_end {
611 return Err(Error::invalid_input(
612 "Packed struct variable child length prefix out of bounds",
613 ));
614 }
615 let len = u32::from_le_bytes(
616 data.data[cursor..end]
617 .try_into()
618 .expect("slice has exact length"),
619 ) as usize;
620 cursor = end;
621 let value_end = cursor + len;
622 if value_end > row_end {
623 return Err(Error::invalid_input(
624 "Packed struct variable child exceeds row bounds",
625 ));
626 }
627 let value_block = DataBlock::VariableWidth(VariableWidthBlock {
628 data: LanceBuffer::from(data.data[cursor..value_end].to_vec()),
629 bits_per_offset: 32,
630 offsets: LanceBuffer::reinterpret_vec(vec![0_u32, len as u32]),
631 num_values: 1,
632 block_info: BlockInfo::new(),
633 });
634 builder.append(&value_block, 0..1);
635 cursor = value_end;
636 }
637 (
638 VariablePackedStructFieldKind::Variable {
639 bits_per_length, ..
640 },
641 FieldAccumulator::Variable64 { builder, .. },
642 ) => {
643 if *bits_per_length != 64 {
644 return Err(Error::invalid_input(
645 "Packed struct length prefix size mismatch",
646 ));
647 }
648 let end = cursor + std::mem::size_of::<u64>();
649 if end > row_end {
650 return Err(Error::invalid_input(
651 "Packed struct variable child length prefix out of bounds",
652 ));
653 }
654 let len = u64::from_le_bytes(
655 data.data[cursor..end]
656 .try_into()
657 .expect("slice has exact length"),
658 ) as usize;
659 cursor = end;
660 let value_end = cursor + len;
661 if value_end > row_end {
662 return Err(Error::invalid_input(
663 "Packed struct variable child exceeds row bounds",
664 ));
665 }
666 let value_block = DataBlock::VariableWidth(VariableWidthBlock {
667 data: LanceBuffer::from(data.data[cursor..value_end].to_vec()),
668 bits_per_offset: 64,
669 offsets: LanceBuffer::reinterpret_vec(vec![0_u64, len as u64]),
670 num_values: 1,
671 block_info: BlockInfo::new(),
672 });
673 builder.append(&value_block, 0..1);
674 cursor = value_end;
675 }
676 _ => {
677 return Err(Error::invalid_input(
678 "Packed struct accumulator kind mismatch",
679 ));
680 }
681 }
682 }
683 if cursor != row_end {
684 return Err(Error::invalid_input(
685 "Packed struct row parsing did not consume full row",
686 ));
687 }
688 }
689
690 let mut children = Vec::with_capacity(self.fields.len());
691 for (field, accumulator) in self.fields.iter().zip(accumulators) {
692 match (field, accumulator) {
693 (
694 VariablePackedStructFieldDecoder {
695 kind: VariablePackedStructFieldKind::Fixed { decompressor, .. },
696 },
697 FieldAccumulator::Fixed { builder, .. },
698 ) => {
699 let DataBlock::FixedWidth(block) = builder.finish() else {
700 panic!("Expected fixed-width datablock from builder");
701 };
702 let decoded = decompressor.decompress(block, num_values)?;
703 children.push(decoded);
704 }
705 (
706 VariablePackedStructFieldDecoder {
707 kind:
708 VariablePackedStructFieldKind::Variable {
709 bits_per_length,
710 decompressor,
711 },
712 },
713 FieldAccumulator::Variable32 { builder, .. },
714 ) => {
715 let DataBlock::VariableWidth(mut block) = builder.finish() else {
716 panic!("Expected variable-width datablock from builder");
717 };
718 debug_assert_eq!(block.bits_per_offset, 32);
719 block.bits_per_offset = (*bits_per_length) as u8;
720 let decoded = decompressor.decompress(block)?;
721 children.push(decoded);
722 }
723 (
724 VariablePackedStructFieldDecoder {
725 kind:
726 VariablePackedStructFieldKind::Variable {
727 bits_per_length,
728 decompressor,
729 },
730 },
731 FieldAccumulator::Variable64 { builder, .. },
732 ) => {
733 let DataBlock::VariableWidth(mut block) = builder.finish() else {
734 panic!("Expected variable-width datablock from builder");
735 };
736 debug_assert_eq!(block.bits_per_offset, 64);
737 block.bits_per_offset = (*bits_per_length) as u8;
738 let decoded = decompressor.decompress(block)?;
739 children.push(decoded);
740 }
741 _ => {
742 return Err(Error::invalid_input(
743 "Packed struct accumulator mismatch during finalize",
744 ));
745 }
746 }
747 }
748
749 Ok(DataBlock::Struct(StructDataBlock {
750 children,
751 block_info: BlockInfo::new(),
752 validity: None,
753 }))
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760 use crate::{
761 compression::DefaultDecompressionStrategy,
762 compression_config::CompressionParams,
763 constants::PACKED_STRUCT_META_KEY,
764 statistics::ComputeStat,
765 testing::{
766 TestCases, TestEncoding, check_round_trip_encoding_of_data, test_compression_strategy,
767 },
768 };
769 use arrow_array::{
770 Array, ArrayRef, BinaryArray, Int32Array, Int64Array, LargeStringArray, StringArray,
771 StructArray, UInt32Array,
772 };
773 use arrow_schema::{DataType, Field as ArrowField, Fields};
774 use std::collections::HashMap;
775 use std::sync::Arc;
776
777 fn fixed_block_from_array(array: Int64Array) -> FixedWidthDataBlock {
778 let num_values = array.len() as u64;
779 let block = DataBlock::from_arrays(&[Arc::new(array) as ArrayRef], num_values);
780 match block {
781 DataBlock::FixedWidth(block) => block,
782 _ => panic!("Expected fixed-width data block"),
783 }
784 }
785
786 fn fixed_i32_block_from_array(array: Int32Array) -> FixedWidthDataBlock {
787 let num_values = array.len() as u64;
788 let block = DataBlock::from_arrays(&[Arc::new(array) as ArrayRef], num_values);
789 match block {
790 DataBlock::FixedWidth(block) => block,
791 _ => panic!("Expected fixed-width data block"),
792 }
793 }
794
795 fn variable_block_from_string_array(array: StringArray) -> VariableWidthBlock {
796 let num_values = array.len() as u64;
797 let block = DataBlock::from_arrays(&[Arc::new(array) as ArrayRef], num_values);
798 match block {
799 DataBlock::VariableWidth(block) => block,
800 _ => panic!("Expected variable-width block"),
801 }
802 }
803
804 fn variable_block_from_large_string_array(array: LargeStringArray) -> VariableWidthBlock {
805 let num_values = array.len() as u64;
806 let block = DataBlock::from_arrays(&[Arc::new(array) as ArrayRef], num_values);
807 match block {
808 DataBlock::VariableWidth(block) => block,
809 _ => panic!("Expected variable-width block"),
810 }
811 }
812
813 fn variable_block_from_binary_array(array: BinaryArray) -> VariableWidthBlock {
814 let num_values = array.len() as u64;
815 let block = DataBlock::from_arrays(&[Arc::new(array) as ArrayRef], num_values);
816 match block {
817 DataBlock::VariableWidth(block) => block,
818 _ => panic!("Expected variable-width block"),
819 }
820 }
821
822 #[test]
823 fn variable_packed_struct_round_trip() -> Result<()> {
824 let arrow_fields: Fields = vec![
825 ArrowField::new("id", DataType::UInt32, false),
826 ArrowField::new("name", DataType::Utf8, true),
827 ]
828 .into();
829 let arrow_struct = ArrowField::new("item", DataType::Struct(arrow_fields), false);
830 let struct_field = Field::try_from(&arrow_struct)?;
831
832 let ids = vec![1_u32, 2, 42];
833 let id_bytes = ids
834 .iter()
835 .flat_map(|value| value.to_le_bytes())
836 .collect::<Vec<_>>();
837 let mut id_block = FixedWidthDataBlock {
838 data: LanceBuffer::reinterpret_vec(ids),
839 bits_per_value: 32,
840 num_values: 3,
841 block_info: BlockInfo::new(),
842 };
843 id_block.compute_stat();
844 let id_block = DataBlock::FixedWidth(id_block);
845
846 let name_offsets = vec![0_i32, 1, 4, 4];
847 let name_bytes = b"abcz".to_vec();
848 let mut name_block = VariableWidthBlock {
849 data: LanceBuffer::from(name_bytes.clone()),
850 bits_per_offset: 32,
851 offsets: LanceBuffer::reinterpret_vec(name_offsets.clone()),
852 num_values: 3,
853 block_info: BlockInfo::new(),
854 };
855 name_block.compute_stat();
856 let name_block = DataBlock::VariableWidth(name_block);
857
858 let struct_block = StructDataBlock {
859 children: vec![id_block, name_block],
860 block_info: BlockInfo::new(),
861 validity: None,
862 };
863
864 let data_block = DataBlock::Struct(struct_block);
865
866 let compression_strategy =
867 test_compression_strategy(TestEncoding::StructuralU32, CompressionParams::default());
868 let compressor = crate::compression::CompressionStrategy::create_per_value(
869 compression_strategy.as_ref(),
870 &struct_field,
871 &data_block,
872 )?;
873 let (compressed, encoding) = compressor.compress(data_block)?;
874
875 let PerValueDataBlock::Variable(zipped) = compressed else {
876 panic!("expected variable-width packed struct output");
877 };
878
879 let decompression_strategy = DefaultDecompressionStrategy::default();
880 let decompressor =
881 crate::compression::DecompressionStrategy::create_variable_per_value_decompressor(
882 &decompression_strategy,
883 &encoding,
884 )?;
885 let decoded = decompressor.decompress(zipped)?;
886
887 let DataBlock::Struct(decoded_struct) = decoded else {
888 panic!("expected struct datablock after decode");
889 };
890
891 let decoded_id = decoded_struct.children[0].as_fixed_width_ref().unwrap();
892 assert_eq!(decoded_id.bits_per_value, 32);
893 assert_eq!(decoded_id.data.as_ref(), id_bytes.as_slice());
894
895 let decoded_name = decoded_struct.children[1].as_variable_width_ref().unwrap();
896 assert_eq!(decoded_name.bits_per_offset, 32);
897 let decoded_offsets = decoded_name.offsets.borrow_to_typed_slice::<i32>();
898 assert_eq!(decoded_offsets.as_ref(), name_offsets.as_slice());
899 assert_eq!(decoded_name.data.as_ref(), name_bytes.as_slice());
900
901 Ok(())
902 }
903
904 #[test]
905 fn variable_packed_struct_large_utf8_round_trip() -> Result<()> {
906 let arrow_fields: Fields = vec![
907 ArrowField::new("value", DataType::Int64, false),
908 ArrowField::new("text", DataType::LargeUtf8, false),
909 ]
910 .into();
911 let arrow_struct = ArrowField::new("item", DataType::Struct(arrow_fields), false);
912 let struct_field = Field::try_from(&arrow_struct)?;
913
914 let id_block = fixed_block_from_array(Int64Array::from(vec![10, 20, 30, 40]));
915 let payload_array = LargeStringArray::from(vec![
916 "alpha",
917 "a considerably longer payload for testing",
918 "mid",
919 "z",
920 ]);
921 let payload_block = variable_block_from_large_string_array(payload_array);
922
923 let struct_block = StructDataBlock {
924 children: vec![
925 DataBlock::FixedWidth(id_block.clone()),
926 DataBlock::VariableWidth(payload_block.clone()),
927 ],
928 block_info: BlockInfo::new(),
929 validity: None,
930 };
931
932 let data_block = DataBlock::Struct(struct_block);
933
934 let compression_strategy =
935 test_compression_strategy(TestEncoding::StructuralU32, CompressionParams::default());
936 let compressor = crate::compression::CompressionStrategy::create_per_value(
937 compression_strategy.as_ref(),
938 &struct_field,
939 &data_block,
940 )?;
941 let (compressed, encoding) = compressor.compress(data_block)?;
942
943 let PerValueDataBlock::Variable(zipped) = compressed else {
944 panic!("expected variable-width packed struct output");
945 };
946
947 let decompression_strategy = DefaultDecompressionStrategy::default();
948 let decompressor =
949 crate::compression::DecompressionStrategy::create_variable_per_value_decompressor(
950 &decompression_strategy,
951 &encoding,
952 )?;
953 let decoded = decompressor.decompress(zipped)?;
954
955 let DataBlock::Struct(decoded_struct) = decoded else {
956 panic!("expected struct datablock after decode");
957 };
958
959 let decoded_id = decoded_struct.children[0].as_fixed_width_ref().unwrap();
960 assert_eq!(decoded_id.bits_per_value, 64);
961 assert_eq!(decoded_id.data.as_ref(), id_block.data.as_ref());
962
963 let decoded_payload = decoded_struct.children[1].as_variable_width_ref().unwrap();
964 assert_eq!(decoded_payload.bits_per_offset, 64);
965 assert_eq!(
966 decoded_payload
967 .offsets
968 .borrow_to_typed_slice::<i64>()
969 .as_ref(),
970 payload_block
971 .offsets
972 .borrow_to_typed_slice::<i64>()
973 .as_ref()
974 );
975 assert_eq!(decoded_payload.data.as_ref(), payload_block.data.as_ref());
976
977 Ok(())
978 }
979
980 #[tokio::test]
981 async fn variable_packed_struct_utf8_round_trip() {
982 let fields = Fields::from(vec![
984 Arc::new(ArrowField::new("id", DataType::UInt32, false)),
985 Arc::new(ArrowField::new("uri", DataType::Utf8, false)),
986 Arc::new(ArrowField::new("long_text", DataType::LargeUtf8, false)),
987 ]);
988
989 let mut meta = HashMap::new();
991 meta.insert(PACKED_STRUCT_META_KEY.to_string(), "true".to_string());
992
993 let array = Arc::new(StructArray::from(vec![
994 (
995 fields[0].clone(),
996 Arc::new(UInt32Array::from(vec![1, 2, 3])) as ArrayRef,
997 ),
998 (
999 fields[1].clone(),
1000 Arc::new(StringArray::from(vec![
1001 Some("a"),
1002 Some("b"),
1003 Some("/tmp/x"),
1004 ])) as ArrayRef,
1005 ),
1006 (
1007 fields[2].clone(),
1008 Arc::new(LargeStringArray::from(vec![
1009 Some("alpha"),
1010 Some("a considerably longer payload for testing"),
1011 Some("mid"),
1012 ])) as ArrayRef,
1013 ),
1014 ]));
1015
1016 let test_cases = TestCases::default()
1017 .with_u32_structural_encodings()
1018 .with_expected_encoding("variable_packed_struct");
1019
1020 check_round_trip_encoding_of_data(vec![array], &test_cases, meta).await;
1021 }
1022
1023 #[test]
1024 fn variable_packed_struct_multi_variable_round_trip() -> Result<()> {
1025 let arrow_fields: Fields = vec![
1026 ArrowField::new("category", DataType::Utf8, false),
1027 ArrowField::new("payload", DataType::Binary, false),
1028 ArrowField::new("count", DataType::Int32, false),
1029 ]
1030 .into();
1031 let arrow_struct = ArrowField::new("item", DataType::Struct(arrow_fields), false);
1032 let struct_field = Field::try_from(&arrow_struct)?;
1033
1034 let category_array = StringArray::from(vec!["red", "blue", "green", "red"]);
1035 let category_block = variable_block_from_string_array(category_array);
1036 let payload_values: Vec<Vec<u8>> =
1037 vec![vec![0x01, 0x02], vec![], vec![0x05, 0x06, 0x07], vec![0xff]];
1038 let payload_array =
1039 BinaryArray::from_iter_values(payload_values.iter().map(|v| v.as_slice()));
1040 let payload_block = variable_block_from_binary_array(payload_array);
1041 let count_block = fixed_i32_block_from_array(Int32Array::from(vec![1, 2, 3, 4]));
1042
1043 let struct_block = StructDataBlock {
1044 children: vec![
1045 DataBlock::VariableWidth(category_block.clone()),
1046 DataBlock::VariableWidth(payload_block.clone()),
1047 DataBlock::FixedWidth(count_block.clone()),
1048 ],
1049 block_info: BlockInfo::new(),
1050 validity: None,
1051 };
1052
1053 let data_block = DataBlock::Struct(struct_block);
1054
1055 let compression_strategy =
1056 test_compression_strategy(TestEncoding::StructuralU32, CompressionParams::default());
1057 let compressor = crate::compression::CompressionStrategy::create_per_value(
1058 compression_strategy.as_ref(),
1059 &struct_field,
1060 &data_block,
1061 )?;
1062 let (compressed, encoding) = compressor.compress(data_block)?;
1063
1064 let PerValueDataBlock::Variable(zipped) = compressed else {
1065 panic!("expected variable-width packed struct output");
1066 };
1067
1068 let decompression_strategy = DefaultDecompressionStrategy::default();
1069 let decompressor =
1070 crate::compression::DecompressionStrategy::create_variable_per_value_decompressor(
1071 &decompression_strategy,
1072 &encoding,
1073 )?;
1074 let decoded = decompressor.decompress(zipped)?;
1075
1076 let DataBlock::Struct(decoded_struct) = decoded else {
1077 panic!("expected struct datablock after decode");
1078 };
1079
1080 let decoded_category = decoded_struct.children[0].as_variable_width_ref().unwrap();
1081 assert_eq!(decoded_category.bits_per_offset, 32);
1082 assert_eq!(
1083 decoded_category
1084 .offsets
1085 .borrow_to_typed_slice::<i32>()
1086 .as_ref(),
1087 category_block
1088 .offsets
1089 .borrow_to_typed_slice::<i32>()
1090 .as_ref()
1091 );
1092 assert_eq!(decoded_category.data.as_ref(), category_block.data.as_ref());
1093
1094 let decoded_payload = decoded_struct.children[1].as_variable_width_ref().unwrap();
1095 assert_eq!(decoded_payload.bits_per_offset, 32);
1096 assert_eq!(
1097 decoded_payload
1098 .offsets
1099 .borrow_to_typed_slice::<i32>()
1100 .as_ref(),
1101 payload_block
1102 .offsets
1103 .borrow_to_typed_slice::<i32>()
1104 .as_ref()
1105 );
1106 assert_eq!(decoded_payload.data.as_ref(), payload_block.data.as_ref());
1107
1108 let decoded_count = decoded_struct.children[2].as_fixed_width_ref().unwrap();
1109 assert_eq!(decoded_count.bits_per_value, 32);
1110 assert_eq!(decoded_count.data.as_ref(), count_block.data.as_ref());
1111
1112 Ok(())
1113 }
1114
1115 #[test]
1116 fn variable_packed_struct_requires_v22() {
1117 let arrow_fields: Fields = vec![
1118 ArrowField::new("value", DataType::Int64, false),
1119 ArrowField::new("text", DataType::Utf8, false),
1120 ]
1121 .into();
1122 let arrow_struct = ArrowField::new("item", DataType::Struct(arrow_fields), false);
1123 let struct_field = Field::try_from(&arrow_struct).unwrap();
1124
1125 let value_block = fixed_block_from_array(Int64Array::from(vec![1, 2, 3]));
1126 let text_block =
1127 variable_block_from_string_array(StringArray::from(vec!["a", "bb", "ccc"]));
1128
1129 let struct_block = StructDataBlock {
1130 children: vec![
1131 DataBlock::FixedWidth(value_block),
1132 DataBlock::VariableWidth(text_block),
1133 ],
1134 block_info: BlockInfo::new(),
1135 validity: None,
1136 };
1137
1138 let compression_strategy =
1139 test_compression_strategy(TestEncoding::StructuralU16, CompressionParams::default());
1140 let result =
1141 compression_strategy.create_per_value(&struct_field, &DataBlock::Struct(struct_block));
1142
1143 assert!(matches!(result, Err(Error::NotSupported { .. })));
1144 }
1145
1146 #[test]
1147 fn variable_packed_struct_decompress_empty_row() -> Result<()> {
1148 let strategy = DefaultDecompressionStrategy::default();
1149 let fixed_decompressor = Arc::from(
1150 crate::compression::DecompressionStrategy::create_fixed_per_value_decompressor(
1151 &strategy,
1152 &ProtobufUtils21::flat(32, None),
1153 )?,
1154 );
1155 let variable_decompressor = Arc::from(
1156 crate::compression::DecompressionStrategy::create_variable_per_value_decompressor(
1157 &strategy,
1158 &ProtobufUtils21::variable(ProtobufUtils21::flat(32, None), None),
1159 )?,
1160 );
1161
1162 let decompressor = PackedStructVariablePerValueDecompressor::new(vec![
1163 VariablePackedStructFieldDecoder {
1164 kind: VariablePackedStructFieldKind::Fixed {
1165 bits_per_value: 32,
1166 decompressor: fixed_decompressor,
1167 },
1168 },
1169 VariablePackedStructFieldDecoder {
1170 kind: VariablePackedStructFieldKind::Variable {
1171 bits_per_length: 32,
1172 decompressor: variable_decompressor,
1173 },
1174 },
1175 ]);
1176
1177 let mut row_data = Vec::new();
1178 row_data.extend_from_slice(&1_u32.to_le_bytes());
1179 row_data.extend_from_slice(&1_u32.to_le_bytes());
1180 row_data.extend_from_slice(b"a");
1181 row_data.extend_from_slice(&2_u32.to_le_bytes());
1182 row_data.extend_from_slice(&0_u32.to_le_bytes());
1183
1184 let input = VariableWidthBlock {
1185 data: LanceBuffer::from(row_data),
1186 bits_per_offset: 32,
1187 offsets: LanceBuffer::reinterpret_vec(vec![0_u32, 9_u32, 9_u32, 17_u32]),
1188 num_values: 3,
1189 block_info: BlockInfo::new(),
1190 };
1191
1192 let decoded = decompressor.decompress(input)?;
1193 let DataBlock::Struct(decoded_struct) = decoded else {
1194 panic!("expected struct output");
1195 };
1196
1197 let fixed = decoded_struct.children[0].as_fixed_width_ref().unwrap();
1198 assert_eq!(fixed.bits_per_value, 32);
1199 assert_eq!(
1200 fixed.data.borrow_to_typed_slice::<u32>().as_ref(),
1201 &[1, 0, 2]
1202 );
1203
1204 let variable = decoded_struct.children[1].as_variable_width_ref().unwrap();
1205 assert_eq!(variable.bits_per_offset, 32);
1206 assert_eq!(
1207 variable.offsets.borrow_to_typed_slice::<u32>().as_ref(),
1208 &[0_u32, 1_u32, 1_u32, 1_u32]
1209 );
1210 assert_eq!(variable.data.as_ref(), b"a");
1211
1212 Ok(())
1213 }
1214}