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(())
+    }
+}

Reply via email to