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.git


The following commit(s) were added to refs/heads/main by this push:
     new f350a8fbd AVRO-3984 [C++] Improve code generated for unions (#3047)
f350a8fbd is described below

commit f350a8fbdd2e1f6797e46900fe905292ae55d7c9
Author: hwse <[email protected]>
AuthorDate: Wed Aug 21 08:43:13 2024 +0200

    AVRO-3984 [C++] Improve code generated for unions (#3047)
    
    * AVRO-3984 [C++] Getters created by avrogencpp return a reference instead 
of a value to avoid calling copy constructor of large classes
    
    * AVRO-3984 [C++] Add getter for generated unions that returns a mutable 
reference. This allows the user to modify values in union branches after 
creation (#3047)
    
    * AVRO-3984 [C++] Add move setters for generated unions to provide a more 
efficient way to set a value (#3047)
    
    * AVRO-3984 [C++] Use std::move in decode implementation of codec_traits 
for unions to avoid a copy (#3047)
    
    * AVRO-3984 [C++] Generate an enum for each union type that maps the branch 
names to the corresponding index. This allows the user to avoid checks against 
"magic numbers" (#3047)
    
    * AVRO-3984 [C++] Add additional checks for the union branch in 
testUnionMethods test (#3047)
    
    * AVRO-3984 [C++] Add additional branch() method that returns the Branch 
enum directly, this avoids a manual static_cast (#3047)
    
    ---------
    
    Co-authored-by: hwse <[email protected]>
---
 lang/c++/CMakeLists.txt          |   3 +-
 lang/c++/impl/avrogencpp.cc      |  60 ++++++++++++++++++---
 lang/c++/jsonschemas/big_union   | 101 +++++++++++++++++++++++++++++++++++
 lang/c++/test/AvrogencppTests.cc | 110 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 265 insertions(+), 9 deletions(-)

diff --git a/lang/c++/CMakeLists.txt b/lang/c++/CMakeLists.txt
index 1b234e041..d4c494cef 100644
--- a/lang/c++/CMakeLists.txt
+++ b/lang/c++/CMakeLists.txt
@@ -181,6 +181,7 @@ gen (crossref cr)
 gen (primitivetypes pt)
 gen (cpp_reserved_words cppres)
 gen (cpp_reserved_words_union_typedef cppres_union)
+gen (big_union big_union)
 
 add_executable (avrogencpp impl/avrogencpp.cc)
 target_link_libraries (avrogencpp avrocpp_s)
@@ -226,7 +227,7 @@ add_dependencies (AvrogencppTests bigrecord_hh 
bigrecord_r_hh bigrecord2_hh
     union_array_union_hh union_map_union_hh union_conflict_hh
     recursive_hh reuse_hh circulardep_hh tree1_hh tree2_hh crossref_hh
     primitivetypes_hh empty_record_hh cpp_reserved_words_union_typedef_hh
-    union_empty_record_hh)
+    union_empty_record_hh big_union_hh)
 
 include (InstallRequiredSystemLibraries)
 
diff --git a/lang/c++/impl/avrogencpp.cc b/lang/c++/impl/avrogencpp.cc
index 39da7af35..c02ea554e 100644
--- a/lang/c++/impl/avrogencpp.cc
+++ b/lang/c++/impl/avrogencpp.cc
@@ -313,12 +313,21 @@ static void generateGetterAndSetter(ostream &os,
 
     os << "inline\n";
 
-    os << type << sn << "get_" << name << "() const {\n"
+    os << "const " << type << "&" << sn << "get_" << name << "() const {\n"
        << "    if (idx_ != " << idx << ") {\n"
        << "        throw avro::Exception(\"Invalid type for "
        << "union " << structName << "\");\n"
        << "    }\n"
-       << "    return std::any_cast<" << type << " >(value_);\n"
+       << "    return *std::any_cast<" << type << " >(&value_);\n"
+       << "}\n\n";
+
+    os << "inline\n"
+       << type << "&" << sn << "get_" << name << "() {\n"
+       << "    if (idx_ != " << idx << ") {\n"
+       << "        throw avro::Exception(\"Invalid type for "
+       << "union " << structName << "\");\n"
+       << "    }\n"
+       << "    return *std::any_cast<" << type << " >(&value_);\n"
        << "}\n\n";
 
     os << "inline\n"
@@ -327,6 +336,13 @@ static void generateGetterAndSetter(ostream &os,
        << "    idx_ = " << idx << ";\n"
        << "    value_ = v;\n"
        << "}\n\n";
+
+    os << "inline\n"
+       << "void" << sn << "set_" << name
+       << "(" << type << "&& v) {\n"
+       << "    idx_ = " << idx << ";\n"
+       << "    value_ = std::move(v);\n"
+       << "}\n\n";
 }
 
 static void generateConstructor(ostream &os,
@@ -376,8 +392,33 @@ string CodeGen::generateUnionType(const NodePtr &n) {
         << "private:\n"
         << "    size_t idx_;\n"
         << "    std::any value_;\n"
-        << "public:\n"
-        << "    size_t idx() const { return idx_; }\n";
+        << "public:\n";
+
+    os_ << "    /** enum representing union branches as returned by the idx() 
function */\n"
+        << "    enum class Branch: size_t {\n";
+
+    // generate a enum that maps the branch name to the corresponding index 
(as returned by idx())
+    std::set<std::string> used_branch_names;
+    for (size_t i = 0; i < c; ++i) {
+        // escape reserved literals for c++
+        auto branch_name = decorate(names[i]);
+        // avoid rare collisions, e.g. somone might name their struct int_
+        if (used_branch_names.find(branch_name) != used_branch_names.end()) {
+            size_t postfix = 2;
+            std::string escaped_name = branch_name + "_" + 
std::to_string(postfix);
+            while (used_branch_names.find(escaped_name) != 
used_branch_names.end()) {
+                ++postfix;
+                escaped_name = branch_name + "_" + std::to_string(postfix);
+            }
+            branch_name = escaped_name;
+        }
+        os_ << "        " << branch_name << " = " << i << ",\n";
+        used_branch_names.insert(branch_name);
+    }
+    os_ << "    };\n";
+
+    os_ << "    size_t idx() const { return idx_; }\n";
+    os_ << "    Branch branch() const { return static_cast<Branch>(idx_); }\n";
 
     for (size_t i = 0; i < c; ++i) {
         const NodePtr &nn = n->leafAt(i);
@@ -392,9 +433,11 @@ string CodeGen::generateUnionType(const NodePtr &n) {
         } else {
             const string &type = types[i];
             const string &name = names[i];
-            os_ << "    " << type << " get_" << name << "() const;\n"
-                                                        "    void set_"
-                << name << "(const " << type << "& v);\n";
+            os_ << "    "
+                << "const " << type << "& get_" << name << "() const;\n"
+                << "    " << type << "& get_" << name << "();\n"
+                << "    void set_" << name << "(const " << type << "& v);\n"
+                << "    void set_" << name << "(" << type << "&& v);\n";
             pendingGettersAndSetters.emplace_back(result, type, name, i);
         }
     }
@@ -645,7 +688,7 @@ void CodeGen::generateUnionTraits(const NodePtr &n) {
             os_ << "            {\n"
                 << "                " << cppTypeOf(nn) << " vv;\n"
                 << "                avro::decode(d, vv);\n"
-                << "                v.set_" << cppNameOf(nn) << "(vv);\n"
+                << "                v.set_" << cppNameOf(nn) << 
"(std::move(vv));\n"
                 << "            }\n";
         }
         os_ << "            break;\n";
@@ -730,6 +773,7 @@ void CodeGen::generate(const ValidSchema &schema) {
 
     os_ << "#include <sstream>\n"
         << "#include <any>\n"
+        << "#include <utility>\n"
         << "#include \"" << includePrefix_ << "Specific.hh\"\n"
         << "#include \"" << includePrefix_ << "Encoder.hh\"\n"
         << "#include \"" << includePrefix_ << "Decoder.hh\"\n"
diff --git a/lang/c++/jsonschemas/big_union b/lang/c++/jsonschemas/big_union
new file mode 100644
index 000000000..34cced449
--- /dev/null
+++ b/lang/c++/jsonschemas/big_union
@@ -0,0 +1,101 @@
+{
+    "type": "record",
+    "doc": "Top level Doc.",
+    "name": "RootRecord",
+    "fields": [
+        {
+            "name": "big_union",
+            "doc": "A large union containing the primitive types, a array, a 
map and records.",
+            "type": [
+                "null",
+                "boolean",
+                "int",
+                "long",
+                "float",
+                "double",
+                {
+                    "type": "fixed",
+                    "size": 16,
+                    "name": "MD5"
+                },
+                "string",
+                {
+                    "type": "record",
+                    "name": "Vec2",
+                    "fields": [
+                        {
+                            "name": "x",
+                            "type": "long"
+                        },
+                        {
+                            "name": "y",
+                            "type": "long"
+                        }
+                    ]
+                },
+                {
+                    "type": "record",
+                    "name": "Vec3",
+                    "fields": [
+                        {
+                            "name": "x",
+                            "type": "long"
+                        },
+                        {
+                            "name": "y",
+                            "type": "long"
+                        },
+                        {
+                            "name": "z",
+                            "type": "long"
+                        }
+                    ]
+                },
+                {
+                    "type": "enum",
+                    "name": "Suit",
+                    "symbols": [
+                        "SPADES",
+                        "HEARTS",
+                        "DIAMONDS",
+                        "CLUBS"
+                    ]
+                },
+                {
+                    "type": "array",
+                    "items": "string",
+                    "default": []
+                },
+                {
+                    "type": "map",
+                    "values": "long",
+                    "default": {}
+                },
+                {
+                    "type": "record",
+                    "name": "int_",
+                    "doc": "try to force a collision with int",
+                    "fields": []
+                },
+                {
+                    "type": "record",
+                    "name": "int__",
+                    "doc": "try to force a collision with int",
+                    "fields": []
+                },
+                {
+                    "type": "record",
+                    "name": "Int",
+                    "doc": "name similar to primitive name",
+                    "fields": []
+                },
+                {
+                    "type": "record",
+                    "name": "_Int",
+                    "doc": "name with underscore as prefix",
+                    "fields": []
+                }
+            ]
+        }
+    ]
+}
diff --git a/lang/c++/test/AvrogencppTests.cc b/lang/c++/test/AvrogencppTests.cc
index d393e373d..e7d5df927 100644
--- a/lang/c++/test/AvrogencppTests.cc
+++ b/lang/c++/test/AvrogencppTests.cc
@@ -17,6 +17,7 @@
  */
 
 #include "Compiler.hh"
+#include "big_union.hh"
 #include "bigrecord.hh"
 #include "bigrecord_r.hh"
 #include "tweet.hh"
@@ -132,6 +133,14 @@ void checkDefaultValues(const testgen_r::RootRecord &r) {
     BOOST_CHECK_EQUAL(r.byteswithDefaultValue.get_bytes()[1], 0xaa);
 }
 
+// enable use of BOOST_CHECK_EQUAL
+template<>
+struct 
boost::test_tools::tt_detail::print_log_value<big_union::RootRecord::big_union_t::Branch>
 {
+    void operator()(std::ostream &stream, const 
big_union::RootRecord::big_union_t::Branch &branch) const {
+        stream << "big_union_t::Branch{" << static_cast<size_t>(branch) << "}";
+    }
+};
+
 void testEncoding() {
     ValidSchema s;
     ifstream ifs("jsonschemas/bigrecord");
@@ -300,6 +309,105 @@ void testEmptyRecord() {
     BOOST_CHECK_EQUAL(calc2.stack[2].idx(), 2);
 }
 
+void testUnionMethods() {
+    ValidSchema schema;
+    ifstream ifs_w("jsonschemas/bigrecord");
+    compileJsonSchema(ifs_w, schema);
+
+    testgen::RootRecord record;
+    // initialize the map and set values with getter
+    record.myunion.set_map({});
+    record.myunion.get_map()["zero"] = 0;
+    record.myunion.get_map()["one"] = 1;
+
+    std::vector<uint8_t> bytes{1, 2, 3, 4};
+    record.anotherunion.set_bytes(std::move(bytes));
+    // after move assignment the local variable should be empty
+    BOOST_CHECK(bytes.empty());
+
+    unique_ptr<OutputStream> out_stream = memoryOutputStream();
+    EncoderPtr encoder = validatingEncoder(schema, binaryEncoder());
+    encoder->init(*out_stream);
+    avro::encode(*encoder, record);
+    encoder->flush();
+
+    DecoderPtr decoder = validatingDecoder(schema, binaryDecoder());
+    unique_ptr<InputStream> is = memoryInputStream(*out_stream);
+    decoder->init(*is);
+    testgen::RootRecord decoded_record;
+    avro::decode(*decoder, decoded_record);
+
+    // check that a reference can be obtained from a union
+    BOOST_CHECK(decoded_record.myunion.branch() == 
testgen::RootRecord::myunion_t::Branch::map);
+    const std::map<std::string, int32_t> &read_map = 
decoded_record.myunion.get_map();
+    BOOST_CHECK_EQUAL(read_map.size(), 2);
+    BOOST_CHECK_EQUAL(read_map.at("zero"), 0);
+    BOOST_CHECK_EQUAL(read_map.at("one"), 1);
+
+    BOOST_CHECK(decoded_record.anotherunion.branch() == 
testgen::RootRecord::anotherunion_t::Branch::bytes);
+    const std::vector<uint8_t> read_bytes = 
decoded_record.anotherunion.get_bytes();
+    const std::vector<uint8_t> expected_bytes{1, 2, 3, 4};
+    BOOST_CHECK_EQUAL_COLLECTIONS(read_bytes.begin(), read_bytes.end(), 
expected_bytes.begin(), expected_bytes.end());
+}
+
+void testUnionBranchEnum() {
+    big_union::RootRecord record;
+
+    using Branch = big_union::RootRecord::big_union_t::Branch;
+
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::null);
+    record.big_union.set_null();
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::null);
+
+    record.big_union.set_bool(false);
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::bool_);
+
+    record.big_union.set_int(123);
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::int_);
+
+    record.big_union.set_long(456);
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::long_);
+
+    record.big_union.set_float(555.555f);
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::float_);
+
+    record.big_union.set_double(777.777);
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::double_);
+
+    record.big_union.set_MD5({});
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::MD5);
+
+    record.big_union.set_string("test");
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::string);
+
+    record.big_union.set_Vec2({});
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::Vec2);
+
+    record.big_union.set_Vec3({});
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::Vec3);
+
+    record.big_union.set_Suit(big_union::Suit::CLUBS);
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::Suit);
+
+    record.big_union.set_array({});
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::array);
+
+    record.big_union.set_map({});
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::map);
+
+    record.big_union.set_int_({});
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::int__2);
+
+    record.big_union.set_int__({});
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::int__);
+
+    record.big_union.set_Int({});
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::Int);
+
+    record.big_union.set__Int({});
+    BOOST_CHECK_EQUAL(record.big_union.branch(), Branch::_Int);
+}
+
 boost::unit_test::test_suite *init_unit_test_suite(int /*argc*/, char * 
/*argv*/[]) {
     auto *ts = BOOST_TEST_SUITE("Code generator tests");
     ts->add(BOOST_TEST_CASE(testEncoding));
@@ -308,5 +416,7 @@ boost::unit_test::test_suite *init_unit_test_suite(int 
/*argc*/, char * /*argv*/
     ts->add(BOOST_TEST_CASE(testEncoding2<umu::r1>));
     ts->add(BOOST_TEST_CASE(testNamespace));
     ts->add(BOOST_TEST_CASE(testEmptyRecord));
+    ts->add(BOOST_TEST_CASE(testUnionMethods));
+    ts->add(BOOST_TEST_CASE(testUnionBranchEnum));
     return ts;
 }

Reply via email to