Repository navigation
feat(model): add data product concept and registry #1001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| #include "phlex/model/data_product_concept.hpp" | ||
|
|
||
| #include <stdexcept> | ||
| #include <typeinfo> | ||
|
|
||
| namespace phlex::experimental { | ||
|
|
||
| data_product_concept::data_product_concept(std::string name) : name_(std::move(name)) | ||
| { | ||
| if (name_.empty()) { | ||
| throw std::invalid_argument("Data product concept name cannot be empty"); | ||
| } | ||
| } | ||
|
|
||
| std::string const& data_product_concept::name() const noexcept { return name_; } | ||
|
|
||
| std::unordered_set<concrete_product_id> const& data_product_concept::concrete_types() | ||
| const noexcept | ||
| { | ||
| return concrete_types_; | ||
| } | ||
|
|
||
| void data_product_concept::add_concrete_type(concrete_product_id type) | ||
| { | ||
| concrete_types_.insert(type); | ||
| } | ||
|
|
||
| void data_product_concept::add_concrete_types( | ||
| std::unordered_set<concrete_product_id> const& types) | ||
| { | ||
| concrete_types_.insert(types.begin(), types.end()); | ||
| } | ||
|
|
||
| bool data_product_concept::has_concrete_type(concrete_product_id type) const | ||
| { | ||
| return concrete_types_.find(type) != concrete_types_.end(); | ||
| } | ||
|
|
||
| } // namespace phlex::experimental | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| #ifndef PHLEX_MODEL_DATA_PRODUCT_CONCEPT_HPP | ||
| #define PHLEX_MODEL_DATA_PRODUCT_CONCEPT_HPP | ||
|
|
||
| #include "phlex/model/identifier.hpp" | ||
| #include "phlex/phlex_model_export.hpp" | ||
|
|
||
| #include <string> | ||
| #include <typeindex> | ||
| #include <typeinfo> | ||
| #include <unordered_set> | ||
| #include <vector> | ||
|
|
||
| namespace phlex::experimental { | ||
|
|
||
| // Type alias for std::type_index, representing a concrete data product type. | ||
| using concrete_product_id = std::type_index; | ||
|
|
||
| // Represents a data product concept. | ||
| // | ||
| // A data product concept is a named category that groups related concrete | ||
| // data product types. For example, "hit" could be a concept that encompasses | ||
| // multiple hit types (HitV1, HitV2, etc.). | ||
| // | ||
| // Each data_product_concept has: | ||
| // - A unique name | ||
| // - A set of concrete types that model this concept | ||
| class PHLEX_MODEL_EXPORT data_product_concept { | ||
| public: | ||
| explicit data_product_concept(std::string name); | ||
|
|
||
| std::string const& name() const noexcept; | ||
|
|
||
| std::unordered_set<concrete_product_id> const& concrete_types() const noexcept; | ||
|
|
||
| void add_concrete_type(concrete_product_id type); | ||
|
|
||
| void add_concrete_types(std::unordered_set<concrete_product_id> const& types); | ||
|
|
||
| bool has_concrete_type(concrete_product_id type) const; | ||
|
|
||
| bool operator==(data_product_concept const&) const = default; | ||
|
|
||
| private: | ||
| std::string name_; | ||
| std::unordered_set<concrete_product_id> concrete_types_; | ||
| }; | ||
|
|
||
| } // namespace phlex::experimental | ||
|
|
||
| #endif // PHLEX_MODEL_DATA_PRODUCT_CONCEPT_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| #include "phlex/model/data_product_concept_registry.hpp" | ||
|
|
||
| #include <algorithm> | ||
| #include <mutex> | ||
| #include <stdexcept> | ||
| #include <utility> | ||
|
|
||
| namespace phlex::experimental { | ||
|
|
||
| bool data_product_concept_registry::register_concept(std::unique_ptr<data_product_concept> con) | ||
| { | ||
| if (!con) { | ||
| throw std::invalid_argument{"Cannot register null concept"}; | ||
| } | ||
| auto name = con->name(); // Copy the name before moving from con. | ||
|
|
||
| std::unique_lock lock{mutex_}; | ||
| if (auto* existing = find_concept_unlocked(name)) { | ||
| existing->add_concrete_types(con->concrete_types()); | ||
| return false; | ||
| } | ||
| concepts_.emplace(std::move(name), std::move(con)); | ||
| return true; | ||
| } | ||
|
|
||
| bool data_product_concept_registry::add_concrete_type(std::string const& name, | ||
| concrete_product_id type) | ||
| { | ||
| std::unique_lock lock{mutex_}; | ||
| if (auto* existing = find_concept_unlocked(name)) { | ||
| existing->add_concrete_type(type); | ||
| return false; | ||
| } | ||
|
|
||
| auto con = std::make_unique<data_product_concept>(name); | ||
| con->add_concrete_type(type); | ||
| concepts_.emplace(name, std::move(con)); | ||
| return true; | ||
| } | ||
|
|
||
| data_product_concept* data_product_concept_registry::find_concept_unlocked( | ||
| std::string const& name) const | ||
| { | ||
| auto it = concepts_.find(name); | ||
| if (it != concepts_.end()) { | ||
| return it->second.get(); | ||
| } | ||
| return nullptr; | ||
| } | ||
|
|
||
| data_product_concept const* data_product_concept_registry::find_concept( | ||
| std::string const& name) const | ||
| { | ||
| std::shared_lock lock{mutex_}; | ||
| return find_concept_unlocked(name); | ||
| } | ||
|
|
||
| data_product_concept* data_product_concept_registry::find_concept(std::string const& name) | ||
| { | ||
| std::shared_lock lock{mutex_}; | ||
| return find_concept_unlocked(name); | ||
| } | ||
|
|
||
| std::vector<std::string> data_product_concept_registry::all_concept_names() const | ||
| { | ||
| std::shared_lock lock{mutex_}; | ||
| std::vector<std::string> names; | ||
| names.reserve(concepts_.size()); | ||
| for (auto const& [name, _] : concepts_) { | ||
| names.push_back(name); | ||
| } | ||
| std::sort(names.begin(), names.end()); | ||
| return names; | ||
| } | ||
|
|
||
| bool data_product_concept_registry::type_matches_concept(std::string const& concept_name, | ||
| concrete_product_id type) const | ||
| { | ||
| std::shared_lock lock{mutex_}; | ||
| auto const* con = find_concept_unlocked(concept_name); | ||
| if (!con) { | ||
| return false; | ||
| } | ||
| return con->has_concrete_type(type); | ||
| } | ||
|
|
||
| data_product_concept const* data_product_concept_registry::get_concept_for_type( | ||
| concrete_product_id type) const | ||
| { | ||
| std::shared_lock lock{mutex_}; | ||
| for (auto const& [name, con] : concepts_) { | ||
| if (con->has_concrete_type(type)) { | ||
| return con.get(); | ||
| } | ||
| } | ||
| return nullptr; | ||
| } | ||
|
|
||
| } // namespace phlex::experimental |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| #ifndef PHLEX_MODEL_DATA_PRODUCT_CONCEPT_REGISTRY_HPP | ||
| #define PHLEX_MODEL_DATA_PRODUCT_CONCEPT_REGISTRY_HPP | ||
|
|
||
| #include "phlex/model/data_product_concept.hpp" | ||
| #include "phlex/phlex_model_export.hpp" | ||
|
|
||
| #include <memory> | ||
| #include <shared_mutex> | ||
| #include <string> | ||
| #include <unordered_map> | ||
| #include <vector> | ||
|
|
||
| namespace phlex::experimental { | ||
|
|
||
| // Registry for data product concepts. | ||
| // | ||
| // This registry stores data_product_concept instances and allows | ||
| // lookup by name. Each concept can be associated with one or more | ||
| // concrete data product types. | ||
| // | ||
| // All public operations are individually safe for concurrent invocation: | ||
| // a registry-level std::shared_mutex protects the map and every access to | ||
| // a stored concept's concrete-type set that is made through the registry's | ||
| // own methods. This guarantee does not extend past the return of a call. | ||
| // In particular, the non-const find_concept overload and | ||
| // get_concept_for_type return a pointer to a stored concept; the caller | ||
| // must not dereference or mutate through that pointer concurrently with a | ||
| // registry write, or concurrently with another caller's mutation via a | ||
| // similarly obtained pointer, since such access is not covered by the | ||
| // registry's lock. | ||
| class PHLEX_MODEL_EXPORT data_product_concept_registry { | ||
| public: | ||
| data_product_concept_registry() = default; | ||
| ~data_product_concept_registry() = default; | ||
|
|
||
| // Register a data product concept. | ||
| // | ||
| // Returns true if a new concept was inserted. Returns false if a | ||
| // concept with the same name was already registered, in which case | ||
| // the incoming concept's concrete types are merged into the existing | ||
| // concept's set and the incoming concept is discarded. | ||
| // | ||
| // Throws std::invalid_argument if con is null. | ||
| bool register_concept(std::unique_ptr<data_product_concept> con); | ||
|
|
||
| // Register a concrete data product type with the concept of the given name. | ||
| // If necessary, a new concept is added to the registry. | ||
| // Return true if a new concept was registered. | ||
| bool add_concrete_type(std::string const& name, concrete_product_id type); | ||
|
|
||
| // Find a data product concept by name. | ||
| // | ||
| // The returned pointer is not safe to dereference (or, for the | ||
| // non-const overload, to mutate through) concurrently with a registry | ||
| // write after this method returns. | ||
| data_product_concept const* find_concept(std::string const& name) const; | ||
| data_product_concept* find_concept(std::string const& name); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent concept assignment from changing a registered name. If a caller assigns
|
||
|
|
||
| // Get all registered concept names. | ||
| std::vector<std::string> all_concept_names() const; | ||
|
|
||
| // Check if a concrete type is associated with a concept. | ||
| bool type_matches_concept(std::string const& concept_name, concrete_product_id type) const; | ||
|
|
||
| // Get the concept associated with a concrete type. | ||
| // | ||
| // The returned pointer is not safe to dereference concurrently with a | ||
| // registry write after this method returns. | ||
| data_product_concept const* get_concept_for_type(concrete_product_id type) const; | ||
|
|
||
| // data_product_concept_registry objects can be neither copied nor moved. | ||
| data_product_concept_registry(data_product_concept_registry const&) = delete; | ||
| data_product_concept_registry& operator=(data_product_concept_registry const&) = delete; | ||
| data_product_concept_registry(data_product_concept_registry&&) noexcept = delete; | ||
| data_product_concept_registry& operator=(data_product_concept_registry&&) noexcept = delete; | ||
|
|
||
| private: | ||
| // Assumes the caller already holds mutex_ (shared or exclusive). | ||
| // Performs a plain, unlocked lookup. This overload is const yet returns | ||
| // a non-const pointer (legal, since the pointee is reached through | ||
| // std::unique_ptr const&::get()); a single helper serves both the | ||
| // const and non-const find_concept overloads, so the const overload's | ||
| // constness is nominal only as far as the pointee is concerned. | ||
| data_product_concept* find_concept_unlocked(std::string const& name) const; | ||
|
|
||
| mutable std::shared_mutex mutex_; | ||
| std::unordered_map<std::string, std::unique_ptr<data_product_concept>> concepts_; | ||
| }; | ||
|
|
||
| } // namespace phlex::experimental | ||
|
|
||
| #endif // PHLEX_MODEL_DATA_PRODUCT_CONCEPT_REGISTRY_HPP | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle a concept’s own set before range insertion.
A caller can invoke
con.add_concrete_types(con.concrete_types()). This passes iterators fromconcrete_types_back to its range-insertion operation. The C++ unordered-container contract forbids source iterators from the destination container, so this call has undefined behavior. Return without inserting whentypesrefers toconcrete_types_. (eel.is)