znone
2017-03-23 9ed5ee9526d36df8d5b96fd461c08c7c56e1dc04
支持返回多结果集的查询

支持绑定字符字段到第三方字符串类型
8 files modified
366 ■■■■ changed files
README.md 40 ●●●●● patch | view | raw | blame | history
include/qtl_common.hpp 159 ●●●● patch | view | raw | blame | history
include/qtl_mysql.hpp 65 ●●●●● patch | view | raw | blame | history
include/qtl_odbc.hpp 31 ●●●● patch | view | raw | blame | history
include/qtl_sqlite.hpp 59 ●●●● patch | view | raw | blame | history
test/TestMysql.cpp 4 ●●●● patch | view | raw | blame | history
test/TestOdbc.cpp 4 ●●●● patch | view | raw | blame | history
test/TestSqlite.cpp 4 ●●●● patch | view | raw | blame | history
README.md
@@ -108,6 +108,46 @@
- length 数据的实际长度
- is_truncated 数据是否被截断
#### 9. 支持标准库以外的字符串类型
除了标准库提供的std::string,另外其他库也提供了自己的字符串类,比如QT的QString,MFC/ATL的CString等。qtl也可以将字符字段绑定到这些类型上。扩展方法是:
1. 为你的字符串类型,对 qtl::bind_string_helper 实现一个专门化。如果该字符串类型有符合标准库字符串语义的以下成员函数,可以跳过这一步:assign,clear,resize,data,size;
2. 为你的字符串类型,对 qtl::bind_field 实现一个专门化;
因为QT的QString有兼容标准库的成员函数,所以绑定到QString只需要一步:
```C++
namespace qtl
{
    template<typename Command>
    inline void bind_field(Command& command, size_t index, QString&& value)
    {
        command.bind_field(index, bind_string(std::forward<QString>(value)));
    }
}
```
#### 10.处理返回多个结果集的查询
有些查询语句会返回多个结果集。使用函数query执行这些查询只能得到第一个结果集。要处理所有结果集需要使用query_multi或query_multi_with_params。query_multi不会为没有结果集的查询调用回调函数。例如:
```SQL
CREATE PROCEDURE test_proc()
BEGIN
    select 0, 'hello world' from dual;
    select now() from dual;
END
```
```C++
db.query_multi("call test_proc",
    [](uint32_t i, const std::string& str) {
        printf("0=\"%d\", 'hello world'=\"%s\"\n", i, str.data());
}, [](const qtl::mysql::time& time) {
    struct tm tm;
    time.as_tm(tm);
    printf("current time is: %s\n", asctime(&tm));
});
```
## 有关MySQL的说明
访问MySQL时,包含头文件qtl_mysql.hpp。
include/qtl_common.hpp
@@ -42,34 +42,11 @@
const size_t blob_buffer_size=64*1024;
template<typename Binder, typename T>
inline void bind(Binder& binder, const T& v)
inline std::string& trim_string(std::string& str, const char* target)
{
    binder.bind(const_cast<T&>(v));
}
template<typename Binder, typename T>
inline void bind(Binder& binder, T&& v)
{
    binder.bind(v);
}
template<typename Binder>
inline void bind(Binder& binder, const char* str)
{
    binder.bind((char*)str, (unsigned long)strlen(str));
}
template<typename Binder>
inline void bind(Binder& binder, const std::string& str)
{
    binder.bind((char*)str.data(), (unsigned long)str.size());
}
template<typename Binder>
inline void bind(Binder& binder, std::string&& str)
{
    binder.bind((char*)str.data(), (unsigned long)str.size());
    str.erase(0, str.find_first_not_of(target));
    str.erase(str.find_last_not_of(target)+1);
    return str;
}
template<typename T>
@@ -94,6 +71,29 @@
    operator const data_type&() const { return data; }
};
template<typename StringT>
struct bind_string_helper
{
    typedef StringT string_type;
    typedef typename string_type::value_type char_type;
    bind_string_helper(string_type&& value) : m_value(std::forward<string_type>(value)) { }
    void clear() { m_value.clear(); }
    char_type* alloc(size_t n) { m_value.resize(n); return (char_type*)m_value.data(); }
    void truncate(size_t n) { m_value.resize(n); }
    void assign(const char_type* str, size_t n) { m_value.assign(str, n); }
    const char_type* data() const { return m_value.data(); }
    size_t size() const { return m_value.size(); }
private:
    string_type&& m_value;
};
template<typename StringT>
inline bind_string_helper<typename std::decay<StringT>::type> bind_string(StringT&& value)
{
    typedef typename std::decay<StringT>::type string_type;
    return bind_string_helper<string_type>(std::forward<string_type>(value));
}
template<typename Command, typename T>
inline void bind_param(Command& command, size_t index, const T& param)
{
@@ -112,10 +112,46 @@
    command.bind_field(index, value, N);
}
template<typename Command, size_t N>
inline void bind_field(Command& command, size_t index, wchar_t (&value)[N])
{
    command.bind_field(index, value, N);
}
template<typename Command>
inline void bind_field(Command& command, size_t index, char* value, size_t length)
{
    command.bind_field(index, value, length);
}
template<typename Command>
inline void bind_field(Command& command, size_t index, wchar_t* value, size_t length)
{
    command.bind_field(index, value, length);
}
template<typename Command>
inline void bind_field(Command& command, size_t index, std::string&& value)
{
    command.bind_field(index, bind_string(std::forward<std::string>(value)));
}
template<typename Command>
inline void bind_field(Command& command, size_t index, std::wstring&& value)
{
    command.bind_field(index, bind_string(std::forward<std::wstring>(value)));
}
template<typename Command>
inline void bind_field(Command& command, size_t index, std::vector<char>&& value)
{
    command.bind_field(index, bind_string(std::forward<std::vector<char>>(value)));
}
template<typename Command>
inline void bind_field(Command& command, size_t index, std::vector<wchar_t>&& value)
{
    command.bind_field(index, bind_string(std::forward<std::vector<wchar_t>>(value)));
}
namespace detail
@@ -390,6 +426,28 @@
    return make_values_noclass(&Functor::operator());
}
template<typename Command, typename ValueProc>
inline void fetch_command(Command& command, ValueProc&& proc)
{
    auto values=make_values(proc);
    typedef decltype(values) values_type;
    while(command.fetch(std::forward<values_type>(values)))
    {
        if(!detail::apply(std::forward<ValueProc>(proc), std::forward<values_type>(values)))
            break;
    }
}
template<typename Command, typename ValueProc, typename... OtherProc>
inline void fetch_command(Command& command, ValueProc&& proc, OtherProc&&... other)
{
    fetch_command(command, std::forward<ValueProc>(proc));
    if(command.next_result())
    {
        fetch_command(command, std::forward<OtherProc>(other)...);
    }
}
}
template<typename Command, typename T>
@@ -581,17 +639,17 @@
    template<typename... Params>
    void execute_direct(const char* query_text, size_t text_length, uint64_t* affected, const Params&... params)
    {
        execute(query_text, text_length, std::make_tuple(params...), affected);
        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)
    {
        execute(query_text, std::make_tuple(params...), affected);
        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)
    {
        execute(query_text, std::make_tuple(params...), affected);
        execute(query_text, std::forward_as_tuple(params...), affected);
    }
    template<typename Params>
