| @@ -111,9 +111,9 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| ::cppmariadb::statement& get_statement_key_from_base() const; | |||
| ::cppmariadb::statement& get_statement_create_table() const; | |||
| ::cppmariadb::statement* get_statement_alter_table() const; | |||
| ::cppmariadb::statement& get_statement_insert_into() const; | |||
| ::cppmariadb::statement* get_statement_insert_into() const; | |||
| ::cppmariadb::statement& get_statement_select(const read_context& context) const; | |||
| ::cppmariadb::statement& get_statement_update(const filter_t& filter, const field_t* owner) const; | |||
| ::cppmariadb::statement* get_statement_update(const filter_t& filter, const field_t* owner) const; | |||
| ::cppmariadb::statement& get_statement_foreign_many_delete() const; | |||
| ::cppmariadb::statement& get_statement_delete() const; | |||
| @@ -121,7 +121,7 @@ beg_namespace_cpphibernate_driver_mariadb | |||
| std::string execute_create_update( | |||
| const create_update_context& context, | |||
| ::cppmariadb::statement& statement) const; | |||
| ::cppmariadb::statement* statement) const; | |||
| std::string get_primary_key(const data_context& context) const; | |||
| std::string get_key_from_base(const data_context& context) const; | |||
| @@ -72,11 +72,11 @@ beg_namespace_cpphibernate_schema | |||
| /* getter_lambda_t */ | |||
| template<typename T_dataset, typename T_lambda> | |||
| template<typename T_lambda, typename T_dataset, typename T_value> | |||
| struct getter_lambda_t | |||
| : public getter_t<T_dataset, decltype(std::declval<T_lambda>()(std::declval<T_dataset>()))> | |||
| : public getter_t<T_dataset, T_value> | |||
| { | |||
| using base_type = getter_t<T_dataset, decltype(std::declval<T_lambda>()(std::declval<T_dataset>()))>; | |||
| using base_type = getter_t<T_dataset, T_value>; | |||
| using dataset_type = typename base_type::dataset_type; | |||
| using value_type = typename base_type::value_type; | |||
| using lambda_type = T_lambda; | |||
| @@ -104,8 +104,8 @@ beg_namespace_cpphibernate_schema | |||
| { }; | |||
| template<typename T> | |||
| struct is_getter_impl<T, mp::enable_if_c< | |||
| mp::is_base_of<getter_t<typename T::dataset_type, typename T::value_type>, T>::value>> | |||
| struct is_getter_impl<T, mp::enable_if< | |||
| mp::is_base_of<getter_t<typename T::dataset_type, typename T::value_type>, T>>> | |||
| : public mp::c_true_t | |||
| { }; | |||
| @@ -122,19 +122,31 @@ beg_namespace_cpphibernate_schema | |||
| template<typename T_dataset, typename T_value> | |||
| constexpr decltype(auto) make_getter_member_var(T_value T_dataset::* member) | |||
| { return __impl::getter_member_var_t<T_dataset, T_value, T_value T_dataset::*>(member); } | |||
| { | |||
| using getter_type = __impl::getter_member_var_t<T_dataset, T_value, T_value T_dataset::*>; | |||
| return getter_type(member); | |||
| } | |||
| template<typename T_dataset, typename T_value> | |||
| constexpr decltype(auto) make_getter_member_func(T_value (T_dataset::*member)()) | |||
| { return __impl::getter_member_func_t<T_dataset, T_value, T_value (T_dataset::*)()>(member); } | |||
| { | |||
| using getter_type = __impl::getter_member_func_t<T_dataset, T_value, T_value (T_dataset::*)()>; | |||
| return getter_type(member); | |||
| } | |||
| template<typename T_dataset, typename T_value> | |||
| constexpr decltype(auto) make_getter_member_func(T_value (T_dataset::*member)() const) | |||
| { return __impl::getter_member_func_t<const T_dataset, T_value, T_value (T_dataset::*)() const>(member); } | |||
| { | |||
| using getter_type = __impl::getter_member_func_t<const T_dataset, T_value, T_value (T_dataset::*)() const>; | |||
| return getter_type(member); | |||
| } | |||
| template<typename T_dataset, typename T_lambda> | |||
| constexpr decltype(auto) make_getter_lambda(T_lambda&& lambda, boost::hana::basic_type<T_dataset>) | |||
| { return __impl::getter_lambda_t<T_dataset, T_lambda>(std::forward<T_lambda>(lambda)); } | |||
| template<typename T_lambda, typename T_wrapped_dataset, typename T_wrapped_value> | |||
| constexpr decltype(auto) make_getter_lambda(T_lambda&& lambda, T_wrapped_dataset&&, T_wrapped_value&&) | |||
| { | |||
| using getter_type = __impl::getter_lambda_t<T_lambda, misc::decay_unwrap_t<T_wrapped_dataset>, misc::decay_unwrap_t<T_wrapped_value>>; | |||
| return getter_type(std::forward<T_lambda>(lambda)); | |||
| } | |||
| /* operations */ | |||
| @@ -160,6 +172,11 @@ beg_namespace_cpphibernate_schema | |||
| template<typename T_func, typename T_dataset, typename T_value> | |||
| constexpr decltype(auto) operator()(T_func&& func, hana::type<T_dataset>&& wrapped_dataset, hana::type<T_value>&& wrapped_value) const | |||
| { return make_getter_lambda(std::forward<T_func>(func), std::forward<hana::type<T_dataset>>(wrapped_dataset), std::forward<hana::type<T_value>>(wrapped_value)); } | |||
| template<typename T_getter> | |||
| constexpr auto operator()(T_getter&& getter) const | |||
| -> mp::enable_if<is_getter<T_getter>, T_getter> | |||
| { return std::forward<T_getter>(getter); } | |||
| }; | |||
| } | |||
| @@ -41,3 +41,18 @@ | |||
| id, \ | |||
| p_member_ptr, \ | |||
| cpphibernate::schema::attribute::primary_key) | |||
| #define cpphibernate_make_temp_id(p_dataset, p_value) \ | |||
| cpphibernate_make_field_custom( \ | |||
| id, \ | |||
| ::cpphibernate::schema::make_getter_lambda( \ | |||
| [](p_dataset&) { \ | |||
| return p_value { }; \ | |||
| }, \ | |||
| ::boost::hana::type_c<p_dataset>, \ | |||
| ::boost::hana::type_c<p_value>), \ | |||
| ::cpphibernate::schema::make_setter_lambda( \ | |||
| [](p_dataset&, p_value&&) { }, \ | |||
| ::boost::hana::type_c<p_dataset>, \ | |||
| ::boost::hana::type_c<p_value>), \ | |||
| cpphibernate::schema::attribute::primary_key) | |||
| @@ -11,20 +11,21 @@ beg_namespace_cpphibernate_schema | |||
| /* setter_t */ | |||
| template<typename T_value> | |||
| template<typename T_dataset, typename T_value> | |||
| struct setter_t | |||
| { | |||
| using value_type = T_value; | |||
| using dataset_type = T_dataset; | |||
| using value_type = T_value; | |||
| }; | |||
| /* setter_none_t */ | |||
| struct setter_none_t | |||
| : public setter_t<void> | |||
| : public setter_t<void, void> | |||
| { | |||
| using base_type = setter_t<void>; | |||
| using value_type = typename base_type::value_type; | |||
| using base_type = setter_t<void, void>; | |||
| using dataset_type = typename base_type::dataset_type; | |||
| using value_type = typename base_type::value_type; | |||
| cpphibernate_constructable(setter_none_t, default); | |||
| cpphibernate_copyable (setter_none_t, delete); | |||
| @@ -35,11 +36,11 @@ beg_namespace_cpphibernate_schema | |||
| template<typename T_dataset, typename T_value, typename T_member> | |||
| struct setter_member_var_t | |||
| : public setter_t<T_value> | |||
| : public setter_t<T_dataset, T_value> | |||
| { | |||
| using base_type = setter_t<T_value>; | |||
| using base_type = setter_t<T_dataset, T_value>; | |||
| using value_type = typename base_type::value_type; | |||
| using dataset_type = T_dataset; | |||
| using dataset_type = typename base_type::dataset_type; | |||
| using member_type = T_member; | |||
| member_type member; | |||
| @@ -61,11 +62,11 @@ beg_namespace_cpphibernate_schema | |||
| template<typename T_dataset, typename T_value, typename T_member> | |||
| struct setter_member_func_t | |||
| : public setter_t<T_value> | |||
| : public setter_t<T_dataset, T_value> | |||
| { | |||
| using base_type = setter_t<T_value>; | |||
| using base_type = setter_t<T_dataset, T_value>; | |||
| using value_type = typename base_type::value_type; | |||
| using dataset_type = T_dataset; | |||
| using dataset_type = typename base_type::dataset_type; | |||
| using member_type = T_member; | |||
| member_type member; | |||
| @@ -85,13 +86,13 @@ beg_namespace_cpphibernate_schema | |||
| /* setter_lambda_t */ | |||
| template<typename T_dataset, typename T_value, typename T_lambda> | |||
| template<typename T_lambda, typename T_dataset, typename T_value> | |||
| struct setter_lambda_t | |||
| : public setter_t<T_value> | |||
| : public setter_t<T_dataset, T_value> | |||
| { | |||
| using base_type = setter_t<T_value>; | |||
| using base_type = setter_t<T_dataset, T_value>; | |||
| using value_type = typename base_type::value_type; | |||
| using dataset_type = T_dataset; | |||
| using dataset_type = typename base_type::dataset_type; | |||
| using lambda_type = T_lambda; | |||
| lambda_type lambda; | |||
| @@ -117,8 +118,8 @@ beg_namespace_cpphibernate_schema | |||
| { }; | |||
| template<typename T> | |||
| struct is_setter_impl<T, mp::enable_if_c< | |||
| mp::is_base_of<setter_t<typename T::value_type>, T>::value>> | |||
| struct is_setter_impl<T, mp::enable_if< | |||
| mp::is_base_of<setter_t<typename T::dataset_type, typename T::value_type>, T>>> | |||
| : public mp::c_true_t | |||
| { }; | |||
| @@ -134,23 +135,38 @@ beg_namespace_cpphibernate_schema | |||
| /* make */ | |||
| constexpr decltype(auto) make_setter_none() | |||
| { return __impl::setter_none_t(); } | |||
| { | |||
| using setter_type = __impl::setter_none_t; | |||
| return setter_type(); | |||
| } | |||
| template<typename T_dataset, typename T_value> | |||
| constexpr decltype(auto) make_setter_member_var(T_value T_dataset::* member) | |||
| { return __impl::setter_member_var_t<T_dataset, T_value, T_value T_dataset::*>(member); } | |||
| { | |||
| using setter_type = __impl::setter_member_var_t<T_dataset, T_value, T_value T_dataset::*>; | |||
| return setter_type(member); | |||
| } | |||
| template<typename T_dataset, typename T_value, typename T_return> | |||
| constexpr decltype(auto) make_setter_member_func(T_return (T_dataset::*member)(T_value)) | |||
| { return __impl::setter_member_func_t<T_dataset, T_value, T_return (T_dataset::*)(T_value)>(member); } | |||
| { | |||
| using setter_type = __impl::setter_member_func_t<T_dataset, T_value, T_return (T_dataset::*)(T_value)>; | |||
| return setter_type(member); | |||
| } | |||
| template<typename T_dataset, typename T_value, typename T_return> | |||
| constexpr decltype(auto) make_setter_member_func(T_return (T_dataset::*member)(T_value) const) | |||
| { return __impl::setter_member_func_t<const T_dataset, T_value, T_return (T_dataset::*)(T_value) const>(member); } | |||
| { | |||
| using setter_type = __impl::setter_member_func_t<const T_dataset, T_value, T_return (T_dataset::*)(T_value) const>; | |||
| return setter_type(member); | |||
| } | |||
| template<typename T_dataset, typename T_value, typename T_lambda> | |||
| constexpr decltype(auto) make_setter_lambda(T_lambda&& lambda, boost::hana::basic_type<T_dataset>, boost::hana::basic_type<T_value>) | |||
| { return __impl::setter_lambda_t<T_dataset, T_value, T_lambda>(std::forward<T_lambda>(lambda)); } | |||
| template<typename T_lambda, typename T_wrapped_dataset, typename T_wrapped_value> | |||
| constexpr decltype(auto) make_setter_lambda(T_lambda&& lambda, T_wrapped_dataset&&, T_wrapped_value&&) | |||
| { | |||
| using setter_type = __impl::setter_lambda_t<T_lambda, misc::decay_unwrap_t<T_wrapped_dataset>, misc::decay_unwrap_t<T_wrapped_value>>; | |||
| return setter_type(std::forward<T_lambda>(lambda)); | |||
| } | |||
| /* operations */ | |||
| @@ -177,9 +193,14 @@ beg_namespace_cpphibernate_schema | |||
| constexpr decltype(auto) operator()(void (T_dataset::*member)(T_value&&)) const | |||
| { return make_setter_member_func(member); } | |||
| template<typename T_func, typename T_dataset, typename T_value> | |||
| constexpr decltype(auto) operator()(T_func&& func, hana::type<T_dataset>&& wrapped_dataset, hana::type<T_value>&& wrapped_value) const | |||
| { return make_setter_lambda(std::forward<T_func>(func), std::forward<hana::type<T_dataset>>(wrapped_dataset), std::forward<hana::type<T_value>>(wrapped_value)); } | |||
| template<typename T_func, typename T_wrapped_dataset, typename T_wrapped_value> | |||
| constexpr decltype(auto) operator()(T_func&& func, T_wrapped_dataset&&, T_wrapped_value&&) const | |||
| { return make_setter_lambda(std::forward<T_func>(func), T_wrapped_dataset { }, T_wrapped_value { }); } | |||
| template<typename T_setter> | |||
| constexpr auto operator()(T_setter&& setter) const | |||
| -> mp::enable_if<is_setter<T_setter>, T_setter> | |||
| { return std::forward<T_setter>(setter); } | |||
| }; | |||
| } | |||
| @@ -1088,7 +1088,9 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt | |||
| << key_info.convert_to_close; | |||
| } | |||
| return os.str(); | |||
| return index == 0 | |||
| ? std::string() | |||
| : os.str(); | |||
| } | |||
| std::string build_select_query( | |||
| @@ -1103,7 +1105,7 @@ std::string build_select_query( | |||
| std::string table_t::execute_create_update( | |||
| const create_update_context& context, | |||
| ::cppmariadb::statement& statement) const | |||
| ::cppmariadb::statement* statement) const | |||
| { | |||
| auto& connection = context.connection; | |||
| auto& filter = context.filter; | |||
| @@ -1112,7 +1114,7 @@ std::string table_t::execute_create_update( | |||
| bool is_update = context.is_update; | |||
| std::string primary_key; | |||
| statement.clear(); | |||
| if (statement) statement->clear(); | |||
| /* primary key */ | |||
| assert(primary_key_field); | |||
| @@ -1120,7 +1122,7 @@ std::string table_t::execute_create_update( | |||
| && !is_update) | |||
| { | |||
| primary_key = primary_key_field->generate_value(context.connection); | |||
| statement.set(index, primary_key); | |||
| if (statement) statement->set(index, primary_key); | |||
| ++index; | |||
| } | |||
| else | |||
| @@ -1137,7 +1139,7 @@ std::string table_t::execute_create_update( | |||
| if (!new_context.derived_table) | |||
| new_context.derived_table = this; | |||
| std::string key = create_update_base(new_context); | |||
| statement.set(index, std::move(key)); | |||
| if (statement) statement->set(index, std::move(key)); | |||
| ++index; | |||
| } | |||
| @@ -1155,11 +1157,17 @@ std::string table_t::execute_create_update( | |||
| /* insert/update dataset */ | |||
| value_t key = field.foreign_create_update(context); | |||
| if (key.has_value()) | |||
| statement.set(index, std::move(key)); | |||
| { | |||
| if (statement) statement->set(index, std::move(key)); | |||
| } | |||
| else if (field.value_is_nullable) | |||
| statement.set_null(index); | |||
| { | |||
| if (statement) statement->set_null(index); | |||
| } | |||
| else | |||
| { | |||
| throw misc::hibernate_exception("Received null key for non nullable foreign dataset!"); | |||
| } | |||
| ++index; | |||
| /* cleanup old dataset (if new one was created) */ | |||
| @@ -1189,18 +1197,24 @@ std::string table_t::execute_create_update( | |||
| if (set_value) | |||
| { | |||
| assert(!context.owner_key.empty()); | |||
| statement.set(index, context.owner_key); | |||
| if (statement) statement->set(index, context.owner_key); | |||
| } | |||
| else | |||
| { | |||
| statement.set_null(index); | |||
| if (statement) statement->set_null(index); | |||
| } | |||
| ++index; | |||
| if (field_info.value_is_ordered) | |||
| { | |||
| if (set_value) statement.set(index, context.index); | |||
| else statement.set(index, 0); | |||
| if (set_value) | |||
| { | |||
| if (statement) statement->set(index, context.index); | |||
| } | |||
| else | |||
| { | |||
| if (statement) statement->set(index, 0); | |||
| } | |||
| ++index; | |||
| } | |||
| } | |||
| @@ -1215,8 +1229,14 @@ std::string table_t::execute_create_update( | |||
| auto& field_info = *ptr; | |||
| auto value = field_info.get(context); | |||
| if (value.has_value()) statement.set(index, *value); | |||
| else statement.set_null(index); | |||
| if (value.has_value()) | |||
| { | |||
| if (statement) statement->set(index, *value); | |||
| } | |||
| else | |||
| { | |||
| if (statement) statement->set_null(index); | |||
| } | |||
| ++index; | |||
| } | |||
| @@ -1225,7 +1245,7 @@ std::string table_t::execute_create_update( | |||
| && !base_table | |||
| && !is_update) | |||
| { | |||
| statement.set(index, context.derived_table | |||
| if (statement) statement->set(index, context.derived_table | |||
| ? context.derived_table->table_id | |||
| : table_id); | |||
| ++index; | |||
| @@ -1235,34 +1255,37 @@ std::string table_t::execute_create_update( | |||
| if (is_update) | |||
| { | |||
| assert(primary_key_field); | |||
| statement.set(index, *primary_key_field->get(context)); | |||
| if (statement) statement->set(index, *primary_key_field->get(context)); | |||
| ++index; | |||
| } | |||
| /* execute */ | |||
| if (!is_update) | |||
| { | |||
| cpphibernate_debug_log("execute INSERT query: " << statement.query(connection)); | |||
| } | |||
| else | |||
| if (statement) | |||
| { | |||
| cpphibernate_debug_log("execute UPDATE query: " << statement.query(connection)); | |||
| } | |||
| 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->value_is_auto_incremented | |||
| && !is_update) | |||
| { | |||
| auto id = connection.execute_id(statement); | |||
| primary_key = utl::to_string(id); | |||
| } | |||
| else | |||
| { | |||
| auto count = connection.execute_rows(statement); | |||
| if (count != 1) | |||
| throw misc::hibernate_exception("Expected exaclty one row to be inserted/updated!"); | |||
| cpphibernate_debug_log(count << " rows inserted/updated"); | |||
| if ( primary_key_field->value_is_auto_incremented | |||
| && !is_update) | |||
| { | |||
| auto id = connection.execute_id(*statement); | |||
| primary_key = utl::to_string(id); | |||
| } | |||
| else | |||
| { | |||
| auto count = connection.execute_rows(*statement); | |||
| if (count != 1) | |||
| throw misc::hibernate_exception("Expected exaclty one row to be inserted/updated!"); | |||
| cpphibernate_debug_log(count << " rows inserted/updated"); | |||
| } | |||
| primary_key_field->set(context, primary_key); | |||
| } | |||
| primary_key_field->set(context, primary_key); | |||
| /* foreign table many fields */ | |||
| for (auto& ptr : foreign_table_many_fields) | |||
| @@ -1437,18 +1460,21 @@ std::string table_t::build_delete_query(const std::string* where) const | |||
| auto query = build_init_stage2_query(*this); | |||
| _statement_alter_table.reset(new ::cppmariadb::statement(query)); | |||
| } | |||
| if (_statement_alter_table->empty()) | |||
| return nullptr; | |||
| return _statement_alter_table.get(); | |||
| return _statement_alter_table->empty() | |||
| ? nullptr | |||
| : _statement_alter_table.get(); | |||
| } | |||
| ::cppmariadb::statement& table_t::get_statement_insert_into() const | |||
| ::cppmariadb::statement* table_t::get_statement_insert_into() const | |||
| { | |||
| if (_statement_insert_into) | |||
| return *_statement_insert_into; | |||
| auto query = build_create_update_query(*this, nullptr, nullptr); | |||
| _statement_create_table.reset(new ::cppmariadb::statement(query)); | |||
| return *_statement_create_table; | |||
| if (!_statement_insert_into) | |||
| { | |||
| auto query = build_create_update_query(*this, nullptr, nullptr); | |||
| _statement_insert_into.reset(new ::cppmariadb::statement(query)); | |||
| } | |||
| return _statement_insert_into->empty() | |||
| ? nullptr | |||
| : _statement_insert_into.get(); | |||
| } | |||
| ::cppmariadb::statement& table_t::get_statement_select(const read_context& context) const | |||
| @@ -1466,7 +1492,7 @@ std::string table_t::build_delete_query(const std::string* where) const | |||
| return it->second; | |||
| } | |||
| ::cppmariadb::statement& table_t::get_statement_update(const filter_t& filter, const field_t* owner) const | |||
| ::cppmariadb::statement* table_t::get_statement_update(const filter_t& filter, const field_t* owner) const | |||
| { | |||
| auto key = std::make_tuple(filter.cache_id, owner); | |||
| auto it = _statement_update.find(key); | |||
| @@ -1475,7 +1501,9 @@ std::string table_t::build_delete_query(const std::string* where) const | |||
| auto query = build_create_update_query(*this, &filter, owner); | |||
| it = _statement_update.emplace(key, ::cppmariadb::statement(query)).first; | |||
| } | |||
| return it->second; | |||
| return it->second.empty() | |||
| ? nullptr | |||
| : &it->second; | |||
| } | |||
| ::cppmariadb::statement& table_t::get_statement_foreign_many_delete() const | |||
| @@ -1586,7 +1614,7 @@ std::string table_t::create_update_intern(const create_update_context& context) | |||
| std::string table_t::create_update_exec(const create_update_context& context) const | |||
| { | |||
| auto& statement = context.is_update | |||
| auto* statement = context.is_update | |||
| ? get_statement_update(context.filter, context.owner_field) | |||
| : get_statement_insert_into(); | |||
| return execute_create_update(context, statement); | |||
| @@ -449,3 +449,72 @@ TEST(CppHibernateTests, create_derived3) | |||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||
| context.create(static_cast<derived2&>(d3)); | |||
| } | |||
| TEST(CppHibernateTests, create_dummy_owner) | |||
| { | |||
| StrictMock<mariadb_mock> mock; | |||
| expect_query(mock, "START TRANSACTION"); | |||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||
| { "00000000-0000-0000-0000-000000000001" } | |||
| })); | |||
| expect_query(mock, "INSERT INTO " | |||
| "`tbl_dummy_owner` " | |||
| "SET " | |||
| "`tbl_dummy_owner_id`=UuidToBin('X00000000-0000-0000-0000-000000000001X')", | |||
| result_affected_rows(1)); | |||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||
| { "00000000-0000-0000-0001-000000000001" } | |||
| })); | |||
| expect_query(mock, "INSERT INTO " | |||
| "`tbl_dummy_id` " | |||
| "SET " | |||
| "`tbl_dummy_id_id`=UuidToBin('X00000000-0000-0000-0001-000000000001X'), " | |||
| "`tbl_dummy_owner_id_dummies`=UuidToBin('X00000000-0000-0000-0000-000000000001X'), " | |||
| "`tbl_dummy_owner_index_dummies`='X0X', " | |||
| "`data`='X123X'", | |||
| result_affected_rows(1)); | |||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||
| { "00000000-0000-0000-0001-000000000002" } | |||
| })); | |||
| expect_query(mock, "INSERT INTO " | |||
| "`tbl_dummy_id` " | |||
| "SET " | |||
| "`tbl_dummy_id_id`=UuidToBin('X00000000-0000-0000-0001-000000000002X'), " | |||
| "`tbl_dummy_owner_id_dummies`=UuidToBin('X00000000-0000-0000-0000-000000000001X'), " | |||
| "`tbl_dummy_owner_index_dummies`='X1X', " | |||
| "`data`='X456X'", | |||
| result_affected_rows(1)); | |||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||
| { "00000000-0000-0000-0001-000000000003" } | |||
| })); | |||
| expect_query(mock, "INSERT INTO " | |||
| "`tbl_dummy_id` " | |||
| "SET " | |||
| "`tbl_dummy_id_id`=UuidToBin('X00000000-0000-0000-0001-000000000003X'), " | |||
| "`tbl_dummy_owner_id_dummies`=UuidToBin('X00000000-0000-0000-0000-000000000001X'), " | |||
| "`tbl_dummy_owner_index_dummies`='X2X', " | |||
| "`data`='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))); | |||
| dummy_owner d; | |||
| d.dummies.emplace_back(123); | |||
| d.dummies.emplace_back(456); | |||
| d.dummies.emplace_back(789); | |||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||
| context.create(d); | |||
| } | |||
| @@ -151,6 +151,28 @@ TEST(CppHibernateTests, init) | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_dummy_id`\n" | |||
| "(\n" | |||
| " `tbl_dummy_id_id` BINARY(16) NOT NULL,\n" | |||
| " `tbl_dummy_owner_id_dummies` BINARY(16) NULL DEFAULT NULL,\n" | |||
| " `tbl_dummy_owner_index_dummies` INT UNSIGNED NOT NULL,\n" | |||
| " `data` INT NOT NULL,\n" | |||
| " PRIMARY KEY ( `tbl_dummy_id_id` ),\n" | |||
| " UNIQUE INDEX `index_tbl_dummy_id_id` ( `tbl_dummy_id_id` ASC ),\n" | |||
| " INDEX `index_tbl_dummy_owner_id_dummies` ( `tbl_dummy_owner_id_dummies` ASC )\n" | |||
| ")\n" | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_dummy_owner`\n" | |||
| "(\n" | |||
| " `tbl_dummy_owner_id` BINARY(16) NOT NULL,\n" | |||
| " PRIMARY KEY ( `tbl_dummy_owner_id` ),\n" | |||
| " UNIQUE INDEX `index_tbl_dummy_owner_id` ( `tbl_dummy_owner_id` ASC )\n" | |||
| ")\n" | |||
| "ENGINE = InnoDB\n" | |||
| "DEFAULT CHARACTER SET = utf8"); | |||
| expect_query(mock, "ALTER TABLE `tbl_test3`\n" | |||
| " ADD CONSTRAINT `fk_tbl_test3_to_tbl_derived3_id_test3_list`\n" | |||
| " FOREIGN KEY IF NOT EXISTS (`tbl_derived3_id_test3_list`)\n" | |||
| @@ -204,6 +226,13 @@ TEST(CppHibernateTests, init) | |||
| " ON DELETE CASCADE\n" | |||
| " ON UPDATE CASCADE"); | |||
| expect_query(mock, "ALTER TABLE `tbl_dummy_id`\n" | |||
| " ADD CONSTRAINT `fk_tbl_dummy_id_to_tbl_dummy_owner_id_dummies`\n" | |||
| " FOREIGN KEY IF NOT EXISTS (`tbl_dummy_owner_id_dummies`)\n" | |||
| " REFERENCES `test`.`tbl_dummy_owner` (`tbl_dummy_owner_id`)\n" | |||
| " ON DELETE SET NULL\n" | |||
| " ON UPDATE NO ACTION"); | |||
| expect_query(mock, "COMMIT"); | |||
| EXPECT_CALL( | |||
| @@ -863,4 +863,58 @@ TEST(CppHibernateTests, read_base_ptr_vector_dynamic) | |||
| } | |||
| EXPECT_EQ(bIt, base_vec.end()); | |||
| } | |||
| TEST(CppHibernateTests, read_dummy_owner) | |||
| { | |||
| StrictMock<mariadb_mock> mock; | |||
| expect_query(mock, "START TRANSACTION"); | |||
| expect_query(mock, "SELECT " | |||
| "BinToUuid(`tbl_dummy_owner`.`tbl_dummy_owner_id`) " | |||
| "FROM " | |||
| "`tbl_dummy_owner` " | |||
| "WHERE " | |||
| "(`tbl_dummy_owner`.`tbl_dummy_owner_id`=UuidToBin('X00000000-0000-0000-0000-000000000001X')) ", | |||
| result_used({ | |||
| { "00000000-0000-0000-0000-000000000001" }, | |||
| })); | |||
| expect_query(mock, "SELECT " | |||
| "BinToUuid(`tbl_dummy_id`.`tbl_dummy_id_id`), " | |||
| "`tbl_dummy_id`.`data` " | |||
| "FROM " | |||
| "`tbl_dummy_id` WHERE (`tbl_dummy_id`.`tbl_dummy_owner_id_dummies`=UuidToBin('X00000000-0000-0000-0000-000000000001X')) " | |||
| "ORDER BY " | |||
| "`tbl_dummy_id`.`tbl_dummy_owner_index_dummies` ASC ", | |||
| result_used({ | |||
| { "00000000-0000-0000-0001-000000000001", "123" }, | |||
| { "00000000-0000-0000-0001-000000000002", "456" }, | |||
| { "00000000-0000-0000-0001-000000000003", "789" }, | |||
| })); | |||
| expect_query(mock, "COMMIT"); | |||
| EXPECT_CALL( | |||
| mock, | |||
| mysql_close( | |||
| reinterpret_cast<MYSQL*>(0x1111))); | |||
| EXPECT_CALL( | |||
| mock, | |||
| mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x1111), _, _, _)) | |||
| .Times(AnyNumber()) | |||
| .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||
| dummy_owner d; | |||
| d.id = uuid("00000000-0000-0000-0000-000000000001"); | |||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||
| context.read(d); | |||
| EXPECT_EQ(d.id, uuid("00000000-0000-0000-0000-000000000001")); | |||
| ASSERT_EQ(d.dummies.size(), 3); | |||
| EXPECT_EQ(d.dummies[0].data, 123); | |||
| EXPECT_EQ(d.dummies[1].data, 456); | |||
| EXPECT_EQ(d.dummies[2].data, 789); | |||
| } | |||
| @@ -634,4 +634,79 @@ TEST(CppHibernateTests, update_dynamic_base) | |||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||
| context.update(b); | |||
| } | |||
| TEST(CppHibernateTests, update_dummy_owner) | |||
| { | |||
| StrictMock<mariadb_mock> mock; | |||
| expect_query(mock, "START TRANSACTION"); | |||
| expect_query(mock, "UPDATE " | |||
| "`tbl_dummy_id` " | |||
| "SET " | |||
| "`tbl_dummy_owner_id_dummies`=NULL, " | |||
| "`tbl_dummy_owner_index_dummies`=0 " | |||
| "WHERE " | |||
| "`tbl_dummy_owner_id_dummies`=UuidToBin('X00000000-0000-0000-0000-000000000001X')"); | |||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||
| { "00000000-0000-0000-0001-000000000001" } | |||
| })); | |||
| expect_query(mock, "INSERT INTO " | |||
| "`tbl_dummy_id` " | |||
| "SET " | |||
| "`tbl_dummy_id_id`=UuidToBin('X00000000-0000-0000-0001-000000000001X'), " | |||
| "`tbl_dummy_owner_id_dummies`=UuidToBin('X00000000-0000-0000-0000-000000000001X'), " | |||
| "`tbl_dummy_owner_index_dummies`='X0X', " | |||
| "`data`='X123X'", | |||
| result_affected_rows(1)); | |||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||
| { "00000000-0000-0000-0001-000000000002" } | |||
| })); | |||
| expect_query(mock, "INSERT INTO " | |||
| "`tbl_dummy_id` " | |||
| "SET " | |||
| "`tbl_dummy_id_id`=UuidToBin('X00000000-0000-0000-0001-000000000002X'), " | |||
| "`tbl_dummy_owner_id_dummies`=UuidToBin('X00000000-0000-0000-0000-000000000001X'), " | |||
| "`tbl_dummy_owner_index_dummies`='X1X', " | |||
| "`data`='X456X'", | |||
| result_affected_rows(1)); | |||
| expect_query(mock, "SELECT Uuid()", result_used({ | |||
| { "00000000-0000-0000-0001-000000000003" } | |||
| })); | |||
| expect_query(mock, "INSERT INTO " | |||
| "`tbl_dummy_id` " | |||
| "SET " | |||
| "`tbl_dummy_id_id`=UuidToBin('X00000000-0000-0000-0001-000000000003X'), " | |||
| "`tbl_dummy_owner_id_dummies`=UuidToBin('X00000000-0000-0000-0000-000000000001X'), " | |||
| "`tbl_dummy_owner_index_dummies`='X2X', " | |||
| "`data`='X789X'", | |||
| result_affected_rows(1)); | |||
| expect_query(mock, "DELETE " | |||
| "`tbl_dummy_id` " | |||
| "FROM " | |||
| "`tbl_dummy_id` " | |||
| "WHERE " | |||
| "(`tbl_dummy_owner_id_dummies` IS NULL)"); | |||
| 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))); | |||
| dummy_owner d; | |||
| d.id = uuid("00000000-0000-0000-0000-000000000001"); | |||
| d.dummies.emplace_back(123); | |||
| d.dummies.emplace_back(456); | |||
| d.dummies.emplace_back(789); | |||
| ::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||
| auto context = make_context<driver::mariadb>(test_schema, connection); | |||
| context.update(d); | |||
| } | |||
| @@ -93,6 +93,21 @@ struct derived3 | |||
| std::vector<test3> test3_vector; | |||
| }; | |||
| struct dummy_id | |||
| { | |||
| int data; | |||
| inline dummy_id(int p_data = 0) | |||
| : data(p_data) | |||
| { } | |||
| }; | |||
| struct dummy_owner | |||
| { | |||
| ::cpphibernate::uuid id; | |||
| std::vector<dummy_id> dummies; | |||
| }; | |||
| constexpr decltype(auto) test_schema = cpphibernate_make_schema( | |||
| test, | |||
| cpphibernate_make_table_name( | |||
| @@ -158,5 +173,19 @@ constexpr decltype(auto) test_schema = cpphibernate_make_schema( | |||
| cpphibernate_make_id (&derived3::derived3_id), | |||
| cpphibernate_make_field (derived3, test3_list), | |||
| cpphibernate_make_field (derived3, test3_vector) | |||
| ), | |||
| cpphibernate_make_table_name( | |||
| tbl_dummy_id, | |||
| dummy_id, | |||
| 14, | |||
| cpphibernate_make_temp_id (dummy_id, ::cpphibernate::uuid), | |||
| cpphibernate_make_field (dummy_id, data) | |||
| ), | |||
| cpphibernate_make_table_name( | |||
| tbl_dummy_owner, | |||
| dummy_owner, | |||
| 15, | |||
| cpphibernate_make_id (&dummy_owner::id), | |||
| cpphibernate_make_field (dummy_owner, dummies) | |||
| ) | |||
| ); | |||