8#ifndef Sawyer_DatabasePostgresql_H
9#define Sawyer_DatabasePostgresql_H
11#if __cplusplus >= 201103L
13#include <Sawyer/Database.h>
15#include <boost/algorithm/string/predicate.hpp>
16#include <boost/format.hpp>
17#include <boost/lexical_cast.hpp>
25class Postgresql:
public Connection {
40 explicit Postgresql(
const Locator &c) {
44 Postgresql& open(
const Locator &c);
50class PostgresqlStatement;
56class PostgresqlConnection:
public ConnectionBase {
57 friend class ::Sawyer::Database::Postgresql;
58 friend class ::Sawyer::Database::Detail::PostgresqlStatement;
62 std::unique_ptr<pqxx::connection> connection;
63 std::unique_ptr<pqxx::work> transaction;
66 ~PostgresqlConnection() {
72 std::string uriEscape(
const std::string &s) {
75 if (::isalnum(ch) || ::strchr(
"-_.~", ch)) {
78 retval += (boost::format(
"%02X") % (unsigned)ch).str();
84 void open(
const Postgresql::Locator &where) {
88 std::string uri =
"postgresql://";
89 if (!where.user.empty() || !where.password.empty()) {
90 uri += uriEscape(where.user);
91 if (!where.password.empty())
92 uri +=
":" + uriEscape(where.password);
95 if (!where.hostname.empty())
96 uri += uriEscape(where.hostname);
97 if (!where.port.empty())
98 uri +=
":" + uriEscape(where.port);
99 if (!where.database.empty())
100 uri +=
"/" + uriEscape(where.database);
102 connection = std::unique_ptr<pqxx::connection>(
new pqxx::connection(uri));
103 transaction = std::unique_ptr<pqxx::work>(
new pqxx::work(*connection));
106 void close()
override {
107 if (connection && connection->is_open() && transaction)
108 transaction->commit();
113 std::string driverName()
const override {
117 Statement prepareStatement(
const std::string &sql)
override;
119 size_t lastInsert()
const override {
120 throw Exception(
"last inserted row ID not supported; suggestion: use UUIDs instead");
128class PostgresqlStatement:
public StatementBase {
129 friend class ::Sawyer::Database::Detail::PostgresqlConnection;
132 std::vector<std::string> pvalues_;
133 pqxx::result result_;
136 PostgresqlStatement(
const std::shared_ptr<ConnectionBase> &db,
const std::string &sql)
137 : StatementBase(db) {
138 auto low = parseParameters(sql);
140 pvalues_.resize(low.second,
"null");
143 void unbindAllParams()
override {
147 void bindLow(
size_t idx,
int value)
override {
148 pvalues_[idx] = boost::lexical_cast<std::string>(value);
151 void bindLow(
size_t idx, int64_t value)
override {
152 pvalues_[idx] = boost::lexical_cast<std::string>(value);
155 void bindLow(
size_t idx,
size_t value)
override {
156 pvalues_[idx] = boost::lexical_cast<std::string>(value);
159 void bindLow(
size_t idx,
double value)
override {
160 pvalues_[idx] = boost::lexical_cast<std::string>(value);
163 void bindLow(
size_t idx,
const std::string &value)
override {
167 auto tx = std::dynamic_pointer_cast<PostgresqlConnection>(connection())->transaction.get();
168 pvalues_[idx] =
"'" + tx->esc_raw(
reinterpret_cast<const unsigned char*
>(value.c_str()), value.size()) +
"'";
174 auto tx = std::dynamic_pointer_cast<PostgresqlConnection>(connection())->transaction.get();
175 pvalues_[idx] =
"'" + tx->esc(value) +
"'";
180 for (
size_t i = 0; i < value.size(); ++i) {
208 if (::isgraph(value[i]) ||
' ' == value[i]) {
211 s += (boost::format(
"\\%03o") % (unsigned)(
unsigned char)value[i]).str();
216 pvalues_[idx] = (s != value ?
"E'" :
"'") + s +
"'";
220 void bindLow(
size_t idx,
const char *value)
override {
221 auto tx = std::dynamic_pointer_cast<PostgresqlConnection>(connection())->transaction.get();
222 pvalues_[idx] =
"'" + tx->esc(value) +
"'";
225 void bindLow(
size_t idx,
const std::vector<uint8_t> &value)
override {
226 auto tx = std::dynamic_pointer_cast<PostgresqlConnection>(connection())->transaction.get();
227 auto data =
static_cast<const unsigned char*
>(value.data());
228 pvalues_[idx] =
"'" + tx->esc_raw(data, value.size()) +
"'";
231 void bindLow(
size_t idx, Nothing)
override {
232 pvalues_[idx] =
"null";
235 Iterator beginLow()
override {
239 bool inQuote =
false;
240 for (
size_t i=0; i<sql_.size(); ++i) {
241 if (
'\'' == sql_[i]) {
244 }
else if (
'?' == sql_[i] && !inQuote) {
245 ASSERT_require(paramIdx < pvalues_.size());
246 sql += pvalues_[paramIdx++];
252 auto tx = std::dynamic_pointer_cast<PostgresqlConnection>(connection())->transaction.get();
253 result_ = tx->exec(sql);
256 return makeIterator();
259 Iterator nextLow()
override {
260 if (rowNumber() >= result_.size()) {
261 state(Statement::FINISHED);
264 return makeIterator();
268 size_t nColumns()
const override {
269 return result_.columns();
273 ASSERT_require(rowNumber() < result_.size());
274 ASSERT_require(idx < result_.columns());
275 if (result_[rowNumber()][boost::numeric_cast<int>(idx)].is_null()) {
278 return unescapeRaw(result_[rowNumber()][boost::numeric_cast<int>(idx)].as<std::string>());
282 static unsigned hex2int(
char ch) {
285 if (ch >=
'a' && ch <=
'f')
286 return ch -
'a' + 10;
287 ASSERT_require(ch >=
'A' && ch <=
'F');
288 return ch -
'A' + 10;
292 static std::string unescapeRaw(
const std::string &s) {
293 if (boost::starts_with(s,
"\\x") && s.size() % 2 == 0) {
295 for (
size_t i=2; i<s.size(); i+=2)
296 retval +=
static_cast<char>(hex2int(s[i])*16 + hex2int(s[i+1]));
304 ASSERT_require(rowNumber() < result_.size());
305 ASSERT_require(idx < result_.columns());
306 if (result_[rowNumber()][boost::numeric_cast<int>(idx)].is_null()) {
309 std::string bytes = unescapeRaw(result_[rowNumber()][boost::numeric_cast<int>(idx)].as<std::string>());
310 auto data =
reinterpret_cast<const uint8_t*
>(bytes.c_str());
311 return std::vector<uint8_t>(data, data + bytes.size());
321PostgresqlConnection::prepareStatement(
const std::string &sql) {
322 auto detail = std::shared_ptr<PostgresqlStatement>(
new PostgresqlStatement(shared_from_this(), sql));
323 return makeStatement(detail);
333Postgresql::open(
const Postgresql::Locator &where) {
334 auto pimpl = std::shared_ptr<Detail::PostgresqlConnection>(
new Detail::PostgresqlConnection);
Holds a value or nothing.