From 63b2ca0dbdae190941d60a55c9cff99d4a75a0e1 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 31 May 2021 13:18:17 +0100 Subject: Initial commit of prepstmt, selects, record sets This is full of holes, but all the basics are covered. --- lib/output/pq/pqBindings.h | 57 ++++++++++++++++++++++++++++ lib/output/pq/pqConn.cpp | 73 +++++++----------------------------- lib/output/pq/pqConn.h | 14 ++++++- lib/output/pq/pqRecordSet.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++ lib/output/pq/pqRecordSet.h | 25 +++++++++++++ lib/output/pq/pqStmt.cpp | 54 +++++++++++++++++++++++++++ lib/output/pq/pqStmt.h | 37 +++++++++++++++++++ 7 files changed, 285 insertions(+), 61 deletions(-) create mode 100644 lib/output/pq/pqBindings.h create mode 100644 lib/output/pq/pqRecordSet.cpp create mode 100644 lib/output/pq/pqRecordSet.h create mode 100644 lib/output/pq/pqStmt.cpp create mode 100644 lib/output/pq/pqStmt.h (limited to 'lib/output') diff --git a/lib/output/pq/pqBindings.h b/lib/output/pq/pqBindings.h new file mode 100644 index 0000000..ef0df84 --- /dev/null +++ b/lib/output/pq/pqBindings.h @@ -0,0 +1,57 @@ +#ifndef MYGRATE_OUTPUT_PQ_PQBINDINGS +#define MYGRATE_OUTPUT_PQ_PQBINDINGS + +#include +#include +#include +#include +#include + +namespace MyGrate::Output::Pq { + struct Bindings { + // NOLINTNEXTLINE(hicpp-explicit-conversions) + explicit Bindings(const std::initializer_list & vs) + { + bufs.reserve(vs.size()); + values.reserve(vs.size()); + lengths.reserve(vs.size()); + for (const auto & v : vs) { + std::visit(*this, v); + } + } + template + void + operator()(const T & v) + { + bufs.emplace_back(std::to_string(v)); + const auto & vw {bufs.back()}; + values.emplace_back(vw.data()); + lengths.emplace_back(vw.length()); + } + template + void + operator()(const T & v) + { + values.emplace_back(v.data()); + lengths.emplace_back(v.size()); + } + template + void + operator()(const T &) + { + throw std::runtime_error("Not implemented"); + } + void + operator()(const std::nullptr_t &) + { + values.emplace_back(nullptr); + lengths.emplace_back(0); + } + + std::vector bufs; + std::vector values; + std::vector lengths; + }; +} + +#endif diff --git a/lib/output/pq/pqConn.cpp b/lib/output/pq/pqConn.cpp index 4f55ba8..81d9610 100644 --- a/lib/output/pq/pqConn.cpp +++ b/lib/output/pq/pqConn.cpp @@ -1,89 +1,44 @@ #include "pqConn.h" +#include "pqBindings.h" +#include "pqStmt.h" +#include #include #include #include #include -#include #include #include -#include #include namespace MyGrate::Output::Pq { - using ResPtr = std::unique_ptr; - - PqConn::PqConn(const char * const str) : conn {PQconnectdb(str)} + PqConn::PqConn(const char * const str) : conn {PQconnectdb(str), PQfinish} { - verify(PQstatus(conn) == CONNECTION_OK, "Connection failure"); - PQsetNoticeProcessor(conn, notice_processor, this); - } - - PqConn::~PqConn() - { - PQfinish(conn); + verify(PQstatus(conn.get()) == CONNECTION_OK, "Connection failure"); + PQsetNoticeProcessor(conn.get(), notice_processor, this); } void PqConn::query(const char * const q) { - ResPtr res {PQexec(conn, q), &PQclear}; + ResPtr res {PQexec(conn.get(), q), &PQclear}; verify(PQresultStatus(res.get()) == PGRES_COMMAND_OK, q); } - struct Bindings { - // NOLINTNEXTLINE(hicpp-explicit-conversions) - explicit Bindings(const std::initializer_list & vs) - { - bufs.reserve(vs.size()); - values.reserve(vs.size()); - lengths.reserve(vs.size()); - for (const auto & v : vs) { - std::visit(*this, v); - } - } - template - void - operator()(const T & v) - { - bufs.emplace_back(std::to_string(v)); - const auto & vw {bufs.back()}; - values.emplace_back(vw.data()); - lengths.emplace_back(vw.length()); - } - template - void - operator()(const T & v) - { - values.emplace_back(v.data()); - lengths.emplace_back(v.size()); - } - template - void - operator()(const T &) - { - throw std::runtime_error("Not implemented"); - } - void - operator()(const std::nullptr_t &) - { - values.emplace_back(nullptr); - lengths.emplace_back(0); - } - - std::vector bufs; - std::vector values; - std::vector lengths; - }; - void PqConn::query(const char * const q, const std::initializer_list & vs) { Bindings b {vs}; - ResPtr res {PQexecParams(conn, q, (int)vs.size(), nullptr, b.values.data(), b.lengths.data(), nullptr, 0), + ResPtr res {PQexecParams(conn.get(), q, (int)vs.size(), nullptr, b.values.data(), b.lengths.data(), nullptr, 0), &PQclear}; verify(PQresultStatus(res.get()) == PGRES_COMMAND_OK, q); } + DbPrepStmtPtr + PqConn::prepare(const char * const q, std::size_t n) + { + return std::make_unique(q, n, this); + } + void PqConn::notice_processor(void * p, const char * n) { diff --git a/lib/output/pq/pqConn.h b/lib/output/pq/pqConn.h index 613af6f..856683d 100644 --- a/lib/output/pq/pqConn.h +++ b/lib/output/pq/pqConn.h @@ -1,25 +1,35 @@ #ifndef MYGRATE_OUTPUT_PQ_PQCONN_H #define MYGRATE_OUTPUT_PQ_PQCONN_H +#include #include #include +#include #include #include +#include +#include +#include namespace MyGrate::Output::Pq { class PqConn : public DbConn { public: explicit PqConn(const char * const str); - virtual ~PqConn(); + virtual ~PqConn() = default; void query(const char * const) override; void query(const char * const, const std::initializer_list &) override; + DbPrepStmtPtr prepare(const char * const, std::size_t nParams) override; + private: static void notice_processor(void *, const char *); virtual void notice_processor(const char *) const; - PGconn * const conn; + std::unique_ptr const conn; + + friend class PqPrepStmt; + std::map> stmts; }; } diff --git a/lib/output/pq/pqRecordSet.cpp b/lib/output/pq/pqRecordSet.cpp new file mode 100644 index 0000000..71ddee4 --- /dev/null +++ b/lib/output/pq/pqRecordSet.cpp @@ -0,0 +1,86 @@ +#include "pqRecordSet.h" +#include "dbTypes.h" +#include "pqStmt.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace MyGrate::Output::Pq { + PqRecordSet::PqRecordSet(ResPtr r) : res {std::move(r)} { } + + std::size_t + PqRecordSet::rows() const + { + return PQntuples(res.get()); + } + + std::size_t + PqRecordSet::columns() const + { + return PQnfields(res.get()); + } + + DbValue + PqRecordSet::at(std::size_t row, std::size_t col) const + { + if (PQgetisnull(res.get(), (int)row, (int)col)) { + return nullptr; + } + const auto value {PQgetvalue(res.get(), (int)row, (int)col)}; + const auto size {static_cast(PQgetlength(res.get(), (int)row, (int)col))}; + const auto type {PQftype(res.get(), (int)col)}; + switch (type) { + // case BITOID: TODO bool + // case BOOLOID: TODO bool + // case BOOLARRAYOID: + case VARBITOID: + case BYTEAOID: + // This is wrong :) + return Blob {reinterpret_cast(value), size}; + case INT2OID: + return static_cast(std::strtol(value, nullptr, 10)); + case INT4OID: + return static_cast(std::strtol(value, nullptr, 10)); + case INT8OID: + return std::strtol(value, nullptr, 10); + case FLOAT4OID: + return std::strtof(value, nullptr); + case FLOAT8OID: + case CASHOID: + case NUMERICOID: + return std::strtod(value, nullptr); + case DATEOID: { + tm tm {}; + const auto end = strptime(value, "%F", &tm); + verify(end && !*end, "Invalid date string"); + return Date {tm}; + } + case TIMEOID: { + tm tm {}; + const auto end = strptime(value, "%T", &tm); + verify(end && !*end, "Invalid time string"); + return Time {tm}; + } + case TIMESTAMPOID: { + tm tm {}; + const auto end = strptime(value, "%FT%T", &tm); + verify(end && !*end, "Invalid timestamp string"); + return DateTime {tm}; + } + // case TIMESTAMPTZOID: Maybe add TZ support? + // case INTERVALOID: Maybe add interval support? + // case TIMETZOID: Maybe add TZ support? + case VOIDOID: + return nullptr; + default: + return std::string_view {value, size}; + } + } +} diff --git a/lib/output/pq/pqRecordSet.h b/lib/output/pq/pqRecordSet.h new file mode 100644 index 0000000..2934d84 --- /dev/null +++ b/lib/output/pq/pqRecordSet.h @@ -0,0 +1,25 @@ +#ifndef MYGRATE_OUTPUT_PQ_PQRECORDSET_H +#define MYGRATE_OUTPUT_PQ_PQRECORDSET_H + +#include "dbRecordSet.h" +#include "dbTypes.h" +#include "pqStmt.h" +#include + +namespace MyGrate::Output::Pq { + class PqRecordSet : public RecordSet { + public: + explicit PqRecordSet(ResPtr r); + + 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: + ResPtr res; + }; +} + +#endif diff --git a/lib/output/pq/pqStmt.cpp b/lib/output/pq/pqStmt.cpp new file mode 100644 index 0000000..04b48c6 --- /dev/null +++ b/lib/output/pq/pqStmt.cpp @@ -0,0 +1,54 @@ +#include "pqStmt.h" +#include "libpq-fe.h" +#include "pqBindings.h" +#include "pqConn.h" +#include "pqRecordSet.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace MyGrate::Output::Pq { + PqPrepStmt::PqPrepStmt(const char * const q, std::size_t n, PqConn * c) : + conn {c->conn.get()}, name {prepareAsNeeded(q, n, c)}, res {nullptr, nullptr} + { + } + + void + PqPrepStmt::execute(const std::initializer_list & vs) + { + Bindings b {vs}; + res = {PQexecPrepared(conn, name.c_str(), (int)vs.size(), b.values.data(), b.lengths.data(), nullptr, 0), + &PQclear}; + verify( + PQresultStatus(res.get()) == PGRES_COMMAND_OK || PQresultStatus(res.get()) == PGRES_TUPLES_OK, name); + } + + std::size_t + PqPrepStmt::rows() const + { + return std::strtoul(PQcmdTuples(res.get()), nullptr, 10); + } + + RecordSetPtr + PqPrepStmt::recordSet() + { + return std::make_unique(std::move(res)); + } + + std::string + PqPrepStmt::prepareAsNeeded(const char * const q, std::size_t n, PqConn * c) + { + if (const auto i = c->stmts.find(q); i != c->stmts.end()) { + return i->second; + } + auto nam {AdHoc::scprintf<"pst%0x">(c->stmts.size())}; + ResPtr res {PQprepare(c->conn.get(), nam.c_str(), q, (int)n, nullptr), PQclear}; + verify(PQresultStatus(res.get()) == PGRES_COMMAND_OK, q); + return c->stmts.emplace(q, std::move(nam)).first->second; + } +} diff --git a/lib/output/pq/pqStmt.h b/lib/output/pq/pqStmt.h new file mode 100644 index 0000000..b806617 --- /dev/null +++ b/lib/output/pq/pqStmt.h @@ -0,0 +1,37 @@ +#ifndef MYGRATE_OUTPUT_PQ_PQSTMT_H +#define MYGRATE_OUTPUT_PQ_PQSTMT_H + +#include "dbConn.h" +#include "dbRecordSet.h" +#include "dbTypes.h" +#include +#include +#include +#include +#include + +namespace MyGrate::Output::Pq { + class PqConn; + + using ResPtr = std::unique_ptr; + + class PqPrepStmt : public DbPrepStmt { + public: + PqPrepStmt(const char * const q, std::size_t n, PqConn * c); + + void execute(const std::initializer_list & vs) override; + + std::size_t rows() const override; + + RecordSetPtr recordSet() override; + + private: + static std::string prepareAsNeeded(const char * const q, std::size_t n, PqConn * c); + + PGconn * conn; + std::string name; + ResPtr res; + }; +} + +#endif -- cgit v1.2.3