1. 允许绑定字段到std::optional和std::any
2. 增加函数bind_fields可以一次绑定到多个字段
3. 查询函数返回数据库对象自身,以支持链式调用
11 files modified
1 files deleted
887 ■■■■■ changed files
.gitignore 29 ●●●●● patch | view | raw | blame | history
README.md 15 ●●●● patch | view | raw | blame | history
include/apply_tuple.h 18 ●●●● patch | view | raw | blame | history
include/qtl_common.hpp 239 ●●●●● patch | view | raw | blame | history
include/qtl_mysql.hpp 237 ●●●● patch | view | raw | blame | history
include/qtl_odbc.hpp 197 ●●●● patch | view | raw | blame | history
include/qtl_sqlite.hpp 42 ●●●●● patch | view | raw | blame | history
test/TestMysql.cpp 38 ●●●● patch | view | raw | blame | history
test/TestMysql.h 1 ●●●● patch | view | raw | blame | history
test/TestOdbc.cpp 8 ●●●● patch | view | raw | blame | history
test/TestSqlite.cpp 60 ●●●●● patch | view | raw | blame | history
test/TestSqlite.h 3 ●●●● patch | view | raw | blame | history
.gitignore
File was deleted
README.md
@@ -72,9 +72,7 @@
    template<>
    inline void bind_record<qtl::mysql::statement, TestMysqlRecord>(qtl::mysql::statement& command, TestMysqlRecord&& v)
    {
        qtl::bind_field(command, 0, v.id);
        qtl::bind_field(command, 1, v.name);
        qtl::bind_field(command, 2, v.create_time);
        qtl::bind_fields(command, v.id, v.name, v.create_time);
    }
}
@@ -109,8 +107,11 @@
- is_null 字段是否为空
- length 数据的实际长度
- is_truncated 数据是否被截断
#### 9. std::optional和std::any
可以绑定字段到 C++17 中的 std::optional 和 std::any。当字段为null时,它们不包含任何内容,否则他们包含字段的值。
#### 9. 支持标准库以外的字符串类型
#### 10. 支持标准库以外的字符串类型
除了标准库提供的std::string,另外其他库也提供了自己的字符串类,比如QT的QString,MFC/ATL的CString等。qtl也可以将字符字段绑定到这些类型上。扩展方法是:
1. 为你的字符串类型,对 qtl::bind_string_helper 实现一个专门化。如果该字符串类型有符合标准库字符串语义的以下成员函数,可以跳过这一步:assign,clear,resize,data,size;
2. 为你的字符串类型,对 qtl::bind_field 实现一个专门化;
@@ -130,7 +131,7 @@
```
#### 10. 在不同查询中复用同一数据结构
#### 11. 在不同查询中复用同一数据结构
通常希望复用结构,将其绑定到多个不同的查询的结果集,这时候 qtl::bind_record就不够用了。需要利用 qtl::custom_bind 实现不同的绑定函数才能实现这一需求。有如下绑定函数:
```C++
@@ -151,7 +152,7 @@
```
qtl::bind_record不是唯一的方法。通过派生类也能实现类似的需求(qtl::record_with_tag)。
#### 11.处理返回多个结果集的查询
#### 12.处理返回多个结果集的查询
有些查询语句会返回多个结果集。使用函数query执行这些查询只能得到第一个结果集。要处理所有结果集需要使用query_multi或query_multi_with_params。query_multi不会为没有结果集的查询调用回调函数。例如:
```SQL
CREATE PROCEDURE test_proc()
@@ -172,7 +173,7 @@
```
#### 12. 异步调用数据库
#### 13. 异步调用数据库
通过类async_connection可以异步调用数据库。所有的异步函数都需要提供一个回调函数接受操作完成后的结果。如果异步调用中发生错误,错误做为回调函数的参数返回给调用者。
```
include/apply_tuple.h
@@ -6,7 +6,15 @@
#include <type_traits>
#include <utility>
#if __cplusplus < 201703L
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
template <class F, class Tuple>
inline constexpr decltype(auto) apply_tuple(F&& f, Tuple&& t)
{
    return std::apply(std::forward<F>(f), std::forward<Tuple>(t));
}
#else
namespace detail
{
@@ -46,14 +54,6 @@
    return detail::apply< std::tuple_size<
        typename std::decay<T>::type
    >::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t));
}
#else
template <class F, class Tuple>
inline constexpr decltype(auto) apply_tuple(F&& f, Tuple&& t)
{
    return std::apply(std;:forward<F>(f), std::forward<Tuple>(t));
}
#endif // C++17
include/qtl_common.hpp
@@ -1,11 +1,7 @@
#ifndef _QTL_COMMON_H_
#define _QTL_COMMON_H_
#if defined(_MSC_VER)
#if _MSC_VER<1800
#error QTL need C++11 compiler
#endif //MSC
#elif __cplusplus<201103L
#if __cplusplus<201103L && _MSC_VER<1800
#error QTL need C++11 compiler
#endif //C++11
@@ -24,6 +20,12 @@
#include <vector>
#include <functional>
#include "apply_tuple.h"
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
#define _QTL_ENABLE_CPP17
#include <optional>
#include <any>
#endif // C++17
namespace qtl
{
@@ -145,6 +147,19 @@
    command.bind_param(index, param);
}
#ifdef _QTL_ENABLE_CPP17
template<typename Command, typename T>
inline void bind_param(Command& command, size_t index, const std::optional<T>& param)
{
    if(param)
        command.bind_param(index, *param);
    else
        command.bind_param(index, nullptr);
}
#endif // C++17
// The order of the overloaded functions 'bind_field' is very important
// The version with the most generic parameters is at the end
@@ -208,22 +223,6 @@
    bind_field(command, index, std::forward<T>(value));
}
template<typename FieldType, typename Command, typename BindType>
inline void bind_field(Command& command, size_t index, BindType& value)
{
    FieldType temp=FieldType();
    bind_field(command, index, temp);
    value=static_cast<BindType>(temp);
}
template<typename FieldType, typename Command, typename BindType, typename CastFun>
inline void bind_field(Command& command, size_t index, BindType& value, CastFun&& fun)
{
    FieldType temp=FieldType();
    bind_field(command, index, temp);
    fun(value, temp);
}
template<typename Command, typename T, typename=typename std::enable_if<!std::is_reference<T>::value>::type>
inline void bind_field(Command& command, const char* name, T&& value)
{
@@ -244,32 +243,14 @@
        bind_field(command, index, std::forward<T>(value));
}
template<typename FieldType, typename Command, typename BindType>
inline void bind_field(Command& command, const char* name, BindType& value)
{
    size_t index=command.find_field(name);
    if(index==-1)
        value=BindType();
    else
        bind_field<FieldType>(command, index, value);
}
template<typename FieldType, typename Command, typename BindType, typename CastFun>
inline void bind_field(Command& command, const char* name, BindType& value, CastFun&& fun)
{
    size_t index=command.find_field(name);
    if(index==-1)
        value=BindType();
    else
        bind_field<FieldType>(command, index, value, fun);
}
template<typename Command>
inline void bind_field(Command& command, const char* name, char* value, size_t length)
{
    size_t index=command.find_field(name);
    if(index==-1)
        value[0]='\0';
    if (index == -1)
    {
        if (length > 0) value[0] = '\0';
    }
    else
        command.bind_field(index, value, length);
}
@@ -278,8 +259,10 @@
inline void bind_field(Command& command, const char* name, wchar_t* value, size_t length)
{
    size_t index=command.find_field(name);
    if(index==-1)
        value[0]='\0';
    if (index == -1)
    {
        if (length > 0) value[0] = '\0';
    }
    else
        command.bind_field(index, value, length);
}
@@ -288,6 +271,47 @@
inline void bind_field(Command& command, const char* name, std::reference_wrapper<T>&& value)
{
    return bind_field(command, value.get());
}
#ifdef _QTL_ENABLE_CPP17
template<typename Command, typename T>
inline void bind_field(Command& command, size_t index, std::optional<T>& value)
{
    value.emplace();
    command.bind_field(index, std::forward<T>(value));
}
template<typename Command, typename T>
inline void bind_field(Command& command, const char* name, std::optional<T>& value)
{
    size_t index = command.find_field(name);
    if (index == -1)
        value.reset();
    else
        bind_field(command, index, value);
}
#endif // C++17
template<typename Command, typename... Fields>
inline size_t bind_fields(Command& command, Fields&&... fields)
{
    return bind_fields(command, (size_t)0, std::forward<Fields>(fields)...);
}
template<typename Command, typename T>
inline size_t bind_fields(Command& command, size_t index, T&& value)
{
    bind_field(command, index, std::forward<T>(value));
    return index+1;
}
template<typename Command, typename T, typename... Other>
inline size_t bind_fields(Command& command, size_t start, T&& value, Other&&... other)
{
    bind_field(command, start, std::forward<T>(value));
    return bind_fields(command, start+1, std::forward<Other>(other)...);
}
namespace detail
@@ -835,39 +859,40 @@
{
public:
    template<typename Params>
    void execute(const char* query_text, size_t text_length, const Params& params, uint64_t* affected=NULL)
    base_database& execute(const char* query_text, size_t text_length, const Params& params, uint64_t* affected=NULL)
    {
        T* pThis=static_cast<T*>(this);
        Command command=pThis->open_command(query_text, text_length);
        command.execute(params);
        if(affected) *affected=command.affetced_rows();
        command.close();
        return *this;
    }
    template<typename Params>
    void execute(const char* query_text, const Params& params, uint64_t* affected=NULL)
    base_database& execute(const char* query_text, const Params& params, uint64_t* affected=NULL)
    {
        return execute(query_text, strlen(query_text), params, affected);
    }
    template<typename Params>
    void execute(const std::string& query_text, const Params& params, uint64_t* affected=NULL)
    base_database& execute(const std::string& query_text, const Params& params, uint64_t* affected=NULL)
    {
        return execute(query_text.data(), query_text.length(), params, affected);
    }
    template<typename... Params>
    void execute_direct(const char* query_text, size_t text_length, uint64_t* affected, const Params&... params)
    base_database& execute_direct(const char* query_text, size_t text_length, uint64_t* affected, const Params&... params)
    {
        execute(query_text, text_length, std::forward_as_tuple(params...), affected);
        return execute(query_text, text_length, std::forward_as_tuple(params...), affected);
    }
    template<typename... Params>
    void execute_direct(const char* query_text, uint64_t* affected, const Params&... params)
    base_database& execute_direct(const char* query_text, uint64_t* affected, const Params&... params)
    {
        execute(query_text, std::forward_as_tuple(params...), affected);
        return execute(query_text, std::forward_as_tuple(params...), affected);
    }
    template<typename... Params>
    void execute_direct(const std::string& query_text, uint64_t* affected, const Params&... params)
    base_database& execute_direct(const std::string& query_text, uint64_t* affected, const Params&... params)
    {
        execute(query_text, std::forward_as_tuple(params...), affected);
        return execute(query_text, std::forward_as_tuple(params...), affected);
    }
    template<typename Params>
@@ -947,7 +972,7 @@
    }
    template<typename Params, typename Values, typename ValueProc>
    void query_explicit(const char* query_text, size_t text_length, const Params& params, Values&& values, ValueProc&& proc)
    base_database& query_explicit(const char* query_text, size_t text_length, const Params& params, Values&& values, ValueProc&& proc)
    {
        T* pThis=static_cast<T*>(this);
        Command command=pThis->open_command(query_text, text_length);
@@ -957,152 +982,154 @@
            if(!detail::apply(std::forward<ValueProc>(proc), std::forward<Values>(values))) break;
        }
        command.close();
        return *this;
    }
    template<typename Params, typename Values, typename ValueProc>
    void query_explicit(const char* query_text, const Params& params, Values&& values, ValueProc&& proc)
    base_database& query_explicit(const char* query_text, const Params& params, Values&& values, ValueProc&& proc)
    {
        query_explicit(query_text, strlen(query_text), params, std::forward<Values>(values), std::forward<ValueProc>(proc));
        return query_explicit(query_text, strlen(query_text), params, std::forward<Values>(values), std::forward<ValueProc>(proc));
    }
    template<typename Params, typename Values, typename ValueProc>
    void query_explicit(const std::string& query_text, const Params& params, Values&& values, ValueProc&& proc)
    base_database& query_explicit(const std::string& query_text, const Params& params, Values&& values, ValueProc&& proc)
    {
        query_explicit(query_text.data(), query_text.size(), params, std::forward<Values>(values), std::forward<ValueProc>(proc));
        return query_explicit(query_text.data(), query_text.size(), params, std::forward<Values>(values), std::forward<ValueProc>(proc));
    }
    template<typename Values, typename ValueProc>
    void query_explicit(const char* query_text, size_t text_length, Values&& values, ValueProc&& proc)
    base_database& query_explicit(const char* query_text, size_t text_length, Values&& values, ValueProc&& proc)
    {
        query_explicit(query_text, text_length, std::make_tuple(), std::forward<Values>(values), std::forward<ValueProc>(proc));
        return query_explicit(query_text, text_length, std::make_tuple(), std::forward<Values>(values), std::forward<ValueProc>(proc));
    }
    template<typename Values, typename ValueProc>
    void query_explicit(const char* query_text, Values&& values, ValueProc&& proc)
    base_database& query_explicit(const char* query_text, Values&& values, ValueProc&& proc)
    {
        query_explicit(query_text, strlen(query_text), std::make_tuple(), std::forward<Values>(values), std::forward<ValueProc>(proc));
        return query_explicit(query_text, strlen(query_text), std::make_tuple(), std::forward<Values>(values), std::forward<ValueProc>(proc));
    }
    template<typename Values, typename ValueProc>
    void query_explicit(const std::string& query_text, Values&& values, ValueProc&& proc)
    base_database& query_explicit(const std::string& query_text, Values&& values, ValueProc&& proc)
    {
        query_explicit(query_text, std::make_tuple(), std::forward<Values>(values), std::forward<ValueProc>(proc));
        return query_explicit(query_text, std::make_tuple(), std::forward<Values>(values), std::forward<ValueProc>(proc));
    }
    template<typename Params, typename ValueProc>
    void query(const char* query_text, size_t text_length, const Params& params, ValueProc&& proc)
    base_database& query(const char* query_text, size_t text_length, const Params& params, ValueProc&& proc)
    {
        query_explicit(query_text, text_length, params, detail::make_values(proc),  std::forward<ValueProc>(proc));
        return query_explicit(query_text, text_length, params, detail::make_values(proc),  std::forward<ValueProc>(proc));
    }
    template<typename Params, typename ValueProc>
    void query(const char* query_text, const Params& params, ValueProc&& proc)
    base_database& query(const char* query_text, const Params& params, ValueProc&& proc)
    {
        query_explicit(query_text, params, detail::make_values(proc),  std::forward<ValueProc>(proc));
        return query_explicit(query_text, params, detail::make_values(proc),  std::forward<ValueProc>(proc));
    }
    template<typename Params, typename ValueProc>
    void query(const std::string& query_text, const Params& params, ValueProc&& proc)
    base_database& query(const std::string& query_text, const Params& params, ValueProc&& proc)
    {
        query_explicit(query_text, params, detail::make_values(proc),  std::forward<ValueProc>(proc));
        return query_explicit(query_text, params, detail::make_values(proc),  std::forward<ValueProc>(proc));
    }
    template<typename ValueProc>
    void query(const char* query_text, size_t text_length, ValueProc&& proc)
    base_database& query(const char* query_text, size_t text_length, ValueProc&& proc)
    {
        query_explicit(query_text, text_length, detail::make_values(proc),  std::forward<ValueProc>(proc));
        return query_explicit(query_text, text_length, detail::make_values(proc),  std::forward<ValueProc>(proc));
    }
    template<typename ValueProc>
    void query(const char* query_text, ValueProc&& proc)
    base_database& query(const char* query_text, ValueProc&& proc)
    {
        query_explicit(query_text, detail::make_values(proc),  std::forward<ValueProc>(proc));
        return query_explicit(query_text, detail::make_values(proc),  std::forward<ValueProc>(proc));
    }
    template<typename ValueProc>
    void query(const std::string& query_text, ValueProc&& proc)
    base_database& query(const std::string& query_text, ValueProc&& proc)
    {
        query_explicit(query_text, detail::make_values(proc), std::forward<ValueProc>(proc));
        return query_explicit(query_text, detail::make_values(proc), std::forward<ValueProc>(proc));
    }
    template<typename Params, typename... ValueProc>
    void query_multi_with_params(const char* query_text, size_t text_length, const Params& params, ValueProc&&... proc)
    base_database& query_multi_with_params(const char* query_text, size_t text_length, const Params& params, ValueProc&&... proc)
    {
        T* pThis=static_cast<T*>(this);
        Command command=pThis->open_command(query_text, text_length);
        command.execute(params);
        detail::fetch_command(command, std::forward<ValueProc>(proc)...);
        command.close();
        return *this;
    }
    template<typename Params, typename... ValueProc>
    void query_multi_with_params(const char* query_text, const Params& params, ValueProc&&... proc)
    base_database& query_multi_with_params(const char* query_text, const Params& params, ValueProc&&... proc)
    {
        query_multi_with_params(query_text, strlen(query_text), params, std::forward<ValueProc>(proc)...);
        return query_multi_with_params(query_text, strlen(query_text), params, std::forward<ValueProc>(proc)...);
    }
    template<typename Params, typename... ValueProc>
    void query_multi_with_params(const std::string& query_text, const Params& params, ValueProc&&... proc)
    base_database& query_multi_with_params(const std::string& query_text, const Params& params, ValueProc&&... proc)
    {
        query_multi_with_params(query_text.data(), query_text.size(), params, std::forward<ValueProc>(proc)...);
        return query_multi_with_params(query_text.data(), query_text.size(), params, std::forward<ValueProc>(proc)...);
    }
    template<typename... ValueProc>
    void query_multi(const char* query_text, size_t text_length, ValueProc&&... proc)
    base_database& query_multi(const char* query_text, size_t text_length, ValueProc&&... proc)
    {
        query_multi_with_params<std::tuple<>, ValueProc...>(query_text, text_length, std::make_tuple(), std::forward<ValueProc>(proc)...);
        return query_multi_with_params<std::tuple<>, ValueProc...>(query_text, text_length, std::make_tuple(), std::forward<ValueProc>(proc)...);
    }
    template<typename... ValueProc>
    void query_multi(const char* query_text, ValueProc&&... proc)
    base_database& query_multi(const char* query_text, ValueProc&&... proc)
    {
        query_multi_with_params<std::tuple<>, ValueProc...>(query_text, strlen(query_text), std::make_tuple(), std::forward<ValueProc>(proc)...);
        return query_multi_with_params<std::tuple<>, ValueProc...>(query_text, strlen(query_text), std::make_tuple(), std::forward<ValueProc>(proc)...);
    }
    template<typename... ValueProc>
    void query_multi(const std::string& query_text, ValueProc&&... proc)
    base_database& query_multi(const std::string& query_text, ValueProc&&... proc)
    {
        query_multi_with_params<std::tuple<>, ValueProc...>(query_text.data(), query_text.size(), std::make_tuple(), std::forward<ValueProc>(proc)...);
        return query_multi_with_params<std::tuple<>, ValueProc...>(query_text.data(), query_text.size(), std::make_tuple(), std::forward<ValueProc>(proc)...);
    }
    template<typename Params, typename Values>
    void query_first(const char* query_text, size_t text_length, const Params& params, Values&& values)
    base_database& query_first(const char* query_text, size_t text_length, const Params& params, Values&& values)
    {
        query_explicit(query_text, text_length, params, std::forward<Values>(values), first_record());
        return query_explicit(query_text, text_length, params, std::forward<Values>(values), first_record());
    }
    template<typename Params, typename Values>
    void query_first(const char* query_text, const Params& params, Values&& values)
    base_database& query_first(const char* query_text, const Params& params, Values&& values)
    {
        query_explicit(query_text, strlen(query_text), params, std::forward<Values>(values), first_record());
        return query_explicit(query_text, strlen(query_text), params, std::forward<Values>(values), first_record());
    }
    template<typename Params, typename Values>
    void query_first(const std::string& query_text, const Params& params, Values&& values)
    base_database& query_first(const std::string& query_text, const Params& params, Values&& values)
    {
        query_explicit(query_text, params, values, first_record());
        return query_explicit(query_text, params, values, first_record());
    }
    template<typename Values>
    void query_first(const char* query_text, size_t text_length, Values&& values)
    base_database& query_first(const char* query_text, size_t text_length, Values&& values)
    {
        query_explicit(query_text, text_length, std::make_tuple(), std::forward<Values>(values), first_record());
        return query_explicit(query_text, text_length, std::make_tuple(), std::forward<Values>(values), first_record());
    }
    template<typename Values>
    void query_first(const char* query_text, Values&& values)
    base_database& query_first(const char* query_text, Values&& values)
    {
        query_explicit(query_text, strlen(query_text), std::make_tuple(), std::forward<Values>(values), first_record());
        return query_explicit(query_text, strlen(query_text), std::make_tuple(), std::forward<Values>(values), first_record());
    }
    template<typename Values>
    void query_first(const std::string& query_text, Values&& values)
    base_database& query_first(const std::string& query_text, Values&& values)
    {
        query_explicit(query_text, std::make_tuple(), std::forward<Values>(values), first_record());
        return query_explicit(query_text, std::make_tuple(), std::forward<Values>(values), first_record());
    }
    template<typename... Values>
    void query_first_direct(const char* query_text, size_t text_length, Values&... values)
    base_database& query_first_direct(const char* query_text, size_t text_length, Values&... values)
    {
        query_first(query_text, text_length, std::tie(values...));
        return query_first(query_text, text_length, std::tie(values...));
    }
    template<typename... Values>
    void query_first_direct(const char* query_text, Values&... values)
    base_database& query_first_direct(const char* query_text, Values&... values)
    {
        query_first(query_text, std::tie(values...));
        return query_first(query_text, std::tie(values...));
    }
    template<typename... Values>
    void query_first_direct(const std::string& query_text, Values&... values)
    base_database& query_first_direct(const std::string& query_text, Values&... values)
    {
        query_first(query_text, std::tie(values...));
        return query_first(query_text, std::tie(values...));
    }
