Browse Source

* 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

master
bergmann 5 years ago
parent
commit
424825c616
15 changed files with 532 additions and 84 deletions
  1. +0
    -8
      include/cpphibernate/driver/mariadb/helper/key_properties.h
  2. +4
    -4
      include/cpphibernate/driver/mariadb/helper/type_properties.h
  3. +3
    -2
      include/cpphibernate/driver/mariadb/schema/field.h
  4. +9
    -0
      include/cpphibernate/driver/mariadb/schema/field.inl
  5. +6
    -5
      include/cpphibernate/driver/mariadb/schema/table.h
  6. +16
    -11
      src/cpphibernate/driver/mariadb/schema/field.cpp
  7. +32
    -6
      src/cpphibernate/driver/mariadb/schema/schema.cpp
  8. +124
    -44
      src/cpphibernate/driver/mariadb/schema/table.cpp
  9. +59
    -0
      test/cpphibernate_create.cpp
  10. +42
    -0
      test/cpphibernate_destroy.cpp
  11. +36
    -0
      test/cpphibernate_init.cpp
  12. +63
    -0
      test/cpphibernate_read.cpp
  13. +89
    -0
      test/cpphibernate_update.cpp
  14. +17
    -4
      test/test_helper.h
  15. +32
    -0
      test/test_schema.h

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

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


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

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


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

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


+ 9
- 0
include/cpphibernate/driver/mariadb/schema/field.inl View File

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


+ 6
- 5
include/cpphibernate/driver/mariadb/schema/table.h View File

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


+ 16
- 11
src/cpphibernate/driver/mariadb/schema/field.cpp View File

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


+ 32
- 6
src/cpphibernate/driver/mariadb/schema/schema.cpp View File

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


+ 124
- 44
src/cpphibernate/driver/mariadb/schema/table.cpp View File

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


+ 59
- 0
test/cpphibernate_create.cpp View File

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

+ 42
- 0
test/cpphibernate_destroy.cpp View File

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

+ 36
- 0
test/cpphibernate_init.cpp View File

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


+ 63
- 0
test/cpphibernate_read.cpp View File

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

+ 89
- 0
test/cpphibernate_update.cpp View File

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

+ 17
- 4
test/test_helper.h View File

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


+ 32
- 0
test/test_schema.h View File

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

Loading…
Cancel
Save