2626#include < utility>
2727
2828#include < boost/icl/concept/interval.hpp>
29+ #include < boost/icl/concept/interval_associator.hpp>
2930#include < boost/icl/concept/interval_set.hpp>
3031
3132#include " binsrv/gtids/common_types.hpp"
3536
3637#include " util/byte_span_extractors.hpp"
3738#include " util/byte_span_fwd.hpp"
39+ #include " util/byte_span_inserters.hpp"
3840#include " util/exception_location_helpers.hpp"
3941
4042namespace binsrv ::gtids {
@@ -73,7 +75,7 @@ gtid_set::gtid_set(util::const_byte_span portion) {
7375 // as a <gtid_format>:
7476 // '0' for untagged GTIDs,
7577 // '1' for tagged GTIDs.
76- // MySQL developers also decided to duplicate this <gtid_format> also in
78+ // MySQL developers also decided to duplicate this <gtid_format> in
7779 // the very first byte (byte 0).
7880 // To sum up, the extraction rules are the following:
7981 // - if the highest byte is equal to '1', extract bytes 1..6 and put them
@@ -90,11 +92,10 @@ gtid_set::gtid_set(util::const_byte_span portion) {
9092 // tagged and untagget encodings)
9193 const auto header_parser{[](std::uint64_t value) {
9294 static constexpr std::size_t format_bit_width{8U };
93- static constexpr std::size_t format_bit_position{
94- std::numeric_limits<decltype (value)>::digits - format_bit_width};
9595
96- const auto gtid_format_field{
97- static_cast <std::uint8_t >(value >> format_bit_position)};
96+ const auto gtid_format_field{static_cast <std::uint8_t >(
97+ value >>
98+ (std::numeric_limits<std::uint64_t >::digits - format_bit_width))};
9899 // if gtid_format_field is anything but 0 or 1
99100 if ((gtid_format_field >> 1U ) != 0U ) {
100101 util::exception_location ().raise <std::invalid_argument>(
@@ -148,6 +149,118 @@ gtid_set::gtid_set(util::const_byte_span portion) {
148149
149150 process_intervals (remainder, current_uuid, current_tag);
150151 }
152+
153+ if (!remainder.empty ()) {
154+ util::exception_location ().raise <std::invalid_argument>(
155+ " extra bytes in the encoded gtid_set" );
156+ }
157+ }
158+
159+ [[nodiscard]] bool gtid_set::contains_tags () const noexcept {
160+ for (const auto &[current_uuid, current_tagged_gnos] : data_) {
161+ for (const auto &[current_tag, current_gnos] : current_tagged_gnos) {
162+ if (!current_tag.is_empty ()) {
163+ return true ;
164+ }
165+ }
166+ }
167+ return false ;
168+ }
169+
170+ [[nodiscard]] std::size_t gtid_set::calculate_encoded_size () const noexcept {
171+ const auto tagged_flag{contains_tags ()};
172+ // 8 bytes for the header (for both tahgged and untagged versions)
173+ std::size_t result{sizeof (std::uint64_t )};
174+ for (const auto &[current_uuid, current_tagged_gnos] : data_) {
175+ for (const auto &[current_tag, current_gnos] : current_tagged_gnos) {
176+ // 16 bytes for UUID
177+ result += uuid::calculate_encoded_size ();
178+ if (tagged_flag) {
179+ result += current_tag.calculate_encoded_size ();
180+ }
181+ // 8 bytes for the number of intervals
182+ result += sizeof (std::uint64_t );
183+ // 16 bytes for each interval
184+ result +=
185+ boost::icl::interval_count (current_gnos) * 2U * sizeof (std::uint64_t );
186+ }
187+ }
188+ return result;
189+ }
190+
191+ void gtid_set::encode_to (util::byte_span &destination) const {
192+ const auto tagged_flag{contains_tags ()};
193+
194+ util::byte_span remainder{destination};
195+ // skipping 8 bytes for the encoded header (number of tsids + tagged flag)
196+ remainder = remainder.subspan (sizeof (std::uint64_t ));
197+
198+ // a helper lambda to form encoded GTID set header (supports both
199+ // tagged and untagget encodings)
200+ const auto header_encoder{[](bool tagged, std::size_t number_of_tsids) {
201+ static constexpr std::size_t format_bit_width{8U };
202+ if (!tagged) {
203+ // ensuring that the value is less then 2^56
204+ if (number_of_tsids >=
205+ (1ULL << (std::numeric_limits<std::uint64_t >::digits -
206+ format_bit_width))) {
207+ util::exception_location ().raise <std::invalid_argument>(
208+ " the number of TSIDs in the untagged GTID set being encoded is too "
209+ " large" );
210+ }
211+ return std::uint64_t {number_of_tsids};
212+ }
213+
214+ // ensuring that the value is less then 2^48
215+ if (number_of_tsids >=
216+ (1ULL << (std::numeric_limits<std::uint64_t >::digits -
217+ 2U * format_bit_width))) {
218+ util::exception_location ().raise <std::invalid_argument>(
219+ " the number of TSIDs in the tagged GTID set being encoded is too "
220+ " large" );
221+ }
222+ // shifting 1 to 48 bits
223+ std::uint64_t result{1ULL << (std::numeric_limits<std::uint64_t >::digits -
224+ 2U * format_bit_width)};
225+ result |= std::uint64_t {number_of_tsids};
226+ result <<= format_bit_width;
227+ result |= 1ULL ;
228+ return result;
229+ }};
230+
231+ std::size_t number_of_tsids{0ULL };
232+ for (const auto &[current_uuid, current_tagged_gnos] : data_) {
233+ for (const auto &[current_tag, current_gnos] : current_tagged_gnos) {
234+ // 16 bytes for UUID
235+ current_uuid.encode_to (remainder);
236+ if (tagged_flag) {
237+ // varlen bytes for tag size
238+ // 1 byte for each character in the tag
239+ current_tag.encode_to (remainder);
240+ }
241+ // 8 bytes for the number of intervals
242+ util::insert_fixed_int_to_byte_span (
243+ remainder, std::uint64_t {boost::icl::interval_count (current_gnos)});
244+ for (const auto &interval : current_gnos) {
245+ // 16 bytes for each interval
246+ util::insert_fixed_int_to_byte_span (
247+ remainder, std::uint64_t {boost::icl::lower (interval)});
248+ // here we need to uncrement upper bound as we have a half-open interval
249+ // in the encoded representation and use closed interval in
250+ // boost::icl::interval_set
251+ util::insert_fixed_int_to_byte_span (
252+ remainder, std::uint64_t {boost::icl::upper (interval) + 1ULL });
253+ }
254+ ++number_of_tsids;
255+ }
256+ }
257+
258+ // writing header
259+ util::byte_span header{destination};
260+ util::insert_fixed_int_to_byte_span (
261+ header, header_encoder (tagged_flag, number_of_tsids));
262+
263+ destination = remainder;
151264}
152265
153266[[nodiscard]] bool gtid_set::contains (const gtid &value) const noexcept {
@@ -256,7 +369,7 @@ void gtid_set::process_intervals(util::const_byte_span &remainder,
256369 // TODO: validate that interval boundary values are increasing between
257370 // iterations
258371
259- // here we need to decrement upper bound as we have a halp -open interval
372+ // here we need to decrement upper bound as we have a half -open interval
260373 // in the encoded representation and use closed interval in the
261374 // gtid_set::add_interval() method
262375 --current_interval_upper;
0 commit comments