c++ 新特性除了零碎的小知識、學不完的文法、艱深難懂的模闆,究竟給我們帶來了哪些好處,這裡從資料庫接口入手談一談。話說如果隻是使用者的話,感覺還蠻好的……
之前寫過一篇文章專門分析了 c++ 模闆編譯過程中報的一個錯誤:《fatal error C1045: 編譯器限制 : 連結規範嵌套太深 》,其中涉及到了 qtl —— 一個使用 c++ 11 建構的資料庫通路庫,當時限于篇幅,沒有深入研究它是如何借助 c++ 11 來簡化資料庫通路接口的,本文現在就來探讨一下這方面的内容。
沒有 c++ 11 之前,苦逼的程式員對于 sql 操作的輸入輸出,隻好一行行敲代碼,例如在調用資料庫接口前設定綁定參數;在調用成功後,循環周遊查詢的記錄。很多時候資料庫表對應在程式中就是一個結構體,程式員需要花費大量的精力将資料庫表字段對應到結構體成員上、或反之,完全沒有展現出來程式員應有的價值。而 qtl 這種 c++ 11 庫的出現,可以極大的簡化上面的程式編寫,下面還是用之前文章中提到的例子作為示範,讓大家感受一下:
插入單條資料
1 uint64_t test_insert_single(qtl::sqlite::database &db)
2 {
3 time_t now = time(0);
4 int tmp = rand() % 1000;
5 uint64_t id = db.insert_direct("insert into popbox_msg(msgid, msgtype, appname, uid, status, count, msgbody, stamp) values(?, ?, ?, ?, ?, ?, ?, ?)",
6 std::to_string(tmp), 108, "GDraw", "1923374929399", 1, 0, "this is msgbody", now);
7
8 printf("insert record with msgid %d return %d\n", tmp, (int)id);
9 return id;
10 }
插入操作需要輸入資料,将資料編入 sql 是一種思路,但更好的方法是使用占位符 (?) 和資料綁定 (binding) 來防止 sql 注入問題,而這會給接口帶來不定數量的輸入參數,幸好 c++ 11 的可變模闆參數特性允許使用者提供不限數量與類型的輸入資料,是不是很友善?下面是 qtl 提供的插入單條資料接口:
1 uint64_t qtl::base_database<T, Command>::insert<Params>(const std::string & query_text, const Params & params);
2 uint64_t qtl::base_database<T, Command>::insert<Params>(const char * query_text, const Params & params);
3 uint64_t qtl::base_database<T, Command>::insert<Params>(const char * query_text, size_t text_length, const Params & params);
4
5 uint64_t qtl::base_database<T, Command>::insert_direct<...Params>(const std::string & query_text, const Params & ...params);
6 uint64_t qtl::base_database<T, Command>::insert_direct<...Params>(const char * query_text, const Params & ...params);
7 uint64_t qtl::base_database<T, Command>::insert_direct<...Params>(const char * query_text, size_t text_length, const Params & ...params);
其中主要分兩組:insert 與 insert_direct,前者隻提供一個輸入綁定參數,後者可以提供多個。而且這些接口會很貼心的将新插入記錄的 rowid 傳回,友善後續操作這條記錄。
更新單條資料
1 void test_update_single(qtl::sqlite::database &db, uint64_t rowid)
2 {
3 time_t now = time(0);
4 uint64_t affected = 0;
5 db.execute_direct("update popbox_msg set status=?, count=?, stamp=? where rowid=?", &affected, 0, 3, now, (int)rowid);
6 printf("update record with rowid %d affected %d records\n", (int)rowid, (int)affected);
7 }
更新操作和插入操作類似,輸入資料是必不可少的,但它有時也需要更新符合條件的記錄,而這會帶來另一坨不定數量的輸入參數,不過好在二者都是輸入參數,可以合二為一使用一個次元的可變模闆參數,依次将更新參數與條件參數羅列在 qtl 接口提供的參數清單中即可:
1 void qtl::base_database<T, Command>::execute<Params>(const std::string & query_text, const Params & params, uint64_t * affected = NULL);
2 void qtl::base_database<T, Command>::execute<Params>(const char * query_text, const Params & params, uint64_t * affected = NULL);
3 void qtl::base_database<T, Command>::execute<Params>(const char * query_text, size_t text_length, const Params & params, uint64_t * affected = NULL);
4
5 void qtl::base_database<T, Command>::execute_direct<...Params>(const std::string & query_text, uint64_t * affected, const Params & ...params);
6 void qtl::base_database<T, Command>::execute_direct<...Params>(const char * query_text, uint64_t * affected, const Params & ...params);
7 void qtl::base_database<T, Command>::execute_direct<...Params>(const char * query_text, size_t text_length, uint64_t * affected, const Params & ...params);
主要也是兩組接口:execute 與 execute_direct,前者隻提供一個輸入綁定參數,後者可以提供多個。由于是插入多條資料,這裡沒有辦法傳回某一條記錄的 rowid,代之以的是更新的行數 affected,如果這個參數為空,則不傳回。
插入多條資料
void test_insert_multi(qtl::sqlite::database &db)
{
uint64_t affected = 0;
int tmp[3] = { 0 };
for (int i=0; i<3; ++i)
tmp[i] = rand() % 1000;
auto stmt = db.open_command("insert into popbox_msg(msgid, msgtype, appname, uid, status, count, msgbody, stamp) "
"values(?, 108, 'GDraw', '1923374929399', 1, 0, 'this is msgbody', strftime('%s','now'))");
qtl::execute(stmt, &affected, std::to_string(tmp[0]), std::to_string(tmp[1]), std::to_string(tmp[2]));
printf("insert %d record\n", (int)affected);
}
插入多條資料時,可變模闆參數清單的每一個參數表示一個輸入綁定參數、針對一條新記錄,這樣一來就不太夠用了。例如上面這個例子中,相當于插入了三條不同的 popbox_msg 記錄,每個輸入參數對應記錄的 msgid 字段,如果一條記錄有多個字段需要輸入就不适用了,那種場景下就需要寫個循環多次調用插入單條資料的操作了(其實插入多條的接口底層也是遞歸為插入單條來執行的,是以這樣做性能沒有太大損失)。
更新多條資料
1 void test_update_multi(qtl::sqlite::database &db)
2 {
3 uint64_t affected = 0;
4 int id[3] = { 19, 20, 21 };
5
6 auto stmt = db.open_command("update popbox_msg set status=0, count=2, stamp=strftime('%s','now') where rowid=? ");
7 qtl::execute(stmt, &affected, id[0], id[1], id[2]);
8 printf("update %d record\n", (int)affected);
9 }
其實和插入多條資料非常相似,每條記錄隻能允許一個輸入綁定參數。
删除資料
1 void test_delete(qtl::sqlite::database &db)
2 {
3 uint64_t affected = 0;
4 db.execute_direct("delete from popbox_msg where msgtype=? and appname=? and uid=?", &affected, 108, "GDraw", "1923374929399");
5 printf("delete record affected %d rows\n", (int)affected);
6 }
删除資料時由于隻需要提供删除條件的輸入綁定參數,而實際結果可能删除一條、也可能删除多條,是以不在數量上做區分。這裡使用的是和更新資料一樣的接口:execute 和 execute_direct,同樣的,前者隻能允許一個輸入綁定參數,适合較簡單的 sql 語句;後者可以允許多個輸入綁定參數,适合較複雜的 sql。最後,删除的行數由 affected 參數傳回給調用者。
查詢單條資料
1 void test_query_single(qtl::sqlite::database &db, uint64_t rowid)
2 {
3 std::string msg;
4 db.query_first("select msgbody from popbox_msg where rowid=?", (int)rowid, msg);
5 printf("row %d: %s\n", (int)rowid, msg.c_str());
6 }
查詢單條資料時可以直接将查詢到的資料以輸出參數方式回傳,而查詢條件往往又需要輸入綁定參數,那 qtl 是如何區分可變模闆參數清單中哪些是入參、哪些是出參呢?答案是區分不了。是以在接口設計上,qtl 的查詢單條資料接口最多允許一個入參:
1 void qtl::base_database<T, Command>::query_first<Values>(const std::string & query_text, Values && values);
2 void qtl::base_database<T, Command>::query_first<Values>(const char * query_text, Values && values);
3 void qtl::base_database<T, Command>::query_first<Values>(const char * query_text, size_t text_length, Values && values);
4
5 void qtl::base_database<T, Command>::query_first<Params, Values>(const std::string & query_text, const Params & params, Values && values);
6 void qtl::base_database<T, Command>::query_first<Params, Values>(const char * query_text, const Params & params, Values && values);
7 void qtl::base_database<T, Command>::query_first<Params, Values>(const char * query_text, size_t text_length, const Params & params, Values && values);
主要分為兩組:隻帶一個出參的 query_first;帶一個出參和一個入參的 query_first。這個接口隻針對特别簡單的 sql 語句,如果想要傳回一條記錄的多個字段時,就必需使用另一組接口:query_first_direct
1 void qtl::base_database<T, Command>::query_first_direct<...Values>(const std::string & query_text, Values & ...values);
2 void qtl::base_database<T, Command>::query_first_direct<...Values>(const char * query_text, Values & ...values);
3 void qtl::base_database<T, Command>::query_first_direct<...Values>(const char * query_text, size_t text_length, Values & ...values);
遺憾的是這個接口雖然能提供多個出參,卻無法提供任何入參,所有入參必需事先建構在 sql 語句中,這十分不優雅,但沒有辦法。下面是使用的例子:
1 void test_query_single_ex(qtl::sqlite::database &db, uint64_t rowid)
2 {
3 time_t stamp = 0;
4 int status = 0, count = 0;
5
6 std::ostringstream oss;
7 oss << "select status, count, stamp from popbox_msg where rowid=" << rowid;
8 db.query_first_direct(oss.str (), status, count, stamp);
9 printf("row %d: status %d, count %d, stamp %d\n", (int)rowid, status, count, (int)stamp);
10 }
從這個實際例子看,以後 c++ 可變模闆參數清單可能需要支援兩個參數列,一列是輸入參數,一列是輸出參數了。但是轉念一想,這樣好像也不對,因為出參與入參在調用點并無任何差別,編譯器如何知道哪個是出參哪個是入參呢?是以這個問題可能還真是無解了。
查詢多條資料
1 void test_query_multi(qtl::sqlite::database &db)
2 {
3 int cnt = 0;
4 db.query("select status, count, stamp from popbox_msg where appname=?", "GDraw",
5 [&cnt](int status, int count, time_t stamp){
6 printf("%d, %d, %d\n", status, count, (int)stamp);
7 cnt++;
8 });
9
10 printf("query %d records\n", cnt);
11 }
因為可能傳回多條資料,這裡使用回調函數(一般為 lambda 表達式)來接收讀取的記錄。回調函數參數清單必需與 select 選擇的資料庫表列相比對。
1 void qtl::base_database<T, Command>::query<ValueProc>(const std::string & query_text, ValueProc && proc);
2 void qtl::base_database<T, Command>::query<ValueProc>(const char * query_text, ValueProc && proc);
3 void qtl::base_database<T, Command>::query<ValueProc>(const char * query_text, size_t text_length, ValueProc && proc);
4
5 void qtl::base_database<T, Command>::query<Params, ValueProc>(const std::string & query_text, const Params & params, ValueProc && proc);
6 void qtl::base_database<T, Command>::query<Params, ValueProc>(const char * query_text, const Params & params, ValueProc && proc);
7 void qtl::base_database<T, Command>::query<Params, ValueProc>(const char * query_text, size_t text_length, const Params & params, ValueProc && proc);
query 接口分為兩組:一組隻提供一個回調函數用于接收資料;另一組還提供一個額外的輸入綁定參數。對于複雜的 sql 查詢,這個還是不太夠用,我不清楚為什麼不能在 ValueProc proc 參數後面加一個可變模闆參數清單,這樣就不可以接收多個輸入綁定參數了麼?此處存疑。不過這個好歹比 query_first 要麼隻傳回一個字段、要麼傳回多個字段但不接收輸入參數要強一點。除了優點,這個接口也有一個不惹人注意的 bug,請看下面這段代碼:
1 void test_query_multi(qtl::sqlite::database &db)
2 {
3 int cnt = 0;
4 db.query("select msgid, msgtype, appname, uid, status, count, msgbody, stamp from popbox_msg where appname=?", "GDraw",
5 [&cnt](std::string const& msgid, int msgtype, std::string const& appname, std::string const& uid, int status, int count, std::string const& msgbody, time_t stamp){
6 printf("%s, %d, %s, %s, %d, %d, %s, %d\n", msgid.c_str(), msgtype, appname.c_str(), uid.c_str(), status, count, msgbody.c_str(), (int)stamp);
7 cnt++;
8 });
9
10 printf("query %d records\n", cnt);
11 }
我增加了從資料庫表中選取的字段,并相應的增加了 lambda 表達式的參數清單,當數量達到一個門檻值時(親測為8),VS2013 編譯器将報錯退出:
e:\code\qtl\include\qtl\apply_tuple.h(17): fatal error C1045: 編譯器限制 : 連結規範嵌套太深
具體分析請參考我的另一篇文章:《fatal error C1045: 編譯器限制 : 連結規範嵌套太深》。這裡我着重想說明的是,使用這種方式傳遞的字段在某些編譯器上是有上限的,是以可移植性不太好。相信聰明的你已經猜到了,由于 query_first_direct 使用了和 query 相同的底層機制,query_first_direct 在 VS2013 上也存在相同的問題。幸好 qtl 還有另外一種方法,可以解決上面的問題,這就是結構體成員綁定:
1 class popbox_msg_t
2 {
3 public:
4 void dump(char const* prompt) const;
5
6 int msgtype = 0; // 108 or 402
7 int status = 0; // send to server result, (1:ok; 0:fail)
8 int count = 0; // retry times, if exceed POPBOX_MSG_RETRY_MAX, stop retry
9 time_t stamp = 0; // receive time
10 std::string msgid;
11 std::string msgbody;
12 std::string appname;
13 std::string uid;
14 };
15
16
17 void popbox_msg_t::dump(char const* prompt) const
18 {
19 tm* t = localtime(&stamp);
20 printf("%s : %s,%s,%s,%d,%d,%d, %04d-%02d-%02d %02d:%02d:%02d, %s\n",
21 prompt,
22 appname.c_str(),
23 uid.c_str(),
24 msgid.c_str(),
25 msgtype,
26 status,
27 count,
28 t->tm_year + 1900,
29 t->tm_mon + 1,
30 t->tm_mday + 1,
31 t->tm_hour,
32 t->tm_min,
33 t->tm_sec,
34 msgbody.c_str());
35 }
36
37 namespace qtl
38 {
39 template<>
40 inline void bind_record<qtl::sqlite::statement, popbox_msg_t>(qtl::sqlite::statement& command, popbox_msg_t&& v)
41 {
42 int n = 0;
43 qtl::bind_field(command, n++, v.msgid);
44 qtl::bind_field(command, n++, v.msgtype);
45 qtl::bind_field(command, n++, v.appname);
46 qtl::bind_field(command, n++, v.uid);
47 qtl::bind_field(command, n++, v.status);
48 qtl::bind_field(command, n++, v.count);
49 qtl::bind_field(command, n++, v.msgbody);
50 qtl::bind_field(command, n++, v.stamp);
51 }
52 }
53
54 void test_query_multi_ex(qtl::sqlite::database &db)
55 {
56 int cnt = 0;
57 db.query("select msgid, msgtype, appname, uid, status, count, msgbody, stamp from popbox_msg where appname=?", "GDraw",
58 [&cnt](popbox_msg_t const& pm){
59 pm.dump("msg");
60 cnt++;
61 });
62
63 printf("query %d records\n", cnt);
64 }
同樣是 query 接口,同樣是 lambda 表達式作為回調函數,不同的是,我提前聲明了一個結構體 popbox_msg_t,并提供了 qtl::bind_record 模闆函數的一個特化、來将資料庫表的列與結構體成員二者關聯起來,這樣我的 lambda 表達式隻要接收結構體就夠了,qtl 在底層會自動根據 bind_record 将讀取的資料初始化到結構體***我們使用。因為這種方式避免了羅列各個輸出參數,是以可以很好的避免上述問題。 另外關于 bind_record 補充一點,最新版本的 qtl 可以在 bind_record 模闆特化中使用一個 bind_fields 來指定所有成員的對應關系了(我使用的舊版沒有這個接口),類似于這樣:
qtl::bind_fields(command, v.msgid, v.msgtype, v.appname, v.uid, v.status, v.count, v.msgbody, v.stamp);
是不是更簡單了呢?有了結構體綁定,還可以玩出許多花樣,例如直接用結構體的成員函數來代替 lambda 表達式:
1 class popbox_msg_t
2 {
3 public:
4 void dump(char const* prompt) const;
5 void print();
6
7 int msgtype = 0; // 108 or 402
8 int status = 0; // send to server result, (1:ok; 0:fail)
9 int count = 0; // retry times, if exceed POPBOX_MSG_RETRY_MAX, stop retry
10 time_t stamp = 0; // receive time
11 std::string msgid;
12 std::string msgbody;
13 std::string appname;
14 std::string uid;
15 };
16
17
18 void popbox_msg_t::dump(char const* prompt) const
19 {
20 tm* t = localtime(&stamp);
21 printf("%s : %s,%s,%s,%d,%d,%d, %04d-%02d-%02d %02d:%02d:%02d, %s\n",
22 prompt,
23 appname.c_str(),
24 uid.c_str(),
25 msgid.c_str(),
26 msgtype,
27 status,
28 count,
29 t->tm_year + 1900,
30 t->tm_mon + 1,
31 t->tm_mday + 1,
32 t->tm_hour,
33 t->tm_min,
34 t->tm_sec,
35 msgbody.c_str());
36 }
37
38 void popbox_msg_t::print ()
39 {
40 dump("msg");
41 }
42
43 namespace qtl
44 {
45 template<>
46 inline void bind_record<qtl::sqlite::statement, popbox_msg_t>(qtl::sqlite::statement& command, popbox_msg_t&& v)
47 {
48 int n = 0;
49 qtl::bind_field(command, n++, v.msgid);
50 qtl::bind_field(command, n++, v.msgtype);
51 qtl::bind_field(command, n++, v.appname);
52 qtl::bind_field(command, n++, v.uid);
53 qtl::bind_field(command, n++, v.status);
54 qtl::bind_field(command, n++, v.count);
55 qtl::bind_field(command, n++, v.msgbody);
56 qtl::bind_field(command, n++, v.stamp);
57 }
58 }
59
60 void test_query_multi_ex(qtl::sqlite::database &db)
61 {
62 int cnt = 0;
63 db.query("select msgid, msgtype, appname, uid, status, count, msgbody, stamp from popbox_msg where appname=?", "GDraw",
64 &popbox_msg_t::print);
65
66 printf("query %d records\n", cnt);
67 }
代碼高亮的部分就是兩個版本的差異,這裡使用了 popbox_msg_t 的一個成員函數 print 來充當 lambda 表達式的作用,這樣做可以将代碼集中到結構體中進行維護。不過缺點也是明顯的,就是不能***的選取外部輸入參數了,例如對周遊記錄數的統計,在新版本中就沒辦法做到了。除了上面的方式,還有一種新花樣:
1 void test_query_multi_ul(qtl::sqlite::database &db)
2 {
3 int cnt = 0;
4 for(auto& pm : db.result<popbox_msg_t>("select msgid, msgtype, appname, uid, status, count, msgbody, stamp from popbox_msg where appname=?", "GDraw"))
5 {
6 pm.dump("msg");
7 cnt++;
8 }
9
10 printf("query %d records\n", cnt);
11 }
這種方式已經脫離了 query 接口,使用的是 result 接口,雖然殼變了,但是底層機制和 query 是一緻的,都是通過 bind_record 将查詢到的資料填充到結構體中,下面是 result 的接口定義:
1 query_result<Command,Record> qtl::base_database<T, Command>::result<Record>(const std::string & query_text);
2 query_result<Command,Record> qtl::base_database<T, Command>::result<Record>(const char * query_text);
3 query_result<Command,Record> qtl::base_database<T, Command>::result<Record>(const char * query_text, size_t text_length);
4
5 query_result<Command,Record> qtl::base_database<T, Command>::result<Record, Params>(const std::string & query_text, const Params & params);
6 query_result<Command,Record> qtl::base_database<T, Command>::result<Record, Params>(const char * query_text, const Params & params);
7 query_result<Command,Record> qtl::base_database<T, Command>::result<Record, Params>(const char * query_text, size_t text_length, const Params & params);
主要分為兩組:一組隻接收 sql 輸入;另一組還可以接收一個額外的輸入綁定參數。除了傳回類型,與 query 接口幾乎一模一樣,可以了解成是将 query 的回調函數轉化成了 result 傳回的 query_result 集合。像上面例子那樣寫代碼,幾乎找到了之前 c 語言操作資料庫的感覺,特别是不用把需要的外部變量在 lambda 表達式裡一一捕獲了,在循環裡就可以直接用它們,就是一個字:爽!
如果有多個操作都從一個表中查詢,可能隻是選取的字段不同,那麼這種情況下一個結構體就不夠了,必需為每個查詢定義一個獨一無二的結構體并提供相應的 bind_record 函數(即使這些結構體擁有近似的成員)。這樣簡直是重複造***,難道不能定義一個包含所有字段的“超集”結構體,讓它來包打所有這個表的查詢嗎?有的人可能會想,你把 sql 語句改造一下,每次選取所有字段、多餘的不要用就好了呀!但是這樣肯定不是一個優雅的解決方案,qtl 最新版本中包含了關于這方面的解決方案,那就是自定義綁定,請看下面這個例子:
1 void my_bind(popbox_msg_t&& v, qtl::sqlite::statement& command)
2 {
3 int n = 0;
4 qtl::bind_field(command, n++, v.status);
5 qtl::bind_field(command, n++, v.count);
6 qtl::bind_field(command, n++, v.stamp);
7 }
8
9 void test_query_multi_custom(qtl::sqlite::database &db)
10 {
11 int cnt = 0;
12 db.query_explicit("select status, count, stamp from popbox_msg where appname=?", "GDraw",
13 qtl::custom_bind(popbox_msg_t(), my_bind),
14 [&cnt](popbox_msg_t const& pm){
15 printf("msg: %d, %d, %d\n", pm.status, pm.count, pm.stamp);
16 cnt++;
17 });
18
19 printf("query %d records\n", cnt);
20 }
這個例子可以和前面的 popbox_msg_t 定義及其預設 bind_record 函數放在一起,由于這裡我們使用 query_explicit 接口明确指定了使用的綁定函數是 my_bind,之前定義的預設綁定函數就不再起作用啦。這個查詢隻要表中的三個字段,是以在查詢結束後也隻有三個字段可用。我在下載下傳了最新版本的 qtl 并嘗試編譯這代碼時,編譯器報錯說沒有找到 custom_bind 的定義,我全文搜尋了一下也确實沒有,但是這個例子可是我照着官網寫的啊,難不成作者後來修改了代碼忘記同步文檔了嗎?不得而知。
最後,對于資料庫應用來說,視圖 (view) 和過程 (procedure) 也是資料庫經常接觸到的概念,有的資料庫過程會調用多個 select 語句查詢結果,此時我們的接口又該怎麼接收這些資料呢?答案就是 query_multi 和 query_multi_with_params,它們允許使用者提供多個回調函數,一般就是寫多個 lambda 表達式啦,這樣就可以按過程中調用 select 語句的順序來接收對應的查詢結果了:
1 void qtl::base_database<T, Command>::query_multi<...ValueProc>(const std::string & query_text, ValueProc && ...proc);
2 void qtl::base_database<T, Command>::query_multi<...ValueProc>(const char * query_text, ValueProc && ...proc);
3 void qtl::base_database<T, Command>::query_multi<...ValueProc>(const char * query_text, size_t text_length, ValueProc && ...proc);
4
5 void qtl::base_database<T, Command>::query_multi_with_params<Params, ...ValueProc>(const std::string & query_text, const Params & params, ValueProc && ...proc);
6 void qtl::base_database<T, Command>::query_multi_with_params<Params, ...ValueProc>(const char * query_text, const Params & params, ValueProc && ...proc);
7 void qtl::base_database<T, Command>::query_multi_with_params<Params, ...ValueProc>(const char * query_text, size_t text_length, const Params & params, ValueProc && ...proc);
query_multi_with_params 顧名思義,就是在 query_multi 的基礎上,允許一個額外的輸入綁定參數。當然這個功能比較偏門,我沒有專門寫 demo 去驗證。
下載下傳
本文所有測試用例都是基于擷取并打開 qtl::sqlite::database 對象的基礎,那麼這個對象又是如何打開的呢,請看下面架構:
1 int main(int argc, char* argv[])
2 {
3 int ret = 0;
4 srand(time(0));
5 uint64_t rowid = 0;
6 qtl::sqlite::database db(SQLITE_TIMEOUT);
7
8 try
9 {
10 // copy of gcm.db, and create following table:
11 // create table popmsg (msgid text not null, msgtype integer not null, cid text not null, uid text not null,
12 // status integer not null, count integer not null, msgbody text not null, stamp timestamp not null,
13 // primary key (msgid, msgtype, cid, uid));
14 db.open("../data/gcm.db", NULL);
15 printf("open db OK\n");
16
17 #if 0
18 rowid = test_insert_single(db);
19 #endif
20
21 #if 0
22 test_update_single(db, rowid);
23 #endif
24
25 #if 0
26 test_insert_multi(db);
27 #endif
28
29 #if 0
30 test_update_multi(db);
31 #endif
32
33 #if 0
34 test_delete(db);
35 #endif
36
37 #if 0
38 test_query_single(db, rowid);
39 #endif
40
41 #if 0
42 test_query_single_ex(db, rowid);
43 #endif
44
45 #if 0
46 test_query_multi(db);
47 #endif
48
49 #if 0
50 test_query_multi_ex(db);
51 #endif
52
53 #if 0
54 test_query_multi_ul(db);
55 #endif
56
57 //test_query_multi_custom(db);
58
59 db.close();
60 }
61 catch (qtl::sqlite::error &e)
62 {
63 printf("manipute db error %d: %s\n", e.code(), e.what());
64 db.close();
65 return -1;
66 }
67
68 return 0;
69 }
可以看到資料庫的打開、關閉過程。因為 qtl 檢測到底層資料庫錯誤時,是通過抛出異常的方式來向上層報告的,是以所有用例都包含在 try_catch 結構中。可以通過編譯開關來打開各個用例,多個用例之間可以組合起來使用,例如同時打開 test_insert_single 和 test_query_single 兩個用例。所有相關的内容,包括 qtl、sqlite 頭檔案;sqlite lib 與 dll 和 so;sqlite 樣例資料 db 檔案;甚至編譯好的可執行檔案(Win10 x64 與 Linux x64),我都打包上傳到部落格園了,可以點選 這裡下載下傳。
qtl 庫最新版本不包含在裡面 ,可以從這裡擷取:https://github.com/goodpaperman/qtl
結語
本文并不是 qtl 的使用指南,qtl 的許多内容(事務、語句對象、blob 類型、異步IO、indicator)都沒有介紹。這裡隻是使用 qtl 這個典型的 c++11 庫、以及資料庫的“增删改查”四大操作、來說明新技術是如何"颠覆"使用者調用接口的,以及在一些特定場景下(例如 query_first 既要不定輸入參數,也要不定輸出參數), c++ 新特性是否有可能去滿足這種需求。從這裡也能看出,c++ 的新需求新特性并不是憑空衍生的,而是從類似 qtl 這種模闆庫的實際需要産生的(如何寫出使用者調用更友善的接口),如果我們離開這些場景去學 c++ 新特性,會感到知識點紛繁複雜,而例子又全然不貼切,完全感覺不到新特性解決的痛點。當然 qtl 也不是盡善盡美,例如在使用回調函數處理輸出資料的情況下,能不能給輸入資料來個“不限量”參數清單?qtl 沒有這樣做是 c++ 不支援,還是 qtl 懶沒有做到這一步,這就暫時不得而知了。