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 a7d27e4ab make it thread-safe and simplify ctor (#3326)
a7d27e4ab is described below

commit a7d27e4abe98cb169ea5094a6414356141f5cdfb
Author: Gang Wu <[email protected]>
AuthorDate: Fri Mar 7 23:04:56 2025 +0800

    make it thread-safe and simplify ctor (#3326)
---
 lang/c++/impl/Compiler.cc            |  6 +++++
 lang/c++/impl/LogicalType.cc         | 35 ++++++++++++++++++++++++-
 lang/c++/impl/Node.cc                |  5 ++++
 lang/c++/impl/NodeImpl.cc            | 12 +++++++++
 lang/c++/include/avro/LogicalType.hh | 51 +++++++++++++++++++++++++++++++++++-
 lang/c++/test/SchemaTests.cc         | 38 +++++++++++++++++++++++++++
 6 files changed, 145 insertions(+), 2 deletions(-)

diff --git a/lang/c++/impl/Compiler.cc b/lang/c++/impl/Compiler.cc
index ee982a36f..6879c81e5 100644
--- a/lang/c++/impl/Compiler.cc
+++ b/lang/c++/impl/Compiler.cc
@@ -399,6 +399,12 @@ static LogicalType makeLogicalType(const Entity &e, const 
Object &m) {
         t = LogicalType::DURATION;
     else if (typeField == "uuid")
         t = LogicalType::UUID;
+    else {
+        auto custom = CustomLogicalTypeRegistry::instance().create(typeField, 
e.toString());
+        if (custom != nullptr) {
+            return LogicalType(std::move(custom));
+        }
+    }
     return LogicalType(t);
 }
 
diff --git a/lang/c++/impl/LogicalType.cc b/lang/c++/impl/LogicalType.cc
index 18da72a23..f59c500a3 100644
--- a/lang/c++/impl/LogicalType.cc
+++ b/lang/c++/impl/LogicalType.cc
@@ -22,7 +22,14 @@
 namespace avro {
 
 LogicalType::LogicalType(Type type)
-    : type_(type), precision_(0), scale_(0) {}
+    : type_(type), precision_(0), scale_(0), custom_(nullptr) {
+    if (type == CUSTOM) {
+        throw Exception("Logical type CUSTOM must be initialized with a custom 
logical type");
+    }
+}
+
+LogicalType::LogicalType(std::shared_ptr<CustomLogicalType> custom)
+    : type_(CUSTOM), precision_(0), scale_(0), custom_(std::move(custom)) {}
 
 LogicalType::Type LogicalType::type() const {
     return type_;
@@ -92,7 +99,33 @@ void LogicalType::printJson(std::ostream &os) const {
         case UUID:
             os << R"("logicalType": "uuid")";
             break;
+        case CUSTOM:
+            custom_->printJson(os);
+            break;
+    }
+}
+
+void CustomLogicalType::printJson(std::ostream &os) const {
+    os << R"("logicalType": ")" << name_ << "\"";
+}
+
+CustomLogicalTypeRegistry &CustomLogicalTypeRegistry::instance() {
+    static CustomLogicalTypeRegistry instance;
+    return instance;
+}
+
+void CustomLogicalTypeRegistry::registerType(const std::string &name, Factory 
factory) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    registry_[name] = factory;
+}
+
+std::shared_ptr<CustomLogicalType> CustomLogicalTypeRegistry::create(const 
std::string &name, const std::string &json) const {
+    std::lock_guard<std::mutex> lock(mutex_);
+    auto it = registry_.find(name);
+    if (it == registry_.end()) {
+        return nullptr;
     }
+    return it->second(json);
 }
 
 } // namespace avro
diff --git a/lang/c++/impl/Node.cc b/lang/c++/impl/Node.cc
index 615727128..017da22b8 100644
--- a/lang/c++/impl/Node.cc
+++ b/lang/c++/impl/Node.cc
@@ -233,6 +233,11 @@ void Node::setLogicalType(LogicalType logicalType) {
                                 "STRING type");
             }
             break;
