@@ -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(); } | |||
@@ -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<> | |||
@@ -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; | |||
}; | |||
@@ -64,6 +64,15 @@ beg_namespace_cpphibernate_driver_mariadb | |||
/* primary_key_field_t */ | |||
template<typename T_field> | |||
void primary_key_field_t<T_field> | |||
::update() | |||
{ | |||
base_type::update(); | |||
this->value_is_auto_incremented = key_props::auto_generated::value; | |||
} | |||
template<typename T_field> | |||
bool primary_key_field_t<T_field> | |||
::is_default(const data_context& context) const | |||
@@ -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<size_t> 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<const table_t*> derived_tables; | |||
const field_t* primary_key_field { nullptr }; | |||
const field_t* primary_key_field { nullptr }; | |||
std::vector<const field_t*> foreign_key_fields; | |||
std::vector<const field_t*> foreign_table_fields; | |||
std::vector<const field_t*> foreign_table_one_fields; | |||
@@ -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 | |||
@@ -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<bool>(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<bool>(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 | |||
@@ -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); | |||
@@ -517,4 +517,63 @@ TEST(CppHibernateTests, create_dummy_owner) | |||
::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||
auto context = make_context<driver::mariadb>(test_schema, connection); | |||
context.create(d); | |||
} | |||
TEST(CppHibernateTests, create_double_usage) | |||
{ | |||
StrictMock<mariadb_mock> 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<MYSQL*>(0x1111), _, _, _)) | |||
.Times(AnyNumber()) | |||
.WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||
EXPECT_CALL( | |||
mock, | |||
mysql_close( | |||
reinterpret_cast<MYSQL*>(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<MYSQL*>(0x1111)); | |||
auto context = make_context<driver::mariadb>(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); | |||
} |
@@ -247,4 +247,46 @@ TEST(CppHibernateTests, destroy_derived3) | |||
::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||
auto context = make_context<driver::mariadb>(test_schema, connection); | |||
context.destroy(static_cast<derived2&>(d3)); | |||
} | |||
TEST(CppHibernateTests, destroy_double_usage) | |||
{ | |||
StrictMock<mariadb_mock> 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<MYSQL*>(0x1111), _, _, _)) | |||
.Times(AnyNumber()) | |||
.WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||
EXPECT_CALL( | |||
mock, | |||
mysql_close( | |||
reinterpret_cast<MYSQL*>(0x1111))); | |||
double_usage d; | |||
d.id = 1; | |||
::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||
auto context = make_context<driver::mariadb>(test_schema, connection); | |||
context.destroy(d); | |||
} |
@@ -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( | |||
@@ -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<mariadb_mock> 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<MYSQL*>(0x1111))); | |||
EXPECT_CALL( | |||
mock, | |||
mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x1111), _, _, _)) | |||
.Times(AnyNumber()) | |||
.WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||
double_usage d; | |||
d.id = 1; | |||
::cppmariadb::connection connection(reinterpret_cast<MYSQL*>(0x1111)); | |||
auto context = make_context<driver::mariadb>(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); | |||
} |
@@ -706,6 +706,95 @@ TEST(CppHibernateTests, update_dummy_owner) | |||
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); | |||
} | |||
TEST(CppHibernateTests, update_double_usage) | |||
{ | |||
StrictMock<mariadb_mock> 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<MYSQL*>(0x1111), _, _, _)) | |||
.Times(AnyNumber()) | |||
.WillRepeatedly(WithArgs<1, 2, 3>(EscapeString())); | |||
EXPECT_CALL( | |||
mock, | |||
mysql_close( | |||
reinterpret_cast<MYSQL*>(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<MYSQL*>(0x1111)); | |||
auto context = make_context<driver::mariadb>(test_schema, connection); | |||
context.update(d); |
@@ -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<typename T_data> | |||
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<T_data>(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<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); } | |||
{ return result_data(std::forward<T_data>(data), true, affected_rows, -1); } | |||
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); } | |||
{ return result_data(std::forward<T_data>(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<typename T_mock, typename T_result> | |||
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<unsigned long long>(res.affected_rows))); | |||
} | |||
else if (res.return_id >= 0) | |||
{ | |||
EXPECT_CALL( | |||
mock, | |||
mysql_insert_id(reinterpret_cast<MYSQL*>(0x1111))) | |||
.InSequence(mock.sequence) | |||
.WillOnce(::testing::Return(static_cast<unsigned long long>(res.return_id))); | |||
} | |||
if (!res.data.empty()) | |||
{ | |||
@@ -108,6 +108,23 @@ struct dummy_owner | |||
std::vector<dummy_id> 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<double_usage_item> single_item; | |||
std::vector<double_usage_item> 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) | |||
) | |||
); |