@@ -621,19 +679,19 @@
    template<typename... Params>
    uint64_t insert_direct(const char* query_text, size_t text_length, const Params&... params)
    {
        return insert(query_text, text_length, std::make_tuple(params...));
        return insert(query_text, text_length, std::forward_as_tuple(params...));
    }
    template<typename... Params>
    uint64_t insert_direct(const char* query_text, const Params&... params)
    {
        return insert(query_text, strlen(query_text), std::make_tuple(params...));
        return insert(query_text, strlen(query_text), std::forward_as_tuple(params...));
    }
    template<typename... Params>
    uint64_t insert_direct(const std::string& query_text, const Params&... params)
    {
        return insert(query_text.data(), query_text.length(), std::make_tuple(params...));
        return insert(query_text.data(), query_text.length(), std::forward_as_tuple(params...));
    }
    template<typename Record, typename Params>
@@ -740,6 +798,41 @@
        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)
    {
        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();
    }
    template<typename Params, typename... ValueProc>
    void 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)...);
    }
    template<typename Params, typename... ValueProc>
    void 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)...);
    }
    template<typename... ValueProc>
    void query_multi(const char* query_text, size_t text_length, ValueProc&&... proc)
    {
        query_multi_with_params<std::tuple<>>(query_text, text_length, std::make_tuple(), std::forward<ValueProc>(proc)...);
    }
    template<typename... ValueProc>
    void query_multi(const char* query_text, ValueProc&&... proc)
    {
        query_multi_with_params<std::tuple<>>(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)
    {
        query_multi_with_params<std::tuple<>>(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)
    {
include/qtl_mysql.hpp
@@ -163,6 +163,24 @@
    }
};
template<typename T>
inline void bind(binder& binder, const T& v)
{
    binder.bind(const_cast<T&>(v));
}
template<typename T>
inline void bind(binder& binder, T&& v)
{
    binder.bind(v);
}
inline void bind(binder& binder, const char* str)
{
    binder.bind((char*)str, (unsigned long)strlen(str));
}
class statement;
class database;
@@ -273,7 +291,7 @@
    template<class Param>
    void bind_param(size_t index, const Param& param)
    {
        qtl::bind(m_binders[index], param);
        bind(m_binders[index], param);
    }
    template<class Type>
@@ -281,7 +299,7 @@
    {
        if(m_result)
        {
            qtl::bind(m_binders[index], std::forward<Type>(value));
            bind(m_binders[index], std::forward<Type>(value));
            m_binderAddins[index].m_after_fetch=if_null<typename std::remove_reference<Type>::type>(value);
        }
    }
@@ -311,17 +329,20 @@
        bind_field(index, value.data(), value.size());
    }
    void bind_field(size_t index, std::string&& value)
    template<typename T>
    void bind_field(size_t index, bind_string_helper<T>&& value)
    {
        if(m_result)
        {
            MYSQL_FIELD* field=mysql_fetch_field_direct(m_result, (unsigned int)index);
            if(field==NULL) throw_exception();
            value.clear();
            value.resize(field->length);
            m_binderAddins[index].m_after_fetch=resize_binder<std::string>(value);
            qtl::bind(m_binders[index], std::forward<std::string>(value));
            m_binders[index].buffer_type=field->type;
            typename bind_string_helper<T>::char_type* data=value.alloc(field->length);
            m_binderAddins[index].m_after_fetch= [value](const binder& b) mutable {
                if(*b.is_null) value.clear();
                else value.truncate(*b.length);
            };
            m_binders[index].bind(data, field->length, field->type);
        }
    }
    void bind_param(size_t index, std::istream& param)
