diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 931c3c9..b489869 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -64,35 +64,41 @@ jobs: - { name: "GCC 14 Debug", label: "debug_gcc14", - run_mtr: true + run_mtr: true, + run_ctest: true } - { name: "GCC 14 RelWithDebInfo", label: "release_gcc14", - run_mtr: true + run_mtr: true, + run_ctest: true } - { name: "GCC 14 ASan", label: "asan_gcc14", run_mtr: true, - mtr_options: "--sanitize" + mtr_options: "--sanitize", + run_ctest: true } - { name: "Clang 19 Debug", label: "debug_clang19", - run_clang_tidy: true + run_clang_tidy: true, + run_ctest: true } - { name: "Clang 19 RelWithDebInfo", label: "release_clang19", - run_clang_tidy: true + run_clang_tidy: true, + run_ctest: true } - { name: "Clang 19 ASan", label: "asan_clang19" # TODO: re-enable running MTR under this "Clang XX ASan" # run_mtr: true, - # mtr_options: "--sanitize" + # mtr_options: "--sanitize", + # run_ctest: true # when "-stdlib=libc++ -fsanitize=address" alloc-dealloc-mismatch issue is fixed # (https://github.com/llvm/llvm-project/issues/59432) # or CI is upgraded to Clang 20 @@ -309,6 +315,7 @@ jobs: --suite=binlog_streaming ${{matrix.config.mtr_options}} - name: CTest + if: matrix.config.run_ctest working-directory: ${{github.workspace}}/src-build-${{matrix.config.label}} # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail diff --git a/CMakeLists.txt b/CMakeLists.txt index 5691180..9217e21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,7 +190,16 @@ set(source_files src/binsrv/event/unknown_post_header.cpp # gtid data structure files - src/binsrv/gtid/common_types.hpp + src/binsrv/gtids/common_types.hpp + src/binsrv/gtids/gtid_fwd.hpp + src/binsrv/gtids/gtid.hpp + src/binsrv/gtids/gtid.cpp + src/binsrv/gtids/gtid_set_fwd.hpp + src/binsrv/gtids/gtid_set.hpp + src/binsrv/gtids/gtid_set.cpp + src/binsrv/gtids/tag_fwd.hpp + src/binsrv/gtids/tag.hpp + src/binsrv/gtids/tag.cpp # binlog files src/binsrv/basic_logger_fwd.hpp @@ -348,3 +357,8 @@ set_target_properties(binlog_server PROPERTIES CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS NO ) + +include(CTest) +if(BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/CMakePresets.json b/CMakePresets.json index 53b0b3f..9522fc3 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -15,7 +15,8 @@ "cacheVariables": { "CMAKE_PREFIX_PATH": "${sourceParentDir}/aws-sdk-cpp-install-${presetName};${sourceParentDir}/boost-install-${presetName}", "CPP_STANDARD": "20", - "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "BUILD_TESTING": "ON" } }, diff --git a/src/binsrv/event/gtid_log_post_header.cpp b/src/binsrv/event/gtid_log_post_header.cpp index ae5f0fa..6332777 100644 --- a/src/binsrv/event/gtid_log_post_header.cpp +++ b/src/binsrv/event/gtid_log_post_header.cpp @@ -30,7 +30,7 @@ #include "binsrv/event/gtid_log_flag_type.hpp" -#include "binsrv/gtid/common_types.hpp" +#include "binsrv/gtids/common_types.hpp" #include "util/byte_span.hpp" #include "util/byte_span_extractors.hpp" @@ -127,8 +127,8 @@ gtid_log_post_header::get_flags() const noexcept { return to_string(get_flags()); } -[[nodiscard]] gtid::uuid gtid_log_post_header::get_uuid() const noexcept { - gtid::uuid result; +[[nodiscard]] gtids::uuid gtid_log_post_header::get_uuid() const noexcept { + gtids::uuid result; const auto &uuid_raw{get_uuid_raw()}; static_assert(std::tuple_size_v == boost::uuids::uuid::static_size()); diff --git a/src/binsrv/event/gtid_log_post_header.hpp b/src/binsrv/event/gtid_log_post_header.hpp index ab29a11..feb1e68 100644 --- a/src/binsrv/event/gtid_log_post_header.hpp +++ b/src/binsrv/event/gtid_log_post_header.hpp @@ -23,7 +23,7 @@ #include "binsrv/event/gtid_log_flag_type_fwd.hpp" -#include "binsrv/gtid/common_types.hpp" +#include "binsrv/gtids/common_types.hpp" #include "util/byte_span_fwd.hpp" @@ -43,10 +43,10 @@ class [[nodiscard]] gtid_log_post_header { [[nodiscard]] gtid_log_flag_set get_flags() const noexcept; [[nodiscard]] std::string get_readable_flags() const; - [[nodiscard]] const gtid::uuid_storage &get_uuid_raw() const noexcept { + [[nodiscard]] const gtids::uuid_storage &get_uuid_raw() const noexcept { return uuid_; } - [[nodiscard]] gtid::uuid get_uuid() const noexcept; + [[nodiscard]] gtids::uuid get_uuid() const noexcept; [[nodiscard]] std::string get_readable_uuid() const; [[nodiscard]] std::int64_t get_gno_raw() const noexcept { return gno_; } @@ -67,7 +67,7 @@ class [[nodiscard]] gtid_log_post_header { // the members are deliberately reordered for better packing std::uint8_t flags_{}; // 0 std::uint8_t logical_ts_code_{}; // 3 - gtid::uuid_storage uuid_{}; // 1 + gtids::uuid_storage uuid_{}; // 1 std::int64_t gno_{}; // 2 std::int64_t last_committed_{}; // 4 std::int64_t sequence_number_{}; // 5 diff --git a/src/binsrv/event/gtid_tagged_log_body_impl.cpp b/src/binsrv/event/gtid_tagged_log_body_impl.cpp index d49808e..752e289 100644 --- a/src/binsrv/event/gtid_tagged_log_body_impl.cpp +++ b/src/binsrv/event/gtid_tagged_log_body_impl.cpp @@ -40,7 +40,7 @@ #include "binsrv/event/code_type.hpp" #include "binsrv/event/gtid_log_flag_type.hpp" -#include "binsrv/gtid/common_types.hpp" +#include "binsrv/gtids/common_types.hpp" #include "util/bounded_string_storage.hpp" #include "util/byte_span.hpp" @@ -152,9 +152,9 @@ generic_body_impl::get_readable_flags() const { return to_string(get_flags()); } -[[nodiscard]] gtid::uuid +[[nodiscard]] gtids::uuid generic_body_impl::get_uuid() const noexcept { - gtid::uuid result; + gtids::uuid result; const auto &uuid_raw{get_uuid_raw()}; static_assert(std::tuple_size_v == boost::uuids::uuid::static_size()); @@ -298,8 +298,8 @@ void generic_body_impl::process_field_data( std::size_t extracted_tag_length{}; varlen_int_extractor(remainder, extracted_tag_length, "tag length"); tag_.resize(extracted_tag_length); - const std::span tag_subrange{std::data(tag_), - extracted_tag_length}; + const std::span tag_subrange{ + std::data(tag_), extracted_tag_length}; if (!util::extract_byte_span_from_byte_span_checked(remainder, tag_subrange)) { util::exception_location().raise( diff --git a/src/binsrv/event/gtid_tagged_log_body_impl.hpp b/src/binsrv/event/gtid_tagged_log_body_impl.hpp index 5ecf747..6e76af6 100644 --- a/src/binsrv/event/gtid_tagged_log_body_impl.hpp +++ b/src/binsrv/event/gtid_tagged_log_body_impl.hpp @@ -23,9 +23,8 @@ #include "binsrv/event/gtid_log_flag_type_fwd.hpp" -#include "binsrv/gtid/common_types.hpp" +#include "binsrv/gtids/common_types.hpp" -#include "util/bounded_string_storage_fwd.hpp" #include "util/byte_span_fwd.hpp" #include "util/semantic_version_fwd.hpp" @@ -35,24 +34,23 @@ template <> class [[nodiscard]] generic_body_impl { public: // https://github.com/mysql/mysql-server/blob/mysql-8.4.6/libs/mysql/binlog/event/control_events.h#L1111 - static constexpr std::size_t tag_length{32U}; - using tag_storage = util::bounded_string_storage; - explicit generic_body_impl(util::const_byte_span portion); [[nodiscard]] std::uint8_t get_flags_raw() const noexcept { return flags_; } [[nodiscard]] gtid_log_flag_set get_flags() const noexcept; [[nodiscard]] std::string get_readable_flags() const; - [[nodiscard]] const gtid::uuid_storage &get_uuid_raw() const noexcept { + [[nodiscard]] const gtids::uuid_storage &get_uuid_raw() const noexcept { return uuid_; } - [[nodiscard]] gtid::uuid get_uuid() const noexcept; + [[nodiscard]] gtids::uuid get_uuid() const noexcept; [[nodiscard]] std::string get_readable_uuid() const; [[nodiscard]] std::int64_t get_gno_raw() const noexcept { return gno_; } - [[nodiscard]] const tag_storage &get_tag_raw() const noexcept { return tag_; } + [[nodiscard]] const gtids::tag_storage &get_tag_raw() const noexcept { + return tag_; + } [[nodiscard]] std::string_view get_tag() const noexcept; [[nodiscard]] std::int64_t get_last_committed_raw() const noexcept { @@ -132,9 +130,9 @@ template <> class [[nodiscard]] generic_body_impl { // the members are deliberately reordered for better packing std::uint8_t flags_{}; // 0 - gtid::uuid_storage uuid_{}; // 1 + gtids::uuid_storage uuid_{}; // 1 std::int64_t gno_{}; // 2 - tag_storage tag_{}; // 3 + gtids::tag_storage tag_{}; // 3 std::int64_t last_committed_{}; // 4 std::int64_t sequence_number_{}; // 5 std::uint64_t immediate_commit_timestamp_{unset_commit_timestamp}; // 6 diff --git a/src/binsrv/gtid/common_types.hpp b/src/binsrv/gtids/common_types.hpp similarity index 65% rename from src/binsrv/gtid/common_types.hpp rename to src/binsrv/gtids/common_types.hpp index b261870..7792f22 100644 --- a/src/binsrv/gtid/common_types.hpp +++ b/src/binsrv/gtids/common_types.hpp @@ -13,22 +13,34 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -#ifndef BINSRV_GTID_COMMON_TYPES_HPP -#define BINSRV_GTID_COMMON_TYPES_HPP +#ifndef BINSRV_GTIDS_COMMON_TYPES_HPP +#define BINSRV_GTIDS_COMMON_TYPES_HPP #include #include #include #include +#include #include -namespace binsrv::gtid { +#include "util/bounded_string_storage_fwd.hpp" + +namespace binsrv::gtids { -using uuid = boost::uuids::uuid; inline constexpr std::size_t uuid_length{boost::uuids::uuid::static_size()}; using uuid_storage = std::array; -} // namespace binsrv::gtid +using uuid = boost::uuids::uuid; + +inline constexpr std::size_t tag_max_length{32U}; +using tag_storage = util::bounded_string_storage; + +using gno_t = std::uint64_t; +inline constexpr gno_t min_gno{1ULL}; +inline constexpr gno_t max_gno{ + std::numeric_limits>::max()}; + +} // namespace binsrv::gtids -#endif // BINSRV_GTID_COMMON_TYPES_HPP +#endif // BINSRV_GTIDS_COMMON_TYPES_HPP diff --git a/src/binsrv/gtids/gtid.cpp b/src/binsrv/gtids/gtid.cpp new file mode 100644 index 0000000..fb64dc4 --- /dev/null +++ b/src/binsrv/gtids/gtid.cpp @@ -0,0 +1,49 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/gtids/gtid.hpp" + +#include +#include + +#include + +#include "binsrv/gtids/common_types.hpp" + +#include "util/exception_location_helpers.hpp" + +namespace binsrv::gtids { + +void gtid::validate_components() { + if (uuid_.is_nil()) { + util::exception_location().raise( + "uuid must not be nil"); + } + if (gno_ < min_gno || gno_ > max_gno) { + util::exception_location().raise( + "gno is out of range"); + } +} + +std::ostream &operator<<(std::ostream &output, const gtid &obj) { + output << obj.get_uuid(); + if (obj.has_tag()) { + output << gtid::tag_separator << obj.get_tag(); + } + output << gtid::gno_separator << obj.get_gno(); + return output; +} + +} // namespace binsrv::gtids diff --git a/src/binsrv/gtids/gtid.hpp b/src/binsrv/gtids/gtid.hpp new file mode 100644 index 0000000..baef7e4 --- /dev/null +++ b/src/binsrv/gtids/gtid.hpp @@ -0,0 +1,67 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_GTIDS_GTID_HPP +#define BINSRV_GTIDS_GTID_HPP + +#include "binsrv/gtids/gtid_fwd.hpp" // IWYU pragma: export + +#include "binsrv/gtids/common_types.hpp" +#include "binsrv/gtids/tag.hpp" + +namespace binsrv::gtids { + +class gtid { +public: + static constexpr char tag_separator{':'}; + static constexpr char gno_separator{':'}; + + gtid() = default; + + // NOLINTNEXTLINE(modernize-pass-by-value) + gtid(const uuid &uuid_component, const tag &tag_component, + gno_t gno_component) + : uuid_{uuid_component}, tag_{tag_component}, gno_{gno_component} { + validate_components(); + } + + gtid(const uuid &uuid_component, gno_t gno_component) + : uuid_{uuid_component}, tag_{}, gno_{gno_component} { + validate_components(); + } + + [[nodiscard]] bool is_empty() const noexcept { return gno_ == 0ULL; } + + [[nodiscard]] const uuid &get_uuid() const noexcept { return uuid_; } + + [[nodiscard]] bool has_tag() const noexcept { return !tag_.is_empty(); } + [[nodiscard]] const tag &get_tag() const noexcept { return tag_; } + + [[nodiscard]] gno_t get_gno() const noexcept { return gno_; } + + [[nodiscard]] friend bool operator==(const gtid &first, + const gtid &second) noexcept = default; + +private: + uuid uuid_{}; + tag tag_{}; + gno_t gno_{}; + + void validate_components(); +}; + +} // namespace binsrv::gtids + +#endif // BINSRV_GTIDS_GTID_HPP diff --git a/src/binsrv/gtids/gtid_fwd.hpp b/src/binsrv/gtids/gtid_fwd.hpp new file mode 100644 index 0000000..0cc9b86 --- /dev/null +++ b/src/binsrv/gtids/gtid_fwd.hpp @@ -0,0 +1,30 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_GTIDS_GTID_FWD_HPP +#define BINSRV_GTIDS_GTID_FWD_HPP + +#include +#include + +namespace binsrv::gtids { + +class gtid; + +std::ostream &operator<<(std::ostream &output, const gtid &obj); + +} // namespace binsrv::gtids + +#endif // BINSRV_GTIDS_GTID_FWD_HPP diff --git a/src/binsrv/gtids/gtid_set.cpp b/src/binsrv/gtids/gtid_set.cpp new file mode 100644 index 0000000..60efd01 --- /dev/null +++ b/src/binsrv/gtids/gtid_set.cpp @@ -0,0 +1,110 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/gtids/gtid_set.hpp" + +#include +#include +#include +#include + +#include +#include + +#include + +#include "binsrv/gtids/gtid.hpp" +#include "binsrv/gtids/tag.hpp" + +#include "util/exception_location_helpers.hpp" + +namespace binsrv::gtids { + +gtid_set::gtid_set() = default; +gtid_set::gtid_set(const gtid_set &other) = default; +gtid_set::gtid_set(gtid_set &&other) noexcept = default; +gtid_set >id_set::operator=(const gtid_set &other) = default; +gtid_set >id_set::operator=(gtid_set &&other) noexcept = default; +gtid_set::~gtid_set() = default; + +gtid_set >id_set::operator+=(const gtid &value) { + if (value.is_empty()) { + util::exception_location().raise( + "cannot add an empty gtid"); + } + data_[value.get_uuid()][value.get_tag()] += value.get_gno(); + return *this; +} + +[[nodiscard]] bool gtid_set::contains(const gtid &value) const noexcept { + const auto uuid_it{data_.find(value.get_uuid())}; + if (uuid_it == std::cend(data_)) { + return false; + } + + const auto &tagged_gnos{uuid_it->second}; + const auto tag_it{tagged_gnos.find(value.get_tag())}; + if (tag_it == std::cend(tagged_gnos)) { + return false; + } + + const auto &gnos{tag_it->second}; + return boost::icl::contains(gnos, value.get_gno()); +} + +gtid_set >id_set::operator+=(const gtid_set &values) { + for (const auto &[current_uuid, current_tagged_gnos] : values.data_) { + for (const auto &[current_tag, current_gnos] : current_tagged_gnos) { + data_[current_uuid][current_tag] += current_gnos; + } + } + return *this; +} + +bool operator==(const gtid_set &first, + const gtid_set &second) noexcept = default; + +std::ostream &operator<<(std::ostream &output, const gtid_set &obj) { + const auto gno_container_printer{ + [](std::ostream &stream, const gtid_set::gno_container &gnos) { + for (const auto &interval : gnos) { + const auto lower = boost::icl::lower(interval); + const auto upper = boost::icl::upper(interval); + stream << gtid::gno_separator << lower; + if (upper != lower) { + stream << gtid_set::interval_separator << upper; + } + } + }}; + + bool first_uuid{true}; + for (const auto &[current_uuid, current_tagged_gnos] : obj.data_) { + for (const auto &[current_tag, current_gnos] : current_tagged_gnos) { + if (!first_uuid) { + output << gtid_set::uuid_separator << output.fill(); + } else { + first_uuid = false; + } + output << current_uuid; + if (!current_tag.is_empty()) { + output << gtid::tag_separator << current_tag; + } + gno_container_printer(output, current_gnos); + } + } + return output; +} + +} // namespace binsrv::gtids diff --git a/src/binsrv/gtids/gtid_set.hpp b/src/binsrv/gtids/gtid_set.hpp new file mode 100644 index 0000000..33a319f --- /dev/null +++ b/src/binsrv/gtids/gtid_set.hpp @@ -0,0 +1,78 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_GTIDS_GTID_SET_HPP +#define BINSRV_GTIDS_GTID_SET_HPP + +#include "binsrv/gtids/gtid_set_fwd.hpp" // IWYU pragma: export + +#include + +#include + +#include "binsrv/gtids/common_types.hpp" +#include "binsrv/gtids/gtid_fwd.hpp" +#include "binsrv/gtids/tag_fwd.hpp" + +namespace binsrv::gtids { + +class gtid_set { +public: + static constexpr char uuid_separator{','}; + static constexpr char interval_separator{'-'}; + + gtid_set(); + + // deliberately implicit + // NOLINTNEXTLINE(hicpp-explicit-conversions) + gtid_set(const gtid &value) { *this += value; } + + gtid_set(const gtid_set &other); + gtid_set(gtid_set &&other) noexcept; + gtid_set &operator=(const gtid_set &other); + gtid_set &operator=(gtid_set &&other) noexcept; + + ~gtid_set(); + + [[nodiscard]] bool is_empty() const noexcept { return data_.empty(); } + + [[nodiscard]] bool contains(const gtid &value) const noexcept; + + gtid_set &operator+=(const gtid &value); + gtid_set &operator+=(const gtid_set &values); + + void clear() noexcept { data_.clear(); } + + friend bool operator==(const gtid_set &first, + const gtid_set &second) noexcept; + + friend std::ostream &operator<<(std::ostream &output, const gtid_set &obj); + +private: + using gno_container = boost::icl::interval_set; + using gnos_by_tag_container = std::map; + using tagged_gnos_by_uid_container = std::map; + + tagged_gnos_by_uid_container data_{}; +}; + +inline gtid_set operator+(const gtid_set &first, const gtid_set &second) { + gtid_set result{first}; + return result += second; +} + +} // namespace binsrv::gtids + +#endif // BINSRV_GTIDS_GTID_SET_HPP diff --git a/src/binsrv/gtids/gtid_set_fwd.hpp b/src/binsrv/gtids/gtid_set_fwd.hpp new file mode 100644 index 0000000..0da9ab3 --- /dev/null +++ b/src/binsrv/gtids/gtid_set_fwd.hpp @@ -0,0 +1,30 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_GTIDS_GTID_SET_FWD_HPP +#define BINSRV_GTIDS_GTID_SET_FWD_HPP + +#include +#include + +namespace binsrv::gtids { + +class gtid_set; + +std::ostream &operator<<(std::ostream &output, const gtid_set &obj); + +} // namespace binsrv::gtids + +#endif // BINSRV_GTIDS_GTID_SET_FWD_HPP diff --git a/src/binsrv/gtids/tag.cpp b/src/binsrv/gtids/tag.cpp new file mode 100644 index 0000000..12d2912 --- /dev/null +++ b/src/binsrv/gtids/tag.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/gtids/tag.hpp" + +#include +#include +#include +#include +#include +#include + +#include "binsrv/gtids/common_types.hpp" + +#include "util/exception_location_helpers.hpp" + +namespace binsrv::gtids { + +tag::tag(std::string_view name) { + static constexpr char underscore{'_'}; + + const auto name_size{std::size(name)}; + if (std::size(name) > tag_max_length) { + util::exception_location().raise( + "tag name is too long"); + } + if (name_size == 0U) { + return; + } + data_.resize(name_size); + const char *name_it{std::data(name)}; + const char *name_en{name_it}; + std::advance(name_en, name_size); + std::byte *data_it{std::data(data_)}; + auto current_ch{*name_it}; + if (current_ch != underscore && std::isalpha(current_ch) == 0) { + util::exception_location().raise( + "tag name must start with a letter or an underscore"); + } + *data_it = static_cast(current_ch); + std::advance(name_it, 1U); + std::advance(data_it, 1U); + for (; name_it != name_en; + std::advance(name_it, 1U), std::advance(data_it, 1U)) { + current_ch = *name_it; + if (current_ch != underscore && std::isalnum(current_ch) == 0) { + util::exception_location().raise( + "tag name must includeonly alphanumeric characters or underscores"); + } + *data_it = static_cast(current_ch); + } +} + +std::ostream &operator<<(std::ostream &output, const tag &obj) { + return output << obj.get_name(); +} + +} // namespace binsrv::gtids diff --git a/src/binsrv/gtids/tag.hpp b/src/binsrv/gtids/tag.hpp new file mode 100644 index 0000000..3b39b2e --- /dev/null +++ b/src/binsrv/gtids/tag.hpp @@ -0,0 +1,58 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_GTIDS_TAG_HPP +#define BINSRV_GTIDS_TAG_HPP + +#include "binsrv/gtids/tag_fwd.hpp" // IWYU pragma: export + +#include + +#include "binsrv/gtids/common_types.hpp" + +#include "util/byte_span.hpp" + +namespace binsrv::gtids { + +class tag { +public: + tag() noexcept = default; + + explicit tag(std::string_view name); + + explicit tag(const tag_storage &data) : tag(util::as_string_view(data)) {} + + [[nodiscard]] std::string_view get_name() const noexcept { + return util::as_string_view(data_); + } + + [[nodiscard]] bool is_empty() const noexcept { return data_.empty(); } + +private: + tag_storage data_{}; +}; + +[[nodiscard]] inline auto operator<=>(const tag &first, + const tag &second) noexcept { + return first.get_name() <=> second.get_name(); +} +[[nodiscard]] inline auto operator==(const tag &first, + const tag &second) noexcept { + return first.get_name() == second.get_name(); +} + +} // namespace binsrv::gtids + +#endif // BINSRV_GTIDS_TAG_HPP diff --git a/src/binsrv/gtids/tag_fwd.hpp b/src/binsrv/gtids/tag_fwd.hpp new file mode 100644 index 0000000..d5e3dce --- /dev/null +++ b/src/binsrv/gtids/tag_fwd.hpp @@ -0,0 +1,30 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_GTIDS_TAG_FWD_HPP +#define BINSRV_GTIDS_TAG_FWD_HPP + +#include +#include + +namespace binsrv::gtids { + +class tag; + +std::ostream &operator<<(std::ostream &output, const tag &obj); + +} // namespace binsrv::gtids + +#endif // BINSRV_GTIDS_TAG_FWD_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..202fefc --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,74 @@ +# Copyright (c) 2023-2024 Percona and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +find_package(Boost REQUIRED COMPONENTS unit_test_framework) + +set(tag_source_files + ${PROJECT_SOURCE_DIR}/src/binsrv/gtids/tag_fwd.hpp + ${PROJECT_SOURCE_DIR}/src/binsrv/gtids/tag.hpp + ${PROJECT_SOURCE_DIR}/src/binsrv/gtids/tag.cpp +) + +set(gtid_source_files + ${PROJECT_SOURCE_DIR}/src/binsrv/gtids/gtid_fwd.hpp + ${PROJECT_SOURCE_DIR}/src/binsrv/gtids/gtid.hpp + ${PROJECT_SOURCE_DIR}/src/binsrv/gtids/gtid.cpp +) + +set(gtid_set_source_files + ${PROJECT_SOURCE_DIR}/src/binsrv/gtids/gtid_set_fwd.hpp + ${PROJECT_SOURCE_DIR}/src/binsrv/gtids/gtid_set.hpp + ${PROJECT_SOURCE_DIR}/src/binsrv/gtids/gtid_set.cpp +) + +add_executable(tag_test tag_test.cpp ${tag_source_files}) +target_include_directories(tag_test PRIVATE "${PROJECT_SOURCE_DIR}/src") +target_link_libraries(tag_test + PRIVATE + binlog_server_compiler_flags + Boost::unit_test_framework +) +set_target_properties(tag_test PROPERTIES + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) + +add_executable(gtid_test gtid_test.cpp ${tag_source_files} ${gtid_source_files}) +target_include_directories(gtid_test PRIVATE "${PROJECT_SOURCE_DIR}/src") +target_link_libraries(gtid_test + PRIVATE + binlog_server_compiler_flags + Boost::unit_test_framework +) +set_target_properties(gtid_test PROPERTIES + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) + +add_executable(gtid_set_test gtid_set_test.cpp ${tag_source_files} ${gtid_source_files} ${gtid_set_source_files}) +target_include_directories(gtid_set_test PRIVATE "${PROJECT_SOURCE_DIR}/src") +target_link_libraries(gtid_set_test + PRIVATE + binlog_server_compiler_flags + Boost::unit_test_framework +) +set_target_properties(gtid_set_test PROPERTIES + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) + +add_test(NAME tag_test COMMAND tag_test) +add_test(NAME gtid_test COMMAND gtid_test) +add_test(NAME gtid_set_test COMMAND gtid_set_test) diff --git a/tests/gtid_set_test.cpp b/tests/gtid_set_test.cpp new file mode 100644 index 0000000..8665b90 --- /dev/null +++ b/tests/gtid_set_test.cpp @@ -0,0 +1,284 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include +#include +#include +#include + +#include + +#define BOOST_TEST_MODULE GtidSetTests +// this include is needed as it provides the 'main()' function +// NOLINTNEXTLINE(misc-include-cleaner) +#include + +#include + +#include + +#include "binsrv/gtids/common_types.hpp" +#include "binsrv/gtids/gtid.hpp" +#include "binsrv/gtids/gtid_set.hpp" +#include "binsrv/gtids/tag.hpp" + +static constexpr binsrv::gtids::uuid first_uuid{ + {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11}}; +static constexpr binsrv::gtids::uuid second_uuid{ + {0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22}}; +static constexpr binsrv::gtids::uuid third_uuid{ + {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33}}; + +BOOST_AUTO_TEST_CASE(GtidSetDefaultConstruction) { + const binsrv::gtids::gtid_set empty_gtid_set{}; + + BOOST_CHECK(empty_gtid_set.is_empty()); +} + +BOOST_AUTO_TEST_CASE(GtidSetCopyConstruction) { + binsrv::gtids::gtid_set gtids{}; + gtids += binsrv::gtids::gtid{first_uuid, 1ULL}; + + const binsrv::gtids::gtid_set copy{gtids}; + + BOOST_CHECK_EQUAL(gtids, copy); +} + +BOOST_AUTO_TEST_CASE(GtidSetMoveConstruction) { + binsrv::gtids::gtid_set gtids{}; + gtids += binsrv::gtids::gtid{first_uuid, 1ULL}; + + const binsrv::gtids::gtid_set copy{std::move(gtids)}; + BOOST_CHECK(!copy.is_empty()); +} + +BOOST_AUTO_TEST_CASE(GtidSetCopyAssignmentOperator) { + binsrv::gtids::gtid_set gtids{}; + gtids += binsrv::gtids::gtid{first_uuid, 1ULL}; + + binsrv::gtids::gtid_set copy{}; + BOOST_CHECK_NE(gtids, copy); + + copy += binsrv::gtids::gtid{first_uuid, 2ULL}; + BOOST_CHECK_NE(gtids, copy); + + copy = gtids; + + BOOST_CHECK_EQUAL(gtids, copy); +} + +BOOST_AUTO_TEST_CASE(GtidSetMoveAssignmentOperator) { + binsrv::gtids::gtid_set gtids{}; + gtids += binsrv::gtids::gtid{first_uuid, 1ULL}; + + binsrv::gtids::gtid_set copy{}; + BOOST_CHECK_NE(gtids, copy); + + copy += binsrv::gtids::gtid{first_uuid, 2ULL}; + BOOST_CHECK_NE(gtids, copy); + + copy = std::move(gtids); + + BOOST_CHECK(!copy.is_empty()); +} + +BOOST_AUTO_TEST_CASE(GtidSetContains) { + const binsrv::gtids::tag first_tag{"alpha"}; + const binsrv::gtids::tag second_tag{"beta"}; + + binsrv::gtids::gtid_set gtids{}; + gtids += binsrv::gtids::gtid{first_uuid, first_tag, 1ULL}; + gtids += binsrv::gtids::gtid{first_uuid, second_tag, 2ULL}; + gtids += binsrv::gtids::gtid{second_uuid, first_tag, 1ULL}; + gtids += binsrv::gtids::gtid{second_uuid, second_tag, 2ULL}; + gtids += binsrv::gtids::gtid{third_uuid, 1ULL}; + + BOOST_CHECK(!gtids.contains(binsrv::gtids::gtid{})); + BOOST_CHECK(gtids.contains(binsrv::gtids::gtid{first_uuid, first_tag, 1ULL})); + BOOST_CHECK( + !gtids.contains(binsrv::gtids::gtid{first_uuid, first_tag, 2ULL})); + BOOST_CHECK( + !gtids.contains(binsrv::gtids::gtid{first_uuid, second_tag, 1ULL})); + BOOST_CHECK( + gtids.contains(binsrv::gtids::gtid{first_uuid, second_tag, 2ULL})); + + BOOST_CHECK( + gtids.contains(binsrv::gtids::gtid{second_uuid, first_tag, 1ULL})); + BOOST_CHECK( + !gtids.contains(binsrv::gtids::gtid{second_uuid, first_tag, 2ULL})); + BOOST_CHECK( + !gtids.contains(binsrv::gtids::gtid{second_uuid, second_tag, 1ULL})); + BOOST_CHECK( + gtids.contains(binsrv::gtids::gtid{second_uuid, second_tag, 2ULL})); + + BOOST_CHECK(gtids.contains(binsrv::gtids::gtid{third_uuid, 1ULL})); + BOOST_CHECK(!gtids.contains(binsrv::gtids::gtid{third_uuid, 2ULL})); +} + +BOOST_AUTO_TEST_CASE(GtidSetOperatorPlus) { + binsrv::gtids::gtid_set first_gtids{}; + + BOOST_CHECK_THROW(first_gtids += binsrv::gtids::gtid{}, + std::invalid_argument); + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + first_gtids += binsrv::gtids::gtid{first_uuid, 1ULL}; + first_gtids += binsrv::gtids::gtid{first_uuid, 3ULL}; + first_gtids += binsrv::gtids::gtid{second_uuid, 1ULL}; + first_gtids += binsrv::gtids::gtid{second_uuid, 2ULL}; + first_gtids += binsrv::gtids::gtid{second_uuid, 3ULL}; + first_gtids += binsrv::gtids::gtid{second_uuid, 5ULL}; + + binsrv::gtids::gtid_set second_gtids{}; + second_gtids += binsrv::gtids::gtid{second_uuid, 3ULL}; + second_gtids += binsrv::gtids::gtid{second_uuid, 4ULL}; + second_gtids += binsrv::gtids::gtid{second_uuid, 5ULL}; + second_gtids += binsrv::gtids::gtid{second_uuid, 6ULL}; + second_gtids += binsrv::gtids::gtid{third_uuid, 1ULL}; + second_gtids += binsrv::gtids::gtid{third_uuid, 3ULL}; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + + const auto merged_gtids{first_gtids + second_gtids}; + BOOST_CHECK_EQUAL(boost::lexical_cast(merged_gtids), + "11111111-1111-1111-1111-111111111111:1:3, " + "22222222-2222-2222-2222-222222222222:1-6, " + "33333333-3333-3333-3333-333333333333:1:3"); + + BOOST_CHECK_EQUAL(boost::lexical_cast( + binsrv::gtids::gtid{first_uuid, 2ULL} + merged_gtids), + "11111111-1111-1111-1111-111111111111:1-3, " + "22222222-2222-2222-2222-222222222222:1-6, " + "33333333-3333-3333-3333-333333333333:1:3"); + + BOOST_CHECK_EQUAL(boost::lexical_cast( + binsrv::gtids::gtid{first_uuid, 2ULL} + merged_gtids + + binsrv::gtids::gtid{third_uuid, 2ULL}), + "11111111-1111-1111-1111-111111111111:1-3, " + "22222222-2222-2222-2222-222222222222:1-6, " + "33333333-3333-3333-3333-333333333333:1-3"); +} + +BOOST_AUTO_TEST_CASE(GtidSetClear) { + binsrv::gtids::gtid_set gtids{}; + BOOST_CHECK(gtids.is_empty()); + + gtids += binsrv::gtids::gtid{first_uuid, 1ULL}; + BOOST_CHECK(!gtids.is_empty()); + + gtids.clear(); + BOOST_CHECK(gtids.is_empty()); +} + +BOOST_AUTO_TEST_CASE(GtidSetOstreamOperatorUntagged) { + binsrv::gtids::gtid_set gtids{}; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + gtids += binsrv::gtids::gtid{first_uuid, 1ULL}; + gtids += binsrv::gtids::gtid{first_uuid, 2ULL}; + gtids += binsrv::gtids::gtid{first_uuid, 3ULL}; + gtids += binsrv::gtids::gtid{first_uuid, 5ULL}; + + gtids += binsrv::gtids::gtid{second_uuid, 11ULL}; + gtids += binsrv::gtids::gtid{second_uuid, 12ULL}; + gtids += binsrv::gtids::gtid{second_uuid, 13ULL}; + gtids += binsrv::gtids::gtid{second_uuid, 15ULL}; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + + BOOST_CHECK_EQUAL(boost::lexical_cast(gtids), + "11111111-1111-1111-1111-111111111111:1-3:5, " + "22222222-2222-2222-2222-222222222222:11-13:15"); +} + +BOOST_AUTO_TEST_CASE(GtidSetOstreamOperatorTagged) { + const binsrv::gtids::tag first_tag{"alpha"}; + const binsrv::gtids::tag second_tag{"beta"}; + + binsrv::gtids::gtid_set gtids{}; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + gtids += binsrv::gtids::gtid{first_uuid, first_tag, 111ULL}; + gtids += binsrv::gtids::gtid{first_uuid, first_tag, 112ULL}; + gtids += binsrv::gtids::gtid{first_uuid, first_tag, 113ULL}; + gtids += binsrv::gtids::gtid{first_uuid, first_tag, 115ULL}; + + gtids += binsrv::gtids::gtid{first_uuid, second_tag, 121ULL}; + gtids += binsrv::gtids::gtid{first_uuid, second_tag, 122ULL}; + gtids += binsrv::gtids::gtid{first_uuid, second_tag, 123ULL}; + gtids += binsrv::gtids::gtid{first_uuid, second_tag, 125ULL}; + + gtids += binsrv::gtids::gtid{second_uuid, first_tag, 211ULL}; + gtids += binsrv::gtids::gtid{second_uuid, first_tag, 212ULL}; + gtids += binsrv::gtids::gtid{second_uuid, first_tag, 213ULL}; + gtids += binsrv::gtids::gtid{second_uuid, first_tag, 215ULL}; + + gtids += binsrv::gtids::gtid{second_uuid, second_tag, 221ULL}; + gtids += binsrv::gtids::gtid{second_uuid, second_tag, 222ULL}; + gtids += binsrv::gtids::gtid{second_uuid, second_tag, 223ULL}; + gtids += binsrv::gtids::gtid{second_uuid, second_tag, 225ULL}; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + + BOOST_CHECK_EQUAL(boost::lexical_cast(gtids), + "11111111-1111-1111-1111-111111111111:alpha:111-113:115, " + "11111111-1111-1111-1111-111111111111:beta:121-123:125, " + "22222222-2222-2222-2222-222222222222:alpha:211-213:215, " + "22222222-2222-2222-2222-222222222222:beta:221-223:225"); +} + +BOOST_AUTO_TEST_CASE(GtidSetOstreamOperatorMixed) { + const binsrv::gtids::tag first_tag{"alpha"}; + const binsrv::gtids::tag second_tag{"beta"}; + + binsrv::gtids::gtid_set gtids{}; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + gtids += binsrv::gtids::gtid{first_uuid, 101ULL}; + gtids += binsrv::gtids::gtid{first_uuid, 102ULL}; + gtids += binsrv::gtids::gtid{first_uuid, 103ULL}; + gtids += binsrv::gtids::gtid{first_uuid, 105ULL}; + + gtids += binsrv::gtids::gtid{first_uuid, first_tag, 111ULL}; + gtids += binsrv::gtids::gtid{first_uuid, first_tag, 112ULL}; + gtids += binsrv::gtids::gtid{first_uuid, first_tag, 113ULL}; + gtids += binsrv::gtids::gtid{first_uuid, first_tag, 115ULL}; + + gtids += binsrv::gtids::gtid{first_uuid, second_tag, 121ULL}; + gtids += binsrv::gtids::gtid{first_uuid, second_tag, 122ULL}; + gtids += binsrv::gtids::gtid{first_uuid, second_tag, 123ULL}; + gtids += binsrv::gtids::gtid{first_uuid, second_tag, 125ULL}; + + gtids += binsrv::gtids::gtid{second_uuid, 201ULL}; + gtids += binsrv::gtids::gtid{second_uuid, 202ULL}; + gtids += binsrv::gtids::gtid{second_uuid, 203ULL}; + gtids += binsrv::gtids::gtid{second_uuid, 205ULL}; + + gtids += binsrv::gtids::gtid{second_uuid, first_tag, 211ULL}; + gtids += binsrv::gtids::gtid{second_uuid, first_tag, 212ULL}; + gtids += binsrv::gtids::gtid{second_uuid, first_tag, 213ULL}; + gtids += binsrv::gtids::gtid{second_uuid, first_tag, 215ULL}; + + gtids += binsrv::gtids::gtid{second_uuid, second_tag, 221ULL}; + gtids += binsrv::gtids::gtid{second_uuid, second_tag, 222ULL}; + gtids += binsrv::gtids::gtid{second_uuid, second_tag, 223ULL}; + gtids += binsrv::gtids::gtid{second_uuid, second_tag, 225ULL}; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + + BOOST_CHECK_EQUAL(boost::lexical_cast(gtids), + "11111111-1111-1111-1111-111111111111:101-103:105, " + "11111111-1111-1111-1111-111111111111:alpha:111-113:115, " + "11111111-1111-1111-1111-111111111111:beta:121-123:125, " + "22222222-2222-2222-2222-222222222222:201-203:205, " + "22222222-2222-2222-2222-222222222222:alpha:211-213:215, " + "22222222-2222-2222-2222-222222222222:beta:221-223:225"); +} diff --git a/tests/gtid_test.cpp b/tests/gtid_test.cpp new file mode 100644 index 0000000..4366741 --- /dev/null +++ b/tests/gtid_test.cpp @@ -0,0 +1,120 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include +#include +#include + +#include + +#define BOOST_TEST_MODULE GtidTests +// this include is needed as it provides the 'main()' function +// NOLINTNEXTLINE(misc-include-cleaner) +#include + +#include + +#include + +#include "binsrv/gtids/common_types.hpp" +#include "binsrv/gtids/gtid.hpp" + +static constexpr binsrv::gtids::uuid uuid_sample{ + {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, + 0x3c, 0x2d, 0x1e, 0x0f}}; +static constexpr binsrv::gtids::gno_t gno_sample{42ULL}; + +BOOST_AUTO_TEST_CASE(GtidDefaultConstruction) { + const binsrv::gtids::gtid empty_gtid{}; + + BOOST_CHECK(empty_gtid.get_uuid().is_nil()); + BOOST_CHECK(empty_gtid.get_tag().is_empty()); + BOOST_CHECK_EQUAL(empty_gtid.get_gno(), 0ULL); +} + +BOOST_AUTO_TEST_CASE(GtidThreeArgumentConstruction) { + const binsrv::gtids::uuid empty_uuid{}; + const binsrv::gtids::uuid hardcoded_uuid{uuid_sample}; + + const binsrv::gtids::tag empty_tag{}; + const binsrv::gtids::tag hardcoded_tag{"alpha"}; + + const binsrv::gtids::gno_t zero_gno{0ULL}; + const binsrv::gtids::gno_t hardcoded_gno{gno_sample}; + const binsrv::gtids::gno_t too_large_gno{binsrv::gtids::max_gno + 1ULL}; + + BOOST_CHECK_THROW(binsrv::gtids::gtid(empty_uuid, empty_tag, zero_gno), + std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::gtid(empty_uuid, empty_tag, hardcoded_gno), + std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::gtid(empty_uuid, empty_tag, too_large_gno), + std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::gtid(empty_uuid, hardcoded_tag, zero_gno), + std::invalid_argument); + BOOST_CHECK_THROW( + binsrv::gtids::gtid(empty_uuid, hardcoded_tag, hardcoded_gno), + std::invalid_argument); + BOOST_CHECK_THROW( + binsrv::gtids::gtid(empty_uuid, hardcoded_tag, too_large_gno), + std::invalid_argument); + + BOOST_CHECK_THROW(binsrv::gtids::gtid(hardcoded_uuid, empty_tag, zero_gno), + std::invalid_argument); + BOOST_CHECK_NO_THROW( + binsrv::gtids::gtid(hardcoded_uuid, empty_tag, hardcoded_gno)); + BOOST_CHECK_THROW( + binsrv::gtids::gtid(hardcoded_uuid, empty_tag, too_large_gno), + std::invalid_argument); + BOOST_CHECK_THROW( + binsrv::gtids::gtid(hardcoded_uuid, hardcoded_tag, zero_gno), + std::invalid_argument); + BOOST_CHECK_NO_THROW( + binsrv::gtids::gtid(hardcoded_uuid, hardcoded_tag, hardcoded_gno)); + BOOST_CHECK_THROW( + binsrv::gtids::gtid(hardcoded_uuid, hardcoded_tag, too_large_gno), + std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(GtidTwoArgumentConstruction) { + const binsrv::gtids::uuid empty_uuid{}; + const binsrv::gtids::uuid hardcoded_uuid{uuid_sample}; + + const binsrv::gtids::gno_t zero_gno{0ULL}; + const binsrv::gtids::gno_t hardcoded_gno{gno_sample}; + const binsrv::gtids::gno_t too_large_gno{binsrv::gtids::max_gno + 1ULL}; + + BOOST_CHECK_THROW(binsrv::gtids::gtid(empty_uuid, zero_gno), + std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::gtid(empty_uuid, hardcoded_gno), + std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::gtid(empty_uuid, too_large_gno), + std::invalid_argument); + + BOOST_CHECK_THROW(binsrv::gtids::gtid(hardcoded_uuid, zero_gno), + std::invalid_argument); + BOOST_CHECK_NO_THROW(binsrv::gtids::gtid(hardcoded_uuid, hardcoded_gno)); + BOOST_CHECK_THROW(binsrv::gtids::gtid(hardcoded_uuid, too_large_gno), + std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(GtidOstreamOperator) { + const binsrv::gtids::gtid empty_gtid{}; + BOOST_CHECK_EQUAL(boost::lexical_cast(empty_gtid), + "00000000-0000-0000-0000-000000000000:0"); + + const binsrv::gtids::gtid non_tagged_gtid{uuid_sample, gno_sample}; + BOOST_CHECK_EQUAL(boost::lexical_cast(non_tagged_gtid), + "f0e1d2c3-b4a5-9687-7869-5a4b3c2d1e0f:42"); +} diff --git a/tests/tag_test.cpp b/tests/tag_test.cpp new file mode 100644 index 0000000..77c2bbd --- /dev/null +++ b/tests/tag_test.cpp @@ -0,0 +1,121 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include +#include +#include + +#define BOOST_TEST_MODULE TagTests +// this include is needed as it provides the 'main()' function +// NOLINTNEXTLINE(misc-include-cleaner) +#include + +#include + +#include + +#include "binsrv/gtids/common_types.hpp" +#include "binsrv/gtids/tag.hpp" + +BOOST_AUTO_TEST_CASE(TagDefaultConstruction) { + const binsrv::gtids::tag empty_tag{}; + + BOOST_CHECK(empty_tag.is_empty()); + + const auto name{empty_tag.get_name()}; + BOOST_CHECK(name.empty()); +} + +BOOST_AUTO_TEST_CASE(TagEmptyStringViewConstruction) { + const std::string_view empty_sv{}; + const binsrv::gtids::tag empty_sv_tag{empty_sv}; + + BOOST_CHECK(empty_sv_tag.is_empty()); + + const auto name{empty_sv_tag.get_name()}; + BOOST_CHECK(name.empty()); +} + +BOOST_AUTO_TEST_CASE(TagValidStringViewConstruction) { + const std::string_view valid_sv{"alpha"}; + const binsrv::gtids::tag valid_sv_tag{valid_sv}; + + BOOST_CHECK(!valid_sv_tag.is_empty()); + + const auto name{valid_sv_tag.get_name()}; + BOOST_CHECK_EQUAL(name, valid_sv); +} + +BOOST_AUTO_TEST_CASE(TagInvalidStringViewConstruction) { + BOOST_CHECK_THROW(binsrv::gtids::tag{"$"}, std::invalid_argument); + BOOST_CHECK_NO_THROW(binsrv::gtids::tag{"_"}); + BOOST_CHECK_NO_THROW(binsrv::gtids::tag{"a"}); + BOOST_CHECK_THROW(binsrv::gtids::tag{"0"}, std::invalid_argument); + + BOOST_CHECK_THROW(binsrv::gtids::tag{"$$"}, std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::tag{"$_"}, std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::tag{"$a"}, std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::tag{"$0"}, std::invalid_argument); + + BOOST_CHECK_THROW(binsrv::gtids::tag{"_$"}, std::invalid_argument); + BOOST_CHECK_NO_THROW(binsrv::gtids::tag{"__"}); + BOOST_CHECK_NO_THROW(binsrv::gtids::tag{"_a"}); + BOOST_CHECK_NO_THROW(binsrv::gtids::tag{"_0"}); + + BOOST_CHECK_THROW(binsrv::gtids::tag{"a$"}, std::invalid_argument); + BOOST_CHECK_NO_THROW(binsrv::gtids::tag{"a_"}); + BOOST_CHECK_NO_THROW(binsrv::gtids::tag{"aa"}); + BOOST_CHECK_NO_THROW(binsrv::gtids::tag{"a0"}); + + BOOST_CHECK_THROW(binsrv::gtids::tag{"0$"}, std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::tag{"0_"}, std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::tag{"0a"}, std::invalid_argument); + BOOST_CHECK_THROW(binsrv::gtids::tag{"00"}, std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(TagLongStringViewConstruction) { + std::string long_name(binsrv::gtids::tag_max_length, 'a'); + const binsrv::gtids::tag valid_sv_tag{long_name}; + + BOOST_CHECK(!valid_sv_tag.is_empty()); + + const auto name{valid_sv_tag.get_name()}; + BOOST_CHECK_EQUAL(name, long_name); + + long_name += 'a'; + BOOST_CHECK_THROW(binsrv::gtids::tag{long_name}, std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(TagComparison) { + const binsrv::gtids::tag empty_tag{}; + const binsrv::gtids::tag alpha_tag{"alpha"}; + const binsrv::gtids::tag beta_tag{"beta"}; + + BOOST_CHECK_EQUAL(empty_tag, empty_tag); + BOOST_CHECK_EQUAL(alpha_tag, alpha_tag); + + BOOST_CHECK_NE(empty_tag, alpha_tag); + BOOST_CHECK_NE(alpha_tag, empty_tag); + BOOST_CHECK_NE(alpha_tag, beta_tag); + BOOST_CHECK_NE(beta_tag, alpha_tag); + + BOOST_CHECK_LT(empty_tag, alpha_tag); + BOOST_CHECK_LT(alpha_tag, beta_tag); + + BOOST_CHECK_LE(empty_tag, empty_tag); + BOOST_CHECK_LE(empty_tag, alpha_tag); + BOOST_CHECK_LE(alpha_tag, alpha_tag); + BOOST_CHECK_LE(alpha_tag, beta_tag); +}