From a4624984bf699446952b6ab5dbc8e1d90c6bed0f Mon Sep 17 00:00:00 2001 From: Morgan Parry Date: Thu, 22 Feb 2024 13:16:32 +0000 Subject: [PATCH] AVRO-2397: [c++] Add support for type and field aliases (#2270) * Add support for type and field aliases * AVRO-2397: Improve code style consistency --------- Co-authored-by: Martin Grigorov --- lang/c++/api/Node.hh | 22 ++++-- lang/c++/api/NodeImpl.hh | 48 ++++--------- lang/c++/impl/Compiler.cc | 46 ++++++++---- lang/c++/impl/Node.cc | 62 +++++++++++++++- lang/c++/impl/NodeImpl.cc | 87 +++++++++++++++++++---- lang/c++/impl/parsing/ResolvingDecoder.cc | 79 ++++++++------------ lang/c++/test/CodecTests.cc | 40 +++++++++++ lang/c++/test/unittest.cc | 3 +- 8 files changed, 264 insertions(+), 123 deletions(-) diff --git a/lang/c++/api/Node.hh b/lang/c++/api/Node.hh index 3f5fe5b05da..0b8f2269cd1 100644 --- a/lang/c++/api/Node.hh +++ b/lang/c++/api/Node.hh @@ -40,30 +40,38 @@ class GenericDatum; using NodePtr = std::shared_ptr; class AVRO_DECL Name { + struct Aliases; + std::string ns_; std::string simpleName_; + std::unique_ptr aliases_; public: - Name() = default; - explicit Name(const std::string &fullname); - Name(std::string simpleName, std::string ns) : ns_(std::move(ns)), simpleName_(std::move(simpleName)) { check(); } + Name(); + explicit Name(const std::string &name); + Name(std::string simpleName, std::string ns); + Name(const Name& other); + Name& operator=(const Name& other); + Name(Name&& other); + Name& operator=(Name&& other); + ~Name(); std::string fullname() const; const std::string &ns() const { return ns_; } const std::string &simpleName() const { return simpleName_; } + const std::vector &aliases() const; void ns(std::string n) { ns_ = std::move(n); } void simpleName(std::string n) { simpleName_ = std::move(n); } void fullname(const std::string &n); + void addAlias(const std::string &alias); bool operator<(const Name &n) const; void check() const; bool operator==(const Name &n) const; bool operator!=(const Name &n) const { return !((*this) == n); } - void clear() { - ns_.clear(); - simpleName_.clear(); - } + bool equalOrAliasedBy(const Name &n) const; + void clear(); explicit operator std::string() const { return fullname(); } diff --git a/lang/c++/api/NodeImpl.hh b/lang/c++/api/NodeImpl.hh index ef1c1dac9b6..8372a3887cf 100644 --- a/lang/c++/api/NodeImpl.hh +++ b/lang/c++/api/NodeImpl.hh @@ -294,42 +294,30 @@ protected: }; class AVRO_DECL NodeRecord : public NodeImplRecord { - std::vector defaultValues; + std::vector> fieldsAliases_; + std::vector fieldsDefaultValues_; public: NodeRecord() : NodeImplRecord(AVRO_RECORD) {} + NodeRecord(const HasName &name, const MultiLeaves &fields, - const LeafNames &fieldsNames, - std::vector dv); + const LeafNames &fieldsNames, std::vector dv); NodeRecord(const HasName &name, const HasDoc &doc, const MultiLeaves &fields, - const LeafNames &fieldsNames, - std::vector dv) : NodeImplRecord(AVRO_RECORD, name, doc, fields, fieldsNames, MultiAttributes(), NoSize()), - defaultValues(std::move(dv)) { - leafNameCheck(); - } + const LeafNames &fieldsNames, std::vector dv); NodeRecord(const HasName &name, const MultiLeaves &fields, - const LeafNames &fieldsNames, - const std::vector& dv, - const MultiAttributes &customAttributes) : - NodeImplRecord(AVRO_RECORD, name, fields, fieldsNames, customAttributes, NoSize()), - defaultValues(dv) { - leafNameCheck(); - } + const LeafNames &fieldsNames, std::vector> fieldsAliases, + std::vector dv, const MultiAttributes &customAttributes); NodeRecord(const HasName &name, const HasDoc &doc, const MultiLeaves &fields, - const LeafNames &fieldsNames, - const std::vector& dv, - const MultiAttributes &customAttributes) : - NodeImplRecord(AVRO_RECORD, name, doc, fields, fieldsNames, customAttributes, NoSize()), - defaultValues(dv) { - leafNameCheck(); - } + const LeafNames &fieldsNames, std::vector> fieldsAliases, + std::vector dv, const MultiAttributes &customAttributes); void swap(NodeRecord &r) { NodeImplRecord::swap(r); - defaultValues.swap(r.defaultValues); + fieldsAliases_.swap(r.fieldsAliases_); + fieldsDefaultValues_.swap(r.fieldsDefaultValues_); } SchemaResolution resolve(const Node &reader) const override; @@ -344,22 +332,10 @@ public: } const GenericDatum &defaultValueAt(size_t index) override { - return defaultValues[index]; + return fieldsDefaultValues_[index]; } void printDefaultToJson(const GenericDatum &g, std::ostream &os, size_t depth) const override; - -private: - // check if leaf name is valid Name and is not duplicate - void leafNameCheck() { - for (size_t i = 0; i < leafNameAttributes_.size(); ++i) { - if (!nameIndex_.add(leafNameAttributes_.get(i), i)) { - throw Exception(boost::format( - "Cannot add duplicate field: %1%") - % leafNameAttributes_.get(i)); - } - } - } }; class AVRO_DECL NodeEnum : public NodeImplEnum { diff --git a/lang/c++/impl/Compiler.cc b/lang/c++/impl/Compiler.cc index 383798c4d63..f46de055e36 100644 --- a/lang/c++/impl/Compiler.cc +++ b/lang/c++/impl/Compiler.cc @@ -147,10 +147,13 @@ string getDocField(const Entity &e, const Object &m) { struct Field { const string name; + const vector aliases; const NodePtr schema; const GenericDatum defaultValue; const CustomAttributes customAttributes; - Field(string n, NodePtr v, GenericDatum dv, const CustomAttributes& ca) : name(std::move(n)), schema(std::move(v)), defaultValue(std::move(dv)), customAttributes(std::move(ca)) {} + + Field(string n, vector a, NodePtr v, GenericDatum dv, const CustomAttributes& ca) + : name(std::move(n)), aliases(std::move(a)), schema(std::move(v)), defaultValue(std::move(dv)), customAttributes(ca) {} }; static void assertType(const Entity &e, EntityType et) { @@ -263,7 +266,7 @@ static GenericDatum makeGenericDatum(NodePtr n, static const std::unordered_set& getKnownFields() { // return known fields static const std::unordered_set kKnownFields = - {"name", "type", "default", "doc", "size", "logicalType", + {"name", "type", "aliases", "default", "doc", "size", "logicalType", "values", "precision", "scale", "namespace"}; return kKnownFields; } @@ -282,7 +285,13 @@ static void getCustomAttributes(const Object& m, CustomAttributes &customAttribu static Field makeField(const Entity &e, SymbolTable &st, const string &ns) { const Object &m = e.objectValue(); - const string &n = getStringField(e, m, "name"); + string n = getStringField(e, m, "name"); + vector aliases; + if (containsField(m, "aliases")) { + for (const auto &alias : getArrayField(e, m, "aliases")) { + aliases.emplace_back(alias.stringValue()); + } + } auto it = findField(e, m, "type"); auto it2 = m.find("default"); NodePtr node = makeNode(it->second, st, ns); @@ -293,34 +302,34 @@ static Field makeField(const Entity &e, SymbolTable &st, const string &ns) { // Get custom attributes CustomAttributes customAttributes; getCustomAttributes(m, customAttributes); - - return Field(n, node, d, customAttributes); + return Field(std::move(n), std::move(aliases), node, d, customAttributes); } // Extended makeRecordNode (with doc). static NodePtr makeRecordNode(const Entity &e, const Name &name, const string *doc, const Object &m, SymbolTable &st, const string &ns) { - const Array &v = getArrayField(e, m, "fields"); concepts::MultiAttribute fieldNames; + vector> fieldAliases; concepts::MultiAttribute fieldValues; concepts::MultiAttribute customAttributes; vector defaultValues; - - for (const auto &it : v) { + for (const auto &it : getArrayField(e, m, "fields")) { Field f = makeField(it, st, ns); fieldNames.add(f.name); + fieldAliases.push_back(f.aliases); fieldValues.add(f.schema); defaultValues.push_back(f.defaultValue); customAttributes.add(f.customAttributes); } + NodeRecord *node; if (doc == nullptr) { node = new NodeRecord(asSingleAttribute(name), fieldValues, fieldNames, - defaultValues, customAttributes); + fieldAliases, defaultValues, customAttributes); } else { node = new NodeRecord(asSingleAttribute(name), asSingleAttribute(*doc), - fieldValues, fieldNames, defaultValues, customAttributes); + fieldValues, fieldNames, fieldAliases, defaultValues, customAttributes); } return NodePtr(node); } @@ -422,8 +431,9 @@ static NodePtr makeMapNode(const Entity &e, const Object &m, static Name getName(const Entity &e, const Object &m, const string &ns) { const string &name = getStringField(e, m, "name"); + Name result; if (isFullName(name)) { - return Name(name); + result = Name(name); } else { auto it = m.find("namespace"); if (it != m.end()) { @@ -432,11 +442,19 @@ static Name getName(const Entity &e, const Object &m, const string &ns) { "Json field \"%1%\" is not a %2%: %3%") % "namespace" % json::type_traits::name() % it->second.toString()); } - Name result = Name(name, it->second.stringValue()); - return result; + result = Name(name, it->second.stringValue()); + } else { + result = Name(name, ns); + } + } + + if (containsField(m, "aliases")) { + for (const auto &alias : getArrayField(e, m, "aliases")) { + result.addAlias(alias.stringValue()); } - return Name(name, ns); } + + return result; } static NodePtr makeNode(const Entity &e, const Object &m, diff --git a/lang/c++/impl/Node.cc b/lang/c++/impl/Node.cc index 46310d0f9ef..56ad1044beb 100644 --- a/lang/c++/impl/Node.cc +++ b/lang/c++/impl/Node.cc @@ -17,6 +17,7 @@ */ #include +#include #include "Node.hh" @@ -26,12 +27,44 @@ using std::string; Node::~Node() = default; +struct Name::Aliases { + std::vector raw; + std::unordered_set fullyQualified; +}; + +Name::Name() = default; + Name::Name(const std::string &name) { fullname(name); } +Name::Name(std::string simpleName, std::string ns) : ns_(std::move(ns)), simpleName_(std::move(simpleName)) { + check(); +} + +Name::Name(const Name& other) { + *this = other; +} + +Name& Name::operator=(const Name& other) { + if (this != &other) { + ns_ = other.ns_; + simpleName_ = other.simpleName_; + if (other.aliases_) { + aliases_ = std::make_unique(*other.aliases_); + } + } + return *this; +} + +Name::Name(Name&& other) = default; + +Name& Name::operator=(Name&& other) = default; + +Name::~Name() = default; + string Name::fullname() const { - return (ns_.empty()) ? simpleName_ : ns_ + "." + simpleName_; + return ns_.empty() ? simpleName_ : ns_ + "." + simpleName_; } void Name::fullname(const string &name) { @@ -46,6 +79,23 @@ void Name::fullname(const string &name) { check(); } +const std::vector& Name::aliases() const { + static const std::vector emptyAliases; + return aliases_ ? aliases_->raw : emptyAliases; +} + +void Name::addAlias(const std::string &alias) { + if (!aliases_) { + aliases_ = std::make_unique(); + } + aliases_->raw.push_back(alias); + if (!ns_.empty() && alias.find_last_of('.') == string::npos) { + aliases_->fullyQualified.emplace(ns_ + "." + alias); + } else { + aliases_->fullyQualified.insert(alias); + } +} + bool Name::operator<(const Name &n) const { return (ns_ < n.ns_) || (!(n.ns_ < ns_) && (simpleName_ < n.simpleName_)); } @@ -72,6 +122,16 @@ bool Name::operator==(const Name &n) const { return ns_ == n.ns_ && simpleName_ == n.simpleName_; } +bool Name::equalOrAliasedBy(const Name &n) const { + return *this == n || (n.aliases_ && n.aliases_->fullyQualified.find(fullname()) != n.aliases_->fullyQualified.end()); +} + +void Name::clear() { + ns_.clear(); + simpleName_.clear(); + aliases_.reset(); +} + void Node::setLogicalType(LogicalType logicalType) { checkLock(); diff --git a/lang/c++/impl/NodeImpl.cc b/lang/c++/impl/NodeImpl.cc index 5549c68fd48..3d1f80a955c 100644 --- a/lang/c++/impl/NodeImpl.cc +++ b/lang/c++/impl/NodeImpl.cc @@ -254,17 +254,34 @@ static void printName(std::ostream &os, const Name &n, size_t depth) { void NodeRecord::printJson(std::ostream &os, size_t depth) const { os << "{\n"; os << indent(++depth) << "\"type\": \"record\",\n"; - printName(os, nameAttribute_.get(), depth); + const Name &name = nameAttribute_.get(); + printName(os, name, depth); + + const auto &aliases = name.aliases(); + if (!aliases.empty()) { + os << indent(depth) << "\"aliases\": ["; + ++depth; + for (size_t i = 0; i < aliases.size(); ++i) { + if (i > 0) { + os << ','; + } + os << '\n' + << indent(depth) << "\"" << aliases[i] << "\""; + } + os << '\n' + << indent(--depth) << "]\n"; + } + if (!getDoc().empty()) { os << indent(depth) << R"("doc": ")" << escape(getDoc()) << "\",\n"; } - os << indent(depth) << "\"fields\": ["; + os << indent(depth) << "\"fields\": ["; size_t fields = leafAttributes_.size(); ++depth; - // Serialize "default" field: - assert(defaultValues.empty() || (defaultValues.size() == fields)); + assert(fieldsAliases_.empty() || (fieldsAliases_.size() == fields)); + assert(fieldsDefaultValues_.empty() || (fieldsDefaultValues_.size() == fields)); assert(customAttributes_.size() == 0 || customAttributes_.size() == fields); for (size_t i = 0; i < fields; ++i) { if (i > 0) { @@ -276,19 +293,37 @@ void NodeRecord::printJson(std::ostream &os, size_t depth) const { os << indent(depth) << "\"type\": "; leafAttributes_.get(i)->printJson(os, depth); - if (!defaultValues.empty()) { - if (!defaultValues[i].isUnion() && defaultValues[i].type() == AVRO_NULL) { + if (!fieldsAliases_.empty() && !fieldsAliases_[i].empty()) { + os << ",\n" + << indent(depth) << "\"aliases\": ["; + ++depth; + for (size_t j = 0; j < fieldsAliases_[i].size(); ++j) { + if (j > 0) { + os << ','; + } + os << '\n' + << indent(depth) << "\"" << fieldsAliases_[i][j] << "\""; + } + os << '\n' + << indent(--depth) << ']'; + } + + // Serialize "default" field: + if (!fieldsDefaultValues_.empty()) { + if (!fieldsDefaultValues_[i].isUnion() && fieldsDefaultValues_[i].type() == AVRO_NULL) { // No "default" field. } else { os << ",\n" << indent(depth) << "\"default\": "; - leafAttributes_.get(i)->printDefaultToJson(defaultValues[i], os, + leafAttributes_.get(i)->printDefaultToJson(fieldsDefaultValues_[i], os, depth); } } + if(customAttributes_.size() == fields) { printCustomAttributes(customAttributes_.get(i), depth, os); } + os << '\n'; os << indent(--depth) << '}'; } @@ -424,16 +459,38 @@ void NodeRecord::printDefaultToJson(const GenericDatum &g, std::ostream &os, << indent(--depth) << "}"; } } -NodeRecord::NodeRecord(const HasName &name, - const MultiLeaves &fields, - const LeafNames &fieldsNames, - std::vector dv) : NodeImplRecord(AVRO_RECORD, name, fields, fieldsNames, MultiAttributes(), NoSize()), - defaultValues(std::move(dv)) { + +NodeRecord::NodeRecord(const HasName &name, const MultiLeaves &fields, + const LeafNames &fieldsNames, std::vector dv) + : NodeRecord(name, HasDoc(), fields, fieldsNames, {}, std::move(dv), MultiAttributes()) {} + +NodeRecord::NodeRecord(const HasName &name, const HasDoc &doc, const MultiLeaves &fields, + const LeafNames &fieldsNames, std::vector dv) + : NodeRecord(name, doc, fields, fieldsNames, {}, std::move(dv), MultiAttributes()) {} + +NodeRecord::NodeRecord(const HasName &name, const MultiLeaves &fields, + const LeafNames &fieldsNames, std::vector> fieldsAliases, + std::vector dv, const MultiAttributes &customAttributes) + : NodeRecord(name, HasDoc(), fields, fieldsNames, std::move(fieldsAliases), std::move(dv), customAttributes) {} + +NodeRecord::NodeRecord(const HasName &name, const HasDoc &doc, const MultiLeaves &fields, + const LeafNames &fieldsNames, std::vector> fieldsAliases, + std::vector dv, const MultiAttributes &customAttributes) + : NodeImplRecord(AVRO_RECORD, name, doc, fields, fieldsNames, customAttributes, NoSize()), + fieldsAliases_(std::move(fieldsAliases)), + fieldsDefaultValues_(std::move(dv)) { + for (size_t i = 0; i < leafNameAttributes_.size(); ++i) { if (!nameIndex_.add(leafNameAttributes_.get(i), i)) { - throw Exception(boost::format( - "Cannot add duplicate field: %1%") - % leafNameAttributes_.get(i)); + throw Exception(boost::format("Cannot add duplicate field: %1%") % leafNameAttributes_.get(i)); + } + + if (!fieldsAliases_.empty()) { + for (const auto &alias : fieldsAliases_[i]) { + if (!nameIndex_.add(alias, i)) { + throw Exception(boost::format("Cannot add duplicate field: %1%") % alias); + } + } } } } diff --git a/lang/c++/impl/parsing/ResolvingDecoder.cc b/lang/c++/impl/parsing/ResolvingDecoder.cc index d86f6e58293..58e5cdc3d07 100644 --- a/lang/c++/impl/parsing/ResolvingDecoder.cc +++ b/lang/c++/impl/parsing/ResolvingDecoder.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -38,17 +39,14 @@ using std::make_shared; namespace parsing { -using std::make_shared; using std::shared_ptr; using std::static_pointer_cast; -using std::find_if; -using std::istringstream; using std::make_pair; using std::map; -using std::ostringstream; using std::pair; using std::reverse; +using std::set; using std::stack; using std::string; using std::unique_ptr; @@ -67,15 +65,6 @@ class ResolvingGrammarGenerator : public ValidatingGrammarGenerator { const NodePtr &reader, map &m, map &m2); - static vector> fields(const NodePtr &n) { - vector> result; - size_t c = n->names(); - for (size_t i = 0; i < c; ++i) { - result.emplace_back(n->nameAt(i), i); - } - return result; - } - static int bestBranch(const NodePtr &writer, const NodePtr &reader); ProductionPtr getWriterProduction(const NodePtr &n, @@ -154,15 +143,6 @@ static shared_ptr> getAvroBinary( return snapshot(*os); } -template -struct equalsFirst { - const T1 &v_; - explicit equalsFirst(const T1 &v) : v_(v) {} - bool operator()(const pair &p) { - return p.first == v_; - } -}; - ProductionPtr ResolvingGrammarGenerator::getWriterProduction( const NodePtr &n, map &m2) { const NodePtr &nn = (n->type() == AVRO_SYMBOLIC) ? static_cast(*n).getNode() : n; @@ -182,10 +162,18 @@ ProductionPtr ResolvingGrammarGenerator::resolveRecords( map &m2) { ProductionPtr result = make_shared(); - vector> wf = fields(writer); - vector> rf = fields(reader); + vector wf(writer->names()); + for (size_t i = 0; i < wf.size(); ++i) { + wf[i] = writer->nameAt(i); + } + + set rf; + for (size_t i = 0; i < reader->names(); ++i) { + rf.emplace(i); + } + vector fieldOrder; - fieldOrder.reserve(reader->names()); + fieldOrder.reserve(rf.size()); /* * We look for all writer fields in the reader. If found, recursively @@ -193,19 +181,15 @@ ProductionPtr ResolvingGrammarGenerator::resolveRecords( * If no matching field is found for reader, arrange to skip the writer * field. */ - for (vector>::const_iterator it = wf.begin(); - it != wf.end(); ++it) { - auto it2 = find_if(rf.begin(), rf.end(), - equalsFirst(it->first)); - if (it2 != rf.end()) { - ProductionPtr p = doGenerate2(writer->leafAt(it->second), - reader->leafAt(it2->second), m, m2); + for (size_t wi = 0; wi != wf.size(); ++wi) { + size_t ri; + if (reader->nameIndex(wf[wi], ri)) { + ProductionPtr p = doGenerate2(writer->leafAt(wi), reader->leafAt(ri), m, m2); copy(p->rbegin(), p->rend(), back_inserter(*result)); - fieldOrder.push_back(it2->second); - rf.erase(it2); + fieldOrder.push_back(ri); + rf.erase(ri); } else { - ProductionPtr p = getWriterProduction( - writer->leafAt(it->second), m2); + ProductionPtr p = getWriterProduction(writer->leafAt(wi), m2); result->push_back(Symbol::skipStart()); if (p->size() == 1) { result->push_back((*p)[0]); @@ -216,24 +200,21 @@ ProductionPtr ResolvingGrammarGenerator::resolveRecords( } /* - * Examine the reader fields left out, (i.e. those didn't have corresponding + * Examine the reader fields left out (i.e. those didn't have corresponding * writer field). */ - for (vector>::const_iterator it = rf.begin(); - it != rf.end(); ++it) { - - NodePtr s = reader->leafAt(it->second); - fieldOrder.push_back(it->second); + for (const auto ri : rf) { + NodePtr s = reader->leafAt(ri); + fieldOrder.push_back(ri); if (s->type() == AVRO_SYMBOLIC) { s = resolveSymbol(s); } shared_ptr> defaultBinary = - getAvroBinary(reader->defaultValueAt(it->second)); + getAvroBinary(reader->defaultValueAt(ri)); result->push_back(Symbol::defaultStartAction(defaultBinary)); - map>::const_iterator it2 = - m.find(NodePair(s, s)); - ProductionPtr p = (it2 == m.end()) ? doGenerate2(s, s, m, m2) : it2->second; + auto it = m.find(NodePair(s, s)); + ProductionPtr p = it == m.end() ? doGenerate2(s, s, m, m2) : it->second; copy(p->rbegin(), p->rend(), back_inserter(*result)); result->push_back(Symbol::defaultEndAction()); } @@ -289,7 +270,7 @@ ProductionPtr ResolvingGrammarGenerator::doGenerate2( case AVRO_BYTES: return make_shared(1, Symbol::bytesSymbol()); case AVRO_FIXED: - if (writer->name() == reader->name() && writer->fixedSize() == reader->fixedSize()) { + if (writer->name().equalOrAliasedBy(reader->name()) && writer->fixedSize() == reader->fixedSize()) { ProductionPtr result = make_shared(); result->push_back(Symbol::sizeCheckSymbol(reader->fixedSize())); result->push_back(Symbol::fixedSymbol()); @@ -298,7 +279,7 @@ ProductionPtr ResolvingGrammarGenerator::doGenerate2( } break; case AVRO_RECORD: - if (writer->name() == reader->name()) { + if (writer->name().equalOrAliasedBy(reader->name())) { const pair key(writer, reader); map::const_iterator kp = m.find(key); if (kp != m.end()) { @@ -312,7 +293,7 @@ ProductionPtr ResolvingGrammarGenerator::doGenerate2( break; case AVRO_ENUM: - if (writer->name() == reader->name()) { + if (writer->name().equalOrAliasedBy(reader->name())) { ProductionPtr result = make_shared(); result->push_back(Symbol::enumAdjustSymbol(writer, reader)); result->push_back(Symbol::enumSymbol()); diff --git a/lang/c++/test/CodecTests.cc b/lang/c++/test/CodecTests.cc index 27779bb03c0..79f546f8565 100644 --- a/lang/c++/test/CodecTests.cc +++ b/lang/c++/test/CodecTests.cc @@ -1264,6 +1264,46 @@ static const TestData3 data3[] = { {R"(["boolean", "int"])", "U1I", R"(["boolean", "long"])", "U1L", 1}, {R"(["boolean", "int"])", "U1I", R"(["long", "boolean"])", "U0L", 1}, + + // Aliases + {"{\"type\":\"record\", \"name\":\"r\", \"fields\":[" + "{\"name\":\"f0\", \"type\":\"int\"}," + "{\"name\":\"f1\", \"type\":\"boolean\"}," + "{\"name\":\"f2\", \"type\":\"double\"}]}", + "IBD", + "{\"type\":\"record\", \"name\":\"s\", \"aliases\":[\"r\"], \"fields\":[" + "{\"name\":\"g0\", \"type\":\"int\", \"aliases\":[\"f0\"]}," + "{\"name\":\"g1\", \"type\":\"boolean\", \"aliases\":[\"f1\"]}," + "{\"name\":\"f2\", \"type\":\"double\", \"aliases\":[\"g2\"]}]}", + "IBD", + 1}, + {"{\"type\":\"record\", \"name\":\"r\", \"namespace\":\"n\", \"fields\":[" + "{\"name\":\"f0\", \"type\":\"int\"}]}", + "I", + "{\"type\":\"record\", \"name\":\"s\", \"namespace\":\"n2\", \"aliases\":[\"t\", \"n.r\"], \"fields\":[" + " {\"name\":\"f0\", \"type\":\"int\"}]}", + "I", + 1}, + {"{\"type\":\"enum\", \"name\":\"e\", \"symbols\":[\"a\", \"b\"]}", + "e1", + "{\"type\":\"enum\", \"name\":\"f\", \"aliases\":[\"e\"], \"symbols\":[\"a\", \"b\", \"c\"]}", + "e1", + 1}, + {"{\"type\":\"enum\", \"name\":\"e\", \"namespace\":\"n\", \"symbols\":[\"a\", \"b\"]}", + "e1", + "{\"type\":\"enum\", \"name\":\"f\", \"namespace\":\"n2\", \"aliases\":[\"g\", \"n.e\"], \"symbols\":[\"a\", \"b\"]}", + "e1", + 1}, + {"{\"type\":\"fixed\", \"name\":\"f\", \"size\":8}", + "f8", + "{\"type\":\"fixed\", \"name\":\"g\", \"aliases\":[\"f\"], \"size\":8}", + "f8", + 1}, + {"{\"type\":\"fixed\", \"name\":\"f\", \"namespace\":\"n\", \"size\":8}", + "f8", + "{\"type\":\"fixed\", \"name\":\"g\", \"namespace\":\"n2\", \"aliases\":[\"h\", \"n.f\"], \"size\":8}", + "f8", + 1}, }; static const TestData4 data4[] = { diff --git a/lang/c++/test/unittest.cc b/lang/c++/test/unittest.cc index 73b92f1f7b3..962310c10e9 100644 --- a/lang/c++/test/unittest.cc +++ b/lang/c++/test/unittest.cc @@ -437,6 +437,7 @@ struct TestSchema { Name recordName("Test"); HasName nameConcept(recordName); concepts::MultiAttribute fieldNames; + std::vector> fieldAliases; concepts::MultiAttribute fieldValues; std::vector defaultValues; concepts::MultiAttribute customAttributes; @@ -453,7 +454,7 @@ struct TestSchema { customAttributes.add(cf); NodeRecord nodeRecordWithCustomAttribute(nameConcept, fieldValues, - fieldNames, defaultValues, + fieldNames, fieldAliases, defaultValues, customAttributes); std::string expectedJsonWithCustomAttribute = "{\"type\": \"record\", \"name\": \"Test\",\"fields\": "