支持返回多结果集的查询
支持绑定字符字段到第三方字符串类型
| | |
| | | - 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。 |
| | |
| | | |
| | | 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> |
| | |
| | | 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) |
| | | { |
| | |
| | | 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 |
| | |
| | | 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> |
| | |
| | | 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> |
| | |
| | | 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> |
| | |
| | | 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) |
| | | { |
| | |
| | | } |
| | | }; |
| | | |
| | | |
| | | 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; |
| | | |
| | |
| | | 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> |
| | |
| | | { |
| | | 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); |
| | | } |
| | | } |
| | |
| | | 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) |
| | |
| | | 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); |
| | |
| | | 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 |
| | | { |
| | |
| | | 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) |
| | | { |
| | |
| | | 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> |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | { |
| | | 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; |
| | | } |
| | |
| | | |
| | | 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() |
| | |
| | | { |
| | | 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(); |
| | | } |
| | |
| | | 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); |
| | |
| | | |
| | | protected: |
| | | sqlite3_stmt* m_stmt; |
| | | std::string m_tail_text; |
| | | |
| | | int m_fetch_result; |
| | | void verify_error(int e) |
| | | { |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
| | | |
| | | } |
| | |
| | | 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) |
| | | { |
| | |
| | | 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); |
| | |
| | | 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) |
| | |
| | | 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)); |
| | |
| | | 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) |
| | | { |
| | |
| | | |
| | | 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)); |