diff options
Diffstat (limited to 'lib/input')
-rw-r--r-- | lib/input/mysqlBindings.h | 178 | ||||
-rw-r--r-- | lib/input/mysqlConn.cpp | 73 | ||||
-rw-r--r-- | lib/input/mysqlConn.h | 3 | ||||
-rw-r--r-- | lib/input/mysqlRecordSet.cpp | 99 | ||||
-rw-r--r-- | lib/input/mysqlRecordSet.h | 37 | ||||
-rw-r--r-- | lib/input/mysqlStmt.cpp | 35 | ||||
-rw-r--r-- | lib/input/mysqlStmt.h | 29 |
7 files changed, 391 insertions, 63 deletions
diff --git a/lib/input/mysqlBindings.h b/lib/input/mysqlBindings.h new file mode 100644 index 0000000..dcb3ebf --- /dev/null +++ b/lib/input/mysqlBindings.h @@ -0,0 +1,178 @@ +#ifndef MYGRATE_INPUT_MYSQLBINDINGS_H +#define MYGRATE_INPUT_MYSQLBINDINGS_H + +#include <dbTypes.h> +#include <helpers.h> +#include <initializer_list> +#include <mysql.h> +#include <mysql_types.h> +#include <variant> +#include <vector> + +namespace MyGrate::Input { + struct BingingData { + explicit BingingData(unsigned long l, my_bool n = 0) : len {l}, null {n} { } + unsigned long len; + my_bool null; + }; + + struct Bindings { + // NOLINTNEXTLINE(hicpp-explicit-conversions) + explicit Bindings(const std::initializer_list<DbValue> & vs) + { + binds.reserve(vs.size()); + data.reserve(vs.size()); + for (const auto & v : vs) { + std::visit(*this, v); + } + } + template<std::integral T> + void + operator()(const T & v) + { + auto & b = binds.emplace_back(); + b.buffer_type = MySQL::CType<T>::type; + b.buffer = const_cast<T *>(&v); + b.is_unsigned = std::unsigned_integral<T>; + } + template<std::floating_point T> + void + operator()(const T & v) + { + auto & b = binds.emplace_back(); + b.buffer_type = MySQL::CType<T>::type; + b.buffer = const_cast<T *>(&v); + } + template<Viewable T> + void + operator()(const T & v) + { + auto & b = binds.emplace_back(); + b.buffer_type = MySQL::CType<T>::type; + b.buffer = const_cast<typename T::value_type *>(v.data()); + b.length = &data.emplace_back(v.size(), 0).len; + } + void + operator()(const std::nullptr_t &) + { + auto & b = binds.emplace_back(); + b.buffer = nullptr; + b.is_null = &data.emplace_back(0, 1).null; + } + template<typename T> + void + operator()(const T &) + { + throw std::runtime_error("Not implemented"); + } + std::vector<MYSQL_BIND> binds; + std::vector<BingingData> data; + }; + + class ResultData : public BingingData { + public: + ResultData() : BingingData {0} { } + virtual ~ResultData() = default; + + [[nodiscard]] virtual DbValue getValue() const = 0; + }; + + template<typename T> class ResultDataT : public ResultData { + public: + ResultDataT(MYSQL_BIND & b, const MYSQL_FIELD & f) + { + b.buffer = &buf; + b.buffer_length = sizeof(T); + b.is_null = &this->null; + b.length = &this->len; + b.is_unsigned = std::is_unsigned_v<T>; + b.buffer_type = f.type; + } + + [[nodiscard]] DbValue + getValue() const override + { + return buf; + } + + private: + T buf {}; + }; + + template<> class ResultDataT<std::string_view> : public ResultData { + public: + ResultDataT(MYSQL_BIND & b, const MYSQL_FIELD & f) : buf(f.length) + { + b.buffer_length = buf.size(); + b.buffer = buf.data(); + b.is_null = &this->null; + b.length = &this->len; + b.buffer_type = f.type; + } + + [[nodiscard]] DbValue + getValue() const override + { + return std::string_view {buf.data(), this->len}; + } + + private: + std::vector<char> buf; + }; + + template<> class ResultDataT<Blob> : public ResultData { + public: + ResultDataT(MYSQL_BIND & b, const MYSQL_FIELD & f) : buf(f.length) + { + b.buffer_length = buf.size(); + b.buffer = buf.data(); + b.is_null = &this->null; + b.length = &this->len; + b.buffer_type = f.type; + } + + [[nodiscard]] DbValue + getValue() const override + { + return Blob {buf.data(), this->len}; + } + + private: + std::vector<std::byte> buf; + }; + + template<typename Out> class ResultDataTime : public ResultData { + public: + ResultDataTime(MYSQL_BIND & b, const MYSQL_FIELD & f) + { + b.buffer_length = sizeof(MYSQL_TIME); + b.buffer = &buf; + b.is_null = &this->null; + b.length = &this->len; + b.buffer_type = f.type; + } + + [[nodiscard]] DbValue + getValue() const override + { + return Out {*this}; + } + + private: + operator Date() const + { + return Date(buf.year, buf.month, buf.day); + } + operator Time() const + { + return Time(buf.hour, buf.minute, buf.second); + } + operator DateTime() const + { + return DateTime(*this, *this); + } + MYSQL_TIME buf; + }; +} + +#endif diff --git a/lib/input/mysqlConn.cpp b/lib/input/mysqlConn.cpp index 179f9d5..46ed7c6 100644 --- a/lib/input/mysqlConn.cpp +++ b/lib/input/mysqlConn.cpp @@ -1,18 +1,17 @@ #include "mysqlConn.h" -#include "helpers.h" +#include "mysqlBindings.h" +#include "mysqlStmt.h" #include <cstddef> #include <cstring> +#include <dbConn.h> #include <dbTypes.h> +#include <helpers.h> #include <memory> #include <mysql.h> -#include <mysql_types.h> #include <stdexcept> -#include <variant> #include <vector> namespace MyGrate::Input { - using StmtPtr = std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_close)>; - MySQLConn::MySQLConn( const char * const host, const char * const user, const char * const pass, unsigned short port) : st_mysql {} @@ -36,64 +35,6 @@ namespace MyGrate::Input { verify<std::runtime_error>(!mysql_query(this, q), q); } - struct Bindings { - // NOLINTNEXTLINE(hicpp-explicit-conversions) - explicit Bindings(const std::initializer_list<DbValue> & vs) - { - binds.reserve(vs.size()); - extras.reserve(vs.size()); - for (const auto & v : vs) { - std::visit(*this, v); - } - } - template<std::integral T> - void - operator()(const T & v) - { - auto & b = binds.emplace_back(); - b.buffer_type = MySQL::CType<T>::type; - b.buffer = const_cast<T *>(&v); - b.is_unsigned = std::unsigned_integral<T>; - } - template<std::floating_point T> - void - operator()(const T & v) - { - auto & b = binds.emplace_back(); - b.buffer_type = MySQL::CType<T>::type; - b.buffer = const_cast<T *>(&v); - } - template<Viewable T> - void - operator()(const T & v) - { - auto & b = binds.emplace_back(); - b.buffer_type = MySQL::CType<T>::type; - b.buffer = const_cast<typename T::value_type *>(v.data()); - b.length = &extras.emplace_back(v.size(), 0).len; - } - void - operator()(const std::nullptr_t &) - { - auto & b = binds.emplace_back(); - b.buffer = nullptr; - b.is_null = &extras.emplace_back(0, 1).null; - } - template<typename T> - void - operator()(const T &) - { - throw std::runtime_error("Not implemented"); - } - struct extra { - explicit extra(unsigned long l, my_bool n = 0) : len {l}, null {n} { } - unsigned long len; - my_bool null; - }; - std::vector<MYSQL_BIND> binds; - std::vector<extra> extras; - }; - void MySQLConn::query(const char * const q, const std::initializer_list<DbValue> & vs) { @@ -104,4 +45,10 @@ namespace MyGrate::Input { verify<std::runtime_error>(!mysql_stmt_bind_param(stmt.get(), b.binds.data()), "Param count mismatch"); verify<std::runtime_error>(!mysql_stmt_execute(stmt.get()), q); } + + DbPrepStmtPtr + MySQLConn::prepare(const char * const q, std::size_t) + { + return std::make_unique<MySQLPrepStmt>(q, this); + } } diff --git a/lib/input/mysqlConn.h b/lib/input/mysqlConn.h index 9e4ec25..2f71262 100644 --- a/lib/input/mysqlConn.h +++ b/lib/input/mysqlConn.h @@ -1,6 +1,7 @@ #ifndef MYGRATE_INPUT_MYSQLCONN_H #define MYGRATE_INPUT_MYSQLCONN_H +#include <cstddef> #include <dbConn.h> #include <dbTypes.h> #include <initializer_list> @@ -14,6 +15,8 @@ namespace MyGrate::Input { void query(const char * const) override; void query(const char * const q, const std::initializer_list<DbValue> &) override; + + DbPrepStmtPtr prepare(const char * const, std::size_t) override; }; } diff --git a/lib/input/mysqlRecordSet.cpp b/lib/input/mysqlRecordSet.cpp new file mode 100644 index 0000000..51be15f --- /dev/null +++ b/lib/input/mysqlRecordSet.cpp @@ -0,0 +1,99 @@ +#include "mysqlRecordSet.h" +#include "mysqlBindings.h" +#include "mysqlStmt.h" +#include <cstdint> +#include <dbTypes.h> +#include <helpers.h> +#include <stdexcept> +#include <string_view> +#include <utility> +// IWYU pragma: no_include <ext/alloc_traits.h> + +namespace MyGrate::Input { + MySQLRecordSet::MySQLRecordSet(StmtPtr s) : + stmt {std::move(s)}, stmtres {nullptr, nullptr}, fields(mysql_stmt_field_count(stmt.get())), + extras(fields.size()) + { + auto getBind = [](const MYSQL_FIELD & f, MYSQL_BIND & b) -> std::unique_ptr<ResultData> { + switch (f.type) { + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_DOUBLE: + return std::make_unique<ResultDataT<double>>(b, f); + case MYSQL_TYPE_FLOAT: + return std::make_unique<ResultDataT<float>>(b, f); + case MYSQL_TYPE_TINY: + return std::make_unique<ResultDataT<int8_t>>(b, f); + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + return std::make_unique<ResultDataT<int16_t>>(b, f); + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_INT24: + return std::make_unique<ResultDataT<int32_t>>(b, f); + case MYSQL_TYPE_LONGLONG: + return std::make_unique<ResultDataT<int64_t>>(b, f); + case MYSQL_TYPE_NULL: + return std::make_unique<ResultDataT<std::nullptr_t>>(b, f); + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: + return std::make_unique<ResultDataTime<DateTime>>(b, f); + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: + return std::make_unique<ResultDataTime<Time>>(b, f); + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_NEWDATE: + return std::make_unique<ResultDataTime<Date>>(b, f); + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_JSON: + case MYSQL_TYPE_ENUM: + return std::make_unique<ResultDataT<std::string_view>>(b, f); + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + return std::make_unique<ResultDataT<Blob>>(b, f); + case MAX_NO_FIELD_TYPES: + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_GEOMETRY:; + } + throw std::runtime_error("Unsupported column type"); + }; + ResPtr meta {mysql_stmt_result_metadata(stmt.get()), mysql_free_result}; + const auto fieldDefs = mysql_fetch_fields(meta.get()); + verify<std::runtime_error>(fieldDefs, "Fetch fields"); + for (std::size_t i = 0; i < fields.size(); i += 1) { + extras[i] = getBind(fieldDefs[i], fields[i]); + } + verify<std::runtime_error>(!mysql_stmt_bind_result(stmt.get(), fields.data()), "Store result error"); + verify<std::runtime_error>(!mysql_stmt_store_result(stmt.get()), "Store result error"); + stmtres = {stmt.get(), mysql_stmt_free_result}; + verify<std::runtime_error>(!mysql_stmt_fetch(stmt.get()), "Fetch"); + } + + std::size_t + MySQLRecordSet::rows() const + { + return mysql_stmt_num_rows(stmt.get()); + } + + std::size_t + MySQLRecordSet::columns() const + { + return fields.size(); + } + + DbValue + MySQLRecordSet::at(std::size_t row, std::size_t col) const + { + mysql_stmt_data_seek(stmt.get(), row); + if (extras[col]->null) { + return nullptr; + } + return extras[col]->getValue(); + } +} diff --git a/lib/input/mysqlRecordSet.h b/lib/input/mysqlRecordSet.h new file mode 100644 index 0000000..849a653 --- /dev/null +++ b/lib/input/mysqlRecordSet.h @@ -0,0 +1,37 @@ +#ifndef MYGRATE_INPUT_MYSQLRECORDSET_H +#define MYGRATE_INPUT_MYSQLRECORDSET_H + +#include "mysqlStmt.h" +#include <cstddef> +#include <dbRecordSet.h> +#include <dbTypes.h> +#include <memory> +#include <mysql.h> +#include <vector> + +namespace MyGrate::Input { + class ResultData; + + class MySQLRecordSet : public RecordSet { + public: + using ResPtr = std::unique_ptr<MYSQL_RES, decltype(&mysql_free_result)>; + using StmtResPtr = std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_free_result)>; + using ResultDataPtr = std::unique_ptr<ResultData>; + + explicit MySQLRecordSet(StmtPtr s); + + std::size_t rows() const override; + + std::size_t columns() const override; + + DbValue at(std::size_t row, std::size_t col) const override; + + private: + StmtPtr stmt; + StmtResPtr stmtres; + std::vector<MYSQL_BIND> fields; + std::vector<ResultDataPtr> extras; + }; +} + +#endif diff --git a/lib/input/mysqlStmt.cpp b/lib/input/mysqlStmt.cpp new file mode 100644 index 0000000..08d1303 --- /dev/null +++ b/lib/input/mysqlStmt.cpp @@ -0,0 +1,35 @@ +#include "mysqlStmt.h" +#include "mysqlBindings.h" +#include "mysqlRecordSet.h" +#include <cstring> +#include <helpers.h> +#include <stdexcept> +#include <utility> +#include <vector> + +namespace MyGrate::Input { + MySQLPrepStmt::MySQLPrepStmt(const char * const q, MYSQL * c) : stmt {mysql_stmt_init(c), &mysql_stmt_close} + { + verify<std::runtime_error>(!mysql_stmt_prepare(stmt.get(), q, strlen(q)), q); + } + + void + MySQLPrepStmt::execute(const std::initializer_list<DbValue> & vs) + { + Bindings b {vs}; + verify<std::runtime_error>(!mysql_stmt_bind_param(stmt.get(), b.binds.data()), "Param count mismatch"); + verify<std::runtime_error>(!mysql_stmt_execute(stmt.get()), "Prepared statement execute"); + } + + std::size_t + MySQLPrepStmt::rows() const + { + return mysql_stmt_affected_rows(stmt.get()); + } + + RecordSetPtr + MySQLPrepStmt::recordSet() + { + return std::make_unique<MySQLRecordSet>(std::move(stmt)); + } +} diff --git a/lib/input/mysqlStmt.h b/lib/input/mysqlStmt.h new file mode 100644 index 0000000..a42e0db --- /dev/null +++ b/lib/input/mysqlStmt.h @@ -0,0 +1,29 @@ +#ifndef MYGRATE_INPUT_MYSQLSTMT_H +#define MYGRATE_INPUT_MYSQLSTMT_H + +#include "dbConn.h" +#include "dbRecordSet.h" +#include "dbTypes.h" +#include <cstddef> +#include <initializer_list> +#include <memory> +#include <mysql.h> + +namespace MyGrate::Input { + using StmtPtr = std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_close)>; + + class MySQLPrepStmt : public DbPrepStmt { + public: + MySQLPrepStmt(const char * const q, MYSQL * c); + void execute(const std::initializer_list<DbValue> & vs) override; + + std::size_t rows() const override; + + RecordSetPtr recordSet() override; + + private: + StmtPtr stmt; + }; +} + +#endif |