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.