1use std::{collections::HashMap, sync::Arc};
15
16use arrow_array::{Array, ArrayRef, RecordBatch};
17use bytes::{Bytes, BytesMut};
18use futures::future::BoxFuture;
19use lance_core::datatypes::{Field, Schema};
20use lance_core::utils::bit::{is_pwr_two, pad_bytes_to};
21use lance_core::{Error, Result};
22
23use crate::buffer::LanceBuffer;
24use crate::data::DataBlock;
25use crate::decoder::PageEncoding;
26use crate::repdef::RepDefBuilder;
27use crate::{
28 decoder::{ColumnInfo, PageInfo},
29 format::pb,
30};
31
32pub use crate::array_encoding::ArrayFieldEncodingStrategy;
33
34pub mod structural;
35
36pub const MIN_PAGE_BUFFER_ALIGNMENT: u64 = 8;
38
39#[derive(Debug)]
41pub struct EncodedArray {
42 pub data: DataBlock,
43 pub encoding: pb::ArrayEncoding,
44}
45
46impl EncodedArray {
47 pub fn new(data: DataBlock, encoding: pb::ArrayEncoding) -> Self {
48 Self { data, encoding }
49 }
50
51 pub fn into_buffers(self) -> (Vec<LanceBuffer>, pb::ArrayEncoding) {
52 (self.data.into_buffers(), self.encoding)
53 }
54}
55
56pub trait ArrayEncoder: std::fmt::Debug + Send + Sync {
58 fn encode(
59 &self,
60 data: DataBlock,
61 data_type: &arrow_schema::DataType,
62 buffer_index: &mut u32,
63 ) -> Result<EncodedArray>;
64}
65
66pub trait ArrayEncodingStrategy: Send + Sync + std::fmt::Debug {
68 fn create_array_encoder(
69 &self,
70 arrays: &[ArrayRef],
71 field: &Field,
72 ) -> Result<Box<dyn ArrayEncoder>>;
73}
74
75#[derive(Debug)]
81pub struct EncodedPage {
82 pub data: Vec<LanceBuffer>,
84 pub description: PageEncoding,
86 pub num_rows: u64,
88 pub row_number: u64,
95 pub column_idx: u32,
97}
98
99pub struct EncodedColumn {
100 pub column_buffers: Vec<LanceBuffer>,
101 pub encoding: pb::ColumnEncoding,
102 pub final_pages: Vec<EncodedPage>,
103}
104
105impl Default for EncodedColumn {
106 fn default() -> Self {
107 Self {
108 column_buffers: Default::default(),
109 encoding: pb::ColumnEncoding {
110 column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
111 },
112 final_pages: Default::default(),
113 }
114 }
115}
116
117pub struct OutOfLineBuffers {
131 position: u64,
132 buffer_alignment: u64,
133 buffers: Vec<LanceBuffer>,
134}
135
136impl OutOfLineBuffers {
137 pub fn new(base_position: u64, buffer_alignment: u64) -> Self {
138 Self {
139 position: base_position,
140 buffer_alignment,
141 buffers: Vec::new(),
142 }
143 }
144
145 pub fn add_buffer(&mut self, buffer: LanceBuffer) -> u64 {
146 let position = self.position;
147 self.position += buffer.len() as u64;
148 self.position += pad_bytes_to(buffer.len(), self.buffer_alignment as usize) as u64;
149 self.buffers.push(buffer);
150 position
151 }
152
153 pub fn take_buffers(self) -> Vec<LanceBuffer> {
154 self.buffers
155 }
156
157 pub fn reset_position(&mut self, position: u64) {
158 self.position = position;
159 }
160}
161
162pub type EncodeTask = BoxFuture<'static, Result<EncodedPage>>;
164
165pub trait FieldEncoder: Send {
176 fn prepare_array(&mut self, array: ArrayRef) -> Result<ArrayRef> {
182 Ok(array)
183 }
184
185 fn maybe_encode(
205 &mut self,
206 array: ArrayRef,
207 external_buffers: &mut OutOfLineBuffers,
208 repdef: RepDefBuilder,
209 row_number: u64,
210 num_rows: u64,
211 ) -> Result<Vec<EncodeTask>>;
212 fn flush(&mut self, external_buffers: &mut OutOfLineBuffers) -> Result<Vec<EncodeTask>>;
221 fn finish(
227 &mut self,
228 external_buffers: &mut OutOfLineBuffers,
229 ) -> BoxFuture<'_, Result<Vec<EncodedColumn>>>;
230
231 fn num_columns(&self) -> u32;
233}
234
235#[derive(Debug, Default)]
238pub struct ColumnIndexSequence {
239 current_index: u32,
240 mapping: Vec<(u32, u32)>,
241}
242
243impl ColumnIndexSequence {
244 pub fn next_column_index(&mut self, field_id: u32) -> u32 {
245 let idx = self.current_index;
246 self.current_index += 1;
247 self.mapping.push((field_id, idx));
248 idx
249 }
250
251 pub fn skip(&mut self) {
252 self.current_index += 1;
253 }
254}
255
256pub struct EncodingOptions {
258 pub cache_bytes_per_column: u64,
262 pub max_page_bytes: u64,
265 pub keep_original_array: bool,
270 pub buffer_alignment: u64,
275}
276
277impl Default for EncodingOptions {
278 fn default() -> Self {
279 Self {
280 cache_bytes_per_column: 8 * 1024 * 1024,
281 max_page_bytes: 32 * 1024 * 1024,
282 keep_original_array: true,
283 buffer_alignment: 64,
284 }
285 }
286}
287
288pub trait FieldEncodingStrategy: Send + Sync + std::fmt::Debug {
294 fn validate_array(&self, _array: &dyn Array, _field: &Field) -> Result<()> {
296 Ok(())
297 }
298
299 fn create_field_encoder(
306 &self,
307 field: &Field,
308 column_index: &mut ColumnIndexSequence,
309 context: &FieldEncodingContext<'_>,
310 ) -> Result<Box<dyn FieldEncoder>>;
311}
312
313pub struct FieldEncodingContext<'a> {
316 pub strategy: &'a dyn FieldEncodingStrategy,
318 pub options: &'a EncodingOptions,
320 pub root_field_metadata: &'a HashMap<String, String>,
322}
323
324pub struct BatchEncoder {
327 pub field_encoders: Vec<Box<dyn FieldEncoder>>,
328 pub field_id_to_column_index: Vec<(u32, u32)>,
329}
330
331impl BatchEncoder {
332 pub fn try_new(
333 schema: &Schema,
334 strategy: &dyn FieldEncodingStrategy,
335 options: &EncodingOptions,
336 ) -> Result<Self> {
337 let mut col_idx = 0;
338 let mut col_idx_sequence = ColumnIndexSequence::default();
339 let field_encoders = schema
340 .fields
341 .iter()
342 .map(|field| {
343 let context = FieldEncodingContext {
344 strategy,
345 options,
346 root_field_metadata: &field.metadata,
347 };
348 let encoder =
349 strategy.create_field_encoder(field, &mut col_idx_sequence, &context)?;
350 col_idx += encoder.as_ref().num_columns();
351 Ok(encoder)
352 })
353 .collect::<Result<Vec<_>>>()?;
354 Ok(Self {
355 field_encoders,
356 field_id_to_column_index: col_idx_sequence.mapping,
357 })
358 }
359
360 pub fn num_columns(&self) -> u32 {
361 self.field_encoders
362 .iter()
363 .map(|field_encoder| field_encoder.num_columns())
364 .sum::<u32>()
365 }
366}
367
368#[derive(Debug)]
372pub struct EncodedBatch {
373 pub data: Bytes,
374 pub page_table: Vec<Arc<ColumnInfo>>,
375 pub schema: Arc<Schema>,
376 pub top_level_columns: Vec<u32>,
377 pub num_rows: u64,
378}
379
380fn write_page_to_data_buffer(page: EncodedPage, data_buffer: &mut BytesMut) -> PageInfo {
381 let buffers = page.data;
382 let mut buffer_offsets_and_sizes = Vec::with_capacity(buffers.len());
383 for buffer in buffers {
384 let buffer_offset = data_buffer.len() as u64;
385 data_buffer.extend_from_slice(&buffer);
386 let size = data_buffer.len() as u64 - buffer_offset;
387 buffer_offsets_and_sizes.push((buffer_offset, size));
388 }
389
390 PageInfo {
391 buffer_offsets_and_sizes: Arc::from(buffer_offsets_and_sizes),
392 encoding: page.description,
393 num_rows: page.num_rows,
394 priority: page.row_number,
395 }
396}
397
398pub async fn encode_batch(
403 batch: &RecordBatch,
404 schema: Arc<Schema>,
405 encoding_strategy: &dyn FieldEncodingStrategy,
406 options: &EncodingOptions,
407) -> Result<EncodedBatch> {
408 if !is_pwr_two(options.buffer_alignment) || options.buffer_alignment < MIN_PAGE_BUFFER_ALIGNMENT
409 {
410 return Err(Error::invalid_input_source(
411 format!(
412 "buffer_alignment must be a power of two and at least {}",
413 MIN_PAGE_BUFFER_ALIGNMENT
414 )
415 .into(),
416 ));
417 }
418
419 let mut data_buffer = BytesMut::new();
420 let lance_schema = Schema::try_from(batch.schema().as_ref())?;
421 let options = EncodingOptions {
422 keep_original_array: true,
423 ..*options
424 };
425 let mut batch_encoder = BatchEncoder::try_new(&lance_schema, encoding_strategy, &options)?;
426 let arrays = batch
427 .columns()
428 .iter()
429 .cloned()
430 .zip(batch_encoder.field_encoders.iter_mut())
431 .map(|(array, encoder)| encoder.prepare_array(array))
432 .collect::<Result<Vec<_>>>()?;
433 let mut page_table = Vec::new();
434 let mut col_idx_offset = 0;
435 for (arr, mut encoder) in arrays.into_iter().zip(batch_encoder.field_encoders) {
436 let mut external_buffers =
437 OutOfLineBuffers::new(data_buffer.len() as u64, options.buffer_alignment);
438 let repdef = RepDefBuilder::default();
439 let encoder = encoder.as_mut();
440 let num_rows = arr.len() as u64;
441 let mut tasks = encoder.maybe_encode(arr, &mut external_buffers, repdef, 0, num_rows)?;
442 tasks.extend(encoder.flush(&mut external_buffers)?);
443 for buffer in external_buffers.take_buffers() {
444 data_buffer.extend_from_slice(&buffer);
445 }
446 let mut pages = HashMap::<u32, Vec<PageInfo>>::new();
447 for task in tasks {
448 let encoded_page = task.await?;
449 pages
451 .entry(encoded_page.column_idx)
452 .or_default()
453 .push(write_page_to_data_buffer(encoded_page, &mut data_buffer));
454 }
455 let mut external_buffers =
456 OutOfLineBuffers::new(data_buffer.len() as u64, options.buffer_alignment);
457 let encoded_columns = encoder.finish(&mut external_buffers).await?;
458 for buffer in external_buffers.take_buffers() {
459 data_buffer.extend_from_slice(&buffer);
460 }
461 let num_columns = encoded_columns.len();
462 for (col_idx, encoded_column) in encoded_columns.into_iter().enumerate() {
463 let col_idx = col_idx + col_idx_offset;
464 let mut col_buffer_offsets_and_sizes = Vec::new();
465 for buffer in encoded_column.column_buffers {
466 let buffer_offset = data_buffer.len() as u64;
467 data_buffer.extend_from_slice(&buffer);
468 let size = data_buffer.len() as u64 - buffer_offset;
469 col_buffer_offsets_and_sizes.push((buffer_offset, size));
470 }
471 for page in encoded_column.final_pages {
472 pages
473 .entry(page.column_idx)
474 .or_default()
475 .push(write_page_to_data_buffer(page, &mut data_buffer));
476 }
477 let col_pages = std::mem::take(pages.entry(col_idx as u32).or_default());
478 page_table.push(Arc::new(ColumnInfo {
479 index: col_idx as u32,
480 buffer_offsets_and_sizes: Arc::from(
481 col_buffer_offsets_and_sizes.into_boxed_slice(),
482 ),
483 page_infos: Arc::from(col_pages.into_boxed_slice()),
484 encoding: encoded_column.encoding,
485 }))
486 }
487 col_idx_offset += num_columns;
488 }
489 let top_level_columns = batch_encoder
490 .field_id_to_column_index
491 .iter()
492 .map(|(_, idx)| *idx)
493 .collect();
494 Ok(EncodedBatch {
495 data: data_buffer.freeze(),
496 top_level_columns,
497 page_table,
498 schema,
499 num_rows: batch.num_rows() as u64,
500 })
501}
502
503#[cfg(test)]
504mod tests {
505 use super::*;
506 use crate::testing::{TestEncoding, create_test_field_encoder, test_encoding_strategy};
507 use arrow_array::make_array;
508 use arrow_buffer::Buffer;
509 use arrow_data::ArrayData;
510 use arrow_schema::{DataType as ArrowDataType, Field as ArrowField, Fields as ArrowFields};
511 use rstest::rstest;
512
513 #[rstest]
514 fn test_nested_variable_width_offsets_are_validated_before_dispatch(
515 #[values(TestEncoding::Array, TestEncoding::StructuralU32)] encoding: TestEncoding,
516 #[values(ArrowDataType::Utf8, ArrowDataType::LargeUtf8)] item_type: ArrowDataType,
517 ) {
518 let offsets = match &item_type {
519 ArrowDataType::Utf8 => Buffer::from_slice_ref([0_i32, 2, 1, 3]),
520 ArrowDataType::LargeUtf8 => Buffer::from_slice_ref([0_i64, 2, 1, 3]),
521 _ => unreachable!(),
522 };
523 let child_data = unsafe {
524 ArrayData::builder(item_type.clone())
525 .len(3)
526 .add_buffer(offsets)
527 .add_buffer(Buffer::from(b"abc"))
528 .build_unchecked()
529 };
530 let item_field = Arc::new(ArrowField::new("item", item_type, false));
531 let data_type = ArrowDataType::FixedSizeList(item_field, 1);
532 let array_data = unsafe {
533 ArrayData::builder(data_type.clone())
534 .len(3)
535 .add_child_data(child_data)
536 .build_unchecked()
537 };
538 let array = make_array(array_data);
539 let field = Field::try_from(&ArrowField::new("payload", data_type, false)).unwrap();
540 let strategy = test_encoding_strategy(encoding);
541 let mut column_index = ColumnIndexSequence::default();
542 let options = EncodingOptions {
543 cache_bytes_per_column: 0,
544 ..Default::default()
545 };
546 let mut encoder =
547 create_test_field_encoder(strategy.as_ref(), &field, &mut column_index, &options)
548 .unwrap();
549 let mut external_buffers = OutOfLineBuffers::new(0, MIN_PAGE_BUFFER_ALIGNMENT);
550
551 let error = encoder
552 .maybe_encode(array, &mut external_buffers, RepDefBuilder::default(), 0, 3)
553 .err()
554 .expect("malformed nested offsets should fail before task dispatch");
555
556 assert!(matches!(error, Error::InvalidInput { .. }));
557 let message = error.to_string();
558 assert!(
559 message.contains("field 'payload'"),
560 "unexpected message: {message}"
561 );
562 assert!(
563 message.contains("non-monotonic offset at position 2"),
564 "unexpected message: {message}"
565 );
566 }
567
568 #[test]
569 fn test_fixed_size_list_struct_requires_v2_2() {
570 let list_item = ArrowField::new(
571 "item",
572 ArrowDataType::Struct(ArrowFields::from(vec![ArrowField::new(
573 "x",
574 ArrowDataType::Int32,
575 true,
576 )])),
577 true,
578 );
579 let arrow_field = ArrowField::new(
580 "list_struct",
581 ArrowDataType::FixedSizeList(Arc::new(list_item), 2),
582 true,
583 );
584 let field = Field::try_from(&arrow_field).unwrap();
585
586 let strategy = test_encoding_strategy(TestEncoding::StructuralU16);
587 let mut column_index = ColumnIndexSequence::default();
588 let options = EncodingOptions::default();
589
590 let result =
591 create_test_field_encoder(strategy.as_ref(), &field, &mut column_index, &options);
592 assert!(
593 result.is_err(),
594 "FixedSizeList<Struct> should be rejected for file version 2.1"
595 );
596 let err = result.err().unwrap();
597
598 assert!(
599 err.to_string()
600 .contains("FixedSizeList<Struct> is not enabled by the selected file format")
601 );
602 }
603}