1use lance_arrow::ARROW_EXT_NAME_KEY;
5use lance_core::datatypes::{Dictionary, Encoding, Field, LogicalType, Schema};
6use lance_core::{Error, Result};
7use std::collections::HashMap;
8
9use crate::format::pb;
10
11#[allow(clippy::fallible_impl_from)]
12impl From<&pb::Field> for Field {
13 fn from(field: &pb::Field) -> Self {
14 let lance_metadata: HashMap<String, String> = field
15 .metadata
16 .iter()
17 .map(|(key, value)| {
18 let string_value = String::from_utf8_lossy(value).to_string();
19 (key.clone(), string_value)
20 })
21 .collect();
22 let mut lance_metadata = lance_metadata;
23 if !field.extension_name.is_empty() {
24 lance_metadata.insert(ARROW_EXT_NAME_KEY.to_string(), field.extension_name.clone());
25 }
26 Self {
27 name: field.name.clone(),
28 id: field.id,
29 parent_id: field.parent_id,
30 logical_type: LogicalType::from(field.logical_type.as_str()),
31 metadata: lance_metadata,
32 encoding: match field.encoding {
33 1 => Some(Encoding::Plain),
34 2 => Some(Encoding::VarBinary),
35 3 => Some(Encoding::Dictionary),
36 4 => Some(Encoding::RLE),
37 _ => None,
38 },
39 nullable: field.nullable,
40 children: vec![],
41 dictionary: field.dictionary.as_ref().map(Dictionary::from),
42 unenforced_primary_key_position: if field.unenforced_primary_key_position > 0 {
43 Some(field.unenforced_primary_key_position)
44 } else if field.unenforced_primary_key {
45 Some(0)
46 } else {
47 None
48 },
49 unenforced_clustering_key_position: if field.unenforced_clustering_key_position > 0 {
50 Some(field.unenforced_clustering_key_position)
51 } else {
52 None
53 },
54 }
55 }
56}
57
58impl From<&Field> for pb::Field {
59 fn from(field: &Field) -> Self {
60 let pb_metadata = field
61 .metadata
62 .iter()
63 .map(|(key, value)| (key.clone(), value.clone().into_bytes()))
64 .collect();
65 Self {
66 id: field.id,
67 parent_id: field.parent_id,
68 name: field.name.clone(),
69 logical_type: field.logical_type.to_string(),
70 encoding: match field.encoding {
71 Some(Encoding::Plain) => 1,
72 Some(Encoding::VarBinary) => 2,
73 Some(Encoding::Dictionary) => 3,
74 Some(Encoding::RLE) => 4,
75 _ => 0,
76 },
77 nullable: field.nullable,
78 dictionary: field.dictionary.as_ref().map(pb::Dictionary::from),
79 metadata: pb_metadata,
80 extension_name: field
81 .extension_name()
82 .map(|name| name.to_owned())
83 .unwrap_or_default(),
84 r#type: 0,
85 unenforced_primary_key: field.unenforced_primary_key_position.is_some(),
86 unenforced_primary_key_position: field.unenforced_primary_key_position.unwrap_or(0),
87 unenforced_clustering_key: false,
88 unenforced_clustering_key_position: field
89 .unenforced_clustering_key_position
90 .unwrap_or(0),
91 }
92 }
93}
94
95pub struct Fields(pub Vec<pb::Field>);
96
97struct FieldNode {
98 field: Field,
99 child_indices: Vec<usize>,
100}
101
102fn first_field_index_by_id(
105 nodes: &[FieldNode],
106 root_indices: &[usize],
107 field_id: i32,
108) -> Option<usize> {
109 let mut to_visit = Vec::with_capacity(nodes.len());
110 to_visit.extend(root_indices.iter().rev().copied());
111
112 while let Some(node_index) = to_visit.pop() {
113 let node = &nodes[node_index];
114 if node.field.id == field_id {
115 return Some(node_index);
116 }
117 to_visit.extend(node.child_indices.iter().rev().copied());
118 }
119
120 None
121}
122
123impl From<&Field> for Fields {
124 fn from(field: &Field) -> Self {
125 let mut protos = vec![pb::Field::from(field)];
126 protos.extend(field.children.iter().flat_map(|val| Self::from(val).0));
127 Self(protos)
128 }
129}
130
131impl TryFrom<&Fields> for Schema {
158 type Error = Error;
159
160 fn try_from(fields: &Fields) -> Result<Self> {
161 let mut nodes: Vec<FieldNode> = Vec::with_capacity(fields.0.len());
162 let mut root_indices = Vec::with_capacity(fields.0.len());
163 let mut field_indices: HashMap<i32, Option<usize>> = HashMap::with_capacity(fields.0.len());
164
165 for proto_field in &fields.0 {
166 let parent_index = if proto_field.parent_id == -1 {
167 None
168 } else {
169 let parent_index = match field_indices.get(&proto_field.parent_id) {
170 Some(Some(parent_index)) => *parent_index,
171 Some(None) => {
172 first_field_index_by_id(&nodes, &root_indices, proto_field.parent_id)
177 .ok_or_else(|| {
178 Error::internal(format!(
179 "Duplicate field id {} has no existing arena node",
180 proto_field.parent_id
181 ))
182 })?
183 }
184 None => {
185 return Err(Error::schema(format!(
186 "Field '{}' (id={}) references parent id {}, which must appear earlier in the protobuf field list",
187 proto_field.name, proto_field.id, proto_field.parent_id
188 )));
189 }
190 };
191 Some(parent_index)
192 };
193
194 let node_index = nodes.len();
195 if let Some(parent_index) = parent_index {
196 nodes[parent_index].child_indices.push(node_index);
197 } else {
198 root_indices.push(node_index);
199 }
200 nodes.push(FieldNode {
201 field: Field::from(proto_field),
202 child_indices: Vec::new(),
203 });
204
205 field_indices
206 .entry(proto_field.id)
207 .and_modify(|field_index| *field_index = None)
208 .or_insert(Some(node_index));
209 }
210
211 let mut fields_by_node = Vec::with_capacity(nodes.len());
212 fields_by_node.resize_with(nodes.len(), || None);
213 for (node_index, mut node) in nodes.into_iter().enumerate().rev() {
214 node.field.children.reserve(node.child_indices.len());
215 for child_index in node.child_indices {
216 let child = fields_by_node
217 .get_mut(child_index)
218 .and_then(Option::take)
219 .ok_or_else(|| {
220 Error::internal(format!(
221 "Schema field arena node {child_index} was not materialized before its parent"
222 ))
223 })?;
224 node.field.children.push(child);
225 }
226 fields_by_node[node_index] = Some(node.field);
227 }
228
229 let fields = root_indices
230 .into_iter()
231 .map(|root_index| {
232 fields_by_node[root_index].take().ok_or_else(|| {
233 Error::internal(format!(
234 "Schema field arena root node {root_index} was not materialized"
235 ))
236 })
237 })
238 .collect::<Result<Vec<_>>>()?;
239
240 Ok(Self {
241 fields,
242 metadata: HashMap::default(),
243 })
244 }
245}
246
247pub struct FieldsWithMeta {
248 pub fields: Fields,
249 pub metadata: HashMap<String, Vec<u8>>,
250}
251
252impl TryFrom<FieldsWithMeta> for Schema {
271 type Error = Error;
272
273 fn try_from(fields_with_meta: FieldsWithMeta) -> Result<Self> {
274 let lance_metadata = fields_with_meta
275 .metadata
276 .into_iter()
277 .map(|(key, value)| {
278 let string_value = String::from_utf8_lossy(&value).to_string();
279 (key, string_value)
280 })
281 .collect();
282
283 let schema_with_fields = Self::try_from(&fields_with_meta.fields)?;
284 Ok(Self {
285 fields: schema_with_fields.fields,
286 metadata: lance_metadata,
287 })
288 }
289}
290
291impl From<&Schema> for Fields {
293 fn from(schema: &Schema) -> Self {
294 let mut protos = vec![];
295 schema.fields.iter().for_each(|f| {
296 protos.extend(Self::from(f).0);
297 });
298 Self(protos)
299 }
300}
301
302impl From<&Schema> for FieldsWithMeta {
304 fn from(schema: &Schema) -> Self {
305 let fields = schema.into();
306 let metadata = schema
307 .metadata
308 .clone()
309 .into_iter()
310 .map(|(key, value)| (key, value.into_bytes()))
311 .collect();
312 Self { fields, metadata }
313 }
314}
315
316impl From<&pb::Dictionary> for Dictionary {
317 fn from(proto: &pb::Dictionary) -> Self {
318 Self {
319 offset: proto.offset as usize,
320 length: proto.length as usize,
321 values: None,
322 }
323 }
324}
325
326impl From<&Dictionary> for pb::Dictionary {
327 fn from(d: &Dictionary) -> Self {
328 Self {
329 offset: d.offset as i64,
330 length: d.length as i64,
331 }
332 }
333}
334
335impl From<Encoding> for pb::Encoding {
336 fn from(e: Encoding) -> Self {
337 match e {
338 Encoding::Plain => Self::Plain,
339 Encoding::VarBinary => Self::VarBinary,
340 Encoding::Dictionary => Self::Dictionary,
341 Encoding::RLE => Self::Rle,
342 }
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use std::collections::HashMap;
349
350 use arrow_schema::DataType;
351 use arrow_schema::Field as ArrowField;
352 use arrow_schema::Fields as ArrowFields;
353 use arrow_schema::Schema as ArrowSchema;
354 use lance_core::Error;
355 use lance_core::datatypes::Schema;
356
357 use super::{Fields, FieldsWithMeta};
358 use crate::format::pb;
359
360 fn proto_field(id: i32, parent_id: i32, name: String, logical_type: &str) -> pb::Field {
361 pb::Field {
362 id,
363 parent_id,
364 name,
365 logical_type: logical_type.to_owned(),
366 ..Default::default()
367 }
368 }
369
370 #[test]
371 fn test_schema_set_ids() {
372 let arrow_schema = ArrowSchema::new(vec![
373 ArrowField::new("a", DataType::Int32, false),
374 ArrowField::new(
375 "b",
376 DataType::Struct(ArrowFields::from(vec![
377 ArrowField::new("f1", DataType::Utf8, true),
378 ArrowField::new("f2", DataType::Boolean, false),
379 ArrowField::new("f3", DataType::Float32, false),
380 ])),
381 true,
382 ),
383 ArrowField::new("c", DataType::Float64, false),
384 ]);
385 let schema = Schema::try_from(&arrow_schema).unwrap();
386
387 let protos: Fields = (&schema).into();
388 assert_eq!(
389 protos.0.iter().map(|p| p.id).collect::<Vec<_>>(),
390 (0..6).collect::<Vec<_>>()
391 );
392 }
393
394 #[test]
395 fn test_schema_metadata() {
396 let mut metadata: HashMap<String, String> = HashMap::new();
397 metadata.insert(String::from("k1"), String::from("v1"));
398 metadata.insert(String::from("k2"), String::from("v2"));
399
400 let arrow_schema = ArrowSchema::new_with_metadata(
401 vec![ArrowField::new("a", DataType::Int32, false)],
402 metadata,
403 );
404
405 let expected_schema = Schema::try_from(&arrow_schema).unwrap();
406 let fields_with_meta: FieldsWithMeta = (&expected_schema).into();
407
408 let schema = Schema::try_from(fields_with_meta).unwrap();
409 assert_eq!(expected_schema, schema);
410 }
411
412 #[test]
413 fn test_reconstruct_wide_nested_schema() {
414 const NUM_STRUCTS: usize = 4096;
415
416 let mut proto_fields = Vec::with_capacity(NUM_STRUCTS * 3);
417 for struct_index in 0..NUM_STRUCTS {
418 let parent_id = (struct_index * 3) as i32;
419 proto_fields.push(proto_field(
420 parent_id,
421 -1,
422 format!("struct_{struct_index}"),
423 "struct",
424 ));
425 proto_fields.push(proto_field(
426 parent_id + 1,
427 parent_id,
428 format!("left_{struct_index}"),
429 "int32",
430 ));
431 proto_fields.push(proto_field(
432 parent_id + 2,
433 parent_id,
434 format!("right_{struct_index}"),
435 "int32",
436 ));
437 }
438
439 let fields = Fields(proto_fields);
440 let schema = Schema::try_from(&fields).unwrap();
441 assert_eq!(schema.fields.len(), NUM_STRUCTS);
442 for (struct_index, field) in schema.fields.iter().enumerate() {
443 let parent_id = (struct_index * 3) as i32;
444 assert_eq!(field.id, parent_id);
445 assert_eq!(field.name, format!("struct_{struct_index}"));
446 assert_eq!(field.children.len(), 2);
447 assert_eq!(field.children[0].id, parent_id + 1);
448 assert_eq!(field.children[0].name, format!("left_{struct_index}"));
449 assert_eq!(field.children[1].id, parent_id + 2);
450 assert_eq!(field.children[1].name, format!("right_{struct_index}"));
451 }
452 }
453
454 #[test]
455 fn test_reconstruct_deep_nested_schema() {
456 const DEPTH: usize = 1024;
457
458 let proto_fields = (0..DEPTH)
459 .map(|depth| {
460 proto_field(
461 depth as i32,
462 if depth == 0 { -1 } else { depth as i32 - 1 },
463 format!("level_{depth}"),
464 if depth + 1 == DEPTH {
465 "int32"
466 } else {
467 "struct"
468 },
469 )
470 })
471 .collect();
472
473 let fields = Fields(proto_fields);
474 let schema = Schema::try_from(&fields).unwrap();
475 assert_eq!(schema.fields.len(), 1);
476 let mut field = &schema.fields[0];
477 for depth in 0..DEPTH {
478 assert_eq!(field.id, depth as i32);
479 assert_eq!(field.name, format!("level_{depth}"));
480 if depth + 1 == DEPTH {
481 assert!(field.children.is_empty());
482 } else {
483 assert_eq!(field.children.len(), 1);
484 field = &field.children[0];
485 }
486 }
487 }
488
489 #[test]
490 fn test_reconstruct_schema_reports_missing_parent() {
491 let fields = Fields(vec![proto_field(7, 42, "child".to_owned(), "int32")]);
492
493 let error = Schema::try_from(&fields).unwrap_err();
494 assert!(matches!(&error, Error::Schema { .. }));
495 assert!(
496 error.to_string().contains(
497 "Field 'child' (id=7) references parent id 42, which must appear earlier"
498 )
499 );
500 }
501
502 #[test]
503 fn test_reconstruct_schema_preserves_legacy_duplicate_id_match() {
504 let fields = Fields(vec![
505 proto_field(1, -1, "root_a".to_owned(), "struct"),
506 proto_field(2, -1, "root_b".to_owned(), "struct"),
507 proto_field(2, 1, "nested_duplicate".to_owned(), "struct"),
508 proto_field(3, 2, "child".to_owned(), "int32"),
509 ]);
510
511 let schema = Schema::try_from(&fields).unwrap();
512 assert_eq!(schema.fields.len(), 2);
513 assert_eq!(schema.fields[0].name, "root_a");
514 assert_eq!(schema.fields[0].children.len(), 1);
515 assert_eq!(schema.fields[0].children[0].name, "nested_duplicate");
516 assert_eq!(schema.fields[0].children[0].children.len(), 1);
517 assert_eq!(schema.fields[0].children[0].children[0].name, "child");
518 assert_eq!(schema.fields[1].name, "root_b");
519 assert!(schema.fields[1].children.is_empty());
520 }
521
522 #[test]
523 fn test_clustering_key_roundtrip() {
524 let arrow_schema = ArrowSchema::new(vec![
525 ArrowField::new("region", DataType::Utf8, true).with_metadata(
526 vec![(
527 "lance-schema:unenforced-clustering-key:position".to_owned(),
528 "1".to_owned(),
529 )]
530 .into_iter()
531 .collect::<HashMap<_, _>>(),
532 ),
533 ArrowField::new("date", DataType::Int32, false).with_metadata(
534 vec![(
535 "lance-schema:unenforced-clustering-key:position".to_owned(),
536 "2".to_owned(),
537 )]
538 .into_iter()
539 .collect::<HashMap<_, _>>(),
540 ),
541 ArrowField::new("value", DataType::Float64, true),
542 ]);
543
544 let schema = Schema::try_from(&arrow_schema).unwrap();
545 let ck = schema.unenforced_clustering_key();
546 assert_eq!(ck.len(), 2);
547 assert_eq!(ck[0].name, "region");
548 assert_eq!(ck[1].name, "date");
549
550 let fields_with_meta: FieldsWithMeta = (&schema).into();
552 let restored = Schema::try_from(fields_with_meta).unwrap();
553
554 let ck2 = restored.unenforced_clustering_key();
555 assert_eq!(ck2.len(), 2);
556 assert_eq!(ck2[0].name, "region");
557 assert_eq!(ck2[1].name, "date");
558 assert_eq!(ck2[0].unenforced_clustering_key_position, Some(1));
559 assert_eq!(ck2[1].unenforced_clustering_key_position, Some(2));
560
561 let value_field = restored.field("value").unwrap();
563 assert!(!value_field.is_unenforced_clustering_key());
564 }
565}