#ifndef _SQL_POSTGRES_H_
|
#define _SQL_POSTGRES_H_
|
|
#pragma once
|
|
#include <string>
|
#include <map>
|
#include <vector>
|
#include <array>
|
#include <exception>
|
#include <sstream>
|
#include <chrono>
|
#include <algorithm>
|
#include <assert.h>
|
#include "qtl_common.hpp"
|
|
#define FRONTEND
|
|
#include <libpq-fe.h>
|
#include <libpq/libpq-fs.h>
|
#include <pgtypes_error.h>
|
#include <pgtypes_interval.h>
|
#include <pgtypes_timestamp.h>
|
#include <pgtypes_numeric.h>
|
#include <pgtypes_date.h>
|
|
extern "C"
|
{
|
#include <c.h>
|
#include <postgres.h>
|
#include <catalog/pg_type.h>
|
}
|
|
|
#ifdef open
|
#undef open
|
#endif //open
|
|
#ifdef vsnprintf
|
#undef vsnprintf
|
#endif
|
#ifdef snprintf
|
#undef snprintf
|
#endif
|
#ifdef sprintf
|
#undef sprintf
|
#endif
|
#ifdef vfprintf
|
#undef vfprintf
|
#endif
|
#ifdef fprintf
|
#undef fprintf
|
#endif
|
#ifdef printf
|
#undef printf
|
#endif
|
#ifdef rename
|
#undef rename
|
#endif
|
#ifdef unlink
|
#undef unlink
|
#endif
|
|
namespace qtl
|
{
|
namespace postgres
|
{
|
|
namespace detail
|
{
|
|
inline int16_t ntoh(int16_t v)
|
{
|
return static_cast<int16_t>(ntohs(v));
|
}
|
inline uint16_t ntoh(uint16_t v)
|
{
|
return ntohs(v);
|
}
|
inline int32_t ntoh(int32_t v)
|
{
|
return static_cast<int32_t>(ntohl(v));
|
}
|
inline uint32_t ntoh(uint32_t v)
|
{
|
return ntohl(v);
|
}
|
inline uint64_t ntoh(uint64_t v)
|
{
|
#ifdef _WIN32
|
return ntohll(v);
|
#else
|
return be64toh(v);
|
#endif
|
}
|
inline int64_t ntoh(int64_t v)
|
{
|
return ntoh(static_cast<uint64_t>(v));
|
}
|
|
template<typename T>
|
inline T& ntoh_inplace(typename std::enable_if<std::is_integral<T>::value && !std::is_const<T>::value, T>::type& v)
|
{
|
v = ntoh(v);
|
return v;
|
}
|
|
inline int16_t hton(int16_t v)
|
{
|
return static_cast<int16_t>(htons(v));
|
}
|
inline uint16_t hton(uint16_t v)
|
{
|
return htons(v);
|
}
|
inline int32_t hton(int32_t v)
|
{
|
return static_cast<int32_t>(htonl(v));
|
}
|
inline uint32_t hton(uint32_t v)
|
{
|
return htonl(v);
|
}
|
inline uint64_t hton(uint64_t v)
|
{
|
#ifdef _WIN32
|
return htonll(v);
|
#else
|
return htobe64(v);
|
#endif
|
}
|
inline int64_t hton(int64_t v)
|
{
|
return hton(static_cast<uint64_t>(v));
|
}
|
|
template<typename T>
|
inline T& hton_inplace(typename std::enable_if<std::is_integral<T>::value && !std::is_const<T>::value>::type& v)
|
{
|
v = hton(v);
|
return v;
|
}
|
|
}
|
|
class base_database;
|
class result;
|
|
class error : public std::exception
|
{
|
public:
|
error() : m_errmsg(nullptr) { }
|
explicit error(PGconn* conn, PGVerbosity verbosity = PQERRORS_DEFAULT, PGContextVisibility show_context = PQSHOW_CONTEXT_ERRORS)
|
{
|
PQsetErrorVerbosity(conn, verbosity);
|
PQsetErrorContextVisibility(conn, show_context);
|
const char* errmsg = PQerrorMessage(conn);
|
if (errmsg) m_errmsg = errmsg;
|
else m_errmsg.clear();
|
}
|
|
explicit error(PGresult* res)
|
{
|
const char* errmsg = PQresultErrorMessage(res);
|
if (errmsg) m_errmsg = errmsg;
|
else m_errmsg.clear();
|
}
|
|
virtual const char* what() const NOEXCEPT override { return m_errmsg.data(); }
|
|
private:
|
std::string m_errmsg;
|
};
|
|
inline void verify_pgtypes_error(int ret)
|
{
|
if(ret && errno != 0)
|
throw std::system_error(std::error_code(errno, std::generic_category()));
|
}
|
|
struct interval
|
{
|
::interval* value;
|
|
interval()
|
{
|
value = PGTYPESinterval_new();
|
}
|
explicit interval(char* str)
|
{
|
value = PGTYPESinterval_from_asc(str, nullptr);
|
}
|
interval(const interval& src) : interval()
|
{
|
verify_pgtypes_error(PGTYPESinterval_copy(src.value, value));
|
}
|
interval(interval&& src)
|
{
|
value = src.value;
|
src.value = PGTYPESinterval_new();
|
}
|
~interval()
|
{
|
PGTYPESinterval_free(value);
|
}
|
|
std::string to_string() const
|
{
|
return PGTYPESinterval_to_asc(value);
|
}
|
|
interval& operator=(const interval& src)
|
{
|
if(&src!=this)
|
verify_pgtypes_error(PGTYPESinterval_copy(src.value, value));
|
return *this;
|
}
|
};
|
|
struct timestamp
|
{
|
::timestamp value;
|
|
timestamp() = default;
|
|
static timestamp now()
|
{
|
timestamp result;
|
PGTYPEStimestamp_current(&result.value);
|
return result;
|
}
|
explicit timestamp(char* str)
|
{
|
value = PGTYPEStimestamp_from_asc(str, nullptr);
|
verify_pgtypes_error(1);
|
}
|
|
int format(char* str, int n, const char* format) const
|
{
|
timestamp temp = *this;
|
return PGTYPEStimestamp_fmt_asc(&temp.value, str, n, format);
|
}
|
static timestamp parse(char* str, const char* format)
|
{
|
timestamp result;
|
verify_pgtypes_error(PGTYPEStimestamp_defmt_asc(str, format, &result.value));
|
return result;
|
}
|
|
std::string to_string() const
|
{
|
char* str = PGTYPEStimestamp_to_asc(value);
|
std::string result = str;
|
PGTYPESchar_free(str);
|
return result;
|
}
|
|
timestamp& operator += (const interval& span)
|
{
|
verify_pgtypes_error(PGTYPEStimestamp_add_interval(&value, span.value, &value));
|
return *this;
|
}
|
|
timestamp& operator -= (const interval& span)
|
{
|
verify_pgtypes_error(PGTYPEStimestamp_sub_interval(&value, span.value, &value));
|
return *this;
|
}
|
};
|
|
inline timestamp operator+(const timestamp& a, const interval& b)
|
{
|
timestamp result=a;
|
return result+=b;
|
}
|
|
inline timestamp operator-(const timestamp& a, const interval& b)
|
{
|
timestamp result=a;
|
result -= b;
|
return result;
|
}
|
|
|
struct timestamptz
|
{
|
::TimestampTz value;
|
/*
|
timestamptz() = default;
|
explicit timestamptz(pg_time_t v)
|
{
|
value = (TimestampTz)v -
|
((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
|
value *= USECS_PER_SEC;
|
}
|
|
static timestamptz now()
|
{
|
timestamptz result;
|
auto tp = std::chrono::system_clock::now();
|
int sec = tp.time_since_epoch().count()*std::nano::num/std::nano::den;
|
int usec = tp.time_since_epoch().count()*std::nano::num % std::nano::den;
|
|
result.value = (TimestampTz)sec -
|
((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
|
result.value = (result.value * USECS_PER_SEC) + usec;
|
|
return result;
|
}
|
*/
|
};
|
|
struct date
|
{
|
::date value;
|
|
date() = default;
|
explicit date(timestamp dt)
|
{
|
value = PGTYPESdate_from_timestamp(dt.value);
|
}
|
explicit date(char* str)
|
{
|
value = PGTYPESdate_from_asc(str, nullptr);
|
verify_pgtypes_error(1);
|
}
|
explicit date(int year, int month, int day)
|
{
|
int mdy[3] = { month, day, year };
|
PGTYPESdate_mdyjul(mdy, &value);
|
}
|
|
std::string to_string() const
|
{
|
char* str = PGTYPESdate_to_asc(value);
|
std::string result = str;
|
PGTYPESchar_free(str);
|
return str;
|
}
|
|
static date now()
|
{
|
date result;
|
PGTYPESdate_today(&result.value);
|
return result;
|
}
|
|
static date parse(char* str, const char* format)
|
{
|
date result;
|
verify_pgtypes_error(PGTYPESdate_defmt_asc(&result.value, format, str));
|
return result;
|
}
|
|
std::string format(const char* format)
|
{
|
std::string result;
|
result.resize(128);
|
verify_pgtypes_error(PGTYPESdate_fmt_asc(value, format, const_cast<char*>(result.data())));
|
result.resize(strlen(result.data()));
|
return result;
|
}
|
|
std::tuple<int, int, int> get_date()
|
{
|
int mdy[3];
|
PGTYPESdate_julmdy(value, mdy);
|
return std::make_tuple(mdy[2], mdy[0], mdy[1]);
|
}
|
|
int dayofweek()
|
{
|
return PGTYPESdate_dayofweek(value);
|
}
|
};
|
|
struct decimal
|
{
|
::decimal value;
|
};
|
|
struct numeric
|
{
|
::numeric* value;
|
|
numeric()
|
{
|
value = PGTYPESnumeric_new();
|
}
|
numeric(int v) : numeric()
|
{
|
verify_pgtypes_error(PGTYPESnumeric_from_int(v, value));
|
}
|
numeric(long v) : numeric()
|
{
|
verify_pgtypes_error(PGTYPESnumeric_from_long(v, value));
|
}
|
numeric(double v) : numeric()
|
{
|
verify_pgtypes_error(PGTYPESnumeric_from_double(v, value));
|
}
|
numeric(const decimal& v) : numeric()
|
{
|
verify_pgtypes_error(PGTYPESnumeric_from_decimal(const_cast<::decimal*>(&v.value), value));
|
}
|
numeric(const numeric& src) : numeric()
|
{
|
verify_pgtypes_error(PGTYPESnumeric_copy(src.value, value));
|
}
|
explicit numeric(const char* str)
|
{
|
value = PGTYPESnumeric_from_asc(const_cast<char*>(str), nullptr);
|
}
|
~numeric()
|
{
|
PGTYPESnumeric_free(value);
|
}
|
|
operator double() const
|
{
|
double result;
|
verify_pgtypes_error(PGTYPESnumeric_to_double(value, &result));
|
return result;
|
}
|
|
operator int() const
|
{
|
int result;
|
verify_pgtypes_error(PGTYPESnumeric_to_int(value, &result));
|
return result;
|
}
|
|
operator long() const
|
{
|
long result;
|
verify_pgtypes_error(PGTYPESnumeric_to_long(value, &result));
|
return result;
|
}
|
|
operator decimal() const
|
{
|
decimal result;
|
verify_pgtypes_error(PGTYPESnumeric_to_decimal(value, &result.value));
|
return result;
|
}
|
|
int compare(const numeric& other) const
|
{
|
return PGTYPESnumeric_cmp(value, other.value);
|
}
|
|
inline numeric& operator+=(const numeric& b)
|
{
|
verify_pgtypes_error(PGTYPESnumeric_add(value, b.value, value));
|
return *this;
|
}
|
|
inline numeric& operator-=(const numeric& b)
|
{
|
verify_pgtypes_error(PGTYPESnumeric_sub(value, b.value, value));
|
return *this;
|
}
|
|
inline numeric& operator*=(const numeric& b)
|
{
|
verify_pgtypes_error(PGTYPESnumeric_mul(value, b.value, value));
|
return *this;
|
}
|
|
inline numeric& operator/=(const numeric& b)
|
{
|
verify_pgtypes_error(PGTYPESnumeric_div(value, b.value, value));
|
return *this;
|
}
|
|
std::string to_string(int dscale=-1) const
|
{
|
char* str = PGTYPESnumeric_to_asc(value, dscale);
|
std::string result = str;
|
PGTYPESchar_free(str);
|
return result;
|
}
|
};
|
|
inline numeric operator+(const numeric& a, const numeric& b)
|
{
|
numeric result;
|
verify_pgtypes_error(PGTYPESnumeric_add(a.value, b.value, result.value));
|
return result;
|
}
|
|
inline numeric operator-(const numeric& a, const numeric& b)
|
{
|
numeric result;
|
verify_pgtypes_error(PGTYPESnumeric_sub(a.value, b.value, result.value));
|
return result;
|
}
|
|
inline numeric operator*(const numeric& a, const numeric& b)
|
{
|
numeric result;
|
verify_pgtypes_error(PGTYPESnumeric_mul(a.value, b.value, result.value));
|
return result;
|
}
|
|
inline numeric operator/(const numeric& a, const numeric& b)
|
{
|
numeric result;
|
verify_pgtypes_error(PGTYPESnumeric_div(a.value, b.value, result.value));
|
return result;
|
}
|
|
inline bool operator==(const numeric& a, const numeric& b)
|
{
|
return a.compare(b) == 0;
|
}
|
|
inline bool operator<(const numeric& a, const numeric& b)
|
{
|
return a.compare(b) < 0;
|
}
|
|
inline bool operator>(const numeric& a, const numeric& b)
|
{
|
return a.compare(b) > 0;
|
}
|
|
inline bool operator<=(const numeric& a, const numeric& b)
|
{
|
return a.compare(b) <= 0;
|
}
|
|
inline bool operator>=(const numeric& a, const numeric& b)
|
{
|
return a.compare(b) >= 0;
|
}
|
|
inline bool operator!=(const numeric& a, const numeric& b)
|
{
|
return a.compare(b) != 0;
|
}
|
|
class large_object : public qtl::blobbuf
|
{
|
public:
|
large_object() : m_conn(nullptr), m_id(InvalidOid), m_fd(-1) { }
|
large_object(PGconn* conn, Oid loid, std::ios_base::openmode mode)
|
{
|
open(conn, loid, mode);
|
}
|
large_object(const large_object&) = delete;
|
large_object(large_object&& src)
|
{
|
swap(src);
|
src.m_conn = nullptr;
|
src.m_fd = -1;
|
}
|
~large_object()
|
{
|
close();
|
}
|
|
static large_object create(PGconn* conn, Oid loid = InvalidOid)
|
{
|
Oid oid = lo_create(conn, loid);
|
if (oid == InvalidOid)
|
throw error(conn);
|
return large_object(conn, oid, std::ios::in|std::ios::out|std::ios::binary);
|
}
|
static large_object load(PGconn* conn, const char* filename, Oid loid = InvalidOid)
|
{
|
Oid oid = lo_import_with_oid(conn, filename, loid);
|
if (oid == InvalidOid)
|
throw error(conn);
|
return large_object(conn, oid, std::ios::in | std::ios::out | std::ios::binary);
|
}
|
void save(const char* filename) const
|
{
|
if (lo_export(m_conn, m_id, filename) < 0)
|
throw error(m_conn);
|
}
|
|
void unlink()
|
{
|
close();
|
if (lo_unlink(m_conn, m_id) < 0)
|
throw error(m_conn);
|
}
|
|
large_object& operator=(const large_object&) = delete;
|
large_object& operator=(large_object&& src)
|
{
|
if (this != &src)
|
{
|
swap(src);
|
src.close();
|
}
|
return *this;
|
}
|
bool is_open() const { return m_fd >= 0; }
|
Oid oid() const { return m_id; }
|
|
void open(PGconn* conn, Oid loid, std::ios_base::openmode mode)
|
{
|
int lomode = 0;
|
if (mode&std::ios_base::in)
|
lomode |= INV_READ;
|
if (mode&std::ios_base::out)
|
lomode |= INV_WRITE;
|
m_conn = conn;
|
m_id = loid;
|
m_fd = lo_open(m_conn, loid, lomode);
|
if (m_fd < 0)
|
throw error(m_conn);
|
|
m_size = size();
|
init_buffer(mode);
|
if (mode&std::ios_base::trunc)
|
{
|
if (lo_truncate(m_conn, m_fd, 0) < 0)
|
throw error(m_conn);
|
}
|
}
|
|
void close()
|
{
|
if (m_fd >= 0)
|
{
|
overflow();
|
if (lo_close(m_conn, m_fd) < 0)
|
throw error(m_conn);
|
m_fd = -1;
|
}
|
}
|
|
void flush()
|
{
|
if (m_fd >= 0)
|
overflow();
|
}
|
|
size_t size() const
|
{
|
pg_int64 size = 0;
|
if (m_fd >= 0)
|
{
|
pg_int64 org = lo_tell64(m_conn, m_fd);
|
size = lo_lseek64(m_conn, m_fd, 0, SEEK_END);
|
lo_lseek64(m_conn, m_fd, org, SEEK_SET);
|
}
|
return size;
|
}
|
|
void resize(size_t n)
|
{
|
if (m_fd >= 0 && lo_truncate64(m_conn, m_fd, n) < 0)
|
throw error(m_conn);
|
}
|
|
void swap(large_object& other)
|
{
|
std::swap(m_conn, other.m_conn);
|
std::swap(m_id, other.m_id);
|
std::swap(m_fd, other.m_fd);
|
qtl::blobbuf::swap(other);
|
}
|
|
protected:
|
enum { default_buffer_size = 4096 };
|
|
virtual bool read_blob(char* buffer, off_type& count, pos_type position) override
|
{
|
return lo_lseek64(m_conn, m_fd, position, SEEK_SET) >= 0 && lo_read(m_conn, m_fd, buffer, count) > 0;
|
}
|
virtual void write_blob(const char* buffer, size_t count) override
|
{
|
if (lo_write(m_conn, m_fd, buffer, count) < 0)
|
throw error(m_conn);
|
}
|
|
private:
|
PGconn* m_conn;
|
Oid m_id;
|
int m_fd;
|
};
|
|
/*
|
template<typename T>
|
struct oid_traits
|
{
|
typedef T value_type;
|
static Oid type();
|
static const value_type& get(const char*);
|
static std::pair<const char*, size_t> data(const T& v);
|
};
|
*/
|
|
template<typename T, Oid id>
|
struct base_object_traits
|
{
|
typedef T value_type;
|
enum { type = id };
|
static bool is_match(Oid v)
|
{
|
return v == type;
|
}
|
};
|
|
template<typename T>
|
struct object_traits;
|
|
#define QTL_POSTGRES_DEFOID(T, oid) \
|
template<> struct object_traits<T> : public base_object_traits<T, oid> { \
|
static value_type get(const char* data, size_t n) { return *reinterpret_cast<const value_type*>(data); } \
|
static std::pair<const char*, size_t> data(const T& v, std::vector<char>& /*data*/) { \
|
return std::make_pair(reinterpret_cast<const char*>(&v), sizeof(T)); \
|
} \
|
};
|
|
QTL_POSTGRES_DEFOID(bool, BOOLOID)
|
QTL_POSTGRES_DEFOID(char, CHAROID)
|
QTL_POSTGRES_DEFOID(float, FLOAT4OID)
|
QTL_POSTGRES_DEFOID(double, FLOAT8OID)
|
|
template<typename T, Oid id>
|
struct integral_traits : public base_object_traits<T, id>
|
{
|
typedef typename base_object_traits<T, id>::value_type value_type;
|
static value_type get(const char* data, size_t n)
|
{
|
return detail::ntoh(*reinterpret_cast<const value_type*>(data));
|
}
|
static std::pair<const char*, size_t> data(value_type v, std::vector<char>& data)
|
{
|
data.resize(sizeof(value_type));
|
*reinterpret_cast<value_type*>(data.data()) = detail::hton(v);
|
return std::make_pair(data.data(), data.size());
|
}
|
};
|
|
template<> struct object_traits<int16_t> : public integral_traits<int16_t, INT2OID>
|
{
|
};
|
|
template<> struct object_traits<int32_t> : public integral_traits<int32_t, INT4OID>
|
{
|
};
|
|
template<> struct object_traits<int64_t> : public integral_traits<int64_t, INT8OID>
|
{
|
};
|
|
template<> struct object_traits<const char*> : public base_object_traits<const char*, TEXTOID>
|
{
|
static bool is_match(Oid v)
|
{
|
return v == TEXTOID || v == VARCHAROID || v == BPCHAROID;
|
}
|
static const char* get(const char* data, size_t n) { return data; }
|
static std::pair<const char*, size_t> data(const char* v, std::vector<char>& /*data*/)
|
{
|
return std::make_pair(v, strlen(v));
|
}
|
};
|
|
template<> struct object_traits<char*> : public object_traits<const char*>
|
{
|
};
|
|
template<> struct object_traits<std::string> : public base_object_traits<std::string, TEXTOID>
|
{
|
static bool is_match(Oid v)
|
{
|
return v == TEXTOID || v == VARCHAROID || v == BPCHAROID;
|
}
|
static value_type get(const char* data, size_t n) { return std::string(data, n); }
|
static std::pair<const char*, size_t> data(const std::string& v, std::vector<char>& /*data*/)
|
{
|
return std::make_pair(v.data(), v.size());
|
}
|
};
|
|
template<> struct object_traits<timestamp> : public base_object_traits<timestamp, TIMESTAMPOID>
|
{
|
static value_type get(const char* data, size_t n)
|
{
|
value_type result = *reinterpret_cast<const timestamp*>(data);
|
result.value = detail::ntoh(result.value);
|
return result;
|
}
|
static std::pair<const char*, size_t> data(const timestamp& v, std::vector<char>& data)
|
{
|
data.resize(sizeof(timestamp));
|
*reinterpret_cast<int64_t*>(data.data()) = detail::hton(v.value);
|
return std::make_pair(data.data(), data.size());
|
}
|
};
|
|
template<> struct object_traits<timestamptz> : public base_object_traits<timestamptz, TIMESTAMPTZOID>
|
{
|
static value_type get(const char* data, size_t n)
|
{
|
value_type result = *reinterpret_cast<const timestamptz*>(data);
|
result.value = detail::ntoh(result.value);
|
return result;
|
}
|
static std::pair<const char*, size_t> data(const timestamptz& v, std::vector<char>& data)
|
{
|
data.resize(sizeof(timestamptz));
|
*reinterpret_cast<int64_t*>(data.data()) = detail::hton(v.value);
|
return std::make_pair(data.data(), data.size());
|
}
|
};
|
|
template<> struct object_traits<interval> : public base_object_traits<interval, INTERVALOID>
|
{
|
static value_type get(const char* data, size_t n)
|
{
|
interval result;
|
const ::interval* value = reinterpret_cast<const ::interval*>(data);
|
result.value->time = detail::ntoh(value->time);
|
result.value->month = detail::ntoh(value->month);
|
return std::move(result);
|
}
|
static std::pair<const char*, size_t> data(const interval& v, std::vector<char>& data)
|
{
|
data.resize(sizeof(::interval));
|
::interval* value = reinterpret_cast<::interval*>(data.data());
|
value->time = detail::hton(v.value->time);
|
value->month = detail::hton(v.value->month);
|
return std::make_pair(data.data(), data.size());
|
}
|
};
|
|
template<> struct object_traits<date> : public base_object_traits<date, DATEOID>
|
{
|
static value_type get(const char* data, size_t n)
|
{
|
date result = *reinterpret_cast<const date*>(data);
|
result.value = detail::ntoh(result.value);
|
return result;
|
}
|
static std::pair<const char*, size_t> data(const date& v, std::vector<char>& data)
|
{
|
data.resize(sizeof(date));
|
reinterpret_cast<date*>(data.data())->value = detail::hton(v.value);
|
return std::make_pair(data.data(), data.size());
|
}
|
};
|
|
template<> struct object_traits<qtl::const_blob_data> : public base_object_traits<qtl::const_blob_data, BYTEAOID>
|
{
|
static value_type get(const char* data, size_t n)
|
{
|
return qtl::const_blob_data(data, n);
|
}
|
static std::pair<const char*, size_t> data(const qtl::const_blob_data& v, std::vector<char>& data)
|
{
|
assert(v.size <= UINT32_MAX);
|
return std::make_pair(static_cast<const char*>(v.data), v.size);
|
}
|
};
|
|
template<> struct object_traits<qtl::blob_data> : public base_object_traits<qtl::blob_data, BYTEAOID>
|
{
|
static void get(qtl::blob_data& value, const char* data, size_t n)
|
{
|
if (value.size < n)
|
throw std::out_of_range("no enough buffer to receive blob data.");
|
memcpy(value.data, data, n);
|
}
|
static std::pair<const char*, size_t> data(const qtl::blob_data& v, std::vector<char>& data)
|
{
|
assert(v.size <= UINT32_MAX);
|
return std::make_pair(static_cast<char*>(v.data), v.size);
|
}
|
};
|
|
template<> struct object_traits<std::vector<uint8_t>> : public base_object_traits<std::vector<uint8_t>, BYTEAOID>
|
{
|
static value_type get(const char* data, size_t n)
|
{
|
const uint8_t* begin = reinterpret_cast<const uint8_t*>(data);
|
return std::vector<uint8_t>(begin, begin+n);
|
}
|
static std::pair<const char*, size_t> data(const std::vector<uint8_t>& v, std::vector<char>& data)
|
{
|
assert(v.size() <= UINT32_MAX);
|
return std::make_pair(reinterpret_cast<const char*>(v.data()), v.size());
|
}
|
};
|
|
template<> struct object_traits<large_object> : public base_object_traits<large_object, OIDOID>
|
{
|
static value_type get(PGconn* conn, const char* data, size_t n)
|
{
|
Oid oid = object_traits<int32_t>::get(data, n);
|
return large_object(conn, oid, std::ios::in | std::ios::out | std::ios::binary);
|
}
|
static std::pair<const char*, size_t> data(const large_object& v, std::vector<char>& data)
|
{
|
return object_traits<int32_t>::data(v.oid(), data);
|
}
|
};
|
|
struct binder
|
{
|
binder() = default;
|
template<typename T>
|
explicit binder(const T& v)
|
{
|
m_type = object_traits<T>::value();
|
auto pair = object_traits<T>::data(v);
|
m_value = pair.first;
|
m_length = pair.second;
|
}
|
binder(const char* data, size_t n, Oid oid)
|
{
|
m_type = oid;
|
m_value = data;
|
m_length = n;
|
}
|
|
Oid constexpr type() const { return m_type; }
|
size_t length() const { return m_length; }
|
const char* value() const { return m_value; }
|
|
template<typename T>
|
T get()
|
{
|
if (!object_traits<T>::is_match(m_type))
|
throw std::bad_cast();
|
|
return object_traits<T>::get(m_value, m_length);
|
}
|
template<typename T>
|
T get(PGconn* conn)
|
{
|
if (!object_traits<T>::is_match(m_type))
|
throw std::bad_cast();
|
|
return object_traits<T>::get(conn, m_value, m_length);
|
}
|
template<typename T>
|
void get(T& v)
|
{
|
if (!object_traits<T>::is_match(m_type))
|
throw std::bad_cast();
|
|
return object_traits<T>::get(v, m_value, m_length);
|
}
|
|
void bind(std::nullptr_t)
|
{
|
m_value = nullptr;
|
m_length = 0;
|
}
|
void bind(qtl::null)
|
{
|
bind(nullptr);
|
}
|
|
template<typename T>
|
void bind(const T& v)
|
{
|
typedef typename std::decay<T>::type param_type;
|
if (m_type!=0 && !object_traits<param_type>::is_match(m_type))
|
throw std::bad_cast();
|
|
auto pair = object_traits<param_type>::data(v, m_data);
|
m_value = pair.first;
|
m_length = pair.second;
|
}
|
void bind(const char* data, size_t length)
|
{
|
m_value = data;
|
m_length = length;
|
}
|
|
private:
|
Oid m_type;
|
const char* m_value;
|
size_t m_length;
|
std::vector<char> m_data;
|
};
|
|
template<size_t N, size_t I, typename Arg, typename... Other>
|
inline void make_binder_list_helper(std::array<binder, N>& binders, Arg&& arg, Other&&... other)
|
{
|
binders[I]=binder(arg);
|
make_binder_list_helper<N, I+1>(binders, std::forward<Other>(other)...);
|
}
|
|
template<typename... Args>
|
inline std::array<binder, sizeof...(Args)> make_binder_list(Args&&... args)
|
{
|
std::array<binder, sizeof...(Args)> binders;
|
binders.reserve(sizeof...(Args));
|
make_binder_list_helper<sizeof...(Args), 0>(binders, std::forward<Args>(args)...);
|
return binders;
|
}
|
|
template<typename T>
|
inline bool in_impl(const T& from, const T& to)
|
{
|
return std::equal_to<T>()(from, to);
|
}
|
|
template<typename T, typename... Ts >
|
inline bool in_impl(const T& from, const T& to, const Ts&... other)
|
{
|
return std::equal_to<T>()(from, to) || in_impl(from, other...);
|
}
|
|
template<typename T, T... values>
|
inline bool in(const T& v)
|
{
|
return in_impl(v, values...);
|
}
|
|
class result
|
{
|
public:
|
result(PGresult* res) : m_res(res) { }
|
result(const result&) = delete;
|
result(result&& src)
|
{
|
m_res = src.m_res;
|
src.m_res = nullptr;
|
}
|
|
result& operator=(const result&) = delete;
|
result& operator=(result&& src)
|
{
|
if (this != &src)
|
{
|
clear();
|
m_res = src.m_res;
|
src.m_res = nullptr;
|
}
|
return *this;
|
}
|
~result()
|
{
|
clear();
|
}
|
|
PGresult* handle() const { return m_res; }
|
operator bool() const { return m_res != nullptr; }
|
|
ExecStatusType status() const
|
{
|
return PQresultStatus(m_res);
|
}
|
|
long long affected_rows() const
|
{
|
char* result = PQcmdTuples(m_res);
|
if (result)
|
return strtoll(result, nullptr, 10);
|
else
|
return 0LL;
|
}
|
|
unsigned int get_column_count() const { return PQnfields(m_res); }
|
|
int get_param_count() const
|
{
|
return PQnparams(m_res);
|
}
|
|
Oid get_param_type(int col) const
|
{
|
return PQparamtype(m_res, col);
|
}
|
|
const char* get_column_name(int col) const
|
{
|
return PQfname(m_res, col);
|
}
|
int get_column_index(const char* name) const
|
{
|
return PQfnumber(m_res, name);
|
}
|
int get_column_length(int col) const
|
{
|
return PQfsize(m_res, col);
|
}
|
Oid get_column_type(int col) const
|
{
|
return PQftype(m_res, col);
|
}
|
|
const char* get_value(int row, int col) const
|
{
|
return PQgetvalue(m_res, row, col);
|
}
|
|
bool is_null(int row, int col) const
|
{
|
return PQgetisnull(m_res, row, col);
|
}
|
|
int length(int row, int col) const
|
{
|
return PQgetlength(m_res, row, col);
|
}
|
|
Oid insert_oid() const
|
{
|
return PQoidValue(m_res);
|
}
|
|
template<ExecStatusType... Excepted>
|
void verify_error()
|
{
|
if (m_res)
|
{
|
ExecStatusType got = status();
|
if (! in<ExecStatusType, Excepted...>(got))
|
throw error(m_res);
|
}
|
}
|
|
void clear()
|
{
|
if (m_res)
|
{
|
PQclear(m_res);
|
m_res = nullptr;
|
}
|
}
|
|
private:
|
PGresult* m_res;
|
};
|
|
class base_statement
|
{
|
friend class error;
|
public:
|
explicit base_statement(base_database& db);
|
~base_statement()
|
{
|
}
|
base_statement(const base_statement&) = delete;
|
base_statement(base_statement&& src)
|
: m_conn(src.m_conn), m_binders(std::move(src.m_binders)), m_res(std::move(src.m_res))
|
{
|
}
|
|
result& get_result() { return m_res; }
|
|
void close()
|
{
|
m_res=nullptr;
|
}
|
|
uint64_t affetced_rows() const
|
{
|
return m_res.affected_rows();
|
}
|
|
void bind_param(size_t index, const char* param, size_t length)
|
{
|
m_binders[index].bind(param, length);
|
}
|
template<class Param>
|
void bind_param(size_t index, const Param& param)
|
{
|
m_binders[index].bind(param);
|
}
|
|
template<class Type>
|
void bind_field(size_t index, Type&& value)
|
{
|
value = m_binders[index].get<typename std::remove_const<Type>::type>();
|
}
|
|
void bind_field(size_t index, char* value, size_t length)
|
{
|
memcpy(value, m_binders[index].value(), std::min<size_t>(length, m_binders[index].length()));
|
}
|
|
template<size_t N>
|
void bind_field(size_t index, std::array<char, N>&& value)
|
{
|
bind_field(index, value.data(), value.size());
|
}
|
|
template<typename T>
|
void bind_field(size_t index, bind_string_helper<T>&& value)
|
{
|
value.assign(m_binders[index].value(), m_binders[index].length());
|
}
|
|
template<typename Type>
|
void bind_field(size_t index, indicator<Type>&& value)
|
{
|
if (m_res)
|
{
|
qtl::bind_field(*this, index, value.data);
|
value.is_null = m_res.is_null(0, static_cast<int>(index));
|
value.length = m_res.length(0, static_cast<int>(index));
|
value.is_truncated = m_binders[index].length() < value.length;
|
}
|
}
|
|
void bind_field(size_t index, large_object&& value)
|
{
|
value = m_binders[index].get<large_object>(m_conn);
|
}
|
void bind_field(size_t index, blob_data&& value)
|
{
|
m_binders[index].get(value);
|
}
|
|
protected:
|
PGconn* m_conn;
|
result m_res;
|
std::vector<binder> m_binders;
|
|
template<ExecStatusType... Excepted>
|
void verify_error()
|
{
|
if (m_res)
|
m_res.verify_error<Excepted...>();
|
else
|
throw error(m_conn);
|
}
|
};
|
|
class statement : public base_statement
|
{
|
public:
|
explicit statement(base_database& db) : base_statement(db)
|
{
|
}
|
statement(const statement&) = delete;
|
statement(statement&& src) : base_statement(std::move(src)), _name(std::move(src._name))
|
{
|
}
|
|
~statement()
|
{
|
finish(m_res);
|
|
if (!_name.empty())
|
{
|
std::ostringstream oss;
|
oss << "DEALLOCATE " << _name << ";";
|
result res = PQexec(m_conn, oss.str().data());
|
error e(res.handle());
|
}
|
}
|
|
void open(const char* command, int nParams=0, const Oid *paramTypes=nullptr)
|
{
|
_name.resize(sizeof(intptr_t) * 2+1);
|
int n = sprintf(const_cast<char*>(_name.data()), "q%p", this);
|
_name.resize(n);
|
std::transform(_name.begin(), _name.end(), _name.begin(), tolower);
|
result res = PQprepare(m_conn, _name.data(), command, nParams, paramTypes);
|
res.verify_error<PGRES_COMMAND_OK>();
|
}
|
template<typename... Types>
|
void open(const char* command)
|
{
|
auto binder_list = make_binder_list(Types()...);
|
std::array<Oid, sizeof...(Types)> types;
|
std::transform(binder_list.begin(), binder_list.end(), types.begin(), [](const binder& b) {
|
return b.type();
|
});
|
|
open(command, types.size(), types.data());
|
}
|
|
void attach(const char* name)
|
{
|
result res = PQdescribePrepared(m_conn, name);
|
res.verify_error<PGRES_COMMAND_OK>();
|
_name = name;
|
}
|
|
void execute()
|
{
|
if(!PQsendQueryPrepared(m_conn, _name.data(), 0, nullptr, nullptr, nullptr, 1))
|
throw error(m_conn);
|
if (!PQsetSingleRowMode(m_conn))
|
throw error(m_conn);
|
m_res = PQgetResult(m_conn);
|
verify_error<PGRES_COMMAND_OK, PGRES_SINGLE_TUPLE>();
|
}
|
|
template<typename Types>
|
void execute(const Types& params)
|
{
|
const size_t count = qtl::params_binder<statement, Types>::size;
|
if (count > 0)
|
{
|
m_binders.resize(count);
|
qtl::bind_params(*this, params);
|
|
std::array<const char*, count> values;
|
std::array<int, count> lengths;
|
std::array<int, count> formats;
|
for (size_t i = 0; i != m_binders.size(); i++)
|
{
|
values[i] = m_binders[i].value();
|
lengths[i] = static_cast<int>(m_binders[i].length());
|
formats[i] = 1;
|
}
|
if (!PQsendQueryPrepared(m_conn, _name.data(), static_cast<int>(m_binders.size()), values.data(), lengths.data(), formats.data(), 1))
|
throw error(m_conn);
|
}
|
else
|
{
|
if (!PQsendQueryPrepared(m_conn, _name.data(), 0, nullptr, nullptr, nullptr, 1))
|
throw error(m_conn);
|
}
|
if (!PQsetSingleRowMode(m_conn))
|
throw error(m_conn);
|
m_res = PQgetResult(m_conn);
|
verify_error<PGRES_COMMAND_OK, PGRES_SINGLE_TUPLE, PGRES_TUPLES_OK>();
|
}
|
|
template<typename Types>
|
bool fetch(Types&& values)
|
{
|
if (m_res)
|
{
|
ExecStatusType status = m_res.status();
|
if (status == PGRES_SINGLE_TUPLE)
|
{
|
int count = m_res.get_column_count();
|
if (count > 0)
|
{
|
m_binders.resize(count);
|
for (int i = 0; i != count; i++)
|
{
|
m_binders[i]=binder(m_res.get_value(0, i), m_res.length(0, i),
|
m_res.get_column_type(i));
|
}
|
qtl::bind_record(*this, std::forward<Types>(values));
|
}
|
m_res = PQgetResult(m_conn);
|
return true;
|
}
|
else
|
{
|
verify_error<PGRES_TUPLES_OK>();
|
}
|
}
|
return false;
|
}
|
|
bool next_result()
|
{
|
m_res = PQgetResult(m_conn);
|
return m_res && m_res.status() == PGRES_SINGLE_TUPLE;
|
}
|
|
void reset()
|
{
|
finish(m_res);
|
m_res.clear();
|
}
|
|
private:
|
std::string _name;
|
|
void finish(result& res)
|
{
|
while (res)
|
{
|
res = PQgetResult(m_conn);
|
}
|
}
|
};
|
|
class base_database
|
{
|
protected:
|
base_database()
|
{
|
m_conn = nullptr;
|
}
|
|
public:
|
base_database(const base_database&) = delete;
|
base_database(base_database&& src)
|
{
|
m_conn = src.m_conn;
|
src.m_conn = nullptr;
|
}
|
|
~base_database()
|
{
|
if (m_conn)
|
PQfinish(m_conn);
|
}
|
|
base_database& operator==(const base_database&) = delete;
|
base_database& operator==(base_database&& src)
|
{
|
if (this != &src)
|
{
|
if (m_conn)
|
PQfinish(m_conn);
|
m_conn = src.m_conn;
|
src.m_conn = nullptr;
|
}
|
return *this;
|
}
|
|
const char* errmsg() const
|
{
|
return PQerrorMessage(m_conn);
|
}
|
|
PGconn* handle() { return m_conn; }
|
|
const char* encoding() const
|
{
|
int encoding = PQclientEncoding(m_conn);
|
return (encoding >= 0) ? pg_encoding_to_char(encoding) : nullptr;
|
}
|
|
void trace(FILE* stream)
|
{
|
PQtrace(m_conn, stream);
|
}
|
void untrace()
|
{
|
PQuntrace(m_conn);
|
}
|
|
const char* current() const
|
{
|
return PQdb(m_conn);
|
}
|
|
const char* user() const
|
{
|
return PQuser(m_conn);
|
}
|
|
const char* host() const
|
{
|
return PQhost(m_conn);
|
}
|
|
const char* password() const
|
{
|
return PQpass(m_conn);
|
}
|
|
const char* port() const
|
{
|
return PQport(m_conn);
|
}
|
|
const char* options() const
|
{
|
return PQoptions(m_conn);
|
}
|
|
ConnStatusType status() const
|
{
|
return PQstatus(m_conn);
|
}
|
|
PGTransactionStatusType transactionStatus() const
|
{
|
return PQtransactionStatus(m_conn);
|
}
|
|
const char* parameterStatus(const char *paramName) const
|
{
|
return PQparameterStatus(m_conn, paramName);
|
}
|
|
void reset()
|
{
|
if(status() == CONNECTION_BAD)
|
PQreset(m_conn);
|
}
|
|
bool is_alive()
|
{
|
}
|
|
PGPing ping()
|
{
|
}
|
|
protected:
|
PGconn* m_conn;
|
void throw_exception() { throw postgres::error(m_conn); }
|
};
|
|
class simple_statment : public base_statement
|
{
|
public:
|
simple_statment(base_database& db, qtl::postgres::result&& res) : base_statement(db)
|
{
|
m_res = std::move(res);
|
}
|
|
template<typename ValueProc>
|
void fetch_all(ValueProc& proc)
|
{
|
int row_count = PQntuples(m_res.handle());
|
if (row_count > 0)
|
{
|
int col_count = m_res.get_column_count();
|
m_binders.resize(col_count);
|
auto values = qtl::detail::make_values(proc);
|
for (int i = 0; i != row_count; i++)
|
{
|
for (int j = 0; j != col_count; j++)
|
{
|
m_binders[j] = binder(m_res.get_value(i, j), m_res.length(i, j),
|
m_res.get_column_type(j));
|
}
|
qtl::bind_record(*this, std::forward<decltype(values)>(values));
|
proc(values);
|
}
|
}
|
}
|
};
|
|
class database : public base_database, public qtl::base_database<database, statement>
|
{
|
public:
|
database() = default;
|
|
bool open(const std::map<std::string, std::string>& params, bool expand_dbname = false)
|
{
|
std::vector<const char*> keywords(params.size()+1);
|
std::vector<const char*> values(params.size()+1);
|
for (auto& param : params)
|
{
|
keywords.push_back(param.first.data());
|
values.push_back(param.second.data());
|
}
|
keywords.push_back(nullptr);
|
values.push_back(nullptr);
|
m_conn = PQconnectdbParams(keywords.data(), values.data(), expand_dbname);
|
return m_conn != nullptr && status()== CONNECTION_OK;
|
}
|
|
bool open(const char * conninfo)
|
{
|
m_conn = PQconnectdb(conninfo);
|
return m_conn != nullptr && status() == CONNECTION_OK;
|
}
|
|
bool open(const char* host, const char* user, const char* password,
|
unsigned short port = 5432, const char* db = "postgres", const char* options = nullptr)
|
{
|
char port_text[16];
|
sprintf(port_text, "%u", port);
|
m_conn = PQsetdbLogin(host, port_text, options, nullptr, db, user, password);
|
return m_conn != nullptr && status() == CONNECTION_OK;
|
}
|
|
void close()
|
{
|
PQfinish(m_conn);
|
m_conn = nullptr;
|
}
|
|
statement open_command(const char* query_text, size_t /*text_length*/)
|
{
|
statement stmt(*this);
|
stmt.open(query_text);
|
return stmt;
|
}
|
statement open_command(const char* query_text)
|
{
|
return open_command(query_text, 0);
|
}
|
statement open_command(const std::string& query_text)
|
{
|
return open_command(query_text.data());
|
}
|
|
void simple_execute(const char* query_text, uint64_t* paffected = nullptr)
|
{
|
qtl::postgres::result res(PQexec(m_conn, query_text));
|
if (!res) throw_exception();
|
res.verify_error<PGRES_COMMAND_OK, PGRES_TUPLES_OK>();
|
if (paffected) *paffected = res.affected_rows();
|
}
|
template<typename ValueProc>
|
void simple_query(const char* query_text, ValueProc&& proc)
|
{
|
qtl::postgres::result res(PQexec(m_conn, query_text));
|
if (!res) throw_exception();
|
res.verify_error<PGRES_COMMAND_OK, PGRES_TUPLES_OK>();
|
if (res.status() == PGRES_TUPLES_OK)
|
{
|
simple_statment stmt(*this, std::move(res));
|
stmt.fetch_all(std::forward<ValueProc>(proc));
|
}
|
}
|
|
void auto_commit(bool on)
|
{
|
if(on)
|
simple_execute("SET AUTOCOMMIT TO ON");
|
else
|
simple_execute("SET AUTOCOMMIT TO OFF");
|
}
|
|
void begin_transaction()
|
{
|
simple_execute("BEGIN");
|
}
|
void rollback()
|
{
|
simple_execute("ROLLBACK");
|
}
|
void commit()
|
{
|
simple_execute("COMMIT");
|
}
|
|
};
|
|
typedef qtl::transaction<database> transaction;
|
|
template<typename Record>
|
using query_iterator = qtl::query_iterator<statement, Record>;
|
|
template<typename Record>
|
using query_result = qtl::query_result<statement, Record>;
|
|
inline base_statement::base_statement(base_database& db) : m_res(nullptr)
|
{
|
m_conn = db.handle();
|
m_res = nullptr;
|
}
|
|
}
|
|
}
|
|
|
#endif //_SQL_POSTGRES_H_
|