This is an automated email from the ASF dual-hosted git repository.
mgrigorov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 8397883 fix: Calculate the RecordSchema's `lookup` table on build
(#411)
8397883 is described below
commit 839788386ae458241947c1b47b7c97809232190b
Author: Martin Grigorov <[email protected]>
AuthorDate: Mon Jan 19 13:43:45 2026 +0200
fix: Calculate the RecordSchema's `lookup` table on build (#411)
* fix: Calculate the RecordSchema's `lookup` table on build
Fixes #403
* Add a unit test for RecordSchema's custom attributes
* Allow to build a RecordSchema without any fields
Co-authored-by: Kriskras99 <[email protected]>
* Do not call `.fields(vec![])`, since it is the default anyway
---------
Co-authored-by: Kriskras99 <[email protected]>
---
avro/src/schema/mod.rs | 3 -
avro/src/schema/record/schema.rs | 135 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 134 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..e02a23e 100644
--- a/avro/src/schema/record/schema.rs
+++ b/avro/src/schema/record/schema.rs
@@ -31,15 +31,26 @@ pub struct RecordSchema {
#[builder(default)]
pub doc: Documentation,
/// The set of fields of the schema
+ #[builder(default)]
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)]
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 +60,125 @@ 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()).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())
+ .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())
+ .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_no_fields_with_attributes() ->
TestResult {
+ let name = Name::new("TestRecord")?;
+ let attrs: BTreeMap<String, Value> = [
+ ("bool_key".into(), Value::Bool(true)),
+ ("key_2".into(), Value::String("value_2".into())),
+ ]
+ .into_iter()
+ .collect();
+
+ let record_schema = RecordSchema::builder()
+ .name(name.clone())
+ .attributes(attrs.clone())
+ .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, attrs);
+
+ 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(())
+ }
+}