微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

栏目: 数据库 · 发布时间: 7年前

内容简介:微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

背景

高效、完整、易用是WCDB的基本原则。前几篇文章分享了WCDB的基本用法和修复工具,接下来将更深入地聊聊WCDB在易用性上的思考和实践。

对于各类客户端数据库,似乎都绕不开拼接字符串这一步。即便在Realm这样的NoSQL的数据库中,在进行查询时,也依赖于字符串的语法:

别看小小的字符串拼接,带来的麻烦可不小:

  • 代码冗余。为了拼接出匹配的 SQL 语句,业务层往往要写许多胶水代码来format字符串。这些代码冗长且没有什么“营养”。

  • 难以查错。对于编译器而言,SQL只是一个字符串。这就意味着即便你只写错了一个字母,也得在代码run起来之后,通过log或断点才能发现错误。倘若SQL所在的代码文件依赖较多,即使改正一个敲错的字母,就得将整个工程重新编译一遍,简直是浪费生命。

  • SQL注入。举一个简单的例子:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

这是插入消息的SQL。倘若对方发来这样的消息: ');DELETE FROM message;-- ,那么这个插入的SQL就会被分成三段进行解析:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

它会在插入一句空消息后,将message表内的所有消息删除。若App内存在这样的漏洞被坏人所用,后果不堪设想。

反注入的通常做法是,

  • 利用 SQLite 的绑定参数。通过绑定参数避免字符串拼接。

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

  • 对于不适用绑定参数的SQL,则可以将单引号替换成双单引号,避免传入的单引号提前截断SQL。

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

尽管反注入并不难,但要求业务开发都了解、并且在开发过程中时时刻刻都警惕着SQL注入,是不现实的。

一旦错过了在框架层统一解决这些问题的机会,后面再通过代码规范、Code Review等等人为的方式去管理,就难免会发生疏漏。

因此,WCDB的原则是,问题应当更早发现更早解决。

  • 能在编译期发现的问题,就不要拖到运行时;

  • 能在框架层解决的问题,就不要再让业务去分担。

基于这个原则,我开始进行对SQLite的接口的抽象。

SQL的组合能力

思考的过程注定不会是一片坦途,我遇到的第一个挑战就是:

问题一:SQL应该怎么抽象?

SQL是千变万化的,它可以是一个很简单的查询,例如:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

这个查询只是取出message表中的所有元素。假设我们可以封装成接口:

但SQL也可以是一个很复杂的查询,例如:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

这个查询包含了条件、分组、分组过滤、 排序 、限制、聚合函数、子查询,多表查询。什么样的接口才能兼容这样的SQL?

遇到这种两极分化的问题,我的思路通常是二八原则。即

  • 封装常用操作,覆盖80%的使用场景。

  • 暴露底层接口,适配剩余20%的特殊情况。

但更多的问题出现:

问题二:怎么定义常用操作?

  • 对于微信常用的操作,是否也适用于所有开发者?

  • 现在不使用的操作,以后是否会变成常用?

问题三:常用操作与常用操作的组合,是否仍属于常用操作?

查询某个字段的最大值或最小值,应该属于常用操作的:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

假设可以封装为

但,SQL是存在组合的能力的。同时查询最大值和最小值,是否仍属于常用操作?

若以此规则,继续封装为:

显然,“常用接口”的定义在不断地扩大,接口的复杂性也在增加。以后维护起来,就会疲于加新接口,并且没有边界。

问题四:特殊场景所暴露的底层接口,应该以什么形式存在?

若底层接口还是接受字符串参数的传入,那么前面所思考的一切都是徒劳。

SQL语法规则

显然,基于上述问题,我需要一个理论基础,去支持我的封装是合理的,而不仅仅是堆砌接口。

于是,我找到造成 SQL千变万化组合的根源 --- SQL 语法规则:http://www.sqlite.org/lang.html

例如,这是一个 SELECT 语句的语法规则:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

SQLite按照图示箭头流向的语法规则解析传入的SQL字符串。每个箭头都有不同的流向可选。

例如, SELECT 后,可以直接接 result-column ,也可以插入 DISTINCT 或者 ALL

语法规则中的每个字段都有其对应涵义,其中

  • SELECTDISTINCTALL 等等大写字母是 keyword ,属于SQL的保留字。

  • result-column、``table-or-subqueryexpr 等等小写字母是token。token可以再进一步地展开其构成的语法规则。

例如,在 WHEREGROUP BYHAVINGLIMITOFFSET 后所跟的参数都是 expr ,它的展开如下:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

