| @@ -3,6 +3,14 @@ | |||||
| #include <boost/hana.hpp> | #include <boost/hana.hpp> | ||||
| #include <cpputils/mp/core.h> | #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() \ | #define cpphibernate_equality_comparable() \ | ||||
| template<typename T_other> \ | template<typename T_other> \ | ||||
| constexpr decltype(auto) operator==(T_other&&) const \ | 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 <cppmariadb.h> | ||||
| #include <cpphibernate/config.h> | #include <cpphibernate/config.h> | ||||
| #include <cpphibernate/driver/mariadb/impl.h> | |||||
| #include <cpphibernate/driver/mariadb/schema.h> | #include <cpphibernate/driver/mariadb/schema.h> | ||||
| beg_namespace_cpphibernate_driver_mariadb | beg_namespace_cpphibernate_driver_mariadb | ||||
| { | { | ||||
| struct mariadb_driver_t | struct mariadb_driver_t | ||||
| { | { | ||||
| private: | private: | ||||
| @@ -23,11 +24,16 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| cpphibernate_copyable(mariadb_driver_t, delete); | cpphibernate_copyable(mariadb_driver_t, delete); | ||||
| cpphibernate_moveable(mariadb_driver_t, default); | 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/fields.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/schema.h> | #include <cpphibernate/driver/mariadb/schema/schema.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/table.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/field.h> | ||||
| #include <cpphibernate/schema/table.h> | #include <cpphibernate/schema/table.h> | ||||
| #include <cpphibernate/schema/schema.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/field.fwd.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/table.fwd.h> | #include <cpphibernate/driver/mariadb/schema/table.fwd.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/attributes.h> | #include <cpphibernate/driver/mariadb/schema/attributes.h> | ||||
| beg_namespace_cpphibernate_driver_mariadb | beg_namespace_cpphibernate_driver_mariadb | ||||
| { | { | ||||
| /* field_t */ | /* field_t */ | ||||
| struct field_t | struct field_t | ||||
| @@ -42,9 +43,13 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| , table (nullptr) | , table (nullptr) | ||||
| , referenced_table (nullptr) | , referenced_table (nullptr) | ||||
| { } | { } | ||||
| virtual ~field_t() = default; | |||||
| virtual ~field_t() { }; | |||||
| void print(std::ostream& os) const; | void print(std::ostream& os) const; | ||||
| /* properties */ | |||||
| virtual std::string type () const; | |||||
| virtual std::string create_table_arguments () const; | |||||
| }; | }; | ||||
| /* simple_field_t */ | /* simple_field_t */ | ||||
| @@ -72,9 +77,17 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| struct value_field_t | struct value_field_t | ||||
| : public simple_field_t<T_schema, T_field> | : 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; | using base_type::base_type; | ||||
| virtual std::string type() const override; | |||||
| }; | }; | ||||
| /* primary_key_field_t */ | /* primary_key_field_t */ | ||||
| @@ -83,9 +96,15 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| struct primary_key_field_t | struct primary_key_field_t | ||||
| : public value_field_t<T_schema, T_field> | : 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; | using base_type::base_type; | ||||
| virtual std::string create_table_arguments() const override; | |||||
| }; | }; | ||||
| /* data_field_t */ | /* data_field_t */ | ||||
| @@ -179,15 +198,21 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| using dataset_type = mp::decay_t<typename getter_type::dataset_type>; | using dataset_type = mp::decay_t<typename getter_type::dataset_type>; | ||||
| using value_dataset_type = misc::real_dataset_t<value_type>; | using value_dataset_type = misc::real_dataset_t<value_type>; | ||||
| using return_type = field_type_t<schema_type, field_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); | 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); | 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; | 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/misc.h> | ||||
| #include <cpphibernate/config.h> | #include <cpphibernate/config.h> | ||||
| #include <cpphibernate/driver/mariadb/helper/context.h> | |||||
| #include <cpphibernate/driver/mariadb/schema/tables.h> | #include <cpphibernate/driver/mariadb/schema/tables.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/schema.fwd.h> | #include <cpphibernate/driver/mariadb/schema/schema.fwd.h> | ||||
| beg_namespace_cpphibernate_driver_mariadb | beg_namespace_cpphibernate_driver_mariadb | ||||
| { | { | ||||
| /* schema_t */ | /* schema_t */ | ||||
| struct schema_t | struct schema_t | ||||
| @@ -24,6 +25,9 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| void update (); | void update (); | ||||
| void print (std::ostream& os) const; | void print (std::ostream& os) const; | ||||
| /* CRUD */ | |||||
| void init(const init_context& context) const; | |||||
| }; | }; | ||||
| namespace __impl | namespace __impl | ||||
| @@ -3,16 +3,18 @@ | |||||
| #include <memory> | #include <memory> | ||||
| #include <vector> | #include <vector> | ||||
| #include <cppmariadb.h> | |||||
| #include <cpphibernate/misc.h> | #include <cpphibernate/misc.h> | ||||
| #include <cpphibernate/config.h> | #include <cpphibernate/config.h> | ||||
| #include <cpphibernate/schema/table.h> | #include <cpphibernate/schema/table.h> | ||||
| #include <cpphibernate/schema/schema.h> | #include <cpphibernate/schema/schema.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/fields.h> | #include <cpphibernate/driver/mariadb/schema/fields.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/table.fwd.h> | #include <cpphibernate/driver/mariadb/schema/table.fwd.h> | ||||
| #include <cpphibernate/driver/mariadb/helper/context.h> | |||||
| beg_namespace_cpphibernate_driver_mariadb | beg_namespace_cpphibernate_driver_mariadb | ||||
| { | { | ||||
| /* table_t */ | /* table_t */ | ||||
| struct table_t | struct table_t | ||||
| @@ -56,9 +58,23 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| , foreign_table_many_fields () | , foreign_table_many_fields () | ||||
| , data_fields () | , data_fields () | ||||
| { } | { } | ||||
| virtual ~table_t() = default; | |||||
| virtual ~table_t() { }; | |||||
| void print(std::ostream& os) const; | 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 */ | /* table_simple_t */ | ||||
| @@ -70,7 +86,7 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| using schema_type = T_schema; | using schema_type = T_schema; | ||||
| using table_type = T_table; | using table_type = T_table; | ||||
| using base_dataset_type = T_base_dataset; | using base_dataset_type = T_base_dataset; | ||||
| const schema_type& schema; | const schema_type& schema; | ||||
| const table_type& table; | const table_type& table; | ||||
| @@ -98,9 +114,9 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| using schema_type = T_schema; | using schema_type = T_schema; | ||||
| using table_type = T_table; | using table_type = T_table; | ||||
| using base_dataset_type = T_base_dataset; | using base_dataset_type = T_base_dataset; | ||||
| using base_type::base_type; | using base_type::base_type; | ||||
| const schema_type& schema; | const schema_type& schema; | ||||
| const table_type& table; | const table_type& table; | ||||
| }; | }; | ||||
| @@ -2,5 +2,6 @@ | |||||
| #include <cpphibernate/misc/general.h> | #include <cpphibernate/misc/general.h> | ||||
| #include <cpphibernate/misc/meta.h> | #include <cpphibernate/misc/meta.h> | ||||
| #include <cpphibernate/misc/nullable_helper.h> | |||||
| #include <cpphibernate/misc/print.h> | #include <cpphibernate/misc/print.h> | ||||
| #include <cpphibernate/misc/wrap.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/field.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/table.h> | #include <cpphibernate/driver/mariadb/schema/table.h> | ||||
| using namespace ::std; | |||||
| using namespace ::utl; | using namespace ::utl; | ||||
| using namespace ::cpphibernate::driver::mariadb_impl; | 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") | << indent << "\"referenced_table\": " << (referenced_table ? std::string("\"") + referenced_table->table_name + "\"" : "null") | ||||
| << decindent | << decindent | ||||
| << indent << '}'; | << 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 <string> | ||||
| #include <iostream> | |||||
| #include <sstream> | |||||
| #include <cpputils/misc/enum.h> | #include <cpputils/misc/enum.h> | ||||
| #include <cpputils/misc/string.h> | #include <cpputils/misc/string.h> | ||||
| @@ -109,4 +109,83 @@ void schema_t::print(std::ostream& os) const | |||||
| }) | }) | ||||
| << decindent | << decindent | ||||
| << indent << '}'; | << 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 | << decindent | ||||
| << indent << '}'; | << 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.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 | 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 | 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 | 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 | struct base | ||||
| { | { | ||||
| uuid id; | |||||
| std::string name; | |||||
| ::cpphibernate::uuid id; | |||||
| std::string name; | |||||
| }; | }; | ||||
| struct derived1 | struct derived1 | ||||
| : public base | : public base | ||||
| { | { | ||||
| uuid derived1_id; | |||||
| test1 test1_data; | |||||
| ::cpphibernate::uuid derived1_id; | |||||
| test1 test1_data; | |||||
| test_enum enum_data; | |||||
| }; | }; | ||||
| struct derived2 | struct derived2 | ||||
| : public base | : 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 | struct derived3 | ||||
| : public derived1 | : 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( | constexpr decltype(auto) test_schema = cpphibernate_make_schema( | ||||
| @@ -67,7 +96,10 @@ constexpr decltype(auto) test_schema = cpphibernate_make_schema( | |||||
| 1, | 1, | ||||
| cpphibernate_make_id (&test1::id), | cpphibernate_make_id (&test1::id), | ||||
| cpphibernate_make_field (test1, str_data), | 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( | cpphibernate_make_table_name( | ||||
| tbl_test2, | tbl_test2, | ||||
| @@ -102,7 +134,8 @@ constexpr decltype(auto) test_schema = cpphibernate_make_schema( | |||||
| derived1, | derived1, | ||||
| 11, | 11, | ||||
| cpphibernate_make_id (&derived1::derived1_id), | 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( | cpphibernate_make_table_name( | ||||
| tbl_derived2, | 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_list), | ||||
| cpphibernate_make_field (derived3, test3_vector) | 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; | |||||
| } | |||||
| ); | |||||