From a7a805ce6cf7f54c992e6086d7b9efd371e1988f Mon Sep 17 00:00:00 2001 From: Yura Sorokin Date: Wed, 11 Mar 2026 17:50:09 +0100 Subject: [PATCH] PS-10679 fix: cannot parse GTID set with multiple UUIDs if no space after a coma before start of next UUID https://perconadev.atlassian.net/browse/PS-10679 Fixed an issue with "binsrv::gtids::gtid_set' parser not being able to accept GTIDs without spaces between UUID groups. 'gtid_set_test' unit test (BOOST_TEST_MODULE GtidSetTests) extended with additional test case 'GtidSetWhitespaces' that checks for 'binsrv::gtids::gtid_set' -> 'std::string' -> 'binsrv::gtids::gtid_set roundtrip' with removed whitespaces. --- src/binsrv/gtids/gtid_set.cpp | 6 ++-- tests/gtid_set_test.cpp | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/binsrv/gtids/gtid_set.cpp b/src/binsrv/gtids/gtid_set.cpp index e95de5a..2ba9171 100644 --- a/src/binsrv/gtids/gtid_set.cpp +++ b/src/binsrv/gtids/gtid_set.cpp @@ -80,7 +80,7 @@ class gtid_set_parser { ::= ( ':')? ('-' )? ::= [a-zA-Z_][a-zA-Z0-9_]{0,31} ::= [0-9]+ - ::= [ \t\r\n\f\v]+ + ::= [ \t\r\n\f\v]* */ gtid_set *result_gtids_; @@ -252,10 +252,8 @@ class gtid_set_parser { parse_optionally_tagged_intervals(remainder); } - // ::= [ \t\r\n\f\v]+ + // ::= [ \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)) { diff --git a/tests/gtid_set_test.cpp b/tests/gtid_set_test.cpp index b628c37..68933be 100644 --- a/tests/gtid_set_test.cpp +++ b/tests/gtid_set_test.cpp @@ -665,3 +665,59 @@ BOOST_AUTO_TEST_CASE(GtidSetStreamOperatorMixed) { boost::lexical_cast(gtids_str)}; BOOST_CHECK_EQUAL(gtids, restored_gtids); } + +BOOST_AUTO_TEST_CASE(GtidSetWhitespaces) { + const binsrv::gtids::uuid first_uuid{first_uuid_sv}; + const binsrv::gtids::uuid second_uuid{second_uuid_sv}; + + const binsrv::gtids::tag first_tag{first_tag_sv}; + const binsrv::gtids::tag second_tag{second_tag_sv}; + + 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) + + auto gtids_str{boost::lexical_cast(gtids)}; + // erasing all space characters from the GTIDs string + + // looks like a misconfiguration in clang-tyidy-19 that doesn't know that + // std::erase() for std::string is located in the system header + + // TODO: re-check this when switching to clang-20 + + // NOLINTNEXTLINE(misc-include-cleaner) + std::erase(gtids_str, ' '); + BOOST_CHECK(gtids_str.find(' ') == std::string::npos); + const auto restored_gtids{ + boost::lexical_cast(gtids_str)}; + BOOST_CHECK_EQUAL(gtids, restored_gtids); +}