diff --git a/CMakeLists.txt b/CMakeLists.txt index d154823..eb26a53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -319,6 +319,8 @@ set(source_files src/binsrv/time_unit.cpp # various utility files + src/util/bnf_parser_helpers.hpp + src/util/bounded_string_storage_fwd.hpp src/util/bounded_string_storage.hpp diff --git a/src/binsrv/ctime_timestamp_fwd.hpp b/src/binsrv/ctime_timestamp_fwd.hpp index 85a23a0..2b15a80 100644 --- a/src/binsrv/ctime_timestamp_fwd.hpp +++ b/src/binsrv/ctime_timestamp_fwd.hpp @@ -16,8 +16,7 @@ #ifndef BINSRV_CTIME_TIMESTAMP_FWD_HPP #define BINSRV_CTIME_TIMESTAMP_FWD_HPP -#include -#include +#include #include "util/nv_tuple_json_support.hpp" diff --git a/src/binsrv/gtids/gtid.cpp b/src/binsrv/gtids/gtid.cpp index 0580ab6..af0c864 100644 --- a/src/binsrv/gtids/gtid.cpp +++ b/src/binsrv/gtids/gtid.cpp @@ -15,8 +15,11 @@ #include "binsrv/gtids/gtid.hpp" +#include +#include #include #include +#include #include "binsrv/gtids/common_types.hpp" #include "binsrv/gtids/tag_fwd.hpp" @@ -26,6 +29,23 @@ namespace binsrv::gtids { +[[nodiscard]] std::string gtid::str() const { + std::string result; + static constexpr std::size_t gno_str_max_length{ + std::numeric_limits::digits10 + 1U}; + + result.reserve(uuid::readable_size + (has_tag() ? tag_.get_size() + 1U : 0U) + + 1U + gno_str_max_length); + result += uuid_.str(); + if (has_tag()) { + result += component_separator; + result += tag_.get_name(); + } + result += component_separator; + result += std::to_string(gno_); + return result; +} + void gtid::validate_components(const uuid &uuid_component, const tag & /*tag_component*/, gno_t gno_component) { @@ -40,12 +60,7 @@ void gtid::validate_components(const uuid &uuid_component, } 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; + return output << obj.str(); } } // namespace binsrv::gtids diff --git a/src/binsrv/gtids/gtid.hpp b/src/binsrv/gtids/gtid.hpp index 6b86d5f..469c27c 100644 --- a/src/binsrv/gtids/gtid.hpp +++ b/src/binsrv/gtids/gtid.hpp @@ -26,8 +26,7 @@ namespace binsrv::gtids { class gtid { public: - static constexpr char tag_separator{':'}; - static constexpr char gno_separator{':'}; + static constexpr char component_separator{':'}; gtid() = default; @@ -52,6 +51,8 @@ class gtid { [[nodiscard]] gno_t get_gno() const noexcept { return gno_; } + [[nodiscard]] std::string str() const; + [[nodiscard]] friend bool operator==(const gtid &first, const gtid &second) noexcept = default; diff --git a/src/binsrv/gtids/gtid_set.cpp b/src/binsrv/gtids/gtid_set.cpp index 224318a..1fdfe4a 100644 --- a/src/binsrv/gtids/gtid_set.cpp +++ b/src/binsrv/gtids/gtid_set.cpp @@ -15,14 +15,22 @@ #include "binsrv/gtids/gtid_set.hpp" +#include +#include +#include #include #include +#include +#include +#include #include #include #include #include #include +#include #include +#include #include #include @@ -34,6 +42,7 @@ #include "binsrv/gtids/tag.hpp" #include "binsrv/gtids/uuid.hpp" +#include "util/bnf_parser_helpers.hpp" #include "util/byte_span_extractors.hpp" #include "util/byte_span_fwd.hpp" #include "util/byte_span_inserters.hpp" @@ -41,6 +50,235 @@ namespace binsrv::gtids { +class gtid_set_parser { +public: + gtid_set_parser(const gtid_set_parser &) = delete; + gtid_set_parser(gtid_set_parser &&) = delete; + gtid_set_parser &operator=(const gtid_set_parser &) = delete; + gtid_set_parser &operator=(gtid_set_parser &&) = delete; + ~gtid_set_parser() = default; + + static void parse(std::string_view readable_value, gtid_set >ids) { + gtid_set_parser parser{gtids}; + std::string_view remainder{readable_value}; + parser.parse_gtid_set(remainder); + } + +private: + /* + BNF grammar for GTID sets + + ::= (',' )* + ::= ':' + ::= {4} '-' {2} '-' + {2} '-' {2} '-' + {6} + ::= {2} + ::= [a-fA-F0-9] + ::= + (':' )* + ::= ( ':')? ('-' )? + ::= [a-zA-Z_][a-zA-Z0-9_]{0,31} + ::= [0-9]+ + ::= [ \t\r\n\f\v]+ + */ + + gtid_set *result_gtids_; + uuid current_uuid_{}; + tag current_tag_{}; + + explicit gtid_set_parser(gtid_set >ids) noexcept : result_gtids_(>ids) {} + + static constexpr auto hexadecimal_predicate{ + [](char character) noexcept -> bool { + return std::isxdigit(character) != 0; + }}; + static constexpr auto tag_first_character_predicate{ + [](char character) noexcept -> bool { + return character == '_' || std::isalpha(character) != 0; + }}; + static constexpr auto tag_other_characters_predicate{ + [](char character) noexcept -> bool { + return character == '_' || std::isalnum(character) != 0; + }}; + static constexpr auto digit_predicate{[](char character) noexcept -> bool { + return std::isdigit(character) != 0; + }}; + static constexpr auto space_predicate{[](char character) noexcept -> bool { + return std::isspace(character) != 0; + }}; + + template + [[nodiscard]] static T char_to_value(char character, char base, + T shift = T{}) noexcept { + using default_unsigned = std::make_unsigned_t; + return static_cast( + static_cast(static_cast(character - base)) + + shift); + } + + // ::= [a-fA-F0-9] + [[nodiscard]] static std::uint8_t + parse_hexadecimal(std::string_view &remainder) { + static constexpr char lower_a_character{'a'}; + static constexpr char upper_a_character{'A'}; + static constexpr char zero_digit_character{'0'}; + + static constexpr std::uint8_t hex_shift{10U}; + + const char character{ + util::parse_character_predicate(remainder, hexadecimal_predicate)}; + if (character >= lower_a_character) { + return char_to_value(character, lower_a_character, + hex_shift); + } + if (character >= upper_a_character) { + return char_to_value(character, upper_a_character, + hex_shift); + } + return char_to_value(character, zero_digit_character); + } + + // ::= {2} + [[nodiscard]] static std::byte + parse_hexadecimal_pair(std::string_view &remainder) { + std::uint8_t result{parse_hexadecimal(remainder)}; + result <<= 4U; + result |= parse_hexadecimal(remainder); + using underlying_byte_type = std::underlying_type_t; + return static_cast(static_cast(result)); + } + + // ::= {4} '-' {2} '-' + // {2} '-' {2} '-' + // {6} + [[nodiscard]] static uuid parse_uuid(std::string_view &remainder) { + uuid_storage buffer{}; + auto *buffer_it{std::begin(buffer)}; + + const auto parse_group{ + [](std::string_view &input, std::byte *&output, std::size_t count) { + for (std::size_t index{0U}; index < count; ++index) { + *output = parse_hexadecimal_pair(input); + std::advance(output, 1U); + } + }}; + static constexpr std::array group_sizes{4U, 2U, 2U, 2U, 6U}; + const auto *group_it{std::cbegin(group_sizes)}; + const auto *const group_en{std::cend(group_sizes)}; + + parse_group(remainder, buffer_it, *group_it); + std::advance(group_it, 1U); + + while (group_it != group_en) { + util::parse_character(remainder, uuid::group_separator); + parse_group(remainder, buffer_it, *group_it); + std::advance(group_it, 1U); + } + + assert(buffer_it == std::end(buffer)); + return uuid{buffer}; + } + + // ::= [a-zA-Z_][a-zA-Z0-9_]{0,31} + [[nodiscard]] static tag parse_tag(std::string_view &remainder) { + using underlying_byte_type = std::underlying_type_t; + + tag_storage buffer{}; + char tag_character{util::parse_character_predicate( + remainder, tag_first_character_predicate)}; + buffer.push_back(static_cast( + static_cast(tag_character))); + + while (std::size(buffer) < tag_max_length && + util::parse_character_predicate_ex( + remainder, tag_other_characters_predicate, tag_character)) { + buffer.push_back(static_cast( + static_cast(tag_character))); + } + assert(std::size(buffer) <= tag_max_length); + + return tag{buffer}; + } + + // ::= [0-9]+ + [[nodiscard]] static gno_t parse_gno(std::string_view &remainder) { + static constexpr gno_t radix{10ULL}; + gno_t result{0ULL}; + + char digit{}; + + digit = util::parse_character_predicate(remainder, digit_predicate); + result += char_to_value(digit, '0', 0U); + + while ( + util::parse_character_predicate_ex(remainder, digit_predicate, digit)) { + // TODO: add overflow checks + result *= radix; + result += char_to_value(digit, '0', 0U); + } + return result; + } + + // ::= ( ':')? ('-' )? + void parse_optionally_tagged_interval(std::string_view &remainder) { + if (!util::check_character_predicate(remainder, digit_predicate)) { + current_tag_ = parse_tag(remainder); + util::parse_character(remainder, gtid::component_separator); + } + const gno_t gno_lower{parse_gno(remainder)}; + char character{}; + if (util::parse_character_ex(remainder, gtid_set::interval_separator, + character)) { + const gno_t gno_upper{parse_gno(remainder)}; + result_gtids_->add_interval(current_uuid_, current_tag_, gno_lower, + gno_upper); + } else { + result_gtids_->add(current_uuid_, current_tag_, gno_lower); + } + } + + // ::= + // (':' )* + void parse_optionally_tagged_intervals(std::string_view &remainder) { + parse_optionally_tagged_interval(remainder); + char character{}; + while (util::parse_character_ex(remainder, gtid::component_separator, + character)) { + parse_optionally_tagged_interval(remainder); + } + } + + // ::= ':' + void parse_uuid_set(std::string_view &remainder) { + current_uuid_ = parse_uuid(remainder); + current_tag_ = tag{}; + util::parse_character(remainder, gtid::component_separator); + parse_optionally_tagged_intervals(remainder); + } + + // ::= [ \t\r\n\f\v]+ + static void parse_space(std::string_view &remainder) { + util::parse_character_predicate(remainder, space_predicate); + + char character{}; + while (util::parse_character_predicate_ex(remainder, space_predicate, + character)) { + } + } + + // ::= (',' )* + void parse_gtid_set(std::string_view &remainder) { + parse_uuid_set(remainder); + char character{}; + while (util::parse_character_ex(remainder, gtid_set::uuid_separator, + character)) { + parse_space(remainder); + parse_uuid_set(remainder); + } + } +}; + gtid_set::gtid_set() = default; gtid_set::gtid_set(const gtid_set &other) = default; gtid_set::gtid_set(gtid_set &&other) noexcept = default; @@ -48,6 +286,20 @@ 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::gtid_set(std::string_view value) { + // empty string correspond to an empty GTID set + if (value.empty()) { + return; + } + + try { + gtid_set_parser::parse(value, *this); + } catch (const std::exception &) { + util::exception_location().raise( + "cannot parse GTID set"); + } +} + gtid_set::gtid_set(util::const_byte_span portion) { // Gtid_set encoding: // https://github.com/mysql/mysql-server/blob/mysql-8.4.6/sql/rpl_gtid_set.cc#L1389 @@ -167,6 +419,64 @@ gtid_set::gtid_set(util::const_byte_span portion) { return false; } +[[nodiscard]] std::string gtid_set::str() const { + const auto string_size_estimator{ + [](const tagged_gnos_by_uid_container &data) -> std::size_t { + // Rough estimate for up to 1 billion transactions + static constexpr std::size_t average_gno_readable_size{9}; + std::size_t estimate{0U}; + for (const auto &[current_uuid, current_tagged_gnos] : data) { + estimate += uuid::readable_size; + estimate += 2; // for ", " + + for (const auto &[current_tag, current_gnos] : current_tagged_gnos) { + ++estimate; // for ':' before tag + estimate += current_tag.get_size(); + // for each interval one ':', one '-' and two numbers + estimate += boost::icl::interval_count(current_gnos) * + (2U * average_gno_readable_size + 2U); + } + } + return estimate; + }}; + + const auto gno_container_printer{ + [](std::string &result, const gno_container &gnos) { + for (const auto &interval : gnos) { + const auto lower = boost::icl::lower(interval); + const auto upper = boost::icl::upper(interval); + result += gtid::component_separator; + result += std::to_string(lower); + if (upper != lower) { + result += interval_separator; + result += std::to_string(upper); + } + } + }}; + + std::string result{}; + result.reserve(string_size_estimator(data_)); + bool first_uuid{true}; + for (const auto &[current_uuid, current_tagged_gnos] : data_) { + if (!first_uuid) { + result += uuid_separator; + result += uuid_separator_whitespace; + } else { + first_uuid = false; + } + result += current_uuid.str(); + + for (const auto &[current_tag, current_gnos] : current_tagged_gnos) { + if (!current_tag.is_empty()) { + result += gtid::component_separator; + result += current_tag.get_name(); + } + gno_container_printer(result, current_gnos); + } + } + return result; +} + [[nodiscard]] std::size_t gtid_set::calculate_encoded_size() const noexcept { const auto tagged_flag{contains_tags()}; // 8 bytes for the header (for both tahgged and untagged versions) @@ -380,34 +690,21 @@ void gtid_set::process_intervals(util::const_byte_span &remainder, } 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; - } - } - }}; + return output << obj.str(); +} - bool first_uuid{true}; - for (const auto &[current_uuid, current_tagged_gnos] : obj.data_) { - if (!first_uuid) { - output << gtid_set::uuid_separator << output.fill(); - } else { - first_uuid = false; - } - output << current_uuid; - for (const auto &[current_tag, current_gnos] : current_tagged_gnos) { - if (!current_tag.is_empty()) { - output << gtid::tag_separator << current_tag; - } - gno_container_printer(output, current_gnos); - } +std::istream &operator>>(std::istream &input, gtid_set &obj) { + std::string gtids_str; + input >> gtids_str; + if (!input) { + return input; + } + try { + obj = gtid_set{gtids_str}; + } catch (const std::exception &) { + input.setstate(std::ios_base::failbit); } - return output; + return input; } } // namespace binsrv::gtids diff --git a/src/binsrv/gtids/gtid_set.hpp b/src/binsrv/gtids/gtid_set.hpp index cf48635..2b012e4 100644 --- a/src/binsrv/gtids/gtid_set.hpp +++ b/src/binsrv/gtids/gtid_set.hpp @@ -34,6 +34,7 @@ namespace binsrv::gtids { class gtid_set { public: static constexpr char uuid_separator{','}; + static constexpr char uuid_separator_whitespace{' '}; static constexpr char interval_separator{'-'}; gtid_set(); @@ -43,6 +44,8 @@ class gtid_set { // NOLINTNEXTLINE(hicpp-explicit-conversions) gtid_set(const gtid &value) : gtid_set() { *this += value; } + explicit gtid_set(std::string_view value); + explicit gtid_set(util::const_byte_span portion); gtid_set(const gtid_set &other); @@ -55,6 +58,8 @@ class gtid_set { [[nodiscard]] bool is_empty() const noexcept { return data_.empty(); } [[nodiscard]] bool contains_tags() const noexcept; + [[nodiscard]] std::string str() const; + [[nodiscard]] std::size_t calculate_encoded_size() const noexcept; void encode_to(util::byte_span &destination) const; @@ -82,8 +87,6 @@ class gtid_set { 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; diff --git a/src/binsrv/gtids/gtid_set_fwd.hpp b/src/binsrv/gtids/gtid_set_fwd.hpp index 4f915a1..30995de 100644 --- a/src/binsrv/gtids/gtid_set_fwd.hpp +++ b/src/binsrv/gtids/gtid_set_fwd.hpp @@ -19,13 +19,19 @@ #include #include +#include "util/nv_tuple_json_support.hpp" + namespace binsrv::gtids { class gtid_set; using optional_gtid_set = std::optional; std::ostream &operator<<(std::ostream &output, const gtid_set &obj); +std::istream &operator>>(std::istream &input, gtid_set &obj); } // namespace binsrv::gtids +template <> +struct util::is_string_convertable : std::true_type {}; + #endif // BINSRV_GTIDS_GTID_SET_FWD_HPP diff --git a/src/binsrv/gtids/tag.cpp b/src/binsrv/gtids/tag.cpp index b6a1fbf..5b22578 100644 --- a/src/binsrv/gtids/tag.cpp +++ b/src/binsrv/gtids/tag.cpp @@ -59,7 +59,7 @@ tag::tag(std::string_view name) { 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"); + "tag name must include only alphanumeric characters or underscores"); } *data_it = static_cast(current_ch); } diff --git a/src/binsrv/gtids/uuid.hpp b/src/binsrv/gtids/uuid.hpp index e61c8ee..53c4f93 100644 --- a/src/binsrv/gtids/uuid.hpp +++ b/src/binsrv/gtids/uuid.hpp @@ -31,6 +31,9 @@ namespace binsrv::gtids { class uuid { public: + static constexpr std::size_t readable_size{36U}; + static constexpr char group_separator{'-'}; + uuid() noexcept = default; explicit uuid(std::string_view value); diff --git a/src/binsrv/size_unit.cpp b/src/binsrv/size_unit.cpp index d4bb120..f6ae389 100644 --- a/src/binsrv/size_unit.cpp +++ b/src/binsrv/size_unit.cpp @@ -18,9 +18,14 @@ #include #include #include +#include +#include +#include #include #include +#include #include +#include #include #include @@ -81,4 +86,22 @@ size_unit::size_unit(std::string_view value_sv) shift_index_ = static_cast(std::distance(shifts_bg, shifts_it)); } +std::ostream &operator<<(std::ostream &output, const size_unit &unit) { + return output << unit.to_string(); +} + +std::istream &operator>>(std::istream &input, size_unit &unit) { + std::string unit_str; + input >> unit_str; + if (!input) { + return input; + } + try { + unit = size_unit{unit_str}; + } catch (const std::exception &) { + input.setstate(std::ios_base::failbit); + } + return input; +} + } // namespace binsrv diff --git a/src/binsrv/size_unit.hpp b/src/binsrv/size_unit.hpp index cc02066..2c3d23f 100644 --- a/src/binsrv/size_unit.hpp +++ b/src/binsrv/size_unit.hpp @@ -19,11 +19,8 @@ #include "binsrv/size_unit_fwd.hpp" // IWYU pragma: export #include -#include #include -#include -#include -#include +#include #include #include @@ -74,30 +71,6 @@ class [[nodiscard]] size_unit { // clang-format on }; -template - requires std::same_as -std::basic_ostream & -operator<<(std::basic_ostream &output, const size_unit &unit) { - return output << unit.to_string(); -} - -template - requires std::same_as -std::basic_istream & -operator>>(std::basic_istream &input, size_unit &unit) { - std::string unit_str; - input >> unit_str; - if (!input) { - return input; - } - try { - unit = size_unit{unit_str}; - } catch (const std::exception &) { - input.setstate(std::ios_base::failbit); - } - return input; -} - } // namespace binsrv #endif // BINSRV_SIZE_UNIT_HPP diff --git a/src/binsrv/size_unit_fwd.hpp b/src/binsrv/size_unit_fwd.hpp index 96cb472..96113cb 100644 --- a/src/binsrv/size_unit_fwd.hpp +++ b/src/binsrv/size_unit_fwd.hpp @@ -16,10 +16,8 @@ #ifndef BINSRV_SIZE_UNIT_FWD_HPP #define BINSRV_SIZE_UNIT_FWD_HPP -#include -#include +#include #include -#include #include "util/nv_tuple_json_support.hpp" @@ -29,15 +27,9 @@ class size_unit; using optional_size_unit = std::optional; -template - requires std::same_as -std::basic_ostream & -operator<<(std::basic_ostream &output, const size_unit &unit); +std::ostream &operator<<(std::ostream &output, const size_unit &unit); -template - requires std::same_as -std::basic_istream & -operator>>(std::basic_istream &input, size_unit &unit); +std::istream &operator>>(std::istream &input, size_unit &unit); } // namespace binsrv diff --git a/src/binsrv/time_unit.cpp b/src/binsrv/time_unit.cpp index 4df0a34..d437269 100644 --- a/src/binsrv/time_unit.cpp +++ b/src/binsrv/time_unit.cpp @@ -18,9 +18,14 @@ #include #include #include +#include +#include +#include #include #include +#include #include +#include #include #include @@ -82,4 +87,22 @@ time_unit::time_unit(std::string_view value_sv) static_cast(std::distance(multipliers_bg, multipliers_it)); } +std::ostream &operator<<(std::ostream &output, const time_unit &unit) { + return output << unit.to_string(); +} + +std::istream &operator>>(std::istream &input, time_unit &unit) { + std::string unit_str; + input >> unit_str; + if (!input) { + return input; + } + try { + unit = time_unit{unit_str}; + } catch (const std::exception &) { + input.setstate(std::ios_base::failbit); + } + return input; +} + } // namespace binsrv diff --git a/src/binsrv/time_unit.hpp b/src/binsrv/time_unit.hpp index c87cb2a..41ee2e9 100644 --- a/src/binsrv/time_unit.hpp +++ b/src/binsrv/time_unit.hpp @@ -19,11 +19,8 @@ #include "binsrv/time_unit_fwd.hpp" // IWYU pragma: export #include -#include #include -#include -#include -#include +#include #include #include @@ -71,30 +68,6 @@ class [[nodiscard]] time_unit { // clang-format on }; -template - requires std::same_as -std::basic_ostream & -operator<<(std::basic_ostream &output, const time_unit &unit) { - return output << unit.to_string(); -} - -template - requires std::same_as -std::basic_istream & -operator>>(std::basic_istream &input, time_unit &unit) { - std::string unit_str; - input >> unit_str; - if (!input) { - return input; - } - try { - unit = time_unit{unit_str}; - } catch (const std::exception &) { - input.setstate(std::ios_base::failbit); - } - return input; -} - } // namespace binsrv #endif // BINSRV_TIME_UNIT_HPP diff --git a/src/binsrv/time_unit_fwd.hpp b/src/binsrv/time_unit_fwd.hpp index 8cca993..cf3ffc5 100644 --- a/src/binsrv/time_unit_fwd.hpp +++ b/src/binsrv/time_unit_fwd.hpp @@ -16,10 +16,8 @@ #ifndef BINSRV_TIME_UNIT_FWD_HPP #define BINSRV_TIME_UNIT_FWD_HPP -#include -#include +#include #include -#include #include "util/nv_tuple_json_support.hpp" @@ -29,15 +27,9 @@ class time_unit; using optional_time_unit = std::optional; -template - requires std::same_as -std::basic_ostream & -operator<<(std::basic_ostream &output, const time_unit &unit); +std::ostream &operator<<(std::ostream &output, const time_unit &unit); -template - requires std::same_as -std::basic_istream & -operator>>(std::basic_istream &input, time_unit &unit); +std::istream &operator>>(std::istream &input, time_unit &unit); } // namespace binsrv diff --git a/src/util/bnf_parser_helpers.hpp b/src/util/bnf_parser_helpers.hpp new file mode 100644 index 0000000..c483cda --- /dev/null +++ b/src/util/bnf_parser_helpers.hpp @@ -0,0 +1,87 @@ +// 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 UTIL_BNF_PARSER_HELPERS_HPP +#define UTIL_BNF_PARSER_HELPERS_HPP + +#include +#include + +namespace util { + +[[nodiscard]] inline bool check_character(std::string_view &remainder, + char expected_character) noexcept { + if (remainder.empty()) { + return false; + } + return remainder.front() == expected_character; +} + +[[nodiscard]] inline bool parse_character_ex(std::string_view &remainder, + char expected_character, + char &result) noexcept { + if (!check_character(remainder, expected_character)) { + return false; + } + result = expected_character; + remainder.remove_prefix(1U); + return true; +} + +inline char parse_character(std::string_view &remainder, + char expected_character) { + char result{}; + if (!parse_character_ex(remainder, expected_character, result)) { + throw std::invalid_argument{"character does not match the expected one"}; + } + return result; +} + +template +[[nodiscard]] inline bool +check_character_predicate(std::string_view &remainder, + Predicate predicate) noexcept { + if (remainder.empty()) { + return false; + } + return predicate(remainder.front()); +} + +template +[[nodiscard]] inline bool +parse_character_predicate_ex(std::string_view &remainder, Predicate predicate, + char &result) noexcept { + if (!check_character_predicate(remainder, predicate)) { + return false; + } + result = remainder.front(); + remainder.remove_prefix(1U); + return true; +} + +template +inline char parse_character_predicate(std::string_view &remainder, + Predicate predicate) { + char result{}; + if (!parse_character_predicate_ex(remainder, predicate, result)) { + throw std::invalid_argument{ + "character does not satisfy the specified predicate"}; + } + return result; +} + +} // namespace util + +#endif // UTIL_BNF_PARSER_HELPERS_HPP diff --git a/src/util/conversion_helpers.hpp b/src/util/conversion_helpers.hpp index e749790..13b310d 100644 --- a/src/util/conversion_helpers.hpp +++ b/src/util/conversion_helpers.hpp @@ -13,8 +13,8 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -#ifndef UTIL_CONVERSION_HELPOERS_HPP -#define UTIL_CONVERSION_HELPOERS_HPP +#ifndef UTIL_CONVERSION_HELPERS_HPP +#define UTIL_CONVERSION_HELPERS_HPP #include #include @@ -48,4 +48,4 @@ template } // namespace util -#endif // UTIL_CONVERSION_HELPOERS_HPP +#endif // UTIL_CONVERSION_HELPERS_HPP diff --git a/tests/gtid_set_test.cpp b/tests/gtid_set_test.cpp index 1f098d1..2c6e7b9 100644 --- a/tests/gtid_set_test.cpp +++ b/tests/gtid_set_test.cpp @@ -40,11 +40,11 @@ #include "util/byte_span_fwd.hpp" static constexpr std::string_view first_uuid_sv{ - "11111111-1111-1111-1111-111111111111"}; + "11111111-aaaa-1111-aaaa-111111111111"}; static constexpr std::string_view second_uuid_sv{ - "22222222-2222-2222-2222-222222222222"}; + "22222222-bbbb-2222-bbbb-222222222222"}; static constexpr std::string_view third_uuid_sv{ - "33333333-3333-3333-3333-333333333333"}; + "33333333-cccc-3333-cccc-333333333333"}; BOOST_AUTO_TEST_CASE(GtidSetDefaultConstruction) { const binsrv::gtids::gtid_set empty_gtid_set{}; @@ -169,22 +169,22 @@ BOOST_AUTO_TEST_CASE(GtidSetOperatorPlus) { 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"); + "11111111-aaaa-1111-aaaa-111111111111:1:3, " + "22222222-bbbb-2222-bbbb-222222222222:1-6, " + "33333333-cccc-3333-cccc-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"); + "11111111-aaaa-1111-aaaa-111111111111:1-3, " + "22222222-bbbb-2222-bbbb-222222222222:1-6, " + "33333333-cccc-3333-cccc-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"); + "11111111-aaaa-1111-aaaa-111111111111:1-3, " + "22222222-bbbb-2222-bbbb-222222222222:1-6, " + "33333333-cccc-3333-cccc-333333333333:1-3"); } BOOST_AUTO_TEST_CASE(GtidSetAddIntervalUntagged) { @@ -198,7 +198,7 @@ BOOST_AUTO_TEST_CASE(GtidSetAddIntervalUntagged) { // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) BOOST_CHECK_EQUAL(boost::lexical_cast(gtids), - "11111111-1111-1111-1111-111111111111:1-3:5-7"); + "11111111-aaaa-1111-aaaa-111111111111:1-3:5-7"); } BOOST_AUTO_TEST_CASE(GtidSetAddIntervalTagged) { @@ -212,7 +212,7 @@ BOOST_AUTO_TEST_CASE(GtidSetAddIntervalTagged) { // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) BOOST_CHECK_EQUAL(boost::lexical_cast(gtids), - "11111111-1111-1111-1111-111111111111:alpha:1-3:5-7"); + "11111111-aaaa-1111-aaaa-111111111111:alpha:1-3:5-7"); } BOOST_AUTO_TEST_CASE(GtidSetClear) { @@ -342,7 +342,16 @@ BOOST_FIXTURE_TEST_CASE(GtidSetEncodingMixed, gtid_set_encoding_fixture) { check_encoding_roundtrip(gtids); } -BOOST_AUTO_TEST_CASE(GtidSetOstreamOperatorUntagged) { +BOOST_AUTO_TEST_CASE(GtidSetStreamOperatorEmpty) { + const binsrv::gtids::gtid_set gtids{}; + + const auto gtids_str{boost::lexical_cast(gtids)}; + BOOST_CHECK_EQUAL(gtids_str, ""); + const auto restored_gtids{binsrv::gtids::gtid_set{gtids_str}}; + BOOST_CHECK_EQUAL(gtids, restored_gtids); +} + +BOOST_AUTO_TEST_CASE(GtidSetStreamOperatorUntagged) { const binsrv::gtids::uuid first_uuid{first_uuid_sv}; const binsrv::gtids::uuid second_uuid{second_uuid_sv}; @@ -359,12 +368,14 @@ BOOST_AUTO_TEST_CASE(GtidSetOstreamOperatorUntagged) { 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"); + const auto gtids_str{boost::lexical_cast(gtids)}; + BOOST_CHECK_EQUAL(gtids_str, "11111111-aaaa-1111-aaaa-111111111111:1-3:5, " + "22222222-bbbb-2222-bbbb-222222222222:11-13:15"); + const auto restored_gtids{binsrv::gtids::gtid_set{gtids_str}}; + BOOST_CHECK_EQUAL(gtids, restored_gtids); } -BOOST_AUTO_TEST_CASE(GtidSetOstreamOperatorTagged) { +BOOST_AUTO_TEST_CASE(GtidSetStreamOperatorTagged) { const binsrv::gtids::uuid first_uuid{first_uuid_sv}; const binsrv::gtids::uuid second_uuid{second_uuid_sv}; @@ -394,14 +405,17 @@ BOOST_AUTO_TEST_CASE(GtidSetOstreamOperatorTagged) { 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:" + const auto gtids_str{boost::lexical_cast(gtids)}; + BOOST_CHECK_EQUAL(gtids_str, + "11111111-aaaa-1111-aaaa-111111111111:alpha:111-113:115:" "beta:121-123:125, " - "22222222-2222-2222-2222-222222222222:alpha:211-213:215:" + "22222222-bbbb-2222-bbbb-222222222222:alpha:211-213:215:" "beta:221-223:225"); + const auto restored_gtids{binsrv::gtids::gtid_set{gtids_str}}; + BOOST_CHECK_EQUAL(gtids, restored_gtids); } -BOOST_AUTO_TEST_CASE(GtidSetOstreamOperatorMixed) { +BOOST_AUTO_TEST_CASE(GtidSetStreamOperatorMixed) { const binsrv::gtids::uuid first_uuid{first_uuid_sv}; const binsrv::gtids::uuid second_uuid{second_uuid_sv}; @@ -441,9 +455,12 @@ BOOST_AUTO_TEST_CASE(GtidSetOstreamOperatorMixed) { 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:alpha:" + const auto gtids_str{boost::lexical_cast(gtids)}; + BOOST_CHECK_EQUAL(gtids_str, + "11111111-aaaa-1111-aaaa-111111111111:101-103:105:alpha:" "111-113:115:beta:121-123:125, " - "22222222-2222-2222-2222-222222222222:201-203:205:alpha:" + "22222222-bbbb-2222-bbbb-222222222222:201-203:205:alpha:" "211-213:215:beta:221-223:225"); + const auto restored_gtids{binsrv::gtids::gtid_set{gtids_str}}; + BOOST_CHECK_EQUAL(gtids, restored_gtids); }