protected:
include/qtl_mysql.hpp
@@ -10,6 +10,7 @@
#include <string>
#include <vector>
#include <array>
#include <utility>
#include <functional>
#include <algorithm>
#include <system_error>
@@ -83,8 +84,9 @@
    void bind(bool& v)
    {
        init();
        buffer_type = MYSQL_TYPE_TINY;
        buffer_type = MYSQL_TYPE_BIT;
        buffer = &v;
        buffer_length = 1;
    }
    void bind(int8_t& v)
    {
@@ -275,6 +277,71 @@
    }
};
struct time : public MYSQL_TIME
{
    time()
    {
        memset(this, 0, sizeof(MYSQL_TIME));
        time_type = MYSQL_TIMESTAMP_NONE;
    }
    time(const struct tm& tm)
    {
        memset(this, 0, sizeof(MYSQL_TIME));
        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;
        time_type = MYSQL_TIMESTAMP_DATETIME;
    }
    time(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)time(tm);
    }
    time(const time& src)
    {
        memcpy(this, &src, sizeof(MYSQL_TIME));
    }
    time& operator=(const time& src)
    {
        if (this != &src)
            memcpy(this, &src, sizeof(MYSQL_TIME));
        return *this;
    }
    static time now()
    {
        time_t value;
        ::time(&value);
        return time(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);
    }
};
class base_statement
{
protected:
@@ -409,7 +476,8 @@
        if (m_result)
        {
            bind(m_binders[index], std::forward<Type>(value));
            m_binderAddins[index].m_after_fetch = if_null<typename std::remove_reference<Type>::type>(value);
            m_binderAddins[index].m_after_fetch =
                if_null<typename std::remove_reference<Type>::type>(value);
        }
    }
