1use std::collections::HashMap;
9use std::sync::Arc;
10
11use arrow::datatypes::{DataType, Field, Schema};
12use lance::Dataset;
13
14use crate::error::{Error, Result};
15use crate::format::check_format;
16
17const COMPRESSION_KEY: &str = "lance-encoding:compression";
19const COMPRESSION_LEVEL_KEY: &str = "lance-encoding:compression-level";
20const BYTE_STREAM_SPLIT_KEY: &str = "lance-encoding:bss";
21const RLE_THRESHOLD_KEY: &str = "lance-encoding:rle-threshold";
22const STRUCTURAL_ENCODING_KEY: &str = "lance-encoding:structural-encoding";
23const KEYS: [&str; 5] = [
24 COMPRESSION_KEY,
25 COMPRESSION_LEVEL_KEY,
26 BYTE_STREAM_SPLIT_KEY,
27 RLE_THRESHOLD_KEY,
28 STRUCTURAL_ENCODING_KEY,
29];
30
31pub(crate) const LANCE_ENCODING_PREFIX: &str = "lance-encoding:";
33
34pub(crate) const COLUMN_DEFAULTS_PREFIX: &str = "lance_nd.column_defaults.";
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum Compression {
40 None,
42 Lz4,
44 Zstd {
46 level: Option<i32>,
48 },
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum StructuralEncoding {
54 MiniBlock,
56 FullZip,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum ByteStreamSplit {
63 Off,
65 On,
67 Auto,
69}
70
71#[derive(Debug, Clone, Default, PartialEq)]
73pub struct ColumnOptions {
74 pub compression: Option<Compression>,
76 pub byte_stream_split: Option<ByteStreamSplit>,
78 pub rle_threshold: Option<f64>,
80 pub structural_encoding: Option<StructuralEncoding>,
82}
83
84impl ColumnOptions {
85 pub fn with_compression(mut self, compression: Compression) -> Self {
87 self.compression = Some(compression);
88 self
89 }
90
91 pub fn with_byte_stream_split(mut self, mode: ByteStreamSplit) -> Self {
93 self.byte_stream_split = Some(mode);
94 self
95 }
96
97 pub fn with_rle_threshold(mut self, threshold: f64) -> Self {
99 self.rle_threshold = Some(threshold);
100 self
101 }
102
103 pub fn with_structural_encoding(mut self, encoding: StructuralEncoding) -> Self {
105 self.structural_encoding = Some(encoding);
106 self
107 }
108
109 pub(crate) fn or(&self, defaults: &ColumnOptions) -> ColumnOptions {
111 ColumnOptions {
112 compression: self.compression.or(defaults.compression),
113 byte_stream_split: self.byte_stream_split.or(defaults.byte_stream_split),
114 rle_threshold: self.rle_threshold.or(defaults.rle_threshold),
115 structural_encoding: self.structural_encoding.or(defaults.structural_encoding),
116 }
117 }
118
119 pub(crate) fn leaf_metadata(&self) -> HashMap<String, String> {
121 let mut metadata = HashMap::new();
122 match self.compression {
123 Some(Compression::None) => insert(&mut metadata, COMPRESSION_KEY, "none"),
124 Some(Compression::Lz4) => insert(&mut metadata, COMPRESSION_KEY, "lz4"),
125 Some(Compression::Zstd { level }) => {
126 insert(&mut metadata, COMPRESSION_KEY, "zstd");
127 if let Some(level) = level {
128 insert(&mut metadata, COMPRESSION_LEVEL_KEY, &level.to_string());
129 }
130 }
131 None => {}
132 }
133 let mode = self.byte_stream_split.map(|mode| match mode {
134 ByteStreamSplit::Off => "off",
135 ByteStreamSplit::On => "on",
136 ByteStreamSplit::Auto => "auto",
137 });
138 if let Some(mode) = mode {
139 insert(&mut metadata, BYTE_STREAM_SPLIT_KEY, mode);
140 }
141 if let Some(threshold) = self.rle_threshold {
142 insert(&mut metadata, RLE_THRESHOLD_KEY, &threshold.to_string());
143 }
144 metadata
145 }
146
147 pub(crate) fn root_metadata(&self) -> HashMap<String, String> {
149 let mut metadata = HashMap::new();
150 let encoding = self.structural_encoding.map(|encoding| match encoding {
151 StructuralEncoding::MiniBlock => "miniblock",
152 StructuralEncoding::FullZip => "fullzip",
153 });
154 if let Some(encoding) = encoding {
155 insert(&mut metadata, STRUCTURAL_ENCODING_KEY, encoding);
156 }
157 metadata
158 }
159
160 pub(crate) fn to_table_metadata(&self) -> HashMap<String, String> {
162 self.leaf_metadata()
163 .into_iter()
164 .chain(self.root_metadata())
165 .map(|(key, value)| (format!("{COLUMN_DEFAULTS_PREFIX}{key}"), value))
166 .collect()
167 }
168
169 pub(crate) fn from_table_metadata(metadata: &HashMap<String, String>) -> Result<Self> {
171 let get = |key: &str| metadata.get(&format!("{COLUMN_DEFAULTS_PREFIX}{key}"));
172 let invalid = |key: &str, value: &str| {
173 Error::InvalidTable(format!(
174 "column default {key} has the invalid value '{value}'"
175 ))
176 };
177 let level = get(COMPRESSION_LEVEL_KEY)
178 .map(|v| v.parse().map_err(|_| invalid(COMPRESSION_LEVEL_KEY, v)))
179 .transpose()?;
180 let compression = get(COMPRESSION_KEY)
181 .map(|v| match v.as_str() {
182 "none" => Ok(Compression::None),
183 "lz4" => Ok(Compression::Lz4),
184 "zstd" => Ok(Compression::Zstd { level }),
185 _ => Err(invalid(COMPRESSION_KEY, v)),
186 })
187 .transpose()?;
188 let byte_stream_split = get(BYTE_STREAM_SPLIT_KEY)
189 .map(|v| match v.as_str() {
190 "off" => Ok(ByteStreamSplit::Off),
191 "on" => Ok(ByteStreamSplit::On),
192 "auto" => Ok(ByteStreamSplit::Auto),
193 _ => Err(invalid(BYTE_STREAM_SPLIT_KEY, v)),
194 })
195 .transpose()?;
196 let rle_threshold = get(RLE_THRESHOLD_KEY)
197 .map(|v| v.parse().map_err(|_| invalid(RLE_THRESHOLD_KEY, v)))
198 .transpose()?;
199 let structural_encoding = get(STRUCTURAL_ENCODING_KEY)
200 .map(|v| match v.as_str() {
201 "miniblock" => Ok(StructuralEncoding::MiniBlock),
202 "fullzip" => Ok(StructuralEncoding::FullZip),
203 _ => Err(invalid(STRUCTURAL_ENCODING_KEY, v)),
204 })
205 .transpose()?;
206 Ok(Self {
207 compression,
208 byte_stream_split,
209 rle_threshold,
210 structural_encoding,
211 })
212 }
213}
214
215fn insert(metadata: &mut HashMap<String, String>, key: &str, value: &str) {
216 metadata.insert(key.to_string(), value.to_string());
217}
218
219pub fn column_defaults(dataset: &Dataset) -> Result<ColumnOptions> {
221 check_format(dataset)?;
222 ColumnOptions::from_table_metadata(&dataset.schema().metadata)
223}
224
225#[tracing::instrument(skip_all, fields(uri = %dataset.uri()), err)]
227pub async fn set_column_defaults(dataset: &mut Dataset, defaults: &ColumnOptions) -> Result<()> {
228 check_format(dataset)?;
229 let mut values = defaults.to_table_metadata();
230 let entries: Vec<(String, Option<String>)> = KEYS
231 .iter()
232 .map(|key| {
233 let key = format!("{COLUMN_DEFAULTS_PREFIX}{key}");
234 let value = values.remove(&key);
235 (key, value)
236 })
237 .collect();
238 dataset.update_schema_metadata(entries).await?;
239 Ok(())
240}
241
242pub(crate) fn with_column_options(
245 schema: &Schema,
246 defaults: &ColumnOptions,
247 columns: &HashMap<String, ColumnOptions>,
248) -> Schema {
249 let fields: Vec<Field> = schema
250 .fields()
251 .iter()
252 .map(|field| {
253 let options = match columns.get(field.name()) {
254 Some(options) => options.or(defaults),
255 None => defaults.clone(),
256 };
257 let leaf = options.leaf_metadata();
258 let mut field = with_leaf_metadata(field, &leaf);
259 let mut metadata = field.metadata().clone();
260 metadata.extend(options.root_metadata());
261 field.set_metadata(metadata);
262 field
263 })
264 .collect();
265 Schema::new_with_metadata(fields, schema.metadata().clone())
266}
267
268fn with_leaf_metadata(field: &Field, leaf: &HashMap<String, String>) -> Field {
270 let data_type = match field.data_type() {
271 DataType::Struct(children) => DataType::Struct(
272 children
273 .iter()
274 .map(|child| with_leaf_metadata(child, leaf))
275 .collect(),
276 ),
277 DataType::List(item) => DataType::List(Arc::new(with_leaf_metadata(item, leaf))),
278 _ => {
279 let mut metadata = field.metadata().clone();
280 metadata.extend(leaf.clone());
281 return field.clone().with_metadata(metadata);
282 }
283 };
284 field.clone().with_data_type(data_type)
285}
286
287pub(crate) fn without_encoding_metadata(field: &Field) -> Field {
289 let data_type = match field.data_type() {
290 DataType::Struct(children) => DataType::Struct(
291 children
292 .iter()
293 .map(|child| without_encoding_metadata(child))
294 .collect(),
295 ),
296 DataType::List(item) => DataType::List(Arc::new(without_encoding_metadata(item))),
297 data_type => data_type.clone(),
298 };
299 let metadata = field
300 .metadata()
301 .iter()
302 .filter(|(key, _)| !key.starts_with(LANCE_ENCODING_PREFIX))
303 .map(|(key, value)| (key.clone(), value.clone()))
304 .collect();
305 field
306 .clone()
307 .with_data_type(data_type)
308 .with_metadata(metadata)
309}