MySQL 9.6.0
Source Code Documentation
gtid_binary_format_conv.h
Go to the documentation of this file.
1// Copyright (c) 2025, Oracle and/or its affiliates.
2//
3// This program is free software; you can redistribute it and/or modify
4// it under the terms of the GNU General Public License, version 2.0,
5// as published by the Free Software Foundation.
6//
7// This program is designed to work with certain software (including
8// but not limited to OpenSSL) that is licensed under separate terms,
9// as designated in a particular file or component or in included license
10// documentation. The authors of MySQL hereby grant you an additional
11// permission to link the program and your derivative works with the
12// separately licensed software that they have either included with
13// the program or referenced in the documentation.
14//
15// This program is distributed in the hope that it will be useful,
16// but WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License, version 2.0, for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with this program; if not, write to the Free Software
22// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23
24#ifndef MYSQL_GTIDS_STRCONV_GTID_BINARY_FORMAT_CONV_H
25#define MYSQL_GTIDS_STRCONV_GTID_BINARY_FORMAT_CONV_H
26
27/// @file
28/// Experimental API header
29
30#include <map> // map
31#include <new> // bad_alloc
32#include "mysql/gtids/gtid.h" // Is_gtid
33#include "mysql/gtids/gtid_set.h" // Is_gtid_set
34#include "mysql/gtids/has_tags.h" // has_tags
35#include "mysql/gtids/strconv/gtid_binary_format.h" // Gtid_binary_format
36#include "mysql/gtids/tag.h" // Is_tag
37#include "mysql/gtids/tsid.h" // Is_tsid
38#include "mysql/strconv/strconv.h" // Is_string_target
39#include "mysql/utils/enumeration_utils.h" // to_underlying
40
41/// @addtogroup GroupLibsMysqlGtids
42/// @{
43
44namespace mysql::strconv {
45
46// ==== Tags ====
47
49 Is_string_target auto &target,
50 const mysql::gtids::Is_tag auto &tag) {
51 switch (format.m_version_policy) {
53 assert(tag.empty());
54 break;
58 target.write(format, tag.string_view());
59 break;
60 }
61}
62
64 mysql::gtids::Is_tag auto &tag) {
65 if (format.m_version_policy ==
67 tag.clear();
68 return;
69 }
70 // Get a string_view that points into the input.
71 std::string_view sv;
73 return;
75 parser.set_parse_error("Invalid tag");
76 return;
77 }
78 // Copy and normalize characters.
79 [[maybe_unused]] auto ret =
80 mysql::utils::void_to_ok([&] { return tag.assign(sv); });
81 // Can't fail because is_valid returned true and the tag does not allocate.
83}
84
85// ==== Tsids ====
86
88 Is_string_target auto &string_target,
89 const mysql::gtids::Is_tsid auto &tsid) {
90 string_target.concat(format, tsid.uuid(), tsid.tag());
91}
92
94 mysql::gtids::Is_tsid auto &tsid) {
97 return;
98 std::ignore = parser.read(format, tsid.tag());
99}
100
101// ==== Gtids ====
102
104 Is_string_target auto &string_target,
105 const mysql::gtids::Is_gtid auto &gtid) {
106 string_target.write(format, gtid.tsid());
107 string_target.write(Binary_format{}, gtid.get_sequence_number());
108}
109
111 mysql::gtids::Is_gtid auto &gtid) {
114 return;
115 mysql::gtids::Sequence_number sequence_number;
116 auto check_sequence_number = Checker([&] {
117 if (!mysql::gtids::is_valid_sequence_number(sequence_number)) {
118 parser.set_parse_error("GTID sequence number out of range");
119 }
120 });
121 if (parser.read(Binary_format{} | check_sequence_number, sequence_number) !=
123 return;
124 [[maybe_unused]] auto ret = gtid.set_sequence_number(sequence_number);
125 assert(ret == mysql::utils::Return_status::ok);
126}
127
128} // namespace mysql::strconv
129
130// ==== Gtid sets ====
131
132namespace mysql::gtids::detail {
133// Todo: move to math library?
134/// Return a value of the given integer type having the low N bits set to 1.
135template <std::integral Int_t = uint64_t>
136constexpr auto low_bits(int n) {
137 assert(n >= 0);
139 if (n == std::numeric_limits<Int_t>::digits) return ~Int_t(0);
140 return (Int_t(1) << n) - Int_t(1);
141}
142
143/// Helper to decode format version and tsid count in formats v0, v1, v2.
144///
145/// @verbatim
146/// v0:
147/// tsid_count: 6 byte little-endian
148/// unused: 1 byte, value 0
149/// version: 1 byte, value 0
150/// v1 and v2:
151/// version: 1 byte, value 1 or 2
152/// tsid_count: 6 byte, little-endian
153/// version: 1 byte, value 1 or 2
154/// @endverbatim
156 static constexpr auto version_mask = low_bits(8);
157 static constexpr auto version_shift0 = 56;
158 static constexpr auto version_shift1 = 0;
159 static constexpr auto tsid_count_mask = low_bits(48);
160 static constexpr auto tsid_count_shift0 = 0;
161 static constexpr auto tsid_count_shift1 = 8;
162
164 std::size_t m_tsid_count;
165};
166
167} // namespace mysql::gtids::detail
168
169namespace mysql::strconv {
170
171void encode_impl(const Gtid_binary_format &format [[maybe_unused]],
172 Is_string_target auto &string_target,
176
177 uint64_t code{0};
178 switch (header.m_version) {
179 case Version::v0_tagless:
180 code = header.m_tsid_count;
181 break;
182 case Version::v1_tags:
183 case Version::v2_tags_compact: {
184 auto version_byte =
185 uint64_t(mysql::utils::to_underlying(header.m_version));
186 code = (version_byte << Gtid_set_header::version_shift0) |
187 (version_byte << Gtid_set_header::version_shift1) |
188 (header.m_tsid_count << Gtid_set_header::tsid_count_shift1);
189 } break;
190 }
191 string_target.write(Fixint_binary_format{}, code);
192}
193
197 using Version_policy = Gtid_binary_format::Version_policy;
199
200 uint64_t header_word;
202
203 auto check_version = Checker([&] {
204 auto version_byte0 = int((header_word >> Gtid_set_header::version_shift0) &
205 Gtid_set_header::version_mask);
206 // Read the version from where it is stored in format 0 (byte 7).
207 auto [version0, status] =
208 mysql::utils::to_enumeration<Version>(version_byte0);
211 "Unknown (future?) GTID set format version number in GTID encoding");
212 return;
213 }
214 if (version0 != Version::v0_tagless) {
215 // Read the version from where it is stored in versions > 0 (byte 0)
216 auto version_byte1 =
217 int((header_word >> Gtid_set_header::version_shift1) &
218 Gtid_set_header::version_mask);
219 // Require the two redundant version numbers to match
220 if (version_byte0 != version_byte1) {
222 "Inconsistent GTID set format version numbers in GTID encoding");
223 return;
224 }
225 }
226 // If caller specified a concrete format (rather than automatic), require
227 // that the actual format on wire matches.
228 if (format.m_version_policy != Version_policy::automatic &&
229 format.m_version_policy !=
232 "Disallowed GTID set format version number in GTID encoding");
233 return;
234 }
235 version = version0;
236 });
237
238 if (parser.read(Fixint_binary_format{} | check_version, header_word) !=
240 return;
241
242 out.m_version = version;
243
244 auto tsid_shift = (version == Version::v0_tagless)
245 ? Gtid_set_header::tsid_count_shift0
246 : Gtid_set_header::tsid_count_shift1;
247 auto tsid_count =
248 (header_word >> tsid_shift) & Gtid_set_header::tsid_count_mask;
249 out.m_tsid_count = tsid_count;
250}
251
252namespace detail {
253
255 Is_string_target auto &string_target,
256 const mysql::gtids::Is_gtid_set auto &gtid_set) {
257 for (const auto &[tsid, interval_set] : gtid_set) {
258 string_target.write(format, tsid);
259 string_target.write(Fixint_binary_format{}, interval_set);
260 }
261}
262
264 Is_string_target auto &string_target,
265 const mysql::gtids::Is_gtid_set auto &gtid_set) {
266 // Don't waste bytes on tag count for empty sets.
267 if (gtid_set.empty()) return;
268
269 // Compute set of tags.
270 using Tag_map = std::map<mysql::gtids::Tag, std::size_t>;
271 using Tag_map_value = Tag_map::value_type;
272 Tag_map tag_map;
273 for (const auto &[tsid, interval_set] : gtid_set) {
274 tag_map.insert(Tag_map_value{mysql::gtids::Tag{tsid.tag()}, 0});
275 // Todo: uncaught std::bad_alloc
276 }
277
278 // Write and enumerate tags
279 string_target.write(format, tag_map.size());
280 {
281 std::size_t tag_index = 0;
282 for (auto &[tag, number] : tag_map) {
283 string_target.write(format, tag);
284 number = tag_index;
285 ++tag_index;
286 }
287 }
288
289 // Write interval sets
290 {
291 std::optional<mysql::uuids::Uuid> last_uuid;
292 for (const auto &[tsid, interval_set] : gtid_set) {
293 bool is_new_uuid = !last_uuid.has_value() || *last_uuid != tsid.uuid();
294 uint64_t code = tag_map[tsid.tag()] << 1;
295 if (is_new_uuid) code |= 1;
296 string_target.write(format, code);
297 if (is_new_uuid) {
298 string_target.write(format, tsid.uuid());
299 }
300 string_target.write(format, interval_set);
301 last_uuid = tsid.uuid();
302 }
303 }
304}
305
306} // namespace detail
307
309 Is_string_target auto &string_target,
310 const mysql::gtids::Is_gtid_set auto &gtid_set) {
311 using Version_policy = Gtid_binary_format::Version_policy;
314 auto size = gtid_set.size();
315 switch (format.m_version_policy) {
316 case Version_policy::v0_tagless:
317 string_target.write(format, Header{Version::v0_tagless, size});
318 detail::encode_v0_v1(format, string_target, gtid_set);
319 break;
320 case Version_policy::v1_tags:
321 string_target.write(format, Header{Version::v1_tags, size});
322 detail::encode_v0_v1(format, string_target, gtid_set);
323 break;
324 case Version_policy::v2_tags_compact:
325 string_target.write(format, Header{Version::v2_tags_compact, size});
326 detail::encode_v2(format, string_target, gtid_set);
327 break;
328 case Version_policy::automatic:
329 // Compute the "best" format version to use. This policy has to weigh the
330 // improvements of newer formats against compatibility of older formats.
331 // So when v1 has existed in 2 major versions, we can stop falling back to
332 // v0, and when v2 has existed in 2 major versions, we can use that.
333 auto version = mysql::gtids::has_tags(gtid_set) ? Version::v1_tags
334 : Version::v0_tagless;
335 string_target.write(format, Header{version, size});
338 string_target, gtid_set);
339 break;
340 }
341}
342
343namespace detail {
344
346 mysql::gtids::Is_gtid_set auto &gtid_set,
347 std::size_t tsid_count) {
348 auto fluent = parser.fluent(format);
351 fluent.call_exact(tsid_count, [&] {
352 fluent.read(tsid)
353 .read_with_format(Fixint_binary_format{}, interval_set)
354 .check_prev_token([&] {
355 if (gtid_set.inplace_union(tsid, std::move(interval_set)) !=
357 parser.set_oom();
358 return;
359 }
360 // It is valid to clear a set after moving from it, because clear
361 // has no preconditions.
362 // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved)
363 interval_set.clear();
364 });
365 });
366}
367
368void decode_v2(const Gtid_binary_format &format [[maybe_unused]],
369 Parser &parser, mysql::gtids::Is_gtid_set auto &gtid_set,
370 std::size_t tsid_count) {
371 static auto return_ok = mysql::utils::Return_status::ok;
372
373 // If the set is empty, it does not contain the tag count.
374 if (tsid_count == 0) return;
375
376 std::size_t tag_count{0};
377 std::vector<mysql::gtids::Tag> tags;
379 bool is_first_tsid{true};
380 auto fluent = parser.fluent(Binary_format{});
381 fluent
382 // Read tags
383 .read(tag_count)
384 .check_prev_token([&] {
385 try {
386 tags.reserve(tag_count);
387 } catch (std::bad_alloc &) {
388 parser.set_oom();
389 }
390 })
391 .call_exact(
392 tag_count,
393 [&] {
395 if (parser.read(Binary_format{}, tag) != return_ok) return;
396 tags.emplace_back(tag); // can't oom because of 'reserve' above
397 })
398 // Read (Gtid, Gtid_interval_set) pairs
399 .call_exact(tsid_count, [&] {
400 // Read code containing tag index and uuid flag
401 uint64_t code{0};
402 std::size_t tag_index{0};
403 bool new_uuid{false};
404 auto check_code = Checker([&] {
405 tag_index = std::size_t(code >> 1);
406 if (tag_index >= tags.size()) {
407 parser.set_parse_error("Tag index out of range");
408 }
409 new_uuid = ((code & 1) != 0);
410 if (is_first_tsid && !new_uuid) {
411 parser.set_parse_error("No UUID given for first Tsid");
412 return;
413 }
414 is_first_tsid = false;
415 });
416 if (parser.read(Binary_format{} | check_code, code) != return_ok)
417 return;
418
419 // Get tag (can't oom because tsid does not throw).
420 tsid.tag().assign(tags[tag_index]);
421
422 // If uuid flag is set, read uuid. (Otherwise reuse previous uuid)
423 if (new_uuid) {
424 if (parser.read(Binary_format{}, tsid.uuid()) != return_ok) return;
425 }
426
427 // Read interval set and insert into set
429 auto check_interval_set = Checker([&] {
430 if (gtid_set.inplace_union(tsid, std::move(interval_set)) !=
431 return_ok) {
432 parser.set_oom();
433 }
434 });
435 if (parser.read(Binary_format{} | check_interval_set, interval_set) !=
436 return_ok)
437 return;
438 });
439}
440
441} // namespace detail
442
444 mysql::gtids::Is_gtid_set auto &gtid_set) {
446
448 if (parser.read(format, header) != mysql::utils::Return_status::ok) return;
449 Gtid_binary_format concrete_format{
450 Gtid_binary_format::to_version_policy(header.m_version)};
451 switch (header.m_version) {
452 case Version::v0_tagless:
453 case Version::v1_tags:
454 detail::decode_v0_v1(concrete_format, parser, gtid_set,
455 header.m_tsid_count);
456 break;
457 case Version::v2_tags_compact:
458 detail::decode_v2(concrete_format, parser, gtid_set, header.m_tsid_count);
459 break;
460 }
461}
462
463} // namespace mysql::strconv
464
465// addtogroup GroupLibsMysqlGtids
466/// @}
467
468#endif // ifndef MYSQL_GTIDS_STRCONV_GTID_BINARY_FORMAT_CONV_H
Class representing a version.
Definition: designator.h:45
Class that defines the Interval set type used for Gtid intervals.
Definition: gtid_set.h:123
Class representing a tag by storing the characters in a member array.
Definition: tag.h:353
Definition: tsid.h:90
static bool is_valid(const std::string_view &sv)
Return true if the given string is a valid tag.
Definition: tag.h:148
const auto & tag() const
Definition: tsid.h:75
const mysql::uuids::Uuid & uuid() const
Definition: tsid.h:72
void clear() noexcept
Make this container empty.
Definition: interval_container.h:188
Class holding a checker function, used to check the validity of a parsed value.
Definition: checker.h:57
Object used to parse strings.
Definition: parser.h:69
Return_status_t read(const Is_parse_options auto &opt, Object_t &obj)
Parse into the given object.
Definition: parser.h:225
void set_parse_error(const std::string_view &message)
Store a result representing that the requested object could not be parsed because the string is wrong...
Definition: parser.h:94
True for all Gtid set types.
Definition: gtid_set.h:178
Definition: gtid.h:53
True if Test is one of the tag classes.
Definition: tag.h:192
Definition: tsid.h:50
Concept that holds for String_counter and String_writer.
Definition: string_target.h:111
Experimental API header.
Experimental API header.
Experimental API header.
Experimental API header.
Experimental API header.
Experimental API header.
Experimental API header.
Experimental API header.
struct Parser parser
uint16_t value_type
Definition: vt100.h:184
std::string format(const routing_guidelines::Session_info &session_info, bool extended_session_info)
Definition: dest_metadata_cache.cc:170
Definition: fts0fts.cc:236
Definition: gtid.h:47
constexpr auto low_bits(int n)
Return a value of the given integer type having the low N bits set to 1.
Definition: gtid_binary_format_conv.h:136
uint64_t Sequence_number
The type of the sequence number component of a GTID.
Definition: sequence_number.h:39
constexpr bool is_valid_sequence_number(Sequence_number sequence_number)
Return true if the given Sequence_number is in the allowed range.
Definition: sequence_number.h:53
bool has_tags(const Is_tag auto &tag)
Definition: has_tags.h:41
void decode_v2(const Gtid_binary_format &format, Parser &parser, mysql::gtids::Is_gtid_set auto &gtid_set, std::size_t tsid_count)
Definition: gtid_binary_format_conv.h:368
void decode_v0_v1(const Gtid_binary_format &format, Parser &parser, mysql::gtids::Is_gtid_set auto &gtid_set, std::size_t tsid_count)
Definition: gtid_binary_format_conv.h:345
void encode_v0_v1(const Gtid_binary_format &format, Is_string_target auto &string_target, const mysql::gtids::Is_gtid_set auto &gtid_set)
Definition: gtid_binary_format_conv.h:254
void encode_v2(const Gtid_binary_format &format, Is_string_target auto &string_target, const mysql::gtids::Is_gtid_set auto &gtid_set)
Definition: gtid_binary_format_conv.h:263
Definition: gtid_binary_format.h:41
void decode_impl(const Gtid_binary_format &format, Parser &parser, mysql::gtids::Is_gtid_set auto &gtid_set)
Definition: gtid_binary_format_conv.h:443
void decode_impl(const Gtid_binary_format &format, Parser &parser, mysql::gtids::Is_tag auto &tag)
Definition: gtid_binary_format_conv.h:63
void encode_impl(const Gtid_binary_format &format, Is_string_target auto &target, const mysql::gtids::Is_tag auto &tag)
Definition: gtid_binary_format_conv.h:48
Return_status
Simple, strongly-typed enumeration to indicate internal status: ok, error.
Definition: return_status.h:40
@ ok
operation succeeded
Return_t void_to_ok(const Func_t &func, Args_t &&...args)
Helper that calls the given function and returns its result, or returns Return_status::ok if the func...
Definition: return_status.h:113
constexpr decltype(auto) to_underlying(Enum_type enum_value)
Helper function that converts enum type to underlying integer type.
Definition: enumeration_utils.h:51
size_t size(const char *const c)
Definition: base64.h:46
required uint32 status
Definition: replication_asynchronous_connection_failover.proto:61
required uint64 version
Definition: replication_group_member_actions.proto:41
static const char digits[]
Definition: stacktrace.cc:644
Helper to decode format version and tsid count in formats v0, v1, v2.
Definition: gtid_binary_format_conv.h:155
static constexpr auto version_mask
Definition: gtid_binary_format_conv.h:156
static constexpr auto tsid_count_shift0
Definition: gtid_binary_format_conv.h:160
static constexpr auto tsid_count_mask
Definition: gtid_binary_format_conv.h:159
static constexpr auto version_shift1
Definition: gtid_binary_format_conv.h:158
std::size_t m_tsid_count
Definition: gtid_binary_format_conv.h:164
static constexpr auto tsid_count_shift1
Definition: gtid_binary_format_conv.h:161
mysql::strconv::Gtid_binary_format::Version m_version
Definition: gtid_binary_format_conv.h:163
static constexpr auto version_shift0
Definition: gtid_binary_format_conv.h:157
Format tag to identify binary format.
Definition: binary_format.h:38
Format tag to identify fixed-length-integer binary format.
Definition: fixint_binary_format.h:42
Definition: gtid_binary_format.h:43
static Version_policy to_version_policy(Version version)
Returns the Version_policy that specifies the given concrete version.
Definition: gtid_binary_format.h:83
Version_policy
Policy for choosing a version.
Definition: gtid_binary_format.h:62
@ automatic
Encode using an automatically selected format.
Version
The format version. See readme.md for format specifications.
Definition: gtid_binary_format.h:47
int n
Definition: xcom_base.cc:509