diff --git a/include/boost/mysql/metadata.hpp b/include/boost/mysql/metadata.hpp index b2af5f005..844810889 100644 --- a/include/boost/mysql/metadata.hpp +++ b/include/boost/mysql/metadata.hpp @@ -15,7 +15,9 @@ #include #include -#include +#include +#include +#include namespace boost { namespace mysql { @@ -97,7 +99,7 @@ class metadata * The returned reference is valid as long as `*this` is alive and hasn't been * assigned to or moved from. */ - string_view database() const noexcept { return schema_; } + string_view database() const noexcept { return substring(0, table_offset_); } /** * \brief Returns the name of the virtual table the column belongs to. @@ -114,7 +116,7 @@ class metadata * The returned reference is valid as long as `*this` is alive and hasn't been * assigned to or moved from. */ - string_view table() const noexcept { return table_; } + string_view table() const noexcept { return substring(table_offset_, org_table_offset_); } /** * \brief Returns the name of the physical table the column belongs to. @@ -131,7 +133,7 @@ class metadata * The returned reference is valid as long as `*this` is alive and hasn't been * assigned to or moved from. */ - string_view original_table() const noexcept { return org_table_; } + string_view original_table() const noexcept { return substring(org_table_offset_, name_offset_); } /** * \brief Returns the actual name of the column. @@ -149,7 +151,7 @@ class metadata * The returned reference is valid as long as `*this` is alive and hasn't been * assigned to or moved from. */ - string_view column_name() const noexcept { return name_; } + string_view column_name() const noexcept { return substring(name_offset_, org_name_offset_); } /** * \brief Returns the original (physical) name of the column. @@ -166,7 +168,7 @@ class metadata * The returned reference is valid as long as `*this` is alive and hasn't been * assigned to or moved from. */ - string_view original_column_name() const noexcept { return org_name_; } + string_view original_column_name() const noexcept { return substring(org_name_offset_, strings_.size()); } /** * \brief Returns the ID of the collation that fields belonging to this column use. @@ -265,34 +267,67 @@ class metadata bool is_set_to_now_on_update() const noexcept { return flag_set(detail::column_flags::on_update_now); } private: - std::string schema_; - std::string table_; // virtual table - std::string org_table_; // physical table - std::string name_; // virtual column name - std::string org_name_; // physical column name - std::uint16_t character_set_; - std::uint32_t column_length_; // maximum length of the field - column_type type_; // type of the column - std::uint16_t flags_; // Flags as defined in Column Definition Flags - std::uint8_t decimals_; // max shown decimal digits. 0x00 for int/static strings; 0x1f for - // dynamic strings, double, float + // All strings together: schema, table, org table, name, org name + std::vector strings_; + std::size_t table_offset_{}; // virtual table + std::size_t org_table_offset_{}; // physical table + std::size_t name_offset_{}; // virtual column name + std::size_t org_name_offset_{}; // physical column name + std::uint16_t character_set_{}; + std::uint32_t column_length_{}; // maximum length of the field + column_type type_{}; // type of the column + std::uint16_t flags_{}; // Flags as defined in Column Definition Flags + std::uint8_t decimals_{}; // max shown decimal digits. 0x00 for int/static strings; 0x1f for + // dynamic strings, double, float + + static std::size_t total_string_size(const detail::coldef_view& coldef) + { + return coldef.database.size() + coldef.table.size() + coldef.org_table.size() + coldef.name.size() + + coldef.org_name.size(); + } + + static char* copy_string(string_view from, char* to) + { + if (!from.empty()) + std::memcpy(to, from.data(), from.size()); + return to + from.size(); + } metadata(const detail::coldef_view& coldef, bool copy_strings) - : schema_(copy_strings ? coldef.database : string_view()), - table_(copy_strings ? coldef.table : string_view()), - org_table_(copy_strings ? coldef.org_table : string_view()), - name_(copy_strings ? coldef.name : string_view()), - org_name_(copy_strings ? coldef.org_name : string_view()), + : strings_(copy_strings ? total_string_size(coldef) : 0u, '\0'), character_set_(coldef.collation_id), column_length_(coldef.column_length), type_(coldef.type), flags_(coldef.flags), decimals_(coldef.decimals) { + if (copy_strings) + { + // Offsets + table_offset_ = coldef.database.size(); + org_table_offset_ = table_offset_ + coldef.table.size(); + name_offset_ = org_table_offset_ + coldef.org_table.size(); + org_name_offset_ = name_offset_ + coldef.name.size(); + + // Values. The packet points into a network packet, so it's guaranteed to + // not overlap with + char* it = strings_.data(); + it = copy_string(coldef.database, it); + it = copy_string(coldef.table, it); + it = copy_string(coldef.org_table, it); + it = copy_string(coldef.name, it); + it = copy_string(coldef.org_name, it); + BOOST_ASSERT(it == strings_.data() + strings_.size()); + } } bool flag_set(std::uint16_t flag) const noexcept { return flags_ & flag; } + string_view substring(std::size_t first, std::size_t last) const + { + return {strings_.data() + first, static_cast(last - first)}; + } + #ifndef BOOST_MYSQL_DOXYGEN friend struct detail::access; #endif diff --git a/test/unit/test/metadata.cpp b/test/unit/test/metadata.cpp index 374513408..a45f20577 100644 --- a/test/unit/test/metadata.cpp +++ b/test/unit/test/metadata.cpp @@ -8,12 +8,18 @@ #include #include #include +#include #include #include +#include #include +#include +#include +#include + #include "test_unit/create_meta.hpp" using namespace boost::mysql; @@ -21,72 +27,309 @@ using namespace boost::mysql::test; namespace collations = boost::mysql::mysql_collations; namespace column_flags = boost::mysql::detail::column_flags; -// TODO: these tests should be more targeted towards metadata's object responsibilities -// and less integration-y +namespace { BOOST_AUTO_TEST_SUITE(test_metadata) -BOOST_AUTO_TEST_CASE(int_primary_key) -{ - detail::coldef_view msg{ - "awesome", - "test_table", - "test_table", - "id", - "id", - collations::binary, - 11, - column_type::int_, - column_flags::pri_key | column_flags::auto_increment | column_flags::not_null, - 0, - }; - auto meta = detail::access::construct(msg, true); - - BOOST_TEST(meta.database() == "awesome"); - BOOST_TEST(meta.table() == "test_table"); - BOOST_TEST(meta.original_table() == "test_table"); - BOOST_TEST(meta.column_name() == "id"); - BOOST_TEST(meta.original_column_name() == "id"); - BOOST_TEST(meta.column_length() == 11u); - BOOST_TEST(meta.type() == column_type::int_); +// Creates a metadata object in dynamic memory, to help sanitizers detect memory errors +std::unique_ptr create_dynamic_meta(const detail::coldef_view& coldef, bool copy_strings) +{ + return std::unique_ptr(new metadata(detail::access::construct(coldef, copy_strings))); +} + +// Default constructing metadata objects should be well defined +BOOST_AUTO_TEST_CASE(default_constructor) +{ + // Setup + metadata meta; + + // Check + BOOST_TEST(meta.database() == ""); + BOOST_TEST(meta.table() == ""); + BOOST_TEST(meta.original_table() == ""); + BOOST_TEST(meta.column_name() == ""); + BOOST_TEST(meta.original_column_name() == ""); + BOOST_TEST(meta.column_collation() == 0u); + BOOST_TEST(meta.column_length() == 0u); + BOOST_TEST(meta.type() == column_type::tinyint); BOOST_TEST(meta.decimals() == 0u); - BOOST_TEST(meta.is_not_null()); + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +// Init ctor, copy_strings=false, there are strings to be copied in the packet +BOOST_AUTO_TEST_CASE(init_nocopy) +{ + // Setup + auto pack = meta_builder() + .database("db") + .table("tab") + .org_table("org_tab") + .name("field") + .org_name("org_field") + .collation_id(42) + .type(column_type::bigint) + .column_length(100) + .decimals(200) + .flags(column_flags::pri_key) + .build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Strings were not copied. The rest of the fields were copied + BOOST_TEST(meta.database() == ""); + BOOST_TEST(meta.table() == ""); + BOOST_TEST(meta.original_table() == ""); + BOOST_TEST(meta.column_name() == ""); + BOOST_TEST(meta.original_column_name() == ""); + BOOST_TEST(meta.column_collation() == 42u); + BOOST_TEST(meta.column_length() == 100u); + BOOST_TEST(meta.type() == column_type::bigint); + BOOST_TEST(meta.decimals() == 200u); + BOOST_TEST(!meta.is_not_null()); BOOST_TEST(meta.is_primary_key()); BOOST_TEST(!meta.is_unique_key()); BOOST_TEST(!meta.is_multiple_key()); BOOST_TEST(!meta.is_unsigned()); BOOST_TEST(!meta.is_zerofill()); - BOOST_TEST(meta.is_auto_increment()); + BOOST_TEST(!meta.is_auto_increment()); BOOST_TEST(!meta.has_no_default_value()); BOOST_TEST(!meta.is_set_to_now_on_update()); } -BOOST_AUTO_TEST_CASE(varchar_with_alias) +// Init ctor, copy_strings=false, strings in the packet are empty +BOOST_AUTO_TEST_CASE(init_nocopy_empty_strings) { - detail::coldef_view msg{ - "awesome", - "child", - "child_table", - "field_alias", - "field_varchar", - collations::utf8mb4_general_ci, - 765, - column_type::varchar, - 0, - 0, - }; - auto meta = detail::access::construct(msg, true); - - BOOST_TEST(meta.database() == "awesome"); - BOOST_TEST(meta.table() == "child"); - BOOST_TEST(meta.original_table() == "child_table"); - BOOST_TEST(meta.column_name() == "field_alias"); - BOOST_TEST(meta.original_column_name() == "field_varchar"); - BOOST_TEST(meta.column_length() == 765u); - BOOST_TEST(meta.type() == column_type::varchar); - BOOST_TEST(meta.decimals() == 0u); + // Setup + auto pack = meta_builder().database("").table("").org_table("").name("").org_name("").build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Strings are also empty, no UB happens + BOOST_TEST(meta.database() == ""); + BOOST_TEST(meta.table() == ""); + BOOST_TEST(meta.original_table() == ""); + BOOST_TEST(meta.column_name() == ""); + BOOST_TEST(meta.original_column_name() == ""); +} + +// String in dynamic storage, to help sanitizers catch memory bugs +struct dynamic_string +{ + std::unique_ptr storage; + std::size_t size; + + explicit dynamic_string(string_view from) : storage(new char[from.size()]), size(from.size()) + { + std::memcpy(storage.get(), from.data(), from.size()); + } + + string_view get() const { return string_view(storage.get(), size); } +}; + +// Init ctor, copy_strings=true, ensure that lifetime guarantees are met +BOOST_AUTO_TEST_CASE(init_copy_lifetimes) +{ + // Construct some strings in dynamic storage, to help catch memory bugs + dynamic_string db("db"), table("tab"), org_table("original_tab"), name("nam"), org_name("original_nam"); + + // Build + auto pack = meta_builder() + .database(db.get()) + .table(table.get()) + .org_table(org_table.get()) + .name(name.get()) + .org_name(org_name.get()) + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Destroy the original strings + db.storage.reset(); + table.storage.reset(); + org_table.storage.reset(); + name.storage.reset(); + org_name.storage.reset(); + + // Check + BOOST_TEST(meta.database() == "db"); + BOOST_TEST(meta.table() == "tab"); + BOOST_TEST(meta.original_table() == "original_tab"); + BOOST_TEST(meta.column_name() == "nam"); + BOOST_TEST(meta.original_column_name() == "original_nam"); +} + +// Init ctor, copy_strings=true, db is empty +BOOST_AUTO_TEST_CASE(init_copy_db_empty) +{ + // Setup + auto pack = meta_builder() + .database("") + .table("Some table value") + .org_table("Some other original table value") + .name("The name of the column") + .org_name("The name of the original column") + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Check + BOOST_TEST(meta.database() == ""); + BOOST_TEST(meta.table() == "Some table value"); + BOOST_TEST(meta.original_table() == "Some other original table value"); + BOOST_TEST(meta.column_name() == "The name of the column"); + BOOST_TEST(meta.original_column_name() == "The name of the original column"); +} + +// Same for table +BOOST_AUTO_TEST_CASE(init_copy_table_empty) +{ + // Setup + auto pack = meta_builder() + .database("Database value") + .table(string_view()) + .org_table("Some other original table value") + .name("The name of the column") + .org_name("The name of the original column") + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Check + BOOST_TEST(meta.database() == "Database value"); + BOOST_TEST(meta.table() == ""); + BOOST_TEST(meta.original_table() == "Some other original table value"); + BOOST_TEST(meta.column_name() == "The name of the column"); + BOOST_TEST(meta.original_column_name() == "The name of the original column"); +} + +// Same for original table +BOOST_AUTO_TEST_CASE(init_copy_org_table_empty) +{ + // Setup + auto pack = meta_builder() + .database("A database") + .table("Some table value") + .org_table(string_view()) + .name("The name of the column") + .org_name("The name of the original column") + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Check + BOOST_TEST(meta.database() == "A database"); + BOOST_TEST(meta.table() == "Some table value"); + BOOST_TEST(meta.original_table() == ""); + BOOST_TEST(meta.column_name() == "The name of the column"); + BOOST_TEST(meta.original_column_name() == "The name of the original column"); +} + +// Same for name +BOOST_AUTO_TEST_CASE(init_copy_name_empty) +{ + // Setup + auto pack = meta_builder() + .database("A database") + .table("Some table value") + .org_table("Some other original table value") + .name(string_view()) + .org_name("The name of the original column") + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Check + BOOST_TEST(meta.database() == "A database"); + BOOST_TEST(meta.table() == "Some table value"); + BOOST_TEST(meta.original_table() == "Some other original table value"); + BOOST_TEST(meta.column_name() == ""); + BOOST_TEST(meta.original_column_name() == "The name of the original column"); +} + +// Same for org_name +BOOST_AUTO_TEST_CASE(init_copy_org_name_empty) +{ + // Setup + auto pack = meta_builder() + .database("A database") + .table("Some table value") + .org_table("Some other original table value") + .name("The name of the column") + .org_name(string_view()) + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Check + BOOST_TEST(meta.database() == "A database"); + BOOST_TEST(meta.table() == "Some table value"); + BOOST_TEST(meta.original_table() == "Some other original table value"); + BOOST_TEST(meta.column_name() == "The name of the column"); + BOOST_TEST(meta.original_column_name() == ""); +} + +// Same, but many strings are empty +BOOST_AUTO_TEST_CASE(init_copy_many_empty) +{ + // Setup + auto pack = meta_builder() + .database("A database") + .table(string_view()) + .org_table(string_view()) + .name("The name of the column") + .org_name(string_view()) + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Check + BOOST_TEST(meta.database() == "A database"); + BOOST_TEST(meta.table() == ""); + BOOST_TEST(meta.original_table() == ""); + BOOST_TEST(meta.column_name() == "The name of the column"); + BOOST_TEST(meta.original_column_name() == ""); +} + +// Same, but all strings are empty +BOOST_AUTO_TEST_CASE(init_copy_all_empty) +{ + // Setup + auto pack = meta_builder() + .database(string_view()) + .table(string_view()) + .org_table(string_view()) + .name(string_view()) + .org_name(string_view()) + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Check + BOOST_TEST(meta.database() == ""); + BOOST_TEST(meta.table() == ""); + BOOST_TEST(meta.original_table() == ""); + BOOST_TEST(meta.column_name() == ""); + BOOST_TEST(meta.original_column_name() == ""); +} + +// copy=true does not affect how non string fields are processed +BOOST_AUTO_TEST_CASE(init_copy_nonstrings) +{ + // Setup + auto pack = meta_builder() + .collation_id(42) + .column_length(200) + .type(column_type::bigint) + .decimals(100) + .flags(column_flags::pri_key) + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Check + BOOST_TEST(meta.column_collation() == 42u); + BOOST_TEST(meta.column_length() == 200u); + BOOST_TEST(meta.type() == column_type::bigint); + BOOST_TEST(meta.decimals() == 100u); BOOST_TEST(!meta.is_not_null()); - BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(meta.is_primary_key()); BOOST_TEST(!meta.is_unique_key()); BOOST_TEST(!meta.is_multiple_key()); BOOST_TEST(!meta.is_unsigned()); @@ -96,32 +339,42 @@ BOOST_AUTO_TEST_CASE(varchar_with_alias) BOOST_TEST(!meta.is_set_to_now_on_update()); } -BOOST_AUTO_TEST_CASE(float_) +// Copy ctor handles strings correctly +BOOST_AUTO_TEST_CASE(copy_constructor) { - detail::coldef_view msg{ - "awesome", - "test_table", - "test_table", - "field_float", - "field_float", - collations::binary, - 12, - column_type::float_, - 0, - 31, - }; - auto meta = detail::access::construct(msg, true); - - BOOST_TEST(meta.database() == "awesome"); - BOOST_TEST(meta.table() == "test_table"); - BOOST_TEST(meta.original_table() == "test_table"); - BOOST_TEST(meta.column_name() == "field_float"); - BOOST_TEST(meta.original_column_name() == "field_float"); - BOOST_TEST(meta.column_length() == 12u); - BOOST_TEST(meta.type() == column_type::float_); - BOOST_TEST(meta.decimals() == 31u); + // Setup. Use both long and short strings to catch any SBO problems + auto pack = meta_builder() + .database("db") + .table("Some table value") + .org_table("Some other original table value") + .name("name") + .org_name("The original name of the database column") + .column_length(200) + .type(column_type::blob) + .decimals(12) + .collation_id(1234) + .flags(column_flags::pri_key) + .build_coldef(); + auto meta_orig = detail::access::construct(pack, true); + + // Copy construct + metadata meta(meta_orig); + + // Invalidate the original object + meta_orig = detail::access::construct(detail::coldef_view{}, true); + + // Check + BOOST_TEST(meta.database() == "db"); + BOOST_TEST(meta.table() == "Some table value"); + BOOST_TEST(meta.original_table() == "Some other original table value"); + BOOST_TEST(meta.column_name() == "name"); + BOOST_TEST(meta.original_column_name() == "The original name of the database column"); + BOOST_TEST(meta.column_collation() == 1234u); + BOOST_TEST(meta.column_length() == 200u); + BOOST_TEST(meta.type() == column_type::blob); + BOOST_TEST(meta.decimals() == 12u); BOOST_TEST(!meta.is_not_null()); - BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(meta.is_primary_key()); BOOST_TEST(!meta.is_unique_key()); BOOST_TEST(!meta.is_multiple_key()); BOOST_TEST(!meta.is_unsigned()); @@ -131,31 +384,325 @@ BOOST_AUTO_TEST_CASE(float_) BOOST_TEST(!meta.is_set_to_now_on_update()); } -BOOST_AUTO_TEST_CASE(dont_copy_strings) +// Double-check that no SBO problems happen +BOOST_AUTO_TEST_CASE(copy_constructor_sbo) { - detail::coldef_view msg{ - "awesome", - "child", - "child_table", - "field_alias", - "field_varchar", - collations::utf8mb4_general_ci, - 765, - column_type::varchar, - 0, - 0, - }; - auto meta = detail::access::construct(msg, false); + // Setup. Create the original object in dynamic memory to help sanitizers + auto pack = meta_builder() + .database("db") + .table("tab") + .org_table("ot") + .name("nam") + .org_name("on") + .build_coldef(); + auto meta_orig = create_dynamic_meta(pack, true); + + // Copy construct + metadata meta(*meta_orig); + + // Destroy the original object + meta_orig.reset(); + // Check + BOOST_TEST(meta.database() == "db"); + BOOST_TEST(meta.table() == "tab"); + BOOST_TEST(meta.original_table() == "ot"); + BOOST_TEST(meta.column_name() == "nam"); + BOOST_TEST(meta.original_column_name() == "on"); +} + +// Copy constructor works without strings, too +BOOST_AUTO_TEST_CASE(copy_constructor_no_strings) +{ + // Setup. Use both long and short strings to catch any SBO problems + auto pack = meta_builder().column_length(200).type(column_type::blob).build_coldef(); + auto meta_orig = detail::access::construct(pack, false); + + // Copy construct + metadata meta(meta_orig); + + // Check BOOST_TEST(meta.database() == ""); - BOOST_TEST(meta.table() == ""); - BOOST_TEST(meta.original_table() == ""); - BOOST_TEST(meta.column_name() == ""); - BOOST_TEST(meta.original_column_name() == ""); - BOOST_TEST(meta.column_length() == 765u); - BOOST_TEST(meta.type() == column_type::varchar); - BOOST_TEST(meta.decimals() == 0u); + BOOST_TEST(meta.column_length() == 200u); + BOOST_TEST(meta.type() == column_type::blob); +} + +// Move ctor handles strings correctly +BOOST_AUTO_TEST_CASE(move_constructor) +{ + // Setup. Use both long and short strings to catch any SBO problems + auto pack = meta_builder() + .database("db") + .table("Some table value") + .org_table("Some other original table value") + .name("name") + .org_name("The original name of the database column") + .column_length(200) + .type(column_type::blob) + .decimals(12) + .collation_id(1234) + .flags(column_flags::pri_key) + .build_coldef(); + auto meta_orig = detail::access::construct(pack, true); + + // Move construct + metadata meta(std::move(meta_orig)); + + // Invalidate the original object + meta_orig = detail::access::construct(detail::coldef_view{}, true); + + // Check + BOOST_TEST(meta.database() == "db"); + BOOST_TEST(meta.table() == "Some table value"); + BOOST_TEST(meta.original_table() == "Some other original table value"); + BOOST_TEST(meta.column_name() == "name"); + BOOST_TEST(meta.original_column_name() == "The original name of the database column"); + BOOST_TEST(meta.column_collation() == 1234u); + BOOST_TEST(meta.column_length() == 200u); + BOOST_TEST(meta.type() == column_type::blob); + BOOST_TEST(meta.decimals() == 12u); BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +// Double-check that no SBO problems happen +BOOST_AUTO_TEST_CASE(move_constructor_sbo) +{ + // Setup. Create the original object in dynamic memory to help sanitizers + auto pack = meta_builder() + .database("db") + .table("tab") + .org_table("ot") + .name("nam") + .org_name("on") + .build_coldef(); + auto meta_orig = create_dynamic_meta(pack, true); + + // Move construct + metadata meta(std::move(*meta_orig)); + + // Destroy the original object + meta_orig.reset(); + + // Check + BOOST_TEST(meta.database() == "db"); + BOOST_TEST(meta.table() == "tab"); + BOOST_TEST(meta.original_table() == "ot"); + BOOST_TEST(meta.column_name() == "nam"); + BOOST_TEST(meta.original_column_name() == "on"); +} + +// Move constructor works without strings, too +BOOST_AUTO_TEST_CASE(move_constructor_no_strings) +{ + // Setup + auto pack = meta_builder().column_length(200).type(column_type::blob).build_coldef(); + auto meta_orig = detail::access::construct(pack, false); + + // Copy construct + metadata meta(std::move(meta_orig)); + + // Check + BOOST_TEST(meta.database() == ""); + BOOST_TEST(meta.column_length() == 200u); + BOOST_TEST(meta.type() == column_type::blob); +} + +// Copy assignment handles strings correctly +BOOST_AUTO_TEST_CASE(copy_assign) +{ + // Setup. Use both long and short strings to catch any SBO problems + auto pack_orig = meta_builder() + .database("db") + .table("Some table value") + .org_table("Some other original table value") + .name("name") + .org_name("The original name of the database column") + .column_length(200) + .type(column_type::blob) + .decimals(12) + .collation_id(1234) + .flags(column_flags::pri_key) + .build_coldef(); + auto meta_orig = create_dynamic_meta(pack_orig, true); + auto pack = meta_builder() + .database("other_db") + .table("another tbl") + .org_table("original tbl") + .name(string_view()) + .org_name("Some test") + .column_length(10) + .type(column_type::varbinary) + .decimals(10) + .collation_id(42) + .flags(column_flags::not_null) + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Copy assign + meta = *meta_orig; + + // Destroy the original object + meta_orig.reset(); + + // Check + BOOST_TEST(meta.database() == "db"); + BOOST_TEST(meta.table() == "Some table value"); + BOOST_TEST(meta.original_table() == "Some other original table value"); + BOOST_TEST(meta.column_name() == "name"); + BOOST_TEST(meta.original_column_name() == "The original name of the database column"); + BOOST_TEST(meta.column_collation() == 1234u); + BOOST_TEST(meta.column_length() == 200u); + BOOST_TEST(meta.type() == column_type::blob); + BOOST_TEST(meta.decimals() == 12u); + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +// Copy assignment works without strings, too +BOOST_AUTO_TEST_CASE(copy_assign_no_strings) +{ + // Setup + auto pack_orig = meta_builder().type(column_type::blob).decimals(12).build_coldef(); + auto meta_orig = create_dynamic_meta(pack_orig, false); + auto pack = meta_builder().type(column_type::varbinary).decimals(10).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Copy assign + meta = *meta_orig; + + // Destroy the original object + meta_orig.reset(); + + // Check + BOOST_TEST(meta.database() == ""); + BOOST_TEST(meta.type() == column_type::blob); + BOOST_TEST(meta.decimals() == 12u); +} + +// Self copy-assign works +BOOST_AUTO_TEST_CASE(copy_assign_self) +{ + // Setup + auto pack = meta_builder() + .database("Some value") + .name("Some name") + .type(column_type::binary) + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Assign + const auto& meta_ref = meta; // avoid warnings + meta = meta_ref; + + // Check + BOOST_TEST(meta.database() == "Some value"); + BOOST_TEST(meta.column_name() == "Some name"); + BOOST_TEST(meta.type() == column_type::binary); +} + +// Move assignment handles strings correctly +BOOST_AUTO_TEST_CASE(move_assign) +{ + // Setup. Use both long and short strings to catch any SBO problems + auto pack_orig = meta_builder() + .database("db") + .table("Some table value") + .org_table("Some other original table value") + .name("name") + .org_name("The original name of the database column") + .column_length(200) + .type(column_type::blob) + .decimals(12) + .collation_id(1234) + .flags(column_flags::pri_key) + .build_coldef(); + auto meta_orig = create_dynamic_meta(pack_orig, true); + auto pack = meta_builder() + .database("other_db") + .table("another tbl") + .org_table("original tbl") + .name(string_view()) + .org_name("Some test") + .column_length(10) + .type(column_type::varbinary) + .decimals(10) + .collation_id(42) + .flags(column_flags::not_null) + .build_coldef(); + auto meta = detail::access::construct(pack, true); + + // Move assign + meta = std::move(*meta_orig); + + // Destroy the original object + meta_orig.reset(); + + // Check + BOOST_TEST(meta.database() == "db"); + BOOST_TEST(meta.table() == "Some table value"); + BOOST_TEST(meta.original_table() == "Some other original table value"); + BOOST_TEST(meta.column_name() == "name"); + BOOST_TEST(meta.original_column_name() == "The original name of the database column"); + BOOST_TEST(meta.column_collation() == 1234u); + BOOST_TEST(meta.column_length() == 200u); + BOOST_TEST(meta.type() == column_type::blob); + BOOST_TEST(meta.decimals() == 12u); + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +// Move assignment works without strings, too +BOOST_AUTO_TEST_CASE(move_assign_no_strings) +{ + // Setup. Use both long and short strings to catch any SBO problems + auto pack_orig = meta_builder().type(column_type::blob).decimals(12).build_coldef(); + auto meta_orig = create_dynamic_meta(pack_orig, false); + auto pack = meta_builder().type(column_type::varbinary).decimals(10).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Move assign + meta = std::move(*meta_orig); + + // Destroy the original object + meta_orig.reset(); + + // Check + BOOST_TEST(meta.database() == ""); + BOOST_TEST(meta.type() == column_type::blob); + BOOST_TEST(meta.decimals() == 12u); +} + +// Flags +BOOST_AUTO_TEST_CASE(flags_not_null) +{ + // Setup + auto pack = meta_builder().flags(column_flags::not_null).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(meta.is_not_null()); BOOST_TEST(!meta.is_primary_key()); BOOST_TEST(!meta.is_unique_key()); BOOST_TEST(!meta.is_multiple_key()); @@ -166,18 +713,210 @@ BOOST_AUTO_TEST_CASE(dont_copy_strings) BOOST_TEST(!meta.is_set_to_now_on_update()); } -BOOST_AUTO_TEST_CASE(string_ownership) +BOOST_AUTO_TEST_CASE(flags_pri_key) { - // Create the meta object - std::string colname = "col1"; - auto msg = meta_builder().name(colname).build_coldef(); - auto meta = detail::access::construct(msg, true); + // Setup + auto pack = meta_builder().flags(column_flags::pri_key).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} - // Check that we actually copy the data - colname = "abcd"; - BOOST_TEST(meta.column_name() == "col1"); +BOOST_AUTO_TEST_CASE(flags_unique_key) +{ + // Setup + auto pack = meta_builder().flags(column_flags::unique_key).build_coldef(); + auto meta = detail::access::construct(pack, false); - // TODO: the other strings + // Check + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +BOOST_AUTO_TEST_CASE(flags_multiple_key) +{ + // Setup + auto pack = meta_builder().flags(column_flags::multiple_key).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +BOOST_AUTO_TEST_CASE(flags_unsigned) +{ + // Setup + auto pack = meta_builder().flags(column_flags::unsigned_).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +BOOST_AUTO_TEST_CASE(flags_zerofill) +{ + // Setup + auto pack = meta_builder().flags(column_flags::zerofill).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +BOOST_AUTO_TEST_CASE(flags_auto_increment) +{ + // Setup + auto pack = meta_builder().flags(column_flags::auto_increment).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +BOOST_AUTO_TEST_CASE(flags_no_default_value) +{ + // Setup + auto pack = meta_builder().flags(column_flags::no_default_value).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +BOOST_AUTO_TEST_CASE(flags_on_update_now) +{ + // Setup + auto pack = meta_builder().flags(column_flags::on_update_now).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(meta.is_set_to_now_on_update()); +} + +// Several flags are set +BOOST_AUTO_TEST_CASE(flags_several) +{ + // Setup + auto pack = meta_builder() + .flags(column_flags::pri_key | column_flags::auto_increment | column_flags::not_null) + .build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(meta.is_not_null()); + BOOST_TEST(meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); +} + +// Some flags are not exposed +BOOST_AUTO_TEST_CASE(flags_ignored) +{ + struct + { + string_view name; + std::uint16_t flags; + } test_cases[] = { + {"binary", column_flags::binary }, + {"enum", column_flags::enum_ }, + {"timestamp", column_flags::timestamp }, + {"set", column_flags::set }, + {"part_key", column_flags::part_key }, + {"num", column_flags::num }, + {"mixed", column_flags::binary | column_flags::enum_ | column_flags::set}, + }; + + for (const auto& tc : test_cases) + { + BOOST_TEST_CONTEXT(tc.name) + { + // Setup + auto pack = meta_builder().flags(tc.flags).build_coldef(); + auto meta = detail::access::construct(pack, false); + + // Check + BOOST_TEST(!meta.is_not_null()); + BOOST_TEST(!meta.is_primary_key()); + BOOST_TEST(!meta.is_unique_key()); + BOOST_TEST(!meta.is_multiple_key()); + BOOST_TEST(!meta.is_unsigned()); + BOOST_TEST(!meta.is_zerofill()); + BOOST_TEST(!meta.is_auto_increment()); + BOOST_TEST(!meta.has_no_default_value()); + BOOST_TEST(!meta.is_set_to_now_on_update()); + } + } } BOOST_AUTO_TEST_SUITE_END() // test_metadata + +} // namespace \ No newline at end of file