| @@ -3,6 +3,14 @@ | |||
| #include <boost/hana.hpp> | |||
| #include <cpputils/mp/core.h> | |||
| #define cpphibernate_debug | |||
| #ifdef cpphibernate_debug | |||
| # include <cpputils/logging/global.h> | |||
| # define cpphibernate_debug_log(...) log_global_message(debug) << __VA_ARGS__ | |||
| #else | |||
| # define cpphibernate_debug_log(...) do { } while(0) | |||
| #endif | |||
| #define cpphibernate_equality_comparable() \ | |||
| template<typename T_other> \ | |||
| constexpr decltype(auto) operator==(T_other&&) const \ | |||
| @@ -0,0 +1,6 @@ | |||
| #pragma once | |||
| #include <cpphibernate/driver/mariadb/helper/context.h> | |||
| #include <cpphibernate/driver/mariadb/helper/key_properties.h> | |||
| #include <cpphibernate/driver/mariadb/helper/transaction_lock.h> | |||
| #include <cpphibernate/driver/mariadb/helper/type_properties.h> | |||
| @@ -0,0 +1,20 @@ | |||
| #pragma once | |||
| #include <cppmariadb.h> | |||
| #include <cpphibernate/config.h> | |||
| #include <cpphibernate/driver/mariadb/schema/schema.fwd.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| /* init_context */ | |||
| struct init_context | |||
| { | |||
| const schema_t& schema; | |||
| ::cppmariadb::connection& connection; | |||
| bool recreate; | |||
| }; | |||
| } | |||
| end_namespace_cpphibernate_driver_mariadb | |||
| @@ -0,0 +1,50 @@ | |||
| #pragma once | |||
| #include <cpphibernate/types.h> | |||
| #include <cpphibernate/config.h> | |||
| #include <cpputils/container/nullable.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| /* key_properties */ | |||
| template<typename T_key, typename = void> | |||
| struct key_properties; | |||
| template<> | |||
| struct key_properties<uuid, void> | |||
| { | |||
| using auto_generated = mp::c_false_t; | |||
| using key_type = uuid; | |||
| static constexpr decltype(auto) create_table_argument = ""; | |||
| static constexpr decltype(auto) create_key_query = "SELECT Uuid()"; | |||
| static constexpr decltype(auto) convert_to_open = "UuidToBin("; | |||
| static constexpr decltype(auto) convert_to_close = ")"; | |||
| static constexpr decltype(auto) convert_from_open = "BinToUuid("; | |||
| static constexpr decltype(auto) convert_from_close = ")"; | |||
| static bool is_default(const key_type& key) | |||
| { return key == key_type(); } | |||
| }; | |||
| template<typename T_key> | |||
| struct key_properties<T_key, mp::enable_if<mp::is_integral<T_key>>> | |||
| { | |||
| using auto_generated = mp::c_true_t; | |||
| using key_type = T_key; | |||
| static constexpr decltype(auto) create_table_argument = "AUTO_INCREMENT"; | |||
| static constexpr decltype(auto) create_key_query = ""; | |||
| static constexpr decltype(auto) convert_to_open = ""; | |||
| static constexpr decltype(auto) convert_to_close = ""; | |||
| static constexpr decltype(auto) convert_from_open = ""; | |||
| static constexpr decltype(auto) convert_from_close = ""; | |||
| static bool is_default(const key_type& key) | |||
| { return key == key_type(); } | |||
| }; | |||
| } | |||
| end_namespace_cpphibernate_driver_mariadb | |||
| @@ -0,0 +1,93 @@ | |||
| #pragma once | |||
| #include <memory> | |||
| #include <iomanip> | |||
| #include <cppmariadb.h> | |||
| #include <cpphibernate/config.h> | |||
| #include <cpputils/misc/type_helper.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| struct transaction_lock final | |||
| { | |||
| public: | |||
| inline transaction_lock(::cppmariadb::connection& con) | |||
| { begin_transaction(*this, con); } | |||
| inline ~transaction_lock() | |||
| { close_transaction(*this); } | |||
| inline bool commit() | |||
| { return commit_transaction(*this); } | |||
| private: | |||
| using transaction_ptr_type = std::unique_ptr<::cppmariadb::transaction>; | |||
| #ifdef cpphibernate_debug | |||
| # define debug_log(str) cpphibernate_debug_log( \ | |||
| "transaction (id=" << std::setw(8) << std::setfill(' ') << lock.id << \ | |||
| ", counter=" << std::setw(2) << std::setfill(' ') << counter << ") " str) | |||
| struct counter { }; | |||
| size_t id { utl::unique_counter<counter>::next() }; | |||
| #else | |||
| # define debug_log(str) do { } while(0) | |||
| #endif | |||
| static size_t& ref_counter() | |||
| { | |||
| static size_t value = 0; | |||
| return value; | |||
| } | |||
| static transaction_ptr_type& ref_transaction_ptr() | |||
| { | |||
| static transaction_ptr_type value; | |||
| return value; | |||
| } | |||
| static void begin_transaction(const transaction_lock& lock, ::cppmariadb::connection& con) | |||
| { | |||
| auto& counter = ref_counter(); | |||
| ++counter; | |||
| debug_log("+++"); | |||
| if (counter == 1) | |||
| { | |||
| debug_log("begin"); | |||
| ref_transaction_ptr().reset(new ::cppmariadb::transaction(con)); | |||
| } | |||
| } | |||
| static bool commit_transaction(const transaction_lock& lock) | |||
| { | |||
| auto& counter = ref_counter(); | |||
| if (counter == 1) | |||
| { | |||
| debug_log("commit"); | |||
| ref_transaction_ptr()->commit(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| static void close_transaction(const transaction_lock& lock) | |||
| { | |||
| auto& counter = ref_counter(); | |||
| debug_log("---"); | |||
| if (counter <= 1) | |||
| { | |||
| debug_log("close"); | |||
| counter = 0; | |||
| ref_transaction_ptr().reset(); | |||
| } | |||
| else | |||
| { | |||
| --counter; | |||
| } | |||
| } | |||
| }; | |||
| #undef debug_log | |||
| } | |||
| end_namespace_cpphibernate_driver_mariadb | |||
| @@ -0,0 +1,346 @@ | |||
| #pragma once | |||
| #include <cpphibernate/misc.h> | |||
| #include <cpphibernate/types.h> | |||
| #include <cpphibernate/config.h> | |||
| #include <cpputils/container/nullable.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| /* value_t */ | |||
| using value_t = utl::nullable<std::string>; | |||
| /* type_properties */ | |||
| template<typename T, typename = void> | |||
| struct type_properties | |||
| { | |||
| static constexpr void type() = delete; | |||
| static T convert_to(const value_t&) = delete; | |||
| static value_t convert_from(const T&) = delete; | |||
| }; | |||
| template<> | |||
| struct type_properties<bool, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "BOOLEAN"; } | |||
| static inline bool convert_to(const value_t& value) | |||
| { return utl::from_string<int>(*value); } | |||
| static inline value_t convert_from(const bool& value) | |||
| { return utl::to_string(static_cast<int>(value)); } | |||
| }; | |||
| template<> | |||
| struct type_properties<uint8_t, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "TINYINT UNSIGNED"; } | |||
| static inline uint8_t convert_to(const value_t& value) | |||
| { return static_cast<uint8_t>(utl::from_string<int>(*value)); } | |||
| static inline value_t convert_from(const uint8_t& value) | |||
| { return utl::to_string(static_cast<int>(value)); } | |||
| }; | |||
| template<> | |||
| struct type_properties<int8_t, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "TINYINT"; } | |||
| static inline int8_t convert_to(const value_t& value) | |||
| { return static_cast<int8_t>(utl::from_string<int>(*value)); } | |||
| static inline value_t convert_from(const int8_t& value) | |||
| { return utl::to_string(static_cast<int>(value)); } | |||
| }; | |||
| template<> | |||
| struct type_properties<uint16_t, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "SMALLINT UNSIGNED"; } | |||
| static inline uint16_t convert_to(const value_t& value) | |||
| { return utl::from_string<uint16_t>(*value); } | |||
| static inline value_t convert_from(const uint16_t& value) | |||
| { return utl::to_string(value); } | |||
| }; | |||
| template<> | |||
| struct type_properties<int16_t, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "SMALLINT"; } | |||
| static inline int16_t convert_to(const value_t& value) | |||
| { return utl::from_string<int16_t>(*value); } | |||
| static inline value_t convert_from(const int16_t& value) | |||
| { return utl::to_string(value); } | |||
| }; | |||
| template<> | |||
| struct type_properties<uint32_t, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "INT UNSIGNED"; } | |||
| static inline uint32_t convert_to(const value_t& value) | |||
| { return utl::from_string<uint32_t>(*value); } | |||
| static inline value_t convert_from(const uint32_t& value) | |||
| { return utl::to_string(value); } | |||
| }; | |||
| template<> | |||
| struct type_properties<int32_t, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "INT"; } | |||
| static inline int32_t convert_to(const value_t& value) | |||
| { return utl::from_string<int32_t>(*value); } | |||
| static inline value_t convert_from(const int32_t& value) | |||
| { return utl::to_string(value); } | |||
| }; | |||
| template<> | |||
| struct type_properties<uint64_t, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "BIGINT UNSIGNED"; } | |||
| static inline uint64_t convert_to(const value_t& value) | |||
| { return utl::from_string<uint64_t>(*value); } | |||
| static inline value_t convert_from(const uint64_t& value) | |||
| { return utl::to_string(value); } | |||
| }; | |||
| template<> | |||
| struct type_properties<int64_t, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "BIGINT"; } | |||
| static inline int64_t convert_to(const value_t& value) | |||
| { return utl::from_string<int64_t>(*value); } | |||
| static inline value_t convert_from(const int64_t& value) | |||
| { return utl::to_string(value); } | |||
| }; | |||
| template<> | |||
| struct type_properties<float, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "FLOAT"; } | |||
| static inline float convert_to(const value_t& value) | |||
| { return utl::from_string<float>(*value); } | |||
| static inline value_t convert_from(const float& value) | |||
| { return utl::to_string(value); } | |||
| }; | |||
| template<> | |||
| struct type_properties<double, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "DOUBLE"; } | |||
| static inline double convert_to(const value_t& value) | |||
| { return utl::from_string<double>(*value); } | |||
| static inline value_t convert_from(const double& value) | |||
| { return utl::to_string(value); } | |||
| }; | |||
| template<> | |||
| struct type_properties<uuid, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "BINARY(16)"; } | |||
| static inline uuid convert_to(const value_t& value) | |||
| { return utl::from_string<uuid>(*value); } | |||
| static inline value_t convert_from(const uuid& value) | |||
| { return utl::to_string(value); } | |||
| }; | |||
| template<> | |||
| struct type_properties<std::string, void> | |||
| { | |||
| static constexpr decltype(auto) type() | |||
| { return "VARCHAR(100)"; } | |||
| static inline std::string convert_to(const value_t& value) | |||
| { return *value; } | |||
| static inline value_t convert_from(const std::string& value) | |||
| { return value; } | |||
| }; | |||
| template<size_t N> | |||
| struct type_properties<string<N>, void> | |||
| { | |||
| static inline std::string make_type() | |||
| { return std::string("VARCHAR(") + utl::to_string(N) + ")"; } | |||
| static inline decltype(auto) type() | |||
| { | |||
| static const std::string v = make_type(); | |||
| return v; | |||
| } | |||
| static inline std::string convert_to(const value_t& value) | |||
| { return *value; } | |||
| static inline value_t convert_from(const std::string& value) | |||
| { return value; } | |||
| }; | |||
| template<typename T> | |||
| struct type_properties<T, mp::enable_if<misc::is_nullable<mp::clean_type<T>>>> | |||
| { | |||
| using nullable_type = T; | |||
| using nullable_helper_type = misc::nullable_helper<nullable_type>; | |||
| using value_type = typename nullable_helper_type::value_type; | |||
| using value_type_props = type_properties<value_type>; | |||
| static constexpr decltype(auto) type() | |||
| { return value_type_props::type(); } | |||
| static inline nullable_type convert_to(const value_t& value) | |||
| { | |||
| auto ret = nullable_helper_type::make(); | |||
| if (value.has_value()) | |||
| nullable_helper_type::fill(ret, value_type_props::convert_to(value)); | |||
| return ret; | |||
| } | |||
| static inline value_t convert_from(const nullable_type& value) | |||
| { | |||
| value_t ret; | |||
| auto v = nullable_helper_type::get(value); | |||
| if (v) | |||
| ret = value_type_props::convert_from(*v); | |||
| return ret; | |||
| } | |||
| }; | |||
| template<typename T> | |||
| struct type_properties<T, mp::enable_if<std::is_enum<mp::clean_type<T>>>> | |||
| { | |||
| using enum_type = T; | |||
| using base_type = typename std::underlying_type<enum_type>::type; | |||
| static std::string make_type() | |||
| { | |||
| std::ostringstream os; | |||
| os << "ENUM ( "; | |||
| auto e = enum_type::first; | |||
| while (e <= enum_type::last) | |||
| { | |||
| if (e != enum_type::first) | |||
| os << ", "; | |||
| os << "'" << utl::enum_conversion<enum_type>::to_string(e, false) << "'"; | |||
| e = static_cast<enum_type>(static_cast<base_type>(e) + 1); | |||
| } | |||
| os << " )"; | |||
| return os.str(); | |||
| } | |||
| static inline decltype(auto) type() | |||
| { | |||
| static const std::string v = make_type(); | |||
| return v; | |||
| } | |||
| static inline enum_type convert_to(const value_t& value) | |||
| { | |||
| enum_type ret; | |||
| if (!utl::enum_conversion<enum_type>::try_to_enum(*value, ret, false)) | |||
| throw misc::hibernate_exception("unable to convert enum value!"); | |||
| return ret; | |||
| } | |||
| static inline value_t convert_from(const enum_type& value) | |||
| { return utl::enum_conversion<enum_type>::to_string(value, false); } | |||
| }; | |||
| template<typename T> | |||
| struct type_properties<T, mp::enable_if<mp::is_specialization_of<mp::clean_type<T>, utl::flags>>> | |||
| { | |||
| using flags_type = T; | |||
| using enum_type = typename flags_type::enum_type; | |||
| using base_type = typename std::underlying_type<enum_type>::type; | |||
| static inline std::string make_type() | |||
| { | |||
| std::ostringstream os; | |||
| os << "SET ( "; | |||
| auto e = enum_type::first; | |||
| while (e <= enum_type::last) | |||
| { | |||
| if (e != enum_type::first) | |||
| os << ", "; | |||
| os << "'" << utl::to_string(e) << "'"; | |||
| e = static_cast<enum_type>(static_cast<base_type>(e) + 1); | |||
| } | |||
| os << " )"; | |||
| return os.str(); | |||
| } | |||
| static inline decltype(auto) type() | |||
| { | |||
| static const std::string v = make_type(); | |||
| return v; | |||
| } | |||
| static inline flags_type convert_to(const value_t& value) | |||
| { | |||
| auto s = *value; | |||
| auto c = s.c_str(); | |||
| auto e = c + s.size(); | |||
| auto p = c; | |||
| flags_type ret; | |||
| while (c <= e) | |||
| { | |||
| if (c == e || *c == ',') | |||
| { | |||
| if (c - p > 0) | |||
| ret.set(utl::enum_conversion<enum_type>::to_enum(std::string(p, static_cast<size_t>(c - p)), true)); | |||
| p = c + 1; | |||
| } | |||
| ++c; | |||
| } | |||
| return ret; | |||
| } | |||
| static inline value_t convert_from(const flags_type& value) | |||
| { | |||
| std::ostringstream os; | |||
| bool first = true; | |||
| for (auto e : value) | |||
| { | |||
| if (first) first = false; | |||
| else os << ","; | |||
| utl::to_string(os, static_cast<int>(e)); | |||
| } | |||
| return os.str(); | |||
| } | |||
| }; | |||
| } | |||
| end_namespace_cpphibernate_driver_mariadb | |||
| @@ -0,0 +1,3 @@ | |||
| #pragma once | |||
| #include <cpphibernate/driver/mariadb/impl/init.h> | |||
| @@ -0,0 +1,44 @@ | |||
| #pragma once | |||
| #include <cppmariadb.h> | |||
| #include <cpphibernate/config.h> | |||
| #include <cpphibernate/driver/mariadb/helper.h> | |||
| #include <cpphibernate/driver/mariadb/schema.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| /* init_impl */ | |||
| template<typename T_context, typename = void> | |||
| struct init_impl | |||
| { | |||
| using context_type = T_context; | |||
| context_type context; | |||
| constexpr init_impl(T_context&& p_context) | |||
| : context(std::forward<T_context>(p_context)) | |||
| { } | |||
| /* operator() */ | |||
| inline void operator()() const | |||
| { | |||
| auto& schema = context.schema; | |||
| auto& connection = context.connection; | |||
| transaction_lock trans(connection); | |||
| schema.init(context); | |||
| trans.commit(); | |||
| } | |||
| }; | |||
| /* make_init_impl */ | |||
| template<typename T_context> | |||
| constexpr decltype(auto) make_init_impl(T_context&& context) | |||
| { return init_impl<T_context>(std::forward<T_context>(context)); } | |||
| } | |||
| end_namespace_cpphibernate_driver_mariadb | |||
| @@ -2,11 +2,12 @@ | |||
| #include <cppmariadb.h> | |||
| #include <cpphibernate/config.h> | |||
| #include <cpphibernate/driver/mariadb/impl.h> | |||
| #include <cpphibernate/driver/mariadb/schema.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| struct mariadb_driver_t | |||
| { | |||
| private: | |||
| @@ -23,11 +24,16 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| cpphibernate_copyable(mariadb_driver_t, delete); | |||
| cpphibernate_moveable(mariadb_driver_t, default); | |||
| inline decltype(auto) connection() | |||
| { return _connection; } | |||
| inline auto& schema() | |||
| { return _schema; } | |||
| protected: | |||
| inline void init_impl(bool recreate) const | |||
| { | |||
| make_init_impl(init_context | |||
| { | |||
| _schema, | |||
| _connection, | |||
| recreate | |||
| })(); | |||
| } | |||
| }; | |||
| } | |||
| @@ -12,4 +12,6 @@ | |||
| #include <cpphibernate/driver/mariadb/schema/fields.h> | |||
| #include <cpphibernate/driver/mariadb/schema/schema.h> | |||
| #include <cpphibernate/driver/mariadb/schema/table.h> | |||
| #include <cpphibernate/driver/mariadb/schema/tables.h> | |||
| #include <cpphibernate/driver/mariadb/schema/tables.h> | |||
| #include <cpphibernate/driver/mariadb/schema/field.inl> | |||
| @@ -5,13 +5,14 @@ | |||
| #include <cpphibernate/schema/field.h> | |||
| #include <cpphibernate/schema/table.h> | |||
| #include <cpphibernate/schema/schema.h> | |||
| #include <cpphibernate/driver/mariadb/helper.h> | |||
| #include <cpphibernate/driver/mariadb/schema/field.fwd.h> | |||
| #include <cpphibernate/driver/mariadb/schema/table.fwd.h> | |||
| #include <cpphibernate/driver/mariadb/schema/attributes.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| /* field_t */ | |||
| struct field_t | |||
| @@ -42,9 +43,13 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| , table (nullptr) | |||
| , referenced_table (nullptr) | |||
| { } | |||
| virtual ~field_t() = default; | |||
| virtual ~field_t() { }; | |||
| void print(std::ostream& os) const; | |||
| /* properties */ | |||
| virtual std::string type () const; | |||
| virtual std::string create_table_arguments () const; | |||
| }; | |||
| /* simple_field_t */ | |||
| @@ -72,9 +77,17 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| struct value_field_t | |||
| : public simple_field_t<T_schema, T_field> | |||
| { | |||
| using base_type = simple_field_t<T_schema, T_field>; | |||
| using base_type = simple_field_t<T_schema, T_field>; | |||
| using schema_type = T_schema; | |||
| using field_type = T_field; | |||
| using getter_type = typename mp::decay_t<field_type>::getter_type; | |||
| using dataset_type = typename getter_type::dataset_type; | |||
| using value_type = typename getter_type::value_type; | |||
| using type_props = type_properties<value_type>; | |||
| using base_type::base_type; | |||
| virtual std::string type() const override; | |||
| }; | |||
| /* primary_key_field_t */ | |||
| @@ -83,9 +96,15 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| struct primary_key_field_t | |||
| : public value_field_t<T_schema, T_field> | |||
| { | |||
| using base_type = value_field_t<T_schema, T_field>; | |||
| using base_type = value_field_t<T_schema, T_field>; | |||
| using schema_type = typename base_type::schema_type; | |||
| using field_type = typename base_type::field_type; | |||
| using value_type = typename base_type::value_type; | |||
| using key_props = key_properties<value_type>; | |||
| using base_type::base_type; | |||
| virtual std::string create_table_arguments() const override; | |||
| }; | |||
| /* data_field_t */ | |||
| @@ -179,15 +198,21 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| using dataset_type = mp::decay_t<typename getter_type::dataset_type>; | |||
| using value_dataset_type = misc::real_dataset_t<value_type>; | |||
| using return_type = field_type_t<schema_type, field_type>; | |||
| using primary_key_type = primary_key_field_t<schema_type, field_type>; | |||
| return_type ret(schema, field); | |||
| ret.table_dataset_id = misc::get_type_id(hana::type_c<dataset_type>), | |||
| ret.value_dataset_id = misc::get_type_id(hana::type_c<value_dataset_type>), | |||
| ret.value_is_nullable = misc::is_nullable<value_type>::value, | |||
| ret.value_is_container = misc::is_container<value_type>::value, | |||
| ret.schema_name = schema.name, | |||
| ret.table_name = table.name, | |||
| ret.field_name = field.name, | |||
| ret.table_dataset_id = misc::get_type_id(hana::type_c<dataset_type>); | |||
| ret.value_dataset_id = misc::get_type_id(hana::type_c<value_dataset_type>); | |||
| ret.value_is_nullable = misc::is_nullable<value_type>::value; | |||
| ret.value_is_container = misc::is_container<value_type>::value; | |||
| ret.schema_name = schema.name; | |||
| ret.table_name = table.name; | |||
| ret.field_name = field.name; | |||
| ret.attributes = make_attributes(field.attributes); | |||
| hana::eval_if( | |||
| hana::equal(hana::type_c<return_type>, hana::type_c<primary_key_type>), | |||
| [&ret](){ | |||
| ret.field_name = ret.table_name + "_" + ret.field_name; | |||
| }, [](){ }); | |||
| return ret; | |||
| } | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| #pragma once | |||
| #include <cpphibernate/driver/mariadb/schema/field.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| /* value_field_t */ | |||
| template<typename T_schema, typename T_field> | |||
| std::string value_field_t<T_schema, T_field>::type() const | |||
| { return type_props::type(); } | |||
| /* primary_key_field_t */ | |||
| template<typename T_schema, typename T_field> | |||
| std::string primary_key_field_t<T_schema, T_field>::create_table_arguments() const | |||
| { return key_props::create_table_argument; } | |||
| } | |||
| end_namespace_cpphibernate_driver_mariadb | |||
| @@ -2,12 +2,13 @@ | |||
| #include <cpphibernate/misc.h> | |||
| #include <cpphibernate/config.h> | |||
| #include <cpphibernate/driver/mariadb/helper/context.h> | |||
| #include <cpphibernate/driver/mariadb/schema/tables.h> | |||
| #include <cpphibernate/driver/mariadb/schema/schema.fwd.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| /* schema_t */ | |||
| struct schema_t | |||
| @@ -24,6 +25,9 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| void update (); | |||
| void print (std::ostream& os) const; | |||
| /* CRUD */ | |||
| void init(const init_context& context) const; | |||
| }; | |||
| namespace __impl | |||
| @@ -3,16 +3,18 @@ | |||
| #include <memory> | |||
| #include <vector> | |||
| #include <cppmariadb.h> | |||
| #include <cpphibernate/misc.h> | |||
| #include <cpphibernate/config.h> | |||
| #include <cpphibernate/schema/table.h> | |||
| #include <cpphibernate/schema/schema.h> | |||
| #include <cpphibernate/driver/mariadb/schema/fields.h> | |||
| #include <cpphibernate/driver/mariadb/schema/table.fwd.h> | |||
| #include <cpphibernate/driver/mariadb/helper/context.h> | |||
| beg_namespace_cpphibernate_driver_mariadb | |||
| { | |||
| /* table_t */ | |||
| struct table_t | |||
| @@ -56,9 +58,23 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| , foreign_table_many_fields () | |||
| , data_fields () | |||
| { } | |||
| virtual ~table_t() = default; | |||
| virtual ~table_t() { }; | |||
| void print(std::ostream& os) const; | |||
| /* CRUD */ | |||
| inline void init(const init_context& context) const | |||
| { return init_intern(context); } | |||
| private: | |||
| using statement_ptr = std::unique_ptr<::cppmariadb::statement>; | |||
| mutable statement_ptr _statement_create_table; | |||
| ::cppmariadb::statement& get_statement_create_table() const; | |||
| protected: | |||
| void init_intern(const init_context& context) const; | |||
| }; | |||
| /* table_simple_t */ | |||
| @@ -70,7 +86,7 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| using schema_type = T_schema; | |||
| using table_type = T_table; | |||
| using base_dataset_type = T_base_dataset; | |||
| const schema_type& schema; | |||
| const table_type& table; | |||
| @@ -98,9 +114,9 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| using schema_type = T_schema; | |||
| using table_type = T_table; | |||
| using base_dataset_type = T_base_dataset; | |||
| using base_type::base_type; | |||
| const schema_type& schema; | |||
| const table_type& table; | |||
| }; | |||
| @@ -2,5 +2,6 @@ | |||
| #include <cpphibernate/misc/general.h> | |||
| #include <cpphibernate/misc/meta.h> | |||
| #include <cpphibernate/misc/nullable_helper.h> | |||
| #include <cpphibernate/misc/print.h> | |||
| #include <cpphibernate/misc/wrap.h> | |||
| @@ -0,0 +1,76 @@ | |||
| #pragma once | |||
| #include <memory> | |||
| #include <cpphibernate/config.h> | |||
| #include <cpphibernate/misc/meta.h> | |||
| #include <cpputils/container/nullable.h> | |||
| beg_namespace_cpphibernate_misc | |||
| { | |||
| /* nullable_helper */ | |||
| template<typename T_nullable, typename = void> | |||
| struct nullable_helper | |||
| { | |||
| using nullable_type = T_nullable; | |||
| using value_type = real_dataset_t<nullable_type>; | |||
| static value_type* get (nullable_type&) = delete; | |||
| static void reset (nullable_type&, value_type* value = nullptr) = delete; | |||
| }; | |||
| /* nullable_helper - utl::nullable */ | |||
| template<typename T_value> | |||
| struct nullable_helper<utl::nullable<T_value>, void> | |||
| { | |||
| using nullable_type = utl::nullable<T_value>; | |||
| using value_type = T_value; | |||
| static inline value_type* get(nullable_type& x) | |||
| { | |||
| return x.has_value() ? &x.value() : nullptr; | |||
| } | |||
| static void reset(nullable_type& x, value_type* value = nullptr) | |||
| { | |||
| if (value) x.reset(); | |||
| else x = *value; | |||
| } | |||
| }; | |||
| /* nullable_helper - std::unique_ptr */ | |||
| template<typename T_value> | |||
| struct nullable_helper<std::unique_ptr<T_value>, void> | |||
| { | |||
| using nullable_type = std::unique_ptr<T_value>; | |||
| using value_type = T_value; | |||
| static inline value_type* get(nullable_type& x) | |||
| { return x.get(); } | |||
| static void reset(nullable_type& x, value_type* value = nullptr) | |||
| { return x.reset(value); } | |||
| }; | |||
| /* nullable_helper - std::shared_ptr */ | |||
| template<typename T_value> | |||
| struct nullable_helper<std::shared_ptr<T_value>, void> | |||
| { | |||
| using nullable_type = std::shared_ptr<T_value>; | |||
| using value_type = T_value; | |||
| static inline value_type* get(nullable_type& x) | |||
| { return x.get(); } | |||
| static void reset(nullable_type& x, value_type* value = nullptr) | |||
| { return x.reset(value); } | |||
| }; | |||
| } | |||
| end_namespace_cpphibernate_misc | |||
| @@ -9,6 +9,7 @@ | |||
| #include <cpphibernate/driver/mariadb/schema/field.h> | |||
| #include <cpphibernate/driver/mariadb/schema/table.h> | |||
| using namespace ::std; | |||
| using namespace ::utl; | |||
| using namespace ::cpphibernate::driver::mariadb_impl; | |||
| @@ -28,4 +29,15 @@ void field_t::print(std::ostream& os) const | |||
| << indent << "\"referenced_table\": " << (referenced_table ? std::string("\"") + referenced_table->table_name + "\"" : "null") | |||
| << decindent | |||
| << indent << '}'; | |||
| } | |||
| } | |||
| #define throw_not_implemented(p_ret, p_name) \ | |||
| p_ret field_t::p_name() const \ | |||
| { \ | |||
| throw misc::hibernate_exception( \ | |||
| std::string("'") + table_name + "." + field_name + \ | |||
| "' does not implement the " #p_name "() method!"); \ | |||
| } | |||
| throw_not_implemented(string, type) | |||
| throw_not_implemented(string, create_table_arguments) | |||
| @@ -1,5 +1,5 @@ | |||
| #include <string> | |||
| #include <iostream> | |||
| #include <sstream> | |||
| #include <cpputils/misc/enum.h> | |||
| #include <cpputils/misc/string.h> | |||
| @@ -109,4 +109,83 @@ void schema_t::print(std::ostream& os) const | |||
| }) | |||
| << decindent | |||
| << indent << '}'; | |||
| } | |||
| } | |||
| #define exec_query() \ | |||
| do { \ | |||
| cpphibernate_debug_log("execute init query: " << ss.str()); \ | |||
| connection.execute(ss.str()); \ | |||
| ss.str(std::string()); \ | |||
| ss.clear(); \ | |||
| } while(0) | |||
| void schema_t::init(const init_context& context) const | |||
| { | |||
| std::ostringstream ss; | |||
| auto& connection = context.connection; | |||
| if (context.recreate) | |||
| { | |||
| ss << "DROP DATABASE IF EXISTS `" | |||
| << schema_name | |||
| << "`"; | |||
| exec_query(); | |||
| } | |||
| /* create schema */ | |||
| ss << "CREATE SCHEMA IF NOT EXISTS `" | |||
| << schema_name | |||
| << "` DEFAULT CHARACTER SET utf8"; | |||
| exec_query(); | |||
| /* use schema */ | |||
| ss << "USE `" | |||
| << schema_name | |||
| << "`"; | |||
| exec_query(); | |||
| /* UuidToBin */ | |||
| ss << "CREATE FUNCTION IF NOT EXISTS UuidToBin(_uuid CHAR(36))\n" | |||
| " RETURNS BINARY(16)\n" | |||
| " LANGUAGE SQL\n" | |||
| " DETERMINISTIC\n" | |||
| " CONTAINS SQL\n" | |||
| " SQL SECURITY INVOKER\n" | |||
| "RETURN\n" | |||
| " UNHEX(CONCAT(\n" | |||
| " SUBSTR(_uuid, 25, 12),\n" // node id | |||
| " SUBSTR(_uuid, 20, 4),\n" // clock sequence | |||
| " SUBSTR(_uuid, 15, 4),\n" // time high and version | |||
| " SUBSTR(_uuid, 10, 4),\n" // time mid | |||
| " SUBSTR(_uuid, 1, 8)\n" // time low | |||
| " )\n" | |||
| ")"; | |||
| exec_query(); | |||
| /* BinToUuid */ | |||
| ss << "CREATE FUNCTION IF NOT EXISTS BinToUuid(_bin BINARY(16))\n" | |||
| " RETURNS CHAR(36)\n" | |||
| " LANGUAGE SQL\n" | |||
| " DETERMINISTIC\n" | |||
| " CONTAINS SQL\n" | |||
| " SQL SECURITY INVOKER\n" | |||
| "RETURN\n" | |||
| " LCASE(CONCAT_WS('-',\n" | |||
| " HEX(SUBSTR(_bin, 13, 4)),\n" // time low | |||
| " HEX(SUBSTR(_bin, 11, 2)),\n" // time mid | |||
| " HEX(SUBSTR(_bin, 9, 2)),\n" // time high and version | |||
| " HEX(SUBSTR(_bin, 7, 2)),\n" // clock sequence | |||
| " HEX(SUBSTR(_bin, 1, 6))\n" // node id | |||
| " )\n" | |||
| ")"; | |||
| exec_query(); | |||
| /* initialize tables */ | |||
| for (auto& kvp : tables) | |||
| { | |||
| assert(kvp.second); | |||
| kvp.second->init(context); | |||
| } | |||
| } | |||
| #undef exec_query | |||
| @@ -46,4 +46,311 @@ void table_t::print(std::ostream& os) const | |||
| }) | |||
| << decindent | |||
| << indent << '}'; | |||
| } | |||
| ::cppmariadb::statement& table_t::get_statement_create_table() const | |||
| { | |||
| if (_statement_create_table) | |||
| return *_statement_create_table; | |||
| std::ostringstream os; | |||
| /* CREATE TABLE */ | |||
| os << "CREATE TABLE IF NOT EXISTS `" | |||
| << table_name | |||
| << "`" | |||
| << indent | |||
| << "(" | |||
| << incindent; | |||
| /* primary key */ | |||
| { | |||
| assert(primary_key_field); | |||
| auto& key_info = *primary_key_field; | |||
| auto args = key_info.create_table_arguments(); | |||
| os << indent | |||
| << "`" | |||
| << key_info.field_name | |||
| << "` " | |||
| << key_info.type() | |||
| << " NOT NULL" | |||
| << (args.empty() ? "" : " ") | |||
| << args | |||
| << ","; | |||
| } | |||
| /* base table key fields */ | |||
| if (static_cast<bool>(base_table)) | |||
| { | |||
| auto& base_table_info = *base_table; | |||
| assert(base_table_info.primary_key_field); | |||
| auto& key_info = *base_table_info.primary_key_field; | |||
| os << indent | |||
| << "`" | |||
| << key_info.field_name | |||
| << "` " | |||
| << key_info.type() | |||
| << " NOT NULL,"; | |||
| } | |||
| /* foreign table one fields */ | |||
| for (auto& ptr : foreign_table_one_fields) | |||
| { | |||
| assert(static_cast<bool>(ptr)); | |||
| auto& field_info = *ptr; | |||
| assert(field_info.referenced_table); | |||
| assert(field_info.referenced_table->primary_key_field); | |||
| auto& ref_key_info = *field_info.referenced_table->primary_key_field; | |||
| os << indent | |||
| << "`" | |||
| << ref_key_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "` " | |||
| << ref_key_info.type() | |||
| << (field_info.value_is_nullable | |||
| ? " NULL DEFAULT NULL," | |||
| : " NOT NULL,"); | |||
| } | |||
| /* foreign fields */ | |||
| for (auto& ptr : foreign_key_fields) | |||
| { | |||
| assert(static_cast<bool>(ptr)); | |||
| auto& field_info = *ptr; | |||
| assert(field_info.table); | |||
| assert(field_info.table->primary_key_field); | |||
| auto& ref_key_info = *field_info.table->primary_key_field; | |||
| os << indent | |||
| << "`" | |||
| << field_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "` " | |||
| << ref_key_info.type() | |||
| << " NULL DEFAULT NULL,"; | |||
| } | |||
| /* data fields */ | |||
| for (auto& ptr : data_fields) | |||
| { | |||
| assert(static_cast<bool>(ptr)); | |||
| auto& field_info = *ptr; | |||
| os << indent | |||
| << "`" | |||
| << field_info.field_name | |||
| << "` " | |||
| << field_info.type() | |||
| << (field_info.value_is_nullable | |||
| ? " NULL DEFAULT NULL," | |||
| : " NOT NULL,"); | |||
| } | |||
| /* type field for derived tables */ | |||
| if (!derived_tables.empty() && | |||
| !base_table) | |||
| { | |||
| os << indent | |||
| << "`__type` INT UNSIGNED NOT NULL,"; | |||
| } | |||
| /* PRIMARY KEY */ | |||
| { | |||
| os << indent | |||
| << "PRIMARY KEY ( `" | |||
| << primary_key_field->field_name | |||
| << "` )"; | |||
| } | |||
| /* UNIQUE INDEX primary key */ | |||
| os << ',' | |||
| << indent | |||
| << "UNIQUE INDEX `index_" | |||
| << primary_key_field->field_name | |||
| << "` ( `" | |||
| << primary_key_field->field_name | |||
| << "` ASC )"; | |||
| /* UNIQUE INDEX base table keys */ | |||
| if (base_table) | |||
| { | |||
| auto& table_info = *base_table; | |||
| auto& key_info = *table_info.primary_key_field; | |||
| os << ',' | |||
| << indent | |||
| << "UNIQUE INDEX `index_" | |||
| << key_info.field_name | |||
| << "` ( `" | |||
| << key_info.field_name | |||
| << "` ASC )"; | |||
| } | |||
| /* INDEX foreign table one fields */ | |||
| for (auto& ptr : foreign_table_one_fields) | |||
| { | |||
| assert(static_cast<bool>(ptr)); | |||
| auto& field_info = *ptr; | |||
| assert(field_info.referenced_table); | |||
| assert(field_info.referenced_table->primary_key_field); | |||
| auto& ref_key_info = *field_info.referenced_table->primary_key_field; | |||
| os << "," | |||
| << indent | |||
| << "INDEX `index_" | |||
| << ref_key_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "` ( `" | |||
| << ref_key_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "` ASC )"; | |||
| } | |||
| /* INDEX foreign fields */ | |||
| for (auto& ptr : foreign_key_fields) | |||
| { | |||
| assert(static_cast<bool>(ptr)); | |||
| auto& field_info = *ptr; | |||
| os << "," | |||
| << indent | |||
| << "INDEX `index_" | |||
| << field_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "` ( `" | |||
| << field_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "` ASC )"; | |||
| } | |||
| /* CONSTRAINT base table */ | |||
| if (base_table) | |||
| { | |||
| assert(base_table->primary_key_field); | |||
| auto& ref_key_info = *base_table->primary_key_field; | |||
| os << "," | |||
| << indent | |||
| << "CONSTRAINT `fk_" | |||
| << table_name | |||
| << "_to_" | |||
| << ref_key_info.field_name | |||
| << "`" | |||
| << incindent | |||
| << indent | |||
| << "FOREIGN KEY (`" | |||
| << ref_key_info.field_name | |||
| << "`)" | |||
| << indent | |||
| << "REFERENCES `" | |||
| << ref_key_info.schema_name | |||
| << "`.`" | |||
| << ref_key_info.table_name | |||
| << "` (`" | |||
| << ref_key_info.field_name | |||
| << "`)" | |||
| << indent | |||
| << "ON DELETE CASCADE" | |||
| << indent | |||
| << "ON UPDATE NO ACTION" | |||
| << decindent; | |||
| } | |||
| /* CONSTRAINT foreign table one fields */ | |||
| for (auto& ptr : foreign_table_one_fields) | |||
| { | |||
| assert(static_cast<bool>(ptr)); | |||
| auto& field_info = *ptr; | |||
| assert(field_info.referenced_table); | |||
| assert(field_info.referenced_table->primary_key_field); | |||
| auto& ref_key_info = *field_info.referenced_table->primary_key_field; | |||
| os << "," | |||
| << indent | |||
| << "CONSTRAINT `fk_" | |||
| << table_name | |||
| << "_to_" | |||
| << ref_key_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "`" | |||
| << incindent | |||
| << indent | |||
| << "FOREIGN KEY (`" | |||
| << ref_key_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "`)" | |||
| << indent | |||
| << "REFERENCES `" | |||
| << ref_key_info.schema_name | |||
| << "`.`" | |||
| << ref_key_info.table_name | |||
| << "` (`" | |||
| << ref_key_info.field_name | |||
| << "`)" | |||
| << indent | |||
| << "ON DELETE CASCADE" | |||
| << indent | |||
| << "ON UPDATE NO ACTION" | |||
| << decindent; | |||
| } | |||
| /* CONSTRAINT foreign fields */ | |||
| for (auto& ptr : foreign_key_fields) | |||
| { | |||
| assert(static_cast<bool>(ptr)); | |||
| auto& field_info = *ptr; | |||
| assert(field_info.table); | |||
| assert(field_info.table->primary_key_field); | |||
| auto& ref_key_info = *field_info.table->primary_key_field; | |||
| os << "," | |||
| << indent | |||
| << "CONSTRAINT `fk_" | |||
| << table_name | |||
| << "_" | |||
| << field_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "`" | |||
| << incindent | |||
| << indent | |||
| << "FOREIGN KEY (`" | |||
| << field_info.table_name | |||
| << "_id_" | |||
| << field_info.field_name | |||
| << "`)" | |||
| << indent | |||
| << "REFERENCES `" | |||
| << ref_key_info.schema_name | |||
| << "`.`" | |||
| << ref_key_info.table_name | |||
| << "` (`" | |||
| << ref_key_info.field_name | |||
| << "`)" | |||
| << indent | |||
| << "ON DELETE SET NULL" | |||
| << indent | |||
| << "ON UPDATE NO ACTION" | |||
| << decindent; | |||
| } | |||
| /* CREATE TABLE end */ | |||
| os << decindent | |||
| << indent | |||
| << ")" | |||
| << indent | |||
| << "ENGINE = InnoDB" | |||
| << indent | |||
| << "DEFAULT CHARACTER SET = utf8"; | |||
| _statement_create_table.reset(new ::cppmariadb::statement(os.str())); | |||
| return *_statement_create_table; | |||
| } | |||
| void table_t::init_intern(const init_context& context) const | |||
| { | |||
| auto& statement = get_statement_create_table(); | |||
| auto& connection = context.connection; | |||
| cpphibernate_debug_log("execute init query: " << statement.query(connection)); | |||
| connection.execute(statement); | |||
| } | |||
| @@ -0,0 +1,226 @@ | |||
| #include <gtest/gtest.h> | |||
| #include <cpphibernate/driver/mariadb.h> | |||
| #include "test_schema.h" | |||
| #include "mariadb_mock.h" | |||
| using namespace ::testing; | |||
| using namespace ::cpphibernate; | |||
| template<typename T_mock> | |||
| inline void expect_query(T_mock& mock, const std::string& query) | |||
| { | |||
| EXPECT_CALL( | |||
| mock, | |||
| mysql_real_query( | |||
| reinterpret_cast<MYSQL*>(0x1111), | |||
| StrEq(query), | |||
| query.size())); | |||
| EXPECT_CALL( | |||
| mock, | |||
| mysql_store_result( | |||
| reinterpret_cast<MYSQL*>(0x1111))) | |||
| .WillOnce(Return(reinterpret_cast<MYSQL_RES*>(0x2222))); | |||
| EXPECT_CALL( | |||
| mock, | |||
| mysql_free_result( | |||
| reinterpret_cast<MYSQL_RES*>(0x2222))); | |||
| } | |||
| TEST(CppHibernateTests, init) | |||
| { | |||
| StrictMock<MariaDbMock> mock; | |||
| InSequence seq; | |||
| expect_query(mock, "START TRANSACTION"); | |||
| expect_query(mock, "DROP DATABASE IF EXISTS `test`"); | |||
| expect_query(mock, "CREATE SCHEMA IF NOT EXISTS `test` DEFAULT CHARACTER SET utf8"); | |||
| expect_query(mock, "USE `test`"); | |||
| expect_query(mock, "CREATE FUNCTION IF NOT EXISTS UuidToBin(_uuid CHAR(36))\n" | |||
| " RETURNS BINARY(16)\n" | |||
| " LANGUAGE SQL\n" | |||
| " DETERMINISTIC\n" | |||
| " CONTAINS SQL\n" | |||
| " SQL SECURITY INVOKER\n" | |||
| "RETURN\n" | |||
| " UNHEX(CONCAT(\n" | |||
| " SUBSTR(_uuid, 25, 12),\n" | |||
| " SUBSTR(_uuid, 20, 4),\n" | |||
| " SUBSTR(_uuid, 15, 4),\n" | |||
| " SUBSTR(_uuid, 10, 4),\n" | |||
| " SUBSTR(_uuid, 1, 8)\n" | |||
| " )\n" | |||
| ")"); | |||
| expect_query(mock, "CREATE FUNCTION IF NOT EXISTS BinToUuid(_bin BINARY(16))\n" | |||
| " RETURNS CHAR(36)\n" | |||
| " LANGUAGE SQL\n" | |||
| " DETERMINISTIC\n" | |||
| " CONTAINS SQL\n" | |||
| " SQL SECURITY INVOKER\n" | |||
| "RETURN\n" | |||
| " LCASE(CONCAT_WS('-',\n" | |||
| " HEX(SUBSTR(_bin, 13, 4)),\n" | |||
| " HEX(SUBSTR(_bin, 11, 2)),\n" | |||
| " HEX(SUBSTR(_bin, 9, 2)),\n" | |||
| " HEX(SUBSTR(_bin, 7, 2)),\n" | |||
| " HEX(SUBSTR(_bin, 1, 6))\n" | |||
| " )\n" | |||
| ")"); | |||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_test1`\n" | |||
| "(\n" | |||
| " `tbl_test1_id` BINARY(16) NOT NULL,\n" | |||
| " `str_data` VARCHAR(100) NOT NULL,\n" | |||
| " `str64_data` VARCHAR(64) NOT NULL,\n" | |||
| " `u32_nullable` INT UNSIGNED NULL DEFAULT NULL,\n" | |||
| " `u32_ptr_u` INT UNSIGNED NULL DEFAULT NULL,\n" | |||
| " `u32_ptr_s` INT UNSIGNED NULL DEFAULT NULL,\n" | |||
| " PRIMARY KEY ( `tbl_test1_id` ),\n" | |||
| " UNIQUE INDEX `index_tbl_test1_id` ( `tbl_test1_id` ASC )\n" | |||
| ")\n" | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_test2`\n" | |||
| "(\n" | |||
| " `tbl_test2_id` BINARY(16) NOT NULL,\n" | |||
| " `u8_data` TINYINT UNSIGNED NOT NULL,\n" | |||
| " `i8_data` TINYINT NOT NULL,\n" | |||
| " `u16_data` SMALLINT UNSIGNED NOT NULL,\n" | |||
| " `i16_data` SMALLINT NOT NULL,\n" | |||
| " PRIMARY KEY ( `tbl_test2_id` ),\n" | |||
| " UNIQUE INDEX `index_tbl_test2_id` ( `tbl_test2_id` ASC )\n" | |||
| ")\n" | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_test3`\n" | |||
| "(\n" | |||
| " `tbl_test3_id` BINARY(16) NOT NULL,\n" | |||
| " `tbl_derived3_id_test3_list` BINARY(16) NULL DEFAULT NULL,\n" | |||
| " `tbl_derived3_id_test3_vector` BINARY(16) NULL DEFAULT NULL,\n" | |||
| " `u32_data` INT UNSIGNED NOT NULL,\n" | |||
| " `i32_data` INT NOT NULL,\n" | |||
| " `u64_data` BIGINT UNSIGNED NOT NULL,\n" | |||
| " `i64_data` BIGINT NOT NULL,\n" | |||
| " PRIMARY KEY ( `tbl_test3_id` ),\n" | |||
| " UNIQUE INDEX `index_tbl_test3_id` ( `tbl_test3_id` ASC ),\n" | |||
| " INDEX `index_tbl_derived3_id_test3_list` ( `tbl_derived3_id_test3_list` ASC ),\n" | |||
| " INDEX `index_tbl_derived3_id_test3_vector` ( `tbl_derived3_id_test3_vector` ASC ),\n" | |||
| " CONSTRAINT `fk_tbl_test3_tbl_derived3_id_test3_list`\n" | |||
| " FOREIGN KEY (`tbl_derived3_id_test3_list`)\n" | |||
| " REFERENCES `test`.`tbl_derived3` (`tbl_derived3_id`)\n" | |||
| " ON DELETE SET NULL\n" | |||
| " ON UPDATE NO ACTION,\n" | |||
| " CONSTRAINT `fk_tbl_test3_tbl_derived3_id_test3_vector`\n" | |||
| " FOREIGN KEY (`tbl_derived3_id_test3_vector`)\n" | |||
| " REFERENCES `test`.`tbl_derived3` (`tbl_derived3_id`)\n" | |||
| " ON DELETE SET NULL\n" | |||
| " ON UPDATE NO ACTION\n" | |||
| ")\n" | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_base`\n" | |||
| "(\n" | |||
| " `tbl_base_id` BINARY(16) NOT NULL,\n" | |||
| " `name` VARCHAR(100) NOT NULL,\n" | |||
| " `__type` INT UNSIGNED NOT NULL,\n" | |||
| " PRIMARY KEY ( `tbl_base_id` ),\n" | |||
| " UNIQUE INDEX `index_tbl_base_id` ( `tbl_base_id` ASC )\n" | |||
| ")\n" | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_derived1`\n" | |||
| "(\n" | |||
| " `tbl_derived1_id` BINARY(16) NOT NULL,\n" | |||
| " `tbl_base_id` BINARY(16) NOT NULL,\n" | |||
| " `tbl_test1_id_test1_data` BINARY(16) NOT NULL,\n" | |||
| " `enum_data` ENUM ( 'test0', 'test1', 'test2', 'test3' ) NOT NULL,\n" | |||
| " PRIMARY KEY ( `tbl_derived1_id` ),\n" | |||
| " UNIQUE INDEX `index_tbl_derived1_id` ( `tbl_derived1_id` ASC ),\n" | |||
| " UNIQUE INDEX `index_tbl_base_id` ( `tbl_base_id` ASC ),\n" | |||
| " INDEX `index_tbl_test1_id_test1_data` ( `tbl_test1_id_test1_data` ASC ),\n" | |||
| " CONSTRAINT `fk_tbl_derived1_to_tbl_base_id`\n" | |||
| " FOREIGN KEY (`tbl_base_id`)\n" | |||
| " REFERENCES `test`.`tbl_base` (`tbl_base_id`)\n" | |||
| " ON DELETE CASCADE\n" | |||
| " ON UPDATE NO ACTION,\n" | |||
| " CONSTRAINT `fk_tbl_derived1_to_tbl_test1_id_test1_data`\n" | |||
| " FOREIGN KEY (`tbl_test1_id_test1_data`)\n" | |||
| " REFERENCES `test`.`tbl_test1` (`tbl_test1_id`)\n" | |||
| " ON DELETE CASCADE\n" | |||
| " ON UPDATE NO ACTION\n" | |||
| ")\n" | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_derived2`\n" | |||
| "(\n" | |||
| " `tbl_derived2_id` BINARY(16) NOT NULL,\n" | |||
| " `tbl_base_id` BINARY(16) NOT NULL,\n" | |||
| " `tbl_test2_id_test2_nullable` BINARY(16) NULL DEFAULT NULL,\n" | |||
| " `tbl_test2_id_test2_ptr_u` BINARY(16) NULL DEFAULT NULL,\n" | |||
| " `tbl_test2_id_test2_ptr_s` BINARY(16) NULL DEFAULT NULL,\n" | |||
| " PRIMARY KEY ( `tbl_derived2_id` ),\n" | |||
| " UNIQUE INDEX `index_tbl_derived2_id` ( `tbl_derived2_id` ASC ),\n" | |||
| " UNIQUE INDEX `index_tbl_base_id` ( `tbl_base_id` ASC ),\n" | |||
| " INDEX `index_tbl_test2_id_test2_nullable` ( `tbl_test2_id_test2_nullable` ASC ),\n" | |||
| " INDEX `index_tbl_test2_id_test2_ptr_u` ( `tbl_test2_id_test2_ptr_u` ASC ),\n" | |||
| " INDEX `index_tbl_test2_id_test2_ptr_s` ( `tbl_test2_id_test2_ptr_s` ASC ),\n" | |||
| " CONSTRAINT `fk_tbl_derived2_to_tbl_base_id`\n" | |||
| " FOREIGN KEY (`tbl_base_id`)\n" | |||
| " REFERENCES `test`.`tbl_base` (`tbl_base_id`)\n" | |||
| " ON DELETE CASCADE\n" | |||
| " ON UPDATE NO ACTION,\n" | |||
| " CONSTRAINT `fk_tbl_derived2_to_tbl_test2_id_test2_nullable`\n" | |||
| " FOREIGN KEY (`tbl_test2_id_test2_nullable`)\n" | |||
| " REFERENCES `test`.`tbl_test2` (`tbl_test2_id`)\n" | |||
| " ON DELETE CASCADE\n" | |||
| " ON UPDATE NO ACTION,\n" | |||
| " CONSTRAINT `fk_tbl_derived2_to_tbl_test2_id_test2_ptr_u`\n" | |||
| " FOREIGN KEY (`tbl_test2_id_test2_ptr_u`)\n" | |||
| " REFERENCES `test`.`tbl_test2` (`tbl_test2_id`)\n" | |||
| " ON DELETE CASCADE\n" | |||
| " ON UPDATE NO ACTION,\n" | |||
| " CONSTRAINT `fk_tbl_derived2_to_tbl_test2_id_test2_ptr_s`\n" | |||
| " FOREIGN KEY (`tbl_test2_id_test2_ptr_s`)\n" | |||
| " REFERENCES `test`.`tbl_test2` (`tbl_test2_id`)\n" | |||
| " ON DELETE CASCADE\n" | |||
| " ON UPDATE NO ACTION\n" | |||
| ")\n" | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_derived3`\n" | |||
| "(\n" | |||
| " `tbl_derived3_id` BINARY(16) NOT NULL,\n" | |||
| " `tbl_derived1_id` BINARY(16) NOT NULL,\n" | |||
| " PRIMARY KEY ( `tbl_derived3_id` ),\n" | |||
| " UNIQUE INDEX `index_tbl_derived3_id` ( `tbl_derived3_id` ASC ),\n" | |||
| " UNIQUE INDEX `index_tbl_derived1_id` ( `tbl_derived1_id` ASC ),\n" | |||
| " CONSTRAINT `fk_tbl_derived3_to_tbl_derived1_id`\n" | |||
| " FOREIGN KEY (`tbl_derived1_id`)\n" | |||
| " REFERENCES `test`.`tbl_derived1` (`tbl_derived1_id`)\n" | |||
| " ON DELETE CASCADE\n" | |||
| " ON UPDATE NO ACTION\n" | |||
| ")\n" | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "COMMIT"); | |||
| EXPECT_CALL( | |||
| mock, | |||
| mysql_close( | |||
| reinterpret_cast<MYSQL*>(0x1111))); | |||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||
| context.init(true); | |||
| } | |||
| @@ -1,62 +1,91 @@ | |||
| #include <gtest/gtest.h> | |||
| #include <cpphibernate.h> | |||
| #include <cpphibernate/driver/mariadb.h> | |||
| using namespace ::cpphibernate; | |||
| enum class test_enum | |||
| { | |||
| test0, | |||
| test1, | |||
| test2, | |||
| test3, | |||
| first = test0, | |||
| last = test3, | |||
| }; | |||
| DEFINE_ENUM_TO_STRING_MAP( | |||
| test_enum, | |||
| { test_enum::test0, "test0" }, | |||
| { test_enum::test1, "test1" }, | |||
| { test_enum::test2, "test2" }, | |||
| { test_enum::test3, "test3" } | |||
| ); | |||
| DEFINE_STRING_TO_ENUM_MAP( | |||
| test_enum, | |||
| invariant_string_less, | |||
| { "test0", test_enum::test0 }, | |||
| { "test1", test_enum::test1 }, | |||
| { "test2", test_enum::test2 }, | |||
| { "test3", test_enum::test3 } | |||
| ); | |||
| struct test1 | |||
| { | |||
| uuid id; | |||
| std::string str_data; | |||
| string<64> str64_data; | |||
| ::cpphibernate::uuid id; | |||
| std::string str_data; | |||
| ::cpphibernate::string<64> str64_data; | |||
| utl::nullable<uint32_t> u32_nullable; | |||
| std::unique_ptr<uint32_t> u32_ptr_u; | |||
| std::shared_ptr<uint32_t> u32_ptr_s; | |||
| }; | |||
| struct test2 | |||
| { | |||
| uuid id; | |||
| uint8_t u8_data; | |||
| int8_t i8_data; | |||
| uint16_t u16_data; | |||
| int16_t i16_data; | |||
| ::cpphibernate::uuid id; | |||
| uint8_t u8_data; | |||
| int8_t i8_data; | |||
| uint16_t u16_data; | |||
| int16_t i16_data; | |||
| }; | |||
| struct test3 | |||
| { | |||
| uuid id; | |||
| uint32_t u32_data; | |||
| int32_t i32_data; | |||
| uint64_t u64_data; | |||
| int64_t i64_data; | |||
| ::cpphibernate::uuid id; | |||
| uint32_t u32_data; | |||
| int32_t i32_data; | |||
| uint64_t u64_data; | |||
| int64_t i64_data; | |||
| }; | |||
| struct base | |||
| { | |||
| uuid id; | |||
| std::string name; | |||
| ::cpphibernate::uuid id; | |||
| std::string name; | |||
| }; | |||
| struct derived1 | |||
| : public base | |||
| { | |||
| uuid derived1_id; | |||
| test1 test1_data; | |||
| ::cpphibernate::uuid derived1_id; | |||
| test1 test1_data; | |||
| test_enum enum_data; | |||
| }; | |||
| struct derived2 | |||
| : public base | |||
| { | |||
| uuid derived2_id; | |||
| utl::nullable<test2> test2_nullable; | |||
| std::unique_ptr<test2> test2_ptr_u; | |||
| std::shared_ptr<test2> test2_ptr_s; | |||
| ::cpphibernate::uuid derived2_id; | |||
| utl::nullable<test2> test2_nullable; | |||
| std::unique_ptr<test2> test2_ptr_u; | |||
| std::shared_ptr<test2> test2_ptr_s; | |||
| }; | |||
| struct derived3 | |||
| : public derived1 | |||
| { | |||
| uuid derived3_id; | |||
| std::list<test3> test3_list; | |||
| std::vector<test3> test3_vector; | |||
| ::cpphibernate::uuid derived3_id; | |||
| std::list<test3> test3_list; | |||
| std::vector<test3> test3_vector; | |||
| }; | |||
| constexpr decltype(auto) test_schema = cpphibernate_make_schema( | |||
| @@ -67,7 +96,10 @@ constexpr decltype(auto) test_schema = cpphibernate_make_schema( | |||
| 1, | |||
| cpphibernate_make_id (&test1::id), | |||
| cpphibernate_make_field (test1, str_data), | |||
| cpphibernate_make_field (test1, str64_data) | |||
| cpphibernate_make_field (test1, str64_data), | |||
| cpphibernate_make_field (test1, u32_nullable), | |||
| cpphibernate_make_field (test1, u32_ptr_u), | |||
| cpphibernate_make_field (test1, u32_ptr_s) | |||
| ), | |||
| cpphibernate_make_table_name( | |||
| tbl_test2, | |||
| @@ -102,7 +134,8 @@ constexpr decltype(auto) test_schema = cpphibernate_make_schema( | |||
| derived1, | |||
| 11, | |||
| cpphibernate_make_id (&derived1::derived1_id), | |||
| cpphibernate_make_field (derived1, test1_data) | |||
| cpphibernate_make_field (derived1, test1_data), | |||
| cpphibernate_make_field (derived1, enum_data) | |||
| ), | |||
| cpphibernate_make_table_name( | |||
| tbl_derived2, | |||
| @@ -121,11 +154,4 @@ constexpr decltype(auto) test_schema = cpphibernate_make_schema( | |||
| cpphibernate_make_field (derived3, test3_list), | |||
| cpphibernate_make_field (derived3, test3_vector) | |||
| ) | |||
| ); | |||
| TEST(CppHibernateTests, fuuu) | |||
| { | |||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1234)); | |||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||
| std::cout << context.schema() << std::endl; | |||
| } | |||
| ); | |||