@@ -428,6 +449,23 @@
        return false;
    }
    bool next_result()
    {
        if(m_result)
        {
            mysql_free_result(m_result);
            m_result=NULL;
            mysql_stmt_free_result(m_stmt);
        }
        int ret=0;
        do
        {
            ret=mysql_stmt_next_result(m_stmt);
            if(ret>0) throw_exception();
        }while(ret==0 && mysql_stmt_field_count(m_stmt)<=0);
        return ret==0;
    }
    my_ulonglong affetced_rows()
    {
        return mysql_stmt_affected_rows(m_stmt);
@@ -490,19 +528,6 @@
    void throw_exception() { throw mysql::error(*this); }
private:
    template<typename CharCont>
    struct resize_binder
    {
        resize_binder(CharCont& cont) : m_cont(cont) { }
        void operator()(const binder& b) const
        {
            if(*b.is_null) m_cont.clear();
            else m_cont.resize(*b.length);
        }
        CharCont& m_cont;
    };
    template<typename Value>
    struct if_null
    {
include/qtl_odbc.hpp
@@ -358,7 +358,7 @@
                text[p.m_indicator]='\0';
            }
        };
        verify_error(SQLBindCol(m_handle, index+1, SQL_C_CHAR, v, n-1, &m_params[index].m_indicator));
        verify_error(SQLBindCol(m_handle, index+1, SQL_C_CHAR, v, n, &m_params[index].m_indicator));
    }
    void bind_field(SQLUSMALLINT index, wchar_t* v, size_t n)
    {
@@ -373,20 +373,20 @@
                text[p.m_indicator]='\0';
            }
        };
        verify_error(SQLBindCol(m_handle, index+1, SQL_C_WCHAR, v, n-1, &m_params[index].m_indicator));
        verify_error(SQLBindCol(m_handle, index+1, SQL_C_WCHAR, v, n, &m_params[index].m_indicator));
    }
    template<typename CharType>
    void bind_field(SQLUSMALLINT index, std::basic_string<CharType>&& v)
    template<typename T>
    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);
        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) {
        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 {
            if(p.m_indicator==SQL_NULL_DATA)
                v.clear();
            else
                v.resize(p.m_indicator);
                v.truncate(p.m_indicator);
        };
    }
    template<size_t N>
