之前写过一篇文章专门分析了 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 懒没有做到这一步,这就暂时不得而知了。

c++ 11 是如何简化你的数据库访问接口的的更多相关文章

  1. 数据库访问接口(ODBC,OLEDB,ADO)

    数据库访问接口发展历史 ODBC历史 ODBC(Open Database Connectivity,开放数据库互连).要了解ODBC是什么,先了解一下数据库连接的相关知识.在最开始连接数据库时,由于 ...

  2. 数据库访问接口之ODBC

    ODBC API 实现数据库操作的手段是句柄.在ODBC中,使用不同的句柄(HANDLE)来标志环境(environment).连接(Connection).语句(statement).描述符(des ...

  3. 数据库访问接口(ODBC、OLE DB、ADO)

    最近在学C#的数据库编程,对于数据库接口技术这块的知识一直比较模糊,网上查了不少资料,看了几天还是朦朦胧胧的,只能做些笔记再研究了. 我们都知道,“数据库”是指一组相关信息的集合,最早的计算机应用之一 ...

  4. Spring Boot初探之数据库访问

    一.背景 Spring boot是集服务发布.数据库管理.日志管理等于一身的服务开发框架:是微服务开发的全能小帮手.这章讲述一下如何使用spring boot访问MySQL数据库. 二.搭建基础环境 ...

  5. Oracle从11.2.0.2开始,数据库补丁包是一个完整安装包(转)

    从11.2.0.2开始,数据库补丁包是一个完整安装包.也就是说:比如要打11.2.0.2的补丁包,直接用11.2.0.2包来安装就可以了,不需要像10G一样先安装数据库软件再来打补丁包. 如果已经安装 ...

  6. 使用Spring简化JDBC操作数据库

    Spring的开发初衷是为了减轻企业级开发的复杂度,其对数据库访问的支持亦如此,使用Spring访问数据库能带来以下好处: 1.1     简化代码 使用原生的JDBC访问数据库,一般总是要执行以下步 ...

  7. AIX6.1/11.2.0.3在有关数据库SWAP一个BUG

    昨天南京到客户服务数据库的优化调整,其中新上线,经过审查alert.log当日志现在是在过去一段时间内取得,每隔几个小时的时间滞后,班会报似的内容: Thu Aug 21 09:01:26 2014 ...

  8. 2017.11.5 Java Web ----案例:数据库访问JavaBean的设计

    (12)案例----数据库访问JavaBean的设计 例题:数据库操作在一个Web应用程序中的后台处理中占有大比重,设计一组JavaBean封装数据库的基本操作供上层模块调用,提高程序的可移植性. [ ...

  9. 三种数据库访问——Spring JDBC

    本篇随笔是上两篇的延续:三种数据库访问——原生JDBC:数据库连接池:Druid Spring的JDBC框架 Spring JDBC提供了一套JDBC抽象框架,用于简化JDBC开发. Spring主要 ...

随机推荐

  1. 004、Python xlsxwriter模块

    简单用法demo # !/usr/bin/python # coding:utf-8 # xlsxwriter的基本用法 import xlsxwriter # 1. 创建一个Excel文件 work ...

  2. 001、Python数据结构

    #! usr/bin/env python # _*_ coding:utf-8 _*_ from random import randint ''' 一.list的增删改查 1.list.appen ...

  3. linux环境下使用jmeter进行分布式测试

    1.前言 熟练使用jmeter进行性能测试的工程师都知道,jmeter的客户端性能是有点差的.这会导致一个问题,其客户端的性能损耗会干扰到性能测试的结果,而且当线程数/并发大到一定程度时,客户端性能会 ...

  4. Filebeat 日志收集

    Filebeat 介绍 Filebeat 安装 # 上传代码包 [root@redis03 ~]# rz filebeat-6.6.0-x86_64.rpm # 安装 [root@redis03 ~] ...

  5. windows 命令行 cmd 控制exe程序输入输出并比较

    参考 https://www.cnblogs.com/zccz14/p/4588634.html 例子: 对exe输入输出 使用fc比较不同

  6. map最最最基本用法

    map<a,b>c中,a,b是变量类型 参数定义的map的名字 #include<stdio.h> #include<map> //头文件 map<int,c ...

  7. Inkscape svg彩色图转灰度图

    操作: Ctrl+A 全选所有对象, 然后使用滤镜渲染成灰度图, 至于这个RGB比值, 看个人需求, 标准情况下rgb2gray是0.299 * R + 0.587 * G + 0.114 * B 原 ...

  8. HTML5 in depth

    HTML5 in depth Content Models Web Storage web storage 存储用户信息, 替代 cookies LocalStorage SessionStorage ...

  9. chrome devtools dark mode

    chrome devtools dark mode default dark mode https://medium.com/@eshwaren/dark-theme-for-chrome-devel ...

  10. nasm astrcpy_s函数 x86

    xxx.asm %define p1 ebp+8 %define p2 ebp+12 %define p3 ebp+16 section .text global dllmain export ast ...