+        case LogicalType::CUSTOM:
+            if (logicalType.customLogicalType() == nullptr) {
+                throw Exception("CUSTOM logical type is not set");
+            }
+            break;
     }
 
     logicalType_ = logicalType;
diff --git a/lang/c++/impl/NodeImpl.cc b/lang/c++/impl/NodeImpl.cc
index bc6bf400a..8434c24cc 100644
--- a/lang/c++/impl/NodeImpl.cc
+++ b/lang/c++/impl/NodeImpl.cc
@@ -252,9 +252,18 @@ static void printName(std::ostream &os, const Name &n, 
size_t depth) {
     os << indent(depth) << R"("name": ")" << n.simpleName() << "\",\n";
 }
 
+static void printLogicalType(std::ostream &os, const LogicalType &logicalType, 
size_t depth) {
+    if (logicalType.type() != LogicalType::NONE) {
+        os << indent(depth);
+        logicalType.printJson(os);
+        os << ",\n";
+    }
+}
+
 void NodeRecord::printJson(std::ostream &os, size_t depth) const {
     os << "{\n";
     os << indent(++depth) << "\"type\": \"record\",\n";
+    printLogicalType(os, logicalType(), depth);
     const Name &name = nameAttribute_.get();
     printName(os, name, depth);
 
@@ -524,6 +533,7 @@ void NodeMap::printDefaultToJson(const GenericDatum &g, 
std::ostream &os,
 void NodeEnum::printJson(std::ostream &os, size_t depth) const {
     os << "{\n";
     os << indent(++depth) << "\"type\": \"enum\",\n";
+    printLogicalType(os, logicalType(), depth);
     if (!getDoc().empty()) {
         os << indent(depth) << R"("doc": ")"
            << escape(getDoc()) << "\",\n";
@@ -550,6 +560,7 @@ void NodeEnum::printJson(std::ostream &os, size_t depth) 
const {
 void NodeArray::printJson(std::ostream &os, size_t depth) const {
     os << "{\n";
     os << indent(depth + 1) << "\"type\": \"array\",\n";
+    printLogicalType(os, logicalType(), depth + 1);
     if (!getDoc().empty()) {
         os << indent(depth + 1) << R"("doc": ")"
            << escape(getDoc()) << "\",\n";
@@ -566,6 +577,7 @@ void NodeArray::printJson(std::ostream &os, size_t depth) 
const {
 void NodeMap::printJson(std::ostream &os, size_t depth) const {
     os << "{\n";
     os << indent(depth + 1) << "\"type\": \"map\",\n";
+    printLogicalType(os, logicalType(), depth + 1);
     if (!getDoc().empty()) {
         os << indent(depth + 1) << R"("doc": ")"
            << escape(getDoc()) << "\",\n";
diff --git a/lang/c++/include/avro/LogicalType.hh 
b/lang/c++/include/avro/LogicalType.hh
index 5b274bcb7..53a1a8fd5 100644
--- a/lang/c++/include/avro/LogicalType.hh
+++ b/lang/c++/include/avro/LogicalType.hh
@@ -19,12 +19,18 @@
 #ifndef avro_LogicalType_hh__
 #define avro_LogicalType_hh__
 
+#include <functional>
 #include <iostream>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
 
 #include "Config.hh"
 
 namespace avro {
 
+class CustomLogicalType;
+
 class AVRO_DECL LogicalType {
 public:
     enum Type {
@@ -41,10 +47,12 @@ public:
         LOCAL_TIMESTAMP_MICROS,
         LOCAL_TIMESTAMP_NANOS,
         DURATION,
-        UUID
+        UUID,
+        CUSTOM // for registered custom logical types
     };
 
     explicit LogicalType(Type type);
+    explicit LogicalType(std::shared_ptr<CustomLogicalType> custom);
 
     Type type() const;
 
@@ -57,12 +65,53 @@ public:
     void setScale(int32_t scale);
     int32_t scale() const { return scale_; }
 
+    const std::shared_ptr<CustomLogicalType> &customLogicalType() const {
+        return custom_;
+    }
+
     void printJson(std::ostream &os) const;
 
 private:
     Type type_;
     int32_t precision_;
     int32_t scale_;
+    std::shared_ptr<CustomLogicalType> custom_;
+};
+
+class AVRO_DECL CustomLogicalType {
+public:
+    CustomLogicalType(const std::string &name) : name_(name) {}
+
+    virtual ~CustomLogicalType() = default;
+
+    const std::string &name() const { return name_; }
+
+    virtual void printJson(std::ostream &os) const;
+
+private:
+    std::string name_;
+};
+
+// Registry for custom logical types.
+// This class is thread-safe.
+class AVRO_DECL CustomLogicalTypeRegistry {
+public:
+    static CustomLogicalTypeRegistry &instance();
+
+    using Factory = std::function<std::shared_ptr<CustomLogicalType>(const 
std::string &json)>;
+
+    // Register a custom logical type and its factory function.
+    void registerType(const std::string &name, Factory factory);
+
+    // Create a custom logical type from a JSON string.
+    // Returns nullptr if the name is not registered.
+    std::shared_ptr<CustomLogicalType> create(const std::string &name, const 
std::string &json) const;
+
+private:
+    CustomLogicalTypeRegistry() = default;
+
+    std::unordered_map<std::string, Factory> registry_;
+    mutable std::mutex mutex_;
 };
 
 } // namespace avro
diff --git a/lang/c++/test/SchemaTests.cc b/lang/c++/test/SchemaTests.cc
index 24e827e3f..3593c6827 100644
--- a/lang/c++/test/SchemaTests.cc
+++ b/lang/c++/test/SchemaTests.cc
@@ -658,6 +658,43 @@ static void testMalformedLogicalTypes(const char *schema) {
     BOOST_CHECK(datum.logicalType().type() == LogicalType::NONE);
 }
 
+static void testCustomLogicalType() {
+    // Declare a custom logical type.
+    struct MapLogicalType : public CustomLogicalType {
+        MapLogicalType() : CustomLogicalType("map") {}
+    };
+
+    // Register the custom logical type with the registry.
+    CustomLogicalTypeRegistry::instance().registerType("map", [](const 
std::string &) {
+        return std::make_shared<MapLogicalType>();
+    });
+
+    auto verifyCustomLogicalType = [](const ValidSchema &schema) {
+        auto logicalType = schema.root()->logicalType();
+        BOOST_CHECK_EQUAL(logicalType.type(), LogicalType::CUSTOM);
+        BOOST_CHECK_EQUAL(logicalType.customLogicalType()->name(), "map");
+    };
+
+    const std::string schema =
+        R"({ "type": "array",
+             "logicalType": "map",
+             "items": {
+               "type": "record",
+               "name": "k12_v13",
+               "fields": [
+                 { "name": "key", "type": "int", "field-id": 12 },
+                 { "name": "value", "type": "string", "field-id": 13 }
+               ]
+             }
+           })";
+    auto compiledSchema = compileJsonSchemaFromString(schema);
+    verifyCustomLogicalType(compiledSchema);
+
+    auto json = compiledSchema.toJson();
+    auto parsedSchema = compileJsonSchemaFromString(json);
+    verifyCustomLogicalType(parsedSchema);
+}
+
 } // namespace schema
 } // namespace avro
 
@@ -681,5 +718,6 @@ init_unit_test_suite(int /*argc*/, char * /*argv*/[]) {
     ADD_PARAM_TEST(ts, avro::schema::testMalformedLogicalTypes,
                    avro::schema::malformedLogicalTypes);
     ts->add(BOOST_TEST_CASE(&avro::schema::testCompactSchemas));
+    ts->add(BOOST_TEST_CASE(&avro::schema::testCustomLogicalType));
     return ts;
 }

Reply via email to