@@ -516,6 +516,21 @@
        return false;
    }
    bool next_result()
    {
        SQLRETURN ret;
        SQLSMALLINT count=0;
        m_binded_cols=false;
        do
        {
            ret=SQLMoreResults(m_handle);
            if(ret==SQL_ERROR || ret==SQL_INVALID_HANDLE)
                verify_error(ret);
            verify_error(SQLNumResultCols(m_handle, &count));
        }while(count==0);
        return ret==SQL_SUCCESS || ret==SQL_SUCCESS_WITH_INFO;
    }
    SQLLEN affetced_rows()
    {
        SQLLEN count=0;
include/qtl_sqlite.hpp
@@ -40,7 +40,8 @@
    statement() : m_stmt(NULL), m_fetch_result(SQLITE_OK) { }
    statement(const statement&) = delete;
    statement(statement&& src) 
        : m_stmt(src.m_stmt), m_fetch_result(src.m_fetch_result)
        : m_stmt(src.m_stmt), m_fetch_result(src.m_fetch_result),
        m_tail_text(std::forward<std::string>(src.m_tail_text))
    {
        src.m_stmt=NULL;
        src.m_fetch_result=SQLITE_OK;
@@ -52,6 +53,7 @@
        {
            m_stmt=src.m_stmt;
            m_fetch_result=src.m_fetch_result;
            m_tail_text=std::forward<std::string>(src.m_tail_text);
            src.m_stmt=NULL;
            src.m_fetch_result=SQLITE_OK;
        }
@@ -64,8 +66,13 @@
    void open(sqlite3* db, const char* query_text, size_t text_length=-1)
    {
        const char* tail=NULL;
        close();
        verify_error(sqlite3_prepare_v2(db, query_text, (int)text_length, &m_stmt, NULL));
        verify_error(sqlite3_prepare_v2(db, query_text, (int)text_length, &m_stmt, &tail));
        if(tail!=NULL)
            m_tail_text.assign(tail, query_text+text_length);
        else
            m_tail_text.clear();
    }
    void close()
@@ -245,19 +252,17 @@
    {
        return sqlite3_column_text(m_stmt, col);
    }
    void get_value(int col, std::string&& value) const
    template<typename CharT>
    const CharT* get_text_value(int col) const;
    template<typename T>
    void get_value(int col, qtl::bind_string_helper<T>&& value) const
    {
        typedef typename qtl::bind_string_helper<T>::char_type char_type;
        int bytes=sqlite3_column_bytes(m_stmt, col);
        if(bytes>0)
            value.assign((const char*)sqlite3_column_text(m_stmt, col), bytes/sizeof(char));
        else
            value.clear();
    }
    void get_value(int col, std::wstring&& value) const
    {
        int bytes=sqlite3_column_bytes16(m_stmt, col);
        if(bytes>0)
            value.assign((const wchar_t*)sqlite3_column_text16(m_stmt, col), bytes/sizeof(wchar_t));
            value.assign(get_text_value<char_type>(col), bytes/sizeof(char_type));
        else
            value.clear();
    }
@@ -326,6 +331,23 @@
        return result;
    }
    bool next_result()
    {
        sqlite3* db=sqlite3_db_handle(m_stmt);
        int count=0;
        do
        {
            trim_string(m_tail_text, " \t\r\n");
            if(!m_tail_text.empty())
            {
                open(db, m_tail_text.data(), m_tail_text.size());
                count=sqlite3_column_count(m_stmt);
                m_fetch_result=SQLITE_OK;
            }
        }while(!m_tail_text.empty() && count==0);
        return count>0;;
    }
    int affetced_rows()
    {
        sqlite3* db=sqlite3_db_handle(m_stmt);
@@ -340,6 +362,8 @@
protected:
    sqlite3_stmt* m_stmt;
    std::string m_tail_text;
    int m_fetch_result;
    void verify_error(int e)
    {
@@ -462,6 +486,17 @@
    return stmt;
}
template<>
inline const char* statement::get_text_value<char>(int col) const
{
    return (const char*)sqlite3_column_text(m_stmt, col);
}
template<>
inline const wchar_t* statement::get_text_value<wchar_t>(int col) const
{
    return (const wchar_t*)sqlite3_column_text16(m_stmt, col);
}
}
}
test/TestMysql.cpp
@@ -214,7 +214,7 @@
        fs.clear();
        fs.seekg(0, ios::beg);
        id=db.insert("INSERT INTO test_blob (Filename, Content, MD5) values(?, ?, ?)",
            make_tuple(filename, ref((istream&)fs), qtl::const_blob_data(md5, sizeof(md5))));
            forward_as_tuple(filename, (istream&)fs, qtl::const_blob_data(md5, sizeof(md5))));
    }
    catch(qtl::mysql::error& e)
    {
@@ -238,7 +238,7 @@
        fs.seekg(ios::beg);
        std::string source_file;
        db.query_first("SELECT Filename, Content, MD5 FROM test_blob WHERE id=?", make_tuple(id),
            make_tuple(ref(source_file), ref((ostream&)fs), qtl::blob_data(md5, sizeof(md5))));
            forward_as_tuple(source_file, (ostream&)fs, qtl::blob_data(md5, sizeof(md5))));
        fs.flush();
        mysql_hex_string(md5_hex, (char*)md5, sizeof(md5));
        printf("MD5 of file %s stored in database: %s.\n", source_file.data(), md5_hex);
