@@ -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; | |||