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 maybe_encode(
196 &mut self,
197 array: ArrayRef,
198 external_buffers: &mut OutOfLineBuffers,
199 repdef: RepDefBuilder,
200 row_number: u64,
201 num_rows: u64,
202 ) -> Result<Vec<EncodeTask>>;
203 fn flush(&mut self, external_buffers: &mut OutOfLineBuffers) -> Result<Vec<EncodeTask>>;
212 fn finish(
218 &mut self,
219 external_buffers: &mut OutOfLineBuffers,
220 ) -> BoxFuture<'_, Result<Vec<EncodedColumn>>>;
221
222 fn num_columns(&self) -> u32;
224}
225
226#[derive(Debug, Default)]
229pub struct ColumnIndexSequence {
230 current_index: u32,
231 mapping: Vec<(u32, u32)>,
232}
233
234impl ColumnIndexSequence {
235 pub fn next_column_index(&mut self, field_id: u32) -> u32 {
236 let idx = self.current_index;
237 self.current_index += 1;
238 self.mapping.push((field_id, idx));
239 idx
240 }
241
242 pub fn skip(&mut self) {
243 self.current_index += 1;
244 }
245}
246
247pub struct EncodingOptions {
249 pub cache_bytes_per_column: u64,
253 pub max_page_bytes: u64,
256 pub keep_original_array: bool,
261 pub buffer_alignment: u64,
266}
267
268impl Default for EncodingOptions {
269 fn default() -> Self {
270 Self {
271 cache_bytes_per_column: 8 * 1024 * 1024,
272 max_page_bytes: 32 * 1024 * 1024,
273 keep_original_array: true,
274 buffer_alignment: 64,
275 }
276 }
277}
278
279pub trait FieldEncodingStrategy: Send + Sync + std::fmt::Debug {
285 fn create_field_encoder(
292 &self,
293 field: &Field,
294 column_index: &mut ColumnIndexSequence,
295 context: &FieldEncodingContext<'_>,
296 ) -> Result<Box<dyn FieldEncoder>>;
297}
298
299pub struct FieldEncodingContext<'a> {
302 pub strategy: &'a dyn FieldEncodingStrategy,
304 pub options: &'a EncodingOptions,
306 pub root_field_metadata: &'a HashMap<String, String>,
308}
309
310pub struct BatchEncoder {
313 pub field_encoders: Vec<Box<dyn FieldEncoder>>,
314 pub field_id_to_column_index: Vec<(u32, u32)>,
315}
316
317impl BatchEncoder {
318 pub fn try_new(
319 schema: &Schema,
320 strategy: &dyn FieldEncodingStrategy,
321 options: &EncodingOptions,
322 ) -> Result<Self> {
323 let mut col_idx = 0;
324 let mut col_idx_sequence = ColumnIndexSequence::default();
325 let field_encoders = schema
326 .fields
327 .iter()
328 .map(|field| {
329 let context = FieldEncodingContext {
330 strategy,
331 options,
332 root_field_metadata: &field.metadata,
333 };
334 let encoder =
335 strategy.create_field_encoder(field, &mut col_idx_sequence, &context)?;
336 col_idx += encoder.as_ref().num_columns();
337 Ok(encoder)
338 })
339 .collect::<Result<Vec<_>>>()?;
340 Ok(Self {
341 field_encoders,
342 field_id_to_column_index: col_idx_sequence.mapping,
343 })
344 }
345
346 pub fn num_columns(&self) -> u32 {
347 self.field_encoders
348 .iter()
349 .map(|field_encoder| field_encoder.num_columns())
350 .sum::<u32>()
351 }
352}
353
354#[derive(Debug)]
358pub struct EncodedBatch {
359 pub data: Bytes,
360 pub page_table: Vec<Arc<ColumnInfo>>,
361 pub schema: Arc<Schema>,
362 pub top_level_columns: Vec<u32>,
363 pub num_rows: u64,
364}
365
366fn write_page_to_data_buffer(page: EncodedPage, data_buffer: &mut BytesMut) -> PageInfo {
367 let buffers = page.data;
368 let mut buffer_offsets_and_sizes = Vec::with_capacity(buffers.len());
369 for buffer in buffers {
370 let buffer_offset = data_buffer.len() as u64;
371 data_buffer.extend_from_slice(&buffer);
372 let size = data_buffer.len() as u64 - buffer_offset;
373 buffer_offsets_and_sizes.push((buffer_offset, size));
374 }
375
376 PageInfo {
377 buffer_offsets_and_sizes: Arc::from(buffer_offsets_and_sizes),
378 encoding: page.description,
379 num_rows: page.num_rows,
380 priority: page.row_number,
381 }
382}
383
384pub async fn encode_batch(
389 batch: &RecordBatch,
390 schema: Arc<Schema>,
391 encoding_strategy: &dyn FieldEncodingStrategy,
392 options: &EncodingOptions,
393) -> Result<EncodedBatch> {
394 if !is_pwr_two(options.buffer_alignment) || options.buffer_alignment < MIN_PAGE_BUFFER_ALIGNMENT
395 {
396 return Err(Error::invalid_input_source(
397 format!(
398 "buffer_alignment must be a power of two and at least {}",
399 MIN_PAGE_BUFFER_ALIGNMENT
400 )
401 .into(),
402 ));
403 }
404
405 let mut data_buffer = BytesMut::new();
406 let lance_schema = Schema::try_from(batch.schema().as_ref())?;
407 let options = EncodingOptions {
408 keep_original_array: true,
409 ..*options
410 };
411 let batch_encoder = BatchEncoder::try_new(&lance_schema, encoding_strategy, &options)?;
412 let mut page_table = Vec::new();
413 let mut col_idx_offset = 0;
414 for (arr, mut encoder) in batch.columns().iter().zip(batch_encoder.field_encoders) {
415 let mut external_buffers =
416 OutOfLineBuffers::new(data_buffer.len() as u64, options.buffer_alignment);
417 let repdef = RepDefBuilder::default();
418 let encoder = encoder.as_mut();
419 let num_rows = arr.len() as u64;
420 let mut tasks =
421 encoder.maybe_encode(arr.clone(), &mut external_buffers, repdef, 0, num_rows)?;
422 tasks.extend(encoder.flush(&mut external_buffers)?);
423 for buffer in external_buffers.take_buffers() {
424 data_buffer.extend_from_slice(&buffer);
425 }
426 let mut pages = HashMap::<u32, Vec<PageInfo>>::new();
427 for task in tasks {
428 let encoded_page = task.await?;
429 pages
431 .entry(encoded_page.column_idx)
432 .or_default()
433 .push(write_page_to_data_buffer(encoded_page, &mut data_buffer));
434 }
435 let mut external_buffers =
436 OutOfLineBuffers::new(data_buffer.len() as u64, options.buffer_alignment);
437 let encoded_columns = encoder.finish(&mut external_buffers).await?;
438 for buffer in external_buffers.take_buffers() {
439 data_buffer.extend_from_slice(&buffer);
440 }
441 let num_columns = encoded_columns.len();
442 for (col_idx, encoded_column) in encoded_columns.into_iter().enumerate() {
443 let col_idx = col_idx + col_idx_offset;
444 let mut col_buffer_offsets_and_sizes = Vec::new();
445 for buffer in encoded_column.column_buffers {
446 let buffer_offset = data_buffer.len() as u64;
447 data_buffer.extend_from_slice(&buffer);
448 let size = data_buffer.len() as u64 - buffer_offset;
449 col_buffer_offsets_and_sizes.push((buffer_offset, size));
450 }
451 for page in encoded_column.final_pages {
452 pages
453 .entry(page.column_idx)
454 .or_default()
455 .push(write_page_to_data_buffer(page, &mut data_buffer));
456 }
457 let col_pages = std::mem::take(pages.entry(col_idx as u32).or_default());
458 page_table.push(Arc::new(ColumnInfo {
459 index: col_idx as u32,
460 buffer_offsets_and_sizes: Arc::from(
461 col_buffer_offsets_and_sizes.into_boxed_slice(),
462 ),
463 page_infos: Arc::from(col_pages.into_boxed_slice()),
464 encoding: encoded_column.encoding,
465 }))
466 }
467 col_idx_offset += num_columns;
468 }
469 let top_level_columns = batch_encoder
470 .field_id_to_column_index
471 .iter()
472 .map(|(_, idx)| *idx)
473 .collect();
474 Ok(EncodedBatch {
475 data: data_buffer.freeze(),
476 top_level_columns,
477 page_table,
478 schema,
479 num_rows: batch.num_rows() as u64,
480 })
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486 use crate::testing::{TestEncoding, create_test_field_encoder, test_encoding_strategy};
487 use arrow_schema::{DataType as ArrowDataType, Field as ArrowField, Fields as ArrowFields};
488
489 #[test]
490 fn test_fixed_size_list_struct_requires_v2_2() {
491 let list_item = ArrowField::new(
492 "item",
493 ArrowDataType::Struct(ArrowFields::from(vec![ArrowField::new(
494 "x",
495 ArrowDataType::Int32,
496 true,
497 )])),
498 true,
499 );
500 let arrow_field = ArrowField::new(
501 "list_struct",
502 ArrowDataType::FixedSizeList(Arc::new(list_item), 2),
503 true,
504 );
505 let field = Field::try_from(&arrow_field).unwrap();
506
507 let strategy = test_encoding_strategy(TestEncoding::StructuralU16);
508 let mut column_index = ColumnIndexSequence::default();
509 let options = EncodingOptions::default();
510
511 let result =
512 create_test_field_encoder(strategy.as_ref(), &field, &mut column_index, &options);
513 assert!(
514 result.is_err(),
515 "FixedSizeList<Struct> should be rejected for file version 2.1"
516 );
517 let err = result.err().unwrap();
518
519 assert!(
520 err.to_string()
521 .contains("FixedSizeList<Struct> is not enabled by the selected file format")
522 );
523 }
524}