refactored identifier, added tests and added primary_key_accessor and tests

This commit is contained in:
Sascha Kühl 2026-04-03 19:14:14 +02:00
parent f751541bdd
commit 920fdc68ed
9 changed files with 698 additions and 277 deletions

View File

@ -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 <memory>
#include <ostream>
#include <string>
#include <type_traits>
#include <typeindex>
#include <variant>
namespace matador::utils {
class identifier_serializer {
@ -31,7 +36,13 @@ template<typename Type, class Enabled = void>
struct identifier_type_traits;
template<typename Type>
struct identifier_type_traits<Type, std::enable_if_t<std::is_integral_v<Type> > > {
struct identifier_type_traits<Type, std::enable_if_t<std::is_integral_v<Type> && std::is_signed_v<Type> > > {
static bool is_valid(Type value) { return value != 0; }
static std::string to_string(const Type value) { return std::to_string(value); }
};
template<typename Type>
struct identifier_type_traits<Type, std::enable_if_t<std::is_integral_v<Type> && !std::is_signed_v<Type> > > {
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<null_type_t, void> {
static std::string to_string() { return "null"; }
};
template<>
struct identifier_type_traits<const char *, void> {
static bool is_valid(const char *value);
static std::string to_string(const char *value);
};
namespace detail {
template<typename Type>
size_t hash(const Type &value) {
return std::hash<Type>()(value);
}
size_t hash(const char *value);
size_t hash(const std::string &value);
}
template <class T>
struct is_identifier_supported : std::false_type {};
template <> struct is_identifier_supported<int8_t> : std::true_type {};
template <> struct is_identifier_supported<int16_t> : std::true_type {};
template <> struct is_identifier_supported<int32_t> : std::true_type {};
template <> struct is_identifier_supported<int64_t> : std::true_type {};
template <> struct is_identifier_supported<uint8_t> : std::true_type {};
template <> struct is_identifier_supported<uint16_t> : std::true_type {};
template <> struct is_identifier_supported<uint32_t> : std::true_type {};
template <> struct is_identifier_supported<uint64_t> : std::true_type {};
template <> struct is_identifier_supported<std::string> : std::true_type {};
template <class T>
inline constexpr bool is_identifier_supported_v = is_identifier_supported<std::decay_t<T>>::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<class IdType>
struct pk final : base {
using self = pk;
explicit pk(const IdType &id)
: base(std::type_index(typeid(IdType)), data_type_traits<IdType>::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<const pk &>(x).id_ == id_;
}
[[nodiscard]] bool less(const base &x) const override {
return id_ < static_cast<const pk &>(x).id_;
}
[[nodiscard]] bool is_valid() const override {
return identifier_type_traits<IdType>::is_valid(id_);
}
[[nodiscard]] std::string str() const override {
return identifier_type_traits<IdType>::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<typename Type>
explicit identifier(const Type &id)
: id_(std::make_shared<pk<Type> >(id)) {
template<typename Type, std::enable_if_t<is_identifier_supported_v<Type>, int> = 0>
explicit identifier(Type &&id)
: value_(std::forward<Type>(id)) {
}
explicit identifier(const char *id)
: id_(std::make_shared<pk<std::string> >(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<typename Type>
identifier &operator=(const Type &value) {
id_ = std::make_shared<pk<Type> >(value);
explicit identifier(std::nullptr_t);
explicit identifier(const char *value);
identifier& operator=(std::nullptr_t);
template<typename Type, std::enable_if_t<std::is_integral_v<std::decay_t<Type>> && !std::is_same_v<std::decay_t<Type>, bool>, int> = 0>
identifier& operator=(Type value) {
assign_integral(value);
return *this;
}
identifier &operator=(const char *value) {
id_ = std::make_shared<pk<std::string> >(value);
identifier& operator=(const std::string &value);
identifier& operator=(const char *value);
template<typename Type, std::enable_if_t<is_identifier_supported_v<Type> && std::is_same_v<std::decay_t<Type>, bool>, int> = 0>
identifier &operator=(Type &&value) {
value_ = std::forward<Type>(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 <typename Type, std::enable_if_t<is_identifier_supported_v<Type>, int> = 0>
result<Type, error> as() const;
template <typename Type, std::enable_if_t<std::is_integral_v<Type> && !std::is_same_v<Type, bool>, int> = 0>
result<Type, error> convert() const;
friend std::ostream &operator<<(std::ostream &out, const identifier &id);
private:
explicit identifier(const std::shared_ptr<base> &id);
template<typename Type>
void assign_integral(Type value) {
if constexpr (std::is_signed_v<Type>) {
if (sizeof(Type) <= sizeof(int8_t)) {
value_ = static_cast<int8_t>(value);
} else if (sizeof(Type) <= sizeof(int16_t)) {
value_ = static_cast<int16_t>(value);
} else if (sizeof(Type) <= sizeof(int32_t)) {
value_ = static_cast<int32_t>(value);
} else {
value_ = static_cast<int64_t>(value);
}
} else {
if (sizeof(Type) <= sizeof(uint8_t)) {
value_ = static_cast<uint8_t>(value);
} else if (sizeof(Type) <= sizeof(uint16_t)) {
value_ = static_cast<uint16_t>(value);
} else if (sizeof(Type) <= sizeof(uint32_t)) {
value_ = static_cast<uint32_t>(value);
} else {
value_ = static_cast<uint64_t>(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<base> id_;
value_type value_{std::monostate{}};
};
template <typename Target, typename Source>
bool in_range(Source value) {
if constexpr (std::is_signed_v<Source> == std::is_signed_v<Target>) {
return value >= static_cast<Target>(std::numeric_limits<Source>::min()) &&
value <= static_cast<Target>(std::numeric_limits<Source>::max());
} else if constexpr (std::is_signed_v<Source> && !std::is_signed_v<Target>) {
if (value < 0) {
return false;
}
using UnsignedSource = std::make_unsigned_t<Source>;
return static_cast<UnsignedSource>(value) <= std::numeric_limits<Target>::max();
} else {
return value <= static_cast<std::make_unsigned_t<Target>>(std::numeric_limits<Target>::max());
}
}
template <typename Type, std::enable_if_t<is_identifier_supported_v<Type>, int>>
result<Type, error> identifier::as() const {
return std::visit([](const auto &v) -> result<Type, error> {
using StoredType = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<StoredType, std::monostate>) {
return failure(error{});
} else if constexpr (std::is_same_v<StoredType, Type>) {
return ok<Type>(v);
} else {
return failure(error{});
}
}, value_);
}
template <typename Type, std::enable_if_t<std::is_integral_v<Type> && !std::is_same_v<Type, bool>, int>>
result<Type, error> identifier::convert() const {
return std::visit([](const auto &v) -> result<Type, error> {
using Stored = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<Stored, std::monostate>) {
return failure(error{});
} else if constexpr (std::is_integral_v<Stored>) {
if (!in_range<Type>(v)) {
return failure(error{});
}
return ok<Type>(static_cast<Type>(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<matador::utils::identifier> {
struct std::hash<matador::utils::identifier> {
size_t operator()(const matador::utils::identifier &id) const noexcept {
return id.hash();
}
};
} // namespace std
}; // namespace std
#endif //MATADOR_IDENTIFIER_HPP

View File

@ -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 <typename ValueType>
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

View File

@ -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 <cstdint>
#include <string>
#include <utility>
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<PrimaryKeyType>(value_);
}
template <typename ObjectType>
void set(ObjectType &obj, const identifier& pk) {
pk_ = pk;
access::process(*this, obj);
}
template<typename PrimaryKeyType>
void on_primary_key(const char * /*id*/, PrimaryKeyType &pk, const primary_key_attribute & = default_pk_attributes) {
const auto value = pk_.as<PrimaryKeyType>();
if (!value) {
// Todo: throw error
}
pk = value.value();
}
template<typename ValueType>
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<typename T>
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<class P>
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<class P>
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<class C>
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<class C>
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<class C>
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<class V>
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<std::uint64_t>(pk) == 0ULL);
}
template<class PrimaryKeyType>
void on_primary_key(const char * /*id*/, PrimaryKeyType &pk, const primary_key_attribute & = default_pk_attributes) {
unset = !identifier_type_traits<PrimaryKeyType>::is_valid(pk);
}
static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {}
template<typename T>
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<class P>
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<class P>
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<class C>
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<class C>
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<class C>
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<class V>
void on_primary_key(const char * /*id*/, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) {
value = static_cast<std::uint64_t>(pk);
template<class PrimaryKeyType>
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<typename T>
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<class P>
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<class P>
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<class C>
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<class C>
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<class C>
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<typename PrimaryKeyType>
class primary_key_accessor {
public:
template<class Type>
identifier get(const Type &obj) {
access::process(pk_getter_, obj);
return pk_getter_.pk_;
}
template<class Type>
void set(Type &obj, const identifier &pk) {
pk_setter_.set(obj, pk);
}
template<class Type>
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
};
}

View File

@ -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})

View File

@ -1,97 +1,165 @@
#include "matador/utils/identifier.hpp"
#include <cstring>
#include <stdexcept>
#include <ostream>
#include <stdexcept>
#include <utility>
namespace matador::utils {
size_t detail::hash(const char *value) {
return std::hash<std::string_view>()(std::string_view(value, std::strlen(value)));
}
bool identifier_type_traits<const char *>::is_valid(const char *value) {
return value != nullptr && strlen(value) > 0;
}
std::string identifier_type_traits<const char *>::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<null_type_t>::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<null_type_t>::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_t>()(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>()(std::string_view(value));
}
identifier::identifier()
: id_(std::make_shared<null_pk>()) {
: 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<decltype(v)>;
if constexpr (std::is_same_v<T, std::monostate>) {
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<decltype(v)>;
if constexpr (std::is_same_v<T, std::monostate>) {
return basic_type::Null;
} else {
return data_type_traits<T>::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<decltype(lhs)>;
using R = std::decay_t<decltype(rhs)>;
if constexpr (std::is_same_v<L, R>) {
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<decltype(v)>;
if constexpr (std::is_same_v<T, std::monostate>) {
return identifier_type_traits<null_type_t>::to_string();
} else if constexpr (std::is_same_v<T, std::string>) {
return identifier_type_traits<std::string>::to_string(v);
} else {
return identifier_type_traits<T>::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<int8_t>(value_)
|| std::holds_alternative<int16_t>(value_)
|| std::holds_alternative<int32_t>(value_)
|| std::holds_alternative<int64_t>(value_)
|| std::holds_alternative<uint8_t>(value_)
|| std::holds_alternative<uint16_t>(value_)
|| std::holds_alternative<uint32_t>(value_)
|| std::holds_alternative<uint64_t>(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<std::string>(value_);
}
bool identifier::is_null() const {
return type_index() == null_identifier.type_index();
return std::holds_alternative<std::monostate>(value_);
}
bool identifier::is_valid() const {
return id_->is_valid();
return std::visit([](const auto &v) -> bool {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, std::monostate>) {
return identifier_type_traits<null_type_t>::is_valid();
} else {
return identifier_type_traits<T>::is_valid(v);
}
}, value_);
}
void identifier::clear() {
id_ = std::make_unique<null_pk>();
value_ = std::monostate{};
}
void identifier::serialize(identifier_serializer &s) const {
id_->serialize(s);
std::visit([&s](const auto &v) {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, std::monostate>) {
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<base> &id)
: id_(id) {
return std::visit([](const auto &v) -> size_t {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, std::monostate>) {
return std::hash<int>{}(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

View File

@ -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<int16_t>::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 &) {
}
}

View File

@ -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)

View File

@ -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);
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<const char*>(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<int32_t>();
REQUIRE(as_i32.is_ok());
REQUIRE(*as_i32 == 42);
const auto as_i64 = id.as<int64_t>();
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<std::string>();
REQUIRE(as_string.is_ok());
REQUIRE(*as_string == "hello");
const auto as_i32 = id.as<int32_t>();
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<int32_t>();
REQUIRE(as_i32.is_error());
const auto as_string = id.as<std::string>();
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<int64_t>();
REQUIRE(as_i64.is_ok());
REQUIRE(*as_i64 == 123);
const auto as_u8 = id.convert<uint8_t>();
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<uint8_t>();
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<uint32_t>();
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<int32_t>();
REQUIRE(as_i32.is_error());
identifier null_id{nullptr};
const auto null_to_i64 = null_id.convert<int64_t>();
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<int32_t>();
REQUIRE(as_i32.is_ok());
REQUIRE(*as_i32 == 500);
const auto as_u8 = id.convert<uint8_t>();
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<uint64_t>();
REQUIRE(as_u64.is_ok());
REQUIRE(*as_u64 == 77);
const auto conv_u64 = id.convert<uint64_t>();
REQUIRE(conv_u64.is_ok());
REQUIRE(*conv_u64 == 77);
}

View File

@ -0,0 +1,68 @@
#include <catch2/catch_test_macros.hpp>
#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<unsigned int>().is_ok());
REQUIRE(*pk.as<unsigned int>() == 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));
}