diff --git a/include/cpphibernate/driver/mariadb/helper.h b/include/cpphibernate/driver/mariadb/helper.h index 322dbe8..8ed0b9e 100644 --- a/include/cpphibernate/driver/mariadb/helper.h +++ b/include/cpphibernate/driver/mariadb/helper.h @@ -2,5 +2,6 @@ #include #include +#include #include #include \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/helper/context.h b/include/cpphibernate/driver/mariadb/helper/context.h index 9308e5f..7e4caa1 100644 --- a/include/cpphibernate/driver/mariadb/helper/context.h +++ b/include/cpphibernate/driver/mariadb/helper/context.h @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include beg_namespace_cpphibernate_driver_mariadb @@ -16,5 +19,74 @@ beg_namespace_cpphibernate_driver_mariadb 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 + struct generic_create_context + : public create_context + { + using dataset_type = mp::decay_t; + + dataset_type& dataset; + + template + constexpr decltype(auto) change(T_new_dataset& new_dataset, const field_t* owner = nullptr) const + { + return generic_create_context + { + { + schema, + nullptr, + owner, + connection + }, + new_dataset + }; + } + }; + + /* update context */ + + struct update_context + : public create_context + { + const filter_t& filter; + }; + + template + struct generic_update_context + : public update_context + { + using dataset_type = mp::decay_t; + + dataset_type& dataset; + + template + constexpr decltype(auto) change(T_new_dataset& new_dataset, const field_t* owner = nullptr) const + { + return generic_update_context + { + { + { + schema, + nullptr, + owner, + connection + }, + filter, + }, + new_dataset + }; + } + }; + } end_namespace_cpphibernate_driver_mariadb \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/helper/reference_stack.h b/include/cpphibernate/driver/mariadb/helper/reference_stack.h new file mode 100644 index 0000000..640008a --- /dev/null +++ b/include/cpphibernate/driver/mariadb/helper/reference_stack.h @@ -0,0 +1,74 @@ +#pragma once + +#include + +#include +#include + +beg_namespace_cpphibernate_driver_mariadb +{ + + /* reference_lock */ + + struct reference_lock + { + virtual ~reference_lock() = default; + }; + + /* reference_stack */ + + template + 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; + + 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::name() + ">: poped element is not the top element!"); + stack().pop(); + } + + public: + static inline decltype(auto) push(T_dataset& dataset) + { return std::make_unique(dataset); } + + static inline T_dataset& top() + { + if (stack().empty()) + throw misc::hibernate_exception(std::string("reference_stack<") + utl::type_helper::name() + ">: does not have stored a dataset!"); + return *stack().top(); + } + + static inline size_t size() + { return stack().size(); } + }; + +} +end_namespace_cpphibernate_driver_mariadb \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/helper/type_properties.h b/include/cpphibernate/driver/mariadb/helper/type_properties.h index 33d1221..c7df09d 100644 --- a/include/cpphibernate/driver/mariadb/helper/type_properties.h +++ b/include/cpphibernate/driver/mariadb/helper/type_properties.h @@ -223,9 +223,9 @@ beg_namespace_cpphibernate_driver_mariadb static inline nullable_type convert_to(const value_t& value) { - auto ret = nullable_helper_type::make(); + nullable_type ret; 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; } diff --git a/include/cpphibernate/driver/mariadb/impl.h b/include/cpphibernate/driver/mariadb/impl.h index a1d6c8a..0a1f3ba 100644 --- a/include/cpphibernate/driver/mariadb/impl.h +++ b/include/cpphibernate/driver/mariadb/impl.h @@ -1,3 +1,4 @@ #pragma once -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/impl/create.h b/include/cpphibernate/driver/mariadb/impl/create.h new file mode 100644 index 0000000..17e8dd8 --- /dev/null +++ b/include/cpphibernate/driver/mariadb/impl/create.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include + +beg_namespace_cpphibernate_driver_mariadb +{ + + /* create_impl_t */ + + template + struct create_impl_t + { + using context_type = T_context; + using dataset_type = typename context_type::dataset_type; + using reference_stack_type = reference_stack; + + 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); + 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 + struct create_impl_t< + T_context, + mp::enable_if>> + { + using context_type = T_context; + using dataset_type = typename context_type::dataset_type; + using nullable_helper_type = misc::nullable_helper; + + 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; + using new_create_impl_type = create_impl_t; + 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 + struct create_impl_t< + T_context, + mp::enable_if>> + { + 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; + using new_create_impl_type = create_impl_t; + new_create_impl_type::apply(context.change(x, context.owner_field)); + } + trans.commit(); + return ret; + } + }; + +} +end_namespace_cpphibernate_driver_mariadb \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/impl/init.h b/include/cpphibernate/driver/mariadb/impl/init.h index af23e78..b3d2743 100644 --- a/include/cpphibernate/driver/mariadb/impl/init.h +++ b/include/cpphibernate/driver/mariadb/impl/init.h @@ -8,22 +8,14 @@ beg_namespace_cpphibernate_driver_mariadb { - /* init_impl */ + /* init_impl_t */ template - struct init_impl + struct init_impl_t { using context_type = T_context; - context_type context; - - constexpr init_impl(T_context&& p_context) - : context(std::forward(p_context)) - { } - - /* operator() */ - - inline void operator()() const + static inline void apply(const context_type& context) { auto& schema = context.schema; auto& connection = context.connection; @@ -34,11 +26,5 @@ beg_namespace_cpphibernate_driver_mariadb } }; - /* make_init_impl */ - - template - constexpr decltype(auto) make_init_impl(T_context&& context) - { return init_impl(std::forward(context)); } - } end_namespace_cpphibernate_driver_mariadb \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/mariadb.h b/include/cpphibernate/driver/mariadb/mariadb.h index ea503e3..d2d379a 100644 --- a/include/cpphibernate/driver/mariadb/mariadb.h +++ b/include/cpphibernate/driver/mariadb/mariadb.h @@ -27,12 +27,28 @@ beg_namespace_cpphibernate_driver_mariadb protected: inline void init_impl(bool recreate) const { - make_init_impl(init_context + init_impl_t::apply(init_context { _schema, _connection, recreate - })(); + }); + } + + template + inline void create_impl(T_dataset& dataset) const + { + using create_context_type = generic_create_context; + create_impl_t::apply(create_context_type + { + { + _schema, + nullptr, + nullptr, + _connection + }, + dataset + }); } }; diff --git a/include/cpphibernate/driver/mariadb/schema.h b/include/cpphibernate/driver/mariadb/schema.h index 6eee3d8..e1feffb 100644 --- a/include/cpphibernate/driver/mariadb/schema.h +++ b/include/cpphibernate/driver/mariadb/schema.h @@ -14,4 +14,5 @@ #include #include -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/schema/field.h b/include/cpphibernate/driver/mariadb/schema/field.h index a6e9775..dc7751a 100644 --- a/include/cpphibernate/driver/mariadb/schema/field.h +++ b/include/cpphibernate/driver/mariadb/schema/field.h @@ -47,9 +47,21 @@ beg_namespace_cpphibernate_driver_mariadb 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 */ virtual std::string type () 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 */ @@ -58,8 +70,12 @@ beg_namespace_cpphibernate_driver_mariadb struct simple_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::getter_type; + using dataset_type = typename getter_type::dataset_type; + using value_type = typename getter_type::value_type; + using ref_stack = reference_stack; const schema_type& schema; const field_type& field; @@ -77,17 +93,21 @@ beg_namespace_cpphibernate_driver_mariadb struct value_field_t : public simple_field_t { - using base_type = simple_field_t; - using schema_type = T_schema; - using field_type = T_field; - using getter_type = typename mp::decay_t::getter_type; - using dataset_type = typename getter_type::dataset_type; - using value_type = typename getter_type::value_type; - using type_props = type_properties; + using base_type = simple_field_t; + 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; + using base_type::base_type; virtual std::string type() const override; + virtual value_t get () const override; + virtual void set (const value_t&) const override; }; /* primary_key_field_t */ @@ -104,7 +124,13 @@ beg_namespace_cpphibernate_driver_mariadb 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 */ @@ -124,9 +150,16 @@ beg_namespace_cpphibernate_driver_mariadb struct foreign_table_field_t : public simple_field_t { - using base_type = simple_field_t; + public: + using base_type = simple_field_t; + using dataset_type = typename base_type::dataset_type; + using ref_stack = typename base_type::ref_stack; 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 diff --git a/include/cpphibernate/driver/mariadb/schema/field.inl b/include/cpphibernate/driver/mariadb/schema/field.inl index 999703c..3708251 100644 --- a/include/cpphibernate/driver/mariadb/schema/field.inl +++ b/include/cpphibernate/driver/mariadb/schema/field.inl @@ -1,5 +1,6 @@ #pragma once +#include #include beg_namespace_cpphibernate_driver_mariadb @@ -11,11 +12,83 @@ beg_namespace_cpphibernate_driver_mariadb std::string value_field_t::type() const { return type_props::type(); } + template + value_t value_field_t::get() const + { return type_props::convert_from(this->field.getter(ref_stack::top())); } + + template + void value_field_t::set(const value_t& value) const + { this->field.setter(ref_stack::top(), type_props::convert_to(value)); } + /* primary_key_field_t */ template std::string primary_key_field_t::create_table_arguments() const - { return key_props::create_table_argument; } + { return key_props::create_table_argument; } + + template + std::string primary_key_field_t + ::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(); + } + + template + bool primary_key_field_t::is_auto_generated() const + { return key_props::auto_generated::value; } + + template + std::string primary_key_field_t::convert_to_open() const + { return key_props::convert_to_open; } + + template + std::string primary_key_field_t::convert_to_close() const + { return key_props::convert_to_close; } + + template + std::string primary_key_field_t::convert_from_open() const + { return key_props::convert_from_open; } + + template + std::string primary_key_field_t::convert_from_close() const + { return key_props::convert_from_close; } + + /* foreign_table_field_t */ + + template + value_t foreign_table_field_t + ::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; + using create_context_type = generic_create_context; + + return create_impl_t::apply( + create_context_type + { + context, + foreign, + }, + false); + } + + template + value_t foreign_table_field_t + ::foreign_update(const update_context& ctx) const + { + /* + auto& context = static_cast&>(ctx); + auto& ref = ref_stack::top(); + auto& foreign = this->field.getter(ref); + return foreign_create_update_helper(context.change(foreign)); + */ + return value_t { }; + } } end_namespace_cpphibernate_driver_mariadb \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/schema/filter.fwd.h b/include/cpphibernate/driver/mariadb/schema/filter.fwd.h new file mode 100644 index 0000000..24e3510 --- /dev/null +++ b/include/cpphibernate/driver/mariadb/schema/filter.fwd.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +beg_namespace_cpphibernate_driver_mariadb +{ + + /* filter_t */ + + struct filter_t; + +} +end_namespace_cpphibernate_driver_mariadb \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/schema/filter.h b/include/cpphibernate/driver/mariadb/schema/filter.h new file mode 100644 index 0000000..33d5fca --- /dev/null +++ b/include/cpphibernate/driver/mariadb/schema/filter.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include + +beg_namespace_cpphibernate_driver_mariadb +{ + + /* filter_t */ + + struct filter_t + { + using field_set_type = std::set; + using table_set_type = std::set; + + 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 \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/schema/schema.h b/include/cpphibernate/driver/mariadb/schema/schema.h index 317ab64..8c48556 100644 --- a/include/cpphibernate/driver/mariadb/schema/schema.h +++ b/include/cpphibernate/driver/mariadb/schema/schema.h @@ -26,6 +26,8 @@ beg_namespace_cpphibernate_driver_mariadb void update (); void print (std::ostream& os) const; + const table_t& table(size_t dataset_id) const; + /* CRUD */ void init(const init_context& context) const; }; diff --git a/include/cpphibernate/driver/mariadb/schema/table.h b/include/cpphibernate/driver/mariadb/schema/table.h index e675b24..2eb7f16 100644 --- a/include/cpphibernate/driver/mariadb/schema/table.h +++ b/include/cpphibernate/driver/mariadb/schema/table.h @@ -10,6 +10,7 @@ #include #include #include +#include #include beg_namespace_cpphibernate_driver_mariadb @@ -19,6 +20,7 @@ beg_namespace_cpphibernate_driver_mariadb struct table_t { + public: size_t dataset_id { 0 }; size_t base_dataset_id { 0 }; size_t table_id { 0 }; @@ -62,19 +64,43 @@ beg_namespace_cpphibernate_driver_mariadb void print(std::ostream& os) const; + const table_t* get_derived(size_t id) const; + /* CRUD */ 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: using statement_ptr = std::unique_ptr<::cppmariadb::statement>; mutable statement_ptr _statement_create_table; + mutable statement_ptr _statement_insert_into; ::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: - 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 */ @@ -83,9 +109,11 @@ beg_namespace_cpphibernate_driver_mariadb struct table_simple_t : public table_t { + public: using schema_type = T_schema; using table_type = T_table; using base_dataset_type = T_base_dataset; + using dataset_type = typename table_type::dataset_type; const schema_type& schema; const table_type& table; @@ -110,15 +138,21 @@ beg_namespace_cpphibernate_driver_mariadb struct table_polymorphic_t : public table_simple_t { + public: using base_type = table_simple_t; - 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; - const schema_type& schema; - const table_type& table; + private: + template + 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 @@ -194,6 +228,7 @@ beg_namespace_cpphibernate_driver_mariadb using wrapped_dataset_type = typename mp::decay_t::wrapped_dataset_type; using dataset_type = misc::unwrap_t; using table_type = table_type_t; + table_type ret(schema, table); ret.dataset_id = misc::get_type_id(table.wrapped_dataset); ret.base_dataset_id = misc::get_type_id(wrapped_base_type { }); diff --git a/include/cpphibernate/driver/mariadb/schema/table.inl b/include/cpphibernate/driver/mariadb/schema/table.inl new file mode 100644 index 0000000..5b0e4d2 --- /dev/null +++ b/include/cpphibernate/driver/mariadb/schema/table.inl @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +beg_namespace_cpphibernate_driver_mariadb +{ + + /* table_polymorphic_t */ + + template + template + constexpr void table_polymorphic_t + ::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), + [&](auto type){ + return hana::and_( + hana::not_(hana::trait(type)), + hana::or_( + type != hana::type_c, + include_self)); + }); + + hana::for_each(derived_types, [&](auto& type){ + using derived_type = misc::decay_unwrap_t; + auto* derived = dynamic_cast(&dataset); + if (derived) + pred(*derived); + }); + } + + template + std::string table_polymorphic_t + ::create_intern(const create_context& ctx) const + { + bool done = false; + auto& context = static_cast&>(ctx); + auto& dataset = context.dataset; + for_each_derived(dataset, hana::false_c, [&](auto& derived_dataset){ + if (!done) + { + using derived_dataset_type = mp::decay_t; + using reference_stack_type = reference_stack; + auto derived_dataset_id = misc::get_type_id(hana::type_c); + 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::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 \ No newline at end of file diff --git a/include/cpphibernate/misc/nullable_helper.h b/include/cpphibernate/misc/nullable_helper.h index 8b4a73c..d358b31 100644 --- a/include/cpphibernate/misc/nullable_helper.h +++ b/include/cpphibernate/misc/nullable_helper.h @@ -18,8 +18,9 @@ beg_namespace_cpphibernate_misc using nullable_type = T_nullable; using value_type = real_dataset_t; - 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 */ @@ -31,15 +32,19 @@ beg_namespace_cpphibernate_misc using value_type = T_value; 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 */ @@ -50,11 +55,23 @@ beg_namespace_cpphibernate_misc using nullable_type = std::unique_ptr; 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(); } - 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 */ @@ -65,11 +82,23 @@ beg_namespace_cpphibernate_misc using nullable_type = std::shared_ptr; 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(); } - 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(); } }; } diff --git a/include/cpphibernate/schema/schema.h b/include/cpphibernate/schema/schema.h index fe18b57..158f94a 100644 --- a/include/cpphibernate/schema/schema.h +++ b/include/cpphibernate/schema/schema.h @@ -180,7 +180,7 @@ beg_namespace_cpphibernate_schema /* schema::get_all_derived_types */ namespace __impl - { + { struct schema_get_all_derived_types_impl { template @@ -192,7 +192,7 @@ beg_namespace_cpphibernate_schema constexpr decltype(auto) operator()(T_type&&) const { return hana::bool_c< - std::is_base_of>::value>; + std::is_base_of>::value>; } }; @@ -245,7 +245,7 @@ beg_namespace_cpphibernate_schema } }; } - + constexpr decltype(auto) get_derived_types = __impl::schema_get_derived_types_impl { }; } diff --git a/include/cpphibernate/schema/table.h b/include/cpphibernate/schema/table.h index 4ca1050..589993c 100644 --- a/include/cpphibernate/schema/table.h +++ b/include/cpphibernate/schema/table.h @@ -19,6 +19,7 @@ beg_namespace_cpphibernate_schema { using name_type = T_name; using wrapped_dataset_type = mp::decay_t; + using dataset_type = misc::decay_unwrap_t; using table_id_type = T_table_id; using fields_type = T_fields; using this_type = table_t; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 191bcaf..d85db15 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,6 @@ Target_Link_Libraries ( If ( __COTIRE_INCLUDED ) Cotire ( cpphibernate ) EndIf ( ) -If ( __STRIP_SYMBOLS_INCLUDED ) +If ( __STRIP_SYMBOLS_INCLUDED AND BUILD_SHARED_LIBS ) Strip_Symbols ( cpphibernate DBG_FILE ) EndIf () diff --git a/src/driver/mariadb/schema/field.cpp b/src/driver/mariadb/schema/field.cpp index f56ca81..912dfc1 100644 --- a/src/driver/mariadb/schema/field.cpp +++ b/src/driver/mariadb/schema/field.cpp @@ -31,13 +31,97 @@ void field_t::print(std::ostream& os) const << 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( \ std::string("'") + table_name + "." + field_name + \ "' does not implement the " #p_name "() method!"); \ } -throw_not_implemented(string, type) -throw_not_implemented(string, create_table_arguments) +/* 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(); +} \ No newline at end of file diff --git a/src/driver/mariadb/schema/filter.cpp b/src/driver/mariadb/schema/filter.cpp new file mode 100644 index 0000000..8fa2d5d --- /dev/null +++ b/src/driver/mariadb/schema/filter.cpp @@ -0,0 +1,19 @@ +#include +#include + +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); +} \ No newline at end of file diff --git a/src/driver/mariadb/schema/schema.cpp b/src/driver/mariadb/schema/schema.cpp index 5273a07..6e21334 100644 --- a/src/driver/mariadb/schema/schema.cpp +++ b/src/driver/mariadb/schema/schema.cpp @@ -111,6 +111,15 @@ void schema_t::print(std::ostream& os) const << 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(it->second)); + return *it->second; +} + #define exec_query() \ do { \ cpphibernate_debug_log("execute init query: " << ss.str()); \ diff --git a/src/driver/mariadb/schema/table.cpp b/src/driver/mariadb/schema/table.cpp index d50dc5e..3cda14a 100644 --- a/src/driver/mariadb/schema/table.cpp +++ b/src/driver/mariadb/schema/table.cpp @@ -7,57 +7,20 @@ #include #include +#include using namespace ::utl; 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 */ os << "CREATE TABLE IF NOT EXISTS `" - << table_name + << table.table_name << "`" << indent << "(" @@ -65,8 +28,8 @@ void table_t::print(std::ostream& os) const /* 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(); os << indent << "`" @@ -80,9 +43,9 @@ void table_t::print(std::ostream& os) const } /* base table key fields */ - if (static_cast(base_table)) + if (static_cast(table.base_table)) { - auto& base_table_info = *base_table; + auto& base_table_info = *table.base_table; assert(base_table_info.primary_key_field); auto& key_info = *base_table_info.primary_key_field; os << indent @@ -94,7 +57,7 @@ void table_t::print(std::ostream& os) const } /* foreign table one fields */ - for (auto& ptr : foreign_table_one_fields) + for (auto& ptr : table.foreign_table_one_fields) { assert(static_cast(ptr)); auto& field_info = *ptr; @@ -114,7 +77,7 @@ void table_t::print(std::ostream& os) const } /* foreign fields */ - for (auto& ptr : foreign_key_fields) + for (auto& ptr : table.foreign_key_fields) { assert(static_cast(ptr)); auto& field_info = *ptr; @@ -132,7 +95,7 @@ void table_t::print(std::ostream& os) const } /* data fields */ - for (auto& ptr : data_fields) + for (auto& ptr : table.data_fields) { assert(static_cast(ptr)); auto& field_info = *ptr; @@ -147,8 +110,8 @@ void table_t::print(std::ostream& os) const } /* type field for derived tables */ - if (!derived_tables.empty() && - !base_table) + if (!table.derived_tables.empty() && + !table.base_table) { os << indent << "`__type` INT UNSIGNED NOT NULL,"; @@ -156,25 +119,32 @@ void table_t::print(std::ostream& os) const /* PRIMARY KEY */ { + assert(table.primary_key_field); + auto& key_info = *table.primary_key_field; os << indent << "PRIMARY KEY ( `" - << primary_key_field->field_name + << key_info.field_name << "` )"; } /* 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 */ - 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; os << ',' << indent @@ -186,7 +156,7 @@ void table_t::print(std::ostream& os) const } /* INDEX foreign table one fields */ - for (auto& ptr : foreign_table_one_fields) + for (auto& ptr : table.foreign_table_one_fields) { assert(static_cast(ptr)); auto& field_info = *ptr; @@ -207,7 +177,7 @@ void table_t::print(std::ostream& os) const } /* INDEX foreign fields */ - for (auto& ptr : foreign_key_fields) + for (auto& ptr : table.foreign_key_fields) { assert(static_cast(ptr)); auto& field_info = *ptr; @@ -225,14 +195,14 @@ void table_t::print(std::ostream& os) const } /* 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 << "," << indent << "CONSTRAINT `fk_" - << table_name + << table.table_name << "_to_" << ref_key_info.field_name << "`" @@ -257,7 +227,7 @@ void table_t::print(std::ostream& os) const } /* CONSTRAINT foreign table one fields */ - for (auto& ptr : foreign_table_one_fields) + for (auto& ptr : table.foreign_table_one_fields) { assert(static_cast(ptr)); auto& field_info = *ptr; @@ -267,7 +237,7 @@ void table_t::print(std::ostream& os) const os << "," << indent << "CONSTRAINT `fk_" - << table_name + << table.table_name << "_to_" << ref_key_info.table_name << "_id_" @@ -296,7 +266,7 @@ void table_t::print(std::ostream& os) const } /* CONSTRAINT foreign fields */ - for (auto& ptr : foreign_key_fields) + for (auto& ptr : table.foreign_key_fields) { assert(static_cast(ptr)); auto& field_info = *ptr; @@ -306,7 +276,7 @@ void table_t::print(std::ostream& os) const os << "," << indent << "CONSTRAINT `fk_" - << table_name + << table.table_name << "_" << field_info.table_name << "_id_" @@ -343,14 +313,422 @@ void table_t::print(std::ostream& os) const << indent << "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(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(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(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(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(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(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(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(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(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; } -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& 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); -} \ No newline at end of file +} + +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); } \ No newline at end of file diff --git a/test/cpphibernate_create.cpp b/test/cpphibernate_create.cpp new file mode 100644 index 0000000..ade36c3 --- /dev/null +++ b/test/cpphibernate_create.cpp @@ -0,0 +1,439 @@ +#include + +#include "test_helper.h" +#include "test_schema.h" +#include "mariadb_mock.h" + +using namespace ::testing; +using namespace ::cpphibernate; + +TEST(CppHibernateTests, create_test1) +{ + StrictMock 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(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(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(456); + t1.u32_ptr_s = std::make_shared(789); + + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + context.create(t1); +} + +TEST(CppHibernateTests, create_test2) +{ + StrictMock 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(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(0x1111))); + + test2 t2; + t2.u8_data = 1; + t2.i8_data = 2; + t2.u16_data = 3; + t2.i16_data = 4; + + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + context.create(t2); +} + +TEST(CppHibernateTests, create_test3) +{ + StrictMock 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(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(0x1111))); + + test3 t3; + t3.u32_data = 5; + t3.i32_data = 6; + t3.u64_data = 7; + t3.i64_data = 8; + + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + context.create(t3); +} + +TEST(CppHibernateTests, create_derived1) +{ + StrictMock 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(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(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(789); + + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + context.create(static_cast(d1)); +} + +TEST(CppHibernateTests, create_derived2) +{ + StrictMock 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(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(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(); + 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(0x1111)); + auto context = make_context(test_schema, connection); + context.create(static_cast(d2)); +} + +TEST(CppHibernateTests, create_derived3) +{ + StrictMock 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(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(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(0x1111)); + auto context = make_context(test_schema, connection); + context.create(static_cast(d3)); +} \ No newline at end of file diff --git a/test/cpphibernate_init.cpp b/test/cpphibernate_init.cpp index 1b61078..f0ac1ee 100644 --- a/test/cpphibernate_init.cpp +++ b/test/cpphibernate_init.cpp @@ -1,39 +1,16 @@ -#include #include +#include "test_helper.h" #include "test_schema.h" #include "mariadb_mock.h" using namespace ::testing; using namespace ::cpphibernate; -template -inline void expect_query(T_mock& mock, const std::string& query) -{ - EXPECT_CALL( - mock, - mysql_real_query( - reinterpret_cast(0x1111), - StrEq(query), - query.size())); - - EXPECT_CALL( - mock, - mysql_store_result( - reinterpret_cast(0x1111))) - .WillOnce(Return(reinterpret_cast(0x2222))); - - EXPECT_CALL( - mock, - mysql_free_result( - reinterpret_cast(0x2222))); -} - TEST(CppHibernateTests, init) { - StrictMock mock; + StrictMock mock; - InSequence seq; expect_query(mock, "START TRANSACTION"); expect_query(mock, "DROP DATABASE IF EXISTS `test`"); expect_query(mock, "CREATE SCHEMA IF NOT EXISTS `test` DEFAULT CHARACTER SET utf8"); @@ -200,13 +177,13 @@ TEST(CppHibernateTests, init) expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_derived3`\n" "(\n" " `tbl_derived3_id` BINARY(16) NOT NULL,\n" - " `tbl_derived1_id` BINARY(16) NOT NULL,\n" + " `tbl_derived2_id` BINARY(16) NOT NULL,\n" " PRIMARY KEY ( `tbl_derived3_id` ),\n" " UNIQUE INDEX `index_tbl_derived3_id` ( `tbl_derived3_id` ASC ),\n" - " UNIQUE INDEX `index_tbl_derived1_id` ( `tbl_derived1_id` ASC ),\n" - " CONSTRAINT `fk_tbl_derived3_to_tbl_derived1_id`\n" - " FOREIGN KEY (`tbl_derived1_id`)\n" - " REFERENCES `test`.`tbl_derived1` (`tbl_derived1_id`)\n" + " 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 UPDATE NO ACTION\n" ")\n" diff --git a/test/mariadb_mock.cpp b/test/mariadb_mock.cpp index 1792b1e..82c5e39 100644 --- a/test/mariadb_mock.cpp +++ b/test/mariadb_mock.cpp @@ -1,13 +1,13 @@ #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; } -void MariaDbMock::clearInstance(MariaDbMock* value) +void mariadb_mock::clearInstance(mariadb_mock* value) { if (mariadb_mock_instance == value) mariadb_mock_instance = nullptr; diff --git a/test/mariadb_mock.h b/test/mariadb_mock.h index b0cdbe1..2c12f94 100644 --- a/test/mariadb_mock.h +++ b/test/mariadb_mock.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -60,11 +61,18 @@ #define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) #define CLIENT_REMEMBER_OPTIONS (1UL << 31) -struct MariaDbMock +struct mariadb_mock_item +{ + virtual ~mariadb_mock_item() = default; +}; + +struct mariadb_mock { 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> _items; public: 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_METHOD1(mysql_init, MYSQL* (MYSQL *mysql)); - MariaDbMock() + ::testing::Sequence sequence; + + template + T_item& store(T_item&& item) + { + _items.emplace_back(new T_item(std::forward(item))); + return *static_cast(_items.back().get()); + } + + mariadb_mock() { setInstance(this); } - ~MariaDbMock() + ~mariadb_mock() { clearInstance(this); } }; diff --git a/test/test_helper.h b/test/test_helper.h new file mode 100644 index 0000000..4ab3b54 --- /dev/null +++ b/test/test_helper.h @@ -0,0 +1,168 @@ +#include +#include +#include + +#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; + using data_type = std::vector; + + struct internal_data_t + { + std::vector data; + std::vector length; + }; + using interal_data_vector = std::vector; + + bool is_stored; + ssize_t affected_rows; + data_type data; + interal_data_vector internal_data; + + template + result_data(T_data&& p_data, bool p_is_stored, ssize_t p_affected_rows) + : data (std::forward(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(str.c_str()); + intern.length[j] = static_cast(str.size()); + } + } + } +}; + +inline MYSQL_RES* next_result() +{ + static MYSQL_RES* value = reinterpret_cast(0x1000); + return ++value; +} + +inline const result_data::data_type& empty_result_data() +{ + static const result_data::data_type value; + return value; +} + +template +inline decltype(auto) result_stored(T_data&& data = empty_result_data(), ssize_t affected_rows = -1) + { return result_data(std::forward(data), true, affected_rows); } + +template +inline decltype(auto) result_used(T_data&& data = empty_result_data(), ssize_t affected_rows = -1) + { return result_data(std::forward(data), false, affected_rows); } + +inline decltype(auto) result_affected_rows(ssize_t affected_rows) + { return result_data(empty_result_data(), true, affected_rows); } + +template +inline void expect_query(T_mock& mock, const std::string& query, T_result&& result) +{ + EXPECT_CALL( + mock, + mysql_real_query( + reinterpret_cast(0x1111), + ::testing::StrEq(query), + query.size())) + .InSequence(mock.sequence); + + auto& res = mock.store(std::forward(result)); + auto ptr = next_result(); + + if (res.is_stored) + { + EXPECT_CALL( + mock, + mysql_store_result(reinterpret_cast(0x1111))) + .InSequence(mock.sequence) + .WillOnce(::testing::Return(ptr)); + } + else + { + EXPECT_CALL( + mock, + mysql_use_result(reinterpret_cast(0x1111))) + .InSequence(mock.sequence) + .WillOnce(::testing::Return(ptr)); + } + + if (res.affected_rows >= 0) + { + EXPECT_CALL( + mock, + mysql_affected_rows(reinterpret_cast(0x1111))) + .InSequence(mock.sequence) + .WillOnce(::testing::Return(static_cast(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 +inline void expect_query(T_mock& mock, const std::string& query) + { expect_query(mock, query, result_stored()); } \ No newline at end of file diff --git a/test/test_schema.h b/test/test_schema.h index ef75bfe..eb60886 100644 --- a/test/test_schema.h +++ b/test/test_schema.h @@ -61,6 +61,8 @@ struct base { ::cpphibernate::uuid id; std::string name; + + virtual ~base() = default; }; struct derived1 @@ -81,7 +83,7 @@ struct derived2 }; struct derived3 - : public derived1 + : public derived2 { ::cpphibernate::uuid derived3_id; std::list test3_list;