From 178a4711e91e1b17d9e24d25fa957e06d68028cb Mon Sep 17 00:00:00 2001 From: bergmann Date: Tue, 4 Sep 2018 20:38:12 +0200 Subject: [PATCH] * implemented read method of mariadb driver for single foreign tables --- .../driver/mariadb/helper/context.h | 2 + .../driver/mariadb/schema/field.h | 16 +- .../driver/mariadb/schema/field.inl | 36 ++++ src/driver/mariadb/schema/field.cpp | 3 +- src/driver/mariadb/schema/schema.cpp | 18 +- src/driver/mariadb/schema/table.cpp | 120 +++++++++-- test/cpphibernate_init.cpp | 18 +- test/cpphibernate_read.cpp | 190 +++++++++++++++--- 8 files changed, 334 insertions(+), 69 deletions(-) diff --git a/include/cpphibernate/driver/mariadb/helper/context.h b/include/cpphibernate/driver/mariadb/helper/context.h index c3551c1..85fb45e 100644 --- a/include/cpphibernate/driver/mariadb/helper/context.h +++ b/include/cpphibernate/driver/mariadb/helper/context.h @@ -171,5 +171,7 @@ beg_namespace_cpphibernate_driver_mariadb { throw misc::hibernate_exception("read_context::finish_intern is not implemented!"); } }; + using read_context_ptr = std::unique_ptr; + } end_namespace_cpphibernate_driver_mariadb \ No newline at end of file diff --git a/include/cpphibernate/driver/mariadb/schema/field.h b/include/cpphibernate/driver/mariadb/schema/field.h index 87869b7..5365dde 100644 --- a/include/cpphibernate/driver/mariadb/schema/field.h +++ b/include/cpphibernate/driver/mariadb/schema/field.h @@ -74,7 +74,10 @@ beg_namespace_cpphibernate_driver_mariadb virtual void update (); /* CRUD */ - virtual value_t foreign_create_update (const create_update_context& context) const; + using read_context_ptr = std::unique_ptr; + + virtual value_t foreign_create_update (const create_update_context& context) const; + virtual read_context_ptr foreign_read (const read_context& context, const value_t& value) const; /* properties */ virtual value_t get (const data_context& context) const; @@ -129,7 +132,7 @@ beg_namespace_cpphibernate_driver_mariadb virtual void update () override; virtual value_t get (const data_context& context) const override; - virtual void set (const data_context& context, const value_t&) const override; + virtual void set (const data_context& context, const value_t& value) const override; }; /* primary_key_field_t */ @@ -168,13 +171,16 @@ beg_namespace_cpphibernate_driver_mariadb : public simple_field_t { public: - using base_type = simple_field_t; - using dataset_type = typename base_type::dataset_type; + using base_type = simple_field_t; + using value_type = typename base_type::value_type; + using real_value_type = typename base_type::real_value_type; + using dataset_type = typename base_type::dataset_type; using base_type::base_type; public: - virtual value_t foreign_create_update(const create_update_context& context) const override; + virtual value_t foreign_create_update(const create_update_context& context) const override; + virtual read_context_ptr foreign_read (const read_context& context, const value_t& value) const override; }; } diff --git a/include/cpphibernate/driver/mariadb/schema/field.inl b/include/cpphibernate/driver/mariadb/schema/field.inl index dd66fc4..0a9b083 100644 --- a/include/cpphibernate/driver/mariadb/schema/field.inl +++ b/include/cpphibernate/driver/mariadb/schema/field.inl @@ -87,6 +87,42 @@ beg_namespace_cpphibernate_driver_mariadb false); } + template + read_context_ptr foreign_table_field_t + ::foreign_read(const read_context& context, const value_t& value) const + { + using is_nullable_type = misc::is_nullable; + return hana::eval_if( + is_nullable_type { }, + [&](auto _) { + auto& dataset = _(context).template get(); + auto& member = this->field.getter(dataset); + using nullable_helper_type = misc::nullable_helper>; + if (value.has_value()) + { + auto& new_dataset = nullable_helper_type::set(member, real_value_type { }); + auto new_context = change_context(context, new_dataset); + return std::make_unique(new_context); + } + else + { + nullable_helper_type::clear(member); + return read_context_ptr { }; + } + }, + [&](auto _) { + if (!value.has_value()) + { + throw misc::hibernate_exception(std::string("excepted value for field ") + + this->table_name + "." + this->field_name + " but received null!"); + } + auto& dataset = context.get(); + auto& member = this->field.getter(dataset); + auto new_context = change_context(context, member); + return std::make_unique(new_context); + }); + } + namespace __impl { diff --git a/src/driver/mariadb/schema/field.cpp b/src/driver/mariadb/schema/field.cpp index 771cef2..091571b 100644 --- a/src/driver/mariadb/schema/field.cpp +++ b/src/driver/mariadb/schema/field.cpp @@ -149,7 +149,8 @@ void field_t::update() /* CRUD */ -throw_not_implemented(value_t, foreign_create_update, const create_update_context&) +throw_not_implemented(value_t, foreign_create_update, const create_update_context&) +throw_not_implemented(read_context_ptr, foreign_read, const read_context&, const value_t&) /* properties */ diff --git a/src/driver/mariadb/schema/schema.cpp b/src/driver/mariadb/schema/schema.cpp index a4a891b..d4b8fad 100644 --- a/src/driver/mariadb/schema/schema.cpp +++ b/src/driver/mariadb/schema/schema.cpp @@ -203,13 +203,17 @@ void schema_t::init(const init_context& context) const " CONTAINS SQL\n" " SQL SECURITY INVOKER\n" "RETURN\n" - " LCASE(CONCAT_WS('-',\n" - " HEX(SUBSTR(_bin, 13, 4)),\n" // time low - " HEX(SUBSTR(_bin, 11, 2)),\n" // time mid - " HEX(SUBSTR(_bin, 9, 2)),\n" // time high and version - " HEX(SUBSTR(_bin, 7, 2)),\n" // clock sequence - " HEX(SUBSTR(_bin, 1, 6))\n" // node id - " )\n" + " IF(\n" + " _bin IS NULL,\n" + " NULL,\n" + " LCASE(CONCAT_WS('-',\n" + " HEX(SUBSTR(_bin, 13, 4)),\n" // time low + " HEX(SUBSTR(_bin, 11, 2)),\n" // time mid + " HEX(SUBSTR(_bin, 9, 2)),\n" // time high and version + " HEX(SUBSTR(_bin, 7, 2)),\n" // clock sequence + " HEX(SUBSTR(_bin, 1, 6))\n" // node id + " )\n" + " )\n" ")"; exec_query(); diff --git a/src/driver/mariadb/schema/table.cpp b/src/driver/mariadb/schema/table.cpp index 6e48ecd..f4b2b60 100644 --- a/src/driver/mariadb/schema/table.cpp +++ b/src/driver/mariadb/schema/table.cpp @@ -10,6 +10,7 @@ #include using namespace ::utl; +using namespace ::cpphibernate; using namespace ::cpphibernate::driver::mariadb_impl; /* data_extractor_t */ @@ -20,6 +21,7 @@ struct data_extractor_t const read_context& _context; const ::cppmariadb::row& _row; + const filter_t& _filter; mutable size_t _index; data_extractor_t( @@ -29,6 +31,8 @@ struct data_extractor_t : _table (p_table) , _context (p_context) , _row (p_row) + , _filter (p_context.filter) + , _index (0) { } inline value_t get_value() const @@ -40,23 +44,26 @@ struct data_extractor_t return ret; } - inline void read_field(const field_t& field) const + inline void read_field(const field_t& field, const read_context* context) const { - field.set(_context, get_value()); + if (context) + { + field.set(*context, get_value()); + } ++_index; } - inline void read_table(const table_t& table) const + inline void read_table(const table_t& table, const read_context* context) const { if (table.base_table) - read_table(*table.base_table); + read_table(*table.base_table, context); if (_context.filter.is_excluded(table)) return; /* primary key */ assert(table.primary_key_field); - read_field(*table.primary_key_field); + read_field(*table.primary_key_field, context); /* data fields */ for (auto& ptr : table.data_fields) @@ -64,7 +71,27 @@ struct data_extractor_t assert(ptr); auto& field = *ptr; if (!_context.filter.is_excluded(field)) - read_field(field); + read_field(field, context); + } + + /* foreign table one */ + for (auto& ptr : table.foreign_table_one_fields) + { + assert(ptr); + assert(ptr->referenced_table); + + auto& field = *ptr; + auto& ref_table = *field.referenced_table; + + if ( _filter.is_excluded(field) + || _filter.is_excluded(ref_table)) + continue; + + read_context_ptr next_context; + if (context) + next_context = field.foreign_read(*context, get_value()); + + read_table(ref_table, next_context.get()); } } @@ -72,7 +99,9 @@ struct data_extractor_t { _index = 0; _context.emplace(); - read_table(_table); + read_table(_table, &_context); + if (_index != _row.size()) + throw misc::hibernate_exception("result was not completely read!"); } }; @@ -84,7 +113,8 @@ struct select_query_builder_t const filter_t& _filter; bool _is_dynamic; - size_t index { 0 }; + size_t alias_id { 0 }; + size_t index { 0 }; std::ostringstream os; std::ostringstream join; @@ -97,36 +127,45 @@ struct select_query_builder_t , _is_dynamic(p_is_dynamic) { } - inline void add_field(const field_t& field) + inline std::string make_alias(const table_t& table) + { return std::string("T") + std::to_string(alias_id++); } + + inline void add_field(const field_t& field, const std::string& alias) { if (index++) os << ", "; - os << "`" - << field.table_name + os << field.convert_from_open + << "`" + << alias << "`.`" << field.field_name - << "`"; + << "`" + << field.convert_from_close; } - inline bool add_table(const table_t& table, const std::string& prefix) + inline bool add_table(const table_t& table, const std::string& alias) { bool ret = false; if (table.base_table) { - auto tmp = add_table(*table.base_table, ""); + auto& base_table = *table.base_table; + auto base_alias = make_alias(base_table); + auto tmp = add_table(base_table, base_alias); if (tmp) { - assert(table.base_table->primary_key_field); - auto& base_key = *table.base_table->primary_key_field; + assert(base_table.primary_key_field); + auto& base_key = *base_table.primary_key_field; ret = true; join << " LEFT JOIN `" - << table.table_name + << base_table.table_name + << "` AS `" + << base_alias << "` ON `" - << table.table_name + << alias << "`.`" << base_key.field_name << "`=`" - << base_key.table_name + << base_alias << "`.`" << base_key.field_name << "`"; @@ -152,7 +191,7 @@ struct select_query_builder_t /* primary key */ assert(table.primary_key_field); - add_field(*table.primary_key_field); + add_field(*table.primary_key_field, alias); /* data fields */ for (auto& ptr : table.data_fields) @@ -160,7 +199,41 @@ struct select_query_builder_t assert(ptr); auto& field = *ptr; if (!_filter.is_excluded(field)) - add_field(field); + add_field(field, alias); + } + + /* foreign table one */ + for (auto& ptr : table.foreign_table_one_fields) + { + assert(ptr); + assert(ptr->referenced_table); + assert(ptr->referenced_table->primary_key_field); + + auto& field = *ptr; + auto& ref_table = *field.referenced_table; + auto& ref_key = *ref_table.primary_key_field; + + if ( _filter.is_excluded(field) + || _filter.is_excluded(ref_table)) + continue; + + auto new_alias = make_alias(ref_table); + add_table(ref_table, new_alias); + join << " LEFT JOIN `" + << ref_table.table_name + << "` AS `" + << new_alias + << "` ON `" + << alias + << "`.`" + << ref_key.table_name + << "_id_" + << field.field_name + << "`=`" + << new_alias + << "`.`" + << ref_key.field_name + << "`"; } return ret; @@ -169,9 +242,12 @@ struct select_query_builder_t inline std::string operator()() { os << "SELECT "; - add_table(_table, ""); + auto alias = make_alias(_table); + add_table(_table, alias); os << " FROM `" << _table.table_name + << "` AS `" + << alias << "`" << join.str() << " ?where! ?order! ?limit!"; diff --git a/test/cpphibernate_init.cpp b/test/cpphibernate_init.cpp index 48f0fce..021e5ab 100644 --- a/test/cpphibernate_init.cpp +++ b/test/cpphibernate_init.cpp @@ -39,13 +39,17 @@ TEST(CppHibernateTests, init) " CONTAINS SQL\n" " SQL SECURITY INVOKER\n" "RETURN\n" - " LCASE(CONCAT_WS('-',\n" - " HEX(SUBSTR(_bin, 13, 4)),\n" - " HEX(SUBSTR(_bin, 11, 2)),\n" - " HEX(SUBSTR(_bin, 9, 2)),\n" - " HEX(SUBSTR(_bin, 7, 2)),\n" - " HEX(SUBSTR(_bin, 1, 6))\n" - " )\n" + " IF(\n" + " _bin IS NULL,\n" + " NULL,\n" + " LCASE(CONCAT_WS('-',\n" + " HEX(SUBSTR(_bin, 13, 4)),\n" + " HEX(SUBSTR(_bin, 11, 2)),\n" + " HEX(SUBSTR(_bin, 9, 2)),\n" + " HEX(SUBSTR(_bin, 7, 2)),\n" + " HEX(SUBSTR(_bin, 1, 6))\n" + " )\n" + " )\n" ")"); expect_query(mock, "CREATE TABLE IF NOT EXISTS `tbl_test1`\n" diff --git a/test/cpphibernate_read.cpp b/test/cpphibernate_read.cpp index eadfb7c..8d4ea76 100644 --- a/test/cpphibernate_read.cpp +++ b/test/cpphibernate_read.cpp @@ -15,14 +15,14 @@ TEST(CppHibernateTests, read_test1) expect_query(mock, "START TRANSACTION"); expect_query(mock, "SELECT " - "`tbl_test1`.`tbl_test1_id`, " - "`tbl_test1`.`str_data`, " - "`tbl_test1`.`str64_data`, " - "`tbl_test1`.`u32_nullable`, " - "`tbl_test1`.`u32_ptr_u`, " - "`tbl_test1`.`u32_ptr_s` " + "BinToUuid(`T0`.`tbl_test1_id`), " + "`T0`.`str_data`, " + "`T0`.`str64_data`, " + "`T0`.`u32_nullable`, " + "`T0`.`u32_ptr_u`, " + "`T0`.`u32_ptr_s` " "FROM " - "`tbl_test1` ", + "`tbl_test1` AS `T0` ", result_used({ { "3d12697a-abb9-11e8-98d0-529269fb1459", "str_data of class `test1` object `t1`", "str64_data of class `test1` object `t1`", nullptr, "123", "456" } })); @@ -48,6 +48,7 @@ TEST(CppHibernateTests, read_test1) using namespace modifier; context.read(t1); + EXPECT_EQ (t1.id, uuid("3d12697a-abb9-11e8-98d0-529269fb1459")); EXPECT_EQ (t1.str_data, "str_data of class `test1` object `t1`"); EXPECT_EQ (t1.str64_data, "str64_data of class `test1` object `t1`"); EXPECT_FALSE(static_cast(t1.u32_nullable)); @@ -63,13 +64,13 @@ TEST(CppHibernateTests, read_test2) expect_query(mock, "START TRANSACTION"); expect_query(mock, "SELECT " - "`tbl_test2`.`tbl_test2_id`, " - "`tbl_test2`.`u8_data`, " - "`tbl_test2`.`i8_data`, " - "`tbl_test2`.`u16_data`, " - "`tbl_test2`.`i16_data` " + "BinToUuid(`T0`.`tbl_test2_id`), " + "`T0`.`u8_data`, " + "`T0`.`i8_data`, " + "`T0`.`u16_data`, " + "`T0`.`i16_data` " "FROM " - "`tbl_test2` ", + "`tbl_test2` AS `T0` ", result_used({ { "3d1270dc-abb9-11e8-98d0-529269fb1459", "1", "2", "3", "4" } })); @@ -94,10 +95,11 @@ TEST(CppHibernateTests, read_test2) test2 t2; context.read(t2, where(equal(test2_key_field, "3d1270dc-abb9-11e8-98d0-529269fb1459"))); - EXPECT_EQ(1, t2.u8_data); - EXPECT_EQ(2, t2.i8_data); - EXPECT_EQ(3, t2.u16_data); - EXPECT_EQ(4, t2.i16_data); + EXPECT_EQ(t2.id, uuid("3d1270dc-abb9-11e8-98d0-529269fb1459")); + EXPECT_EQ(t2.u8_data, 1); + EXPECT_EQ(t2.i8_data, 2); + EXPECT_EQ(t2.u16_data, 3); + EXPECT_EQ(t2.i16_data, 4); } TEST(CppHibernateTests, read_test3) @@ -106,13 +108,13 @@ TEST(CppHibernateTests, read_test3) expect_query(mock, "START TRANSACTION"); expect_query(mock, "SELECT " - "`tbl_test3`.`tbl_test3_id`, " - "`tbl_test3`.`u32_data`, " - "`tbl_test3`.`i32_data`, " - "`tbl_test3`.`u64_data`, " - "`tbl_test3`.`i64_data` " + "BinToUuid(`T0`.`tbl_test3_id`), " + "`T0`.`u32_data`, " + "`T0`.`i32_data`, " + "`T0`.`u64_data`, " + "`T0`.`i64_data` " "FROM " - "`tbl_test3` ", + "`tbl_test3` AS `T0` ", result_used({ { "3d12737a-abb9-11e8-98d0-529269fb1459", "5", "6", "7", "8" } })); @@ -137,8 +139,142 @@ TEST(CppHibernateTests, read_test3) context.read(t3); - EXPECT_EQ(5, t3.u32_data); - EXPECT_EQ(6, t3.i32_data); - EXPECT_EQ(7, t3.u64_data); - EXPECT_EQ(8, t3.i64_data); + EXPECT_EQ(t3.id, uuid("3d12737a-abb9-11e8-98d0-529269fb1459")); + EXPECT_EQ(t3.u32_data, 5); + EXPECT_EQ(t3.i32_data, 6); + EXPECT_EQ(t3.u64_data, 7); + EXPECT_EQ(t3.i64_data, 8); +} + +TEST(CppHibernateTests, read_derived1_static) +{ + StrictMock mock; + + expect_query(mock, "START TRANSACTION"); + expect_query(mock, "SELECT " + "BinToUuid(`T1`.`tbl_base_id`), " + "`T1`.`name`, " + "BinToUuid(`T0`.`tbl_derived1_id`), " + "`T0`.`enum_data`, " + "BinToUuid(`T2`.`tbl_test1_id`), " + "`T2`.`str_data`, " + "`T2`.`str64_data`, " + "`T2`.`u32_nullable`, " + "`T2`.`u32_ptr_u`, " + "`T2`.`u32_ptr_s` " + "FROM " + "`tbl_derived1` AS `T0` " + "LEFT JOIN " + "`tbl_base` AS `T1` ON `T0`.`tbl_base_id`=`T1`.`tbl_base_id` " + "LEFT JOIN " + "`tbl_test1` AS `T2` ON `T0`.`tbl_test1_id_test1_data`=`T2`.`tbl_test1_id` ", + result_used({ + { "3d12778a-abb9-11e8-98d0-529269fb1459", "derived1", "3d12758c-abb9-11e8-98d0-529269fb1459", "test2", "3d127988-abb9-11e8-98d0-529269fb1459", "str_data of class `test1` object `d1.test1_data`", "str64_data of class `test1` object `d1.test1_data`", "32", nullptr, "789" } + })); + 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))); + + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + + derived1 d1; + d1.derived1_id = uuid("3d12758c-abb9-11e8-98d0-529269fb1459"); + + context.read(d1); + + EXPECT_EQ (d1.id, uuid("3d12778a-abb9-11e8-98d0-529269fb1459")); + EXPECT_EQ (d1.derived1_id, uuid("3d12758c-abb9-11e8-98d0-529269fb1459")); + EXPECT_EQ (d1.name, "derived1"); + EXPECT_EQ (d1.enum_data, test_enum::test2); + EXPECT_EQ (d1.test1_data.str_data, "str_data of class `test1` object `d1.test1_data`"); + EXPECT_EQ (d1.test1_data.str64_data, "str64_data of class `test1` object `d1.test1_data`"); + ASSERT_TRUE (static_cast(d1.test1_data.u32_nullable)); + EXPECT_EQ (*d1.test1_data.u32_nullable, 32); + ASSERT_TRUE (static_cast(d1.test1_data.u32_ptr_s)); + EXPECT_EQ (*d1.test1_data.u32_ptr_s, 789); + EXPECT_FALSE(static_cast(d1.test1_data.u32_ptr_u)); +} + +TEST(CppHibernateTests, read_derived2_static) +{ + StrictMock mock; + + expect_query(mock, "START TRANSACTION"); + expect_query(mock, "SELECT " + "BinToUuid(`T1`.`tbl_base_id`), " + "`T1`.`name`, " + "BinToUuid(`T0`.`tbl_derived2_id`), " + "BinToUuid(`T2`.`tbl_test2_id`), " + "`T2`.`u8_data`, " + "`T2`.`i8_data`, " + "`T2`.`u16_data`, " + "`T2`.`i16_data`, " + "BinToUuid(`T3`.`tbl_test2_id`), " + "`T3`.`u8_data`, " + "`T3`.`i8_data`, " + "`T3`.`u16_data`, " + "`T3`.`i16_data`, " + "BinToUuid(`T4`.`tbl_test2_id`), " + "`T4`.`u8_data`, " + "`T4`.`i8_data`, " + "`T4`.`u16_data`, " + "`T4`.`i16_data` " + "FROM " + "`tbl_derived2` AS `T0` " + "LEFT JOIN " + "`tbl_base` AS `T1` ON `T0`.`tbl_base_id`=`T1`.`tbl_base_id` " + "LEFT JOIN " + "`tbl_test2` AS `T2` ON `T0`.`tbl_test2_id_test2_nullable`=`T2`.`tbl_test2_id` " + "LEFT JOIN " + "`tbl_test2` AS `T3` ON `T0`.`tbl_test2_id_test2_ptr_u`=`T3`.`tbl_test2_id` " + "LEFT JOIN " + "`tbl_test2` AS `T4` ON `T0`.`tbl_test2_id_test2_ptr_s`=`T4`.`tbl_test2_id` ", + result_used({ + { "3d127db6-abb9-11e8-98d0-529269fb1459", "derived2", "3d127bcc-abb9-11e8-98d0-529269fb1459", "3d1283a6-abb9-11e8-98d0-529269fb1459", "10", "11", "12", "13", "3d128522-abb9-11e8-98d0-529269fb1459", "20", "21", "22", "23", nullptr, nullptr, nullptr, nullptr, nullptr } + })); + 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))); + + ::cppmariadb::connection connection(reinterpret_cast(0x1111)); + auto context = make_context(test_schema, connection); + + derived2 d2; + d2.derived2_id = uuid("3d127bcc-abb9-11e8-98d0-529269fb1459"); + + context.read(d2); + + EXPECT_EQ (d2.id, uuid("3d127db6-abb9-11e8-98d0-529269fb1459")); + EXPECT_EQ (d2.derived2_id, uuid("3d127bcc-abb9-11e8-98d0-529269fb1459")); + EXPECT_EQ (d2.name, "derived2"); + ASSERT_TRUE (static_cast(d2.test2_nullable)); + EXPECT_EQ (d2.test2_nullable->u8_data, 10); + EXPECT_EQ (d2.test2_nullable->i8_data, 11); + EXPECT_EQ (d2.test2_nullable->u16_data, 12); + EXPECT_EQ (d2.test2_nullable->i16_data, 13); + ASSERT_TRUE (static_cast(d2.test2_ptr_u)); + EXPECT_EQ (d2.test2_ptr_u->u8_data, 20); + EXPECT_EQ (d2.test2_ptr_u->i8_data, 21); + EXPECT_EQ (d2.test2_ptr_u->u16_data, 22); + EXPECT_EQ (d2.test2_ptr_u->i16_data, 23); + EXPECT_FALSE(static_cast(d2.test2_ptr_s)); }