@@ -518,6 +586,104 @@
        }
    }
#ifdef _QTL_ENABLE_CPP17
    template<typename T>
    inline void bind_field(size_t index, std::optional<T>&& value)
    {
        if (m_result)
        {
            qtl::bind_field(*this, index, *value);
            binder_addin& addin = m_binderAddins[index];
            auto fetch_fun = addin.m_after_fetch;
            addin.m_after_fetch = [&addin, fetch_fun, &value](const binder& b) {
                if (fetch_fun) fetch_fun(b);
                if (*b.is_null) value.reset();
            };
        }
    }
    inline void bind_field(size_t index, std::any&& value)
    {
        if (m_result)
        {
            MYSQL_FIELD* field = mysql_fetch_field_direct(m_result, (unsigned int)index);
            if (field == nullptr) throw_exception();
            switch (field->type)
            {
            case MYSQL_TYPE_NULL:
                value.reset();
                break;
            case MYSQL_TYPE_BIT:
                value.emplace<bool>();
                bind_field(index, std::any_cast<bool&>(value));
                break;
            case MYSQL_TYPE_TINY:
                value.emplace<int8_t>();
                bind_field(index, std::any_cast<int8_t&>(value));
                break;
            case MYSQL_TYPE_SHORT:
                value.emplace<int16_t>();
                bind_field(index, std::any_cast<int16_t&>(value));
                break;
            case MYSQL_TYPE_LONG:
                value.emplace<int32_t>();
                bind_field(index, std::any_cast<int32_t&>(value));
                break;
            case MYSQL_TYPE_LONGLONG:
                value.emplace<int64_t>();
                bind_field(index, std::any_cast<int64_t&>(value));
                break;
            case MYSQL_TYPE_FLOAT:
                value.emplace<float>();
                bind_field(index, std::any_cast<float&>(value));
                break;
            case MYSQL_TYPE_DOUBLE:
                value.emplace<double>();
                bind_field(index, std::any_cast<double&>(value));
                break;
            case MYSQL_TYPE_DATE:
            case MYSQL_TYPE_TIME:
            case MYSQL_TYPE_DATETIME:
            case MYSQL_TYPE_TIMESTAMP:
            case MYSQL_TYPE_TIMESTAMP2:
            case MYSQL_TYPE_DATETIME2:
            case MYSQL_TYPE_TIME2:
                value.emplace<qtl::mysql::time>();
                bind_field(index, std::any_cast<qtl::mysql::time&>(value));
                break;
            case MYSQL_TYPE_VARCHAR:
            case MYSQL_TYPE_VAR_STRING:
            case MYSQL_TYPE_STRING:
            case MYSQL_TYPE_ENUM:
            case MYSQL_TYPE_JSON:
            case MYSQL_TYPE_DECIMAL:
            case MYSQL_TYPE_NEWDECIMAL:
            case MYSQL_TYPE_GEOMETRY:
                value.emplace<std::string>();
                bind_field(index, qtl::bind_string(std::any_cast<std::string&>(value)));
                break;
            case MYSQL_TYPE_TINY_BLOB:
            case MYSQL_TYPE_MEDIUM_BLOB:
            case MYSQL_TYPE_BLOB:
            case MYSQL_TYPE_LONG_BLOB:
                value.emplace<blobbuf>();
                bind_field(index, std::forward<blobbuf>(std::any_cast<blobbuf&>(value)));
                break;
            default:
                throw mysql::error(CR_UNSUPPORTED_PARAM_TYPE, "Unsupported field type");
            }
            binder_addin& addin = m_binderAddins[index];
            auto fetch_fun = addin.m_after_fetch;
            addin.m_after_fetch = [&addin, fetch_fun, &value](const binder& b) {
                if (fetch_fun) fetch_fun(b);
                if (*b.is_null) value.reset();
            };
        }
    }
