Skip to main content

lance_file/versions/v2_3/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Lance v2.3 file composition.
5
6use std::{
7    collections::BTreeMap,
8    sync::{
9        Arc,
10        atomic::{AtomicBool, Ordering},
11    },
12};
13
14use bytes::Bytes;
15use lance_core::{
16    Error, Result,
17    datatypes::{Field, Schema},
18};
19use lance_encoding::{
20    compression_config::CompressionParams,
21    encoder::{
22        ColumnIndexSequence, EncodedBatch, FieldEncoder, FieldEncodingContext,
23        FieldEncodingStrategy,
24        structural::{
25            PrimitiveFieldEncoding, PrimitivePageEncoding, try_create_list, try_create_map,
26            try_create_struct, try_create_structural_blob, try_create_structural_fixed_size_list,
27        },
28    },
29};
30use lance_io::traits::Writer as ObjectWriter;
31
32use crate::{
33    reader::{ReadProjection, structural},
34    writer::FileWriterOptions,
35};
36
37mod compression;
38mod reader;
39mod writer;
40
41pub(crate) use reader::{
42    decode_column_metadata, finish_metadata, finish_metadata_index, validate_global_buffers,
43};
44pub use reader::{
45    projection_from_column_names, projection_from_field_ids, projection_from_whole_schema,
46};
47
48pub(crate) fn read_projection() -> Arc<dyn ReadProjection> {
49    structural::read_projection(reader::decode_column)
50}
51pub use writer::Writer;
52
53static WARNED_ON_UNSTABLE_FORMAT: AtomicBool = AtomicBool::new(false);
54
55/// Count physical columns represented by a field in a v2.3 footer.
56pub fn physical_column_count(field: &Field) -> usize {
57    structural::physical_column_count(field)
58}
59
60/// Build persisted field-to-column entries for a v2.3 data file.
61pub fn data_file_columns(schema: &Schema) -> (Vec<i32>, Vec<i32>) {
62    structural::data_file_columns(schema)
63}
64
65pub(super) fn field_id_to_column_index(schema: &Schema) -> BTreeMap<u32, u32> {
66    structural::field_id_to_column_index(schema)
67}
68
69#[derive(Debug)]
70struct FieldStrategy {
71    primitive: PrimitiveFieldEncoding,
72}
73
74impl FieldEncodingStrategy for FieldStrategy {
75    fn create_field_encoder(
76        &self,
77        field: &Field,
78        column_index: &mut ColumnIndexSequence,
79        context: &FieldEncodingContext<'_>,
80    ) -> Result<Box<dyn FieldEncoder>> {
81        if field.is_blob()
82            && matches!(
83                field.data_type(),
84                arrow_schema::DataType::Binary | arrow_schema::DataType::LargeBinary
85            )
86        {
87            return Err(Error::invalid_input(
88                "Lance 2.3 does not write Blob v1 pages; use a Blob v2 descriptor struct",
89            ));
90        }
91        if let Some(encoder) =
92            try_create_structural_blob(&self.primitive, field, column_index, context)?
93        {
94            return Ok(encoder);
95        }
96        if field.is_blob() {
97            return Err(Error::invalid_input_source(
98                format!(
99                    "Blob encoding is not available for field '{}' with data type {}",
100                    field.name,
101                    field.data_type()
102                )
103                .into(),
104            ));
105        }
106        if let Some(encoder) = try_create_map(field, column_index, context)? {
107            return Ok(encoder);
108        }
109        if let Some(encoder) = try_create_structural_fixed_size_list(field, column_index, context)?
110        {
111            return Ok(encoder);
112        }
113        if let Some(encoder) = self.primitive.try_create(field, column_index, context)? {
114            return Ok(encoder);
115        }
116        if let Some(encoder) = try_create_list(field, column_index, context)? {
117            return Ok(encoder);
118        }
119        if let Some(encoder) = try_create_struct(field, column_index, context)? {
120            return Ok(encoder);
121        }
122        Err(Error::not_supported_source(
123            format!(
124                "Lance v2.3 has no field encoding for '{}' with data type {}",
125                field.name,
126                field.data_type()
127            )
128            .into(),
129        ))
130    }
131}
132
133/// Compose the v2.3 field encoding mechanisms.
134pub fn encoding_strategy(params: CompressionParams) -> Arc<dyn FieldEncodingStrategy> {
135    let compression = Arc::new(compression::Strategy::new(params));
136    Arc::new(FieldStrategy {
137        primitive: PrimitiveFieldEncoding::new([PrimitivePageEncoding::planar(compression)]),
138    })
139}
140
141fn warn_unstable_format() {
142    if WARNED_ON_UNSTABLE_FORMAT
143        .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
144        .is_ok()
145    {
146        log::warn!(
147            "You have requested an unstable format version.  Files written with this format version may not be readable in the future!  This is a development feature and should only be used for experimentation and never for production data."
148        );
149    }
150}
151
152/// Create a v2.3 writer with an explicit schema.
153pub fn create_writer(
154    object_writer: Box<dyn ObjectWriter>,
155    schema: Schema,
156    options: FileWriterOptions,
157) -> Result<Writer> {
158    warn_unstable_format();
159    Writer::try_new(object_writer, schema, options)
160}
161
162/// Create a v2.3 writer with explicit compression tuning.
163pub fn create_writer_with_compression(
164    object_writer: Box<dyn ObjectWriter>,
165    schema: Schema,
166    options: FileWriterOptions,
167    compression: CompressionParams,
168) -> Result<Writer> {
169    warn_unstable_format();
170    Writer::try_new_with_compression(object_writer, schema, options, compression)
171}
172
173/// Create a v2.3 writer whose schema is inferred from the first batch.
174pub fn create_lazy_writer(
175    object_writer: Box<dyn ObjectWriter>,
176    options: FileWriterOptions,
177) -> Writer {
178    warn_unstable_format();
179    Writer::new_lazy(object_writer, options)
180}
181
182/// Create a lazy v2.3 writer with explicit compression tuning.
183pub fn create_lazy_writer_with_compression(
184    object_writer: Box<dyn ObjectWriter>,
185    options: FileWriterOptions,
186    compression: CompressionParams,
187) -> Writer {
188    warn_unstable_format();
189    Writer::new_lazy_with_compression(object_writer, options, compression)
190}
191
192/// Encode a self-described v2.3 batch.
193pub fn encode_self_described_batch(batch: &EncodedBatch) -> Result<Bytes> {
194    writer::concat_lance_footer(batch, true)
195}
196
197/// Encode a mini-lance v2.3 batch.
198pub fn encode_mini_batch(batch: &EncodedBatch) -> Result<Bytes> {
199    writer::concat_lance_footer(batch, false)
200}