可以看到, expr 有很多种构成方式,例如:

  • expr : literal-valueliteral-value 可以进一步展开,它是纯粹的数值。

    • 如数字1、数字30、字符串"Hello"等都是 literal-value ,因此它们也是 expr

  • expr : expr (binary operator) expr 。两个 expr 通过二元操作符进行连接,其结果依然属于 expr

    • 如1+"Hello"。1和"Hello"都是 literal-value ,因此它们都是 expr ,通过二元操作符"+"号连接,其结果仍然是一个 expr 。尽管1+"Hello"看上去没有实质的意义,但它仍是SQL正确的语法。

以刚才那个复杂的SQL中的查询语句为例:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

  1. content IS NOT NULL ,符合 expr IS NOT NULL 的语法,因此其可以归并为 expr

  2. createTime!=modifiedTime ,符合 expr (binary operator) expr 的语法,因此其可以归并为 expr

  3. type NOT BETWEEN 0 AND 2 ,符合 expr NOT BETWEEN expr AND expr 的语法,因此其可以归并为 expr

  4. 1. AND 2. ,符合 expr (binary operator) expr 的语法,因此其可以归并为 expr

  5. 4. OR 3. ,符合 expr (binary operator) expr 的语法,因此其可以归并为 expr

最终,这么长的条件语句归并为了一个 expr ,符合 SELECT 语法规则中 WHERE expr 的语法,因此是正确的SQL条件语句。

也正是基于此,可以得出:只要按照SQL的语法封装,就可以保留其组合的能力,就不会错过任何接口,落入疲于加接口的陷阱。

WCDB的具体做法是:

  1. 将固定的keyword,封装为函数名,作为连接。

  2. 将可以展开的token,封装为类,并在类内实现其不同的组合。

以SELECT语句为例:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

在语法规则中, WHERELIMIT 等都接受 expr 作为参数。因此,不管SQL多么复杂, StatementSelect 也只接受 Expr 的参数。而其组合的能力,则在 Expr 类内实现。

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

Expr 通过构造函数和C++的偏特化模版,实现了从字符串和数字等进行初始化的效果。同时,通过C++运算符重载的特性,可以将SQL的运算符无损地移植到过来,使得语法上也可以更接近于SQL。

在对应函数里,再进行SQL的字符串拼接即可。同时,所有传入的字符串都会在这一层预处理,以防注入。如:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

基于这个抽象方式,就可以对复杂查询中的条件语句进行重写为:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

首先通过 Column 创建对应数据库字段的映射,再转换为 Expr ,调用对应封装的函数或运算符,即可完成字符串拼接操作。

这个抽象便是WCDB的语言集成查询的特性 --- WINQ( W CDB In tegrated Q uery)。
WINQ+ORM

更进一步,由于WCDB在接口层的ORM封装,使得开发者可以直接通过 className.propertyName 的方式,拿到字段的映射。因此连上述的转换操作也可以省去,查询代码可以在一行代码内完成。

以下是WCDB在接口层和WINQ的支持下,对前面所提到的SQL语句的代码示例:

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

总结

WCDB通过WINQ抽象SQLite语法规则,使得开发者可以告别字符串拼接的胶水代码。通过和接口层的ORM结合,使得即便是很复杂的查询,也可以通过一行代码完成。并借助IDE的代码提示和编译检查的特性,大大提升了开发效率。同时还内建了反注入的保护。

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

(代码提示)

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇

(编译时检查)

虽然WINQ在实现上使用了C++11特性和模版等,但在使用过程并不需要涉及。对于熟悉SQL的开发,只需按照本能即可写出SQL对应的WINQ语句。最终达到提高WCDB易用性的目的。 同时,基于C++的实现也使得WINQ在性能可以期待。

后续我们还将分享WCDB在多线程管理上的思考。

扫码关注WCDB,来Github给我们 star 吧!

微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇


以上所述就是小编给大家介绍的《微信移动端数据库组件 WCDB 系列(三):WINQ 原理篇》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

UNIX网络编程 卷2

UNIX网络编程 卷2

W.Richard Stevens / 人民邮电出版社 / 2009-11 / 89.00元

《UNIX网络编程 卷2:进程间通信(英文版·第2版)》是一部UNIX网络编程的经典之作。进程间通信(IPC)几乎是所有Unix程序性能的关键,理解IPC也是理解如何开发不同主机间网络应用程序的必要条件。《UNIX网络编程 卷2:进程间通信(英文版·第2版)》从对Posix IPC和System V IPC的内部结构开始讨论,全面深入地介绍了4种IPC形式:消息传递(管道、FIFO、消息队列)、同......一起来看看 《UNIX网络编程 卷2》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具