#endif // C++17
    void close()
    {
        if (m_result)
@@ -571,7 +737,7 @@
    template<typename Value>
    struct if_null
    {
        if_null(Value& value, Value&& def=Value()) : m_value(value), m_def(std::move(def)) { }
        explicit if_null(Value& value, Value&& def=Value()) : m_value(value), m_def(std::move(def)) { }
        void operator()(const binder& b)
        {
            if(*b.is_null) m_value=m_def;
@@ -1000,71 +1166,6 @@
#endif //MariaDB
};
struct time : public MYSQL_TIME
{
    time()
    {
        memset(this, 0, sizeof(MYSQL_TIME));
        time_type=MYSQL_TIMESTAMP_NONE;
    }
    time(const struct tm& tm)
    {
        memset(this, 0, sizeof(MYSQL_TIME));
        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;
        time_type=MYSQL_TIMESTAMP_DATETIME;
    }
    time(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)time(tm);
    }
    time(const time& src)
    {
        memcpy(this, &src, sizeof(MYSQL_TIME));
    }
    time& operator=(const time& src)
    {
        if(this!=&src)
            memcpy(this, &src, sizeof(MYSQL_TIME));
        return *this;
    }
    static time now()
    {
        time_t value;
        ::time(&value);
        return time(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);
    }
};
#if MARIADB_VERSION_ID >= 100000
include/qtl_odbc.hpp
@@ -16,6 +16,11 @@
#include <sys/time.h>
#endif //_WIN32
#if (ODBCVER >= 0x0380) && (WIN32_WINNT >= 0x0602)
#define QTL_ODBC_ENABLE_ASYNC_MODE 1
#endif //ODBC 3.80 && Windows
#include "qtl_common.hpp"
namespace qtl
@@ -34,7 +39,7 @@
    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; }
    operator bool() const { 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;
@@ -149,6 +154,13 @@
        verify_error(SQLSetEnvAttr(m_handle, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0));
    }
    environment(environment&& src) : object(std::forward<environment>(src)) { }
    int32_t version() const
    {
        int32_t ver = 0;
        verify_error(SQLGetEnvAttr(m_handle, SQL_ATTR_ODBC_VERSION, &ver, sizeof(DWORD), NULL));
        return ver;
    }
};
class statement final : public object<SQL_HANDLE_STMT>
@@ -445,7 +457,7 @@
    void bind_field(SQLUSMALLINT index, qtl::bind_string_helper<T>&& v)
    {
        SQLLEN length=0;
        SQLColAttribute(m_handle, index+1, SQL_DESC_LENGTH, NULL, 0, NULL, &length);
        verify_error(SQLColAttribute(m_handle, index+1, SQL_DESC_LENGTH, NULL, 0, NULL, &length));
        typename qtl::bind_string_helper<T>::char_type* data=v.alloc(length);
        bind_field(index, data, length+1);
        m_params[index].m_after_fetch=[v](const param_data& p) mutable {
@@ -488,7 +500,7 @@
        };
    }
    void bind_field(size_t index, blobbuf&& value)
    void bind_field(SQLUSMALLINT index, blobbuf&& value)
    {
        m_params[index].m_data = nullptr;
        m_params[index].m_size = 0;
@@ -498,7 +510,7 @@
    }
    template<typename Type>
    void bind_field(size_t index, indicator<Type>&& value)
    void bind_field(SQLUSMALLINT index, indicator<Type>&& value)
    {
        qtl::bind_field(*this, index, value.data);
        param_data& param=m_params[index];
@@ -521,6 +533,145 @@
        };
    }
