From 424825c6167ff92dffdac895de1850bf8be75248 Mon Sep 17 00:00:00 2001 From: bergmann Date: Tue, 26 Feb 2019 22:52:34 +0100 Subject: [PATCH] * handle datatypes that are used inside a container as well as a normal reference by using a one-to-many reference/foreign key for the normal references --- .../driver/mariadb/helper/key_properties.h | 8 - .../driver/mariadb/helper/type_properties.h | 8 +- .../driver/mariadb/schema/field.h | 5 +- .../driver/mariadb/schema/field.inl | 9 + .../driver/mariadb/schema/table.h | 11 +- .../driver/mariadb/schema/field.cpp | 27 +-- .../driver/mariadb/schema/schema.cpp | 38 +++- .../driver/mariadb/schema/table.cpp | 168 +++++++++++++----- test/cpphibernate_create.cpp | 59 ++++++ test/cpphibernate_destroy.cpp | 42 +++++ test/cpphibernate_init.cpp | 36 ++++ test/cpphibernate_read.cpp | 63 +++++++ test/cpphibernate_update.cpp | 89 ++++++++++ test/test_helper.h | 21 ++- test/test_schema.h | 32 ++++ 15 files changed, 532 insertions(+), 84 deletions(-) diff --git a/include/cpphibernate/driver/mariadb/helper/key_properties.h b/include/cpphibernate/driver/mariadb/helper/key_properties.h index 87498b4..6a800ea 100644 --- a/include/cpphibernate/driver/mariadb/helper/key_properties.h +++ b/include/cpphibernate/driver/mariadb/helper/key_properties.h @@ -20,10 +20,6 @@ beg_namespace_cpphibernate_driver_mariadb static constexpr decltype(auto) create_table_argument = ""; static constexpr decltype(auto) create_key_query = "SELECT Uuid()"; - static constexpr decltype(auto) convert_to_open = "UuidToBin("; - static constexpr decltype(auto) convert_to_close = ")"; - static constexpr decltype(auto) convert_from_open = "BinToUuid("; - static constexpr decltype(auto) convert_from_close = ")"; static bool is_default(const key_type& key) { return key == key_type(); } @@ -37,10 +33,6 @@ beg_namespace_cpphibernate_driver_mariadb static constexpr decltype(auto) create_table_argument = "AUTO_INCREMENT"; static constexpr decltype(auto) create_key_query = ""; - static constexpr decltype(auto) convert_to_open = ""; - static constexpr decltype(auto) convert_to_close = ""; - static constexpr decltype(auto) convert_from_open = ""; - static constexpr decltype(auto) convert_from_close = ""; static bool is_default(const key_type& key) { return key == key_type(); } diff --git a/include/cpphibernate/driver/mariadb/helper/type_properties.h b/include/cpphibernate/driver/mariadb/helper/type_properties.h index 65dc069..5d781e6 100644 --- a/include/cpphibernate/driver/mariadb/helper/type_properties.h +++ b/include/cpphibernate/driver/mariadb/helper/type_properties.h @@ -314,16 +314,16 @@ beg_namespace_cpphibernate_driver_mariadb { return utl::to_string(value); } static constexpr const char* convert_to_open() - { return nullptr; } + { return "UuidToBin("; } static constexpr const char* convert_to_close() - { return nullptr; } + { return ")"; } static constexpr const char* convert_from_open() - { return nullptr; } + { return "BinToUuid("; } static constexpr const char* convert_from_close() - { return nullptr; } + { return ")"; } }; template<> diff --git a/include/cpphibernate/driver/mariadb/schema/field.h b/include/cpphibernate/driver/mariadb/schema/field.h index a507b40..8d037cf 100644 --- a/include/cpphibernate/driver/mariadb/schema/field.h +++ b/include/cpphibernate/driver/mariadb/schema/field.h @@ -30,8 +30,8 @@ beg_namespace_cpphibernate_driver_mariadb bool value_is_ordered { false }; // value is stored in a ordered container (vector, list, ...) bool value_is_auto_incremented { false }; // value is a auto incremented field - const table_t* table { nullptr }; // table this field belongs to - const table_t* referenced_table { nullptr }; // table that belongs to the value (if exists) + table_t* table { nullptr }; // table this field belongs to + table_t* referenced_table { nullptr }; // table that belongs to the value (if exists) std::string schema_name; // name of the SQL schema std::string table_name; // name of the SQL table @@ -162,6 +162,7 @@ beg_namespace_cpphibernate_driver_mariadb using base_type::base_type; + virtual void update () override; virtual bool is_default (const data_context& context) const override; virtual std::string generate_value(::cppmariadb::connection& connection) const override; }; diff --git a/include/cpphibernate/driver/mariadb/schema/field.inl b/include/cpphibernate/driver/mariadb/schema/field.inl index fd9bbbf..fef4450 100644 --- a/include/cpphibernate/driver/mariadb/schema/field.inl +++ b/include/cpphibernate/driver/mariadb/schema/field.inl @@ -64,6 +64,15 @@ beg_namespace_cpphibernate_driver_mariadb /* primary_key_field_t */ + template + void primary_key_field_t + ::update() + { + base_type::update(); + + this->value_is_auto_incremented = key_props::auto_generated::value; + } + template bool primary_key_field_t ::is_default(const data_context& context) const diff --git a/include/cpphibernate/driver/mariadb/schema/table.h b/include/cpphibernate/driver/mariadb/schema/table.h index 1f637fd..28605e4 100644 --- a/include/cpphibernate/driver/mariadb/schema/table.h +++ b/include/cpphibernate/driver/mariadb/schema/table.h @@ -21,9 +21,10 @@ 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 }; + size_t dataset_id { 0 }; + size_t base_dataset_id { 0 }; + size_t table_id { 0 }; + bool is_used_in_container { false }; std::vector derived_dataset_ids; std::string table_name; @@ -31,10 +32,10 @@ beg_namespace_cpphibernate_driver_mariadb fields_t fields; - const table_t* base_table { nullptr }; + const table_t* base_table { nullptr }; std::vector derived_tables; - const field_t* primary_key_field { nullptr }; + const field_t* primary_key_field { nullptr }; std::vector foreign_key_fields; std::vector foreign_table_fields; std::vector foreign_table_one_fields; diff --git a/src/cpphibernate/driver/mariadb/schema/field.cpp b/src/cpphibernate/driver/mariadb/schema/field.cpp index 132c3b9..f11e29f 100644 --- a/src/cpphibernate/driver/mariadb/schema/field.cpp +++ b/src/cpphibernate/driver/mariadb/schema/field.cpp @@ -76,8 +76,7 @@ void field_t::update() case attribute_t::compress: ss << "COMPRESS("; break; - case attribute_t::primary_key: - ss << "UuidToBin("; + default: break; } } @@ -93,9 +92,10 @@ void field_t::update() { case attribute_t::hex: case attribute_t::compress: - case attribute_t::primary_key: ss << ')'; break; + default: + break; } } convert_to_close = ss.str(); @@ -114,8 +114,7 @@ void field_t::update() case attribute_t::compress: ss << "UNCOMPRESS("; break; - case attribute_t::primary_key: - ss << "BinToUuid("; + default: break; } } @@ -131,9 +130,10 @@ void field_t::update() { case attribute_t::hex: case attribute_t::compress: - case attribute_t::primary_key: ss << ')'; break; + default: + break; } } convert_from_close = ss.str(); @@ -259,11 +259,16 @@ throw_not_implemented(::cppmariadb::statement&, get_statement_foreign_many_updat << table_name << "_id_" << field_name - << "`=NULL, `" - << table_name - << "_index_" - << field_name - << "`=0 WHERE `" + << "`=NULL"; + if (value_is_container) + { + os << ", `" + << table_name + << "_index_" + << field_name + << "`=0"; + } + os << " WHERE `" << table_name << "_id_" << field_name diff --git a/src/cpphibernate/driver/mariadb/schema/schema.cpp b/src/cpphibernate/driver/mariadb/schema/schema.cpp index 68fb45a..41ce605 100644 --- a/src/cpphibernate/driver/mariadb/schema/schema.cpp +++ b/src/cpphibernate/driver/mariadb/schema/schema.cpp @@ -25,6 +25,7 @@ void schema_t::update() table.foreign_table_one_fields.clear(); table.foreign_table_many_fields.clear(); table.data_fields.clear(); + table.is_used_in_container = false; for (auto& ptr : table.fields) { @@ -58,6 +59,7 @@ void schema_t::update() // update fields for (auto& ptr : table.fields) { + assert(ptr); auto& field = *ptr; // table @@ -86,12 +88,7 @@ void schema_t::update() table.foreign_table_fields.emplace_back(&field); if (field.value_is_container) { - table.foreign_table_many_fields.emplace_back(&field); - referenced_table->foreign_key_fields.push_back(&field); - } - else - { - table.foreign_table_one_fields.emplace_back(&field); + referenced_table->is_used_in_container = true; } } @@ -104,6 +101,35 @@ void schema_t::update() if (!static_cast(table.primary_key_field)) throw misc::hibernate_exception(std::string("Table '") + table.table_name + "' does not have a primary key!"); } + + // update foreign fields (one, many, key) + for (auto& kvp : tables) + { + assert(static_cast(kvp.second)); + auto& table = *kvp.second; + + for (auto& ptr : table.foreign_table_fields) + { + assert(ptr); + assert(ptr->referenced_table); + auto& field = *ptr; + auto& referenced_table = *field.referenced_table; + + if (field.value_is_container) + { + table.foreign_table_many_fields.emplace_back(&field); + referenced_table.foreign_key_fields.push_back(&field); + } + else + { + table.foreign_table_one_fields.emplace_back(&field); + if (referenced_table.is_used_in_container) + { + referenced_table.foreign_key_fields.push_back(&field); + } + } + } + } } void schema_t::print(std::ostream& os) const diff --git a/src/cpphibernate/driver/mariadb/schema/table.cpp b/src/cpphibernate/driver/mariadb/schema/table.cpp index cd95f2f..195807c 100644 --- a/src/cpphibernate/driver/mariadb/schema/table.cpp +++ b/src/cpphibernate/driver/mariadb/schema/table.cpp @@ -328,10 +328,14 @@ struct select_query_builder_t for (auto& ptr : ctx.table.foreign_table_one_fields) { assert(ptr); + assert(ptr->table); + assert(ptr->table->primary_key_field); assert(ptr->referenced_table); assert(ptr->referenced_table->primary_key_field); auto& field = *ptr; + auto& table = *field.table; + auto& own_key = *table.primary_key_field; auto& ref_table = *field.referenced_table; auto& ref_key = *ref_table.primary_key_field; @@ -342,21 +346,42 @@ struct select_query_builder_t auto new_alias = make_alias(); std::ostringstream ss; - ss << " LEFT JOIN `" - << ref_table.table_name - << "` AS `" - << new_alias - << "` ON `" - << real_alias - << "`.`" - << ref_key.table_name - << "_id_" - << field.field_name - << "`=`" - << new_alias - << "`.`" - << ref_key.field_name - << "`"; + if (ref_table.is_used_in_container) + { + ss << " LEFT JOIN `" + << ref_table.table_name + << "` AS `" + << new_alias + << "` ON `" + << real_alias + << "`.`" + << own_key.field_name + << "`=`" + << new_alias + << "`.`" + << table.table_name + << "_id_" + << field.field_name + << "`"; + } + else + { + ss << " LEFT JOIN `" + << ref_table.table_name + << "` AS `" + << new_alias + << "` ON `" + << real_alias + << "`.`" + << ref_key.table_name + << "_id_" + << field.field_name + << "`=`" + << new_alias + << "`.`" + << ref_key.field_name + << "`"; + } auto it = joins.insert(joins.end(), ss.str()); if (!add_table({ @@ -498,29 +523,53 @@ struct delete_query_builder_t for (auto& ptr : table.foreign_table_one_fields) { assert(ptr); + assert(ptr->table); + assert(ptr->table->primary_key_field); assert(ptr->referenced_table); assert(ptr->referenced_table->primary_key_field); auto& field = *ptr; + auto& own_key = *table.primary_key_field; auto& ref_table = *field.referenced_table; auto& ref_key = *ref_table.primary_key_field; - auto new_alias = make_alias(); + auto new_alias = make_alias(); - os << " LEFT JOIN `" - << ref_table.table_name - << "` AS `" - << new_alias - << "` ON `" - << real_alias - << "`.`" - << ref_key.table_name - << "_id_" - << field.field_name - << "`=`" - << new_alias - << "`.`" - << ref_key.field_name - << "`"; + if (ref_table.is_used_in_container) + { + os << " LEFT JOIN `" + << ref_table.table_name + << "` AS `" + << new_alias + << "` ON `" + << real_alias + << "`.`" + << own_key.field_name + << "`=`" + << new_alias + << "`.`" + << table.table_name + << "_id_" + << field.field_name + << "`"; + } + else + { + os << " LEFT JOIN `" + << ref_table.table_name + << "` AS `" + << new_alias + << "` ON `" + << real_alias + << "`.`" + << ref_key.table_name + << "_id_" + << field.field_name + << "`=`" + << new_alias + << "`.`" + << ref_key.field_name + << "`"; + } add_table(ref_table, new_alias, true, true); } @@ -645,6 +694,8 @@ std::string build_init_stage1_query(const table_t& table) auto& field_info = *ptr; assert(field_info.referenced_table); assert(field_info.referenced_table->primary_key_field); + if (field_info.referenced_table->is_used_in_container) + continue; auto& ref_key_info = *field_info.referenced_table->primary_key_field; os << indent << "`" @@ -753,6 +804,8 @@ std::string build_init_stage1_query(const table_t& table) auto& field_info = *ptr; assert(field_info.referenced_table); assert(field_info.referenced_table->primary_key_field); + if (field_info.referenced_table->is_used_in_container) + continue; auto& ref_key_info = *field_info.referenced_table->primary_key_field; os << "," << indent @@ -848,6 +901,8 @@ std::string build_init_stage2_query(const table_t& table) auto& field_info = *ptr; assert(field_info.referenced_table); assert(field_info.referenced_table->primary_key_field); + if (field_info.referenced_table->is_used_in_container) + continue; auto& ref_key_info = *field_info.referenced_table->primary_key_field; if (index++) os << ","; os << indent @@ -940,7 +995,7 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt : "INSERT INTO") << " `" << table.table_name - << "` SET "; + << "`"; /* primary key */ if (!is_update) @@ -951,6 +1006,8 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt { if (index++) os << ", "; + else + os << " SET "; os << "`" << key_info.field_name << "`=" @@ -969,6 +1026,8 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt { if (index++) os << ", "; + else + os << " SET "; auto& base_table_info = *table.base_table; assert(base_table_info.primary_key_field); auto& key_info = *base_table_info.primary_key_field; @@ -989,10 +1048,14 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt auto& field_info = *ptr; if (is_update && filter->is_excluded(field_info)) continue; - if (index++) - os << ", "; assert(field_info.referenced_table); assert(field_info.referenced_table->primary_key_field); + if (field_info.referenced_table->is_used_in_container) + continue; + if (index++) + os << ", "; + else + os << " SET "; auto& key_info = *field_info.referenced_table->primary_key_field; os << "`" << key_info.table_name @@ -1016,6 +1079,8 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt continue; if (index++) os << ", "; + else + os << " SET "; auto& field_info = *ptr; assert(field_info.table); assert(field_info.table->primary_key_field); @@ -1035,7 +1100,9 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt if (field_info.value_is_ordered) { if (index++) - os << ", "; + os << ", "; + else + os << " SET "; os << "`" << field_info.table_name << "_index_" @@ -1053,6 +1120,8 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt continue; if (index++) os << ", "; + else + os << " SET "; os << "`" << field_info.field_name << "`=" @@ -1070,6 +1139,8 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt { if (index++) os << ", "; + else + os << " SET "; os << "`__type`=?__type?"; } @@ -1088,7 +1159,7 @@ std::string build_create_update_query(const table_t& table, const filter_t* filt << key_info.convert_to_close; } - return index == 0 + return index == 0 && !(table.primary_key_field->value_is_auto_incremented && !is_update) ? std::string() : os.str(); } @@ -1118,17 +1189,16 @@ std::string table_t::execute_create_update( /* primary key */ assert(primary_key_field); - if ( !primary_key_field->value_is_auto_incremented - && !is_update) + if (is_update) + { + primary_key = get_primary_key(context); + } + else if (!primary_key_field->value_is_auto_incremented) { primary_key = primary_key_field->generate_value(context.connection); if (statement) statement->set(index, primary_key); ++index; } - else - { - primary_key = get_primary_key(context); - } /* base_key */ if ( base_table @@ -1150,9 +1220,13 @@ std::string table_t::execute_create_update( for (auto& ptr : foreign_table_one_fields) { assert(ptr); + assert(ptr->referenced_table); + auto& field = *ptr; if (is_update && filter.is_excluded(field)) continue; + if (field.referenced_table->is_used_in_container) + continue; /* insert/update dataset */ value_t key = field.foreign_create_update(context); @@ -1288,7 +1362,7 @@ std::string table_t::execute_create_update( } /* foreign table many fields */ - for (auto& ptr : foreign_table_many_fields) + for (auto& ptr : foreign_table_fields) { assert(ptr); assert(ptr->referenced_table); @@ -1296,6 +1370,9 @@ std::string table_t::execute_create_update( auto& field = *ptr; auto& ref_table = *field.referenced_table; + if (!ref_table.is_used_in_container) + continue; + if ( is_update && ( filter.is_excluded(field) || filter.is_excluded(ref_table))) @@ -1558,7 +1635,8 @@ void table_t::execute_foreign_many_delete(const base_context& context) const std::string table_t::get_primary_key(const data_context& context) const { assert(primary_key_field); - if (primary_key_field->is_default(context)) + if ( primary_key_field->is_default(context) + && base_table) { auto key = get_key_from_base(context); primary_key_field->set(context, key); @@ -1573,7 +1651,9 @@ std::string table_t::get_primary_key(const data_context& context) const std::string table_t::get_key_from_base(const data_context& context) const { if (!base_table) + { throw exception(std::string("table has no base table: ") + table_name); + } auto& statement = get_statement_key_from_base(); auto base_key = base_table->get_primary_key(context); statement.set(0, base_key); diff --git a/test/cpphibernate_create.cpp b/test/cpphibernate_create.cpp index b2b0517..37b74da 100644 --- a/test/cpphibernate_create.cpp +++ b/test/cpphibernate_create.cpp @@ -517,4 +517,63 @@ TEST(CppHibernateTests, create_dummy_owner) ::cppmariadb::connection connection(reinterpret_cast(0x1111)); auto context = make_context(test_schema, connection); context.create(d); +} + +TEST(CppHibernateTests, create_double_usage) +{ + StrictMock mock; + + expect_query(mock, "START TRANSACTION"); + expect_query(mock, "INSERT INTO `tbl_double_usage`", + result_id(1)); + expect_query(mock, "INSERT INTO " + "`tbl_double_usage_item` " + "SET " + "`tbl_double_usage_id_single_item`='X1X', " + "`tbl_double_usage_id_multiple_items`=null, " + "`tbl_double_usage_index_multiple_items`='X0X', " + "`data`='X123X'", + result_id(1001)); + expect_query(mock, "INSERT INTO " + "`tbl_double_usage_item` " + "SET " + "`tbl_double_usage_id_single_item`=null, " + "`tbl_double_usage_id_multiple_items`='X1X', " + "`tbl_double_usage_index_multiple_items`='X0X', " + "`data`='X456X'", + result_id(1002)); + expect_query(mock, "INSERT INTO " + "`tbl_double_usage_item` " + "SET " + "`tbl_double_usage_id_single_item`=null, " + "`tbl_double_usage_id_multiple_items`='X1X', " + "`tbl_double_usage_index_multiple_items`='X1X', " + "`data`='X789X'", + result_id(1003)); + expect_query(mock, "COMMIT"); + + EXPECT_CALL( + mock, + mysql_real_escape_string(reinterpret_cast(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(0x1111))); + + double_usage d; + d.single_item.reset(new double_usage_item(123)); + d.multiple_items.emplace_back(456); + d.multiple_items.emplace_back(789); + + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + context.create(d); + + EXPECT_EQ(d.id, 1); + EXPECT_EQ(d.single_item->id, 1001); + EXPECT_EQ(d.multiple_items[0].id, 1002); + EXPECT_EQ(d.multiple_items[1].id, 1003); } \ No newline at end of file diff --git a/test/cpphibernate_destroy.cpp b/test/cpphibernate_destroy.cpp index 58aaf4d..6834cb8 100644 --- a/test/cpphibernate_destroy.cpp +++ b/test/cpphibernate_destroy.cpp @@ -247,4 +247,46 @@ TEST(CppHibernateTests, destroy_derived3) ::cppmariadb::connection connection(reinterpret_cast(0x1111)); auto context = make_context(test_schema, connection); context.destroy(static_cast(d3)); +} + +TEST(CppHibernateTests, destroy_double_usage) +{ + StrictMock mock; + + expect_query(mock, "START TRANSACTION"); + expect_query(mock, "DELETE " + "`tbl_double_usage`, " + "`T0` " + "FROM " + "`tbl_double_usage` " + "LEFT JOIN " + "`tbl_double_usage_item` AS `T0` ON `tbl_double_usage`.`tbl_double_usage_id`=`T0`.`tbl_double_usage_id_single_item` " + "WHERE " + "`tbl_double_usage`.`tbl_double_usage_id`='X1X'"); + expect_query(mock, "DELETE " + "`tbl_double_usage_item` " + "FROM " + "`tbl_double_usage_item` " + "WHERE " + "(`tbl_double_usage_id_single_item` IS NULL) " + "AND (`tbl_double_usage_id_multiple_items` IS NULL)"); + expect_query(mock, "COMMIT"); + + EXPECT_CALL( + mock, + mysql_real_escape_string(reinterpret_cast(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(0x1111))); + + double_usage d; + d.id = 1; + + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + context.destroy(d); } \ No newline at end of file diff --git a/test/cpphibernate_init.cpp b/test/cpphibernate_init.cpp index 666f430..f86e94a 100644 --- a/test/cpphibernate_init.cpp +++ b/test/cpphibernate_init.cpp @@ -173,6 +173,30 @@ TEST(CppHibernateTests, init) "ENGINE = InnoDB\n" "DEFAULT CHARACTER SET = utf8"); + expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_double_usage_item`\n" + "(\n" + " `tbl_double_usage_item_id` INT UNSIGNED NOT NULL,\n" + " `tbl_double_usage_id_single_item` INT NULL DEFAULT NULL,\n" + " `tbl_double_usage_id_multiple_items` INT NULL DEFAULT NULL,\n" + " `tbl_double_usage_index_multiple_items` INT UNSIGNED NOT NULL,\n" + " `data` INT NOT NULL,\n" + " PRIMARY KEY ( `tbl_double_usage_item_id` ),\n" + " UNIQUE INDEX `index_tbl_double_usage_item_id` ( `tbl_double_usage_item_id` ASC ),\n" + " INDEX `index_tbl_double_usage_id_single_item` ( `tbl_double_usage_id_single_item` ASC ),\n" + " INDEX `index_tbl_double_usage_id_multiple_items` ( `tbl_double_usage_id_multiple_items` ASC )\n" + ")\n" + "ENGINE = InnoDB\n" + "DEFAULT CHARACTER SET = utf8"); + + expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_double_usage`\n" + "(\n" + " `tbl_double_usage_id` INT NOT NULL,\n" + " PRIMARY KEY ( `tbl_double_usage_id` ),\n" + " UNIQUE INDEX `index_tbl_double_usage_id` ( `tbl_double_usage_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" @@ -233,6 +257,18 @@ TEST(CppHibernateTests, init) " ON DELETE SET NULL\n" " ON UPDATE NO ACTION"); + expect_query(mock, "ALTER TABLE `tbl_double_usage_item`\n" + " ADD CONSTRAINT `fk_tbl_double_usage_item_to_tbl_double_usage_id_single_item`\n" + " FOREIGN KEY IF NOT EXISTS (`tbl_double_usage_id_single_item`)\n" + " REFERENCES `test`.`tbl_double_usage` (`tbl_double_usage_id`)\n" + " ON DELETE SET NULL\n" + " ON UPDATE NO ACTION,\n" + " ADD CONSTRAINT `fk_tbl_double_usage_item_to_tbl_double_usage_id_multiple_items`\n" + " FOREIGN KEY IF NOT EXISTS (`tbl_double_usage_id_multiple_items`)\n" + " REFERENCES `test`.`tbl_double_usage` (`tbl_double_usage_id`)\n" + " ON DELETE SET NULL\n" + " ON UPDATE NO ACTION"); + expect_query(mock, "COMMIT"); EXPECT_CALL( diff --git a/test/cpphibernate_read.cpp b/test/cpphibernate_read.cpp index ef100d3..bac682a 100644 --- a/test/cpphibernate_read.cpp +++ b/test/cpphibernate_read.cpp @@ -917,4 +917,67 @@ TEST(CppHibernateTests, read_dummy_owner) EXPECT_EQ(d.dummies[0].data, 123); EXPECT_EQ(d.dummies[1].data, 456); EXPECT_EQ(d.dummies[2].data, 789); +} + +TEST(CppHibernateTests, read_double_usage) +{ + StrictMock mock; + + expect_query(mock, "START TRANSACTION"); + expect_query(mock, "SELECT " + "`tbl_double_usage`.`tbl_double_usage_id`, " + "`T0`.`tbl_double_usage_item_id`, " + "`T0`.`data` " + "FROM " + "`tbl_double_usage` " + "LEFT JOIN " + "`tbl_double_usage_item` AS `T0` ON `tbl_double_usage`.`tbl_double_usage_id`=`T0`.`tbl_double_usage_id_single_item` " + "WHERE " + "(`tbl_double_usage`.`tbl_double_usage_id`='X1X') ", + result_used({ + { "1", "1001", "123" } + })); + expect_query(mock, "SELECT " + "`tbl_double_usage_item`.`tbl_double_usage_item_id`, " + "`tbl_double_usage_item`.`data` " + "FROM " + "`tbl_double_usage_item` " + "WHERE " + "(`tbl_double_usage_item`.`tbl_double_usage_id_multiple_items`='X1X') " + "ORDER BY " + "`tbl_double_usage_item`.`tbl_double_usage_index_multiple_items` ASC ", + result_used({ + { "1002", "456" }, + { "1003", "789" } + })); + expect_query(mock, "COMMIT"); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(0x1111))); + + EXPECT_CALL( + mock, + mysql_real_escape_string(reinterpret_cast(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + double_usage d; + d.id = 1; + + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + + context.read(d); + + EXPECT_EQ (d.id, 1); + ASSERT_TRUE(d.single_item); + ASSERT_EQ (d.multiple_items.size(), 2); + EXPECT_EQ (d.single_item->id, 1001); + EXPECT_EQ (d.single_item->data, 123); + EXPECT_EQ (d.multiple_items[0].id, 1002); + EXPECT_EQ (d.multiple_items[0].data, 456); + EXPECT_EQ (d.multiple_items[1].id, 1003); + EXPECT_EQ (d.multiple_items[1].data, 789); } \ No newline at end of file diff --git a/test/cpphibernate_update.cpp b/test/cpphibernate_update.cpp index 7ba371c..e4c859c 100644 --- a/test/cpphibernate_update.cpp +++ b/test/cpphibernate_update.cpp @@ -706,6 +706,95 @@ TEST(CppHibernateTests, update_dummy_owner) d.dummies.emplace_back(456); d.dummies.emplace_back(789); + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + context.update(d); +} + +TEST(CppHibernateTests, update_double_usage) +{ + StrictMock mock; + + expect_query(mock, "START TRANSACTION"); + expect_query(mock, "UPDATE " + "`tbl_double_usage_item` " + "SET " + "`tbl_double_usage_id_single_item`=NULL " + "WHERE " + "`tbl_double_usage_id_single_item`='X1X'"); + expect_query(mock, "UPDATE " + "`tbl_double_usage_item` " + "SET " + "`tbl_double_usage_id_single_item`='X1X', " + "`data`='X123X' " + "WHERE " + "`tbl_double_usage_item_id`='X1001X'", + result_affected_rows(1)); + expect_query(mock, "DELETE " + "`tbl_double_usage_item` " + "FROM " + "`tbl_double_usage_item` " + "WHERE " + "(`tbl_double_usage_id_single_item` IS NULL) " + "AND (`tbl_double_usage_id_multiple_items` IS NULL)"); + + expect_query(mock, "UPDATE " + "`tbl_double_usage_item` " + "SET " + "`tbl_double_usage_id_multiple_items`=NULL, " + "`tbl_double_usage_index_multiple_items`=0 " + "WHERE " + "`tbl_double_usage_id_multiple_items`='X1X'"); + expect_query(mock, "UPDATE " + "`tbl_double_usage_item` " + "SET " + "`tbl_double_usage_id_multiple_items`='X1X', " + "`tbl_double_usage_index_multiple_items`='X0X', " + "`data`='X456X' " + "WHERE " + "`tbl_double_usage_item_id`='X1002X'", + result_affected_rows(1)); + expect_query(mock, "UPDATE " + "`tbl_double_usage_item` " + "SET " + "`tbl_double_usage_id_multiple_items`='X1X', " + "`tbl_double_usage_index_multiple_items`='X1X', " + "`data`='X789X' " + "WHERE " + "`tbl_double_usage_item_id`='X1003X'", + result_affected_rows(1)); + expect_query(mock, "DELETE " + "`tbl_double_usage_item` " + "FROM " + "`tbl_double_usage_item` " + "WHERE " + "(`tbl_double_usage_id_single_item` IS NULL) " + "AND (`tbl_double_usage_id_multiple_items` IS NULL)"); + expect_query(mock, "COMMIT"); + + EXPECT_CALL( + mock, + mysql_real_escape_string(reinterpret_cast(0x1111), _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); + + EXPECT_CALL( + mock, + mysql_close( + reinterpret_cast(0x1111))); + + double_usage d; + d.id = 1; + d.single_item.reset(new double_usage_item()); + d.single_item->id = 1001; + d.single_item->data = 123; + d.multiple_items.emplace_back(); + d.multiple_items.back().id = 1002; + d.multiple_items.back().data = 456; + d.multiple_items.emplace_back(); + d.multiple_items.back().id = 1003; + d.multiple_items.back().data = 789; + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); auto context = make_context(test_schema, connection); context.update(d); diff --git a/test/test_helper.h b/test/test_helper.h index c35c52e..18b2360 100644 --- a/test/test_helper.h +++ b/test/test_helper.h @@ -38,14 +38,16 @@ struct result_data bool is_stored; ssize_t affected_rows; + ssize_t return_id; data_type data; interal_data_vector internal_data; template - result_data(T_data&& p_data, bool p_is_stored, ssize_t p_affected_rows) + result_data(T_data&& p_data, bool p_is_stored, ssize_t p_affected_rows, ssize_t p_return_id) : data (std::forward(p_data)) , is_stored (p_is_stored) , affected_rows (p_affected_rows) + , return_id (p_return_id) { internal_data.resize(data.size()); for (size_t i = 0; i < data.size(); ++i) @@ -78,14 +80,17 @@ inline const result_data::data_type& empty_result_data() template inline decltype(auto) result_stored(T_data&& data = empty_result_data(), ssize_t affected_rows = -1) - { return result_data(std::forward(data), true, affected_rows); } + { return result_data(std::forward(data), true, affected_rows, -1); } template inline decltype(auto) result_used(T_data&& data = empty_result_data(), ssize_t affected_rows = -1) - { return result_data(std::forward(data), false, affected_rows); } + { return result_data(std::forward(data), false, affected_rows, -1); } inline decltype(auto) result_affected_rows(ssize_t affected_rows) - { return result_data(empty_result_data(), true, affected_rows); } + { return result_data(empty_result_data(), true, affected_rows, -1); } + +inline decltype(auto) result_id(ssize_t id) + { return result_data(empty_result_data(), true, -1, id); } template inline void expect_query(T_mock& mock, const std::string& query, T_result&& result) @@ -126,6 +131,14 @@ inline void expect_query(T_mock& mock, const std::string& query, T_result&& resu .InSequence(mock.sequence) .WillOnce(::testing::Return(static_cast(res.affected_rows))); } + else if (res.return_id >= 0) + { + EXPECT_CALL( + mock, + mysql_insert_id(reinterpret_cast(0x1111))) + .InSequence(mock.sequence) + .WillOnce(::testing::Return(static_cast(res.return_id))); + } if (!res.data.empty()) { diff --git a/test/test_schema.h b/test/test_schema.h index 253d148..bc77cb7 100644 --- a/test/test_schema.h +++ b/test/test_schema.h @@ -108,6 +108,23 @@ struct dummy_owner std::vector dummies; }; +struct double_usage_item +{ + unsigned int id { 0 }; + int data { 0 }; + + double_usage_item(int p_data = 0) + : data(p_data) + { } +}; + +struct double_usage +{ + int id { 0 }; + std::unique_ptr single_item; + std::vector multiple_items; +}; + constexpr decltype(auto) test_schema = cpphibernate_make_schema( test, cpphibernate_make_table_name( @@ -187,5 +204,20 @@ constexpr decltype(auto) test_schema = cpphibernate_make_schema( 15, cpphibernate_make_id (&dummy_owner::id), cpphibernate_make_field (dummy_owner, dummies) + ), + cpphibernate_make_table_name( + tbl_double_usage_item, + double_usage_item, + 16, + cpphibernate_make_id (&double_usage_item::id), + cpphibernate_make_field (double_usage_item, data) + ), + cpphibernate_make_table_name( + tbl_double_usage, + double_usage, + 17, + cpphibernate_make_id (&double_usage::id), + cpphibernate_make_field (double_usage, single_item), + cpphibernate_make_field (double_usage, multiple_items) ) ); \ No newline at end of file