This is an automated email from the ASF dual-hosted git repository. mgrigorov pushed a commit to branch 403-recordschema-builder-improvements in repository https://gitbox.apache.org/repos/asf/avro-rs.git
commit 57dbc15e6664a8a0404b62c94ce733631ad0292b Author: Martin Tzvetanov Grigorov <[email protected]> AuthorDate: Mon Jan 19 12:31:34 2026 +0200 fix: Calculate the RecordSchema's `lookup` table on build Fixes #403 --- avro/src/schema/mod.rs | 3 -- avro/src/schema/record/schema.rs | 114 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs index 25319f7..bc7832b 100644 --- a/avro/src/schema/mod.rs +++ b/avro/src/schema/mod.rs @@ -2702,8 +2702,6 @@ mod tests { // AVRO-3248 #[test] fn test_union_of_records() -> TestResult { - use std::iter::FromIterator; - // A and B are the same except the name. let schema_str_a = r#"{ "name": "A", @@ -2751,7 +2749,6 @@ mod tests { ])?)) .build(), ]) - .lookup(BTreeMap::from_iter(vec![("field_one".to_string(), 0)])) .build(), ); diff --git a/avro/src/schema/record/schema.rs b/avro/src/schema/record/schema.rs index 28a30a6..be32e36 100644 --- a/avro/src/schema/record/schema.rs +++ b/avro/src/schema/record/schema.rs @@ -34,12 +34,22 @@ pub struct RecordSchema { pub fields: Vec<RecordField>, /// The `lookup` table maps field names to their position in the `Vec` /// of `fields`. + #[builder(skip = calculate_lookup_table(&fields))] pub lookup: BTreeMap<String, usize>, /// The custom attributes of the schema - #[builder(default = BTreeMap::new())] + #[builder(default = Default::default())] pub attributes: BTreeMap<String, Value>, } +/// Calculate the lookup table for the given fields. +fn calculate_lookup_table(fields: &[RecordField]) -> BTreeMap<String, usize> { + fields + .iter() + .enumerate() + .map(|(i, field)| (field.name.clone(), i)) + .collect() +} + #[derive(Debug, Default)] pub(crate) enum RecordSchemaParseLocation { /// When the parse is happening at root level @@ -49,3 +59,105 @@ pub(crate) enum RecordSchemaParseLocation { /// When the parse is happening inside a record field FromField, } + +#[cfg(test)] +mod tests { + use super::*; + use crate::Schema; + use apache_avro_test_helper::TestResult; + use pretty_assertions::assert_eq; + + #[test] + fn avro_rs_403_record_schema_builder_no_fields() -> TestResult { + let name = Name::new("TestRecord")?; + + let record_schema = RecordSchema::builder() + .name(name.clone()) + .fields(vec![]) + .build(); + + assert_eq!(record_schema.name, name); + assert_eq!(record_schema.aliases, None); + assert_eq!(record_schema.doc, None); + assert_eq!(record_schema.fields.len(), 0); + assert_eq!(record_schema.lookup.len(), 0); + assert_eq!(record_schema.attributes.len(), 0); + + Ok(()) + } + + #[test] + fn avro_rs_403_record_schema_builder_no_fields_with_aliases() -> TestResult { + let name = Name::new("TestRecord")?; + + let record_schema = RecordSchema::builder() + .name(name.clone()) + .fields(vec![]) + .aliases(Some(vec!["alias_1".into()])) + .build(); + + assert_eq!(record_schema.name, name); + assert_eq!(record_schema.aliases, Some(vec!["alias_1".into()])); + assert_eq!(record_schema.doc, None); + assert_eq!(record_schema.fields.len(), 0); + assert_eq!(record_schema.lookup.len(), 0); + assert_eq!(record_schema.attributes.len(), 0); + + Ok(()) + } + + #[test] + fn avro_rs_403_record_schema_builder_no_fields_with_doc() -> TestResult { + let name = Name::new("TestRecord")?; + + let record_schema = RecordSchema::builder() + .name(name.clone()) + .fields(vec![]) + .doc(Some("some_doc".into())) + .build(); + + assert_eq!(record_schema.name, name); + assert_eq!(record_schema.aliases, None); + assert_eq!(record_schema.doc, Some("some_doc".into())); + assert_eq!(record_schema.fields.len(), 0); + assert_eq!(record_schema.lookup.len(), 0); + assert_eq!(record_schema.attributes.len(), 0); + + Ok(()) + } + + #[test] + fn avro_rs_403_record_schema_builder_with_fields() -> TestResult { + let name = Name::new("TestRecord")?; + let fields = vec![ + RecordField::builder() + .name("field1_null".into()) + .schema(Schema::Null) + .build(), + RecordField::builder() + .name("field2_bool".into()) + .schema(Schema::Boolean) + .build(), + ]; + + let record_schema = RecordSchema::builder() + .name(name.clone()) + .fields(fields.clone()) + .build(); + + let expected_lookup: BTreeMap<String, usize> = + [("field1_null".into(), 0), ("field2_bool".into(), 1)] + .iter() + .cloned() + .collect(); + + assert_eq!(record_schema.name, name); + assert_eq!(record_schema.aliases, None); + assert_eq!(record_schema.doc, None); + assert_eq!(record_schema.fields, fields); + assert_eq!(record_schema.lookup, expected_lookup); + assert_eq!(record_schema.attributes.len(), 0); + + Ok(()) + } +}
