summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Jamfile.jam9
-rw-r--r--lib/bitset.cpp66
-rw-r--r--lib/bitset.h45
-rw-r--r--lib/compileTimeFormatter.h495
-rw-r--r--lib/helpers.h35
-rw-r--r--lib/mysql_types.cpp212
-rw-r--r--lib/mysql_types.h87
-rw-r--r--lib/rawDataReader.cpp47
-rw-r--r--lib/rawDataReader.h76
-rw-r--r--lib/row.cpp101
-rw-r--r--lib/row.h21
-rw-r--r--lib/streamSupport.cpp53
-rw-r--r--lib/streamSupport.h68
13 files changed, 1315 insertions, 0 deletions
diff --git a/lib/Jamfile.jam b/lib/Jamfile.jam
new file mode 100644
index 0000000..6a31bec
--- /dev/null
+++ b/lib/Jamfile.jam
@@ -0,0 +1,9 @@
+lib mygrate :
+ [ glob *.cpp ]
+ :
+ <link>static
+ <use>..//libmariadb
+ : :
+ <include>.
+ <library>..//libmariadb
+ ;
diff --git a/lib/bitset.cpp b/lib/bitset.cpp
new file mode 100644
index 0000000..bbd802d
--- /dev/null
+++ b/lib/bitset.cpp
@@ -0,0 +1,66 @@
+#include "bitset.h"
+
+namespace MyGrate {
+ static_assert(std::forward_iterator<BitSet::Iterator>);
+
+ BitSet::Iterator::Iterator(const std::byte * bb) : b {bb} { }
+
+ bool
+ BitSet::Iterator::operator*() const
+ {
+ return (*b & m) != std::byte {};
+ }
+
+ bool
+ BitSet::Iterator::operator==(const BitSet::Iterator & o) const
+ {
+ return b == o.b && m == o.m;
+ }
+
+ bool
+ BitSet::Iterator::operator!=(const BitSet::Iterator & o) const
+ {
+ return b != o.b || m != o.m;
+ }
+
+ BitSet::Iterator &
+ BitSet::Iterator::operator++()
+ {
+ if (m == std::byte {128}) {
+ b++;
+ m = std::byte {1};
+ }
+ else {
+ m <<= 1;
+ }
+ return *this;
+ }
+
+ BitSet::Iterator
+ BitSet::Iterator::operator++(int)
+ {
+ auto pre {*this};
+ ++*this;
+ return pre;
+ }
+
+ BitSet::BitSet(const std::span<const std::byte> bs) : bytes {bs} { }
+
+ std::size_t
+ BitSet::size() const
+ {
+ return bytes.size() * 8;
+ }
+
+ BitSet::Iterator
+ BitSet::begin() const
+ {
+ return BitSet::Iterator {bytes.data()};
+ }
+
+ BitSet::Iterator
+ BitSet::end() const
+ {
+ return BitSet::Iterator {bytes.data() + bytes.size()};
+ }
+}
diff --git a/lib/bitset.h b/lib/bitset.h
new file mode 100644
index 0000000..40ee3ed
--- /dev/null
+++ b/lib/bitset.h
@@ -0,0 +1,45 @@
+#ifndef MYGRATE_BITSET_H
+#define MYGRATE_BITSET_H
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace MyGrate {
+ class BitSet {
+ public:
+ class Iterator {
+ public:
+ using iterator_category = std::forward_iterator_tag;
+ using difference_type = int;
+ using value_type = bool;
+ using pointer = bool *;
+ using reference = bool;
+
+ explicit Iterator();
+ explicit Iterator(const std::byte *);
+
+ bool operator*() const;
+ bool operator!=(const Iterator &) const;
+ bool operator==(const Iterator &) const;
+
+ Iterator & operator++();
+ Iterator operator++(int);
+
+ private:
+ const std::byte * b;
+ std::byte m {1};
+ };
+
+ explicit BitSet(const std::span<const std::byte> b);
+
+ std::size_t size() const;
+ Iterator begin() const;
+ Iterator end() const;
+
+ private:
+ const std::span<const std::byte> bytes;
+ };
+}
+
+#endif
diff --git a/lib/compileTimeFormatter.h b/lib/compileTimeFormatter.h
new file mode 100644
index 0000000..6e8c813
--- /dev/null
+++ b/lib/compileTimeFormatter.h
@@ -0,0 +1,495 @@
+#ifndef ADHOCUTIL_COMPILE_TIME_FORMATTER_H
+#define ADHOCUTIL_COMPILE_TIME_FORMATTER_H
+
+#include <array>
+#include <boost/preprocessor/variadic/size.hpp>
+#include <cstring>
+#include <iostream>
+#include <optional>
+#include <sstream> // IWYU pragma: export
+
+namespace AdHoc {
+ // Template char utils
+ template<typename char_type>
+ constexpr bool
+ isdigit(const char_type & ch)
+ {
+ return (ch >= '0' && ch <= '9');
+ }
+
+ template<typename char_type>
+ constexpr bool
+ ispositivedigit(const char_type & ch)
+ {
+ return (ch >= '1' && ch <= '9');
+ }
+
+ // Template string utils
+ template<const auto S>
+ static constexpr auto
+ strlen()
+ {
+ auto off = 0U;
+ while (S[off]) {
+ ++off;
+ }
+ return off;
+ }
+
+ template<typename char_type>
+ static constexpr auto
+ strlen(const char_type * S)
+ {
+ auto off = 0U;
+ while (S[off]) {
+ ++off;
+ }
+ return off;
+ }
+
+ template<const auto S, auto n, auto start = 0U, auto L = strlen<S>()>
+ static constexpr std::optional<decltype(start)>
+ strchr()
+ {
+ static_assert(start <= L);
+ decltype(start) off = start;
+ while (off < L && S[off] != n) {
+ ++off;
+ }
+ if (off == L) {
+ return {};
+ }
+ return off;
+ }
+
+ template<const auto S, auto n, auto start = 0U, auto L = strlen<S>()>
+ static constexpr decltype(L)
+ strchrnul()
+ {
+ decltype(start) off = start;
+ while (off < L && S[off] != n) {
+ ++off;
+ }
+ return off;
+ }
+
+ template<const auto S, const auto L> class FormatterDetail;
+
+ /// Template used to apply parameters to a stream.
+ template<const auto S, auto L, auto pos, typename stream, typename, auto...> struct StreamWriter {
+ /// Write parameters to stream.
+ template<typename... Pn>
+ static void
+ write(stream &, const Pn &...)
+ {
+ static_assert(!L, "invalid format string/arguments");
+ }
+ };
+
+ /// Helper to simplify implementations of StreamWriter.
+ template<const auto S, auto L, auto pos, typename stream> struct StreamWriterBase {
+ /// Continue processing parameters.
+ template<typename... Pn>
+ static inline void
+ next(stream & s, const Pn &... pn)
+ {
+ FormatterDetail<S, L>::template Parser<stream, pos + 1, Pn...>::run(s, pn...);
+ }
+ };
+
+#define StreamWriterT(C...) \
+ template<const auto S, auto L, auto pos, typename stream, auto... sn> \
+ struct StreamWriter<S, L, pos, stream, void, '%', C, sn...> : \
+ public StreamWriterBase<S, L, BOOST_PP_VARIADIC_SIZE(C) + pos, stream>
+
+#define StreamWriterTP(P, C...) \
+ template<const auto S, auto L, auto pos, typename stream, auto P, auto... sn> \
+ struct StreamWriter<S, L, pos, stream, void, '%', C, sn...> : \
+ public StreamWriterBase<S, L, BOOST_PP_VARIADIC_SIZE(C) + pos, stream>
+
+ // Default stream writer formatter
+ StreamWriterT('?') {
+ template<typename P, typename... Pn>
+ static inline void
+ write(stream & s, const P & p, const Pn &... pn)
+ {
+ s << p;
+ StreamWriter::next(s, pn...);
+ }
+ };
+
+ // Escaped % stream writer formatter
+ StreamWriterT('%') {
+ template<typename... Pn>
+ static inline void
+ write(stream & s, const Pn &... pn)
+ {
+ s << '%';
+ StreamWriter::next(s, pn...);
+ }
+ };
+
+ template<typename stream, typename char_type>
+ static inline void
+ appendStream(stream & s, const char_type * p, size_t n)
+ {
+ s.write(p, n);
+ }
+
+ template<typename stream>
+ static inline auto
+ streamLength(stream & s)
+ {
+ return s.tellp();
+ }
+
+ /**
+ * Compile time string formatter.
+ * @param S the format string.
+ */
+ template<const auto S, const auto L> class FormatterDetail {
+ private:
+ using strlen_t = decltype(strlen<S>());
+ template<const auto, auto, auto, typename> friend struct StreamWriterBase;
+
+ public:
+ /// The derived charater type of the format string.
+ using char_type = typename std::decay<decltype(S[0])>::type;
+ /**
+ * Get a string containing the result of formatting.
+ * @param pn the format arguments.
+ * @return the formatted string.
+ */
+ template<typename... Pn>
+ static inline auto
+ get(const Pn &... pn)
+ {
+ std::basic_stringstream<char_type> s;
+ return write(s, pn...).str();
+ }
+ /**
+ * Get a string containing the result of formatting.
+ * @param pn the format arguments.
+ * @return the formatted string.
+ */
+ template<typename... Pn>
+ inline auto
+ operator()(const Pn &... pn) const
+ {
+ return get(pn...);
+ }
+
+ /**
+ * Write the result of formatting to the given stream.
+ * @param s the stream to write to.
+ * @param pn the format arguments.
+ * @return the stream.
+ */
+ template<typename stream, typename... Pn>
+ static inline stream &
+ write(stream & s, const Pn &... pn)
+ {
+ return Parser<stream, 0U, Pn...>::run(s, pn...);
+ }
+ /**
+ * Write the result of formatting to the given stream.
+ * @param s the stream to write to.
+ * @param pn the format arguments.
+ * @return the stream.
+ */
+ template<typename stream, typename... Pn>
+ inline typename std::enable_if<std::is_base_of_v<std::basic_ostream<char_type>, stream>, stream>::type &
+ operator()(stream & s, const Pn &... pn) const
+ {
+ return write(s, pn...);
+ }
+
+ private:
+ template<typename stream, auto pos, typename... Pn> struct Parser {
+ static inline stream &
+ run(stream & s, const Pn &... pn)
+ {
+ if (pos != L) {
+ constexpr auto ph = strchrnul<S, '%', pos, L>();
+ if constexpr (ph != pos) {
+ appendStream(s, &S[pos], ph - pos);
+ }
+ if constexpr (ph != L) {
+ packAndWrite<ph>(s, pn...);
+ }
+ }
+ return s;
+ }
+ template<strlen_t ph, strlen_t off = 0U, auto... Pck>
+ static inline void
+ packAndWrite(stream & s, const Pn &... pn)
+ {
+ if constexpr (ph + off == L || sizeof...(Pck) == 32) {
+ StreamWriter<S, L, ph, stream, void, Pck...>::write(s, pn...);
+ }
+ else if constexpr (ph + off < L) {
+ packAndWrite<ph, off + 1, Pck..., S[ph + off]>(s, pn...);
+ }
+ }
+ };
+ };
+
+ // New C++20 implementation
+ namespace support {
+ template<typename CharT, std::size_t N> class basic_fixed_string : public std::array<CharT, N> {
+ public:
+ // cppcheck-suppress noExplicitConstructor
+ constexpr basic_fixed_string(const CharT (&str)[N + 1])
+ {
+ for (decltype(N) x = 0; x < N; x++) {
+ this->at(x) = str[x];
+ }
+ }
+ constexpr basic_fixed_string(const CharT * str, decltype(N) len)
+ {
+ for (decltype(N) x = 0; x < len; x++) {
+ this->at(x) = str[x];
+ }
+ }
+ };
+
+ template<typename CharT, std::size_t N>
+ basic_fixed_string(const CharT (&str)[N]) -> basic_fixed_string<CharT, N - 1>;
+ }
+
+ template<const support::basic_fixed_string Str> class LiteralFormatter : public FormatterDetail<Str, Str.size()> {
+ };
+
+ template<const auto & S, decltype(strlen(S)) L = strlen(S)>
+ class Formatter :
+ public FormatterDetail<support::basic_fixed_string<typename std::decay<decltype(S[0])>::type, L>(S, L), L> {
+ };
+
+#define AdHocFormatter(name, str) using name = ::AdHoc::LiteralFormatter<str>
+
+ template<const support::basic_fixed_string Str, typename... Pn>
+ inline auto
+ scprintf(const Pn &... pn)
+ {
+ return FormatterDetail<Str, Str.size()>::get(pn...);
+ }
+
+ template<const support::basic_fixed_string Str, typename stream, typename... Pn>
+ inline auto &
+ scprintf(stream & strm, const Pn &... pn)
+ {
+ return FormatterDetail<Str, Str.size()>::write(strm, pn...);
+ }
+
+ template<const support::basic_fixed_string Str, typename... Pn>
+ inline auto &
+ cprintf(const Pn &... pn)
+ {
+ return scprintf<Str>(std::cout, pn...);
+ }
+}
+
+#include <boost/assert.hpp>
+#include <boost/preprocessor/arithmetic/add.hpp>
+#include <boost/preprocessor/cat.hpp>
+#include <boost/preprocessor/comma_if.hpp>
+#include <boost/preprocessor/if.hpp>
+#include <boost/preprocessor/repetition/repeat.hpp>
+#include <iomanip>
+#include <type_traits>
+
+namespace AdHoc {
+#define BASICCONV(PARAMTYPE, OP, ...) \
+ StreamWriterT(__VA_ARGS__) { \
+ template<typename... Pn> \
+ static inline void \
+ write(stream & s, const PARAMTYPE & p, const Pn &... pn) \
+ { \
+ OP; \
+ s.copyfmt(std::ios(nullptr)); \
+ StreamWriter::next(s, pn...); \
+ } \
+ }
+
+ // Integers (d, i, o, u, x, X)
+#define INTCONV(BASE, OP, CONV) \
+ BASICCONV(BASE, OP, CONV); \
+ BASICCONV(short BASE, OP, 'h', CONV); \
+ BASICCONV(long BASE, OP, 'l', CONV); \
+ BASICCONV(long long BASE, OP, 'l', 'l', CONV);
+ INTCONV(int, s << std::dec << p, 'i');
+ INTCONV(int, s << std::dec << p, 'd');
+ INTCONV(unsigned int, s << std::oct << p, 'o');
+ INTCONV(unsigned int, s << std::dec << p, 'u');
+ INTCONV(unsigned int, s << std::nouppercase << std::hex << p, 'x');
+ INTCONV(unsigned int, s << std::uppercase << std::hex << p, 'X');
+#undef INTCONV
+
+ BASICCONV(intmax_t, s << std::dec << p, 'j', 'd');
+ BASICCONV(uintmax_t, s << std::dec << p, 'j', 'u');
+ BASICCONV(ssize_t, s << std::dec << p, 'z', 'd');
+ BASICCONV(size_t, s << std::dec << p, 'z', 'u');
+ BASICCONV(short int, s << std::dec << p, 'h', 'h', 'i'); // char
+ BASICCONV(short int, s << std::dec << p, 'h', 'h', 'd'); // char
+ BASICCONV(unsigned char, s << std::dec << p, 'h', 'h', 'u');
+ BASICCONV(unsigned char, s << std::oct << p, 'h', 'h', 'o');
+ BASICCONV(unsigned char, s << std::nouppercase << std::hex << p, 'h', 'h', 'x');
+ BASICCONV(unsigned char, s << std::uppercase << std::hex << p, 'h', 'h', 'X');
+
+ // Floating point (a, A, e, E, f, F, g, G)
+#define FPCONV(BASE, OP, CONV) \
+ BASICCONV(BASE, OP, CONV); \
+ BASICCONV(long BASE, OP, 'L', CONV);
+ FPCONV(double, s << std::nouppercase << std::hexfloat << p, 'a');
+ FPCONV(double, s << std::uppercase << std::hexfloat << p, 'A');
+ FPCONV(double, s << std::nouppercase << std::scientific << p, 'e');
+ FPCONV(double, s << std::uppercase << std::scientific << p, 'E');
+ FPCONV(double, s << std::nouppercase << std::fixed << p, 'f');
+ FPCONV(double, s << std::uppercase << std::fixed << p, 'F');
+ FPCONV(double, s << std::nouppercase << std::defaultfloat << p, 'g');
+ FPCONV(double, s << std::uppercase << std::defaultfloat << p, 'G');
+#undef FPCONV
+
+ BASICCONV(std::string_view, s << p, 's');
+ BASICCONV(std::wstring_view, s << p, 'l', 's');
+ BASICCONV(char, s << p, 'c');
+ BASICCONV(wchar_t, s << p, 'l', 'c');
+#undef BASICCONV
+ StreamWriterT('p') {
+ template<typename Obj, typename... Pn>
+ static inline void
+ write(stream & s, Obj * const ptr, const Pn &... pn)
+ {
+ s << std::showbase << std::hex << (long unsigned int)ptr;
+ s.copyfmt(std::ios(nullptr));
+ StreamWriter::next(s, pn...);
+ }
+ template<typename Ptr, typename... Pn>
+ static inline void
+ write(stream & s, const Ptr & ptr, const Pn &... pn)
+ {
+ write(s, ptr.get(), pn...);
+ }
+ };
+
+ StreamWriterT('m') {
+ template<typename... Pn>
+ static inline void
+ write(stream & s, const Pn &... pn)
+ {
+ s << strerror(errno);
+ s.copyfmt(std::ios(nullptr));
+ StreamWriter::next(s, pn...);
+ }
+ };
+ StreamWriterT('n') {
+ template<typename... Pn>
+ static inline void
+ write(stream & s, int * n, const Pn &... pn)
+ {
+ BOOST_ASSERT_MSG(n, "%n conversion requires non-null parameter");
+ *n = streamLength(s);
+ s.copyfmt(std::ios(nullptr));
+ StreamWriter::next(s, pn...);
+ }
+ };
+
+ ////
+ // Width/precision embedded in format string
+ template<auto... chs>
+ constexpr auto
+ decdigits()
+ {
+ static_assert((isdigit(chs) && ... && true));
+ int n = 0;
+ (
+ [&n](auto ch) {
+ n = (n * 10) + (ch - '0');
+ }(chs),
+ ...);
+ return n;
+ }
+#define AUTON(z, n, data) \
+ BOOST_PP_COMMA_IF(n) \
+ auto BOOST_PP_CAT(data, n)
+#define NS(z, n, data) \
+ BOOST_PP_COMMA_IF(n) \
+ BOOST_PP_CAT(data, n)
+#define ISDIGIT(z, n, data) &&isdigit(BOOST_PP_CAT(data, BOOST_PP_ADD(n, 1)))
+#define FMTWIDTH(unused, d, data) \
+ template<const auto S, auto L, auto pos, typename stream, BOOST_PP_REPEAT(BOOST_PP_ADD(d, 1), AUTON, n), auto nn, \
+ auto... sn> \
+ struct StreamWriter<S, L, pos, stream, \
+ typename std::enable_if<ispositivedigit(n0) BOOST_PP_REPEAT(d, ISDIGIT, n) && !isdigit(nn)>::type, '%', \
+ BOOST_PP_REPEAT(BOOST_PP_ADD(d, 1), NS, n), nn, sn...> { \
+ template<typename... Pn> \
+ static inline void \
+ write(stream & s, const Pn &... pn) \
+ { \
+ constexpr auto p = decdigits<BOOST_PP_REPEAT(BOOST_PP_ADD(d, 1), NS, n)>(); \
+ s << std::setw(p); \
+ StreamWriter<S, L, pos + BOOST_PP_ADD(d, 1), stream, void, '%', nn, sn...>::write(s, pn...); \
+ } \
+ };
+ BOOST_PP_REPEAT(6, FMTWIDTH, void);
+#define FMTPRECISION(unused, d, data) \
+ template<const auto S, auto L, auto pos, typename stream, BOOST_PP_REPEAT(BOOST_PP_ADD(d, 1), AUTON, n), auto nn, \
+ auto... sn> \
+ struct StreamWriter<S, L, pos, stream, \
+ typename std::enable_if<isdigit(n0) BOOST_PP_REPEAT(d, ISDIGIT, n) && !isdigit(nn)>::type, '%', '.', \
+ BOOST_PP_REPEAT(BOOST_PP_ADD(d, 1), NS, n), nn, sn...> { \
+ template<typename... Pn> \
+ static inline void \
+ write(stream & s, const Pn &... pn) \
+ { \
+ constexpr auto p = decdigits<BOOST_PP_REPEAT(BOOST_PP_ADD(d, 1), NS, n)>(); \
+ s << std::setprecision(p); \
+ StreamWriter<S, L, pos + BOOST_PP_ADD(d, 2), stream, void, '%', nn, sn...>::write(s, pn...); \
+ } \
+ };
+ BOOST_PP_REPEAT(6, FMTPRECISION, void);
+#undef AUTON
+#undef NS
+#undef ISDIGIT
+#undef FMTWIDTH
+
+ StreamWriterT('.', '*') {
+ template<typename... Pn>
+ static inline void
+ write(stream & s, int l, const Pn &... pn)
+ {
+ s << std::setw(l);
+ StreamWriter<S, L, pos + 2, stream, void, '%', sn...>::write(s, pn...);
+ }
+ };
+ StreamWriterT('.', '*', 's') {
+ template<typename... Pn>
+ static inline void
+ write(stream & s, int l, const std::string_view & p, const Pn &... pn)
+ {
+ s << p.substr(0, l);
+ s.copyfmt(std::ios(nullptr));
+ StreamWriter::next(s, pn...);
+ }
+ };
+
+ // Flags
+#define FLAGCONV(OP, ...) \
+ StreamWriterT(__VA_ARGS__) { \
+ template<typename... Pn> \
+ static inline void \
+ write(stream & s, const Pn &... pn) \
+ { \
+ OP; \
+ StreamWriter<S, L, pos + 1, stream, void, '%', sn...>::write(s, pn...); \
+ } \
+ };
+ FLAGCONV(s << std::showbase, '#');
+ FLAGCONV(s << std::setfill('0'), '0');
+ FLAGCONV(s << std::left, '-');
+ FLAGCONV(s << std::showpos, '+');
+ FLAGCONV(s << std::setfill(' '), ' ');
+#undef FLAGCONV
+}
+
+#endif
diff --git a/lib/helpers.h b/lib/helpers.h
new file mode 100644
index 0000000..3a94ead
--- /dev/null
+++ b/lib/helpers.h
@@ -0,0 +1,35 @@
+#ifndef MYGRATE_HELPERS_H
+#define MYGRATE_HELPERS_H
+
+#include <concepts>
+#include <cstdint>
+#include <utility>
+
+namespace MyGrate {
+ template<std::integral I>
+ constexpr inline auto
+ bitslice(const I i, uint8_t offset, uint8_t size)
+ {
+ return (i >> offset) & ((1U << size) - 1U);
+ }
+
+ template<typename X, typename... P>
+ constexpr inline void
+ verify(bool expr, P &&... p)
+ {
+ if (!expr) {
+ throw X(std::forward<P...>(p)...);
+ }
+ }
+
+ template<typename T>
+ constexpr inline auto
+ mod100_extract(T & i)
+ {
+ const auto r {i % 100};
+ i /= 100;
+ return r;
+ }
+}
+
+#endif
diff --git a/lib/mysql_types.cpp b/lib/mysql_types.cpp
new file mode 100644
index 0000000..fa4698d
--- /dev/null
+++ b/lib/mysql_types.cpp
@@ -0,0 +1,212 @@
+#include "mysql_types.h"
+#include "helpers.h"
+#include "rawDataReader.h"
+#include <iostream>
+#include <stdexcept>
+
+namespace MyGrate::MySQL {
+ typename Type<MYSQL_TYPE_NULL, false>::C
+ Type<MYSQL_TYPE_NULL, false>::read(RawDataReader &, RawDataReader &)
+ {
+ return nullptr;
+ }
+
+#define INTEGER_TYPE(ET) \
+ typename Type<ET, false>::C Type<ET, false>::read(RawDataReader &, RawDataReader & data) \
+ { \
+ return data.readValue<typename Type<ET>::C>(); \
+ } \
+ typename Type<ET, true>::C Type<ET, true>::read(RawDataReader &, RawDataReader & data) \
+ { \
+ return data.readValue<typename Type<ET>::C>(); \
+ }
+ INTEGER_TYPE(MYSQL_TYPE_TINY);
+ INTEGER_TYPE(MYSQL_TYPE_SHORT);
+ INTEGER_TYPE(MYSQL_TYPE_LONG);
+ INTEGER_TYPE(MYSQL_TYPE_LONGLONG);
+#undef INTEGER_TYPE
+
+#define FLOAT_TYPE(ET) \
+ typename Type<ET, false>::C Type<ET, false>::read(RawDataReader & md, RawDataReader & data) \
+ { \
+ verify<std::length_error>(sizeof(typename Type<ET>::C) == md.readValue<uint8_t>(), "Invalid " #ET " size"); \
+ return data.readValue<typename Type<ET>::C>(); \
+ }
+ FLOAT_TYPE(MYSQL_TYPE_FLOAT);
+ FLOAT_TYPE(MYSQL_TYPE_DOUBLE);
+#undef FLOAT_TYPE
+
+ typename Type<MYSQL_TYPE_DECIMAL>::C
+ Type<MYSQL_TYPE_DECIMAL>::read(RawDataReader &, RawDataReader &)
+ {
+ throw std::logic_error("Not implemented");
+ }
+
+ typename Type<MYSQL_TYPE_NEWDECIMAL>::C
+ Type<MYSQL_TYPE_NEWDECIMAL>::read(RawDataReader &, RawDataReader &)
+ {
+ throw std::logic_error("Not implemented");
+ }
+
+#define INTEGER_TYPE(ET, s, l) \
+ typename Type<ET, s>::C Type<ET, s>::read(RawDataReader &, RawDataReader & data) \
+ { \
+ return data.readValue<typename Type<ET, s>::C, l>(); \
+ }
+ INTEGER_TYPE(MYSQL_TYPE_INT24, false, 3);
+ INTEGER_TYPE(MYSQL_TYPE_INT24, true, 3);
+ INTEGER_TYPE(MYSQL_TYPE_YEAR, false, 2);
+#undef INTEGER24_TYPE
+
+ static Blob
+ readBlob(RawDataReader & md, RawDataReader & data)
+ {
+ const auto lenlen {md.readValue<uint8_t>()};
+ const auto len {data.readValue<uint64_t>(lenlen)};
+ return data.viewValue<Blob>(len);
+ }
+#define BLOB_TYPE(ET) \
+ typename Type<ET, false>::C Type<ET, false>::read(RawDataReader & md, RawDataReader & data) \
+ { \
+ return readBlob(md, data); \
+ }
+ BLOB_TYPE(MYSQL_TYPE_TINY_BLOB);
+ BLOB_TYPE(MYSQL_TYPE_MEDIUM_BLOB);
+ BLOB_TYPE(MYSQL_TYPE_LONG_BLOB);
+ BLOB_TYPE(MYSQL_TYPE_GEOMETRY); // Ummm, pass this to the target to handle?
+#undef BLOB_TYPE
+
+ typename Type<MYSQL_TYPE_BLOB, false>::C
+ Type<MYSQL_TYPE_BLOB, false>::read(RawDataReader & md, RawDataReader & data)
+ {
+ const auto lenlen {md.readValue<uint8_t>()};
+ const auto len {data.readValue<uint64_t>(lenlen)};
+ return data.viewValue<std::string_view>(len);
+ }
+
+ typename Type<MYSQL_TYPE_BIT>::C
+ Type<MYSQL_TYPE_BIT>::read(RawDataReader &, RawDataReader &)
+ {
+ throw std::logic_error("Not implemented");
+ }
+
+#define STRING_TYPE(ET) \
+ typename Type<ET, false>::C Type<ET, false>::read(RawDataReader & md, RawDataReader & data) \
+ { \
+ const auto realtype {md.readValue<enum_field_types, 1>()}; \
+ const auto lenlen {md.readValue<uint8_t>()}; \
+ switch (realtype) { \
+ case MYSQL_TYPE_ENUM: { \
+ return data.viewValue<std::string_view>(lenlen); \
+ break; \
+ } \
+ default: \
+ throw std::logic_error("Not implemented: sub-type: " + std::to_string(realtype)); \
+ break; \
+ } \
+ throw std::logic_error("Didn't return a value: " + std::to_string(realtype)); \
+ }
+ STRING_TYPE(MYSQL_TYPE_STRING);
+ STRING_TYPE(MYSQL_TYPE_JSON);
+ STRING_TYPE(MYSQL_TYPE_SET);
+ STRING_TYPE(MYSQL_TYPE_ENUM);
+#undef STRING_TYPE
+
+ typename Type<MYSQL_TYPE_VARCHAR>::C
+ Type<MYSQL_TYPE_VARCHAR>::read(RawDataReader & md, RawDataReader & data)
+ {
+ const auto fieldlen {md.readValue<uint16_t>()};
+ const auto len {data.readValue<uint16_t>(fieldlen > 255 ? 2 : 1)};
+ return data.viewValue<std::string_view>(len);
+ }
+
+ typename Type<MYSQL_TYPE_VAR_STRING>::C
+ Type<MYSQL_TYPE_VAR_STRING>::read(RawDataReader & md, RawDataReader & data)
+ {
+ const auto fieldlen {md.readValue<uint16_t>()};
+ const auto len {md.readValue<uint16_t>(fieldlen > 255 ? 2 : 1)};
+ return data.viewValue<std::string_view>(len);
+ }
+
+ typename Type<MYSQL_TYPE_DATETIME>::C
+ Type<MYSQL_TYPE_DATETIME>::read(RawDataReader &, RawDataReader & data)
+ {
+ auto dtint {data.readValue<uint64_t>()};
+ DateTime dt;
+ dt.second = mod100_extract(dtint);
+ dt.minute = mod100_extract(dtint);
+ dt.hour = mod100_extract(dtint);
+ dt.day = mod100_extract(dtint);
+ dt.month = mod100_extract(dtint);
+ dt.year = dtint;
+ return dt;
+ }
+
+ typename Type<MYSQL_TYPE_TIME>::C
+ Type<MYSQL_TYPE_TIME>::read(RawDataReader &, RawDataReader & data)
+ {
+ auto tint {data.readValue<uint32_t, 3>()};
+ Time t;
+ t.second = mod100_extract(tint);
+ t.minute = mod100_extract(tint);
+ t.hour = tint;
+ return t;
+ }
+
+ typename Type<MYSQL_TYPE_TIME2>::C
+ Type<MYSQL_TYPE_TIME2>::read(RawDataReader &, RawDataReader &)
+ {
+ throw std::logic_error("Not implemented");
+ }
+
+ typename Type<MYSQL_TYPE_DATE>::C
+ Type<MYSQL_TYPE_DATE>::read(RawDataReader &, RawDataReader & data)
+ {
+ auto dint {data.readValue<uint32_t, 3>()};
+ Date d;
+ d.day = bitslice(dint, 0, 6);
+ d.month = bitslice(dint, 6, 4);
+ d.day = bitslice(dint, 10, 14);
+ return d;
+ }
+
+ typename Type<MYSQL_TYPE_NEWDATE>::C
+ Type<MYSQL_TYPE_NEWDATE>::read(RawDataReader &, RawDataReader &)
+ {
+ throw std::logic_error("Not implemented");
+ }
+
+ typename Type<MYSQL_TYPE_TIMESTAMP>::C
+ Type<MYSQL_TYPE_TIMESTAMP>::read(RawDataReader &, RawDataReader & data)
+ {
+ return {data.readValue<int32_t>(), 0};
+ }
+
+ typename Type<MYSQL_TYPE_TIMESTAMP2>::C
+ Type<MYSQL_TYPE_TIMESTAMP2>::read(RawDataReader & md, RawDataReader & data)
+ {
+ const auto decimals {md.readValue<uint8_t>()};
+ return {data.readValue<int32_t>(), data.readValue<long>(decimals)};
+ }
+
+ typename Type<MYSQL_TYPE_DATETIME2>::C
+ Type<MYSQL_TYPE_DATETIME2>::read(RawDataReader & md, RawDataReader & data)
+ {
+ md.discard(1);
+ union {
+ uint64_t i;
+ std::array<uint8_t, 5> b;
+ } r {};
+ r.b = data.readValue<decltype(r.b)>();
+ std::reverse(r.b.begin(), r.b.end());
+
+ DateTime dt;
+ dt.year = (bitslice(r.i, 22, 17) / 13);
+ dt.month = (bitslice(r.i, 22, 17) % 13);
+ dt.day = bitslice(r.i, 17, 5);
+ dt.hour = bitslice(r.i, 12, 5);
+ dt.minute = bitslice(r.i, 6, 6);
+ dt.second = bitslice(r.i, 0, 6);
+ return dt;
+ }
+}
diff --git a/lib/mysql_types.h b/lib/mysql_types.h
new file mode 100644
index 0000000..ec578f7
--- /dev/null
+++ b/lib/mysql_types.h
@@ -0,0 +1,87 @@
+#ifndef MYGRATE_MYSQL_TYPES_H
+#define MYGRATE_MYSQL_TYPES_H
+
+#include "bitset.h"
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <ctime>
+#include <mysql.h> // IWYU pragma: keep
+#include <span>
+#include <string_view>
+#include <variant>
+
+#include <mariadb_rpl.h>
+
+namespace MyGrate {
+ class RawDataReader;
+ namespace MySQL {
+ struct Date {
+ uint16_t year;
+ uint8_t month;
+ uint8_t day;
+ };
+ struct Time {
+ uint8_t hour;
+ uint8_t minute;
+ uint8_t second;
+ };
+ struct DateTime : public Date, public Time {
+ };
+ using Blob = std::span<const std::byte>;
+
+ template<enum_field_types, bool /*unsigned*/ = false> struct Type;
+
+#define DEFINE_BASE_TYPE(ET, CT, MDS, SIGN) \
+ template<> struct Type<ET, SIGN> { \
+ using C = CT; \
+ constexpr static size_t md_bytes {MDS}; \
+ static C read(RawDataReader & md, RawDataReader & data); \
+ }
+#define DEFINE_TYPE(ET, CT, MDS) DEFINE_BASE_TYPE(ET, CT, MDS, false)
+#define DEFINE_ITYPE(ET, BCT, MDS) \
+ DEFINE_BASE_TYPE(ET, BCT, MDS, false); \
+ DEFINE_BASE_TYPE(ET, u##BCT, MDS, true)
+
+ DEFINE_TYPE(MYSQL_TYPE_DECIMAL, double, 2);
+ DEFINE_ITYPE(MYSQL_TYPE_TINY, int8_t, 0);
+ DEFINE_ITYPE(MYSQL_TYPE_SHORT, int16_t, 0);
+ DEFINE_ITYPE(MYSQL_TYPE_LONG, int32_t, 0);
+ DEFINE_TYPE(MYSQL_TYPE_FLOAT, float, 1);
+ DEFINE_TYPE(MYSQL_TYPE_DOUBLE, double, 1);
+ DEFINE_TYPE(MYSQL_TYPE_NULL, std::nullptr_t, 0);
+ DEFINE_TYPE(MYSQL_TYPE_TIMESTAMP, timespec, 0);
+ DEFINE_TYPE(MYSQL_TYPE_TIMESTAMP2, timespec, 1);
+ DEFINE_ITYPE(MYSQL_TYPE_LONGLONG, int64_t, 0);
+ DEFINE_ITYPE(MYSQL_TYPE_INT24, int32_t, 0);
+ DEFINE_TYPE(MYSQL_TYPE_DATE, Date, 0);
+ DEFINE_TYPE(MYSQL_TYPE_TIME, Time, 0);
+ DEFINE_TYPE(MYSQL_TYPE_TIME2, Time, 0);
+ DEFINE_TYPE(MYSQL_TYPE_DATETIME, DateTime, 0);
+ DEFINE_TYPE(MYSQL_TYPE_DATETIME2, DateTime, 1);
+ DEFINE_TYPE(MYSQL_TYPE_YEAR, int16_t, 0);
+ DEFINE_TYPE(MYSQL_TYPE_NEWDATE, Date, 0);
+ DEFINE_TYPE(MYSQL_TYPE_VARCHAR, std::string_view, 2);
+ DEFINE_TYPE(MYSQL_TYPE_BIT, BitSet, 2);
+ DEFINE_TYPE(MYSQL_TYPE_NEWDECIMAL, double, 2);
+ DEFINE_TYPE(MYSQL_TYPE_ENUM, std::string_view, 0);
+ DEFINE_TYPE(MYSQL_TYPE_SET, std::string_view, 0);
+ DEFINE_TYPE(MYSQL_TYPE_TINY_BLOB, Blob, 0);
+ DEFINE_TYPE(MYSQL_TYPE_MEDIUM_BLOB, Blob, 0);
+ DEFINE_TYPE(MYSQL_TYPE_LONG_BLOB, Blob, 0);
+ DEFINE_TYPE(MYSQL_TYPE_BLOB, std::string_view, 1);
+ DEFINE_TYPE(MYSQL_TYPE_VAR_STRING, std::string_view, 2);
+ DEFINE_TYPE(MYSQL_TYPE_STRING, std::string_view, 2);
+ DEFINE_TYPE(MYSQL_TYPE_JSON, std::string_view, 2);
+ DEFINE_TYPE(MYSQL_TYPE_GEOMETRY, Blob, 1);
+
+#undef DEFINE_ITYPE
+#undef DEFINE_USTYPE
+#undef DEFINE_TYPE
+
+ using FieldValue = std::variant<std::nullptr_t, double, float, int8_t, uint8_t, int16_t, uint16_t, int32_t,
+ uint32_t, int64_t, uint64_t, timespec, Date, Time, DateTime, std::string_view, BitSet, Blob>;
+ }
+}
+
+#endif
diff --git a/lib/rawDataReader.cpp b/lib/rawDataReader.cpp
new file mode 100644
index 0000000..2dd87a9
--- /dev/null
+++ b/lib/rawDataReader.cpp
@@ -0,0 +1,47 @@
+#include "rawDataReader.h"
+#include "helpers.h"
+#include "streamSupport.h"
+#include <cstring>
+#include <iomanip>
+#include <iostream>
+
+namespace MyGrate {
+ RawDataReader::RawDataReader(const void * const d, std::size_t l) :
+ rawdata {static_cast<const std::byte *>(d)}, len {l}
+ {
+ }
+
+ RawDataReader::RawDataReader(const MARIADB_STRING & str) : RawDataReader {str.str, str.length} { }
+
+ void
+ RawDataReader::offsetSizeCheck(size_t l) const
+ {
+ if (offset + l > len) {
+ throw std::range_error("Read beyond end of data");
+ }
+ }
+
+ void
+ RawDataReader::discard(size_t d)
+ {
+ offset += d;
+ }
+
+ template<>
+ uint64_t
+ RawDataReader::readValue<PackedInteger>()
+ {
+ switch (const auto byte1 {readValue<uint8_t>()}) {
+ case 0 ... 250: // The value as-is
+ return byte1;
+ case 252: // 2 bytes
+ return readValue<uint16_t, 2>();
+ case 253: // 3 bytes
+ return readValue<uint32_t, 3>();
+ case 254: // 8 bytes
+ return readValue<uint64_t, 8>();
+ default:;
+ }
+ throw std::domain_error("Invalid first byte of packed integer");
+ }
+}
diff --git a/lib/rawDataReader.h b/lib/rawDataReader.h
new file mode 100644
index 0000000..0ed2847
--- /dev/null
+++ b/lib/rawDataReader.h
@@ -0,0 +1,76 @@
+#ifndef MYGRATE_RAW_DATA_READER_H
+#define MYGRATE_RAW_DATA_READER_H
+
+#include "helpers.h"
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <ctime>
+#include <mysql.h> // IWYU pragma: keep
+#include <span>
+#include <stdexcept>
+#include <string_view>
+
+#include <mariadb_rpl.h>
+
+namespace MyGrate {
+ struct PackedInteger {
+ };
+ template<typename T> struct type_map {
+ using target = T;
+ };
+ template<> struct type_map<PackedInteger> {
+ using target = uint64_t;
+ };
+
+ class RawDataReader {
+ public:
+ explicit RawDataReader(const void * const d, std::size_t len);
+ explicit RawDataReader(const MARIADB_STRING & str);
+
+ template<typename T, size_t L = sizeof(T)>
+ typename type_map<T>::target
+ readValue()
+ {
+ static_assert(L > 0 && L <= sizeof(T), "Read exceeds target size");
+ return readValue<T>(L);
+ }
+
+ template<typename T>
+ typename type_map<T>::target
+ readValue(size_t L)
+ {
+ verify<std::logic_error>(L > 0 && L <= sizeof(T), "Read exceeds target size");
+ offsetSizeCheck(L);
+ T v {};
+ memcpy(&v, rawdata + offset, L);
+ offset += L;
+ return v;
+ }
+
+ template<typename T>
+ T
+ viewValue(size_t L)
+ {
+ static_assert(sizeof(typename T::value_type) == sizeof(std::byte));
+ offsetSizeCheck(L);
+ T v {reinterpret_cast<const typename T::value_type *>(rawdata + offset), L};
+ offset += L;
+ return v;
+ }
+
+ void discard(size_t);
+
+ private:
+ void offsetSizeCheck(size_t) const;
+
+ const std::byte * const rawdata;
+ const std::size_t len;
+ std::size_t offset {0};
+ };
+
+ template<> uint64_t RawDataReader::readValue<PackedInteger>();
+}
+
+#endif
diff --git a/lib/row.cpp b/lib/row.cpp
new file mode 100644
index 0000000..d59cdba
--- /dev/null
+++ b/lib/row.cpp
@@ -0,0 +1,101 @@
+#include "row.h"
+#include <stdexcept>
+#include <string>
+
+namespace MyGrate {
+ Row::Row(const st_mariadb_rpl_rows_event & r, const st_mariadb_rpl_table_map_event & t) : row {r}, tm {t} { }
+
+ void
+ Row::forEachField(const std::function<void(MySQL::FieldValue)> & cb)
+ {
+ MyGrate::RawDataReader md {tm.metadata};
+ MyGrate::RawDataReader data {row.row_data, row.row_data_size};
+ const size_t flagBytes {(tm.column_count + 7) / 8};
+ MyGrate::BitSet nullFlags {data.viewValue<std::span<const std::byte>>(flagBytes)};
+ MyGrate::BitSet columnFlags {{reinterpret_cast<const std::byte *>(row.column_bitmap), flagBytes}};
+ auto nullIter {nullFlags.begin()};
+ auto colIter {columnFlags.begin()};
+ for (auto c {0U}; c < tm.column_count; c++) {
+ if (*colIter) {
+ const enum_field_types type {(enum_field_types)(unsigned char)tm.column_types.str[c]};
+ const auto null {*nullIter};
+ if (null) {
+ switch (type) {
+ case MYSQL_TYPE_BIT:
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ case MYSQL_TYPE_NEWDECIMAL:
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_VARCHAR:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_STRING:
+ md.discard(2);
+ break;
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ case MYSQL_TYPE_TIMESTAMP2:
+ case MYSQL_TYPE_DATETIME2:
+ case MYSQL_TYPE_TIME2:
+ md.discard(1);
+ break;
+ default:;
+ }
+ cb(nullptr);
+ }
+ else {
+ switch (type) {
+#define TYPE_CALLBACK(T) \
+ case T: \
+ cb(MySQL::Type<T>::read(md, data)); \
+ break
+ TYPE_CALLBACK(MYSQL_TYPE_DECIMAL);
+ TYPE_CALLBACK(MYSQL_TYPE_TINY);
+ TYPE_CALLBACK(MYSQL_TYPE_SHORT);
+ TYPE_CALLBACK(MYSQL_TYPE_LONG);
+ TYPE_CALLBACK(MYSQL_TYPE_FLOAT);
+ TYPE_CALLBACK(MYSQL_TYPE_DOUBLE);
+ TYPE_CALLBACK(MYSQL_TYPE_NULL);
+ TYPE_CALLBACK(MYSQL_TYPE_TIMESTAMP);
+ TYPE_CALLBACK(MYSQL_TYPE_LONGLONG);
+ TYPE_CALLBACK(MYSQL_TYPE_INT24);
+ TYPE_CALLBACK(MYSQL_TYPE_DATE);
+ TYPE_CALLBACK(MYSQL_TYPE_TIME);
+ TYPE_CALLBACK(MYSQL_TYPE_DATETIME);
+ TYPE_CALLBACK(MYSQL_TYPE_YEAR);
+ TYPE_CALLBACK(MYSQL_TYPE_NEWDATE);
+ TYPE_CALLBACK(MYSQL_TYPE_BIT);
+ TYPE_CALLBACK(MYSQL_TYPE_TIMESTAMP2);
+ TYPE_CALLBACK(MYSQL_TYPE_TIME2);
+ TYPE_CALLBACK(MYSQL_TYPE_JSON);
+ TYPE_CALLBACK(MYSQL_TYPE_NEWDECIMAL);
+ TYPE_CALLBACK(MYSQL_TYPE_ENUM);
+ TYPE_CALLBACK(MYSQL_TYPE_SET);
+ TYPE_CALLBACK(MYSQL_TYPE_TINY_BLOB);
+ TYPE_CALLBACK(MYSQL_TYPE_MEDIUM_BLOB);
+ TYPE_CALLBACK(MYSQL_TYPE_LONG_BLOB);
+ TYPE_CALLBACK(MYSQL_TYPE_BLOB);
+ TYPE_CALLBACK(MYSQL_TYPE_GEOMETRY);
+ TYPE_CALLBACK(MYSQL_TYPE_VAR_STRING);
+ TYPE_CALLBACK(MYSQL_TYPE_VARCHAR);
+ TYPE_CALLBACK(MYSQL_TYPE_DATETIME2);
+ TYPE_CALLBACK(MYSQL_TYPE_STRING);
+#undef TYPE_CALLBACK
+ case MAX_NO_FIELD_TYPES:
+ // This is why you don't the end of the enum as member!
+#ifdef NDEBUG
+ [[fallthrough]];
+ default:
+#endif
+ throw std::logic_error("Unknown type: " + std::to_string(type));
+ }
+ }
+ ++nullIter;
+ }
+ ++colIter;
+ }
+ }
+}
diff --git a/lib/row.h b/lib/row.h
new file mode 100644
index 0000000..ac2c135
--- /dev/null
+++ b/lib/row.h
@@ -0,0 +1,21 @@
+#ifndef MYGRATE_ROW_H
+#define MYGRATE_ROW_H
+
+#include "mysql_types.h"
+#include "rawDataReader.h"
+#include <functional>
+
+namespace MyGrate {
+ class Row {
+ public:
+ Row(const st_mariadb_rpl_rows_event &, const st_mariadb_rpl_table_map_event &);
+
+ void forEachField(const std::function<void(MySQL::FieldValue)> & callback);
+
+ private:
+ const st_mariadb_rpl_rows_event & row;
+ const st_mariadb_rpl_table_map_event & tm;
+ };
+}
+
+#endif
diff --git a/lib/streamSupport.cpp b/lib/streamSupport.cpp
new file mode 100644
index 0000000..541b191
--- /dev/null
+++ b/lib/streamSupport.cpp
@@ -0,0 +1,53 @@
+#include "streamSupport.h"
+#include "compileTimeFormatter.h"
+
+namespace std {
+ std::ostream &
+ operator<<(std::ostream & strm, const std::byte byt)
+ {
+ return AdHoc::scprintf<"%#02x">(strm, std::to_integer<uint8_t>(byt));
+ }
+
+ std::ostream &
+ operator<<(std::ostream & s, const MARIADB_STRING & str)
+ {
+ return s.write(str.str, str.length);
+ }
+
+ std::ostream &
+ operator<<(std::ostream & s, const tm & tm)
+ {
+ return AdHoc::scprintf<"%04d-%02d-%02d %02d:%02d:%02d">(
+ s, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+ }
+
+ std::ostream &
+ operator<<(std::ostream & s, const timespec & ts)
+ {
+ return AdHoc::scprintf<"%d.%09d">(s, ts.tv_sec, ts.tv_nsec);
+ }
+
+ std::ostream &
+ operator<<(std::ostream & s, const MyGrate::MySQL::Date & d)
+ {
+ return AdHoc::scprintf<"%04d-%02d-%02d">(s, d.year, d.month, d.day);
+ }
+
+ std::ostream &
+ operator<<(std::ostream & s, const MyGrate::MySQL::Time & t)
+ {
+ return AdHoc::scprintf<"%02d:%02d:%02d">(s, t.hour, t.minute, t.second);
+ }
+
+ std::ostream &
+ operator<<(std::ostream & s, const MyGrate::MySQL::DateTime & dt)
+ {
+ return AdHoc::scprintf<"%? %?">(s, (const MyGrate::MySQL::Date)dt, (const MyGrate::MySQL::Time)dt);
+ }
+
+ std::ostream &
+ operator<<(std::ostream & s, const MyGrate::BitSet & bs)
+ {
+ return s << std::make_pair(bs.begin(), bs.end());
+ }
+}
diff --git a/lib/streamSupport.h b/lib/streamSupport.h
new file mode 100644
index 0000000..0723faf
--- /dev/null
+++ b/lib/streamSupport.h
@@ -0,0 +1,68 @@
+#ifndef MYGRATE_STREAM_SUPPORT_H
+#define MYGRATE_STREAM_SUPPORT_H
+
+#include <array>
+#include <cstddef>
+#include <iomanip>
+#include <mysql.h>
+#include <ostream>
+#include <span>
+#include <vector>
+
+#include "mysql_types.h"
+#include <mariadb_rpl.h>
+
+namespace std {
+ std::ostream & operator<<(std::ostream & strm, const std::byte byt);
+
+ std::ostream & operator<<(std::ostream & s, const MARIADB_STRING & str);
+
+ std::ostream & operator<<(std::ostream & s, const tm & tm);
+
+ std::ostream & operator<<(std::ostream & s, const timespec & ts);
+
+ std::ostream & operator<<(std::ostream & s, const MyGrate::MySQL::Date & d);
+
+ std::ostream & operator<<(std::ostream & s, const MyGrate::MySQL::Time & t);
+
+ std::ostream & operator<<(std::ostream & s, const MyGrate::MySQL::DateTime & dt);
+
+ std::ostream & operator<<(std::ostream & s, const MyGrate::BitSet & bs);
+
+ template<std::forward_iterator Iter>
+ inline std::ostream &
+ operator<<(std::ostream & strm, const std::pair<Iter, Iter> range)
+ {
+ strm << '[';
+ for (auto i {range.first}; i != range.second; i++) {
+ if (i != range.first) {
+ strm << ',';
+ }
+ strm << *i;
+ }
+ return strm << ']';
+ }
+
+ template<typename T>
+ std::ostream &
+ operator<<(std::ostream & strm, const std::span<const T> spn)
+ {
+ return strm << std::make_pair(spn.begin(), spn.end());
+ }
+
+ template<typename T>
+ std::ostream &
+ operator<<(std::ostream & strm, const std::vector<T> & v)
+ {
+ return strm << std::span<const T>(v.data(), v.size());
+ }
+
+ template<typename T, std::size_t L>
+ std::ostream &
+ operator<<(std::ostream & strm, const std::array<T, L> & a)
+ {
+ return strm << std::span<const T>(a.begin(), a.end());
+ }
+}
+
+#endif