test/TestOdbc.cpp
@@ -192,7 +192,7 @@
        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))));
            forward_as_tuple(filename, (istream&)fs, qtl::const_blob_data(md5, sizeof(md5))));
        m_db.query_first("SELECT @@IDENTITY", id);
    }
    catch(qtl::odbc::error& e)
@@ -214,7 +214,7 @@
        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)));
            forward_as_tuple(source_file, qtl::blob_data(md5, sizeof(md5)), (ostream&)fs));
        fs.flush();
        cout<<"MD5 of file "<<source_file<<" stored in database: ";
        print_hex((const unsigned char*)md5, sizeof(md5));
test/TestSqlite.cpp
@@ -225,7 +225,7 @@
        print_hex(md5, sizeof(md5));
        cout<<endl;
        id=db.insert("INSERT INTO test_blob (Filename, Content, MD5) values(?, ?, ?)",
            make_tuple(filename, qtl::const_blob_data(str.data(), str.size()), qtl::const_blob_data(md5, sizeof(md5))));
            forward_as_tuple(filename, qtl::const_blob_data(str.data(), str.size()), qtl::const_blob_data(md5, sizeof(md5))));
    }
    catch(qtl::sqlite::error& e)
    {
@@ -248,7 +248,7 @@
        fs.seekg(ios::beg);
        db.query_first("SELECT Filename, Content, MD5 FROM test_blob WHERE id=?", make_tuple(id),
            make_tuple(ref(source_file), ref(fs), qtl::blob_data(md5, 16)));
            forward_as_tuple(source_file, fs, qtl::blob_data(md5, 16)));
        fs.flush();
        cout<<"MD5 of file "<<source_file<<" stored in database: ";
        print_hex((const unsigned char*)md5, sizeof(md5));