#ifdef _QTL_ENABLE_CPP17
    template<typename Type>
    void bind_field(SQLUSMALLINT index, std::optional<Type>&& value)
    {
        qtl::bind_field(*this, index, *value);
        param_data& param = m_params[index];
        auto fetch_fun = param.m_after_fetch;
        param.m_after_fetch = [fetch_fun, &value](const param_data& p) {
            if (fetch_fun) fetch_fun(p);
            if (p.m_indicator == SQL_NULL_DATA)
                value.reset();
        };
    }
    void bind_field(SQLUSMALLINT index, std::any&& value)
    {
        SQLLEN type = 0, isUnsigned=SQL_FALSE;
        verify_error(SQLColAttribute(m_handle, index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &type));
        verify_error(SQLColAttribute(m_handle, index + 1, SQL_DESC_UNSIGNED, NULL, 0, NULL, &isUnsigned));
        switch (type)
        {
        case SQL_BIT:
            value.emplace<bool>();
            bind_field(index, std::forward<bool>(std::any_cast<bool&>(value)));
            break;
        case SQL_TINYINT:
            if (isUnsigned)
            {
                value.emplace<uint8_t>();
                bind_field(index, std::forward<uint8_t>(std::any_cast<uint8_t&>(value)));
            }
            else
            {
                value.emplace<int8_t>();
                bind_field(index, std::forward<int8_t>(std::any_cast<int8_t&>(value)));
            }
            break;
        case SQL_SMALLINT:
            if (isUnsigned)
            {
                value.emplace<uint16_t>();
                bind_field(index, std::forward<uint16_t>(std::any_cast<uint16_t&>(value)));
            }
            else
            {
                value.emplace<int16_t>();
                bind_field(index, std::forward<int16_t>(std::any_cast<int16_t&>(value)));
            }
            break;
        case SQL_INTEGER:
            if (isUnsigned)
            {
                value.emplace<uint32_t>();
                bind_field(index, std::forward<uint32_t>(std::any_cast<uint32_t&>(value)));
            }
            else
            {
                value.emplace<int32_t>();
                bind_field(index, std::forward<int32_t>(std::any_cast<int32_t&>(value)));
            }
            break;
        case SQL_BIGINT:
            if (isUnsigned)
            {
                value.emplace<uint64_t>();
                bind_field(index, std::forward<uint64_t>(std::any_cast<uint64_t&>(value)));
            }
            else
            {
                value.emplace<int64_t>();
                bind_field(index, std::forward<int64_t>(std::any_cast<int64_t&>(value)));
            }
            break;
        case SQL_FLOAT:
            value.emplace<float>();
            bind_field(index, std::forward<float>(std::any_cast<float&>(value)));
            break;
        case SQL_DOUBLE:
            value.emplace<double>();
            bind_field(index, std::forward<double>(std::any_cast<double&>(value)));
            break;
        case SQL_NUMERIC:
            value.emplace<SQL_NUMERIC_STRUCT>();
            bind_field(index, std::forward<SQL_NUMERIC_STRUCT>(std::any_cast<SQL_NUMERIC_STRUCT&>(value)));
            break;
        case SQL_TIME:
            value.emplace<SQL_TIME_STRUCT>();
            bind_field(index, std::forward<SQL_TIME_STRUCT>(std::any_cast<SQL_TIME_STRUCT&>(value)));
            break;
        case SQL_DATE:
            value.emplace<SQL_DATE_STRUCT>();
            bind_field(index, std::forward<SQL_DATE_STRUCT>(std::any_cast<SQL_DATE_STRUCT&>(value)));
            break;
        case SQL_TIMESTAMP:
            value.emplace<SQL_TIMESTAMP_STRUCT>();
            bind_field(index, std::forward<SQL_TIMESTAMP_STRUCT>(std::any_cast<SQL_TIMESTAMP_STRUCT&>(value)));
            break;
        case SQL_INTERVAL_MONTH:
        case SQL_INTERVAL_YEAR:
        case SQL_INTERVAL_YEAR_TO_MONTH:
        case SQL_INTERVAL_DAY:
        case SQL_INTERVAL_HOUR:
        case SQL_INTERVAL_MINUTE:
        case SQL_INTERVAL_SECOND:
        case SQL_INTERVAL_DAY_TO_HOUR:
        case SQL_INTERVAL_DAY_TO_MINUTE:
        case SQL_INTERVAL_DAY_TO_SECOND:
        case SQL_INTERVAL_HOUR_TO_MINUTE:
        case SQL_INTERVAL_HOUR_TO_SECOND:
        case SQL_INTERVAL_MINUTE_TO_SECOND:
            value.emplace<SQL_INTERVAL_STRUCT>();
            bind_field(index, std::forward<SQL_INTERVAL_STRUCT>(std::any_cast<SQL_INTERVAL_STRUCT&>(value)));
            break;
        case SQL_CHAR:
            value.emplace<std::string>();
            bind_field(index, qtl::bind_string(std::any_cast<std::string&>(value)));
            break;
        case SQL_GUID:
            value.emplace<SQLGUID>();
            bind_field(index, std::forward<SQLGUID>(std::any_cast<SQLGUID&>(value)));
            break;
        case SQL_BINARY:
            value.emplace<blobbuf>();
            bind_field(index, std::forward<blobbuf>(std::any_cast<blobbuf&>(value)));
            break;
        default:
            throw odbc::error(*this, SQL_ERROR);
        }
        param_data& param = m_params[index];
        auto fetch_fun = param.m_after_fetch;
        param.m_after_fetch = [fetch_fun, &value](const param_data& p) {
            if (fetch_fun) fetch_fun(p);
            if (p.m_indicator == SQL_NULL_DATA)
                value.reset();
        };
    }
