diff options
Diffstat (limited to 'lib')
-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 |
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 |