This is an automated email from the ASF dual-hosted git repository.
thiru pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro.git
The following commit(s) were added to refs/heads/main by this push:
new 7b106b12a AVRO-4121: [C++] Fix get CustomAttributes from Node (#3332)
7b106b12a is described below
commit 7b106b12ae22853c977259710d92a237d76f2236
Author: Gang Wu <[email protected]>
AuthorDate: Tue Mar 18 01:33:36 2025 +0800
AVRO-4121: [C++] Fix get CustomAttributes from Node (#3332)
* AVRO-4121: [C++] Fix get CustomAttributes from Node
* add bound check and more test
---
lang/c++/include/avro/Node.hh | 3 +-
lang/c++/include/avro/NodeImpl.hh | 17 ++--
lang/c++/test/SchemaTests.cc | 163 ++++++++++++++++++++++++++++++++++++++
lang/c++/test/unittest.cc | 11 ---
4 files changed, 174 insertions(+), 20 deletions(-)
diff --git a/lang/c++/include/avro/Node.hh b/lang/c++/include/avro/Node.hh
index ad01dddb4..7918c3302 100644
--- a/lang/c++/include/avro/Node.hh
+++ b/lang/c++/include/avro/Node.hh
@@ -170,7 +170,8 @@ public:
doAddCustomAttribute(customAttributes);
}
- virtual CustomAttributes getCustomAttributes() const = 0;
+ virtual size_t customAttributes() const = 0;
+ virtual const CustomAttributes &customAttributesAt(size_t index) const = 0;
virtual bool isValid() const = 0;
diff --git a/lang/c++/include/avro/NodeImpl.hh
b/lang/c++/include/avro/NodeImpl.hh
index 4a35d6fb1..2038c9d5c 100644
--- a/lang/c++/include/avro/NodeImpl.hh
+++ b/lang/c++/include/avro/NodeImpl.hh
@@ -164,15 +164,16 @@ protected:
customAttributes_.add(customAttributes);
}
- CustomAttributes getCustomAttributes() const override {
- CustomAttributes mergedCustomAttributes;
- for (size_t i = 0; i < customAttributes_.size(); i++) {
- const auto &customAttribute = customAttributes_.get(i);
- for (const auto &[key, value] : customAttribute.attributes()) {
- mergedCustomAttributes.addAttribute(key, value);
- }
+ size_t customAttributes() const override {
+ return customAttributes_.size();
+ }
+
+ const CustomAttributes &customAttributesAt(size_t index) const override {
+ if (index >= customAttributes_.size()) {
+ throw Exception("Custom attribute index {} is out of bounds for
size {}",
+ index, customAttributes_.size());
}
- return mergedCustomAttributes;
+ return customAttributes_.get(index);
}
SchemaResolution furtherResolution(const Node &reader) const {
diff --git a/lang/c++/test/SchemaTests.cc b/lang/c++/test/SchemaTests.cc
index 3593c6827..4830ef04c 100644
--- a/lang/c++/test/SchemaTests.cc
+++ b/lang/c++/test/SchemaTests.cc
@@ -18,6 +18,7 @@
#include "Compiler.hh"
#include "GenericDatum.hh"
+#include "NodeImpl.hh"
#include "ValidSchema.hh"
#include <boost/algorithm/string/replace.hpp>
@@ -695,6 +696,166 @@ static void testCustomLogicalType() {
verifyCustomLogicalType(parsedSchema);
}
+static void testParseCustomAttributes() {
+ const std::string schema = R"({
+ "type": "record",
+ "name": "my_record",
+ "fields": [
+ { "name": "long_field",
+ "type": ["null", "long"],
+ "field-id": 1 },
+ { "name": "array_field",
+ "type": { "type": "array", "items": "int", "element-id": 3 },
+ "field-id": 2,
+ "extra": "1", "extra2": "2" },
+ { "name": "map_field",
+ "type": { "type": "map", "values": "int", "key-id": 5,
"value-id": 6 },
+ "field-id": 4,
+ "extra": "foo" },
+ { "name": "timestamp_field",
+ "type": "long", "logicalType": "timestamp-micros",
"adjust-to-utc": true,
+ "field-id": 10,
+ "extra": "bar" },
+ { "name": "no_custom_attributes_field",
+ "type": "long" }
+ ]
+ })";
+
+ ValidSchema compiledSchema = compileJsonSchemaFromString(schema);
+ const NodePtr &root = compiledSchema.root();
+ BOOST_CHECK_EQUAL(root->customAttributes(), 5);
+
+ // long_field
+ {
+ auto customAttributes = root->customAttributesAt(0);
+ BOOST_CHECK_EQUAL(customAttributes.getAttribute("field-id").value(),
"1");
+ }
+
+ // array_field
+ {
+ auto customAttributes = root->customAttributesAt(1);
+ BOOST_CHECK_EQUAL(customAttributes.getAttribute("extra").value(), "1");
+ BOOST_CHECK_EQUAL(customAttributes.getAttribute("extra2").value(),
"2");
+ BOOST_CHECK_EQUAL(customAttributes.getAttribute("field-id").value(),
"2");
+
+ auto arrayField = root->leafAt(1);
+ BOOST_CHECK_EQUAL(arrayField->customAttributes(), 1);
+ auto arrayFieldCustomAttributes = arrayField->customAttributesAt(0);
+
BOOST_CHECK_EQUAL(arrayFieldCustomAttributes.getAttribute("element-id").value(),
"3");
+ }
+
+ // map_field
+ {
+ auto customAttributes = root->customAttributesAt(2);
+ BOOST_CHECK_EQUAL(customAttributes.getAttribute("field-id").value(),
"4");
+ BOOST_CHECK_EQUAL(customAttributes.getAttribute("extra").value(),
"foo");
+
+ auto mapField = root->leafAt(2);
+ BOOST_CHECK_EQUAL(mapField->customAttributes(), 1);
+ auto mapFieldCustomAttributes = mapField->customAttributesAt(0);
+
BOOST_CHECK_EQUAL(mapFieldCustomAttributes.getAttribute("key-id").value(), "5");
+
BOOST_CHECK_EQUAL(mapFieldCustomAttributes.getAttribute("value-id").value(),
"6");
+ }
+
+ // timestamp_field
+ {
+ auto customAttributes = root->customAttributesAt(3);
+ BOOST_CHECK_EQUAL(customAttributes.getAttribute("field-id").value(),
"10");
+ BOOST_CHECK_EQUAL(customAttributes.getAttribute("extra").value(),
"bar");
+
BOOST_CHECK_EQUAL(customAttributes.getAttribute("adjust-to-utc").value(),
"true");
+ }
+
+ // no_custom_attributes_field
+ {
+ auto customAttributes = root->customAttributesAt(4);
+ BOOST_CHECK_EQUAL(customAttributes.attributes().size(), 0);
+ }
+}
+
+static void testAddCustomAttributes() {
+ auto recordNode = std::make_shared<NodeRecord>();
+
+ // long_field
+ {
+ CustomAttributes customAttributes;
+ customAttributes.addAttribute("field-id", "1");
+ recordNode->addCustomAttributesForField(customAttributes);
+ recordNode->addLeaf(std::make_shared<NodePrimitive>(AVRO_LONG));
+ recordNode->addName("long_field");
+ }
+
+ // array_field
+ {
+ auto arrayField =
std::make_shared<NodeArray>(SingleLeaf(std::make_shared<NodePrimitive>(AVRO_INT)));
+ CustomAttributes elementCustomAttributes;
+ elementCustomAttributes.addAttribute("element-id", "3");
+ arrayField->addCustomAttributesForField(elementCustomAttributes);
+
+ CustomAttributes customAttributes;
+ customAttributes.addAttribute("field-id", "2");
+ customAttributes.addAttribute("extra", "1");
+ customAttributes.addAttribute("extra2", "2");
+ recordNode->addCustomAttributesForField(customAttributes);
+ recordNode->addLeaf(arrayField);
+ recordNode->addName("array_field");
+ }
+
+ // map_field
+ {
+ auto mapField =
std::make_shared<NodeMap>(SingleLeaf(std::make_shared<NodePrimitive>(AVRO_INT)));
+ CustomAttributes keyValueCustomAttributes;
+ keyValueCustomAttributes.addAttribute("key-id", "5");
+ keyValueCustomAttributes.addAttribute("value-id", "6");
+ mapField->addCustomAttributesForField(keyValueCustomAttributes);
+
+ CustomAttributes customAttributes;
+ customAttributes.addAttribute("field-id", "4");
+ customAttributes.addAttribute("extra", "foo");
+ recordNode->addCustomAttributesForField(customAttributes);
+ recordNode->addLeaf(mapField);
+ recordNode->addName("map_field");
+ }
+
+ // timestamp_field
+ {
+ auto timestampField = std::make_shared<NodePrimitive>(AVRO_LONG);
+ CustomAttributes customAttributes;
+ customAttributes.addAttribute("field-id", "10");
+ customAttributes.addAttribute("extra", "bar");
+ customAttributes.addAttribute("adjust-to-utc", "true");
+ recordNode->addCustomAttributesForField(customAttributes);
+ recordNode->addLeaf(timestampField);
+ recordNode->addName("timestamp_field");
+ }
+
+ const std::string expected = R"({
+ "type": "record",
+ "name": "",
+ "fields": [
+ { "name": "long_field",
+ "type": "long",
+ "field-id": "1" },
+ { "name": "array_field",
+ "type": { "type": "array", "items": "int", "element-id": "3" },
+ "extra": "1",
+ "extra2": "2",
+ "field-id": "2" },
+ { "name": "map_field",
+ "type": { "type": "map", "values": "int", "key-id": "5",
"value-id": "6" },
+ "extra": "foo",
+ "field-id": "4" },
+ { "name": "timestamp_field",
+ "type": "long",
+ "adjust-to-utc": "true",
+ "extra": "bar",
+ "field-id": "10" }
+ ]
+ })";
+ ValidSchema schema(recordNode);
+ std::string json = schema.toJson();
+ BOOST_CHECK_EQUAL(removeWhitespaceFromSchema(json),
removeWhitespaceFromSchema(expected));
+}
+
} // namespace schema
} // namespace avro
@@ -719,5 +880,7 @@ init_unit_test_suite(int /*argc*/, char * /*argv*/[]) {
avro::schema::malformedLogicalTypes);
ts->add(BOOST_TEST_CASE(&avro::schema::testCompactSchemas));
ts->add(BOOST_TEST_CASE(&avro::schema::testCustomLogicalType));
+ ts->add(BOOST_TEST_CASE(&avro::schema::testParseCustomAttributes));
+ ts->add(BOOST_TEST_CASE(&avro::schema::testAddCustomAttributes));
return ts;
}
diff --git a/lang/c++/test/unittest.cc b/lang/c++/test/unittest.cc
index feafa31a4..b0cb44c5b 100644
--- a/lang/c++/test/unittest.cc
+++ b/lang/c++/test/unittest.cc
@@ -1066,16 +1066,6 @@ void testNestedMapSchema() {
BOOST_CHECK_EQUAL(expected, actual.str());
}
-void testCustomAttributes() {
- std::string schema =
R"({"type":"array","items":"long","extra":"1","extra2":"2"})";
- avro::ValidSchema compiledSchema =
- compileJsonSchemaFromString(schema);
- auto customAttributes = compiledSchema.root()->getCustomAttributes();
- BOOST_CHECK_EQUAL(customAttributes.attributes().size(), 2);
- BOOST_CHECK_EQUAL(customAttributes.getAttribute("extra").value(), "1");
- BOOST_CHECK_EQUAL(customAttributes.getAttribute("extra2").value(), "2");
-}
-
boost::unit_test::test_suite *
init_unit_test_suite(int /*argc*/, char * /*argv*/[]) {
using namespace boost::unit_test;
@@ -1096,7 +1086,6 @@ init_unit_test_suite(int /*argc*/, char * /*argv*/[]) {
boost::make_shared<TestResolution>()));
test->add(BOOST_TEST_CASE(&testNestedArraySchema));
test->add(BOOST_TEST_CASE(&testNestedMapSchema));
- test->add(BOOST_TEST_CASE(&testCustomAttributes));
return test;
}