#endif // C++17
    template<typename Types>
    void execute(const Types& params)
    {
@@ -720,9 +871,9 @@
    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();
        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;
        m_opened = true;
    }
    void open(const char* server_name, const char* user_name, const char* password)
    {
@@ -732,21 +883,21 @@
    {
        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)
    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,
        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;
        m_opened = true;
    }
    void open(const std::string& input_text, SQLSMALLINT driver_completion=SQL_DRIVER_NOPROMPT, SQLHWND hwnd=NULL)
    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)
    void open(SQLHWND hwnd, SQLSMALLINT driver_completion = SQL_DRIVER_COMPLETE)
    {
        open("", SQL_NTS, driver_completion, hwnd);
    }
@@ -755,27 +906,27 @@
    template<typename InputPred>
    void open(const char* connection_text, size_t text_length, InputPred&& pred)
    {
        SQLSMALLINT length=0;
        SQLINTEGER ret=SQL_SUCCESS;
        SQLSMALLINT length = 0;
        SQLINTEGER ret = SQL_SUCCESS;
        std::string input_text;
        if(m_opened) close();
        if(text_length==SQL_NTS)
            input_text=connection_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,
        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))
            if (!pred(parameters))
                throw error(SQL_NEED_DATA, "User cancel operation.");
            input_text=create_connection_text(parameters);
            input_text = create_connection_text(parameters);
        }
        if(ret==SQL_ERROR || ret==SQL_SUCCESS_WITH_INFO)
        if (ret == SQL_ERROR || ret == SQL_SUCCESS_WITH_INFO)
            verify_error(ret);
        m_opened=true;
        m_opened = true;
    }
    template<typename InputPred>
    void open(const char* connection_text, InputPred&& pred)
