| @@ -2,5 +2,6 @@ | |||||
| #include <cpphibernate/driver/mariadb/helper/context.h> | #include <cpphibernate/driver/mariadb/helper/context.h> | ||||
| #include <cpphibernate/driver/mariadb/helper/key_properties.h> | #include <cpphibernate/driver/mariadb/helper/key_properties.h> | ||||
| #include <cpphibernate/driver/mariadb/helper/reference_stack.h> | |||||
| #include <cpphibernate/driver/mariadb/helper/transaction_lock.h> | #include <cpphibernate/driver/mariadb/helper/transaction_lock.h> | ||||
| #include <cpphibernate/driver/mariadb/helper/type_properties.h> | #include <cpphibernate/driver/mariadb/helper/type_properties.h> | ||||
| @@ -2,6 +2,9 @@ | |||||
| #include <cppmariadb.h> | #include <cppmariadb.h> | ||||
| #include <cpphibernate/config.h> | #include <cpphibernate/config.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/field.fwd.h> | |||||
| #include <cpphibernate/driver/mariadb/schema/table.fwd.h> | |||||
| #include <cpphibernate/driver/mariadb/schema/filter.fwd.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 | ||||
| @@ -16,5 +19,74 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| bool recreate; | bool recreate; | ||||
| }; | }; | ||||
| /* create context */ | |||||
| struct create_context | |||||
| { | |||||
| const schema_t& schema; | |||||
| const table_t* derived_table; | |||||
| const field_t* owner_field; | |||||
| ::cppmariadb::connection& connection; | |||||
| }; | |||||
| template<typename T_dataset> | |||||
| struct generic_create_context | |||||
| : public create_context | |||||
| { | |||||
| using dataset_type = mp::decay_t<T_dataset>; | |||||
| dataset_type& dataset; | |||||
| template<typename T_new_dataset> | |||||
| constexpr decltype(auto) change(T_new_dataset& new_dataset, const field_t* owner = nullptr) const | |||||
| { | |||||
| return generic_create_context<T_new_dataset> | |||||
| { | |||||
| { | |||||
| schema, | |||||
| nullptr, | |||||
| owner, | |||||
| connection | |||||
| }, | |||||
| new_dataset | |||||
| }; | |||||
| } | |||||
| }; | |||||
| /* update context */ | |||||
| struct update_context | |||||
| : public create_context | |||||
| { | |||||
| const filter_t& filter; | |||||
| }; | |||||
| template<typename T_dataset> | |||||
| struct generic_update_context | |||||
| : public update_context | |||||
| { | |||||
| using dataset_type = mp::decay_t<T_dataset>; | |||||
| dataset_type& dataset; | |||||
| template<typename T_new_dataset> | |||||
| constexpr decltype(auto) change(T_new_dataset& new_dataset, const field_t* owner = nullptr) const | |||||
| { | |||||
| return generic_update_context<T_new_dataset> | |||||
| { | |||||
| { | |||||
| { | |||||
| schema, | |||||
| nullptr, | |||||
| owner, | |||||
| connection | |||||
| }, | |||||
| filter, | |||||
| }, | |||||
| new_dataset | |||||
| }; | |||||
| } | |||||
| }; | |||||
| } | } | ||||
| end_namespace_cpphibernate_driver_mariadb | end_namespace_cpphibernate_driver_mariadb | ||||
| @@ -0,0 +1,74 @@ | |||||
| #pragma once | |||||
| #include <stack> | |||||
| #include <cpphibernate/config.h> | |||||
| #include <cpputils/misc/type_helper.h> | |||||
| beg_namespace_cpphibernate_driver_mariadb | |||||
| { | |||||
| /* reference_lock */ | |||||
| struct reference_lock | |||||
| { | |||||
| virtual ~reference_lock() = default; | |||||
| }; | |||||
| /* reference_stack */ | |||||
| template<typename T_dataset> | |||||
| struct reference_stack | |||||
| { | |||||
| private: | |||||
| struct lock | |||||
| : public reference_lock | |||||
| { | |||||
| private: | |||||
| T_dataset& dataset; | |||||
| public: | |||||
| inline lock(T_dataset& p_dataset) | |||||
| : dataset(p_dataset) | |||||
| { push_impl(dataset); } | |||||
| virtual ~lock() override | |||||
| { pop_impl(dataset); } | |||||
| }; | |||||
| private: | |||||
| using stack_type = std::stack<T_dataset*>; | |||||
| static inline stack_type& stack() | |||||
| { | |||||
| static stack_type value; | |||||
| return value; | |||||
| } | |||||
| static inline void push_impl(T_dataset& dataset) | |||||
| { stack().push(&dataset); } | |||||
| static inline void pop_impl(T_dataset& dataset) | |||||
| { | |||||
| if (stack().empty() || stack().top() != &dataset) | |||||
| throw misc::hibernate_exception(std::string("reference_stack<") + utl::type_helper<T_dataset>::name() + ">: poped element is not the top element!"); | |||||
| stack().pop(); | |||||
| } | |||||
| public: | |||||
| static inline decltype(auto) push(T_dataset& dataset) | |||||
| { return std::make_unique<lock>(dataset); } | |||||
| static inline T_dataset& top() | |||||
| { | |||||
| if (stack().empty()) | |||||
| throw misc::hibernate_exception(std::string("reference_stack<") + utl::type_helper<T_dataset>::name() + ">: does not have stored a dataset!"); | |||||
| return *stack().top(); | |||||
| } | |||||
| static inline size_t size() | |||||
| { return stack().size(); } | |||||
| }; | |||||
| } | |||||
| end_namespace_cpphibernate_driver_mariadb | |||||
| @@ -223,9 +223,9 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| static inline nullable_type convert_to(const value_t& value) | static inline nullable_type convert_to(const value_t& value) | ||||
| { | { | ||||
| auto ret = nullable_helper_type::make(); | |||||
| nullable_type ret; | |||||
| if (value.has_value()) | if (value.has_value()) | ||||
| nullable_helper_type::fill(ret, value_type_props::convert_to(value)); | |||||
| nullable_helper_type::set(ret, value_type_props::convert_to(value)); | |||||
| return ret; | return ret; | ||||
| } | } | ||||
| @@ -1,3 +1,4 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <cpphibernate/driver/mariadb/impl/init.h> | |||||
| #include <cpphibernate/driver/mariadb/impl/init.h> | |||||
| #include <cpphibernate/driver/mariadb/impl/create.h> | |||||
| @@ -0,0 +1,95 @@ | |||||
| #pragma once | |||||
| #include <cppmariadb.h> | |||||
| #include <cpphibernate/misc.h> | |||||
| #include <cpphibernate/config.h> | |||||
| beg_namespace_cpphibernate_driver_mariadb | |||||
| { | |||||
| /* create_impl_t */ | |||||
| template<typename T_context, typename = void> | |||||
| struct create_impl_t | |||||
| { | |||||
| using context_type = T_context; | |||||
| using dataset_type = typename context_type::dataset_type; | |||||
| using reference_stack_type = reference_stack<dataset_type>; | |||||
| static inline value_t apply(const context_type& context, bool strict = true) | |||||
| { | |||||
| value_t ret; | |||||
| auto dataset_id = misc::get_type_id(hana::type_c<dataset_type>); | |||||
| auto& connection = context.connection; | |||||
| auto& schema = context.schema; | |||||
| auto& table = schema.table(dataset_id); | |||||
| auto& dataset = context.dataset; | |||||
| auto ref_lock = reference_stack_type::push(dataset); | |||||
| transaction_lock trans(connection); | |||||
| ret = table.create(context); | |||||
| trans.commit(); | |||||
| return ret; | |||||
| } | |||||
| }; | |||||
| /* create_impl_t - nullable */ | |||||
| template<typename T_context> | |||||
| struct create_impl_t< | |||||
| T_context, | |||||
| mp::enable_if<misc::is_nullable<typename T_context::dataset_type>>> | |||||
| { | |||||
| using context_type = T_context; | |||||
| using dataset_type = typename context_type::dataset_type; | |||||
| using nullable_helper_type = misc::nullable_helper<dataset_type>; | |||||
| static inline value_t apply(const context_type& context, bool strict = true) | |||||
| { | |||||
| value_t ret; | |||||
| auto& dataset = context.dataset; | |||||
| auto* value = nullable_helper_type::get(dataset); | |||||
| if (value) | |||||
| { | |||||
| using new_context_type = mp::decay_t<decltype(context.change(*value))>; | |||||
| using new_create_impl_type = create_impl_t<new_context_type>; | |||||
| ret = new_create_impl_type::apply(context.change(*value)); | |||||
| } | |||||
| else if (strict) | |||||
| { | |||||
| throw misc::hibernate_exception("can not create nullable type with no value!"); | |||||
| } | |||||
| return ret; | |||||
| } | |||||
| }; | |||||
| /* create_impl_t - container */ | |||||
| template<typename T_context> | |||||
| struct create_impl_t< | |||||
| T_context, | |||||
| mp::enable_if<misc::is_container<typename T_context::dataset_type>>> | |||||
| { | |||||
| using context_type = T_context; | |||||
| static inline value_t apply(const context_type& context, bool strict = true) | |||||
| { | |||||
| value_t ret; | |||||
| auto& connection = context.connection; | |||||
| auto& dataset = context.dataset; | |||||
| transaction_lock trans(connection); | |||||
| for (auto& x : dataset) | |||||
| { | |||||
| using new_context_type = mp::decay_t<decltype(context.change(x))>; | |||||
| using new_create_impl_type = create_impl_t<new_context_type>; | |||||
| new_create_impl_type::apply(context.change(x, context.owner_field)); | |||||
| } | |||||
| trans.commit(); | |||||
| return ret; | |||||
| } | |||||
| }; | |||||
| } | |||||
| end_namespace_cpphibernate_driver_mariadb | |||||
| @@ -8,22 +8,14 @@ | |||||
| beg_namespace_cpphibernate_driver_mariadb | beg_namespace_cpphibernate_driver_mariadb | ||||
| { | { | ||||
| /* init_impl */ | |||||
| /* init_impl_t */ | |||||
| template<typename T_context, typename = void> | template<typename T_context, typename = void> | ||||
| struct init_impl | |||||
| struct init_impl_t | |||||
| { | { | ||||
| using context_type = T_context; | 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 | |||||
| static inline void apply(const context_type& context) | |||||
| { | { | ||||
| auto& schema = context.schema; | auto& schema = context.schema; | ||||
| auto& connection = context.connection; | auto& connection = context.connection; | ||||
| @@ -34,11 +26,5 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| } | } | ||||
| }; | }; | ||||
| /* 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 | end_namespace_cpphibernate_driver_mariadb | ||||
| @@ -27,12 +27,28 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| protected: | protected: | ||||
| inline void init_impl(bool recreate) const | inline void init_impl(bool recreate) const | ||||
| { | { | ||||
| make_init_impl(init_context | |||||
| init_impl_t<init_context>::apply(init_context | |||||
| { | { | ||||
| _schema, | _schema, | ||||
| _connection, | _connection, | ||||
| recreate | recreate | ||||
| })(); | |||||
| }); | |||||
| } | |||||
| template<typename T_dataset> | |||||
| inline void create_impl(T_dataset& dataset) const | |||||
| { | |||||
| using create_context_type = generic_create_context<T_dataset>; | |||||
| create_impl_t<create_context_type>::apply(create_context_type | |||||
| { | |||||
| { | |||||
| _schema, | |||||
| nullptr, | |||||
| nullptr, | |||||
| _connection | |||||
| }, | |||||
| dataset | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -14,4 +14,5 @@ | |||||
| #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> | |||||
| #include <cpphibernate/driver/mariadb/schema/field.inl> | |||||
| #include <cpphibernate/driver/mariadb/schema/table.inl> | |||||
| @@ -47,9 +47,21 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| void print(std::ostream& os) const; | void print(std::ostream& os) const; | ||||
| /* CRUD */ | |||||
| virtual value_t foreign_create(const create_context& context) const; | |||||
| virtual value_t foreign_update(const update_context& context) const; | |||||
| /* properties */ | /* properties */ | ||||
| virtual std::string type () const; | virtual std::string type () const; | ||||
| virtual std::string create_table_arguments () const; | virtual std::string create_table_arguments () const; | ||||
| virtual std::string generate_value (::cppmariadb::connection& connection) const; | |||||
| virtual bool is_auto_generated () const; | |||||
| virtual std::string convert_to_open () const; | |||||
| virtual std::string convert_to_close () const; | |||||
| virtual std::string convert_from_open () const; | |||||
| virtual std::string convert_from_close () const; | |||||
| virtual value_t get () const; | |||||
| virtual void set (const value_t&) const; | |||||
| }; | }; | ||||
| /* simple_field_t */ | /* simple_field_t */ | ||||
| @@ -58,8 +70,12 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| struct simple_field_t | struct simple_field_t | ||||
| : public field_t | : public field_t | ||||
| { | { | ||||
| using schema_type = T_schema; | |||||
| using field_type = 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 ref_stack = reference_stack<dataset_type>; | |||||
| const schema_type& schema; | const schema_type& schema; | ||||
| const field_type& field; | const field_type& field; | ||||
| @@ -77,17 +93,21 @@ 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 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 = simple_field_t<T_schema, T_field>; | |||||
| using schema_type = T_schema; | |||||
| using field_type = T_field; | |||||
| using getter_type = typename base_type::getter_type; | |||||
| using dataset_type = typename base_type::dataset_type; | |||||
| using value_type = typename base_type::value_type; | |||||
| using ref_stack = typename base_type::ref_stack; | |||||
| using type_props = type_properties<value_type>; | |||||
| using base_type::base_type; | using base_type::base_type; | ||||
| virtual std::string type() const override; | virtual std::string type() const override; | ||||
| virtual value_t get () const override; | |||||
| virtual void set (const value_t&) const override; | |||||
| }; | }; | ||||
| /* primary_key_field_t */ | /* primary_key_field_t */ | ||||
| @@ -104,7 +124,13 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| using base_type::base_type; | using base_type::base_type; | ||||
| virtual std::string create_table_arguments() const override; | |||||
| virtual std::string create_table_arguments () const override; | |||||
| virtual std::string generate_value (::cppmariadb::connection& connection) const override; | |||||
| virtual bool is_auto_generated () const override; | |||||
| virtual std::string convert_to_open () const override; | |||||
| virtual std::string convert_to_close () const override; | |||||
| virtual std::string convert_from_open () const override; | |||||
| virtual std::string convert_from_close () const override; | |||||
| }; | }; | ||||
| /* data_field_t */ | /* data_field_t */ | ||||
| @@ -124,9 +150,16 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| struct foreign_table_field_t | struct foreign_table_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>; | |||||
| public: | |||||
| using base_type = simple_field_t<T_schema, T_field>; | |||||
| using dataset_type = typename base_type::dataset_type; | |||||
| using ref_stack = typename base_type::ref_stack; | |||||
| using base_type::base_type; | using base_type::base_type; | ||||
| public: | |||||
| virtual value_t foreign_create(const create_context& context) const override; | |||||
| virtual value_t foreign_update(const update_context& context) const override; | |||||
| }; | }; | ||||
| namespace __impl | namespace __impl | ||||
| @@ -1,5 +1,6 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <cpphibernate/driver/mariadb/impl/create.h> | |||||
| #include <cpphibernate/driver/mariadb/schema/field.h> | #include <cpphibernate/driver/mariadb/schema/field.h> | ||||
| beg_namespace_cpphibernate_driver_mariadb | beg_namespace_cpphibernate_driver_mariadb | ||||
| @@ -11,11 +12,83 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| std::string value_field_t<T_schema, T_field>::type() const | std::string value_field_t<T_schema, T_field>::type() const | ||||
| { return type_props::type(); } | { return type_props::type(); } | ||||
| template<typename T_schema, typename T_field> | |||||
| value_t value_field_t<T_schema, T_field>::get() const | |||||
| { return type_props::convert_from(this->field.getter(ref_stack::top())); } | |||||
| template<typename T_schema, typename T_field> | |||||
| void value_field_t<T_schema, T_field>::set(const value_t& value) const | |||||
| { this->field.setter(ref_stack::top(), type_props::convert_to(value)); } | |||||
| /* primary_key_field_t */ | /* primary_key_field_t */ | ||||
| template<typename T_schema, typename T_field> | template<typename T_schema, typename T_field> | ||||
| std::string primary_key_field_t<T_schema, T_field>::create_table_arguments() const | std::string primary_key_field_t<T_schema, T_field>::create_table_arguments() const | ||||
| { return key_props::create_table_argument; } | |||||
| { return key_props::create_table_argument; } | |||||
| template<typename T_schema, typename T_field> | |||||
| std::string primary_key_field_t<T_schema, T_field> | |||||
| ::generate_value(::cppmariadb::connection& connection) const | |||||
| { | |||||
| auto ret = connection.execute_used(key_props::create_key_query); | |||||
| if (!ret || !ret->next()) | |||||
| throw misc::hibernate_exception("unable to generate key value!"); | |||||
| return ret->current()->at(0).template get<std::string>(); | |||||
| } | |||||
| template<typename T_schema, typename T_field> | |||||
| bool primary_key_field_t<T_schema, T_field>::is_auto_generated() const | |||||
| { return key_props::auto_generated::value; } | |||||
| template<typename T_schema, typename T_field> | |||||
| std::string primary_key_field_t<T_schema, T_field>::convert_to_open() const | |||||
| { return key_props::convert_to_open; } | |||||
| template<typename T_schema, typename T_field> | |||||
| std::string primary_key_field_t<T_schema, T_field>::convert_to_close() const | |||||
| { return key_props::convert_to_close; } | |||||
| template<typename T_schema, typename T_field> | |||||
| std::string primary_key_field_t<T_schema, T_field>::convert_from_open() const | |||||
| { return key_props::convert_from_open; } | |||||
| template<typename T_schema, typename T_field> | |||||
| std::string primary_key_field_t<T_schema, T_field>::convert_from_close() const | |||||
| { return key_props::convert_from_close; } | |||||
| /* foreign_table_field_t */ | |||||
| template<typename T_schema, typename T_field> | |||||
| value_t foreign_table_field_t<T_schema, T_field> | |||||
| ::foreign_create(const create_context& context) const | |||||
| { | |||||
| auto& ref = ref_stack::top(); | |||||
| auto& foreign = this->field.getter(ref); | |||||
| using foreign_dataset_type = mp::decay_t<decltype(foreign)>; | |||||
| using create_context_type = generic_create_context<foreign_dataset_type>; | |||||
| return create_impl_t<create_context_type>::apply( | |||||
| create_context_type | |||||
| { | |||||
| context, | |||||
| foreign, | |||||
| }, | |||||
| false); | |||||
| } | |||||
| template<typename T_schema, typename T_field> | |||||
| value_t foreign_table_field_t<T_schema, T_field> | |||||
| ::foreign_update(const update_context& ctx) const | |||||
| { | |||||
| /* | |||||
| auto& context = static_cast<const generic_create_context<dataset_type>&>(ctx); | |||||
| auto& ref = ref_stack::top(); | |||||
| auto& foreign = this->field.getter(ref); | |||||
| return foreign_create_update_helper<update_impl_t>(context.change(foreign)); | |||||
| */ | |||||
| return value_t { }; | |||||
| } | |||||
| } | } | ||||
| end_namespace_cpphibernate_driver_mariadb | end_namespace_cpphibernate_driver_mariadb | ||||
| @@ -0,0 +1,13 @@ | |||||
| #pragma once | |||||
| #include <cpphibernate/config.h> | |||||
| beg_namespace_cpphibernate_driver_mariadb | |||||
| { | |||||
| /* filter_t */ | |||||
| struct filter_t; | |||||
| } | |||||
| end_namespace_cpphibernate_driver_mariadb | |||||
| @@ -0,0 +1,28 @@ | |||||
| #pragma once | |||||
| #include <set> | |||||
| #include <cpphibernate/config.h> | |||||
| #include <cpphibernate/driver/mariadb/schema/field.fwd.h> | |||||
| #include <cpphibernate/driver/mariadb/schema/table.fwd.h> | |||||
| #include <cpphibernate/driver/mariadb/schema/filter.fwd.h> | |||||
| beg_namespace_cpphibernate_driver_mariadb | |||||
| { | |||||
| /* filter_t */ | |||||
| struct filter_t | |||||
| { | |||||
| using field_set_type = std::set<const field_t*>; | |||||
| using table_set_type = std::set<const table_t*>; | |||||
| size_t cache_id; | |||||
| field_set_type fields; | |||||
| table_set_type tables; | |||||
| bool contains(const table_t* table, bool check_base) const; | |||||
| bool contains(const field_t* field) const; | |||||
| }; | |||||
| } | |||||
| end_namespace_cpphibernate_driver_mariadb | |||||
| @@ -26,6 +26,8 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| void update (); | void update (); | ||||
| void print (std::ostream& os) const; | void print (std::ostream& os) const; | ||||
| const table_t& table(size_t dataset_id) const; | |||||
| /* CRUD */ | /* CRUD */ | ||||
| void init(const init_context& context) const; | void init(const init_context& context) const; | ||||
| }; | }; | ||||
| @@ -10,6 +10,7 @@ | |||||
| #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/schema/filter.fwd.h> | |||||
| #include <cpphibernate/driver/mariadb/helper/context.h> | #include <cpphibernate/driver/mariadb/helper/context.h> | ||||
| beg_namespace_cpphibernate_driver_mariadb | beg_namespace_cpphibernate_driver_mariadb | ||||
| @@ -19,6 +20,7 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| struct table_t | struct table_t | ||||
| { | { | ||||
| public: | |||||
| size_t dataset_id { 0 }; | size_t dataset_id { 0 }; | ||||
| size_t base_dataset_id { 0 }; | size_t base_dataset_id { 0 }; | ||||
| size_t table_id { 0 }; | size_t table_id { 0 }; | ||||
| @@ -62,19 +64,43 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| void print(std::ostream& os) const; | void print(std::ostream& os) const; | ||||
| const table_t* get_derived(size_t id) const; | |||||
| /* CRUD */ | /* CRUD */ | ||||
| inline void init(const init_context& context) const | inline void init(const init_context& context) const | ||||
| { return init_intern(context); } | |||||
| { return init_exec(context); } | |||||
| inline decltype(auto) create(const create_context& context) const | |||||
| { return create_intern(context); } | |||||
| inline void read() const | |||||
| { } | |||||
| inline void update(const update_context& context) const | |||||
| { update_intern(context); } | |||||
| private: | private: | ||||
| using statement_ptr = std::unique_ptr<::cppmariadb::statement>; | using statement_ptr = std::unique_ptr<::cppmariadb::statement>; | ||||
| mutable statement_ptr _statement_create_table; | mutable statement_ptr _statement_create_table; | ||||
| mutable statement_ptr _statement_insert_into; | |||||
| ::cppmariadb::statement& get_statement_create_table() const; | ::cppmariadb::statement& get_statement_create_table() const; | ||||
| ::cppmariadb::statement& get_statement_insert_into() const; | |||||
| std::string execute_insert_update( | |||||
| const create_context& context, | |||||
| ::cppmariadb::statement& statement, | |||||
| const filter_t* filter) const; | |||||
| protected: | protected: | ||||
| void init_intern(const init_context& context) const; | |||||
| void init_exec (const init_context& context) const; | |||||
| virtual std::string create_intern (const create_context& context) const; | |||||
| std::string create_exec (const create_context& context) const; | |||||
| virtual std::string update_intern (const update_context& context) const; | |||||
| std::string update_exec (const update_context& context) const; | |||||
| }; | }; | ||||
| /* table_simple_t */ | /* table_simple_t */ | ||||
| @@ -83,9 +109,11 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| struct table_simple_t | struct table_simple_t | ||||
| : public table_t | : public table_t | ||||
| { | { | ||||
| public: | |||||
| 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 dataset_type = typename table_type::dataset_type; | |||||
| const schema_type& schema; | const schema_type& schema; | ||||
| const table_type& table; | const table_type& table; | ||||
| @@ -110,15 +138,21 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| struct table_polymorphic_t | struct table_polymorphic_t | ||||
| : public table_simple_t<T_schema, T_table, T_base_dataset> | : public table_simple_t<T_schema, T_table, T_base_dataset> | ||||
| { | { | ||||
| public: | |||||
| using base_type = table_simple_t<T_schema, T_table, T_base_dataset>; | using base_type = table_simple_t<T_schema, T_table, T_base_dataset>; | ||||
| using schema_type = T_schema; | |||||
| using table_type = T_table; | |||||
| using base_dataset_type = T_base_dataset; | |||||
| using schema_type = typename base_type::schema_type; | |||||
| using table_type = typename base_type::table_type; | |||||
| using base_dataset_type = typename base_type::base_dataset_type; | |||||
| using dataset_type = typename table_type::dataset_type; | |||||
| using base_type::base_type; | using base_type::base_type; | ||||
| const schema_type& schema; | |||||
| const table_type& table; | |||||
| private: | |||||
| template<typename T_dataset, typename T_pred, typename T_include_self> | |||||
| constexpr void for_each_derived(T_dataset& dataset, const T_include_self& include_self, const T_pred& pred) const; | |||||
| protected: | |||||
| virtual std::string create_intern(const create_context& context) const override; | |||||
| }; | }; | ||||
| namespace __impl | namespace __impl | ||||
| @@ -194,6 +228,7 @@ beg_namespace_cpphibernate_driver_mariadb | |||||
| using wrapped_dataset_type = typename mp::decay_t<T_table>::wrapped_dataset_type; | using wrapped_dataset_type = typename mp::decay_t<T_table>::wrapped_dataset_type; | ||||
| using dataset_type = misc::unwrap_t<wrapped_dataset_type>; | using dataset_type = misc::unwrap_t<wrapped_dataset_type>; | ||||
| using table_type = table_type_t<dataset_type, base_type>; | using table_type = table_type_t<dataset_type, base_type>; | ||||
| table_type ret(schema, table); | table_type ret(schema, table); | ||||
| ret.dataset_id = misc::get_type_id(table.wrapped_dataset); | ret.dataset_id = misc::get_type_id(table.wrapped_dataset); | ||||
| ret.base_dataset_id = misc::get_type_id(wrapped_base_type { }); | ret.base_dataset_id = misc::get_type_id(wrapped_base_type { }); | ||||
| @@ -0,0 +1,62 @@ | |||||
| #pragma once | |||||
| #include <cpphibernate/config.h> | |||||
| #include <cpphibernate/driver/mariadb/schema/table.h> | |||||
| beg_namespace_cpphibernate_driver_mariadb | |||||
| { | |||||
| /* table_polymorphic_t */ | |||||
| template<typename T_schema, typename T_table, typename T_base_dataset> | |||||
| template<typename T_dataset, typename T_pred, typename T_include_self> | |||||
| constexpr void table_polymorphic_t<T_schema, T_table, T_base_dataset> | |||||
| ::for_each_derived(T_dataset& dataset, const T_include_self& include_self, const T_pred& pred) const | |||||
| { | |||||
| auto derived_types = hana::filter( | |||||
| schema::get_all_derived_types(this->schema, hana::type_c<dataset_type>), | |||||
| [&](auto type){ | |||||
| return hana::and_( | |||||
| hana::not_(hana::trait<std::is_abstract>(type)), | |||||
| hana::or_( | |||||
| type != hana::type_c<dataset_type>, | |||||
| include_self)); | |||||
| }); | |||||
| hana::for_each(derived_types, [&](auto& type){ | |||||
| using derived_type = misc::decay_unwrap_t<decltype(type)>; | |||||
| auto* derived = dynamic_cast<derived_type*>(&dataset); | |||||
| if (derived) | |||||
| pred(*derived); | |||||
| }); | |||||
| } | |||||
| template<typename T_schema, typename T_table, typename T_base_dataset> | |||||
| std::string table_polymorphic_t<T_schema, T_table, T_base_dataset> | |||||
| ::create_intern(const create_context& ctx) const | |||||
| { | |||||
| bool done = false; | |||||
| auto& context = static_cast<const generic_create_context<dataset_type>&>(ctx); | |||||
| auto& dataset = context.dataset; | |||||
| for_each_derived(dataset, hana::false_c, [&](auto& derived_dataset){ | |||||
| if (!done) | |||||
| { | |||||
| using derived_dataset_type = mp::decay_t<decltype(derived_dataset)>; | |||||
| using reference_stack_type = reference_stack<derived_dataset_type>; | |||||
| auto derived_dataset_id = misc::get_type_id(hana::type_c<derived_dataset_type>); | |||||
| auto derived_table = this->get_derived(derived_dataset_id); | |||||
| if (!derived_table) | |||||
| throw misc::hibernate_exception(std::string("unable to find derived table info for dataset '") + utl::type_helper<derived_dataset_type>::name() + "'!"); | |||||
| auto ref_lock = reference_stack_type::push(derived_dataset); | |||||
| derived_table->create(context.change(derived_dataset, context.owner_field)); | |||||
| done = true; | |||||
| } | |||||
| }); | |||||
| return done | |||||
| ? *this->primary_key_field->get() | |||||
| : this->create_exec(context); | |||||
| } | |||||
| } | |||||
| end_namespace_cpphibernate_driver_mariadb | |||||
| @@ -18,8 +18,9 @@ beg_namespace_cpphibernate_misc | |||||
| using nullable_type = T_nullable; | using nullable_type = T_nullable; | ||||
| using value_type = real_dataset_t<nullable_type>; | 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; | |||||
| static value_type* get (const nullable_type&) = delete; | |||||
| static value_type& set (nullable_type&, const value_type&) = delete; | |||||
| static void clear (nullable_type&) = delete; | |||||
| }; | }; | ||||
| /* nullable_helper - utl::nullable */ | /* nullable_helper - utl::nullable */ | ||||
| @@ -31,15 +32,19 @@ beg_namespace_cpphibernate_misc | |||||
| using value_type = T_value; | using value_type = T_value; | ||||
| static inline value_type* get(nullable_type& x) | static inline value_type* get(nullable_type& x) | ||||
| { | |||||
| return x.has_value() ? &x.value() : nullptr; | |||||
| } | |||||
| { return x.has_value() ? &x.value() : nullptr; } | |||||
| static void reset(nullable_type& x, value_type* value = nullptr) | |||||
| { | |||||
| if (value) x.reset(); | |||||
| else x = *value; | |||||
| } | |||||
| static inline const value_type* get(const nullable_type& x) | |||||
| { return x.has_value() ? &x.value() : nullptr; } | |||||
| static inline value_type& set(nullable_type& x, const value_type& value) | |||||
| { return *(x = value); } | |||||
| static inline value_type& set(nullable_type& x, value_type&& value) | |||||
| { return *(x = std::move(value)); } | |||||
| static void clear(nullable_type& x) | |||||
| { x.reset(); } | |||||
| }; | }; | ||||
| /* nullable_helper - std::unique_ptr */ | /* nullable_helper - std::unique_ptr */ | ||||
| @@ -50,11 +55,23 @@ beg_namespace_cpphibernate_misc | |||||
| using nullable_type = std::unique_ptr<T_value>; | using nullable_type = std::unique_ptr<T_value>; | ||||
| using value_type = T_value; | using value_type = T_value; | ||||
| static inline value_type* get(nullable_type& x) | |||||
| static inline value_type* get(const nullable_type& x) | |||||
| { return x.get(); } | { return x.get(); } | ||||
| static void reset(nullable_type& x, value_type* value = nullptr) | |||||
| { return x.reset(value); } | |||||
| static inline value_type& set(nullable_type& x, value_type&& value) | |||||
| { | |||||
| x.reset(new value_type(std::move(value))); | |||||
| return *x; | |||||
| } | |||||
| static inline value_type& set(nullable_type& x, const value_type& value) | |||||
| { | |||||
| x.reset(new value_type(value)); | |||||
| return *x; | |||||
| } | |||||
| static void clear(nullable_type& x) | |||||
| { return x.reset(); } | |||||
| }; | }; | ||||
| /* nullable_helper - std::shared_ptr */ | /* nullable_helper - std::shared_ptr */ | ||||
| @@ -65,11 +82,23 @@ beg_namespace_cpphibernate_misc | |||||
| using nullable_type = std::shared_ptr<T_value>; | using nullable_type = std::shared_ptr<T_value>; | ||||
| using value_type = T_value; | using value_type = T_value; | ||||
| static inline value_type* get(nullable_type& x) | |||||
| static inline value_type* get(const nullable_type& x) | |||||
| { return x.get(); } | { return x.get(); } | ||||
| static void reset(nullable_type& x, value_type* value = nullptr) | |||||
| { return x.reset(value); } | |||||
| static inline value_type& set(nullable_type& x, value_type&& value) | |||||
| { | |||||
| x.reset(new value_type(std::move(value))); | |||||
| return *x; | |||||
| } | |||||
| static inline value_type& set(nullable_type& x, const value_type& value) | |||||
| { | |||||
| x.reset(new value_type(value)); | |||||
| return *x; | |||||
| } | |||||
| static void clear(nullable_type& x) | |||||
| { return x.reset(); } | |||||
| }; | }; | ||||
| } | } | ||||
| @@ -180,7 +180,7 @@ beg_namespace_cpphibernate_schema | |||||
| /* schema::get_all_derived_types */ | /* schema::get_all_derived_types */ | ||||
| namespace __impl | namespace __impl | ||||
| { | |||||
| { | |||||
| struct schema_get_all_derived_types_impl | struct schema_get_all_derived_types_impl | ||||
| { | { | ||||
| template<typename T_wrapped_dataset> | template<typename T_wrapped_dataset> | ||||
| @@ -192,7 +192,7 @@ beg_namespace_cpphibernate_schema | |||||
| constexpr decltype(auto) operator()(T_type&&) const | constexpr decltype(auto) operator()(T_type&&) const | ||||
| { | { | ||||
| return hana::bool_c< | return hana::bool_c< | ||||
| std::is_base_of<dataset_type, misc::unwrap_t<T_type>>::value>; | |||||
| std::is_base_of<dataset_type, misc::decay_unwrap_t<T_type>>::value>; | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -245,7 +245,7 @@ beg_namespace_cpphibernate_schema | |||||
| } | } | ||||
| }; | }; | ||||
| } | } | ||||
| constexpr decltype(auto) get_derived_types = __impl::schema_get_derived_types_impl { }; | constexpr decltype(auto) get_derived_types = __impl::schema_get_derived_types_impl { }; | ||||
| } | } | ||||
| @@ -19,6 +19,7 @@ beg_namespace_cpphibernate_schema | |||||
| { | { | ||||
| using name_type = T_name; | using name_type = T_name; | ||||
| using wrapped_dataset_type = mp::decay_t<T_wrapped_dataset>; | using wrapped_dataset_type = mp::decay_t<T_wrapped_dataset>; | ||||
| using dataset_type = misc::decay_unwrap_t<wrapped_dataset_type>; | |||||
| using table_id_type = T_table_id; | using table_id_type = T_table_id; | ||||
| using fields_type = T_fields; | using fields_type = T_fields; | ||||
| using this_type = table_t<name_type, wrapped_dataset_type, table_id_type, fields_type>; | using this_type = table_t<name_type, wrapped_dataset_type, table_id_type, fields_type>; | ||||
| @@ -33,6 +33,6 @@ Target_Link_Libraries ( | |||||
| If ( __COTIRE_INCLUDED ) | If ( __COTIRE_INCLUDED ) | ||||
| Cotire ( cpphibernate ) | Cotire ( cpphibernate ) | ||||
| EndIf ( ) | EndIf ( ) | ||||
| If ( __STRIP_SYMBOLS_INCLUDED ) | |||||
| If ( __STRIP_SYMBOLS_INCLUDED AND BUILD_SHARED_LIBS ) | |||||
| Strip_Symbols ( cpphibernate DBG_FILE ) | Strip_Symbols ( cpphibernate DBG_FILE ) | ||||
| EndIf () | EndIf () | ||||
| @@ -31,13 +31,97 @@ void field_t::print(std::ostream& os) const | |||||
| << indent << '}'; | << indent << '}'; | ||||
| } | } | ||||
| #define throw_not_implemented(p_ret, p_name) \ | |||||
| p_ret field_t::p_name() const \ | |||||
| #define throw_not_implemented(p_ret, p_name, ...) \ | |||||
| p_ret field_t::p_name(__VA_ARGS__) const \ | |||||
| { \ | { \ | ||||
| throw misc::hibernate_exception( \ | throw misc::hibernate_exception( \ | ||||
| std::string("'") + table_name + "." + field_name + \ | std::string("'") + table_name + "." + field_name + \ | ||||
| "' does not implement the " #p_name "() method!"); \ | "' does not implement the " #p_name "() method!"); \ | ||||
| } | } | ||||
| throw_not_implemented(string, type) | |||||
| throw_not_implemented(string, create_table_arguments) | |||||
| /* CRUD */ | |||||
| throw_not_implemented(value_t, foreign_create, const create_context&) | |||||
| throw_not_implemented(value_t, foreign_update, const update_context&) | |||||
| /* properties */ | |||||
| throw_not_implemented(string, type) | |||||
| throw_not_implemented(string, create_table_arguments) | |||||
| throw_not_implemented(string, generate_value, ::cppmariadb::connection&) | |||||
| throw_not_implemented(value_t, get) | |||||
| throw_not_implemented(void, set, const value_t&) | |||||
| bool field_t::is_auto_generated() const | |||||
| { return false; } | |||||
| std::string field_t::convert_to_open() const | |||||
| { | |||||
| std::ostringstream ss; | |||||
| for (auto it = this->attributes.begin(); it != this->attributes.end(); ++it) | |||||
| { | |||||
| switch(*it) | |||||
| { | |||||
| case attribute_t::hex: ss << "HEX("; break; | |||||
| case attribute_t::compress: ss << "COMPRESS("; break; | |||||
| case attribute_t::primary_key: break; | |||||
| } | |||||
| } | |||||
| return ss.str(); | |||||
| } | |||||
| std::string field_t::convert_to_close() const | |||||
| { | |||||
| std::ostringstream ss; | |||||
| for (auto it = this->attributes.begin(); it != this->attributes.end(); ++it) | |||||
| { | |||||
| switch(*it) | |||||
| { | |||||
| case attribute_t::hex: | |||||
| case attribute_t::compress: | |||||
| ss << ')'; | |||||
| break; | |||||
| case attribute_t::primary_key: | |||||
| break; | |||||
| } | |||||
| } | |||||
| return ss.str(); | |||||
| } | |||||
| std::string field_t::convert_from_open() const | |||||
| { | |||||
| std::ostringstream ss; | |||||
| for (auto it = this->attributes.rbegin(); it != this->attributes.rend(); ++it) | |||||
| { | |||||
| switch(*it) | |||||
| { | |||||
| case attribute_t::hex: | |||||
| ss << "UNHEX("; | |||||
| break; | |||||
| case attribute_t::compress: | |||||
| ss << "UNCOMPRESS("; | |||||
| break; | |||||
| case attribute_t::primary_key: | |||||
| break; | |||||
| } | |||||
| } | |||||
| return ss.str(); | |||||
| } | |||||
| std::string field_t::convert_from_close() const | |||||
| { | |||||
| std::ostringstream ss; | |||||
| for (auto it = this->attributes.rbegin(); it != this->attributes.rend(); ++it) | |||||
| { | |||||
| switch(*it) | |||||
| { | |||||
| case attribute_t::hex: | |||||
| case attribute_t::compress: | |||||
| ss << ')'; | |||||
| break; | |||||
| case attribute_t::primary_key: | |||||
| break; | |||||
| } | |||||
| } | |||||
| return ss.str(); | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| #include <cpphibernate/driver/mariadb/schema/table.h> | |||||
| #include <cpphibernate/driver/mariadb/schema/filter.h> | |||||
| using namespace ::cpphibernate::driver::mariadb_impl; | |||||
| bool filter_t::contains(const table_t* table, bool check_base) const | |||||
| { | |||||
| if (tables.count(table)) | |||||
| return true; | |||||
| else if (check_base && table->base_table) | |||||
| return contains(table->base_table, true); | |||||
| else | |||||
| return false; | |||||
| } | |||||
| bool filter_t::contains(const field_t* field) const | |||||
| { | |||||
| return (fields.count(field) > 0); | |||||
| } | |||||
| @@ -111,6 +111,15 @@ void schema_t::print(std::ostream& os) const | |||||
| << indent << '}'; | << indent << '}'; | ||||
| } | } | ||||
| const table_t& schema_t::table(size_t dataset_id) const | |||||
| { | |||||
| auto it = tables.find(dataset_id); | |||||
| if (it == tables.end()) | |||||
| throw misc::hibernate_exception(std::string("unable to find table for dataset with id ") + std::to_string(dataset_id)); | |||||
| assert(static_cast<bool>(it->second)); | |||||
| return *it->second; | |||||
| } | |||||
| #define exec_query() \ | #define exec_query() \ | ||||
| do { \ | do { \ | ||||
| cpphibernate_debug_log("execute init query: " << ss.str()); \ | cpphibernate_debug_log("execute init query: " << ss.str()); \ | ||||
| @@ -7,57 +7,20 @@ | |||||
| #include <cpphibernate/misc.h> | #include <cpphibernate/misc.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/table.h> | #include <cpphibernate/driver/mariadb/schema/table.h> | ||||
| #include <cpphibernate/driver/mariadb/schema/filter.h> | |||||
| using namespace ::utl; | using namespace ::utl; | ||||
| using namespace ::cpphibernate::driver::mariadb_impl; | using namespace ::cpphibernate::driver::mariadb_impl; | ||||
| void table_t::print(std::ostream& os) const | |||||
| { | |||||
| os << indent << '{' | |||||
| << incindent | |||||
| << indent << "\"dataset_id\": " << dataset_id << "," | |||||
| << indent << "\"base_dataset_id\": " << base_dataset_id << "," | |||||
| << indent << "\"table_id\": " << table_id << "," | |||||
| << indent << "\"derived_dataset_ids\": " << misc::print_container(derived_dataset_ids, false) << "," | |||||
| << indent << "\"schema_name\": \"" << schema_name << "\"," | |||||
| << indent << "\"table_name\": \"" << table_name << "\"," | |||||
| << indent << "\"fields\":" << misc::print_container(fields, true, [](auto& os, auto& field) { | |||||
| field->print(os); | |||||
| }) << "," | |||||
| << indent << "\"base_table\": " << (base_table ? std::string("\"") + base_table->table_name + "\"" : "null") << "," | |||||
| << indent << "\"derived_tables\":" << misc::print_container(derived_tables, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->table_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"primary_key_field\": " << (primary_key_field ? std::string("\"") + primary_key_field->field_name + "\"" : "null") << "," | |||||
| << indent << "\"foreign_key_fields\": " << misc::print_container(foreign_key_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->table_name << '.' << ptr->field_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"foreign_table_fields\": " << misc::print_container(foreign_table_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->field_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"foreign_table_one_fields\": " << misc::print_container(foreign_table_one_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->field_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"foreign_table_many_fields\": " << misc::print_container(foreign_table_many_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->field_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"data_fields\": " << misc::print_container(data_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->field_name << '"'; | |||||
| }) | |||||
| << decindent | |||||
| << indent << '}'; | |||||
| } | |||||
| /* build queries */ | |||||
| ::cppmariadb::statement& table_t::get_statement_create_table() const | |||||
| std::string build_create_table_query(const table_t& table) | |||||
| { | { | ||||
| if (_statement_create_table) | |||||
| return *_statement_create_table; | |||||
| std::ostringstream os; | |||||
| std::ostringstream os; | |||||
| /* CREATE TABLE */ | /* CREATE TABLE */ | ||||
| os << "CREATE TABLE IF NOT EXISTS `" | os << "CREATE TABLE IF NOT EXISTS `" | ||||
| << table_name | |||||
| << table.table_name | |||||
| << "`" | << "`" | ||||
| << indent | << indent | ||||
| << "(" | << "(" | ||||
| @@ -65,8 +28,8 @@ void table_t::print(std::ostream& os) const | |||||
| /* primary key */ | /* primary key */ | ||||
| { | { | ||||
| assert(primary_key_field); | |||||
| auto& key_info = *primary_key_field; | |||||
| assert(table.primary_key_field); | |||||
| auto& key_info = *table.primary_key_field; | |||||
| auto args = key_info.create_table_arguments(); | auto args = key_info.create_table_arguments(); | ||||
| os << indent | os << indent | ||||
| << "`" | << "`" | ||||
| @@ -80,9 +43,9 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* base table key fields */ | /* base table key fields */ | ||||
| if (static_cast<bool>(base_table)) | |||||
| if (static_cast<bool>(table.base_table)) | |||||
| { | { | ||||
| auto& base_table_info = *base_table; | |||||
| auto& base_table_info = *table.base_table; | |||||
| assert(base_table_info.primary_key_field); | assert(base_table_info.primary_key_field); | ||||
| auto& key_info = *base_table_info.primary_key_field; | auto& key_info = *base_table_info.primary_key_field; | ||||
| os << indent | os << indent | ||||
| @@ -94,7 +57,7 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* foreign table one fields */ | /* foreign table one fields */ | ||||
| for (auto& ptr : foreign_table_one_fields) | |||||
| for (auto& ptr : table.foreign_table_one_fields) | |||||
| { | { | ||||
| assert(static_cast<bool>(ptr)); | assert(static_cast<bool>(ptr)); | ||||
| auto& field_info = *ptr; | auto& field_info = *ptr; | ||||
| @@ -114,7 +77,7 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* foreign fields */ | /* foreign fields */ | ||||
| for (auto& ptr : foreign_key_fields) | |||||
| for (auto& ptr : table.foreign_key_fields) | |||||
| { | { | ||||
| assert(static_cast<bool>(ptr)); | assert(static_cast<bool>(ptr)); | ||||
| auto& field_info = *ptr; | auto& field_info = *ptr; | ||||
| @@ -132,7 +95,7 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* data fields */ | /* data fields */ | ||||
| for (auto& ptr : data_fields) | |||||
| for (auto& ptr : table.data_fields) | |||||
| { | { | ||||
| assert(static_cast<bool>(ptr)); | assert(static_cast<bool>(ptr)); | ||||
| auto& field_info = *ptr; | auto& field_info = *ptr; | ||||
| @@ -147,8 +110,8 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* type field for derived tables */ | /* type field for derived tables */ | ||||
| if (!derived_tables.empty() && | |||||
| !base_table) | |||||
| if (!table.derived_tables.empty() && | |||||
| !table.base_table) | |||||
| { | { | ||||
| os << indent | os << indent | ||||
| << "`__type` INT UNSIGNED NOT NULL,"; | << "`__type` INT UNSIGNED NOT NULL,"; | ||||
| @@ -156,25 +119,32 @@ void table_t::print(std::ostream& os) const | |||||
| /* PRIMARY KEY */ | /* PRIMARY KEY */ | ||||
| { | { | ||||
| assert(table.primary_key_field); | |||||
| auto& key_info = *table.primary_key_field; | |||||
| os << indent | os << indent | ||||
| << "PRIMARY KEY ( `" | << "PRIMARY KEY ( `" | ||||
| << primary_key_field->field_name | |||||
| << key_info.field_name | |||||
| << "` )"; | << "` )"; | ||||
| } | } | ||||
| /* UNIQUE INDEX primary key */ | /* UNIQUE INDEX primary key */ | ||||
| os << ',' | |||||
| << indent | |||||
| << "UNIQUE INDEX `index_" | |||||
| << primary_key_field->field_name | |||||
| << "` ( `" | |||||
| << primary_key_field->field_name | |||||
| << "` ASC )"; | |||||
| { | |||||
| assert(table.primary_key_field); | |||||
| auto& key_info = *table.primary_key_field; | |||||
| os << ',' | |||||
| << indent | |||||
| << "UNIQUE INDEX `index_" | |||||
| << key_info.field_name | |||||
| << "` ( `" | |||||
| << key_info.field_name | |||||
| << "` ASC )"; | |||||
| } | |||||
| /* UNIQUE INDEX base table keys */ | /* UNIQUE INDEX base table keys */ | ||||
| if (base_table) | |||||
| if (table.base_table) | |||||
| { | { | ||||
| auto& table_info = *base_table; | |||||
| auto& table_info = *table.base_table; | |||||
| assert(table_info.primary_key_field); | |||||
| auto& key_info = *table_info.primary_key_field; | auto& key_info = *table_info.primary_key_field; | ||||
| os << ',' | os << ',' | ||||
| << indent | << indent | ||||
| @@ -186,7 +156,7 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* INDEX foreign table one fields */ | /* INDEX foreign table one fields */ | ||||
| for (auto& ptr : foreign_table_one_fields) | |||||
| for (auto& ptr : table.foreign_table_one_fields) | |||||
| { | { | ||||
| assert(static_cast<bool>(ptr)); | assert(static_cast<bool>(ptr)); | ||||
| auto& field_info = *ptr; | auto& field_info = *ptr; | ||||
| @@ -207,7 +177,7 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* INDEX foreign fields */ | /* INDEX foreign fields */ | ||||
| for (auto& ptr : foreign_key_fields) | |||||
| for (auto& ptr : table.foreign_key_fields) | |||||
| { | { | ||||
| assert(static_cast<bool>(ptr)); | assert(static_cast<bool>(ptr)); | ||||
| auto& field_info = *ptr; | auto& field_info = *ptr; | ||||
| @@ -225,14 +195,14 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* CONSTRAINT base table */ | /* CONSTRAINT base table */ | ||||
| if (base_table) | |||||
| if (table.base_table) | |||||
| { | { | ||||
| assert(base_table->primary_key_field); | |||||
| auto& ref_key_info = *base_table->primary_key_field; | |||||
| assert(table.base_table->primary_key_field); | |||||
| auto& ref_key_info = *table.base_table->primary_key_field; | |||||
| os << "," | os << "," | ||||
| << indent | << indent | ||||
| << "CONSTRAINT `fk_" | << "CONSTRAINT `fk_" | ||||
| << table_name | |||||
| << table.table_name | |||||
| << "_to_" | << "_to_" | ||||
| << ref_key_info.field_name | << ref_key_info.field_name | ||||
| << "`" | << "`" | ||||
| @@ -257,7 +227,7 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* CONSTRAINT foreign table one fields */ | /* CONSTRAINT foreign table one fields */ | ||||
| for (auto& ptr : foreign_table_one_fields) | |||||
| for (auto& ptr : table.foreign_table_one_fields) | |||||
| { | { | ||||
| assert(static_cast<bool>(ptr)); | assert(static_cast<bool>(ptr)); | ||||
| auto& field_info = *ptr; | auto& field_info = *ptr; | ||||
| @@ -267,7 +237,7 @@ void table_t::print(std::ostream& os) const | |||||
| os << "," | os << "," | ||||
| << indent | << indent | ||||
| << "CONSTRAINT `fk_" | << "CONSTRAINT `fk_" | ||||
| << table_name | |||||
| << table.table_name | |||||
| << "_to_" | << "_to_" | ||||
| << ref_key_info.table_name | << ref_key_info.table_name | ||||
| << "_id_" | << "_id_" | ||||
| @@ -296,7 +266,7 @@ void table_t::print(std::ostream& os) const | |||||
| } | } | ||||
| /* CONSTRAINT foreign fields */ | /* CONSTRAINT foreign fields */ | ||||
| for (auto& ptr : foreign_key_fields) | |||||
| for (auto& ptr : table.foreign_key_fields) | |||||
| { | { | ||||
| assert(static_cast<bool>(ptr)); | assert(static_cast<bool>(ptr)); | ||||
| auto& field_info = *ptr; | auto& field_info = *ptr; | ||||
| @@ -306,7 +276,7 @@ void table_t::print(std::ostream& os) const | |||||
| os << "," | os << "," | ||||
| << indent | << indent | ||||
| << "CONSTRAINT `fk_" | << "CONSTRAINT `fk_" | ||||
| << table_name | |||||
| << table.table_name | |||||
| << "_" | << "_" | ||||
| << field_info.table_name | << field_info.table_name | ||||
| << "_id_" | << "_id_" | ||||
| @@ -343,14 +313,422 @@ void table_t::print(std::ostream& os) const | |||||
| << indent | << indent | ||||
| << "DEFAULT CHARACTER SET = utf8"; | << "DEFAULT CHARACTER SET = utf8"; | ||||
| _statement_create_table.reset(new ::cppmariadb::statement(os.str())); | |||||
| return os.str(); | |||||
| } | |||||
| std::string build_insert_update_query(const table_t& table, const filter_t* filter, const field_t* owner) | |||||
| { | |||||
| std::ostringstream os; | |||||
| size_t index = 0; | |||||
| bool is_update = static_cast<bool>(filter); | |||||
| /* INSER INTO / UPDATE */ | |||||
| os << (is_update | |||||
| ? "UPDATE" | |||||
| : "INSERT INTO") | |||||
| << " `" | |||||
| << table.table_name | |||||
| << "` SET "; | |||||
| /* primary key */ | |||||
| if (!is_update) | |||||
| { | |||||
| assert(table.primary_key_field); | |||||
| auto& key_info = *table.primary_key_field; | |||||
| if (!key_info.is_auto_generated()) | |||||
| { | |||||
| if (index++) | |||||
| os << ", "; | |||||
| os << "`" | |||||
| << key_info.field_name | |||||
| << "`=" | |||||
| << key_info.convert_to_open() | |||||
| << "?" | |||||
| << key_info.field_name | |||||
| << "?" | |||||
| << key_info.convert_to_close(); | |||||
| } | |||||
| } | |||||
| /* base table key fields */ | |||||
| if ( static_cast<bool>(table.base_table) | |||||
| && ( !is_update | |||||
| || filter->contains(table.base_table, true))) | |||||
| { | |||||
| if (index++) | |||||
| os << ", "; | |||||
| auto& base_table_info = *table.base_table; | |||||
| assert(base_table_info.primary_key_field); | |||||
| auto& key_info = *base_table_info.primary_key_field; | |||||
| os << "`" | |||||
| << key_info.field_name | |||||
| << "`=" | |||||
| << key_info.convert_to_open() | |||||
| << "?" | |||||
| << key_info.field_name | |||||
| << "?" | |||||
| << key_info.convert_to_close(); | |||||
| } | |||||
| /* foreign table one fields */ | |||||
| for (auto& ptr : table.foreign_table_one_fields) | |||||
| { | |||||
| assert(static_cast<bool>(ptr)); | |||||
| if (is_update && !filter->contains(ptr)) | |||||
| continue; | |||||
| if (index++) | |||||
| os << ", "; | |||||
| auto& field_info = *ptr; | |||||
| assert(field_info.referenced_table); | |||||
| assert(field_info.referenced_table->primary_key_field); | |||||
| auto& key_info = *field_info.referenced_table->primary_key_field; | |||||
| os << "`" | |||||
| << key_info.table_name | |||||
| << "_id_" | |||||
| << field_info.field_name | |||||
| << "`=" | |||||
| << key_info.convert_to_open() | |||||
| << "?" | |||||
| << key_info.table_name | |||||
| << "_id_" | |||||
| << field_info.field_name | |||||
| << "?" | |||||
| << key_info.convert_to_close(); | |||||
| } | |||||
| /* foreign fields */ | |||||
| for (auto& ptr : table.foreign_key_fields) | |||||
| { | |||||
| assert(static_cast<bool>(ptr)); | |||||
| if (is_update && ptr != owner) | |||||
| continue; | |||||
| if (index++) | |||||
| os << ", "; | |||||
| auto& field_info = *ptr; | |||||
| assert(field_info.table); | |||||
| assert(field_info.table->primary_key_field); | |||||
| auto& key_info = *field_info.table->primary_key_field; | |||||
| os << "`" | |||||
| << field_info.table_name | |||||
| << "_id_" | |||||
| << field_info.field_name | |||||
| << "`=" | |||||
| << key_info.convert_to_open() | |||||
| << "?" | |||||
| << field_info.table_name | |||||
| << "_id_" | |||||
| << field_info.field_name | |||||
| << "?" | |||||
| << key_info.convert_to_close(); | |||||
| } | |||||
| /* data fields */ | |||||
| for (auto& ptr : table.data_fields) | |||||
| { | |||||
| if (is_update && !filter->contains(ptr)) | |||||
| continue; | |||||
| if (index++) | |||||
| os << ", "; | |||||
| assert(static_cast<bool>(ptr)); | |||||
| auto& field_info = *ptr; | |||||
| os << "`" | |||||
| << field_info.field_name | |||||
| << "`=" | |||||
| << field_info.convert_to_open() | |||||
| << "?" | |||||
| << field_info.field_name | |||||
| << "?" | |||||
| << field_info.convert_to_close(); | |||||
| } | |||||
| /* type field for derived tables */ | |||||
| if (!table.derived_tables.empty() && | |||||
| !table.base_table) | |||||
| { | |||||
| if (index++) | |||||
| os << ", "; | |||||
| os << "`__type`=?__type?"; | |||||
| } | |||||
| /* where primary key (for update) */ | |||||
| if (is_update) | |||||
| { | |||||
| assert(table.primary_key_field); | |||||
| auto& key_info = *table.primary_key_field; | |||||
| os << " WHERE `" | |||||
| << key_info.field_name | |||||
| << "`=" | |||||
| << key_info.convert_to_open() | |||||
| << "?" | |||||
| << key_info.field_name | |||||
| << "?" | |||||
| << key_info.convert_to_close(); | |||||
| } | |||||
| return os.str(); | |||||
| } | |||||
| /* execute_insert_update */ | |||||
| std::string table_t::execute_insert_update( | |||||
| const create_context& context, | |||||
| ::cppmariadb::statement& statement, | |||||
| const filter_t* filter) const | |||||
| { | |||||
| auto& connection = context.connection; | |||||
| size_t index = 0; | |||||
| bool is_update = static_cast<bool>(filter); | |||||
| std::string primary_key; | |||||
| statement.clear(); | |||||
| /* primary key */ | |||||
| assert(primary_key_field); | |||||
| if ( !primary_key_field->is_auto_generated() | |||||
| && !is_update) | |||||
| { | |||||
| primary_key = primary_key_field->generate_value(context.connection); | |||||
| statement.set(index, primary_key); | |||||
| ++index; | |||||
| } | |||||
| else | |||||
| { | |||||
| primary_key = *primary_key_field->get(); | |||||
| } | |||||
| /* base_key */ | |||||
| if ( base_table | |||||
| && ( !is_update | |||||
| || filter->contains(base_table, true))) | |||||
| { | |||||
| std::string key; | |||||
| if (is_update) | |||||
| { | |||||
| auto new_context = static_cast<const update_context&>(context); | |||||
| if (!new_context.derived_table) | |||||
| new_context.derived_table = this; | |||||
| key = base_table->update_exec(new_context); | |||||
| } | |||||
| else | |||||
| { | |||||
| auto new_context = context; | |||||
| if (!new_context.derived_table) | |||||
| new_context.derived_table = this; | |||||
| key = base_table->create_exec(new_context); | |||||
| } | |||||
| statement.set(index, std::move(key)); | |||||
| ++index; | |||||
| } | |||||
| if (is_update && !filter->contains(this, false)) | |||||
| return primary_key; | |||||
| /* foreign table one fields */ | |||||
| for (auto& ptr : foreign_table_one_fields) | |||||
| { | |||||
| assert(ptr); | |||||
| if (is_update && !filter->contains(ptr)) | |||||
| continue; | |||||
| value_t key = !is_update | |||||
| ? ptr->foreign_create(context) | |||||
| : ptr->foreign_update(static_cast<const update_context&>(context)); | |||||
| if (key.has_value()) statement.set(index, std::move(key)); | |||||
| else statement.set_null(index); | |||||
| ++index; | |||||
| } | |||||
| /* foreign fields */ | |||||
| for (auto& ptr : foreign_key_fields) | |||||
| { | |||||
| if (is_update && ptr != context.owner_field) | |||||
| continue; | |||||
| if ( context.owner_field | |||||
| && ptr == context.owner_field) | |||||
| { | |||||
| auto& field_info = *ptr; | |||||
| assert(field_info.table); | |||||
| assert(field_info.table->primary_key_field); | |||||
| statement.set(index, field_info.table->primary_key_field->get()); | |||||
| } | |||||
| else | |||||
| statement.set_null(index); | |||||
| ++index; | |||||
| } | |||||
| /* data fields */ | |||||
| for (auto& ptr : data_fields) | |||||
| { | |||||
| if (is_update && !filter->contains(ptr)) | |||||
| continue; | |||||
| assert(ptr); | |||||
| auto& field_info = *ptr; | |||||
| auto value = field_info.get(); | |||||
| if (value.has_value()) statement.set(index, *value); | |||||
| else statement.set_null(index); | |||||
| ++index; | |||||
| } | |||||
| /* type field for derived tables */ | |||||
| if (!derived_tables.empty() && | |||||
| !base_table) | |||||
| { | |||||
| statement.set(index, context.derived_table | |||||
| ? context.derived_table->table_id | |||||
| : table_id); | |||||
| ++index; | |||||
| } | |||||
| /* where primary key (for update) */ | |||||
| if (is_update) | |||||
| { | |||||
| assert(primary_key_field); | |||||
| statement.set(index, *primary_key_field->get()); | |||||
| ++index; | |||||
| } | |||||
| /* execute */ | |||||
| if (!is_update) | |||||
| { | |||||
| cpphibernate_debug_log("execute INSERT query: " << statement.query(connection)); | |||||
| } | |||||
| else | |||||
| { | |||||
| cpphibernate_debug_log("execute UPDATE query: " << statement.query(connection)); | |||||
| } | |||||
| if ( primary_key_field->is_auto_generated() | |||||
| && !is_update) | |||||
| { | |||||
| auto id = connection.execute_id(statement); | |||||
| primary_key = utl::to_string(id); | |||||
| } | |||||
| else | |||||
| { | |||||
| auto count = connection.execute_rows(statement); | |||||
| cpphibernate_debug_log(count << " rows inserted/updated"); | |||||
| } | |||||
| primary_key_field->set(primary_key); | |||||
| /* foreign table many fields */ | |||||
| for (auto& ptr : foreign_table_many_fields) | |||||
| { | |||||
| assert(ptr); | |||||
| if ( is_update | |||||
| && ( !filter->contains(ptr) | |||||
| || !filter->contains(ptr->referenced_table, true))) | |||||
| continue; | |||||
| if (!is_update) | |||||
| { | |||||
| auto next_context = context; | |||||
| next_context.owner_field = ptr; | |||||
| ptr->foreign_create(next_context); | |||||
| } | |||||
| else | |||||
| { | |||||
| auto next_context = static_cast<const update_context&>(context); | |||||
| next_context.owner_field = ptr; | |||||
| ptr->foreign_update(next_context); | |||||
| } | |||||
| } | |||||
| return primary_key; | |||||
| } | |||||
| /* table_t */ | |||||
| void table_t::print(std::ostream& os) const | |||||
| { | |||||
| os << indent << '{' | |||||
| << incindent | |||||
| << indent << "\"dataset_id\": " << dataset_id << "," | |||||
| << indent << "\"base_dataset_id\": " << base_dataset_id << "," | |||||
| << indent << "\"table_id\": " << table_id << "," | |||||
| << indent << "\"derived_dataset_ids\": " << misc::print_container(derived_dataset_ids, false) << "," | |||||
| << indent << "\"schema_name\": \"" << schema_name << "\"," | |||||
| << indent << "\"table_name\": \"" << table_name << "\"," | |||||
| << indent << "\"fields\":" << misc::print_container(fields, true, [](auto& os, auto& field) { | |||||
| field->print(os); | |||||
| }) << "," | |||||
| << indent << "\"base_table\": " << (base_table ? std::string("\"") + base_table->table_name + "\"" : "null") << "," | |||||
| << indent << "\"derived_tables\":" << misc::print_container(derived_tables, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->table_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"primary_key_field\": " << (primary_key_field ? std::string("\"") + primary_key_field->field_name + "\"" : "null") << "," | |||||
| << indent << "\"foreign_key_fields\": " << misc::print_container(foreign_key_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->table_name << '.' << ptr->field_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"foreign_table_fields\": " << misc::print_container(foreign_table_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->field_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"foreign_table_one_fields\": " << misc::print_container(foreign_table_one_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->field_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"foreign_table_many_fields\": " << misc::print_container(foreign_table_many_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->field_name << '"'; | |||||
| }) << "," | |||||
| << indent << "\"data_fields\": " << misc::print_container(data_fields, true, [](auto& os, auto& ptr){ | |||||
| os << indent << '"' << ptr->field_name << '"'; | |||||
| }) | |||||
| << decindent | |||||
| << indent << '}'; | |||||
| } | |||||
| const table_t* table_t::get_derived(size_t id) const | |||||
| { | |||||
| if (dataset_id == id) | |||||
| return this; | |||||
| for (auto ptr : derived_tables) | |||||
| { | |||||
| assert(ptr); | |||||
| auto ret = ptr->get_derived(id); | |||||
| if (ret) return ret; | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| ::cppmariadb::statement& table_t::get_statement_create_table() const | |||||
| { | |||||
| if (_statement_create_table) | |||||
| return *_statement_create_table; | |||||
| auto query = build_create_table_query(*this); | |||||
| _statement_create_table.reset(new ::cppmariadb::statement(query)); | |||||
| return *_statement_create_table; | |||||
| } | |||||
| ::cppmariadb::statement& table_t::get_statement_insert_into() const | |||||
| { | |||||
| if (_statement_insert_into) | |||||
| return *_statement_insert_into; | |||||
| auto query = build_insert_update_query(*this, nullptr, nullptr); | |||||
| _statement_create_table.reset(new ::cppmariadb::statement(query)); | |||||
| return *_statement_create_table; | return *_statement_create_table; | ||||
| } | } | ||||
| void table_t::init_intern(const init_context& context) const | |||||
| void table_t::init_exec(const init_context& context) const | |||||
| { | { | ||||
| auto& statement = get_statement_create_table(); | auto& statement = get_statement_create_table(); | ||||
| auto& connection = context.connection; | auto& connection = context.connection; | ||||
| cpphibernate_debug_log("execute init query: " << statement.query(connection)); | |||||
| cpphibernate_debug_log("execute CREATE TABLE query: " << statement.query(connection)); | |||||
| connection.execute(statement); | connection.execute(statement); | ||||
| } | |||||
| } | |||||
| std::string table_t::create_exec(const create_context& context) const | |||||
| { | |||||
| auto& statement = get_statement_insert_into(); | |||||
| return execute_insert_update(context, statement, nullptr); | |||||
| } | |||||
| std::string table_t::update_exec(const update_context& context) const | |||||
| { | |||||
| return std::string(); | |||||
| } | |||||
| std::string table_t::create_intern(const create_context& context) const | |||||
| { return create_exec(context); } | |||||
| std::string table_t::update_intern(const update_context& context) const | |||||
| { return update_exec(context); } | |||||
| @@ -0,0 +1,439 @@ | |||||
| #include <cpphibernate/driver/mariadb.h> | |||||
| #include "test_helper.h" | |||||
| #include "test_schema.h" | |||||
| #include "mariadb_mock.h" | |||||
| using namespace ::testing; | |||||
| using namespace ::cpphibernate; | |||||
| TEST(CppHibernateTests, create_test1) | |||||
| { | |||||
| StrictMock<mariadb_mock> mock; | |||||
| expect_query(mock, "START TRANSACTION"); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "02689aa7-aa28-11e8-bf41-0242ac110002" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test1` " | |||||
| "SET " | |||||
| "`tbl_test1_id`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`str_data`='Xstr_data of class `test1` object `t1`X', " | |||||
| "`str64_data`='Xstr64_data of class `test1` object `t1`X', " | |||||
| "`u32_nullable`=null, " | |||||
| "`u32_ptr_u`='X456X', " | |||||
| "`u32_ptr_s`='X789X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "COMMIT"); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x1111), _, _, _)) | |||||
| .Times(AnyNumber()) | |||||
| .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_close( | |||||
| reinterpret_cast<MYSQL*>(0x1111))); | |||||
| test1 t1; | |||||
| t1.str_data = "str_data of class `test1` object `t1`"; | |||||
| t1.str64_data = "str64_data of class `test1` object `t1`"; | |||||
| t1.u32_ptr_u = std::make_unique<uint32_t>(456); | |||||
| t1.u32_ptr_s = std::make_shared<uint32_t>(789); | |||||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||||
| context.create(t1); | |||||
| } | |||||
| TEST(CppHibernateTests, create_test2) | |||||
| { | |||||
| StrictMock<mariadb_mock> mock; | |||||
| expect_query(mock, "START TRANSACTION"); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "02689aa7-aa28-11e8-bf41-0242ac110002" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test2` " | |||||
| "SET " | |||||
| "`tbl_test2_id`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`u8_data`='X1X', " | |||||
| "`i8_data`='X2X', " | |||||
| "`u16_data`='X3X', " | |||||
| "`i16_data`='X4X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "COMMIT"); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x1111), _, _, _)) | |||||
| .Times(AnyNumber()) | |||||
| .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_close( | |||||
| reinterpret_cast<MYSQL*>(0x1111))); | |||||
| test2 t2; | |||||
| t2.u8_data = 1; | |||||
| t2.i8_data = 2; | |||||
| t2.u16_data = 3; | |||||
| t2.i16_data = 4; | |||||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||||
| context.create(t2); | |||||
| } | |||||
| TEST(CppHibernateTests, create_test3) | |||||
| { | |||||
| StrictMock<mariadb_mock> mock; | |||||
| expect_query(mock, "START TRANSACTION"); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "02689aa7-aa28-11e8-bf41-0242ac110002" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test3` " | |||||
| "SET " | |||||
| "`tbl_test3_id`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`tbl_derived3_id_test3_list`=UuidToBin(null), " | |||||
| "`tbl_derived3_id_test3_vector`=UuidToBin(null), " | |||||
| "`u32_data`='X5X', " | |||||
| "`i32_data`='X6X', " | |||||
| "`u64_data`='X7X', " | |||||
| "`i64_data`='X8X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "COMMIT"); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x1111), _, _, _)) | |||||
| .Times(AnyNumber()) | |||||
| .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_close( | |||||
| reinterpret_cast<MYSQL*>(0x1111))); | |||||
| test3 t3; | |||||
| t3.u32_data = 5; | |||||
| t3.i32_data = 6; | |||||
| t3.u64_data = 7; | |||||
| t3.i64_data = 8; | |||||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||||
| context.create(t3); | |||||
| } | |||||
| TEST(CppHibernateTests, create_derived1) | |||||
| { | |||||
| StrictMock<mariadb_mock> mock; | |||||
| expect_query(mock, "START TRANSACTION"); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "02689aa7-aa28-11e8-bf41-0242ac110002" } | |||||
| })); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "a572edde-aadb-11e8-98d0-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_base` " | |||||
| "SET " | |||||
| "`tbl_base_id`=UuidToBin('Xa572edde-aadb-11e8-98d0-529269fb1459X'), " | |||||
| "`name`='Xderived1X', " | |||||
| "`__type`='X11X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "b80ffb20-aae6-11e8-98d0-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test1` " | |||||
| "SET " | |||||
| "`tbl_test1_id`=UuidToBin('Xb80ffb20-aae6-11e8-98d0-529269fb1459X'), " | |||||
| "`str_data`='Xstr_data of class `test1` object `d1.test1_data`X', " | |||||
| "`str64_data`='Xstr64_data of class `test1` object `d1.test1_data`X', " | |||||
| "`u32_nullable`='X32X', " | |||||
| "`u32_ptr_u`=null, " | |||||
| "`u32_ptr_s`='X789X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_derived1` " | |||||
| "SET " | |||||
| "`tbl_derived1_id`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`tbl_base_id`=UuidToBin('Xa572edde-aadb-11e8-98d0-529269fb1459X'), " | |||||
| "`tbl_test1_id_test1_data`=UuidToBin('Xb80ffb20-aae6-11e8-98d0-529269fb1459X'), " | |||||
| "`enum_data`='Xtest2X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "COMMIT"); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x1111), _, _, _)) | |||||
| .Times(AnyNumber()) | |||||
| .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_close( | |||||
| reinterpret_cast<MYSQL*>(0x1111))); | |||||
| derived1 d1; | |||||
| d1.name = "derived1"; | |||||
| d1.enum_data = test_enum::test2; | |||||
| d1.test1_data.str_data = "str_data of class `test1` object `d1.test1_data`"; | |||||
| d1.test1_data.str64_data = "str64_data of class `test1` object `d1.test1_data`"; | |||||
| d1.test1_data.u32_nullable = 32; | |||||
| d1.test1_data.u32_ptr_s = std::make_shared<uint32_t>(789); | |||||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||||
| context.create(static_cast<base&>(d1)); | |||||
| } | |||||
| TEST(CppHibernateTests, create_derived2) | |||||
| { | |||||
| StrictMock<mariadb_mock> mock; | |||||
| expect_query(mock, "START TRANSACTION"); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "02689aa7-aa28-11e8-bf41-0242ac110002" } | |||||
| })); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "a572edde-aadb-11e8-98d0-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_base` " | |||||
| "SET " | |||||
| "`tbl_base_id`=UuidToBin('Xa572edde-aadb-11e8-98d0-529269fb1459X'), " | |||||
| "`name`='Xderived2X', " | |||||
| "`__type`='X12X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "b80ffb20-aae6-11e8-98d0-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test2` " | |||||
| "SET " | |||||
| "`tbl_test2_id`=UuidToBin('Xb80ffb20-aae6-11e8-98d0-529269fb1459X'), " | |||||
| "`u8_data`='X10X', " | |||||
| "`i8_data`='X11X', " | |||||
| "`u16_data`='X12X', " | |||||
| "`i16_data`='X13X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "9470a0be-aae8-11e8-a137-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test2` " | |||||
| "SET " | |||||
| "`tbl_test2_id`=UuidToBin('X9470a0be-aae8-11e8-a137-529269fb1459X'), " | |||||
| "`u8_data`='X20X', " | |||||
| "`i8_data`='X21X', " | |||||
| "`u16_data`='X22X', " | |||||
| "`i16_data`='X23X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_derived2` " | |||||
| "SET " | |||||
| "`tbl_derived2_id`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`tbl_base_id`=UuidToBin('Xa572edde-aadb-11e8-98d0-529269fb1459X'), " | |||||
| "`tbl_test2_id_test2_nullable`=UuidToBin('Xb80ffb20-aae6-11e8-98d0-529269fb1459X'), " | |||||
| "`tbl_test2_id_test2_ptr_u`=UuidToBin('X9470a0be-aae8-11e8-a137-529269fb1459X'), " | |||||
| "`tbl_test2_id_test2_ptr_s`=UuidToBin(null)", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "COMMIT"); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x1111), _, _, _)) | |||||
| .Times(AnyNumber()) | |||||
| .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_close( | |||||
| reinterpret_cast<MYSQL*>(0x1111))); | |||||
| derived2 d2; | |||||
| d2.name = "derived2"; | |||||
| d2.test2_nullable = test2 { }; | |||||
| d2.test2_nullable->u8_data = 10; | |||||
| d2.test2_nullable->i8_data = 11; | |||||
| d2.test2_nullable->u16_data = 12; | |||||
| d2.test2_nullable->i16_data = 13; | |||||
| d2.test2_ptr_u = std::make_unique<test2>(); | |||||
| d2.test2_ptr_u->u8_data = 20; | |||||
| d2.test2_ptr_u->i8_data = 21; | |||||
| d2.test2_ptr_u->u16_data = 22; | |||||
| d2.test2_ptr_u->i16_data = 23; | |||||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||||
| context.create(static_cast<base&>(d2)); | |||||
| } | |||||
| TEST(CppHibernateTests, create_derived3) | |||||
| { | |||||
| StrictMock<mariadb_mock> mock; | |||||
| expect_query(mock, "START TRANSACTION"); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "02689aa7-aa28-11e8-bf41-0242ac110002" } | |||||
| })); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "df032510-aae9-11e8-98d0-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "a572edde-aadb-11e8-98d0-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_base` " | |||||
| "SET " | |||||
| "`tbl_base_id`=UuidToBin('Xa572edde-aadb-11e8-98d0-529269fb1459X'), " | |||||
| "`name`='Xderived3X', " | |||||
| "`__type`='X13X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_derived2` " | |||||
| "SET " | |||||
| "`tbl_derived2_id`=UuidToBin('Xdf032510-aae9-11e8-98d0-529269fb1459X'), " | |||||
| "`tbl_base_id`=UuidToBin('Xa572edde-aadb-11e8-98d0-529269fb1459X'), " | |||||
| "`tbl_test2_id_test2_nullable`=UuidToBin(null), " | |||||
| "`tbl_test2_id_test2_ptr_u`=UuidToBin(null), " | |||||
| "`tbl_test2_id_test2_ptr_s`=UuidToBin(null)", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_derived3` " | |||||
| "SET " | |||||
| "`tbl_derived3_id`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`tbl_derived2_id`=UuidToBin('Xdf032510-aae9-11e8-98d0-529269fb1459X')", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "be0baad8-aaeb-11e8-a137-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test3` " | |||||
| "SET " | |||||
| "`tbl_test3_id`=UuidToBin('Xbe0baad8-aaeb-11e8-a137-529269fb1459X'), " | |||||
| "`tbl_derived3_id_test3_list`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`tbl_derived3_id_test3_vector`=UuidToBin(null), " | |||||
| "`u32_data`='X100X', " | |||||
| "`i32_data`='X101X', " | |||||
| "`u64_data`='X102X', " | |||||
| "`i64_data`='X103X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "be0bb3e8-aaeb-11e8-a137-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test3` " | |||||
| "SET " | |||||
| "`tbl_test3_id`=UuidToBin('Xbe0bb3e8-aaeb-11e8-a137-529269fb1459X'), " | |||||
| "`tbl_derived3_id_test3_list`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`tbl_derived3_id_test3_vector`=UuidToBin(null), " | |||||
| "`u32_data`='X110X', " | |||||
| "`i32_data`='X111X', " | |||||
| "`u64_data`='X112X', " | |||||
| "`i64_data`='X113X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "be0bb974-aaeb-11e8-a137-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test3` " | |||||
| "SET " | |||||
| "`tbl_test3_id`=UuidToBin('Xbe0bb974-aaeb-11e8-a137-529269fb1459X'), " | |||||
| "`tbl_derived3_id_test3_list`=UuidToBin(null), " | |||||
| "`tbl_derived3_id_test3_vector`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`u32_data`='X120X', " | |||||
| "`i32_data`='X121X', " | |||||
| "`u64_data`='X122X', " | |||||
| "`i64_data`='X123X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "be0bbbc2-aaeb-11e8-a137-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test3` " | |||||
| "SET " | |||||
| "`tbl_test3_id`=UuidToBin('Xbe0bbbc2-aaeb-11e8-a137-529269fb1459X'), " | |||||
| "`tbl_derived3_id_test3_list`=UuidToBin(null), " | |||||
| "`tbl_derived3_id_test3_vector`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`u32_data`='X130X', " | |||||
| "`i32_data`='X131X', " | |||||
| "`u64_data`='X132X', " | |||||
| "`i64_data`='X133X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||||
| { "78ee918a-aaec-11e8-98d0-529269fb1459" } | |||||
| })); | |||||
| expect_query(mock, "INSERT INTO " | |||||
| "`tbl_test3` " | |||||
| "SET " | |||||
| "`tbl_test3_id`=UuidToBin('X78ee918a-aaec-11e8-98d0-529269fb1459X'), " | |||||
| "`tbl_derived3_id_test3_list`=UuidToBin(null), " | |||||
| "`tbl_derived3_id_test3_vector`=UuidToBin('X02689aa7-aa28-11e8-bf41-0242ac110002X'), " | |||||
| "`u32_data`='X140X', " | |||||
| "`i32_data`='X141X', " | |||||
| "`u64_data`='X142X', " | |||||
| "`i64_data`='X143X'", | |||||
| result_affected_rows(1)); | |||||
| expect_query(mock, "COMMIT"); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x1111), _, _, _)) | |||||
| .Times(AnyNumber()) | |||||
| .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_close( | |||||
| reinterpret_cast<MYSQL*>(0x1111))); | |||||
| derived3 d3; | |||||
| d3.name = "derived3"; | |||||
| d3.test3_list.emplace_back(); | |||||
| d3.test3_list.back().u32_data = 100; | |||||
| d3.test3_list.back().i32_data = 101; | |||||
| d3.test3_list.back().u64_data = 102; | |||||
| d3.test3_list.back().i64_data = 103; | |||||
| d3.test3_list.emplace_back(); | |||||
| d3.test3_list.back().u32_data = 110; | |||||
| d3.test3_list.back().i32_data = 111; | |||||
| d3.test3_list.back().u64_data = 112; | |||||
| d3.test3_list.back().i64_data = 113; | |||||
| d3.test3_vector.emplace_back(); | |||||
| d3.test3_vector.back().u32_data = 120; | |||||
| d3.test3_vector.back().i32_data = 121; | |||||
| d3.test3_vector.back().u64_data = 122; | |||||
| d3.test3_vector.back().i64_data = 123; | |||||
| d3.test3_vector.emplace_back(); | |||||
| d3.test3_vector.back().u32_data = 130; | |||||
| d3.test3_vector.back().i32_data = 131; | |||||
| d3.test3_vector.back().u64_data = 132; | |||||
| d3.test3_vector.back().i64_data = 133; | |||||
| d3.test3_vector.emplace_back(); | |||||
| d3.test3_vector.back().u32_data = 140; | |||||
| d3.test3_vector.back().i32_data = 141; | |||||
| d3.test3_vector.back().u64_data = 142; | |||||
| d3.test3_vector.back().i64_data = 143; | |||||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||||
| context.create(static_cast<base&>(d3)); | |||||
| } | |||||
| @@ -1,39 +1,16 @@ | |||||
| #include <gtest/gtest.h> | |||||
| #include <cpphibernate/driver/mariadb.h> | #include <cpphibernate/driver/mariadb.h> | ||||
| #include "test_helper.h" | |||||
| #include "test_schema.h" | #include "test_schema.h" | ||||
| #include "mariadb_mock.h" | #include "mariadb_mock.h" | ||||
| using namespace ::testing; | using namespace ::testing; | ||||
| using namespace ::cpphibernate; | 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) | TEST(CppHibernateTests, init) | ||||
| { | { | ||||
| StrictMock<MariaDbMock> mock; | |||||
| StrictMock<mariadb_mock> mock; | |||||
| InSequence seq; | |||||
| expect_query(mock, "START TRANSACTION"); | expect_query(mock, "START TRANSACTION"); | ||||
| expect_query(mock, "DROP DATABASE IF EXISTS `test`"); | expect_query(mock, "DROP DATABASE IF EXISTS `test`"); | ||||
| expect_query(mock, "CREATE SCHEMA IF NOT EXISTS `test` DEFAULT CHARACTER SET utf8"); | expect_query(mock, "CREATE SCHEMA IF NOT EXISTS `test` DEFAULT CHARACTER SET utf8"); | ||||
| @@ -200,13 +177,13 @@ TEST(CppHibernateTests, init) | |||||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_derived3`\n" | expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_derived3`\n" | ||||
| "(\n" | "(\n" | ||||
| " `tbl_derived3_id` BINARY(16) NOT NULL,\n" | " `tbl_derived3_id` BINARY(16) NOT NULL,\n" | ||||
| " `tbl_derived1_id` BINARY(16) NOT NULL,\n" | |||||
| " `tbl_derived2_id` BINARY(16) NOT NULL,\n" | |||||
| " PRIMARY KEY ( `tbl_derived3_id` ),\n" | " PRIMARY KEY ( `tbl_derived3_id` ),\n" | ||||
| " UNIQUE INDEX `index_tbl_derived3_id` ( `tbl_derived3_id` ASC ),\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" | |||||
| " UNIQUE INDEX `index_tbl_derived2_id` ( `tbl_derived2_id` ASC ),\n" | |||||
| " CONSTRAINT `fk_tbl_derived3_to_tbl_derived2_id`\n" | |||||
| " FOREIGN KEY (`tbl_derived2_id`)\n" | |||||
| " REFERENCES `test`.`tbl_derived2` (`tbl_derived2_id`)\n" | |||||
| " ON DELETE CASCADE\n" | " ON DELETE CASCADE\n" | ||||
| " ON UPDATE NO ACTION\n" | " ON UPDATE NO ACTION\n" | ||||
| ")\n" | ")\n" | ||||
| @@ -1,13 +1,13 @@ | |||||
| #include "mariadb_mock.h" | #include "mariadb_mock.h" | ||||
| MariaDbMock* mariadb_mock_instance; | |||||
| mariadb_mock* mariadb_mock_instance; | |||||
| void MariaDbMock::setInstance(MariaDbMock* value) | |||||
| void mariadb_mock::setInstance(mariadb_mock* value) | |||||
| { | { | ||||
| mariadb_mock_instance = value; | mariadb_mock_instance = value; | ||||
| } | } | ||||
| void MariaDbMock::clearInstance(MariaDbMock* value) | |||||
| void mariadb_mock::clearInstance(mariadb_mock* value) | |||||
| { | { | ||||
| if (mariadb_mock_instance == value) | if (mariadb_mock_instance == value) | ||||
| mariadb_mock_instance = nullptr; | mariadb_mock_instance = nullptr; | ||||
| @@ -1,5 +1,6 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <memory> | |||||
| #include <gmock/gmock.h> | #include <gmock/gmock.h> | ||||
| #include <mariadb/errmsg.h> | #include <mariadb/errmsg.h> | ||||
| #include <mariadb/mysqld_error.h> | #include <mariadb/mysqld_error.h> | ||||
| @@ -60,11 +61,18 @@ | |||||
| #define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) | #define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) | ||||
| #define CLIENT_REMEMBER_OPTIONS (1UL << 31) | #define CLIENT_REMEMBER_OPTIONS (1UL << 31) | ||||
| struct MariaDbMock | |||||
| struct mariadb_mock_item | |||||
| { | |||||
| virtual ~mariadb_mock_item() = default; | |||||
| }; | |||||
| struct mariadb_mock | |||||
| { | { | ||||
| private: | private: | ||||
| static void setInstance(MariaDbMock* value); | |||||
| static void clearInstance(MariaDbMock* value); | |||||
| static void setInstance(mariadb_mock* value); | |||||
| static void clearInstance(mariadb_mock* value); | |||||
| std::vector<std::unique_ptr<mariadb_mock_item>> _items; | |||||
| public: | public: | ||||
| MOCK_METHOD1(mysql_num_rows, my_ulonglong (MYSQL_RES *res)); | MOCK_METHOD1(mysql_num_rows, my_ulonglong (MYSQL_RES *res)); | ||||
| @@ -90,10 +98,19 @@ public: | |||||
| MOCK_METHOD8(mysql_real_connect, MYSQL* (MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag)); | MOCK_METHOD8(mysql_real_connect, MYSQL* (MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag)); | ||||
| MOCK_METHOD1(mysql_init, MYSQL* (MYSQL *mysql)); | MOCK_METHOD1(mysql_init, MYSQL* (MYSQL *mysql)); | ||||
| MariaDbMock() | |||||
| ::testing::Sequence sequence; | |||||
| template<typename T_item> | |||||
| T_item& store(T_item&& item) | |||||
| { | |||||
| _items.emplace_back(new T_item(std::forward<T_item>(item))); | |||||
| return *static_cast<T_item*>(_items.back().get()); | |||||
| } | |||||
| mariadb_mock() | |||||
| { setInstance(this); } | { setInstance(this); } | ||||
| ~MariaDbMock() | |||||
| ~mariadb_mock() | |||||
| { clearInstance(this); } | { clearInstance(this); } | ||||
| }; | }; | ||||
| @@ -0,0 +1,168 @@ | |||||
| #include <string> | |||||
| #include <gtest/gtest.h> | |||||
| #include <gmock/gmock.h> | |||||
| #include "mariadb_mock.h" | |||||
| ACTION(EscapeString) | |||||
| { | |||||
| char* dst = arg0; | |||||
| const char* src = arg1; | |||||
| unsigned long len = arg2; | |||||
| if (len <= 0) | |||||
| return 0; | |||||
| *(dst++) = 'X'; | |||||
| for (unsigned long i = 0; i < len; ++i) | |||||
| *(dst++) = *(src++); | |||||
| *(dst++) = 'X'; | |||||
| return len + 2; | |||||
| } | |||||
| struct result_data | |||||
| : public mariadb_mock_item | |||||
| { | |||||
| using row_type = std::vector<std::string>; | |||||
| using data_type = std::vector<row_type>; | |||||
| struct internal_data_t | |||||
| { | |||||
| std::vector<char*> data; | |||||
| std::vector<unsigned long> length; | |||||
| }; | |||||
| using interal_data_vector = std::vector<internal_data_t>; | |||||
| bool is_stored; | |||||
| ssize_t affected_rows; | |||||
| data_type data; | |||||
| interal_data_vector internal_data; | |||||
| template<typename T_data> | |||||
| result_data(T_data&& p_data, bool p_is_stored, ssize_t p_affected_rows) | |||||
| : data (std::forward<T_data>(p_data)) | |||||
| , is_stored (p_is_stored) | |||||
| , affected_rows (p_affected_rows) | |||||
| { | |||||
| internal_data.resize(data.size()); | |||||
| for (size_t i = 0; i < data.size(); ++i) | |||||
| { | |||||
| auto& intern = internal_data.at(i); | |||||
| auto& d = data.at(i); | |||||
| intern.data.resize(d.size()); | |||||
| intern.length.resize(d.size()); | |||||
| for (size_t j = 0; j < d.size(); ++j) | |||||
| { | |||||
| auto& str = d.at(j); | |||||
| intern.data[j] = const_cast<char*>(str.c_str()); | |||||
| intern.length[j] = static_cast<unsigned long>(str.size()); | |||||
| } | |||||
| } | |||||
| } | |||||
| }; | |||||
| inline MYSQL_RES* next_result() | |||||
| { | |||||
| static MYSQL_RES* value = reinterpret_cast<MYSQL_RES*>(0x1000); | |||||
| return ++value; | |||||
| } | |||||
| inline const result_data::data_type& empty_result_data() | |||||
| { | |||||
| static const result_data::data_type value; | |||||
| return value; | |||||
| } | |||||
| template<typename T_data = decltype(empty_result_data())> | |||||
| inline decltype(auto) result_stored(T_data&& data = empty_result_data(), ssize_t affected_rows = -1) | |||||
| { return result_data(std::forward<T_data>(data), true, affected_rows); } | |||||
| template<typename T_data = decltype(empty_result_data())> | |||||
| inline decltype(auto) result_used(T_data&& data = empty_result_data(), ssize_t affected_rows = -1) | |||||
| { return result_data(std::forward<T_data>(data), false, affected_rows); } | |||||
| inline decltype(auto) result_affected_rows(ssize_t affected_rows) | |||||
| { return result_data(empty_result_data(), true, affected_rows); } | |||||
| template<typename T_mock, typename T_result> | |||||
| inline void expect_query(T_mock& mock, const std::string& query, T_result&& result) | |||||
| { | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_real_query( | |||||
| reinterpret_cast<MYSQL*>(0x1111), | |||||
| ::testing::StrEq(query), | |||||
| query.size())) | |||||
| .InSequence(mock.sequence); | |||||
| auto& res = mock.store(std::forward<T_result>(result)); | |||||
| auto ptr = next_result(); | |||||
| if (res.is_stored) | |||||
| { | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_store_result(reinterpret_cast<MYSQL*>(0x1111))) | |||||
| .InSequence(mock.sequence) | |||||
| .WillOnce(::testing::Return(ptr)); | |||||
| } | |||||
| else | |||||
| { | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_use_result(reinterpret_cast<MYSQL*>(0x1111))) | |||||
| .InSequence(mock.sequence) | |||||
| .WillOnce(::testing::Return(ptr)); | |||||
| } | |||||
| if (res.affected_rows >= 0) | |||||
| { | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_affected_rows(reinterpret_cast<MYSQL*>(0x1111))) | |||||
| .InSequence(mock.sequence) | |||||
| .WillOnce(::testing::Return(static_cast<unsigned long long>(res.affected_rows))); | |||||
| } | |||||
| if (!res.data.empty()) | |||||
| { | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_num_fields(ptr)) | |||||
| .Times(::testing::AnyNumber()) | |||||
| .WillRepeatedly(::testing::Return(res.data.at(0).size())); | |||||
| } | |||||
| for (auto& x : res.internal_data) | |||||
| { | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_fetch_row(ptr)) | |||||
| .InSequence(mock.sequence) | |||||
| .WillOnce(::testing::Return(x.data.data())); | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_fetch_lengths(ptr)) | |||||
| .InSequence(mock.sequence) | |||||
| .WillOnce(::testing::Return(x.length.data())); | |||||
| } | |||||
| if (!res.is_stored) | |||||
| { | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_fetch_row(ptr)) | |||||
| .InSequence(mock.sequence) | |||||
| .WillOnce(::testing::Return(nullptr)); | |||||
| } | |||||
| EXPECT_CALL( | |||||
| mock, | |||||
| mysql_free_result(ptr)) | |||||
| .InSequence(mock.sequence); | |||||
| } | |||||
| template<typename T_mock> | |||||
| inline void expect_query(T_mock& mock, const std::string& query) | |||||
| { expect_query(mock, query, result_stored()); } | |||||
| @@ -61,6 +61,8 @@ struct base | |||||
| { | { | ||||
| ::cpphibernate::uuid id; | ::cpphibernate::uuid id; | ||||
| std::string name; | std::string name; | ||||
| virtual ~base() = default; | |||||
| }; | }; | ||||
| struct derived1 | struct derived1 | ||||
| @@ -81,7 +83,7 @@ struct derived2 | |||||
| }; | }; | ||||
| struct derived3 | struct derived3 | ||||
| : public derived1 | |||||
| : public derived2 | |||||
| { | { | ||||
| ::cpphibernate::uuid derived3_id; | ::cpphibernate::uuid derived3_id; | ||||
| std::list<test3> test3_list; | std::list<test3> test3_list; | ||||