From 150e2d1bed201ba1466cde47b21014b0811a22a3 Mon Sep 17 00:00:00 2001
From: znone <glyc@sina.com.cn>
Date: Tue, 02 Feb 2021 12:35:52 +0000
Subject: [PATCH] Add support for PostgreSQL

---
 test/TestPostgres.cpp                      |  230 ++++++
 include/qtl_common.hpp                     |   10 
 include/apply_tuple.h                      |   10 
 test/TestPostgres.h                        |   35 +
 include/qtl_postgres.hpp                   | 1420 +++++++++++++++++++++++++++++++++++++++++++
 test/test_postgres.mak                     |   25 
 include/qtl_sqlite.hpp                     |    4 
 test/test_mariadb.mak                      |    4 
 test/test_mysql.mak                        |    4 
 include/qtl_mysql.hpp                      |    2 
 test/TestSuite.h                           |    1 
 test/vs/test.sln                           |    6 
 test/vs/test_postgres/test_postgres.vcproj |  215 ++++++
 13 files changed, 1,956 insertions(+), 10 deletions(-)

diff --git a/include/apply_tuple.h b/include/apply_tuple.h
index 3e37cd9..b5a87c1 100644
--- a/include/apply_tuple.h
+++ b/include/apply_tuple.h
@@ -6,6 +6,12 @@
 #include <type_traits>
 #include <utility>
 
+namespace qtl
+{
+
+namespace detail
+{
+
 #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
 
 template <class F, class Tuple>
@@ -58,4 +64,8 @@
 
 #endif // C++17
 
+}
+
+}
+
 #endif //_APPLY_TUPLE_H_
diff --git a/include/qtl_common.hpp b/include/qtl_common.hpp
index 34436c2..4b7996c 100644
--- a/include/qtl_common.hpp
+++ b/include/qtl_common.hpp
@@ -19,6 +19,7 @@
 #include <string>
 #include <vector>
 #include <functional>
+#include <algorithm>
 #include "apply_tuple.h"
 
 #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
@@ -367,7 +368,7 @@
 		typedef bool result_type;
 		result_type operator()(F&& f, arg_type&& v)
 		{
-			apply_tuple(std::forward<F>(f), std::forward<arg_type>(v));
+			qtl::detail::apply_tuple(std::forward<F>(f), std::forward<arg_type>(v));
 			return true;
 		}
 	};
@@ -377,7 +378,7 @@
 		typedef Ret result_type;
 		result_type operator()(F&& f, arg_type&& v)
 		{
-			return apply_tuple(std::forward<F>(f), std::forward<arg_type>(v));
+			return qtl::detail::apply_tuple(std::forward<F>(f), std::forward<arg_type>(v));
 		}
 	};
 
@@ -593,7 +594,7 @@
 	typedef decltype(values) values_type;
 	while(command.fetch(std::forward<values_type>(values)))
 	{
-		if(!detail::apply(std::forward<ValueProc>(proc), std::forward<values_type>(values))) 
+		if(!apply(std::forward<ValueProc>(proc), std::forward<values_type>(values)))
 			break;
 	}
 }
