znone
2017-03-06 e51bed25201e89b2fed36e45e1642812d88f6398
增加对ODBC的支持。
6 files added
9 files modified
1861 ■■■■■ changed files
README.md 61 ●●●●● patch | view | raw | blame | history
include/qtl_common.hpp 6 ●●●●● patch | view | raw | blame | history
include/qtl_database_pool.hpp 7 ●●●●● patch | view | raw | blame | history
include/qtl_mysql.hpp 5 ●●●●● patch | view | raw | blame | history
include/qtl_mysql_pool.hpp 12 ●●●● patch | view | raw | blame | history
include/qtl_odbc.hpp 1047 ●●●●● patch | view | raw | blame | history
include/qtl_odbc_pool.hpp 46 ●●●●● patch | view | raw | blame | history
include/qtl_sqlite.hpp 62 ●●●● patch | view | raw | blame | history
include/qtl_sqlite_pool.hpp 11 ●●●●● patch | view | raw | blame | history
test/TestOdbc.cpp 271 ●●●●● patch | view | raw | blame | history
test/TestOdbc.h 39 ●●●●● patch | view | raw | blame | history
test/stdafx.h 4 ●●●● patch | view | raw | blame | history
test/test_odbc.mak 25 ●●●●● patch | view | raw | blame | history
test/vs/test.sln 6 ●●●●● patch | view | raw | blame | history
test/vs/test_odbc/test_odbc.vcproj 259 ●●●●● patch | view | raw | blame | history
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&lt;char, N&gt;<br>std::string |
| WCHAR<br>WVARCHAR | wchar_t[N]<br>std::array&lt;wchar_t, N&gt;<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")。
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>
{
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);
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';
            }
        };
    }
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:
include/qtl_odbc.hpp
New file
@@ -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_
include/qtl_odbc_pool.hpp
New file
@@ -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_
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));
    }
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:
test/TestOdbc.cpp
New file
@@ -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;
}
test/TestOdbc.h
New file
@@ -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_
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>
test/test_odbc.mak
New file
@@ -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
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
test/vs/test_odbc/test_odbc.vcproj
New file
@@ -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>