1. 综述
其实一看到这两个单词的时候我有点莫名其妙,可能英语没有学好,我的理解就是quoting是“引用”的意思,而Escaping是“逃脱”的意思。后来在看到了作者的TUTORIAL之后才大致明白了两者的意思。
QUOTING大白话就是为SQL语句打上单引号。考虑如下的情况
SELECT * FROM stock WHERE item = 'Hotdog Buns'
由于中间有一个空格,所以这个单引号必不可少。
ESCAPING大白话就是转义。例如在C的printf中,为了打出引号(“),我们需要使用”\””的方式。在SQL语句中,这种情况仍然存在,下文节选自MYSQL C API中的的说明。
Characters encoded are NUL(ASCII 0), “\n”, “\r”, “\”, “'”, “"”, and Control-Z . (Strictly speaking, MySQL requires only that backslash and the quote character used to quote the string in the query be escaped. This function quotes the other characters to make them easier to read in log files.)
例如,下面的语句显然不行
SELECT * FROM stock WHERE item = 'Frank's Brand Hotdog Buns'
需要改成
SELECT * FROM stock WHERE item = 'Frank''s Brand Hotdog Buns'
在MYSQL++中,Quoting和Escaping工作都是通过mysqlpp:: Query在建立SQL语句的时候自动化生成的,而且具体的操作都被定义在了manip.h和manip.cpp中。
同时,在下文中我们可以发现,MYSQL++其实非常的聪明,他可以自动检测出哪些类型是需要进行QUOTING和ESCAPING,哪些是不需要的。即使你为他加入了“建议”QUOTING或者ESCAPING的标志,MYSQL++在内部仍然会自己选择到底做不做QUOTING和ESCAPING。
2. MYSQL++中的Quoting和Escaping的用法
先来看一下到底该怎么用这些功能
string s = "Hotdog Buns";
query << "SELECT * FROM stock WHERE item = " << mysqlpp::quote_only << s;
query << "SELECT * FROM stock WHERE item = " << mysqlpp::quote << s;
可以看到,我们可以通过流式操作来生成SQL串,在需要的地方加上特殊的标志(比如上文中的mysqlpp:: quote_only和mysqlpp:: quote等,具体下文中再说)。大家可能会有疑问,为什么一定要通过这种看似ugly的方法来生成SQL串,为什么不能够直接implicit地为我们把quoting和escaping的事情做完?我在看作者的注释的时候,有这些一句话(摘自,)
You must use manipulators and template query flags as necessary to tell MySQL++ where quoting and escaping is necessary. It would be nice if MySQL++ could do quoting and escaping implicitly based on data type, but this isn’t possible in all cases. Since MySQL++ can’t reliably guess when quoting and escaping is appropriate, and the programmer doesn’t need to, MySQL++ makes you tell it.
在深入源代码之前,我们先来看一下MYSQL++支持哪些QUOTING和ESCAPING方式(即上面代码中的表示加载quoting和escaping的ENUM到底有多少。
类型 | 代码中的说明 | 备注 |
quote | insert into a Query stream to single-quote and escape next item | 需要时为后一项加入必要的单引号和转义 |
quote_only | insert into a std::ostream to single-quote next item | 需要时为后一项加入必要的单引号 |
quote_double_only | insert into a std::ostream to double-quote next item | 需要时为后一项加入必要的双引号 |
escape | 需要时为后一项加入必要的转义 | |
do_nothing | insert into a std::ostream to override manipulation of next item | 下一项什么额外操作都不要做了。该操作在Template Query中有一定的作用。When used with SQLQueryParms it will make sure that it does not get formatted in any way, overriding any setting set by the template query. 具体请参看template Query相关章节。 |
ignore | insert into a std::ostream as a dummy manipulator | 效果和do_nothing类似,只是it will not override formatting set by the template query. It is simply ignored. 具体请参看template Query相关章节。 |
3. MYSQL++中的Quoting和Escaping的实现
通过查看代码,我们可以看到上述几个选项的做法其实都差不多,所以我们主要关注最麻烦的quote,至于有一点点特殊的do_nothing与ignore放到相关章节去做吧。
再来回顾一下QUOTING和ESCAPING怎么用。
mysqlpp:: Query query = …;
query << "SELECT * FROM stock WHERE item = ";
query << mysqlpp::quote << “Hotdog’s Buns”;
我的第一反应是去查看有没有mysql:: Query:: operator<<( ) 的重载方法,通过查看query.h,我发现根本没有这样的方法重载,很可惜,我没有找到
(在query.h中确实有一个全局函数
std::ostream& operator <<(std::ostream& os, Query& q) ,但是从签名和作者的注释中我们可以看到这个函数其实就是为了方便用cout 等进行输出,如cout << query;在通过这个全局函数之后的作用等效于cout << query.str();)
那么这句话为什么能够被编译通过?
原来mysqlpp:: Query继承自std::ostream,说实话,可能是因为我编程经验不够,这种用法我没有怎么见到过,更多的是利用一个delegate,然后自己手动去重载operator << 来实现类似的功能。
言归正传,有了这条线索我们可以继续向下。很显然,上述代码中的query << “SELECT …”这句,其实就是普通的ostream流处理,也就是把字符串放入到ostream自己的缓存中。那么第二句,query << mysqlpp::quote又是在做什么呢?
全局搜了一下quote是什么?
原来是个enum,所以针对query << mysqlpp::quote的用法,找到了如下的代码
又出现了一个神奇的东西——quote_type1,再去看
注意到mysqlpp:: Query继承自std::ostream,所以对于query << mysqlpp:: quote的用法,在operator << (ostream, quote_type0)中,直接就把这个query给保存在了quote_type1的ostr中。那么这个又是做什么?
注意到基本用法中的第二行query << mysqlpp::quote << “Hotdog’s Buns”,刚才说了前半段调用的是operator << (ostream, quote_type0)这个全局方法,他返回的是什么?quote_type1!所以这一行代码接下去的动作一定就是会调用operator << (quote_type1, …)。查了一下果然有!
我应该在介绍SQLTypeAdapter的时候说过,这个类型就是为了做为一个适配器来减少公共函数的数量,他还可以包罗万象,把各种数据类型都包进去。所以对于query << mysqlpp::quote << “Hotdog’s Buns”,显然还会有一步“把Hotdog’s Buns”放入到SQLTypeAdapter中的过程,具体的过程请查看相关章节。
然后就简单了,看上面的代码。
首先,看到了两个强制转换,用的都是dynamic_cast。这里先来补一下dynamic_cast和static_cast的区别。简单理解就是这两者都可以在父类和子类指针或者引用之间相互转化(即父类指针转换为子类指针,或者反之),但是static_cast一定能够成功(即使两者根本就不知父子关系,在后续调用时才会报运行时错),而dynamic_cast则会多一步检查,即检查相互转换的两者是否是可以转换的,如果不能就会返回NULL。也就是说dynamic_cast很像C#关键字as。
第二点需要关注的是SQLStream,这是一个MYSQL++自定义的类型,但是从全局搜索的结果来看,这个类型更多的是用来做测试(因为只出现在了test/manip.cpp中),所以我们可以先忽略这个类型,当然61行的操作也可以仍然psqls结果为NULL。
第三点是怎么做的quoting?显然上述代码片段的65和85行告诉了我们答案(其实就是在需要quoting的item的两边加上了 \’ 而已)。那关键是这里的mysqlpp:: SQLTypeAdapter:: quote_q()做了什么(65行)?
其中buffer_是个SQLBuffer
这个SQLBuffer:: quote_q( )主要要对一个表示timestamp的NOW()进行过滤,主要原因是,下面的SQL语句是合法的Insert into tbl(col_time_stamp) values(NOW()); 此时的NOW()表示的是aggregate函数。然后就交给mysql_type_info:: quote_q( )进行检测。
然后需要关注的就是68行和73行,其中68行的作用是检测输入的内容是否需要被escapte。检测方法其实和上面很类似,也就是利用SQLBuffer:: escape_q()
第73行 pq->escape_string(&escaped, in.data(), in.length());
这一行其实就是就是调用了mysqlpp:: Query:: escape_string()方法,
然后直接调用DbDriver的escape_string方法,该方法其实就是对mysql_real_escape_string的包装而已(该函数在官网上的说法很简单,This function is used to create a legal SQL string that you can use in an SQL statement)。
原创作品,转载请注明出处。