From e51bed25201e89b2fed36e45e1642812d88f6398 Mon Sep 17 00:00:00 2001
From: znone <glyc@sina.com.cn>
Date: Tue, 07 Mar 2017 11:06:28 +0000
Subject: [PATCH] 增加对ODBC的支持。
---
include/qtl_database_pool.hpp | 7
include/qtl_sqlite_pool.hpp | 11
test/TestOdbc.cpp | 271 ++++++++
include/qtl_common.hpp | 6
test/stdafx.h | 4
test/TestOdbc.h | 39 +
README.md | 61 +
include/qtl_sqlite.hpp | 62 -
test/test_odbc.mak | 25
test/vs/test_odbc/test_odbc.vcproj | 259 ++++++++
include/qtl_mysql_pool.hpp | 12
include/qtl_odbc.hpp | 1047 ++++++++++++++++++++++++++++++++
include/qtl_mysql.hpp | 5
test/vs/test.sln | 6
include/qtl_odbc_pool.hpp | 46 +
15 files changed, 1,807 insertions(+), 54 deletions(-)
diff --git a/README.md b/README.md
index e19e54c..b519a03 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# QTL
-QTL是一个访问SQL数据库的C++库,目前支持MySQL和SQLite。QTL是一个轻量级的库,只由头文件组成,不需要单独编译安装。QTL是对数据库原生客户端接口的薄封装,能提供友好使用方式的同时拥有接近于使用原生接口的性能。
+QTL是一个访问SQL数据库的C++库,目前支持MySQL、SQLite和ODBC。QTL是一个轻量级的库,只由头文件组成,不需要单独编译安装。QTL是对数据库原生客户端接口的薄封装,能提供友好使用方式的同时拥有接近于使用原生接口的性能。
使用QTL需要支持C++11的编译器。
## 使用方式
@@ -189,6 +189,65 @@
- qtl::sqlite::query_result
表示一个SQLite的查询结果集,用于以迭代器方式遍历查询结果。
+## 有关ODBC的说明
+
+通过ODBC访问数据库时,包含头文件qtl_odbc.hpp。
+QTL不支持ODBC的输出参数。
+
+### ODBC的参数数据绑定
+
+| 参数类型 | C++类型 |
+| ------- | ------ |
+| TINYINT | int8_t<br>uint8_t |
+| SMALLINT | int16_t<br>uint16_t |
+| INTEGER | int32_t<br>uint32_t |
+| BIGINT | int64_t<br>uint64_t |
+| FLOAT | float |
+| DOUBLE | double |
+| NUMERIC | SQL_NUMERIC_STRUCT |
+| BIT | bool |
+| CHAR<br>VARCHAR | const char*<br>std::string |
+| WCHAR<br>WVARCHAR | const wchar_t*<br>std::wstring |
+| BINARY | qtl::const_blob_data |
+| LONGVARBINARY | std::istream |
+| DATE | qtl::odbc::date |
+| TIME<br>UTCTIME | qtl::odbc::time |
+| TIMESTAMP<br>UTCDATETIME | qtl::odbc::datetime |
+| GUID | SQLGUID |
+
+### ODBC的字段数据绑定
+
+| 字段类型 | C++类型 |
+| ------- | ------ |
+| TINYINT | int8_t<br>uint8_t |
+| SMALLINT | int16_t<br>uint16_t |
+| INTEGER | int32_t<br>uint32_t |
+| BIGINT | int64_t<br>uint64_t |
+| FLOAT | float |
+| DOUBLE | double |
+| NUMERIC | SQL_NUMERIC_STRUCT |
+| BIT | bool |
+| CHAR<br>VARCHAR | char[N]<br>std::array<char, N><br>std::string |
+| WCHAR<br>WVARCHAR | wchar_t[N]<br>std::array<wchar_t, N><br>std::string |
+| BINARY | qtl::blob_data |
+| LONGVARBINARY | std::ostream |
+| DATE | qtl::odbc::date |
+| TIME<br>UTCTIME | qtl::odbc::time |
+| TIMESTAMP<br>UTCDATETIME | qtl::odbc::datetime |
+| GUID | SQLGUID |
+
+### ODBC相关的C++类
+- qtl::odbc::database
+表示一个ODBC的数据库连接,程序主要通过这个类操纵数据库。
+- qtl::odbc::statement
+表示一个ODBC的查询语句,实现查询相关操作。
+- qtl::odbc::error
+表示一个ODBC的错误,当操作出错时,抛出该类型的异常,包含错误信息。
+- qtl::odbc::transaction
+表示一个ODBC的事务操作。
+- qtl::odbc::query_result
+表示一个ODBC的查询结果集,用于以迭代器方式遍历查询结果。
+
## 关于测试
编译测试用例的第三方库需要另外下载。除了数据库相关的库外,测试用例用到了测试框架[CppTest](https://sourceforge.net/projects/cpptest/ "CppTest")。
diff --git a/include/qtl_common.hpp b/include/qtl_common.hpp
index 847a6cd..af9f409 100644
--- a/include/qtl_common.hpp
+++ b/include/qtl_common.hpp
@@ -463,12 +463,6 @@
binder(command, std::forward<T>(value));
}
-template<typename Command, typename T>
-inline void bind_record(Command& command, T& value)
-{
- bind_record(command, std::forward<T>(value));
-}
-
template<typename Command, typename Record>
class query_iterator final : public std::iterator<std::forward_iterator_tag, Record>
{
diff --git a/include/qtl_database_pool.hpp b/include/qtl_database_pool.hpp
index 68e31c6..1d04e2f 100644
--- a/include/qtl_database_pool.hpp
+++ b/include/qtl_database_pool.hpp
@@ -79,7 +79,7 @@
std::thread m_background_thread;
bool m_stop_thread;
- virtual bool open_database(Database& db)=0;
+ virtual Database* new_database() throw()=0;
void recovery(Database* db)
{
if(db==NULL) return;
@@ -101,9 +101,8 @@
Database* create_database()
{
- Database* db=new Database;
- if(open_database(*db))
- return db;
+ Database* db=new_database();
+ if(db) return db;
{
std::lock_guard<std::mutex> lock(m_pool_mutex);
diff --git a/include/qtl_mysql.hpp b/include/qtl_mysql.hpp
index 60c552b..b0cb96b 100644
--- a/include/qtl_mysql.hpp
+++ b/include/qtl_mysql.hpp
@@ -297,6 +297,11 @@
m_binderAddins[index].m_after_fetch=[](const binder& bind) {
if(*bind.is_null)
memset(bind.buffer, 0, bind.buffer_length+1);
+ else
+ {
+ char* text=reinterpret_cast<char*>(bind.buffer);
+ text[*bind.length]='\0';
+ }
};
}
diff --git a/include/qtl_mysql_pool.hpp b/include/qtl_mysql_pool.hpp
index 236d625..53c5a4e 100644
--- a/include/qtl_mysql_pool.hpp
+++ b/include/qtl_mysql_pool.hpp
@@ -15,10 +15,16 @@
public:
database_pool() : m_port(0) { }
virtual ~database_pool() { }
- virtual bool open_database(database& db) override
+ virtual database* new_database() throw() override
{
- db.charset_name("utf8");
- return db.open(m_host.data(), m_user.data(), m_password.data(), m_database.data(), 0, m_port);
+ database* db=new database;
+ db->charset_name("utf8");
+ if(!db->open(m_host.data(), m_user.data(), m_password.data(), m_database.data(), 0, m_port))
+ {
+ delete db;
+ db=NULL;
+ }
+ return db;
}
protected:
diff --git a/include/qtl_odbc.hpp b/include/qtl_odbc.hpp
new file mode 100644
index 0000000..e021ef6
--- /dev/null
+++ b/include/qtl_odbc.hpp
@@ -0,0 +1,1047 @@
+#ifndef _QTL_ODCB_H_
+#define _QTL_ODCB_H_
+
+#include <sql.h>
+#include <sqlext.h>
+#include <sstream>
+#include <vector>
+#include <array>
+#include <time.h>
+#include <assert.h>
+#include <malloc.h>
+#include <limits.h>
+
+#if !defined(_WIN32) || defined(__MINGW32__)
+#include <sys/time.h>
+#endif //_WIN32
+
+#include "qtl_common.hpp"
+
+namespace qtl
+{
+
+namespace odbc
+{
+
+template<SQLSMALLINT> class object;
+class database;
+
+class error : public std::exception
+{
+public:
+ template<SQLSMALLINT Type>
+ error(const object<Type>& h, SQLINTEGER code);
+ error(SQLINTEGER code, const char* msg) : m_errno(code), m_errmsg(msg) { }
+ SQLINTEGER code() const { return m_errno; }
+ operator bool() { return m_errno==SQL_SUCCESS || m_errno==SQL_SUCCESS_WITH_INFO; }
+ virtual const char* what() const throw() override { return m_errmsg.data(); }
+private:
+ SQLINTEGER m_errno;
+ std::string m_errmsg;
+};
+
+template<SQLSMALLINT Type>
+class object
+{
+public:
+ enum { handler_type=Type };
+ object() : m_handle(SQL_NULL_HANDLE) { };
+ object(const object&) = delete;
+ object(object&& src) : m_handle(src.m_handle)
+ {
+ src.m_handle=SQL_NULL_HANDLE;
+ }
+ explicit object(SQLHANDLE parent)
+ {
+ verify_error(SQLAllocHandle(handler_type, parent, &m_handle));
+ }
+ ~object()
+ {
+ close();
+ }
+ object& operator=(const object&) = delete;
+ object& operator=(object&& src)
+ {
+ if(this!=&src)
+ {
+ close();
+ m_handle=src.m_handle;
+ src.m_handle=NULL;
+ }
+ return *this;
+ }
+ SQLHANDLE handle() const { return m_handle; }
+
+ void close()
+ {
+ if(m_handle)
+ {
+ verify_error(SQLFreeHandle(handler_type, m_handle));
+ m_handle=SQL_NULL_HANDLE;
+ }
+ }
+
+protected:
+ SQLHANDLE m_handle;
+ void verify_error(SQLINTEGER code) const
+ {
+ if(code<0)
+ throw odbc::error(*this, code);
+ }
+};
+
+class environment final : public object<SQL_HANDLE_ENV>
+{
+public:
+ environment() : object(SQL_NULL_HANDLE)
+ {
+ verify_error(SQLSetEnvAttr(m_handle, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0));
+ }
+ environment(environment&& src) : object(std::forward<environment>(src)) { }
+};
+
+class statement final : public object<SQL_HANDLE_STMT>
+{
+public:
+ explicit statement(database& db);
+ statement(statement&& src)
+ : object(std::forward<statement>(src)), m_params(std::forward<std::vector<param_data>>(src.m_params))
+ {
+ m_binded_cols=src.m_binded_cols;
+ src.m_binded_cols=false;
+ m_blob_buffer=src.m_blob_buffer;
+ src.m_blob_buffer=NULL;
+ }
+ ~statement()
+ {
+ if(m_blob_buffer)
+ free(m_blob_buffer);
+ }
+ statement& operator=(statement&& src)
+ {
+ if(this!=&src)
+ {
+ object::operator =(std::forward<statement>(src));
+ m_params=std::forward<std::vector<param_data>>(src.m_params);
+ m_binded_cols=src.m_binded_cols;
+ src.m_binded_cols=false;
+ m_blob_buffer=src.m_blob_buffer;
+ src.m_blob_buffer=NULL;
+ }
+ return *this;
+ }
+
+ void open(const char* query_text, size_t text_length=SQL_NTS)
+ {
+ verify_error(SQLPrepare(m_handle, (SQLCHAR*)query_text, text_length));
+ }
+ void open(const std::string& query_text)
+ {
+ open(query_text.data(), query_text.size());
+ }
+
+ void bind_param(SQLUSMALLINT index, const std::nullptr_t&)
+ {
+ m_params[index].m_indicator=SQL_NULL_DATA;
+ verify_error(SQLBindParameter(m_handle, index+1,
+ SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_DEFAULT, 0, 0, NULL, 0, &m_params[index].m_indicator));
+ }
+ void bind_param(SQLUSMALLINT index, const qtl::null&)
+ {
+ bind_param(index, nullptr);
+ }
+ void bind_param(SQLUSMALLINT index, const int8_t& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_STINYINT, SQL_TINYINT,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const uint8_t& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_UTINYINT, SQL_TINYINT,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const int16_t& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_SSHORT, SQL_SMALLINT,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const uint16_t& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_USHORT, SQL_SMALLINT,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const int32_t& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const uint32_t& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const int64_t& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const uint64_t& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_BIGINT,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const double& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const float& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_FLOAT, SQL_FLOAT,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const bool& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_BIT, SQL_BIT,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const DATE_STRUCT& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_DATE, SQL_DATE,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const TIME_STRUCT& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_TIME, SQL_TIME,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const TIMESTAMP_STRUCT& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_TIMESTAMP, SQL_TIMESTAMP,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const SQLGUID& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_GUID, SQL_GUID,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const SQL_NUMERIC_STRUCT& v)
+ {
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_NUMERIC, SQL_NUMERIC,
+ 0, 0, (SQLPOINTER)&v, 0, NULL));
+ }
+ void bind_param(SQLUSMALLINT index, const char* v, size_t n=SQL_NTS, SQLULEN size=0)
+ {
+ m_params[index].m_indicator=n;
+ if(size==0) size=strlen(v);
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
+ size, 0, (SQLPOINTER)v, 0, &m_params[index].m_indicator));
+ }
+ void bind_param(SQLUSMALLINT index, const wchar_t* v, size_t n=SQL_NTS, SQLULEN size=0)
+ {
+ m_params[index].m_indicator=n;
+ if(size==0) size=wcslen(v);
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_WCHAR, SQL_WCHAR,
+ size, 0, (SQLPOINTER)v, 0, &m_params[index].m_indicator));
+ }
+ void bind_param(SQLUSMALLINT index, const std::string& v)
+ {
+ bind_param(index, v.data(), v.size(), v.size());
+ }
+ void bind_param(SQLUSMALLINT index, const std::wstring& v)
+ {
+ bind_param(index, v.data(), v.size(), v.size());
+ }
+ void bind_param(SQLUSMALLINT index, const const_blob_data& v)
+ {
+ m_params[index].m_indicator=v.size;
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY,
+ v.size, 0, (SQLPOINTER)v.data, 0, &m_params[index].m_indicator));
+ }
+ void bind_param(SQLUSMALLINT index, std::istream& s)
+ {
+ if(m_blob_buffer==NULL)
+ m_blob_buffer=malloc(blob_buffer_size);
+ m_params[index].m_data=m_blob_buffer;
+ m_params[index].m_size=blob_buffer_size;
+ m_params[index].m_indicator=SQL_LEN_DATA_AT_EXEC(m_params[index].m_size);
+ verify_error(SQLBindParameter(m_handle, index+1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY,
+ INT_MAX, 0, &m_params[index], 0, &m_params[index].m_indicator));
+ m_params[index].m_after_fetch=[this, &s](const param_data& p) {
+ SQLLEN readed=SQL_NULL_DATA;
+ while(!s.eof() && !s.fail())
+ {
+ s.read((char*)p.m_data, p.m_size);
+ readed=(unsigned long)s.gcount();
+ if(readed>0)
+ {
+ verify_error(SQLPutData(m_handle, p.m_data, readed));
+ }
+ }
+ };
+ }
+
+ void bind_field(SQLUSMALLINT index, bool&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_BIT, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, int8_t&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_STINYINT, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, uint8_t&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_UTINYINT, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, int16_t&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_SSHORT, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, uint16_t&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_USHORT, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, int32_t&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_SLONG, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, uint32_t&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_ULONG, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, int64_t&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_SBIGINT, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, uint64_t&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_UBIGINT, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, float&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_FLOAT, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, double&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_DOUBLE, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, DATE_STRUCT&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_TYPE_DATE, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, TIME_STRUCT&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_TYPE_TIME, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, TIMESTAMP_STRUCT&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_TYPE_TIMESTAMP, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, SQLGUID&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_GUID, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, SQL_NUMERIC_STRUCT&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_NUMERIC, &v, 0, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, char* v, size_t n)
+ {
+ m_params[index].m_data=v;
+ m_params[index].m_size=n;
+ m_params[index].m_after_fetch=[](const param_data& p) {
+ if(p.m_indicator==SQL_NULL_DATA)
+ memset(p.m_data, 0, p.m_size*sizeof(char));
+ else
+ {
+ char* text=reinterpret_cast<char*>(p.m_data);
+ text[p.m_indicator]='\0';
+ }
+ };
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_CHAR, v, n-1, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, wchar_t* v, size_t n)
+ {
+ m_params[index].m_data=v;
+ m_params[index].m_size=n;
+ m_params[index].m_after_fetch=[](const param_data& p) {
+ if(p.m_indicator==SQL_NULL_DATA)
+ memset(p.m_data, 0, p.m_size*sizeof(wchar_t));
+ else
+ {
+ wchar_t* text=reinterpret_cast<wchar_t*>(p.m_data);
+ text[p.m_indicator]='\0';
+ }
+ };
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_WCHAR, v, n-1, &m_params[index].m_indicator));
+ }
+ template<typename CharType>
+ void bind_field(SQLUSMALLINT index, std::basic_string<CharType>&& v)
+ {
+ SQLLEN length=0;
+ SQLColAttribute(m_handle, index+1, SQL_DESC_LENGTH, NULL, 0, NULL, &length);
+ v.resize(length);
+ bind_field(index, const_cast<char*>(v.data()), v.size()+1);
+ m_params[index].m_after_fetch=[&v](const param_data& p) {
+ if(p.m_indicator==SQL_NULL_DATA)
+ v.clear();
+ else
+ v.resize(p.m_indicator);
+ };
+ }
+ template<size_t N>
+ void bind_field(SQLUSMALLINT index, std::array<char, N>&& value)
+ {
+ bind_field(index, value.data(), value.size());
+ }
+ template<size_t N>
+ void bind_field(SQLUSMALLINT index, std::array<wchar_t, N>&& value)
+ {
+ bind_field(index, value.data(), value.size());
+ }
+ void bind_field(SQLUSMALLINT index, qtl::blob_data&& v)
+ {
+ verify_error(SQLBindCol(m_handle, index+1, SQL_C_BINARY, v.data, v.size, &m_params[index].m_indicator));
+ }
+ void bind_field(SQLUSMALLINT index, std::ostream&& v)
+ {
+ if(m_blob_buffer==NULL)
+ m_blob_buffer=malloc(blob_buffer_size);
+ m_params[index].m_data=m_blob_buffer;
+ m_params[index].m_size=blob_buffer_size;
+ m_params[index].m_after_fetch=[this, index, &v](const param_data& p) {
+ SQLRETURN ret=SQLGetData(m_handle, index+1, SQL_C_BINARY, p.m_data, p.m_size, const_cast<SQLLEN*>(&p.m_indicator));
+ while(ret!=SQL_NO_DATA)
+ {
+ size_t n = (p.m_indicator > blob_buffer_size) || (p.m_indicator == SQL_NO_TOTAL) ?
+ blob_buffer_size : p.m_indicator;
+ verify_error(ret);
+ v.write((const char*)p.m_data, n);
+ ret=SQLGetData(m_handle, index+1, SQL_C_BINARY, p.m_data, p.m_size, const_cast<SQLLEN*>(&p.m_indicator));
+ }
+ };
+ }
+
+ template<typename Type>
+ void bind_field(size_t index, indicator<Type>&& value)
+ {
+ qtl::bind_field(*this, index, value.data);
+ param_data& param=m_params[index];
+ auto fetch_fun=param.m_after_fetch;
+ param.m_after_fetch=[fetch_fun, &value](const param_data& p) {
+ value.is_truncated=false;
+ if(p.m_indicator==SQL_NULL_DATA)
+ {
+ value.is_null=true;
+ value.length=0;
+ }
+ else if(p.m_indicator>=0)
+ {
+ value.is_null=false;
+ value.length=p.m_indicator;
+ if(p.m_size>0 && p.m_indicator>=p.m_size)
+ value.is_truncated=true;
+ }
+ if(fetch_fun) fetch_fun(p);
+ };
+ }
+
+ template<typename Types>
+ void execute(const Types& params)
+ {
+ SQLSMALLINT count=0;
+ verify_error(SQLNumParams(m_handle, &count));
+ if(count>0)
+ {
+ m_params.resize(count);
+ qtl::bind_params(*this, params);
+ }
+
+ SQLRETURN ret=SQLExecute(m_handle);
+ verify_error(ret);
+ if(ret==SQL_NEED_DATA)
+ {
+ SQLPOINTER token;
+ size_t i=0;
+ ret=SQLParamData(m_handle, &token);
+ verify_error(ret);
+ while(ret==SQL_NEED_DATA)
+ {
+ while(i!=count)
+ {
+ if(&m_params[i]==token)
+ {
+ if(m_params[i].m_after_fetch)
+ m_params[i].m_after_fetch(m_params[i]);
+ break;
+ }
+ ++i;
+ }
+ ret=SQLParamData(m_handle, &token);
+ verify_error(ret);
+ }
+ }
+ }
+
+ template<typename Types>
+ bool fetch(Types&& values)
+ {
+ if(!m_binded_cols)
+ {
+ SQLSMALLINT count=0;
+ verify_error(SQLNumResultCols(m_handle, &count));
+ if(count>0)
+ {
+ m_params.resize(count);
+ qtl::bind_record(*this, std::forward<Types>(values));
+ }
+ m_binded_cols=true;
+ }
+ return fetch();
+ }
+
+ bool fetch()
+ {
+ SQLRETURN ret=SQLFetch(m_handle);
+ if(ret==SQL_SUCCESS || ret==SQL_SUCCESS_WITH_INFO)
+ {
+ for(const param_data& data : m_params)
+ {
+ if(data.m_after_fetch)
+ data.m_after_fetch(data);
+ }
+ return true;
+ }
+ verify_error(ret);
+ return false;
+ }
+
+ SQLLEN affetced_rows()
+ {
+ SQLLEN count=0;
+ verify_error(SQLRowCount(m_handle, &count));
+ return count;
+ }
+
+ /*
+ ODBC do not support this function, but you can use query to instead it:
+ For MS SQL Server: SELECT @@IDENTITY;
+ For MySQL: SELECT LAST_INSERT_ID();
+ For SQLite: SELECT last_insert_rowid();
+ */
+ /*uint64_t insert_id()
+ {
+ assert(false);
+ return 0;
+ }*/
+
+ void reset()
+ {
+ verify_error(SQLFreeStmt(m_handle, SQL_RESET_PARAMS));
+ }
+
+private:
+ struct param_data
+ {
+ SQLPOINTER m_data;
+ SQLLEN m_size;
+ SQLLEN m_indicator;
+ std::function<void(const param_data&)> m_after_fetch;
+
+ param_data() : m_data(NULL), m_size(0), m_indicator(0) { }
+ };
+ SQLPOINTER m_blob_buffer;
+ std::vector<param_data> m_params;
+ bool m_binded_cols;
+};
+
+struct connection_parameter
+{
+ std::string m_name;
+ std::string m_prompt;
+ std::string m_value;
+ std::vector<std::string> m_value_list;
+ bool m_optinal;
+ bool m_assigned;
+
+ connection_parameter() : m_optinal(false), m_assigned(false) { }
+ void reset()
+ {
+ m_name.clear();
+ m_prompt.clear();
+ m_value.clear();
+ m_value_list.clear();
+ m_optinal=false;
+ m_assigned=false;
+ }
+};
+typedef std::vector<connection_parameter> connection_parameters;
+
+class database : public object<SQL_HANDLE_DBC>, public qtl::base_database<database, statement>
+{
+public:
+ explicit database(environment& env) : object(env.handle()), m_opened(false)
+ {
+ }
+ database(database&& src)
+ : object(std::forward<database>(src)), m_connection(std::forward<std::string>(src.m_connection))
+ {
+ m_opened=src.m_opened;
+ src.m_opened=false;
+ }
+ ~database()
+ {
+ close();
+ }
+ database& operator=(database&& src)
+ {
+ if(this!=&src)
+ {
+ object::operator =(std::forward<database>(src));
+ m_opened=src.m_opened;
+ src.m_opened=false;
+ m_connection=std::forward<std::string>(src.m_connection);
+ }
+ return *this;
+ }
+
+ void open(const char* server_name, size_t server_name_length,
+ const char* user_name, size_t user_name_length, const char* password, size_t password_length)
+ {
+ if(m_opened) close();
+ verify_error(SQLConnectA(m_handle, (SQLCHAR*)server_name, server_name_length, (SQLCHAR*)user_name, user_name_length, (SQLCHAR*)password, password_length));
+ m_opened=true;
+ }
+ void open(const char* server_name, const char* user_name, const char* password)
+ {
+ verify_error(SQLConnectA(m_handle, (SQLCHAR*)server_name, SQL_NTS, (SQLCHAR*)user_name, SQL_NTS, (SQLCHAR*)password, SQL_NTS));
+ }
+ void open(const std::string& server_name, const std::string& user_name, const std::string& password)
+ {
+ open(server_name.data(), server_name.size(), user_name.data(), user_name.size(), password.data(), password.size());
+ }
+ void open(const char* input_text, size_t text_length=SQL_NTS, SQLSMALLINT driver_completion=SQL_DRIVER_NOPROMPT, SQLHWND hwnd=NULL)
+ {
+ m_connection.resize(512);
+ SQLSMALLINT out_len;
+ if(m_opened) close();
+ verify_error(SQLDriverConnectA(m_handle, hwnd, (SQLCHAR*)input_text, (SQLSMALLINT)text_length,
+ (SQLCHAR*)m_connection.data(), (SQLSMALLINT)m_connection.size(), &out_len, driver_completion));
+ m_connection.resize(out_len);
+ m_opened=true;
+ }
+ void open(const std::string& input_text, SQLSMALLINT driver_completion=SQL_DRIVER_NOPROMPT, SQLHWND hwnd=NULL)
+ {
+ open(input_text.data(), input_text.size(), driver_completion, hwnd);
+ }
+ void open(SQLHWND hwnd, SQLSMALLINT driver_completion=SQL_DRIVER_COMPLETE)
+ {
+ open("", SQL_NTS, driver_completion, hwnd);
+ }
+ // InputPred like:
+ // bool input_parameters(connection_parameters& parameters);
+ template<typename InputPred>
+ void open(const char* connection_text, size_t text_length, InputPred&& pred)
+ {
+ SQLSMALLINT length=0;
+ SQLINTEGER ret=SQL_SUCCESS;
+ std::string input_text;
+ if(m_opened) close();
+ if(text_length==SQL_NTS)
+ input_text=connection_text;
+ else
+ input_text.assign(connection_text, text_length);
+ m_connection.resize(1024);
+ while( (ret=SQLBrowseConnectA(m_handle, (SQLCHAR*)input_text.data(), SQL_NTS,
+ (SQLCHAR*)m_connection.data(), m_connection.size(), &length)) == SQL_NEED_DATA)
+ {
+ connection_parameters parameters;
+ parse_browse_string(m_connection.data(), length, parameters);
+ if(!pred(parameters))
+ throw error(SQL_NEED_DATA, "User cancel operation.");
+ input_text=create_connection_text(parameters);
+ }
+ if(ret==SQL_ERROR || ret==SQL_SUCCESS_WITH_INFO)
+ verify_error(ret);
+ m_opened=true;
+ }
+ template<typename InputPred>
+ void open(const char* connection_text, InputPred&& pred)
+ {
+ open(connection_text, SQL_NTS, std::forward<InputPred>(pred));
+ }
+ template<typename InputPred>
+ void open(const std::string& connection_text, InputPred&& pred)
+ {
+ open(connection_text.data(), connection_text.size(), std::forward<InputPred>(pred));
+ }
+ void close()
+ {
+ if(m_opened)
+ {
+ verify_error(SQLDisconnect(m_handle));
+ m_opened=false;
+ }
+ }
+
+ void set_attribute(SQLINTEGER attr, SQLINTEGER value)
+ {
+ verify_error(SQLSetConnectAttr(m_handle, attr, &value, 0));
+ }
+ void get_attribute(SQLINTEGER attr, SQLINTEGER& value) const
+ {
+ verify_error(SQLGetConnectAttr(m_handle, attr, &value, sizeof(SQLINTEGER), 0));
+ }
+ void get_info(SQLSMALLINT info, std::string& value, SQLSMALLINT size=SQL_MAX_OPTION_STRING_LENGTH) const
+ {
+ value.resize(size);
+ verify_error(SQLGetInfo(m_handle, info, (SQLPOINTER)value.data(), size, &size));
+ value.resize(size);
+ }
+
+ void auto_commit(bool on)
+ {
+ set_attribute(SQL_ATTR_AUTOCOMMIT, on ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF);
+ }
+ void begin_transaction()
+ {
+ auto_commit(false);
+ }
+ void rollback()
+ {
+ verify_error(SQLEndTran(handler_type, m_handle, SQL_ROLLBACK));
+ auto_commit(true);
+ }
+ void commit()
+ {
+ verify_error(SQLEndTran(handler_type, m_handle, SQL_COMMIT));
+ auto_commit(true);
+ }
+
+ std::string dbms_name() const
+ {
+ std::string name;
+ get_info(SQL_DBMS_NAME, name);
+ return name;
+ }
+ std::string server_name() const
+ {
+ std::string name;
+ get_info(SQL_SERVER_NAME, name);
+ return name;
+ }
+ std::string user_name() const
+ {
+ std::string name;
+ get_info(SQL_USER_NAME, name);
+ return name;
+ }
+ std::string db_name() const
+ {
+ std::string name;
+ get_info(SQL_DATABASE_NAME, name);
+ return name;
+ }
+ const std::string& connection_text() const
+ {
+ return m_connection;
+ }
+
+ bool is_alive()
+ {
+ SQLINTEGER value;
+ get_attribute(SQL_ATTR_CONNECTION_DEAD, value);
+ return value==SQL_CD_FALSE;
+ }
+
+ statement open_command(const char* query_text, size_t text_length)
+ {
+ statement stmt(*this);
+ stmt.open(query_text, text_length);
+ return stmt;
+ }
+ statement open_command(const char* query_text)
+ {
+ return open_command(query_text, strlen(query_text));
+ }
+ statement open_command(const std::string& query_text)
+ {
+ return open_command(query_text.data(), query_text.length());
+ }
+
+ void simple_execute(const char* query_text, size_t text_length=SQL_NTS)
+ {
+ statement command(*this);
+ SQLRETURN ret=SQLExecDirectA(command.handle(), (SQLCHAR*)query_text, text_length);
+ if(ret!=SQL_SUCCESS && ret!=SQL_NO_DATA)
+ verify_error(ret);
+ }
+ void simple_execute(const std::string& query_text)
+ {
+ simple_execute(query_text.data(), query_text.size());
+ }
+
+private:
+ bool m_opened;
+ std::string m_connection;
+
+ void parse_browse_string(const char* output_text, size_t text_length, connection_parameters& parameters);
+ std::string create_connection_text(const connection_parameters& parameters);
+};
+
+struct date : public SQL_DATE_STRUCT
+{
+ date()
+ {
+ memset(this, 0, sizeof(SQL_DATE_STRUCT));
+ }
+};
+
+struct time : public SQL_TIME_STRUCT
+{
+ time()
+ {
+ memset(this, 0, sizeof(SQL_TIME_STRUCT));
+ }
+};
+
+struct timestamp : public SQL_TIMESTAMP_STRUCT
+{
+ timestamp()
+ {
+ memset(this, 0, sizeof(SQL_TIMESTAMP_STRUCT));
+ }
+ timestamp(struct tm& tm)
+ {
+ year=tm.tm_year+1900;
+ month=tm.tm_mon+1;
+ day=tm.tm_mday;
+ hour=tm.tm_hour;
+ minute=tm.tm_min;
+ second=tm.tm_sec;
+ }
+ timestamp(time_t value)
+ {
+ struct tm tm;
+#if defined(_MSC_VER)
+ localtime_s(&tm, &value);
+#elif defined(_POSIX_VERSION)
+ localtime_r(&value, &tm);
+#else
+ tm=*localtime(&value);
+#endif
+ new(this)timestamp(tm);
+ }
+ timestamp(const timestamp& src)
+ {
+ memcpy(this, &src, sizeof(SQL_TIMESTAMP_STRUCT));
+ }
+ timestamp& operator=(const timestamp& src)
+ {
+ if(this!=&src)
+ memcpy(this, &src, sizeof(SQL_TIMESTAMP_STRUCT));
+ return *this;
+ }
+
+ static timestamp now()
+ {
+ time_t value;
+ ::time(&value);
+ return timestamp(value);
+ }
+
+ time_t as_tm(struct tm& tm) const
+ {
+ tm.tm_year=year-1900;
+ tm.tm_mon=month-1;
+ tm.tm_mday=day;
+ tm.tm_hour=hour;
+ tm.tm_min=minute;
+ tm.tm_sec=second;
+ return mktime(&tm);
+ }
+ time_t get_time() const
+ {
+ struct tm tm;
+ return as_tm(tm);
+ }
+ timeval get_timeval() const
+ {
+ timeval tv;
+ struct tm tm;
+ tv.tv_sec=as_tm(tm);
+ tv.tv_usec=fraction/1000;
+ }
+};
+
+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>;
+
+template<typename Params>
+inline statement& operator<<(statement& stmt, const Params& params)
+{
+ stmt.reset();
+ stmt.execute(params);
+ return stmt;
+}
+
+template<SQLSMALLINT Type>
+inline error::error(const object<Type>& h, SQLINTEGER code)
+{
+ m_errno=code;
+ if(code==SQL_ERROR || code==SQL_SUCCESS_WITH_INFO)
+ {
+ SQLSMALLINT i=0;
+ SQLINTEGER err=SQL_SUCCESS;
+ SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
+ SQLCHAR state[SQL_SQLSTATE_SIZE+1];
+ std::ostringstream oss;
+ while(SQLGetDiagRecA(object<Type>::handler_type, h.handle(), ++i, state, &err,
+ message, SQL_MAX_MESSAGE_LENGTH, NULL)==SQL_SUCCESS)
+ {
+ oss<<"["<<state<<"] ("<<err<<") "<<message<<std::endl;
+ }
+ m_errmsg=oss.str();
+ }
+ else if(code==SQL_INVALID_HANDLE)
+ {
+ m_errmsg="Invalid handle.";
+ }
+}
+
+inline void database::parse_browse_string(const char* output_text, size_t text_length, connection_parameters& parameters)
+{
+ enum { part_name, part_prompt, part_list, part_value };
+ const char* sp=output_text;
+ const char* token=sp;
+ connection_parameter parameter;
+ int part_type=part_name;
+ while(sp!=output_text+text_length)
+ {
+ switch(*sp)
+ {
+ case ';':
+ parameters.emplace_back(parameter);
+ parameter.reset();
+ part_type=part_name;
+ token=sp+1;
+ break;
+ case '=':
+ if(part_type==part_prompt)
+ parameter.m_prompt.assign(token, sp-token);
+ part_type=part_value;
+ token=sp+1;
+ break;
+ case ':':
+ if(part_type==part_name)
+ parameter.m_name.assign(token, sp-token);
+ part_type=part_prompt;
+ token=sp+1;
+ break;
+ case '{':
+ part_type=part_list;
+ parameter.m_value_list.clear();
+ token=sp+1;
+ break;;
+ case '}':
+ case ',':
+ if(part_type==part_list)
+ parameter.m_value_list.emplace_back(token, sp-token);
+ token=sp+1;
+ break;
+ case '*':
+ if(part_type==part_name && token==sp)
+ {
+ parameter.m_optinal=true;
+ token=sp+1;
+ }
+ break;
+ case '?':
+ token=sp+1;
+ break;
+ }
+ ++sp;
+ }
+ if(!parameter.m_name.empty())
+ parameters.emplace_back(parameter);
+}
+
+inline std::string database::create_connection_text(const connection_parameters& parameters)
+{
+ std::ostringstream oss;
+ for(auto& parameter : parameters)
+ {
+ if(parameter.m_assigned)
+ oss<<parameter.m_name<<'='<<parameter.m_value<<';';
+ }
+ return oss.str();
+}
+
+inline statement::statement(database& db)
+ : object(db.handle()), m_blob_buffer(NULL), m_binded_cols(false)
+{
+}
+
+} //odbc
+
+#ifdef _WIN32
+
+namespace mssql
+{
+
+class database : public odbc::database
+{
+public:
+ explicit database(odbc::environment& env) : odbc::database(env) { }
+ database(database&& src) : odbc::database(std::forward<database>(src)) { }
+
+ void open(const char* server, const char* db=NULL, const char* user=NULL, const char* password=NULL)
+ {
+ std::ostringstream oss;
+ oss<<"DRIVER={SQL Server};SERVER="<<server<<";";
+ if(user==NULL)
+ oss<<"UID=;PWD=;Trusted_Connection=yes;";
+ else
+ {
+ oss<<"UID="<<user<<";PWD=";
+ if(password) oss<<password;
+ oss<<";Trusted_Connection=no;";
+ }
+ oss<<"DATABASE="<<db;
+ odbc::database::open(oss.str());
+ }
+};
+
+} //mssql
+
+namespace msaccess
+{
+
+class database : public odbc::database
+{
+public:
+ explicit database(odbc::environment& env) : odbc::database(env) { }
+ database(database&& src) : odbc::database(std::forward<database>(src)) { }
+
+ void open(const char* filename, const char* user=NULL, const char* password=NULL)
+ {
+ std::ostringstream oss;
+ oss<<"DRIVER={Microsoft Access Driver};DBQ="<<filename;
+ if(user) oss<<";UID:"<<user;
+ if(password) oss<<";PWD="<<password;
+ odbc::database::open(oss.str());
+ }
+};
+
+} //msaccess
+
+#endif //_WIN32
+
+}
+
+#endif //_QTL_ODCB_H_
diff --git a/include/qtl_odbc_pool.hpp b/include/qtl_odbc_pool.hpp
new file mode 100644
index 0000000..4014cb1
--- /dev/null
+++ b/include/qtl_odbc_pool.hpp
@@ -0,0 +1,46 @@
+#ifndef _QTL_ODBC_POOL_HPP_
+#define _QTL_ODBC_POOL_HPP_
+
+#include "qtl_database_pool.hpp"
+#include "qtl_odbc.hpp"
+
+namespace qtl
+{
+
+namespace odbc
+{
+
+class database_pool : public qtl::database_pool<database>
+{
+public:
+ database_pool() { }
+ virtual ~database_pool() { }
+ virtual database* new_database() throw() override
+ {
+ database* db=NULL;
+ try
+ {
+ db=new database(m_env);
+ db->open(m_connection);
+ }
+ catch(error& e)
+ {
+ if(db)
+ {
+ delete db;
+ db=NULL;
+ }
+ }
+ return db;
+ }
+
+protected:
+ std::string m_connection;
+ environment m_env;
+};
+
+}
+
+}
+
+#endif //_QTL_ODBC_POOL_HPP_
diff --git a/include/qtl_sqlite.hpp b/include/qtl_sqlite.hpp
index 86ee324..20f7e00 100644
--- a/include/qtl_sqlite.hpp
+++ b/include/qtl_sqlite.hpp
@@ -77,63 +77,63 @@
}
}
- void bind(int col, int value)
+ void bind_param(int index, int value)
{
- verify_error(sqlite3_bind_int(m_stmt, col, value));
+ verify_error(sqlite3_bind_int(m_stmt, index+1, value));
}
- void bind(int col, int64_t value)
+ void bind_param(int index, int64_t value)
{
- verify_error(sqlite3_bind_int64(m_stmt, col, value));
+ verify_error(sqlite3_bind_int64(m_stmt, index+1, value));
}
- void bind(int col, double value)
+ void bind_param(int index, double value)
{
- verify_error(sqlite3_bind_double(m_stmt, col, value));
+ verify_error(sqlite3_bind_double(m_stmt, index+1, value));
}
- void bind(int col, const char* value, size_t n=-1)
+ void bind_param(int index, const char* value, size_t n=-1)
{
if(value)
- verify_error(sqlite3_bind_text(m_stmt, col, value, (int)n, NULL));
+ verify_error(sqlite3_bind_text(m_stmt, index+1, value, (int)n, NULL));
else
- verify_error(sqlite3_bind_null(m_stmt, col));
+ verify_error(sqlite3_bind_null(m_stmt, index+1));
}
- void bind(int col, const wchar_t* value, size_t n=-1)
+ void bind_param(int index, const wchar_t* value, size_t n=-1)
{
if(value)
- verify_error(sqlite3_bind_text16(m_stmt, col, value, (int)n, NULL));
+ verify_error(sqlite3_bind_text16(m_stmt, index+1, value, (int)n, NULL));
else
- verify_error(sqlite3_bind_null(m_stmt, col));
+ verify_error(sqlite3_bind_null(m_stmt, index+1));
}
- void bind(int col, const std::string& value)
+ void bind_param(int index, const std::string& value)
{
- bind(col, value.data(), value.size());
+ bind_param(index, value.data(), value.size());
}
- void bind(int col, const std::wstring& value)
+ void bind_param(int index, const std::wstring& value)
{
- bind(col, value.data(), value.size());
+ bind_param(index, value.data(), value.size());
}
- void bind(int col, const const_blob_data& value)
+ void bind_param(int index, const const_blob_data& value)
{
if(value.size)
{
if(value.data)
- verify_error(sqlite3_bind_blob(m_stmt, col, value.data, (int)value.size, NULL));
+ verify_error(sqlite3_bind_blob(m_stmt, index+1, value.data, (int)value.size, NULL));
else
- verify_error(sqlite3_bind_zeroblob(m_stmt, col, (int)value.size));
+ verify_error(sqlite3_bind_zeroblob(m_stmt, index+1, (int)value.size));
}
else
- verify_error(sqlite3_bind_null(m_stmt, col));
+ verify_error(sqlite3_bind_null(m_stmt, index+1));
}
- void bind_zero_blob(int col, int n)
+ void bind_zero_blob(int index, int n)
{
- verify_error(sqlite3_bind_zeroblob(m_stmt, col, (int)n));
+ verify_error(sqlite3_bind_zeroblob(m_stmt, index+1, (int)n));
}
- void bind(int col, qtl::null value)
+ void bind_param(int index, qtl::null)
{
- verify_error(sqlite3_bind_null(m_stmt, col));
+ verify_error(sqlite3_bind_null(m_stmt, index+1));
}
- void bind(int col)
+ void bind_param(int index, std::nullptr_t)
{
- verify_error(sqlite3_bind_null(m_stmt, col));
+ verify_error(sqlite3_bind_null(m_stmt, index+1));
}
int get_parameter_count() const
{
@@ -146,12 +146,6 @@
int get_parameter_index(const char* param_name) const
{
return sqlite3_bind_parameter_index(m_stmt, param_name);
- }
-
- template<class Param>
- void bind_param(size_t index, const Param& param)
- {
- bind((int)index+1, param);
}
template<class Type>
@@ -181,7 +175,7 @@
{
size_t col_length=get_column_length((int)index);
if(col_length>0)
- strncpy(value, (const char*)sqlite3_column_text(m_stmt, (int)index), std::min(length, col_length));
+ strncpy(value, (const char*)sqlite3_column_text(m_stmt, (int)index), std::min(length, col_length+1));
else
memset(value, 0, length*sizeof(char));
}
@@ -189,7 +183,7 @@
{
size_t col_length=sqlite3_column_bytes16(m_stmt, (int)index);
if(col_length>0)
- wcsncpy(value, (const wchar_t*)sqlite3_column_text16(m_stmt, (int)index), std::min(length, col_length));
+ wcsncpy(value, (const wchar_t*)sqlite3_column_text16(m_stmt, (int)index), std::min(length, col_length+1));
else
memset(value, 0, length*sizeof(wchar_t));
}
diff --git a/include/qtl_sqlite_pool.hpp b/include/qtl_sqlite_pool.hpp
index 17b9a86..39ada04 100644
--- a/include/qtl_sqlite_pool.hpp
+++ b/include/qtl_sqlite_pool.hpp
@@ -14,17 +14,20 @@
{
public:
database_pool() : m_flags(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) { }
- virtual bool open_database(database& db) override
+ virtual database* new_database() throw() override
{
+ database* db=NULL;
try
{
- db.open(m_filename.data(), m_flags);
- return true;
+ db=new database;
+ db->open(m_filename.data(), m_flags);
}
catch (error& e)
{
- return false;
+ delete db;
+ db=NULL;
}
+ return db;
}
protected:
diff --git a/test/TestOdbc.cpp b/test/TestOdbc.cpp
new file mode 100644
index 0000000..58cc44a
--- /dev/null
+++ b/test/TestOdbc.cpp
@@ -0,0 +1,271 @@
+#include "stdafx.h"
+#include "TestOdbc.h"
+#include <fstream>
+#include <array>
+#include "md5.h"
+
+using namespace std;
+
+struct TestOdbcRecord
+{
+ uint32_t id;
+ char name[33];
+ qtl::odbc::timestamp create_time;
+
+ TestOdbcRecord()
+ {
+ memset(this, 0, sizeof(TestOdbcRecord));
+ }
+
+ void print() const
+ {
+ printf("ID=\"%d\", Name=\"%s\"\n",
+ id, name);
+ }
+};
+
+namespace qtl
+{
+ template<>
+ inline void bind_record<qtl::odbc::statement, TestOdbcRecord>(qtl::odbc::statement& command, TestOdbcRecord&& v)
+ {
+ qtl::bind_field(command, 0, v.id);
+ qtl::bind_field(command, 1, v.name);
+ qtl::bind_field(command, 2, v.create_time);
+ }
+}
+
+TestOdbc::TestOdbc() : m_db(m_env)
+{
+ m_db.open("DRIVER={SQL Server};SERVER=(local);UID=;PWD=;Trusted_Connection=yes;DATABASE=test");
+ cout<<"DBMS: "<<m_db.dbms_name()<<endl;
+ cout<<"SERVER: "<<m_db.server_name()<<endl;
+ cout<<"USER: "<<m_db.user_name()<<endl;
+ cout<<"DATABASE: "<<m_db.db_name()<<endl;
+
+ id=0;
+ TEST_ADD(TestOdbc::test_dual)
+ TEST_ADD(TestOdbc::test_clear)
+ TEST_ADD(TestOdbc::test_insert)
+ TEST_ADD(TestOdbc::test_select)
+ TEST_ADD(TestOdbc::test_update)
+ TEST_ADD(TestOdbc::test_insert2)
+ TEST_ADD(TestOdbc::test_iterator)
+ TEST_ADD(TestOdbc::test_insert_blob)
+ TEST_ADD(TestOdbc::test_select_blob)
+}
+
+void TestOdbc::test_dual()
+{
+ try
+ {
+ m_db.query("select 0, 'hello world';",
+ [](uint32_t i, const std::string& str) {
+ printf("0=\"%d\", 'hello world'=\"%s\"\n",
+ i, str.data());
+ });
+ }
+ catch(qtl::odbc::error& e)
+ {
+ if(!e) ASSERT_EXCEPTION(e);
+ }
+}
+
+void TestOdbc::test_select()
+{
+ try
+ {
+ m_db.query("select * from test where id=?", id,
+ [](const qtl::indicator<uint32_t>& id, const std::string& name, const qtl::odbc::timestamp& create_time) {
+ printf("ID=\"%d\", Name=\"%s\"\n",
+ id.data, name.data());
+ });
+
+ m_db.query("select * from test where id=?", id,
+ [](const TestOdbcRecord& record) {
+ printf("ID=\"%d\", Name=\"%s\"\n",
+ record.id, record.name);
+ });
+
+ m_db.query("select * from test where id=?", id,
+ &TestOdbcRecord::print);
+ }
+ catch(qtl::odbc::error& e)
+ {
+ ASSERT_EXCEPTION(e);
+ }
+}
+
+void TestOdbc::test_insert()
+{
+ try
+ {
+ m_db.execute("insert into test(Name, CreateTime) values(?, CURRENT_TIMESTAMP)",
+ "test_user");
+ m_db.query_first("SELECT @@IDENTITY", id);
+ }
+ catch(qtl::odbc::error& e)
+ {
+ ASSERT_EXCEPTION(e);
+ }
+ TEST_ASSERT_MSG(id>0, "insert failture.");
+}
+
+void TestOdbc::test_insert2()
+{
+ try
+ {
+ uint64_t affected=0;
+ qtl::odbc::statement stmt=m_db.open_command("insert into test(Name, CreateTime) values(?, CURRENT_TIMESTAMP)");
+ qtl::execute(stmt, &affected, "second_user", "third_user");
+ TEST_ASSERT_MSG(affected==2, "Cannot insert 2 records to table test.");
+ }
+ catch(qtl::odbc::error& e)
+ {
+ ASSERT_EXCEPTION(e);
+ }
+}
+
+void TestOdbc::test_update()
+{
+ try
+ {
+ m_db.execute_direct("update test set Name=? WHERE ID=?", NULL,
+ "other_user", id);
+ }
+ catch(qtl::odbc::error& e)
+ {
+ ASSERT_EXCEPTION(e);
+ }
+ TEST_ASSERT_MSG(id>0, "insert failture.");
+}
+
+void TestOdbc::test_clear()
+{
+ try
+ {
+ m_db.simple_execute("delete from test");
+ }
+ catch(qtl::odbc::error& e)
+ {
+ ASSERT_EXCEPTION(e);
+ }
+}
+
+void TestOdbc::test_iterator()
+{
+ try
+ {
+ cout<<"after insert all:"<<endl;
+ for(auto& record : m_db.result<TestOdbcRecord>("select ID, Name, CreateTime from test"))
+ {
+ printf("ID=\"%d\", Name=\"%s\"\n",
+ record.id, record.name);
+ }
+ }
+ catch(qtl::odbc::error& e)
+ {
+ ASSERT_EXCEPTION(e);
+ }
+}
+
+void TestOdbc::test_insert_blob()
+{
+ try
+ {
+#ifdef _WIN32
+ const char filename[]="C:\\windows\\explorer.exe";
+#else
+ const char filename[]="/bin/sh";
+#endif //_WIN32
+ fstream fs(filename, ios::binary|ios::in);
+ TEST_ASSERT_MSG(fs, "Cannot open test file.");
+ unsigned char md5[16]={0};
+ char md5_hex[33]={0};
+ get_md5(fs, md5);
+ cout<<"MD5 of file "<<filename<<": ";
+ print_hex(md5, sizeof(md5));
+ cout<<endl;
+
+ m_db.simple_execute("DELETE FROM test_blob");
+
+ fs.clear();
+ fs.seekg(0, ios::beg);
+ m_db.execute("INSERT INTO test_blob (Filename, [Content], MD5) values(?, ?, ?)",
+ make_tuple(filename, ref((istream&)fs), qtl::const_blob_data(md5, sizeof(md5))));
+ m_db.query_first("SELECT @@IDENTITY", id);
+ }
+ catch(qtl::odbc::error& e)
+ {
+ ASSERT_EXCEPTION(e);
+ }
+}
+
+void TestOdbc::test_select_blob()
+{
+ try
+ {
+ const char dest_file[]="explorer.exe";
+ fstream fs(dest_file, ios::binary|ios::in|ios::out|ios::trunc);
+ TEST_ASSERT_MSG(fs, "Cannot open test file.");
+ unsigned char md5[16]={0};
+ char md5_hex[33]={0};
+
+ fs.seekg(ios::beg);
+ std::string source_file;
+ m_db.query_first("SELECT Filename, MD5 , [Content]FROM test_blob WHERE id=?", make_tuple(id),
+ make_tuple(ref(source_file), qtl::blob_data(md5, sizeof(md5)), ref((ostream&)fs)));
+ fs.flush();
+ cout<<"MD5 of file "<<source_file<<" stored in database: ";
+ print_hex((const unsigned char*)md5, sizeof(md5));
+ fs.clear();
+ fs.seekg(0, ios::beg);
+ get_md5(fs, md5);
+ cout<<endl<<"MD5 of file "<<dest_file<<": ";
+ print_hex(md5, sizeof(md5));
+ cout<<endl;
+ }
+ catch(qtl::odbc::error& e)
+ {
+ ASSERT_EXCEPTION(e);
+ }
+}
+
+void TestOdbc::get_md5(std::istream& is, unsigned char* result)
+{
+ std::array<char, 64*1024> buffer;
+ MD5_CTX context;
+ MD5Init(&context);
+ while(!is.eof())
+ {
+ is.read(&buffer.front(), buffer.size());
+ MD5Update(&context, (unsigned char*)buffer.data(), (unsigned int)is.gcount());
+ }
+ MD5Final(result, &context);
+}
+
+void TestOdbc::print_hex(const unsigned char* data, size_t n)
+{
+ cout<<hex;
+ for(size_t i=0; i!=n; i++)
+ cout<<(data[i]&0xFF);
+}
+
+int main(int argc, char* argv[])
+{
+ Test::TextOutput output(Test::TextOutput::Verbose);
+
+ cout<<endl<<"Testing MSSQL(ODBC):"<<endl;
+
+ try
+ {
+ TestOdbc test_odbc;
+ test_odbc.run(output);
+ }
+ catch(qtl::odbc::error& e)
+ {
+ cerr<<e.what()<<endl;
+ }
+ return 0;
+}
+
diff --git a/test/TestOdbc.h b/test/TestOdbc.h
new file mode 100644
index 0000000..ac5cf80
--- /dev/null
+++ b/test/TestOdbc.h
@@ -0,0 +1,39 @@
+#ifndef _TEST_ODBC_H_
+#define _TEST_ODBC_H_
+
+#include "TestSuite.h"
+#include "../include/qtl_odbc.hpp"
+
+namespace qtl
+{
+ namespace odbc
+ {
+ class database;
+ }
+}
+
+class TestOdbc : public TestSuite
+{
+public:
+ TestOdbc();
+
+private:
+ void test_dual();
+ void test_clear();
+ void test_insert();
+ void test_select();
+ void test_update();
+ void test_insert2();
+ void test_iterator();
+ void test_insert_blob();
+ void test_select_blob();
+
+private:
+ uint64_t id;
+ qtl::odbc::environment m_env;
+ qtl::odbc::database m_db;
+ void get_md5(std::istream& is, unsigned char* result);
+ static void print_hex(const unsigned char* data, size_t n);
+};
+
+#endif //_TEST_ODBC_H_
diff --git a/test/stdafx.h b/test/stdafx.h
index 48ad6c9..0f276fa 100644
--- a/test/stdafx.h
+++ b/test/stdafx.h
@@ -10,8 +10,8 @@
#define NOMINMAX 1
#endif //NOMINMAX
-#ifndef _WIN32_WINNT // ����ʹ���ض��� Windows XP ����߰汾�Ĺ��ܡ�
-#define _WIN32_WINNT 0x0501 // ����ֵ����Ϊ��Ӧ��ֵ���������� Windows �������汾��
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501
#endif
#include <WinSock2.h>
diff --git a/test/test_odbc.mak b/test/test_odbc.mak
new file mode 100644
index 0000000..b754a0a
--- /dev/null
+++ b/test/test_odbc.mak
@@ -0,0 +1,25 @@
+TARGET=test_odbc
+CC=g++
+PCH_HEADER=stdafx.h
+PCH=stdafx.h.gch
+OBJ=TestOdbc.o md5.o
+CFLAGS=-g -D_DEBUG -O2 -I/usr/local/include
+CXXFLAGS=-I../include -std=c++11
+LDFLAGS= -L/usr/local/lib -lcpptest -lodbc
+
+all : $(TARGET)
+
+$(PCH) : $(PCH_HEADER)
+ $(CC) $(CFLAGS) $(CXXFLAGS) -x c++-header -o $@ $<
+
+TestOdbc.o : TestOdbc.cpp TestOdbc.h $(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 c297dbf..f69c59d 100644
--- a/test/vs/test.sln
+++ b/test/vs/test.sln
@@ -5,6 +5,8 @@
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_sqlite", "test_sqlite\test_sqlite.vcproj", "{8C793DE3-283E-460C-A876-29455F68CB45}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_odbc", "test_odbc\test_odbc.vcproj", "{86C60EAD-F5B4-482B-9CC2-92A77323E759}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -19,6 +21,10 @@
{8C793DE3-283E-460C-A876-29455F68CB45}.Debug|Win32.Build.0 = Debug|Win32
{8C793DE3-283E-460C-A876-29455F68CB45}.Release|Win32.ActiveCfg = Release|Win32
{8C793DE3-283E-460C-A876-29455F68CB45}.Release|Win32.Build.0 = Release|Win32
+ {86C60EAD-F5B4-482B-9CC2-92A77323E759}.Debug|Win32.ActiveCfg = Debug|Win32
+ {86C60EAD-F5B4-482B-9CC2-92A77323E759}.Debug|Win32.Build.0 = Debug|Win32
+ {86C60EAD-F5B4-482B-9CC2-92A77323E759}.Release|Win32.ActiveCfg = Release|Win32
+ {86C60EAD-F5B4-482B-9CC2-92A77323E759}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/test/vs/test_odbc/test_odbc.vcproj b/test/vs/test_odbc/test_odbc.vcproj
new file mode 100644
index 0000000..314e9ba
--- /dev/null
+++ b/test/vs/test_odbc/test_odbc.vcproj
@@ -0,0 +1,259 @@
+<?xml version="1.0" encoding="gb2312"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="test_odbc"
+ ProjectGUID="{86C60EAD-F5B4-482B-9CC2-92A77323E759}"
+ RootNamespace="test_odbc"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="0"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies=" cpptest_debug.lib"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ 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="0"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies=" cpptest.lib"
+ LinkIncremental="1"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ 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="..\..\md5.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\TestOdbc.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="ͷ�ļ�"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\md5.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\TestOdbc.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>
+ <File
+ RelativePath=".\ReadMe.txt"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
--
Gitblit v1.9.3