@@ -863,7 +1014,7 @@
    {
        SQLINTEGER value;
        get_attribute(SQL_ATTR_CONNECTION_DEAD, value);
        return value==SQL_CD_FALSE;
        return value == SQL_CD_FALSE;
    }
    statement open_command(const char* query_text, size_t text_length)
include/qtl_sqlite.hpp
@@ -224,6 +224,48 @@
        bind_field(index, value.data(), value.size());
    }
#ifdef _QTL_ENABLE_CPP17
    template<typename T>
    inline void bind_field(size_t index, std::optional<T>&& value)
    {
        int type = get_column_type(index);
        if (type == SQLITE_NULL)
        {
            value.reset();
        }
        else
        {
            qtl::bind_field(*this, index, *value);
        }
    }
    inline void bind_field(size_t index, std::any&& value)
    {
        int type = get_column_type(index);
        switch(type)
        {
        case SQLITE_NULL:
            value.reset();
        case SQLITE_INTEGER:
            value = sqlite3_column_int64(m_stmt, index);
            break;
        case SQLITE_FLOAT:
            value = sqlite3_column_double(m_stmt, index);
            break;
        case SQLITE_TEXT:
            value.emplace<std::string_view>((const char*)sqlite3_column_text(m_stmt, index), sqlite3_column_bytes(m_stmt, index));
            break;
        case SQLITE_BLOB:
            value.emplace<const_blob_data>(sqlite3_column_text(m_stmt, index), sqlite3_column_bytes(m_stmt, index));
            break;
        default:
            throw sqlite::error(SQLITE_MISMATCH);
        }
    }
