1. 綜述
其實一看到這兩個單詞的時候我有點莫名其妙,可能英語沒有學好,我的理解就是quoting是“引用”的意思,而Escaping是“逃脫”的意思。后來在看到了作者的TUTORIAL之后才大致明白了兩者的意思。
QUOTING大白話就是為SQL語句打上單引號。考慮如下的情況
SELECT * FROM stock WHERE item = 'Hotdog Buns'
由於中間有一個空格,所以這個單引號必不可少。
ESCAPING大白話就是轉義。例如在C的printf中,為了打出引號(“),我們需要使用”\””的方式。在SQL語句中,這種情況仍然存在,下文節選自MYSQL C API中的mysql_real_escape_string的說明。
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的事情做完?我在看作者的注釋的時候,有這些一句話(摘自,http://tangentsoft.net/mysql++/doc/html/userman/tutorial.html#qescape)
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)。
原創作品,轉載請注明出處www.cnblogs.com/aicro。