From 920fdc68ed88eca843e58c4e2bf7e1ee122a6e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Fri, 3 Apr 2026 19:14:14 +0200 Subject: [PATCH] refactored identifier, added tests and added primary_key_accessor and tests --- include/matador/utils/identifier.hpp | 262 ++++++++++-------- include/matador/utils/identifier_accessor.hpp | 30 ++ .../matador/utils/primary_key_accessor.hpp | 111 +++++--- source/core/CMakeLists.txt | 12 +- source/core/utils/identifier.cpp | 258 ++++++++++------- source/core/utils/identifier_accessor.cpp | 38 +++ test/core/CMakeLists.txt | 5 +- test/core/utils/IdentifierTest.cpp | 191 ++++++++++++- test/core/utils/PrimaryKeyAccessorTest.cpp | 68 +++++ 9 files changed, 698 insertions(+), 277 deletions(-) create mode 100644 include/matador/utils/identifier_accessor.hpp create mode 100644 source/core/utils/identifier_accessor.cpp create mode 100644 test/core/utils/PrimaryKeyAccessorTest.cpp diff --git a/include/matador/utils/identifier.hpp b/include/matador/utils/identifier.hpp index 613c12a..5b5b836 100644 --- a/include/matador/utils/identifier.hpp +++ b/include/matador/utils/identifier.hpp @@ -1,14 +1,19 @@ #ifndef MATADOR_IDENTIFIER_HPP #define MATADOR_IDENTIFIER_HPP -#include "matador/utils/default_type_traits.hpp" -#include "matador/utils/field_attributes.hpp" #include "matador/utils/basic_types.hpp" +#include "matador/utils/default_type_traits.hpp" +#include "matador/utils/error.hpp" +#include "matador/utils/field_attributes.hpp" +#include "matador/utils/result.hpp" #include "matador/utils/types.hpp" #include +#include #include +#include #include +#include namespace matador::utils { class identifier_serializer { @@ -31,7 +36,13 @@ template struct identifier_type_traits; template -struct identifier_type_traits > > { +struct identifier_type_traits && std::is_signed_v > > { + static bool is_valid(Type value) { return value != 0; } + static std::string to_string(const Type value) { return std::to_string(value); } +}; + +template +struct identifier_type_traits && !std::is_signed_v > > { static bool is_valid(Type value) { return value > 0; } static std::string to_string(const Type value) { return std::to_string(value); } }; @@ -48,122 +59,70 @@ struct identifier_type_traits { static std::string to_string() { return "null"; } }; -template<> -struct identifier_type_traits { - static bool is_valid(const char *value); - static std::string to_string(const char *value); -}; - namespace detail { template size_t hash(const Type &value) { return std::hash()(value); } -size_t hash(const char *value); +size_t hash(const std::string &value); } +template +struct is_identifier_supported : std::false_type {}; + +template <> struct is_identifier_supported : std::true_type {}; +template <> struct is_identifier_supported : std::true_type {}; +template <> struct is_identifier_supported : std::true_type {}; +template <> struct is_identifier_supported : std::true_type {}; +template <> struct is_identifier_supported : std::true_type {}; +template <> struct is_identifier_supported : std::true_type {}; +template <> struct is_identifier_supported : std::true_type {}; +template <> struct is_identifier_supported : std::true_type {}; +template <> struct is_identifier_supported : std::true_type {}; + +template +inline constexpr bool is_identifier_supported_v = is_identifier_supported>::value; + class identifier { -private: - struct base { - base(const std::type_index &ti, basic_type type); - base(const base &x) = delete; - base &operator=(const base &x) = delete; - base(base &&x) = delete; - base &operator=(base &&x) = delete; - virtual ~base() = default; - - [[nodiscard]] virtual base *copy() const = 0; - [[nodiscard]] virtual bool equal_to(const base &x) const = 0; - [[nodiscard]] virtual bool less(const base &x) const = 0; - [[nodiscard]] virtual bool is_valid() const = 0; - virtual void serialize(identifier_serializer &s) = 0; - [[nodiscard]] virtual std::string str() const = 0; - [[nodiscard]] virtual size_t hash() const = 0; - - std::type_index type_index_; - basic_type type_{basic_type::Null}; - }; - - template - struct pk final : base { - using self = pk; - - explicit pk(const IdType &id) - : base(std::type_index(typeid(IdType)), data_type_traits::type(1)) - , id_(id) - , size_(sizeof(IdType)) { - } - - [[nodiscard]] base *copy() const override { - return new self(id_); - } - - [[nodiscard]] bool equal_to(const base &x) const override { - return static_cast(x).id_ == id_; - } - - [[nodiscard]] bool less(const base &x) const override { - return id_ < static_cast(x).id_; - } - - [[nodiscard]] bool is_valid() const override { - return identifier_type_traits::is_valid(id_); - } - - [[nodiscard]] std::string str() const override { - return identifier_type_traits::to_string(id_); - } - - void serialize(identifier_serializer &s) override { - s.serialize(id_, size_); - } - - [[nodiscard]] size_t hash() const override { - return detail::hash(id_); - } - - IdType id_; - size_t size_{}; - }; - - struct null_pk final : base { - null_pk(); - [[nodiscard]] base *copy() const override; - [[nodiscard]] bool equal_to(const base &x) const override; - [[nodiscard]] bool less(const base &x) const override; - [[nodiscard]] bool is_valid() const override; - void serialize(identifier_serializer &s) override; - [[nodiscard]] std::string str() const override; - [[nodiscard]] size_t hash() const override; - null_type_t null_; - }; - public: + using value_type = std::variant< + std::monostate, + int8_t, int16_t, int32_t, int64_t, + uint8_t, uint16_t, uint32_t, uint64_t, + std::string + >; + identifier(); - template - explicit identifier(const Type &id) - : id_(std::make_shared >(id)) { + template, int> = 0> + explicit identifier(Type &&id) + : value_(std::forward(id)) { } - explicit identifier(const char *id) - : id_(std::make_shared >(id)) { - } - - identifier(const identifier &x); + identifier(const identifier &x) = default; identifier &operator=(const identifier &x); - identifier(identifier &&x) noexcept; - identifier &operator=(identifier &&x) noexcept; + identifier(identifier &&x) noexcept = default; + identifier &operator=(identifier &&x) noexcept = default; - template - identifier &operator=(const Type &value) { - id_ = std::make_shared >(value); + explicit identifier(std::nullptr_t); + explicit identifier(const char *value); + + identifier& operator=(std::nullptr_t); + + template> && !std::is_same_v, bool>, int> = 0> + identifier& operator=(Type value) { + assign_integral(value); return *this; } - identifier &operator=(const char *value) { - id_ = std::make_shared >(value); + identifier& operator=(const std::string &value); + + identifier& operator=(const char *value); + + template && std::is_same_v, bool>, int> = 0> + identifier &operator=(Type &&value) { + value_ = std::forward(value); return *this; } @@ -180,17 +139,8 @@ public: [[nodiscard]] const std::type_index &type_index() const; [[nodiscard]] basic_type type() const; - [[nodiscard]] identifier share() const; - [[nodiscard]] size_t use_count() const; - [[nodiscard]] bool is_integer() const; - [[nodiscard]] bool is_floating_point() const; - [[nodiscard]] bool is_bool() const; [[nodiscard]] bool is_varchar() const; - [[nodiscard]] bool is_date() const; - [[nodiscard]] bool is_time() const; - [[nodiscard]] bool is_timestamp() const; - [[nodiscard]] bool is_blob() const; [[nodiscard]] bool is_null() const; [[nodiscard]] bool is_valid() const; @@ -200,15 +150,97 @@ public: [[nodiscard]] size_t hash() const; + template , int> = 0> + result as() const; + + template && !std::is_same_v, int> = 0> + result convert() const; + friend std::ostream &operator<<(std::ostream &out, const identifier &id); private: - explicit identifier(const std::shared_ptr &id); + template + void assign_integral(Type value) { + if constexpr (std::is_signed_v) { + if (sizeof(Type) <= sizeof(int8_t)) { + value_ = static_cast(value); + } else if (sizeof(Type) <= sizeof(int16_t)) { + value_ = static_cast(value); + } else if (sizeof(Type) <= sizeof(int32_t)) { + value_ = static_cast(value); + } else { + value_ = static_cast(value); + } + } else { + if (sizeof(Type) <= sizeof(uint8_t)) { + value_ = static_cast(value); + } else if (sizeof(Type) <= sizeof(uint16_t)) { + value_ = static_cast(value); + } else if (sizeof(Type) <= sizeof(uint32_t)) { + value_ = static_cast(value); + } else { + value_ = static_cast(value); + } + } + } + + static size_t type_rank(const value_type &value); + static std::type_index type_index_from_value(const value_type &value); + static basic_type type_from_value(const value_type &value); private: - std::shared_ptr id_; + value_type value_{std::monostate{}}; }; +template +bool in_range(Source value) { + if constexpr (std::is_signed_v == std::is_signed_v) { + return value >= static_cast(std::numeric_limits::min()) && + value <= static_cast(std::numeric_limits::max()); + } else if constexpr (std::is_signed_v && !std::is_signed_v) { + if (value < 0) { + return false; + } + using UnsignedSource = std::make_unsigned_t; + return static_cast(value) <= std::numeric_limits::max(); + } else { + return value <= static_cast>(std::numeric_limits::max()); + } +} + +template , int>> +result identifier::as() const { + return std::visit([](const auto &v) -> result { + using StoredType = std::decay_t; + + if constexpr (std::is_same_v) { + return failure(error{}); + } else if constexpr (std::is_same_v) { + return ok(v); + } else { + return failure(error{}); + } + }, value_); +} + +template && !std::is_same_v, int>> +result identifier::convert() const { + return std::visit([](const auto &v) -> result { + using Stored = std::decay_t; + + if constexpr (std::is_same_v) { + return failure(error{}); + } else if constexpr (std::is_integral_v) { + if (!in_range(v)) { + return failure(error{}); + } + return ok(static_cast(v)); + } else { + return failure(error{}); + } + }, value_); +} + static identifier null_identifier{}; /// @cond MATADOR_DEV @@ -219,13 +251,11 @@ struct id_pk_hash { /// @endcond } -namespace std { template<> -struct hash { +struct std::hash { size_t operator()(const matador::utils::identifier &id) const noexcept { return id.hash(); } -}; -} // namespace std +}; // namespace std -#endif //MATADOR_IDENTIFIER_HPP +#endif //MATADOR_IDENTIFIER_HPP \ No newline at end of file diff --git a/include/matador/utils/identifier_accessor.hpp b/include/matador/utils/identifier_accessor.hpp new file mode 100644 index 0000000..4577fa8 --- /dev/null +++ b/include/matador/utils/identifier_accessor.hpp @@ -0,0 +1,30 @@ +#ifndef MATADOR_IDENTIFIER_ACCESSOR_HPP +#define MATADOR_IDENTIFIER_ACCESSOR_HPP + +#include "matador/utils/identifier.hpp" +#include "matador/utils/result.hpp" + +namespace matador::utils { +class identifier_setter : public identifier_serializer { +public: + explicit identifier_setter(identifier &id); + + template + void set(const ValueType &val) { + id_.serialize(*this, val); + } + void serialize(int16_t &, const field_attributes &) override; + void serialize(int32_t &, const field_attributes &) override; + void serialize(int64_t &, const field_attributes &) override; + void serialize(uint8_t &, const field_attributes &) override; + void serialize(uint16_t &, const field_attributes &) override; + void serialize(uint32_t &, const field_attributes &) override; + void serialize(uint64_t &, const field_attributes &) override; + void serialize(std::string &, const field_attributes &) override; + void serialize(null_type_t &, const field_attributes &) override; + +private: + identifier &id_; +}; +} +#endif //MATADOR_IDENTIFIER_ACCESSOR_HPP diff --git a/include/matador/utils/primary_key_accessor.hpp b/include/matador/utils/primary_key_accessor.hpp index 67b9a7d..113213f 100644 --- a/include/matador/utils/primary_key_accessor.hpp +++ b/include/matador/utils/primary_key_accessor.hpp @@ -1,104 +1,125 @@ #ifndef MATADOR_PRIMARY_KEY_ACCESSOR_HPP #define MATADOR_PRIMARY_KEY_ACCESSOR_HPP +#include "matador/utils/access.hpp" #include "matador/utils/field_attributes.hpp" +#include "matador/utils/identifier.hpp" #include "matador/utils/primary_key_attribute.hpp" #include #include +#include namespace matador::utils { class foreign_attributes; namespace detail { -template < typename PrimaryKeyType > class primary_key_setter { public: - explicit primary_key_setter(const std::string &name, const PrimaryKeyType& value) - : name_(name) - , value_(value){} - - void on_primary_key(const char *id, PrimaryKeyType &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) { - if (id != nullptr && name_ == id) { - pk = static_cast(value_); - } + template + void set(ObjectType &obj, const identifier& pk) { + pk_ = pk; + access::process(*this, obj); + } + + template + void on_primary_key(const char * /*id*/, PrimaryKeyType &pk, const primary_key_attribute & = default_pk_attributes) { + const auto value = pk_.as(); + if (!value) { + // Todo: throw error + } + + pk = value.value(); } - template - static void on_primary_key(const char * /*id*/, ValueType & /*pk*/, const utils::primary_key_attribute & = utils::default_pk_attributes) {} static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {} template - static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {} + static void on_attribute(const char * /*id*/, T &, const field_attributes & = null_attributes) {} template - static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {} + static void on_belongs_to(const char * /*id*/, P &, const foreign_attributes & ) {} template - static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {} + static void on_has_one(const char * /*id*/, P &, const foreign_attributes & ) {} template - static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {} + static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const foreign_attributes & ) {} template - static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {} + static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const foreign_attributes & ) {} template - static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {} + static void on_has_many_to_many(const char * /*id*/, C &, const foreign_attributes & ) {} private: - const std::string &name_; - PrimaryKeyType value_{}; + identifier pk_{}; }; struct pk_unset_checker { - const std::string &name; bool unset{true}; - template - void on_primary_key(const char *id, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) { - if (id != nullptr && name == id) { - // Your convention: 0 means unset for integer PKs - unset = (static_cast(pk) == 0ULL); - } + template + void on_primary_key(const char * /*id*/, PrimaryKeyType &pk, const primary_key_attribute & = default_pk_attributes) { + unset = !identifier_type_traits::is_valid(pk); } static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {} template - static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {} + static void on_attribute(const char * /*id*/, T &, const field_attributes & = null_attributes) {} template - static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {} + static void on_belongs_to(const char * /*id*/, P &, const foreign_attributes & ) {} template - static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {} + static void on_has_one(const char * /*id*/, P &, const foreign_attributes & ) {} template - static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {} + static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const foreign_attributes & ) {} template - static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {} + static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const foreign_attributes & ) {} template - static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {} + static void on_has_many_to_many(const char * /*id*/, C &, const foreign_attributes & ) {} }; -struct pk_value_extractor { +struct primary_key_getter { std::uint64_t value{0}; - template - void on_primary_key(const char * /*id*/, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) { - value = static_cast(pk); + template + void on_primary_key(const char * /*id*/, PrimaryKeyType &pk, const primary_key_attribute & = default_pk_attributes) { + pk_ = pk; } static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {} template - static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {} + static void on_attribute(const char * /*id*/, T &, const field_attributes & = null_attributes) {} template - static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {} + static void on_belongs_to(const char * /*id*/, P &, const foreign_attributes & ) {} template - static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {} + static void on_has_one(const char * /*id*/, P &, const foreign_attributes & ) {} template - static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {} + static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const foreign_attributes & ) {} template - static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {} + static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const foreign_attributes & ) {} template - static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {} + static void on_has_many_to_many(const char * /*id*/, C &, const foreign_attributes & ) {} + + identifier pk_; }; } -template class primary_key_accessor { public: + template + identifier get(const Type &obj) { + access::process(pk_getter_, obj); + return pk_getter_.pk_; + } + + template + void set(Type &obj, const identifier &pk) { + pk_setter_.set(obj, pk); + } + + template + bool is_set(const Type &obj) { + access::process(pk_unset_checker_, obj); + return !pk_unset_checker_.unset; + } + +private: + detail::primary_key_setter pk_setter_; + detail::pk_unset_checker pk_unset_checker_; + detail::primary_key_getter pk_getter_; - PrimaryKeyType get() const; - PrimaryKeyType }; } diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index 763cbf9..b3603d3 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -22,12 +22,14 @@ add_library(matador-core STATIC ../../include/matador/object/collection_proxy.hpp ../../include/matador/object/collection_resolver.hpp ../../include/matador/object/collection_resolver_factory.hpp + ../../include/matador/object/collection_utils.hpp ../../include/matador/object/error_code.hpp ../../include/matador/object/foreign_node_completer.hpp ../../include/matador/object/internal/observer_list_copy_creator.hpp ../../include/matador/object/internal/observer_list_creator.hpp ../../include/matador/object/many_to_many_relation.hpp ../../include/matador/object/object.hpp + ../../include/matador/object/object_cache.hpp ../../include/matador/object/object_generator.hpp ../../include/matador/object/object_info.hpp ../../include/matador/object/object_proxy.hpp @@ -35,6 +37,7 @@ add_library(matador-core STATIC ../../include/matador/object/object_resolver.hpp ../../include/matador/object/object_resolver_factory.hpp ../../include/matador/object/observer.hpp + ../../include/matador/object/pk_field_locator.hpp ../../include/matador/object/primary_key_resolver.hpp ../../include/matador/object/relation_completer.hpp ../../include/matador/object/relation_endpoint.hpp @@ -63,6 +66,7 @@ add_library(matador-core STATIC ../../include/matador/utils/file.hpp ../../include/matador/utils/foreign_attributes.hpp ../../include/matador/utils/identifier.hpp + ../../include/matador/utils/identifier_accessor.hpp ../../include/matador/utils/identifier_to_value_converter.hpp ../../include/matador/utils/leader_follower_thread_pool.hpp ../../include/matador/utils/library.hpp @@ -70,6 +74,7 @@ add_library(matador-core STATIC ../../include/matador/utils/message_bus.hpp ../../include/matador/utils/os.hpp ../../include/matador/utils/placeholder.hpp + ../../include/matador/utils/primary_key_accessor.hpp ../../include/matador/utils/primary_key_attribute.hpp ../../include/matador/utils/primary_key_generator_type.hpp ../../include/matador/utils/result.hpp @@ -91,6 +96,7 @@ add_library(matador-core STATIC object/attribute.cpp object/basic_object_info.cpp object/basic_repository.cpp + object/collection_utils.cpp object/error_code.cpp object/foreign_node_completer.cpp object/object.cpp @@ -108,6 +114,7 @@ add_library(matador-core STATIC utils/file.cpp utils/foreign_attributes.cpp utils/identifier.cpp + utils/identifier_accessor.cpp utils/identifier_to_value_converter.cpp utils/leader_follower_thread_pool.cpp utils/library.cpp @@ -120,11 +127,6 @@ add_library(matador-core STATIC utils/uuid.cpp utils/value.cpp utils/version.cpp - ../../include/matador/object/collection_utils.hpp - object/collection_utils.cpp - ../../include/matador/object/pk_field_locator.hpp - ../../include/matador/object/object_cache.hpp - ../../include/matador/utils/primary_key_accessor.hpp ) target_link_libraries(matador-core ${CMAKE_DL_LIBS}) diff --git a/source/core/utils/identifier.cpp b/source/core/utils/identifier.cpp index 9d7bf6b..49b8e6c 100644 --- a/source/core/utils/identifier.cpp +++ b/source/core/utils/identifier.cpp @@ -1,97 +1,165 @@ #include "matador/utils/identifier.hpp" -#include -#include #include +#include +#include namespace matador::utils { -size_t detail::hash(const char *value) { - return std::hash()(std::string_view(value, std::strlen(value))); -} -bool identifier_type_traits::is_valid(const char *value) { - return value != nullptr && strlen(value) > 0; -} - -std::string identifier_type_traits::to_string(const char *value) { +namespace { +inline const std::type_index& null_type_index() { + static const std::type_index value{typeid(null_type_t)}; return value; } -identifier::base::base(const std::type_index &ti, const basic_type type) -: type_index_(ti) - , type_(type) { +inline const std::type_index& i8_type_index() { + static const std::type_index value{typeid(int8_t)}; + return value; } -identifier::null_pk::null_pk() -: base(std::type_index(typeid(null_type_t)), basic_type::Null) { +inline const std::type_index& i16_type_index() { + static const std::type_index value{typeid(int16_t)}; + return value; } -identifier::base *identifier::null_pk::copy() const { - return new null_pk; +inline const std::type_index& i32_type_index() { + static const std::type_index value{typeid(int32_t)}; + return value; } -bool identifier::null_pk::equal_to(const base &x) const { - return type_index_ == x.type_index_; +inline const std::type_index& i64_type_index() { + static const std::type_index value{typeid(int64_t)}; + return value; } -bool identifier::null_pk::less(const base &x) const { - return type_index_ == x.type_index_; +inline const std::type_index& u8_type_index() { + static const std::type_index value{typeid(uint8_t)}; + return value; } -bool identifier::null_pk::is_valid() const { - return identifier_type_traits::is_valid(); +inline const std::type_index& u16_type_index() { + static const std::type_index value{typeid(uint16_t)}; + return value; } -std::string identifier::null_pk::str() const { - return identifier_type_traits::to_string(); +inline const std::type_index& u32_type_index() { + static const std::type_index value{typeid(uint32_t)}; + return value; } -void identifier::null_pk::serialize(identifier_serializer &s) { - s.serialize(null_, {}); +inline const std::type_index& u64_type_index() { + static const std::type_index value{typeid(uint64_t)}; + return value; } -size_t identifier::null_pk::hash() const { - return std::hash()(nullptr); +inline const std::type_index& string_type_index() { + static const std::type_index value{typeid(std::string)}; + return value; +} +} // namespace + +size_t detail::hash(const std::string &value) { + return std::hash()(std::string_view(value)); } identifier::identifier() -: id_(std::make_shared()) { +: value_(std::monostate{}) { } -identifier::identifier(const identifier &x) -: id_(x.id_->copy()) { +size_t identifier::type_rank(const value_type &value) { + return value.index(); } -identifier &identifier::operator=(const identifier &x) { - if (this == &x) { - return *this; +std::type_index identifier::type_index_from_value(const value_type &value) { + return std::visit([](const auto &v) -> std::type_index { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return null_type_index(); + } else { + return std::type_index(typeid(T)); + } + }, value); +} + +const std::type_index &identifier::type_index() const { + switch (value_.index()) { + case 0: return null_type_index(); + case 1: return i8_type_index(); + case 2: return i16_type_index(); + case 3: return i32_type_index(); + case 4: return i64_type_index(); + case 5: return u8_type_index(); + case 6: return u16_type_index(); + case 7: return u32_type_index(); + case 8: return u64_type_index(); + case 9: return string_type_index(); + default: return null_type_index(); } - id_.reset(x.id_->copy()); - return *this; } -identifier::identifier(identifier &&x) noexcept -: id_(std::move(x.id_)) { - x.clear(); +basic_type identifier::type_from_value(const value_type &value) { + return std::visit([](const auto &v) -> basic_type { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return basic_type::Null; + } else { + return data_type_traits::type(1); + } + }, value); } -identifier &identifier::operator=(identifier &&x) noexcept { - id_ = std::move(x.id_); - x.clear(); +identifier::identifier(std::nullptr_t) +: value_(std::monostate{}) { +} +identifier::identifier(const char *value) +: value_(value ? std::string(value) : std::string{}) { +} + +identifier & identifier::operator=(const char *value) { + value_ = value ? std::string(value) : std::string{}; return *this; } bool identifier::operator==(const identifier &x) const { - return id_->equal_to(*x.id_); + return value_ == x.value_; } bool identifier::operator!=(const identifier &x) const { return !operator==(x); } +identifier &identifier::operator=(std::nullptr_t) { + value_ = std::monostate{}; + return *this; +} + +identifier &identifier::operator=(const std::string &value) { + value_ = value; + return *this; +} + +identifier &identifier::operator=(const identifier &other) { + if (this != &other) { + value_ = other.value_; + } + return *this; +} + bool identifier::operator<(const identifier &x) const { - return id_->less(*x.id_); + if (value_.index() != x.value_.index()) { + return value_.index() < x.value_.index(); + } + + return std::visit([](const auto &lhs, const auto &rhs) -> bool { + using L = std::decay_t; + using R = std::decay_t; + if constexpr (std::is_same_v) { + return lhs < rhs; + } else { + return false; + } + }, value_, x.value_); } bool identifier::operator<=(const identifier &x) const { @@ -107,79 +175,78 @@ bool identifier::operator>=(const identifier &x) const { } std::string identifier::str() const { - return id_->str(); -} - -const std::type_index &identifier::type_index() const { - return id_->type_index_; + return std::visit([](const auto &v) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return identifier_type_traits::to_string(); + } else if constexpr (std::is_same_v) { + return identifier_type_traits::to_string(v); + } else { + return identifier_type_traits::to_string(v); + } + }, value_); } basic_type identifier::type() const { - return id_->type_; -} - -identifier identifier::share() const { - return identifier(id_); -} - -size_t identifier::use_count() const { - return id_.use_count(); + return type_from_value(value_); } bool identifier::is_integer() const { - return id_->type_ >= basic_type::Int8 && id_->type_ <= basic_type::UInt64; -} - -bool identifier::is_floating_point() const { - return id_->type_ == basic_type::Float || id_->type_ == basic_type::Double; -} - -bool identifier::is_bool() const { - return id_->type_ == basic_type::Boolean; + return std::holds_alternative(value_) + || std::holds_alternative(value_) + || std::holds_alternative(value_) + || std::holds_alternative(value_) + || std::holds_alternative(value_) + || std::holds_alternative(value_) + || std::holds_alternative(value_) + || std::holds_alternative(value_); } bool identifier::is_varchar() const { - return id_->type_ == basic_type::Varchar; -} - -bool identifier::is_date() const { - return id_->type_ == basic_type::Date; -} - -bool identifier::is_time() const { - return id_->type_ == basic_type::Time; -} - -bool identifier::is_timestamp() const { - return id_->type_ == basic_type::DateTime; -} - -bool identifier::is_blob() const { - return id_->type_ == basic_type::Blob; + return std::holds_alternative(value_); } bool identifier::is_null() const { - return type_index() == null_identifier.type_index(); + return std::holds_alternative(value_); } bool identifier::is_valid() const { - return id_->is_valid(); + return std::visit([](const auto &v) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return identifier_type_traits::is_valid(); + } else { + return identifier_type_traits::is_valid(v); + } + }, value_); } void identifier::clear() { - id_ = std::make_unique(); + value_ = std::monostate{}; } void identifier::serialize(identifier_serializer &s) const { - id_->serialize(s); + std::visit([&s](const auto &v) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + null_type_t n{}; + s.serialize(n, {}); + } else { + auto tmp = v; + s.serialize(tmp, {}); + } + }, value_); } size_t identifier::hash() const { - return id_->hash(); -} - -identifier::identifier(const std::shared_ptr &id) -: id_(id) { + return std::visit([](const auto &v) -> size_t { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::hash{}(0); + } else { + return detail::hash(v); + } + }, value_); } std::ostream &operator<<(std::ostream &out, const identifier &id) { @@ -190,4 +257,5 @@ std::ostream &operator<<(std::ostream &out, const identifier &id) { size_t id_pk_hash::operator()(const identifier &id) const { return id.hash(); } -} + +} // namespace matador::utils \ No newline at end of file diff --git a/source/core/utils/identifier_accessor.cpp b/source/core/utils/identifier_accessor.cpp new file mode 100644 index 0000000..277a72a --- /dev/null +++ b/source/core/utils/identifier_accessor.cpp @@ -0,0 +1,38 @@ +#include "matador/utils/identifier_accessor.hpp" +#include "matador/utils/default_type_traits.hpp" + +namespace matador::utils { +identifier_setter::identifier_setter(identifier &id) +: id_(id) { +} + +void identifier_setter::serialize(int16_t &val, const field_attributes &) { + if (id_.type() == data_type_traits::type()) { + id_ = val; + } +} + +void identifier_setter::serialize(int32_t &, const field_attributes &) { +} + +void identifier_setter::serialize(int64_t &, const field_attributes &) { +} + +void identifier_setter::serialize(uint8_t &, const field_attributes &) { +} + +void identifier_setter::serialize(uint16_t &, const field_attributes &) { +} + +void identifier_setter::serialize(uint32_t &, const field_attributes &) { +} + +void identifier_setter::serialize(uint64_t &, const field_attributes &) { +} + +void identifier_setter::serialize(std::string &, const field_attributes &) { +} + +void identifier_setter::serialize(null_type_t &, const field_attributes &) { +} +} diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 16f3b41..b8dd879 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -5,6 +5,7 @@ list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) add_executable(CoreTests ../backends/SchemaFixture.hpp logger/LoggerTest.cpp + object/ObjectCacheTest.cpp object/ObjectTest.cpp object/PrimaryKeyResolverTest.cpp object/RepositoryTest.cpp @@ -14,13 +15,13 @@ add_executable(CoreTests utils/DependencyInjectionTest.cpp utils/FieldAttributeTest.cpp utils/IdentifierTest.cpp + utils/IsDatabasePrimitiveTest.cpp utils/MessageBusTest.cpp + utils/PrimaryKeyAccessorTest.cpp utils/ResultTest.cpp utils/StringTest.cpp utils/ThreadPoolTest.cpp utils/VersionTest.cpp - utils/IsDatabasePrimitiveTest.cpp - object/ObjectCacheTest.cpp ) target_link_libraries(CoreTests matador-core Catch2::Catch2WithMain) diff --git a/test/core/utils/IdentifierTest.cpp b/test/core/utils/IdentifierTest.cpp index 3c0f227..0d5a110 100644 --- a/test/core/utils/IdentifierTest.cpp +++ b/test/core/utils/IdentifierTest.cpp @@ -10,12 +10,7 @@ TEST_CASE("Test create identifier", "[identifier][create]") { REQUIRE(id.is_null()); REQUIRE(!id.is_integer()); - REQUIRE(!id.is_floating_point()); - REQUIRE(!id.is_bool()); REQUIRE(!id.is_varchar()); - REQUIRE(!id.is_date()); - REQUIRE(!id.is_time()); - REQUIRE(!id.is_blob()); REQUIRE(!id.is_valid()); REQUIRE(id.str() == "null"); } @@ -88,7 +83,7 @@ TEST_CASE("Test copy identifier" "[identifier][copy]") { identifier id3 = id1; REQUIRE(id1 == id3); - REQUIRE(id1 < id3); + REQUIRE(!(id1 < id3)); REQUIRE(id3.is_null()); REQUIRE(id1.hash() == id3.hash()); @@ -103,16 +98,184 @@ TEST_CASE("Test move identifier", "[identifier][move]") { REQUIRE(!id1.is_null()); const auto id2 = std::move(id1); - REQUIRE(id1.is_null()); + REQUIRE(!id1.is_null()); REQUIRE(id2.is_integer()); } -TEST_CASE("Test share identifier", "[identifier][share]") { - const identifier id1{6}; +TEST_CASE("identifier assignment from integer types", "[utils][identifier][assign]") { + identifier id; - REQUIRE(id1.use_count() == 1); + id = int8_t{-5}; + REQUIRE(id.type() == basic_type::Int8); + REQUIRE(id.str() == "-5"); + REQUIRE(id.is_integer()); + REQUIRE(id.is_valid()); - auto id2 = id1.share(); - REQUIRE(id1 == id2); - REQUIRE(id1.use_count() == 2); -} \ No newline at end of file + id = int16_t{42}; + REQUIRE(id.type() == basic_type::Int16); + REQUIRE(id.str() == "42"); + REQUIRE(id.is_integer()); + REQUIRE(id.is_valid()); + + id = uint32_t{123456u}; + REQUIRE(id.type() == basic_type::UInt32); + REQUIRE(id.str() == "123456"); + REQUIRE(id.is_integer()); + REQUIRE(id.is_valid()); +} + +TEST_CASE("identifier assignment from string type", "[utils][identifier][assign]") { + identifier id; + + id = std::string{"hello"}; + REQUIRE(id.type() == basic_type::Varchar); + REQUIRE(id.str() == "hello"); + REQUIRE(id.is_varchar()); + REQUIRE(id.is_valid()); + + id = std::string{}; + REQUIRE(id.type() == basic_type::Varchar); + REQUIRE(id.str().empty()); + REQUIRE_FALSE(id.is_valid()); +} + +TEST_CASE("identifier assignment from const char*", "[utils][identifier][assign]") { + identifier id; + + id = "world"; + REQUIRE(id.type() == basic_type::Varchar); + REQUIRE(id.str() == "world"); + REQUIRE(id.is_varchar()); + REQUIRE(id.is_valid()); + + id = static_cast(nullptr); + REQUIRE(id.type() == basic_type::Varchar); + REQUIRE(id.str().empty()); + REQUIRE_FALSE(id.is_valid()); +} + +TEST_CASE("identifier assignment from nullptr", "[utils][identifier][assign]") { + identifier id{42}; + + REQUIRE(id.is_valid()); + REQUIRE_FALSE(id.is_null()); + + id = nullptr; + REQUIRE(id.is_null()); + REQUIRE(id.type() == basic_type::Null); + REQUIRE(id.str() == "null"); + REQUIRE_FALSE(id.is_valid()); +} + +TEST_CASE("identifier reassignment between types", "[utils][identifier][assign]") { + identifier id{uint64_t{7}}; + + REQUIRE(id.is_integer()); + REQUIRE(id.str() == "7"); + + id = std::string{"abc"}; + REQUIRE(id.is_varchar()); + REQUIRE(id.str() == "abc"); + REQUIRE(id.is_valid()); + + id = int64_t{99}; + REQUIRE(id.is_integer()); + REQUIRE(id.str() == "99"); + REQUIRE(id.is_valid()); + + id = nullptr; + REQUIRE(id.is_null()); + REQUIRE(id.str() == "null"); +} + +TEST_CASE("identifier as() returns exact integer type", "[utils][identifier][as]") { + identifier id{int32_t{42}}; + + const auto as_i32 = id.as(); + REQUIRE(as_i32.is_ok()); + REQUIRE(*as_i32 == 42); + + const auto as_i64 = id.as(); + REQUIRE(as_i64.is_error()); +} + +TEST_CASE("identifier as() returns exact string type", "[utils][identifier][as]") { + identifier id{std::string{"hello"}}; + + const auto as_string = id.as(); + REQUIRE(as_string.is_ok()); + REQUIRE(*as_string == "hello"); + + const auto as_i32 = id.as(); + REQUIRE(as_i32.is_error()); +} + +TEST_CASE("identifier as() returns null type failure for null identifier", "[utils][identifier][as]") { + identifier id{nullptr}; + + const auto as_i32 = id.as(); + REQUIRE(as_i32.is_error()); + + const auto as_string = id.as(); + REQUIRE(as_string.is_error()); +} + +TEST_CASE("identifier convert() converts between integer types", "[utils][identifier][convert]") { + identifier id{int32_t{123}}; + + const auto as_i64 = id.convert(); + REQUIRE(as_i64.is_ok()); + REQUIRE(*as_i64 == 123); + + const auto as_u8 = id.convert(); + REQUIRE(as_u8.is_ok()); + REQUIRE(*as_u8 == 123); +} + +TEST_CASE("identifier convert() rejects out of range integer conversion", "[utils][identifier][convert]") { + identifier id{int32_t{300}}; + + const auto as_u8 = id.convert(); + REQUIRE(as_u8.is_error()); +} + +TEST_CASE("identifier convert() rejects negative to unsigned conversion", "[utils][identifier][convert]") { + identifier id{int32_t{-1}}; + + const auto as_u32 = id.convert(); + REQUIRE(as_u32.is_error()); +} + +TEST_CASE("identifier convert() rejects non-integer source types", "[utils][identifier][convert]") { + identifier id{std::string{"123"}}; + + const auto as_i32 = id.convert(); + REQUIRE(as_i32.is_error()); + + identifier null_id{nullptr}; + const auto null_to_i64 = null_id.convert(); + REQUIRE(null_to_i64.is_error()); +} + +TEST_CASE("identifier convert() works with unsigned integer sources", "[utils][identifier][convert]") { + identifier id{uint16_t{500}}; + + const auto as_i32 = id.convert(); + REQUIRE(as_i32.is_ok()); + REQUIRE(*as_i32 == 500); + + const auto as_u8 = id.convert(); + REQUIRE(as_u8.is_error()); +} + +TEST_CASE("identifier as() and convert() behave consistently for same integer type", "[utils][identifier][as][convert]") { + identifier id{uint64_t{77}}; + + const auto as_u64 = id.as(); + REQUIRE(as_u64.is_ok()); + REQUIRE(*as_u64 == 77); + + const auto conv_u64 = id.convert(); + REQUIRE(conv_u64.is_ok()); + REQUIRE(*conv_u64 == 77); +} diff --git a/test/core/utils/PrimaryKeyAccessorTest.cpp b/test/core/utils/PrimaryKeyAccessorTest.cpp new file mode 100644 index 0000000..6b8dea4 --- /dev/null +++ b/test/core/utils/PrimaryKeyAccessorTest.cpp @@ -0,0 +1,68 @@ +#include + +#include "matador/utils/identifier.hpp" +#include "matador/utils/primary_key_accessor.hpp" + +#include "../test/models/person.hpp" + +TEST_CASE("primary_key_accessor detects unset primary key", "[utils][primary_key_accessor]") { + using namespace matador; + + utils::primary_key_accessor accessor; + test::person p{}; + + REQUIRE_FALSE(accessor.is_set(p)); +} + +TEST_CASE("primary_key_accessor detects set primary key", "[utils][primary_key_accessor]") { + using namespace matador; + + utils::primary_key_accessor accessor; + test::person p{}; + p.id = 42; + + REQUIRE(accessor.is_set(p)); +} + +TEST_CASE("primary_key_accessor gets integer primary key", "[utils][primary_key_accessor]") { + using namespace matador; + + utils::primary_key_accessor accessor; + test::person p{}; + p.id = 17; + + const auto pk = accessor.get(p); + + REQUIRE(pk.is_integer()); + REQUIRE(pk.is_valid()); + REQUIRE(pk.as().is_ok()); + REQUIRE(*pk.as() == 17u); +} + +TEST_CASE("primary_key_accessor sets integer primary key", "[utils][primary_key_accessor]") { + using namespace matador; + + utils::primary_key_accessor accessor; + test::person p{}; + + accessor.set(p, utils::identifier{123u}); + + REQUIRE(p.id == 123u); + REQUIRE(accessor.is_set(p)); +} + +TEST_CASE("primary_key_accessor can round-trip primary key value", "[utils][primary_key_accessor]") { + using namespace matador; + + utils::primary_key_accessor accessor; + test::person original{}; + original.id = 88u; + + const auto pk = accessor.get(original); + + test::person copy{}; + accessor.set(copy, pk); + + REQUIRE(copy.id == 88u); + REQUIRE(accessor.is_set(copy)); +} \ No newline at end of file