Browse Source

* implemented create methods for mariadb driver

master
bergmann 5 years ago
parent
commit
f12074c188
30 changed files with 1795 additions and 180 deletions
  1. +1
    -0
      include/cpphibernate/driver/mariadb/helper.h
  2. +72
    -0
      include/cpphibernate/driver/mariadb/helper/context.h
  3. +74
    -0
      include/cpphibernate/driver/mariadb/helper/reference_stack.h
  4. +2
    -2
      include/cpphibernate/driver/mariadb/helper/type_properties.h
  5. +2
    -1
      include/cpphibernate/driver/mariadb/impl.h
  6. +95
    -0
      include/cpphibernate/driver/mariadb/impl/create.h
  7. +3
    -17
      include/cpphibernate/driver/mariadb/impl/init.h
  8. +18
    -2
      include/cpphibernate/driver/mariadb/mariadb.h
  9. +2
    -1
      include/cpphibernate/driver/mariadb/schema.h
  10. +44
    -11
      include/cpphibernate/driver/mariadb/schema/field.h
  11. +74
    -1
      include/cpphibernate/driver/mariadb/schema/field.inl
  12. +13
    -0
      include/cpphibernate/driver/mariadb/schema/filter.fwd.h
  13. +28
    -0
      include/cpphibernate/driver/mariadb/schema/filter.h
  14. +2
    -0
      include/cpphibernate/driver/mariadb/schema/schema.h
  15. +42
    -7
      include/cpphibernate/driver/mariadb/schema/table.h
  16. +62
    -0
      include/cpphibernate/driver/mariadb/schema/table.inl
  17. +45
    -16
      include/cpphibernate/misc/nullable_helper.h
  18. +3
    -3
      include/cpphibernate/schema/schema.h
  19. +1
    -0
      include/cpphibernate/schema/table.h
  20. +1
    -1
      src/CMakeLists.txt
  21. +88
    -4
      src/driver/mariadb/schema/field.cpp
  22. +19
    -0
      src/driver/mariadb/schema/filter.cpp
  23. +9
    -0
      src/driver/mariadb/schema/schema.cpp
  24. +453
    -75
      src/driver/mariadb/schema/table.cpp
  25. +439
    -0
      test/cpphibernate_create.cpp
  26. +7
    -30
      test/cpphibernate_init.cpp
  27. +3
    -3
      test/mariadb_mock.cpp
  28. +22
    -5
      test/mariadb_mock.h
  29. +168
    -0
      test/test_helper.h
  30. +3
    -1
      test/test_schema.h

+ 1
- 0
include/cpphibernate/driver/mariadb/helper.h View File

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

+ 72
- 0
include/cpphibernate/driver/mariadb/helper/context.h View File

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

+ 74
- 0
include/cpphibernate/driver/mariadb/helper/reference_stack.h View File

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

+ 2
- 2
include/cpphibernate/driver/mariadb/helper/type_properties.h View File

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



+ 2
- 1
include/cpphibernate/driver/mariadb/impl.h View File

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

+ 95
- 0
include/cpphibernate/driver/mariadb/impl/create.h View File

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

+ 3
- 17
include/cpphibernate/driver/mariadb/impl/init.h View File

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

+ 18
- 2
include/cpphibernate/driver/mariadb/mariadb.h View File

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



+ 2
- 1
include/cpphibernate/driver/mariadb/schema.h View File

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

+ 44
- 11
include/cpphibernate/driver/mariadb/schema/field.h View File

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


+ 74
- 1
include/cpphibernate/driver/mariadb/schema/field.inl View File

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

+ 13
- 0
include/cpphibernate/driver/mariadb/schema/filter.fwd.h View File

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

+ 28
- 0
include/cpphibernate/driver/mariadb/schema/filter.h View File

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

+ 2
- 0
include/cpphibernate/driver/mariadb/schema/schema.h View File

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


+ 42
- 7
include/cpphibernate/driver/mariadb/schema/table.h View File

@@ -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 { });


+ 62
- 0
include/cpphibernate/driver/mariadb/schema/table.inl View File

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

+ 45
- 16
include/cpphibernate/misc/nullable_helper.h View File

@@ -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(); }
};

}

+ 3
- 3
include/cpphibernate/schema/schema.h View File

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

}

+ 1
- 0
include/cpphibernate/schema/table.h View File

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


+ 1
- 1
src/CMakeLists.txt View File

@@ -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 ()

+ 88
- 4
src/driver/mariadb/schema/field.cpp View File

@@ -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();
}

+ 19
- 0
src/driver/mariadb/schema/filter.cpp View File

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

+ 9
- 0
src/driver/mariadb/schema/schema.cpp View File

@@ -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()); \


+ 453
- 75
src/driver/mariadb/schema/table.cpp View File

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

+ 439
- 0
test/cpphibernate_create.cpp View File

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

+ 7
- 30
test/cpphibernate_init.cpp View File

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


+ 3
- 3
test/mariadb_mock.cpp View File

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


+ 22
- 5
test/mariadb_mock.h View File

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



+ 168
- 0
test/test_helper.h View File

@@ -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()); }

+ 3
- 1
test/test_schema.h View File

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


Loading…
Cancel
Save