From 9ed5ee9526d36df8d5b96fd461c08c7c56e1dc04 Mon Sep 17 00:00:00 2001
From: znone <glyc@sina.com.cn>
Date: Sat, 15 Apr 2017 09:32:33 +0000
Subject: [PATCH] 支持返回多结果集的查询
---
include/qtl_sqlite.hpp | 59 ++++++-
test/TestOdbc.cpp | 4
test/TestSqlite.cpp | 4
test/TestMysql.cpp | 4
include/qtl_common.hpp | 159 ++++++++++++++++++----
include/qtl_odbc.hpp | 31 +++-
include/qtl_mysql.hpp | 65 ++++++--
README.md | 40 +++++
8 files changed, 287 insertions(+), 79 deletions(-)
diff --git a/README.md b/README.md
index b519a03..06806b7 100644
--- a/README.md
+++ b/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。
diff --git a/include/qtl_common.hpp b/include/qtl_common.hpp
index af9f409..5015d9c 100644
--- a/include/qtl_common.hpp
+++ b/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)
{
diff --git a/include/qtl_mysql.hpp b/include/qtl_mysql.hpp
index b0cb96b..198b82f 100644
--- a/include/qtl_mysql.hpp
+++ b/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
{
diff --git a/include/qtl_odbc.hpp b/include/qtl_odbc.hpp
index e021ef6..99a4c49 100644
--- a/include/qtl_odbc.hpp
+++ b/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;
diff --git a/include/qtl_sqlite.hpp b/include/qtl_sqlite.hpp
index 20f7e00..f316bed 100644
--- a/include/qtl_sqlite.hpp
+++ b/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);
+}
+
}
}
diff --git a/test/TestMysql.cpp b/test/TestMysql.cpp
index 53330c1..d7dda0b 100644
--- a/test/TestMysql.cpp
+++ b/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);
diff --git a/test/TestOdbc.cpp b/test/TestOdbc.cpp
index 58cc44a..c018451 100644
--- a/test/TestOdbc.cpp
+++ b/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));
diff --git a/test/TestSqlite.cpp b/test/TestSqlite.cpp
index 7ac4ad6..c349ad1 100644
--- a/test/TestSqlite.cpp
+++ b/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));
--
Gitblit v1.9.3