#endif // C++17
    size_t find_field(const char* name) const
    {
        size_t count=get_column_count();
test/TestMysql.cpp
@@ -2,6 +2,7 @@
#include "TestMysql.h"
#include <fstream>
#include <array>
#include <iomanip>
#include "md5.h"
#include "../include/qtl_mysql.hpp"
@@ -30,9 +31,7 @@
    template<>
    inline void bind_record<qtl::mysql::statement, TestMysqlRecord>(qtl::mysql::statement& command, TestMysqlRecord&& v)
    {
        qtl::bind_field(command, static_cast<size_t>(0), v.id);
        qtl::bind_field(command, 1, v.name);
        qtl::bind_field(command, 2, v.create_time);
        qtl::bind_fields(command, v.id, v.name, v.create_time);
    }
}
@@ -48,7 +47,8 @@
    TEST_ADD(TestMysql::test_iterator)
    TEST_ADD(TestMysql::test_insert_blob)
    TEST_ADD(TestMysql::test_select_blob)
    //TEST_ADD(TestMysql::test_insert_stream)
    TEST_ADD(TestMysql::test_any)
        //TEST_ADD(TestMysql::test_insert_stream)
    //TEST_ADD(TestMysql::test_fetch_stream)
}
@@ -331,6 +331,36 @@
    MD5Final(result, &context);
}
void TestMysql::test_any()
{
#ifdef _QTL_ENABLE_CPP17
    qtl::mysql::database db;
    connect(db);
    try
    {
        db.query("select 0, 'hello world', now() from dual",
            [](const std::any& i, const std::any& str, const std::any& now) {
                const qtl::mysql::time& time = std::any_cast<const qtl::mysql::time&>(now);
                struct tm tm;
                time.as_tm(tm);
                cout << "0=\"" << std::any_cast<int32_t>(i) << "\", 'hello world'=\"" <<
                    std::any_cast<const std::string&>(str) << "\", now=\"" <<
                    std::put_time(&tm, "%c") << "\" \n";
        });
    }
    catch (qtl::mysql::error& e)
    {
        ASSERT_EXCEPTION(e);
    }
    catch (std::bad_cast& e)
    {
        ASSERT_EXCEPTION(e);
    }
#endif
}
int main(int argc, char* argv[])
{
    Test::TextOutput output(Test::TextOutput::Verbose);
test/TestMysql.h
@@ -28,6 +28,7 @@
    void test_select_blob();
    void test_insert_stream();
    void test_fetch_stream();
    void test_any();
private:
    uint32_t id;
test/TestOdbc.cpp
@@ -29,16 +29,13 @@
    template<>
    inline void bind_record<qtl::odbc::statement, TestOdbcRecord>(qtl::odbc::statement& command, TestOdbcRecord&& v)
    {
        qtl::bind_field(command, (size_t)0, v.id);
        qtl::bind_field(command, 1, v.name);
        qtl::bind_field(command, 2, v.create_time);
        qtl::bind_fields(command, v.id, v.name, v.create_time);
    }
}
TestOdbc::TestOdbc() : m_db(m_env)
{
    m_db.open("DRIVER={SQL Server};SERVER=(local);UID=;PWD=;Trusted_Connection=yes;DATABASE=test");
    //m_db.open("DRIVER={SQL Server};SERVER=(local);UID=;PWD=;Trusted_Connection=no;DATABASE=test;UID=sa;PWD=111111;");
    m_db.open("DRIVER={SQL Server};SERVER=(local);UID=;PWD=;Trusted_Connection=no;DATABASE=test;UID=sa;PWD=111111;");
    cout<<"DBMS: "<<m_db.dbms_name()<<endl;
    cout<<"SERVER: "<<m_db.server_name()<<endl;
    cout<<"USER: "<<m_db.user_name()<<endl;
@@ -308,7 +305,6 @@
    cout<<hex;
    for(size_t i=0; i!=n; i++)
        cout<<(data[i]&0xFF);
    cout<<dec;
}
int main(int argc, char* argv[])
test/TestSqlite.cpp
@@ -32,9 +32,7 @@
    template<>
    inline void bind_record<qtl::sqlite::statement, TestSqliteRecord>(qtl::sqlite::statement& command, TestSqliteRecord&& v)
    {
        qtl::bind_field(command, 0, v.id);
        qtl::bind_field(command, 1, v.name);
        qtl::bind_field(command, 2, v.create_time);
        qtl::bind_fields(command, v.id, v.name, v.create_time);
    }
}
@@ -50,10 +48,12 @@
    TEST_ADD(TestSqlite::test_iterator)
    TEST_ADD(TestSqlite::test_insert_blob)
    TEST_ADD(TestSqlite::test_select_blob)
    TEST_ADD(TestSqlite::test_any)
}
inline void TestSqlite::connect(qtl::sqlite::database& db)
inline qtl::sqlite::database TestSqlite::connect()
{
    qtl::sqlite::database db;
    try
    {
        db.open("test.db");
@@ -62,12 +62,12 @@
    {
        ASSERT_EXCEPTION(e);
    }
    return db;
}
void TestSqlite::test_dual()
{
    qtl::sqlite::database db;
    connect(db);
    qtl::sqlite::database db = connect();
    try
    {
@@ -85,8 +85,7 @@
void TestSqlite::test_clear()
{
    qtl::sqlite::database db;
    connect(db);
    qtl::sqlite::database db = connect();
    try
    {
@@ -100,8 +99,7 @@
void TestSqlite::test_insert()
{
    qtl::sqlite::database db;
    connect(db);
    qtl::sqlite::database db = connect();
    try
    {
@@ -117,8 +115,7 @@
void TestSqlite::test_insert2()
{
    qtl::sqlite::database db;
    connect(db);
    qtl::sqlite::database db = connect();
    try
    {
@@ -135,8 +132,7 @@
void TestSqlite::test_update()
{
    qtl::sqlite::database db;
    connect(db);
    qtl::sqlite::database db = connect();
    try
    {
@@ -152,8 +148,7 @@
void TestSqlite::test_query()
{
    qtl::sqlite::database db;
    connect(db);
    qtl::sqlite::database db = connect();
    try
    {
@@ -181,8 +176,7 @@
void TestSqlite::test_iterator()
{
    qtl::sqlite::database db;
    connect(db);
    qtl::sqlite::database db = connect();
    try
    {
@@ -201,8 +195,7 @@
void TestSqlite::test_insert_blob()
{
    qtl::sqlite::database db;
    connect(db);
    qtl::sqlite::database db = connect();
    try
    {
@@ -235,8 +228,7 @@
void TestSqlite::test_select_blob()
{
    qtl::sqlite::database db;
    connect(db);
    qtl::sqlite::database db = connect();
    try
    {
@@ -260,6 +252,30 @@
    }
}
void TestSqlite::test_any()
{
#ifdef _QTL_ENABLE_CPP17
    qtl::sqlite::database db = connect();
    try
    {
        db.query("select 0, 'hello world'",
            [](const std::any& i, const std::any& str) {
                cout << "0=\"" << std::any_cast<int64_t>(i) << "\", 'hello world'=\"" <<
                    std::any_cast<const std::string_view&>(str).data() << "\" \n";
        });
    }
    catch (qtl::sqlite::error& e)
    {
        ASSERT_EXCEPTION(e);
    }
    catch (std::bad_cast& e)
    {
        ASSERT_EXCEPTION(e);
    }
#endif // C++17
}
void TestSqlite::get_md5(std::string& str, unsigned char* result)
{
    MD5_CTX context;
test/TestSqlite.h
@@ -26,10 +26,11 @@
    void test_iterator();
    void test_insert_blob();
    void test_select_blob();
    void test_any();
private:
    int64_t id;
    void connect(qtl::sqlite::database& db);
    qtl::sqlite::database connect();
    void get_md5(std::string& str, unsigned char* result);
    void copy_stream(std::istream& is, std::ostream& os);
    static void print_hex(const unsigned char* data, size_t n);