@@ -613,6 +614,7 @@
 template<typename Command, typename T>
 struct params_binder
 {
+	enum { size = 1 };
 	inline void operator()(Command& command, const T& param) const
 	{
 		qtl::bind_param(command, 0, param);
@@ -622,6 +624,7 @@
 template<typename Command, typename... Types>
 struct params_binder<Command, std::tuple<Types...>>
 {
+	enum { size = sizeof...(Types) };
 	void operator()(Command& command, const std::tuple<Types...>& params) const
 	{
 		(detail::bind_helper<Command, std::tuple_size<std::tuple<Types...>>::value, Types...>(command))(params);
@@ -631,6 +634,7 @@
 template<typename Command, typename Type1, typename Type2>
 struct params_binder<Command, std::pair<Type1, Type2>>
 {
+	enum { size = 2 };
 	void operator()(Command& command, std::pair<Type1, Type2>&& values) const
 	{
 		qtl::bind_param(command, 0, std::forward<Type1>(values.first));
diff --git a/include/qtl_mysql.hpp b/include/qtl_mysql.hpp
index 179df88..1bcd2a1 100644
--- a/include/qtl_mysql.hpp
+++ b/include/qtl_mysql.hpp
@@ -214,7 +214,7 @@
 	virtual ~error() throw() { }
 	int code() const throw() { return m_error; }
 	operator bool() const { return m_error != 0;  }
-	virtual const char* what() const throw() override { return m_errmsg.data(); }
+	virtual const char* what() const NOEXCEPT override { return m_errmsg.data(); }
 private:
 	unsigned int m_error;
 	std::string m_errmsg;
diff --git a/include/qtl_postgres.hpp b/include/qtl_postgres.hpp
new file mode 100644
index 0000000..eee5959
--- /dev/null
+++ b/include/qtl_postgres.hpp
@@ -0,0 +1,1420 @@
+#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 "qtl_common.hpp"
+
+#define FRONTEND
+
+#include <libpq-fe.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
+
+namespace qtl
+{
+namespace postgres
+{
+
+namespace detail
+{
+
+	inline int16_t ntoh(int16_t v)
+	{
+		return ntohs(v);
+	}
+	inline uint16_t ntoh(uint16_t v)
+	{
+		return ntohs(v);
+	}
+	inline int32_t ntoh(int32_t v)
+	{
+		return 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 htons(v);
+	}
+	inline uint16_t hton(uint16_t v)
+	{
+		return htons(v);
+	}
+	inline int32_t hton(int32_t v)
+	{
+		return 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;
+}
+
+/*
+	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());
+	}
+};
+
+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);
+	}
+
+	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;
+		}
+	}
+
+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>();
+	}
+
+	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_
+
diff --git a/include/qtl_sqlite.hpp b/include/qtl_sqlite.hpp
index 99147ad..b5a4b44 100644
--- a/include/qtl_sqlite.hpp
+++ b/include/qtl_sqlite.hpp
@@ -411,13 +411,13 @@
 		return count>0;;
 	}
 
-	int affetced_rows()
+	int affetced_rows() const
 	{
 		sqlite3* db=sqlite3_db_handle(m_stmt);
 		return db ? sqlite3_changes(db) : 0;
 	}
 
-	int64_t insert_id()
+	int64_t insert_id() const
 	{
 		sqlite3* db=sqlite3_db_handle(m_stmt);
 		return db ? sqlite3_last_insert_rowid(db) : 0;
diff --git a/test/TestPostgres.cpp b/test/TestPostgres.cpp
new file mode 100644
index 0000000..983b084
--- /dev/null
+++ b/test/TestPostgres.cpp
@@ -0,0 +1,230 @@
+#include "stdafx.h"
+#include "TestPostgres.h"
+#include <iostream>
+#include <fstream>
+#include <array>
+#include <iomanip>
+#include "md5.h"
+#include "../include/qtl_postgres.hpp"
+
+using namespace std;
+
+struct TestpostgresRecord
+{
+	int32_t id;
+	char name[33];
+	qtl::postgres::timestamp create_time;
+
+	TestpostgresRecord()
+	{
+		memset(this, 0, sizeof(TestpostgresRecord));
+	}
+
+	void print() const
+	{
+		printf("ID=\"%d\", Name=\"%s\"\n",
+			id, name);
+	}
+};
+
+namespace qtl
+{
+	template<>
+	inline void bind_record<qtl::postgres::statement, TestpostgresRecord>(qtl::postgres::statement& command, TestpostgresRecord&& v)
+	{
+		qtl::bind_fields(command, v.id, v.name, v.create_time);
+	}
+}
+
+TestPostgres::TestPostgres()
+{
+	this->id = 0;
+	TEST_ADD(TestPostgres::test_dual)
+		TEST_ADD(TestPostgres::test_clear)
+		TEST_ADD(TestPostgres::test_insert)
+		TEST_ADD(TestPostgres::test_select)
+		TEST_ADD(TestPostgres::test_update)
+		TEST_ADD(TestPostgres::test_insert2)
+		TEST_ADD(TestPostgres::test_iterator)
+		TEST_ADD(TestPostgres::test_any)
+}
+
+inline void TestPostgres::connect(qtl::postgres::database& db)
+{
+	TEST_ASSERT_MSG(db.open("localhost", "postgres", "111111", 5432U, "test") == true, "Cannot connect to database");
+}
+
+void TestPostgres::test_dual()
+{
+	qtl::postgres::database db;
+	connect(db);
+
+	try
+	{
+		db.query("select 0, 'hello world';",
+			[](int32_t i, const std::string& str) {
+			printf("0=\"%d\", 'hello world'=\"%s\"\n",
+				i, str.data());
+		});
+	}
+	catch (qtl::postgres::error& e)
+	{
+		ASSERT_EXCEPTION(e);
+	}
+}
+
+void TestPostgres::test_select()
+{
+	qtl::postgres::database db;
+	connect(db);
+
+	try
+	{
+		db.query("select * from test where id=$1", 0, id,
+			[](const qtl::indicator<int32_t>& id, const std::string& name, const qtl::postgres::timestamp& create_time) {
+			printf("ID=\"%d\", Name=\"%s\"\n",
+				id.data, name.data());
+		});
+
+		db.query("select * from test where id=$1", 0, id,
+			[](const TestpostgresRecord& record) {
+			printf("ID=\"%d\", Name=\"%s\"\n",
+				record.id, record.name);
+		});
+
+		db.query("select * from test where id=$1", 0, id,
+			&TestpostgresRecord::print);
+	}
+	catch (qtl::postgres::error& e)
+	{
+		ASSERT_EXCEPTION(e);
+	}
+}
+
+void TestPostgres::test_insert()
+{
+	qtl::postgres::database db;
+	connect(db);
+
+	try
+	{
+		db.query_first("insert into test(Name, CreateTime) values($1, LOCALTIMESTAMP) returning ID",
+			"test_user", id);
+	}
+	catch (qtl::postgres::error& e)
+	{
+		ASSERT_EXCEPTION(e);
+	}
+	TEST_ASSERT_MSG(id > 0, "insert failture.");
+}
+
+void TestPostgres::test_insert2()
+{
+	qtl::postgres::database db;
+	connect(db);
+
+	try
+	{
+		uint64_t affected = 0;
+		qtl::postgres::statement stmt = db.open_command("insert into test(Name, CreateTime) values($1, LOCALTIMESTAMP)");
+		qtl::execute(stmt, &affected, "second_user", "third_user");
+		TEST_ASSERT_MSG(affected == 2, "Cannot insert 2 records to table test.");
+	}
+	catch (qtl::postgres::error& e)
+	{
+		ASSERT_EXCEPTION(e);
+	}
+}
+
+void TestPostgres::test_update()
+{
+	qtl::postgres::database db;
+	connect(db);
+
+	try
+	{
+		db.execute_direct("update test set Name=$1 WHERE ID=$2", NULL,
+			"other_user", id);
+	}
+	catch (qtl::postgres::error& e)
+	{
+		ASSERT_EXCEPTION(e);
+	}
+	TEST_ASSERT_MSG(id > 0, "insert failture.");
+}
+
+void TestPostgres::test_clear()
+{
+	qtl::postgres::database db;
+	connect(db);
+
+	try
+	{
+		db.simple_execute("delete from test");
+	}
+	catch (qtl::postgres::error& e)
+	{
+		ASSERT_EXCEPTION(e);
+	}
+}
+
+void TestPostgres::test_iterator()
+{
+	qtl::postgres::database db;
+	connect(db);
+
+	try
+	{
+		cout << "after insert all:" << endl;
+		for (auto& record : db.result<TestpostgresRecord>("select * from test"))
+		{
+			printf("ID=\"%d\", Name=\"%s\"\n",
+				record.id, record.name);
+		}
+	}
+	catch (qtl::postgres::error& e)
+	{
+		ASSERT_EXCEPTION(e);
+	}
+}
+
+void TestPostgres::test_any()
+{
+#ifdef _QTL_ENABLE_CPP17
+
+	qtl::postgres::database db;
+	connect(db);
+
+	try
+	{
+		db.query("select 0, 'hello world', LOCALTIMESTAMP",
+			[](const std::any& i, const std::any& str, const std::any& now) {
+			const qtl::postgres::time& time = std::any_cast<const qtl::postgres::time&>(now);
+			struct tm tm;
+			time.as_tm(tm);
+			cout << "0=\"" << std::any_cast<int32_t>(i) << "\", 'hello world'=\"" <<
+				std::any_cast<const std::string&>(str) << "\", now=\"" <<
+				std::put_time(&tm, "%c") << "\" \n";
+		});
+	}
+	catch (qtl::postgres::error& e)
+	{
+		ASSERT_EXCEPTION(e);
+	}
+	catch (std::bad_cast& e)
+	{
+		ASSERT_EXCEPTION(e);
+	}
+#endif
+}
+
+int main(int argc, char* argv[])
+{
+	Test::TextOutput output(Test::TextOutput::Verbose);
+
+	cout << endl << "Testing postgres:" << endl;
+	TestPostgres test_postgres;
+	test_postgres.run(output);
+	return 0;
+}
+
diff --git a/test/TestPostgres.h b/test/TestPostgres.h
new file mode 100644
index 0000000..943b57d
--- /dev/null
+++ b/test/TestPostgres.h
@@ -0,0 +1,35 @@
+#ifndef _TEST_MYSQL_H_
+#define _TEST_MYSQL_H_
+
+#include "TestSuite.h"
+
+namespace qtl
+{
+	namespace postgres
+	{
+		class database;
+	}
+}
+
+class TestPostgres : public TestSuite
+{
+public:
+	TestPostgres();
+
+private:
+	void test_dual();
+	void test_clear();
+	void test_insert();
+	void test_select();
+	void test_update();
+	void test_insert2();
+	void test_iterator();
+	void test_any();
+
+private:
+	int32_t id;
+	void connect(qtl::postgres::database& db);
+	void get_md5(std::istream& is, unsigned char* result);
+};
+
+#endif //_TEST_MYSQL_H_
diff --git a/test/TestSuite.h b/test/TestSuite.h
index b14e39d..fee7ac1 100644
--- a/test/TestSuite.h
+++ b/test/TestSuite.h
@@ -2,6 +2,7 @@
 #define _TEST_SUITE_H_
 
 #include <exception>
+#include <cpptest.h>
 
 class TestSuite : public Test::Suite
 {
diff --git a/test/test_mariadb.mak b/test/test_mariadb.mak
index ee0f245..ca462c7 100644
--- a/test/test_mariadb.mak
+++ b/test/test_mariadb.mak
@@ -3,9 +3,9 @@
 PCH_HEADER=stdafx.h
 PCH=stdafx.h.gch
 OBJ=TestMariaDB.o
-CFLAGS=-g -D_DEBUG -O2 -I/usr/include -I/usr/include/mariadb -I/usr/local/include -I/usr/local/include/mariadb 
+CFLAGS=-g -D_DEBUG -O2 -D_QTL_USE_MARIADB -I/usr/include $(shell mariadb_config --cflags)
 CXXFLAGS=-I../include -std=c++11
-LDFLAGS= -L/usr/local/lib -L/usr/local/mariadb/lib -lmariadb
+LDFLAGS= -L/usr/local/lib $(shell -L/usr/local/lib/mariadb/ -lmariadb)
 
 all : $(TARGET)
 
diff --git a/test/test_mysql.mak b/test/test_mysql.mak
index 2f85ada..4ab14b2 100644
--- a/test/test_mysql.mak
+++ b/test/test_mysql.mak
@@ -3,9 +3,9 @@
 PCH_HEADER=stdafx.h
 PCH=stdafx.h.gch
 OBJ=TestMysql.o md5.o
-CFLAGS=-g -D_DEBUG -O2 -I../include -I/usr/include/mysql -I/usr/local/include -I/usr/local/mysql/include 
+CFLAGS=-g -D_DEBUG -O2 -I../include -I/usr/include -I/usr/local/include $(shell mysql_config --cflags) 
 CXXFLAGS=-std=c++11
-LDFLAGS= -L/usr/local/lib -L/usr/local/mysql/lib -lcpptest -lmysqlclient
+LDFLAGS= -L/usr/local/lib -lcpptest $(shell mysql_config --libs)
 
 all : $(TARGET)
 
diff --git a/test/test_postgres.mak b/test/test_postgres.mak
new file mode 100644
index 0000000..337a635
--- /dev/null
+++ b/test/test_postgres.mak
@@ -0,0 +1,25 @@
+TARGET=test_postgres
+CC=g++
+PCH_HEADER=stdafx.h
+PCH=stdafx.h.gch
+OBJ=TestPostgres.o md5.o
+CFLAGS=-g -D_DEBUG -O2-I/usr/include -I/usr/local/include -I$(shell pg_config --includedir) -I$(shell pg_config --includedir-server )
+CXXFLAGS= -I../include -std=c++11
+LDFLAGS= -L$(shell pg_config --libdir) -lcpptest -lpq -lpgtypes
+
+all : $(TARGET)
+
+$(PCH) : $(PCH_HEADER)
+	$(CC) $(CFLAGS) $(CXXFLAGS) -x c++-header -o $@ $<
+
+TestPostgres.o : TestPostgres.cpp $(PCH)
+	$(CC) -c $(CFLAGS) $(CXXFLAGS) -o $@ $< 
+	
+md5.o : md5.c md5.h
+	gcc -c $(CFLAGS) -o $@ $<
+
+$(TARGET) : $(OBJ)
+	libtool --tag=CXX --mode=link $(CC) $(LDFLAGS) -o $@ $^
+
+clean:
+	rm $(TARGET) $(PCH) $(OBJ) -f
diff --git a/test/vs/test.sln b/test/vs/test.sln
index b3a4bc1..0e7dff6 100644
--- a/test/vs/test.sln
+++ b/test/vs/test.sln
@@ -9,6 +9,8 @@
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_mariadb", "test_mariadb\test_mariadb.vcproj", "{2A539BFA-B934-4192-A029-524593AEC6B8}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_postgres", "test_postgres\test_postgres.vcproj", "{C8634EFC-2489-4F39-94EB-F54DC1ED2211}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Win32 = Debug|Win32
@@ -31,6 +33,10 @@
 		{2A539BFA-B934-4192-A029-524593AEC6B8}.Debug|Win32.Build.0 = Debug|Win32
 		{2A539BFA-B934-4192-A029-524593AEC6B8}.Release|Win32.ActiveCfg = Release|Win32
 		{2A539BFA-B934-4192-A029-524593AEC6B8}.Release|Win32.Build.0 = Release|Win32
+		{C8634EFC-2489-4F39-94EB-F54DC1ED2211}.Debug|Win32.ActiveCfg = Debug|Win32
+		{C8634EFC-2489-4F39-94EB-F54DC1ED2211}.Debug|Win32.Build.0 = Debug|Win32
+		{C8634EFC-2489-4F39-94EB-F54DC1ED2211}.Release|Win32.ActiveCfg = Release|Win32
+		{C8634EFC-2489-4F39-94EB-F54DC1ED2211}.Release|Win32.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/test/vs/test_postgres/test_postgres.vcproj b/test/vs/test_postgres/test_postgres.vcproj
new file mode 100644
index 0000000..4ba0b92
--- /dev/null
+++ b/test/vs/test_postgres/test_postgres.vcproj
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="gb2312"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="test_postgres"
+	ProjectGUID="{C8634EFC-2489-4F39-94EB-F54DC1ED2211}"
+	RootNamespace="test_postgres"
+	Keyword="Win32Proj"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="1"
+			CharacterSet="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS"
+				MinimalRebuild="true"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				UsePrecompiledHeader="0"
+				WarningLevel="3"
+				Detect64BitPortabilityProblems="true"
+				DebugInformationFormat="4"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="libpq.lib ws2_32.lib cpptest.lib"
+				LinkIncremental="2"
+				GenerateDebugInformation="true"
+				SubSystem="2"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCWebDeploymentTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Release|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="1"
+			CharacterSet="1"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS"
+				RuntimeLibrary="2"
+				UsePrecompiledHeader="0"
+				WarningLevel="3"
+				Detect64BitPortabilityProblems="true"
+				DebugInformationFormat="3"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="libpq.lib ws2_32.lib cpptest.lib"
+				LinkIncremental="1"
+				GenerateDebugInformation="true"
+				SubSystem="2"
+				OptimizeReferences="2"
+				EnableCOMDATFolding="2"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCWebDeploymentTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Դ�ļ�"
+			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+			>
+			<File
+				RelativePath="..\..\stdafx.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\..\TestPostgres.cpp"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="ͷ�ļ�"
+			Filter="h;hpp;hxx;hm;inl;inc;xsd"
+			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+			>
+			<File
+				RelativePath="..\..\stdafx.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\TestPostgres.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\TestSuite.h"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="��Դ�ļ�"
+			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+			>
+		</Filter>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>

--
Gitblit v1.9.3