| @@ -2,5 +2,6 @@ | |||
| #include <cpphibernate/driver/mariadb/helper/context.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/type_properties.h> | |||
| @@ -2,6 +2,9 @@ | |||
| #include <cppmariadb.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> | |||
| 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<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 | |||
| @@ -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) | |||
| { | |||
| 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; | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| #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 | |||
| { | |||
| /* init_impl */ | |||
| /* init_impl_t */ | |||
| template<typename T_context, typename = void> | |||
| 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<T_context>(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<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 | |||
| @@ -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<init_context>::apply(init_context | |||
| { | |||
| _schema, | |||
| _connection, | |||
| 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/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; | |||
| /* 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<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 field_type& field; | |||
| @@ -77,17 +93,21 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| struct value_field_t | |||
| : public simple_field_t<T_schema, T_field> | |||
| { | |||
| using base_type = simple_field_t<T_schema, T_field>; | |||
| using 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; | |||
| 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<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; | |||
| 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 | |||
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include <cpphibernate/driver/mariadb/impl/create.h> | |||
| #include <cpphibernate/driver/mariadb/schema/field.h> | |||
| 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 | |||
| { 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 */ | |||
| template<typename T_schema, typename T_field> | |||
| std::string primary_key_field_t<T_schema, T_field>::create_table_arguments() const | |||
| { return key_props::create_table_argument; } | |||
| { 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 | |||
| @@ -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 print (std::ostream& os) const; | |||
| const table_t& table(size_t dataset_id) const; | |||
| /* CRUD */ | |||
| void init(const init_context& context) const; | |||
| }; | |||
| @@ -10,6 +10,7 @@ | |||
| #include <cpphibernate/schema/schema.h> | |||
| #include <cpphibernate/driver/mariadb/schema/fields.h> | |||
| #include <cpphibernate/driver/mariadb/schema/table.fwd.h> | |||
| #include <cpphibernate/driver/mariadb/schema/filter.fwd.h> | |||
| #include <cpphibernate/driver/mariadb/helper/context.h> | |||
| 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<T_schema, T_table, T_base_dataset> | |||
| { | |||
| public: | |||
| 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; | |||
| 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 | |||
| @@ -194,6 +228,7 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| using wrapped_dataset_type = typename mp::decay_t<T_table>::wrapped_dataset_type; | |||
| using dataset_type = misc::unwrap_t<wrapped_dataset_type>; | |||
| using table_type = table_type_t<dataset_type, base_type>; | |||
| 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 { }); | |||
| @@ -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 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 */ | |||
| @@ -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<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(); } | |||
| 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<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(); } | |||
| 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 */ | |||
| namespace __impl | |||
| { | |||
| { | |||
| struct schema_get_all_derived_types_impl | |||
| { | |||
| template<typename T_wrapped_dataset> | |||
| @@ -192,7 +192,7 @@ beg_namespace_cpphibernate_schema | |||
| constexpr decltype(auto) operator()(T_type&&) const | |||
| { | |||
| 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 { }; | |||
| } | |||
| @@ -19,6 +19,7 @@ beg_namespace_cpphibernate_schema | |||
| { | |||
| using name_type = T_name; | |||
| 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 fields_type = T_fields; | |||
| 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 ) | |||
| Cotire ( cpphibernate ) | |||
| EndIf ( ) | |||
| If ( __STRIP_SYMBOLS_INCLUDED ) | |||
| If ( __STRIP_SYMBOLS_INCLUDED AND BUILD_SHARED_LIBS ) | |||
| Strip_Symbols ( cpphibernate DBG_FILE ) | |||
| EndIf () | |||
| @@ -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(); | |||
| } | |||
| @@ -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 << '}'; | |||
| } | |||
| 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() \ | |||
| do { \ | |||
| cpphibernate_debug_log("execute init query: " << ss.str()); \ | |||
| @@ -7,57 +7,20 @@ | |||
| #include <cpphibernate/misc.h> | |||
| #include <cpphibernate/driver/mariadb/schema/table.h> | |||
| #include <cpphibernate/driver/mariadb/schema/filter.h> | |||
| 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<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); | |||
| 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<bool>(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<bool>(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<bool>(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<bool>(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<bool>(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<bool>(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<bool>(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<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; | |||
| } | |||
| 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); | |||
| } | |||
| } | |||
| 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 "test_helper.h" | |||
| #include "test_schema.h" | |||
| #include "mariadb_mock.h" | |||
| using namespace ::testing; | |||
| using namespace ::cpphibernate; | |||
| template<typename T_mock> | |||
| inline void expect_query(T_mock& mock, const std::string& query) | |||
| { | |||
| EXPECT_CALL( | |||
| mock, | |||
| mysql_real_query( | |||
| reinterpret_cast<MYSQL*>(0x1111), | |||
| StrEq(query), | |||
| query.size())); | |||
| EXPECT_CALL( | |||
| mock, | |||
| mysql_store_result( | |||
| reinterpret_cast<MYSQL*>(0x1111))) | |||
| .WillOnce(Return(reinterpret_cast<MYSQL_RES*>(0x2222))); | |||
| EXPECT_CALL( | |||
| mock, | |||
| mysql_free_result( | |||
| reinterpret_cast<MYSQL_RES*>(0x2222))); | |||
| } | |||
| TEST(CppHibernateTests, init) | |||
| { | |||
| StrictMock<MariaDbMock> mock; | |||
| StrictMock<mariadb_mock> 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" | |||
| @@ -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; | |||
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include <memory> | |||
| #include <gmock/gmock.h> | |||
| #include <mariadb/errmsg.h> | |||
| #include <mariadb/mysqld_error.h> | |||
| @@ -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<std::unique_ptr<mariadb_mock_item>> _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<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); } | |||
| ~MariaDbMock() | |||
| ~mariadb_mock() | |||
| { 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; | |||
| 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> test3_list; | |||