From b9aaad1d4d8be6604c84aa11651b99ac932794af Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 5 Jun 2017 16:47:06 +0100 Subject: Add support for bytea / blob type data --- libpqpp/pq-bulkselectcommand.cpp | 2 +- libpqpp/pq-column.cpp | 20 +++++++++++++++++++- libpqpp/pq-column.h | 3 +++ libpqpp/pq-command.cpp | 19 ++++++++++++++----- libpqpp/pq-command.h | 3 +++ libpqpp/pq-cursorselectcommand.cpp | 2 +- libpqpp/pq-modifycommand.cpp | 2 +- libpqpp/unittests/pqschema.sql | 3 +++ libpqpp/unittests/testpq.cpp | 24 ++++++++++++++++++++++++ 9 files changed, 69 insertions(+), 9 deletions(-) (limited to 'libpqpp') diff --git a/libpqpp/pq-bulkselectcommand.cpp b/libpqpp/pq-bulkselectcommand.cpp index 503d535..ab44df5 100644 --- a/libpqpp/pq-bulkselectcommand.cpp +++ b/libpqpp/pq-bulkselectcommand.cpp @@ -16,7 +16,7 @@ PQ::BulkSelectCommand::execute() { if (!executed) { execRes = c->checkResult( - PQexecPrepared(c->conn, prepare(), values.size(), &values.front(), &lengths.front(), NULL, 0), + PQexecPrepared(c->conn, prepare(), values.size(), &values.front(), &lengths.front(), &formats.front(), 0), PGRES_TUPLES_OK); nTuples = PQntuples(execRes); tuple = -1; diff --git a/libpqpp/pq-column.cpp b/libpqpp/pq-column.cpp index bb09c45..92107e8 100644 --- a/libpqpp/pq-column.cpp +++ b/libpqpp/pq-column.cpp @@ -7,10 +7,18 @@ PQ::Column::Column(const SelectBase * s, unsigned int i) : DB::Column(PQfname(s->execRes, i), i), sc(s), - oid(PQftype(sc->execRes, colNo)) + oid(PQftype(sc->execRes, colNo)), + buf(nullptr) { } +PQ::Column::~Column() +{ + if (buf) { + PQfreemem(buf); + } +} + bool PQ::Column::isNull() const { @@ -67,6 +75,16 @@ PQ::Column::apply(DB::HandleField & h) const case 1184: //TIMESTAMPTZOID: h.timestamp(boost::posix_time::time_from_string(PQgetvalue(sc->execRes, sc->tuple, colNo))); break; + case 17: //BYTEAOID + { + if (buf) { + PQfreemem(buf); + } + size_t len; + buf = PQunescapeBytea((unsigned char *)PQgetvalue(sc->execRes, sc->tuple, colNo), &len); + h.blob(DB::Blob(buf, len)); + break; + } default: h.string(PQgetvalue(sc->execRes, sc->tuple, colNo), PQgetlength(sc->execRes, sc->tuple, colNo)); } diff --git a/libpqpp/pq-column.h b/libpqpp/pq-column.h index f01ee9a..08e66a6 100644 --- a/libpqpp/pq-column.h +++ b/libpqpp/pq-column.h @@ -9,6 +9,7 @@ namespace PQ { class Column : public DB::Column { public: Column(const SelectBase *, unsigned int field); + ~Column(); bool isNull() const override; void apply(DB::HandleField &) const override; @@ -16,6 +17,8 @@ namespace PQ { protected: const SelectBase * sc; const Oid oid; + // Buffer for PQunescapeBytea + mutable unsigned char * buf; }; } diff --git a/libpqpp/pq-command.cpp b/libpqpp/pq-command.cpp index f31d78a..5476393 100644 --- a/libpqpp/pq-command.cpp +++ b/libpqpp/pq-command.cpp @@ -23,7 +23,7 @@ PQ::Command::~Command() if (bufs[i]) { delete bufs[i]; } - else { + else if (formats[i] == 0) { free(values[i]); } } @@ -75,6 +75,7 @@ PQ::Command::paramsAtLeast(unsigned int n) if (values.size() <= n) { values.resize(n + 1, NULL); lengths.resize(n + 1, 0); + formats.resize(n + 1, 0); bufs.resize(n + 1, NULL); } else { @@ -82,10 +83,11 @@ PQ::Command::paramsAtLeast(unsigned int n) delete bufs[n]; bufs[n] = nullptr; } - else { + else if (formats[n] == 0) { free(values[n]); } values[n] = NULL; + formats[n] = 0; } } @@ -95,15 +97,12 @@ PQ::Command::paramSet(unsigned int n, const char * fmt, const T & ... v) { paramsAtLeast(n); lengths[n] = asprintf(&values[n], fmt, v...); - delete bufs[n]; - bufs[n] = nullptr; } void PQ::Command::paramSet(unsigned int n, const std::string & b) { paramsAtLeast(n); - delete bufs[n]; bufs[n] = new std::string(b); lengths[n] = b.length(); values[n] = const_cast(bufs[n]->data()); @@ -170,6 +169,16 @@ PQ::Command::bindParamT(unsigned int n, const boost::posix_time::ptime & v) paramSet(n, boost::posix_time::to_iso_extended_string(v)); } void +PQ::Command::bindParamBLOB(unsigned int n, const DB::Blob & v) +{ + paramsAtLeast(n); + lengths[n] = v.len; + formats[n] = 1; + values[n] = reinterpret_cast(const_cast(v.data)); + delete bufs[n]; + bufs[n] = nullptr; +} +void PQ::Command::bindNull(unsigned int n) { paramsAtLeast(n); diff --git a/libpqpp/pq-command.h b/libpqpp/pq-command.h index 7612262..989844c 100644 --- a/libpqpp/pq-command.h +++ b/libpqpp/pq-command.h @@ -43,6 +43,8 @@ namespace PQ { void bindParamT(unsigned int, const boost::posix_time::time_duration &) override; void bindParamT(unsigned int, const boost::posix_time::ptime &) override; + void bindParamBLOB(unsigned int, const DB::Blob &) override; + void bindNull(unsigned int) override; protected: void prepareSql(std::stringstream & psql, const std::string & sql) const; @@ -56,6 +58,7 @@ namespace PQ { void paramSet(unsigned int, const std::string &); std::vector values; std::vector lengths; + std::vector formats; std::vector bufs; }; } diff --git a/libpqpp/pq-cursorselectcommand.cpp b/libpqpp/pq-cursorselectcommand.cpp index d754680..54b843f 100644 --- a/libpqpp/pq-cursorselectcommand.cpp +++ b/libpqpp/pq-cursorselectcommand.cpp @@ -50,7 +50,7 @@ PQ::CursorSelectCommand::execute() s_declare = mkdeclare(); } c->checkResultFree( - PQexecParams(c->conn, s_declare.c_str(), values.size(), NULL, &values.front(), &lengths.front(), NULL, 0), + PQexecParams(c->conn, s_declare.c_str(), values.size(), NULL, &values.front(), &lengths.front(), &formats.front(), 0), PGRES_COMMAND_OK); fetchTuples(); createColumns(execRes); diff --git a/libpqpp/pq-modifycommand.cpp b/libpqpp/pq-modifycommand.cpp index 7629f00..b2cf626 100644 --- a/libpqpp/pq-modifycommand.cpp +++ b/libpqpp/pq-modifycommand.cpp @@ -17,7 +17,7 @@ PQ::ModifyCommand::~ModifyCommand() unsigned int PQ::ModifyCommand::execute(bool anc) { - PGresult * res = PQexecPrepared(c->conn, prepare(), values.size(), &values.front(), &lengths.front(), NULL, 0); + PGresult * res = PQexecPrepared(c->conn, prepare(), values.size(), &values.front(), &lengths.front(), &formats.front(), 0); c->checkResult(res, PGRES_COMMAND_OK, PGRES_TUPLES_OK); unsigned int rows = atoi(PQcmdTuples(res)); PQclear(res); diff --git a/libpqpp/unittests/pqschema.sql b/libpqpp/unittests/pqschema.sql index 2a7dd09..a79b678 100644 --- a/libpqpp/unittests/pqschema.sql +++ b/libpqpp/unittests/pqschema.sql @@ -38,4 +38,7 @@ CREATE TABLE bulktest( CREATE TABLE idtest( id serial, foo int); +CREATE TABLE blobtest( + data bytea, + md5 text); diff --git a/libpqpp/unittests/testpq.cpp b/libpqpp/unittests/testpq.cpp index 87992ca..97dc22d 100644 --- a/libpqpp/unittests/testpq.cpp +++ b/libpqpp/unittests/testpq.cpp @@ -383,6 +383,30 @@ BOOST_AUTO_TEST_CASE( closeOnError ) ro->commitTx(); } +BOOST_AUTO_TEST_CASE( blobs ) +{ + auto ro = DB::ConnectionPtr(DB::MockDatabase::openConnectionTo("PQmock")); + std::vector buf(29); + memcpy(&buf[0], "This is some binary text data", 29); + auto ins = ro->modify("INSERT INTO blobtest(data) VALUES(?)"); + ins->bindParamBLOB(0, buf); + ins->execute(); + + ro->execute("UPDATE blobtest SET md5 = md5(data)"); + + auto sel = ro->select("SELECT data, md5, length(data) FROM blobtest"); + for (const auto & r : sel->as()) { + // Assert the DB understood the insert + BOOST_REQUIRE_EQUAL(r.value<2>(), buf.size()); + BOOST_REQUIRE_EQUAL(r.value<1>(), "37c7c3737f93e8d17e845deff8fa74d2"); + // Assert the fetch of the data is correct + BOOST_REQUIRE_EQUAL(r.value<0>().len, buf.size()); + std::string str((const char *)r.value<0>().data, r.value<0>().len); + BOOST_REQUIRE_EQUAL(str, "This is some binary text data"); + BOOST_REQUIRE(memcmp(r.value<0>().data, &buf[0], 29) == 0); + } +} + BOOST_AUTO_TEST_SUITE_END(); BOOST_AUTO_TEST_CASE( connfail ) -- cgit v1.2.3