diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Jamroot.jam | 38 | ||||
-rw-r--r-- | iwyu.json | 58 | ||||
-rw-r--r-- | lib/Jamfile.jam | 9 | ||||
-rw-r--r-- | lib/bitset.cpp | 66 | ||||
-rw-r--r-- | lib/bitset.h | 45 | ||||
-rw-r--r-- | lib/compileTimeFormatter.h | 495 | ||||
-rw-r--r-- | lib/helpers.h | 35 | ||||
-rw-r--r-- | lib/mysql_types.cpp | 212 | ||||
-rw-r--r-- | lib/mysql_types.h | 87 | ||||
-rw-r--r-- | lib/rawDataReader.cpp | 47 | ||||
-rw-r--r-- | lib/rawDataReader.h | 76 | ||||
-rw-r--r-- | lib/row.cpp | 101 | ||||
-rw-r--r-- | lib/row.h | 21 | ||||
-rw-r--r-- | lib/streamSupport.cpp | 53 | ||||
-rw-r--r-- | lib/streamSupport.h | 68 | ||||
-rw-r--r-- | main/Jamfile.jam | 2 | ||||
-rw-r--r-- | main/main.cpp | 195 | ||||
-rw-r--r-- | test/Jamfile.jam | 11 | ||||
-rw-r--r-- | test/helpers.h | 44 | ||||
-rw-r--r-- | test/test-bitset.cpp | 31 | ||||
-rw-r--r-- | test/test-rawDataReader.cpp | 163 | ||||
-rw-r--r-- | test/test-streams.cpp | 92 |
23 files changed, 1950 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin diff --git a/Jamroot.jam b/Jamroot.jam new file mode 100644 index 0000000..7a18a1e --- /dev/null +++ b/Jamroot.jam @@ -0,0 +1,38 @@ +using gcc ; + +using pkg-config ; +import pkg-config ; +import testing ; + +pkg-config.import libmariadb ; +pkg-config.import libpq ; + +variant coverage : debug ; + +project : requirements + <cxxstd>20 + <visibility>hidden + <linkflags>"-Wl,-z,defs,--warn-once,--gc-sections" + <variant>release:<lto>on + <variant>debug:<warnings>extra + <variant>debug:<warnings-as-errors>on + <variant>coverage:<coverage>on + <toolset>tidy:<checkxx>boost-* + <toolset>tidy:<checkxx>bugprone-* + <toolset>tidy:<xcheckxx>bugprone-macro-parentheses + <toolset>tidy:<checkxx>clang-* + <toolset>tidy:<checkxx>misc-* + <toolset>tidy:<xcheckxx>misc-non-private-member-variables-in-classes + <toolset>tidy:<checkxx>modernize-* + <toolset>tidy:<xcheckxx>modernize-use-trailing-return-type + <toolset>tidy:<checkxx>hicpp-* + <toolset>tidy:<xcheckxx>hicpp-vararg + <toolset>tidy:<xcheckxx>hicpp-signed-bitwise + <toolset>tidy:<xcheckxx>hicpp-named-parameter + <toolset>tidy:<xcheckxx>hicpp-no-array-decay + <toolset>tidy:<checkxx>performance-* + <toolset>tidy:<mapping>iwyu.json + ; + +build-project main ; +build-project test ; diff --git a/iwyu.json b/iwyu.json new file mode 100644 index 0000000..5785628 --- /dev/null +++ b/iwyu.json @@ -0,0 +1,58 @@ +[ + { + "symbol": [ + "tm", + "private", + "<ctime>", + "public" + ] + }, + { + "include": [ + "<mariadb_com.h>", + "private", + "<mysql.h>", + "public" + ] + }, + { + "include": [ + "<boost/test/unit_test_suite.hpp>", + "private", + "<boost/test/unit_test.hpp>", + "public" + ] + }, + { + "include": [ + "@<boost/test/utils/.*>", + "private", + "<boost/test/unit_test.hpp>", + "public" + ] + }, + { + "include": [ + "@<boost/test/data/.*>", + "private", + "<boost/test/data/test_case.hpp>", + "public" + ] + }, + { + "include": [ + "@<boost/test/tools/.*>", + "private", + "<boost/test/unit_test.hpp>", + "public" + ] + }, + { + "include": [ + "@<boost/preprocessor/.*>", + "private", + "<boost/test/unit_test.hpp>", + "public" + ] + } +] 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 diff --git a/main/Jamfile.jam b/main/Jamfile.jam new file mode 100644 index 0000000..d03d005 --- /dev/null +++ b/main/Jamfile.jam @@ -0,0 +1,2 @@ +exe mygrate : main.cpp : <library>../lib//mygrate ; +#run main.cpp : : : <library>../lib//mygrate ; diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 0000000..d976a0a --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,195 @@ +#include <cstddef> +#include <mysql.h> + +#include <array> +#include <bitset> +#include <cmath> +#include <compileTimeFormatter.h> +#include <iomanip> +#include <map> +#include <mariadb_rpl.h> +#include <memory> +#include <vector> + +#include <bitset.h> +#include <rawDataReader.h> +#include <row.h> +#include <streamSupport.h> + +using namespace AdHoc; +using MariaDB_Rpl_Ptr = std::unique_ptr<MARIADB_RPL, decltype(&mariadb_rpl_close)>; +using MariaDB_Event_Ptr = std::unique_ptr<MARIADB_RPL_EVENT, decltype(&mariadb_free_rpl_event)>; + +struct EventHandler { + std::string_view name; + void (*func)(MariaDB_Event_Ptr); +}; +using EventHandlers = std::array<EventHandler, ENUM_END_EVENT>; + +using TableId = decltype(st_mariadb_rpl_table_map_event::table_id); +using TableMaps = std::map<TableId, MariaDB_Event_Ptr>; +TableMaps tableMaps; + +struct write { + template<typename T> + void + operator()(const T & v) const + { + scprintf<"\t\t%?\n">(std::cout, v); + } + void + operator()(const uint8_t & v) const + { + scprintf<"\t\t%d\n">(std::cout, v); + } + void + operator()(const int8_t & v) const + { + scprintf<"\t\t%d\n">(std::cout, v); + } +}; +static void +dumpRowData(const st_mariadb_rpl_rows_event & row) +{ + MyGrate::Row {row, tableMaps.at(row.table_id)->event.table_map}.forEachField([](auto && fv) { + std::visit(write {}, fv); + }); +} + +static void +doTableMap(MariaDB_Event_Ptr event) +{ + const auto & tm = event->event.table_map; + AdHoc::scprintf<"Table map %?.%? -> %?\n">(std::cout, tm.database, tm.table, tm.table_id); + // for (auto c = 0U; c < tm.column_types.length; c++) { + // AdHoc::scprintf<"\t%#02hx\n">(std::cout, tm.column_types.str[c]); + //} + tableMaps.insert_or_assign(tm.table_id, std::move(event)); +} + +static void +doWrite(MariaDB_Event_Ptr event) +{ + const auto & rs = event->event.rows; + AdHoc::scprintf<"Insert into %?\n">(std::cout, rs.table_id); + dumpRowData(event->event.rows); +} + +static void +doUpdate(MariaDB_Event_Ptr event) +{ + const auto & rs = event->event.rows; + AdHoc::scprintf<"Update %?\n">(std::cout, rs.table_id); + dumpRowData(event->event.rows); +} + +static void +doDelete(MariaDB_Event_Ptr event) +{ + const auto & rs = event->event.rows; + AdHoc::scprintf<"Delete from %?\n">(std::cout, rs.table_id); + dumpRowData(event->event.rows); +} + +constexpr EventHandlers eventHandlers {[]() { + EventHandlers eh {}; + eh[UNKNOWN_EVENT] = {"UNKNOWN_EVENT", nullptr}; + eh[START_EVENT_V3] = {"START_EVENT_V3", nullptr}; + eh[QUERY_EVENT] = {"QUERY_EVENT", nullptr}; + eh[STOP_EVENT] = {"STOP_EVENT", nullptr}; + eh[ROTATE_EVENT] = {"ROTATE_EVENT", nullptr}; + eh[INTVAR_EVENT] = {"INTVAR_EVENT", nullptr}; + eh[LOAD_EVENT] = {"LOAD_EVENT", nullptr}; + eh[SLAVE_EVENT] = {"SLAVE_EVENT", nullptr}; + eh[CREATE_FILE_EVENT] = {"CREATE_FILE_EVENT", nullptr}; + eh[APPEND_BLOCK_EVENT] = {"APPEND_BLOCK_EVENT", nullptr}; + eh[EXEC_LOAD_EVENT] = {"EXEC_LOAD_EVENT", nullptr}; + eh[DELETE_FILE_EVENT] = {"DELETE_FILE_EVENT", nullptr}; + eh[NEW_LOAD_EVENT] = {"NEW_LOAD_EVENT", nullptr}; + eh[RAND_EVENT] = {"RAND_EVENT", nullptr}; + eh[USER_VAR_EVENT] = {"USER_VAR_EVENT", nullptr}; + eh[FORMAT_DESCRIPTION_EVENT] = {"FORMAT_DESCRIPTION_EVENT", nullptr}; + eh[XID_EVENT] = {"XID_EVENT", nullptr}; + eh[BEGIN_LOAD_QUERY_EVENT] = {"BEGIN_LOAD_QUERY_EVENT", nullptr}; + eh[EXECUTE_LOAD_QUERY_EVENT] = {"EXECUTE_LOAD_QUERY_EVENT", nullptr}; + eh[TABLE_MAP_EVENT] = {"TABLE_MAP_EVENT", doTableMap}; + eh[PRE_GA_WRITE_ROWS_EVENT] = {"PRE_GA_WRITE_ROWS_EVENT", nullptr}; + eh[PRE_GA_UPDATE_ROWS_EVENT] = {"PRE_GA_UPDATE_ROWS_EVENT", nullptr}; + eh[PRE_GA_DELETE_ROWS_EVENT] = {"PRE_GA_DELETE_ROWS_EVENT", nullptr}; + eh[WRITE_ROWS_EVENT_V1] = {"WRITE_ROWS_EVENT_V1", doWrite}; + eh[UPDATE_ROWS_EVENT_V1] = {"UPDATE_ROWS_EVENT_V1", doUpdate}; + eh[DELETE_ROWS_EVENT_V1] = {"DELETE_ROWS_EVENT_V1", doDelete}; + eh[INCIDENT_EVENT] = {"INCIDENT_EVENT", nullptr}; + eh[HEARTBEAT_LOG_EVENT] = {"HEARTBEAT_LOG_EVENT", nullptr}; + eh[IGNORABLE_LOG_EVENT] = {"IGNORABLE_LOG_EVENT", nullptr}; + eh[ROWS_QUERY_LOG_EVENT] = {"ROWS_QUERY_LOG_EVENT", nullptr}; + eh[WRITE_ROWS_EVENT] = {"WRITE_ROWS_EVENT", nullptr}; + eh[UPDATE_ROWS_EVENT] = {"UPDATE_ROWS_EVENT", nullptr}; + eh[DELETE_ROWS_EVENT] = {"DELETE_ROWS_EVENT", nullptr}; + eh[GTID_LOG_EVENT] = {"GTID_LOG_EVENT", nullptr}; + eh[ANONYMOUS_GTID_LOG_EVENT] = {"ANONYMOUS_GTID_LOG_EVENT", nullptr}; + eh[PREVIOUS_GTIDS_LOG_EVENT] = {"PREVIOUS_GTIDS_LOG_EVENT", nullptr}; + eh[TRANSACTION_CONTEXT_EVENT] = {"TRANSACTION_CONTEXT_EVENT", nullptr}; + eh[VIEW_CHANGE_EVENT] = {"VIEW_CHANGE_EVENT", nullptr}; + eh[XA_PREPARE_LOG_EVENT] = {"XA_PREPARE_LOG_EVENT", nullptr}; + eh[MARIA_EVENTS_BEGIN] = {"MARIA_EVENTS_BEGIN", nullptr}; + eh[ANNOTATE_ROWS_EVENT] = {"ANNOTATE_ROWS_EVENT", nullptr}; + eh[BINLOG_CHECKPOINT_EVENT] = {"BINLOG_CHECKPOINT_EVENT", nullptr}; + eh[GTID_EVENT] = {"GTID_EVENT", nullptr}; + eh[GTID_LIST_EVENT] = {"GTID_LIST_EVENT", nullptr}; + eh[START_ENCRYPTION_EVENT] = {"START_ENCRYPTION_EVENT", nullptr}; + eh[QUERY_COMPRESSED_EVENT] = {"QUERY_COMPRESSED_EVENT", nullptr}; + eh[WRITE_ROWS_COMPRESSED_EVENT_V1] = {"WRITE_ROWS_COMPRESSED_EVENT_V1", nullptr}; + eh[UPDATE_ROWS_COMPRESSED_EVENT_V1] = {"UPDATE_ROWS_COMPRESSED_EVENT_V1", nullptr}; + eh[DELETE_ROWS_COMPRESSED_EVENT_V1] = {"DELETE_ROWS_COMPRESSED_EVENT_V1", nullptr}; + eh[WRITE_ROWS_COMPRESSED_EVENT] = {"WRITE_ROWS_COMPRESSED_EVENT", nullptr}; + eh[UPDATE_ROWS_COMPRESSED_EVENT] = {"UPDATE_ROWS_COMPRESSED_EVENT", nullptr}; + eh[DELETE_ROWS_COMPRESSED_EVENT] = {"DELETE_ROWS_COMPRESSED_EVENT", nullptr}; + return eh; +}()}; + +static void +read_events(MYSQL * mysql) +{ + auto rpl = MariaDB_Rpl_Ptr {mariadb_rpl_init(mysql), &mariadb_rpl_close}; + + mysql_query(mysql, "SET @mariadb_slave_capability=4"); + // mysql_query(mysql, "SET @slave_gtid_strict_mode=1"); + // mysql_query(mysql, "SET @slave_gtid_ignore_duplicates=1"); + mysql_query(mysql, "SET NAMES utf8"); + mysql_query(mysql, "SET @master_binlog_checksum= @@global.binlog_checksum"); + + mariadb_rpl_optionsv(rpl.get(), MARIADB_RPL_SERVER_ID, 12); + mariadb_rpl_optionsv(rpl.get(), MARIADB_RPL_FILENAME, "mariadb-bin.000242"); + mariadb_rpl_optionsv(rpl.get(), MARIADB_RPL_START, 4); + mariadb_rpl_optionsv(rpl.get(), MARIADB_RPL_FLAGS, MARIADB_RPL_BINLOG_SEND_ANNOTATE_ROWS); + + if (mariadb_rpl_open(rpl.get())) { + throw std::runtime_error("Failed to mariadb_rpl_open"); + } + + while (MariaDB_Event_Ptr event {mariadb_rpl_fetch(rpl.get(), nullptr), &mariadb_free_rpl_event}) { + const auto & h = eventHandlers.at(event->event_type); + if (h.func) { + h.func(std::move(event)); + } + else { + std::cout << h.name << "\n"; + } + } +} + +int +main(int, char **) +{ + std::cout << std::boolalpha; + MYSQL conn; + mysql_init(&conn); + if (!mysql_real_connect(&conn, "192.168.1.38", "repl", "r3pl", "", + // NOLINTNEXTLINE(hicpp-signed-bitwise) + 3306, nullptr, CLIENT_LOCAL_FILES | CLIENT_MULTI_STATEMENTS)) { + throw std::runtime_error("ConnectionError"); + } + read_events(&conn); + mysql_close(&conn); +} diff --git a/test/Jamfile.jam b/test/Jamfile.jam new file mode 100644 index 0000000..e6a833a --- /dev/null +++ b/test/Jamfile.jam @@ -0,0 +1,11 @@ +lib boost_unit_test_framework ; + +project : requirements + <library>../lib//mygrate + <library>boost_unit_test_framework + <define>BOOST_TEST_DYN_LINK + ; + +run test-rawDataReader.cpp ; +run test-bitset.cpp ; +run test-streams.cpp ; diff --git a/test/helpers.h b/test/helpers.h new file mode 100644 index 0000000..107eda5 --- /dev/null +++ b/test/helpers.h @@ -0,0 +1,44 @@ +#ifndef MYGRATE_TEST_HELPERS_H +#define MYGRATE_TEST_HELPERS_H + +#include <concepts> +#include <cstddef> +#include <ctime> + +inline constexpr std::byte operator""_b(const unsigned long long hex) +{ + return std::byte(hex); +} + +inline constexpr bool +operator==(const struct tm & a, const struct tm & b) +{ + return (a.tm_year == b.tm_year) && (a.tm_mon == b.tm_mon) && (a.tm_mday == b.tm_mday) && (a.tm_hour == b.tm_hour) + && (a.tm_min == b.tm_min) && (a.tm_sec == b.tm_sec); +} + +namespace std { + template<integral T> + inline constexpr bool + operator!=(const byte b, const T i) + { + return to_integer<T>(b) != i; + } +} + +inline struct tm +make_tm(int year, int mon, int day, int hr, int min, int sec) +{ + struct tm tm { + }; + tm.tm_year = year - 1900; + tm.tm_mon = mon - 1; + tm.tm_mday = day; + tm.tm_hour = hr; + tm.tm_min = min; + tm.tm_sec = sec; + mktime(&tm); + return tm; +} + +#endif diff --git a/test/test-bitset.cpp b/test/test-bitset.cpp new file mode 100644 index 0000000..83e136c --- /dev/null +++ b/test/test-bitset.cpp @@ -0,0 +1,31 @@ +#define BOOST_TEST_MODULE BitSet +#include <boost/test/data/test_case.hpp> +#include <boost/test/unit_test.hpp> + +#include "helpers.h" +#include <bitset.h> +#include <bitset> + +using namespace MyGrate; + +BOOST_TEST_DONT_PRINT_LOG_VALUE(BitSet::Iterator); + +BOOST_AUTO_TEST_CASE(bitset) +{ + std::byte bytes[3] {0x00_b, 0xFF_b, 0xa1_b}; + BitSet bs {bytes}; + + BOOST_REQUIRE_EQUAL(bs.size(), 24); + + std::bitset<24> exp {0xa1ff00}; + + auto iter {bs.begin()}; + + for (auto x {0U}; x < 24 && iter != bs.end(); x++) { + BOOST_TEST_INFO(x); + BOOST_CHECK_EQUAL(*iter, exp.test(x)); + iter++; + } + + BOOST_CHECK_EQUAL(iter, bs.end()); +} diff --git a/test/test-rawDataReader.cpp b/test/test-rawDataReader.cpp new file mode 100644 index 0000000..4ac0a8a --- /dev/null +++ b/test/test-rawDataReader.cpp @@ -0,0 +1,163 @@ +#define BOOST_TEST_MODULE RawDataReader +#include <boost/mpl/list.hpp> +#include <boost/test/data/test_case.hpp> +#include <boost/test/unit_test.hpp> + +#include "helpers.h" +#include <mysql_types.h> +#include <rawDataReader.h> +#include <streamSupport.h> + +using namespace MyGrate; + +using Bytes = std::vector<uint8_t>; +template<typename T> using BytesTo = std::tuple<Bytes, T>; + +BOOST_DATA_TEST_CASE(read_packedinteger, + boost::unit_test::data::make<BytesTo<uint64_t>>({ + {{0x00, 0xb2, 0x2, 0}, 0}, + {{0x01, 0xb2, 0x2, 0}, 1}, + {{0xfa, 0xb2, 0x2, 0}, 0xfa}, + {{0xfc, 0xb2, 0x2, 0}, 0x02b2}, + {{0xfd, 0xb2, 0x2, 0}, 690}, + {{0xfe, 0xb2, 0x2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 690}, + }), + bytes, out) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + BOOST_CHECK_EQUAL((rdr.readValue<PackedInteger>()), out); +} + +BOOST_DATA_TEST_CASE(invalid_packed_ints, + boost::unit_test::data::make<Bytes>({ + {0xFF, 0x78, 0x38, 0x1a, 0x0b}, + {0xFB, 0x78, 0x38, 0x1a, 0x0b}, + }), + bytes) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + BOOST_CHECK_THROW(rdr.readValue<PackedInteger>(), std::domain_error); +} + +BOOST_DATA_TEST_CASE(read_overflow, + boost::unit_test::data::make<Bytes>({ + {0x00, 0xFB, 0x12, 0x00}, + }), + bytes) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + rdr.discard(1); + BOOST_CHECK_EQUAL(rdr.readValue<uint16_t>(), 0x12FB); + BOOST_CHECK_THROW(rdr.readValue<uint16_t>(), std::range_error); +} + +/* +BOOST_DATA_TEST_CASE(read_overflow_string, + boost::unit_test::data::make<Bytes>({ + {0x04, 'a', 'b', 'c'}, + }), + bytes) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + // BOOST_CHECK_THROW(rdr.readValue<VarChar<1>>(), std::range_error); +} + +BOOST_DATA_TEST_CASE(read_datetime2, + boost::unit_test::data::make<BytesTo<struct tm>>({ + {{0x99, 0x78, 0x38, 0x1a, 0x0b}, make_tm(2006, 2, 28, 1, 40, 11)}, + }), + bytes, exp) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + (void)exp; + // BOOST_CHECK_EQUAL((rdr.readValue<DateTime2>()), out); +} + +BOOST_DATA_TEST_CASE(read_varchars1, + boost::unit_test::data::make<BytesTo<std::string_view>>({ + {{0x00}, ""}, + {{0x01, 'A'}, "A"}, + {{0x03, 'a', 'b', 'c'}, "abc"}, + {{0x10, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}, + "0123456789abcdef"}, + }), + bytes, exp) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + (void)exp; + // BOOST_CHECK_EQUAL((rdr.readValue<VarChar<1>>()), exp); +} + +BOOST_DATA_TEST_CASE(read_varchars2, + boost::unit_test::data::make<BytesTo<std::string_view>>({ + {{0x00, 0x00}, ""}, + {{0x01, 0x00, 'A'}, "A"}, + {{0x03, 0x00, 'a', 'b', 'c'}, "abc"}, + {{0x10, 0x00, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}, + "0123456789abcdef"}, + }), + bytes, exp) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + (void)exp; + // BOOST_CHECK_EQUAL((rdr.readValue<VarChar<2>>()), exp); +} +*/ + +BOOST_DATA_TEST_CASE(read_bytes, + boost::unit_test::data::make<Bytes>({ + {}, + {0x01}, + {0x00, 0x01, 0x02}, + {0xFF, 0xFE, 0xFD}, + {0xFF, 0xFE, 0xFD, 0x00, 0x01, 0x02}, + }), + bytes) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + const auto out {rdr.viewValue<MyGrate::MySQL::Blob>(bytes.size())}; + BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), bytes.begin(), bytes.end()); +} + +BOOST_DATA_TEST_CASE(read_bytes_overflow, + boost::unit_test::data::make<Bytes>({ + {}, + {0x01}, + {0x00, 0x01, 0x02}, + {0xFF, 0xFE, 0xFD}, + {0xFF, 0xFE, 0xFD, 0x00, 0x01, 0x02}, + }), + bytes) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + BOOST_CHECK_THROW(rdr.viewValue<MyGrate::MySQL::Blob>(bytes.size() + 1), std::range_error); +} + +BOOST_DATA_TEST_CASE(read_field_type, + boost::unit_test::data::make<BytesTo<enum_field_types>>({ + {{0x00}, MYSQL_TYPE_DECIMAL}, + {{0x04}, MYSQL_TYPE_FLOAT}, + {{0xFF}, MYSQL_TYPE_GEOMETRY}, + }), + bytes, exp) +{ + RawDataReader rdr {bytes.data(), bytes.size()}; + BOOST_CHECK_EQUAL(rdr.readValue<enum_field_types>(1), exp); +} + +BOOST_AUTO_TEST_CASE(rdr_from_MARIADB_STRING) +{ + char buf[5] = "test"; + MARIADB_STRING str {buf, strlen(buf)}; + RawDataReader rdr {str}; + BOOST_CHECK_EQUAL(rdr.viewValue<std::string_view>(4), "test"); +} + +using SimpleTypes = boost::mpl::list<std::byte, int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, + uint64_t, float, double>; + +BOOST_AUTO_TEST_CASE_TEMPLATE(rdr_read_simple, T, SimpleTypes) +{ + RawDataReader rdr {"don't care, some bytes", 20}; + rdr.readValue<T>(); +} diff --git a/test/test-streams.cpp b/test/test-streams.cpp new file mode 100644 index 0000000..9e79bd6 --- /dev/null +++ b/test/test-streams.cpp @@ -0,0 +1,92 @@ +#define BOOST_TEST_MODULE StreamSupport +#include <boost/test/data/test_case.hpp> +#include <boost/test/unit_test.hpp> + +#include "helpers.h" +#include <streamSupport.h> + +BOOST_FIXTURE_TEST_SUITE(stream, std::stringstream); + +template<typename In> using ToStream = std::tuple<In, std::string_view>; +BOOST_DATA_TEST_CASE(bytes, + boost::unit_test::data::make<ToStream<std::byte>>({ + //{0x00_b, "0x00"}, + {0x10_b, "0x10"}, + {0xFE_b, "0xfe"}, + {0xff_b, "0xff"}, + }), + in, exp) +{ + *this << in; + BOOST_CHECK_EQUAL(this->str(), exp); +} + +BOOST_DATA_TEST_CASE(tms, + boost::unit_test::data::make<ToStream<tm>>({ + {make_tm(2016, 1, 4, 12, 13, 14), "2016-01-04 12:13:14"}, + {make_tm(2016, 12, 31, 0, 0, 1), "2016-12-31 00:00:01"}, + }), + in, exp) +{ + *this << in; + BOOST_CHECK_EQUAL(this->str(), exp); +} + +BOOST_DATA_TEST_CASE(rts, + boost::unit_test::data::make<ToStream<MyGrate::MySQL::DateTime>>({ + {{{2016, 1, 4}, {12, 13, 14}}, "2016-01-04 12:13:14"}, + {{{2016, 12, 31}, {0, 0, 1}}, "2016-12-31 00:00:01"}, + }), + in, exp) +{ + *this << in; + BOOST_CHECK_EQUAL(this->str(), exp); +} + +BOOST_DATA_TEST_CASE(tss, + boost::unit_test::data::make<ToStream<timespec>>({ + {{0, 0}, "0.000000000"}, + {{0, 1}, "0.000000001"}, + {{1, 0}, "1.000000000"}, + {{123, 0}, "123.000000000"}, + {{123, 999999999}, "123.999999999"}, + }), + in, exp) +{ + *this << in; + BOOST_CHECK_EQUAL(this->str(), exp); +} +constexpr std::byte somebits[3] {0x00_b, 0xFF_b, 0xa1_b}; +BOOST_DATA_TEST_CASE(bss, + boost::unit_test::data::make<ToStream<MyGrate::BitSet>>({ + {{MyGrate::BitSet {somebits}}, "[0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,1]"}, + }), + in, exp) +{ + *this << in; + BOOST_CHECK_EQUAL(this->str(), exp); +} + +BOOST_AUTO_TEST_CASE(mariadb_string) +{ + char buf[5] = "test"; + MARIADB_STRING str {buf, strlen(buf)}; + *this << str; + BOOST_CHECK_EQUAL(this->str(), buf); +} + +BOOST_AUTO_TEST_CASE(array) +{ + std::array<int, 3> arr {1, 123456, -78910}; + *this << arr; + BOOST_CHECK_EQUAL(this->str(), "[1,123456,-78910]"); +} + +BOOST_AUTO_TEST_CASE(vector) +{ + std::vector<int> arr {1, 123456, -78910}; + *this << arr; + BOOST_CHECK_EQUAL(this->str(), "[1,123456,-78910]"); +} + +BOOST_AUTO_TEST_SUITE_END(); |