【原創】12. MYSQL++之Template Query

1. 什麼是Template Queryhtml

在咱們實際的編程過程當中,咱們很容易碰到printf這類須要在運行時來決定到底打印出什麼的函數,例如mysql

printf(「hello %s」, sth);

在這個例子中,那個%s佔位符代表了咱們之後但願打印的內容格式和位置。一樣,在咱們書寫SQL語句的時候,也會出現這樣的狀況,例如sql

SELECT * from tbl1 WHERE id = ???

咱們頗有可能在代碼中會根據用戶的輸入來決定如何寫入這個???。一種解決辦法是,咱們在程序中本身記錄除了???直往外的內容,而後每次拼接出這句SQL。另外一種辦法就是使用MYSQL++的Template Query功能。編程

說到底,Template Query就是讓咱們省去每次去作「拼接」的過程。MYSQL++引擎會爲咱們作這一切事情。緩存

 

2. 如何使用Template Queryide

  • 基本用法

經過做者爲咱們準備的manual以及tquery*.cpp這幾個示例,咱們能夠總結出如下兩種廣泛的用法。函數

    • 方法一:利用參數傳入實際值
 
mysqlpp::Query query = con.query("select * from stock where item = %0q");
 
query.parse();
 
mysqlpp::StoreQueryResult res1 = query.store("Nürnberger Brats");
 

這個用法總結起來就是先設置template,而後調用Query:: store, Query:: exec, Query:: storein等等用於執行SQL語句的方法,而後把template中的佔位符的內容「依次」(這個「依次」有玄機,見下文)寫入這些方法的入參。this

須要注意的是,上文中的%0,%1等表示輸入的參數的順序(可是在template中,%0,%1等數字能夠任意排放),這個必須和Query:: store等傳入的參數的順序匹配。spa

 

    • 方法二:利用mysqlpp:: SQLQueryParms傳入參數
mysqlpp::Query query = con.query("select * from stock where item = %0q");
 
query.parse();
 
mysqlpp::SQLQueryParms sqp;
 
sqp << "hello";
 
mysqlpp::StoreQueryResult res1 = query.store(sqp);
 

大致上和普通用法一致,主要的區別在於對於Query:: store, Query:: exec等執行函數的參數上,再也不使用逐個輸入,而是相似於流式輸入通常,先將全部的內容寫入到mysqlpp:: SQLQueryParms中,而後一同當作入參。我認爲此法換湯不換藥。可是做者認爲,提供這個機制的緣由在於This is useful when the calling code doesn't know in advance how many parameters there will be. This is most likely because the templates are coming from somewhere else, or being generated..net

 

    • 方法三:默認參數
query << "select (%2:field1, %3:field2) from stock where %1:wheref = %0q:what";
 
query.parse();
 
query.template_defaults[1] = "item";
 
query.template_defaults["wheref"] = "item";

上面的兩個query.template_defaults是一個效果,其中,第一個是使用了位置,第二個是使用了名字。有了這個默認參數以後,在後續的store等調用中,就不須要給這些參數進行設置了。

值得注意的是,MYSQL++的默認參數和C++的默參機制同樣,都是隻支持默認參數放在最後。這也就解釋了爲何要爲參數設置編號了(好比上面的%2:field1等)。

 

    • 方法四:重置template query

當咱們在一句template query以後,想要從新利用這個Query變量,應該如何通知MYSQL++來重用?query.reset() !

      • 佔位符

在做者的manual中專門有一個章節提到了佔位符。我這裏只是傳達一下意思,

query << "select (%2:field1, %3:field2) from stock where %1:wheref = %0q:what";

 

上文中的%0q:what就是一個佔位符。他的規範模式是這樣的

%###(modifier)(:name)(:)

我以爲最後一個冒號(colon)能夠先行忽略,它的做用就是爲了防止你的name當中出現了冒號。根據做者的解釋,###是數字,modifier主要有如下幾個

符號

含義

%

須要打印出%

「」

告訴MYSQL++引擎不要quote和escape

q

告訴MYSQL++引擎根據須要quote和escape

Q

告訴MYSQL++引擎根據須要quote而不管如何不要escape

至於那個name,個人理解就是一個助記符。由於MYSQL++支持同時用位置和名字對參數進行設置,因此這個名字就有那麼點意思了。

 

3. MYSQL++ Template Query的實現機理

在開始真正的探索Teamplate Query以前,咱們須要先從他會用到的一些幫手入手,瞭解一些蛛絲馬跡。

clip_image001

  • SQLQueryParms

從做者的說明上來看,該類型的做用就是爲了給Template Query作參數填入的。因此猜想該類型的功能不該該不少,更多的應該是一些輔助功能。

從實現上來看,該類型繼承了vector<mysqlpp:: SQLTypeAdapter>,並從新實現了一系列的set方法(下圖的代碼片斷中所用到的operator<<是被SQLQueryParms override的,實際上就是一句push_back)

clip_image003

另外,該類型還提供了對於參數的escape操做,

clip_image005

而上面的parent_是一個Query*類型,這個在後面講mysqlpp:: Query構造器的時候會很容易看到的。

這個類型還有一些東西是須要關注的,主要是覆蓋(overwrite,不是重載override)了std::vector的一些如operator[ ]等定位、置放的方法,舉例以下

clip_image007

其實主要就是對於不在size範圍內的索引,不是返回錯誤,而是進行拓展。爲何須要這麼作?考慮一下template query的默認參數。若是template總共有4個佔位符(params),只有最後一個param是有默參的。因此很顯然,咱們會有

 

my_query.template_defaults[3] = 「XXXYYY」

 

這樣,咱們就須要拓展這個template_defaults(他就是一個SQLQueryParms類型)的容量到4,並且須要把第四個元素替換成「XXXYYY」。

 

  • mysqlpp:: Query對template query的支持
    • 成員變量

我認爲要理解Template Query的機制仍是要從mysqlpp:: Query入手。

看實現以前,咱們先來看一下一些稍後會用到的Query成員變量

 

// Used for filling in parameterized queries.
 
SQLQueryParms template_defaults;
 

該變量的做用就是爲template query(第二種用法)的params提供一個存儲空間。

 

如下3個變量是相輔相成的,配合起來表達的就是一個意思——提供template query(第一種用法)保存params的空間,以及其對應的名字(記得上述的佔位符中的name?)和對應的位置(上述佔位符中%xxx的那個xxx)

// List of template query parameters
 
std::vector<SQLParseElement> parse_elems_;
 

該變量保存變量的方式是「順序」保存最原始的template中的表示變量的部分(%後面的部分)。例如,若是template是「select * from %2q:ParamA, %1:ParamB」,則在parse_elems_中的排列順序就是[0]==ParamA, [1]==ParamB。

 
// Maps template parameter position values to the corresponding parameter name.
 
std::vector<std::string> parsed_names_;
 

該變量保存變量的方式是根據最原始的template中的表示變量的部分(%後面的部分)的真實順序保存在對應的index下。例如,若是template是「select * from %2q:ParamA, %1:ParamB where %0q:ParamC」,則在parse_elems_中的排列順序就是[0]==ParamC, [1]==ParamB, [2]==ParamA。

// Maps template parameter names to their position value.
 
std::map<std::string, short int> parsed_nums_;
 

這個是名字到索引的對應map。Key就是名字,而value是該parameter在parse_names_(見上面)中的位置。

// String buffer for storing assembled query
 
std::stringbuf sbuffer_;
 

 

    • 成員函數

咱們先以第一種用法進行代碼解析。

mysqlpp:: Query query = con.query(「select * from stock where item = %0q」);
 
query.parse();
 
// 執行方法一:
 
query.store(「hello」);
 
// 執行方法二:
 
SQLQueryParms sqlQueryParms;
 
sqlQueryParms << 「hello」;
 
query.store(sqlQueryParms);
 
      • 準備template query

從上面的代碼,線索在於Connection:: query( )方法。

clip_image008

看一下Query的構造函數,其實就是把mysqlpp::Connection放入到Query本身的變量中,把qstr表明的語句放入到本身的緩存(sbuffer_)中,最後再初始化了一個SQLQueryParms變量。

clip_image010

具體來看一下這個sbuffer_吧,他其實就是std::stringbuf。而這個SQLQueryParms類型的template_defaults變量的構造器調用上文在介紹SQLQueryParms的時候已經解釋過了。再也不贅述。

 

      • 解析template query

Template query的關鍵就是Query:: parse方法,爲何每次在使用template query以前必定要調用這個方法?由於正是這個方法告訴了mysql++,你將要使用的是template query! This method sets up the internal structures used by all of the other members that accept template query parameters.

接下去的問題是,那個所謂的」internal structures」是什麼?(因爲代碼比較長,這裏只貼出來該方法的一些特別的小片斷了,具體實現能夠本身去看query.cpp中的Query::parse()方法實現。)

其實這個Query::parse()主要作的事情就是解析已經被保存在sbuffer_中的字符串(對的,就是剛纔講的基本的template)。至於怎麼解析?關鍵點也在以前講過了,就是那個佔位符(%###(modifier)(:name)(:))!

clip_image012

其實這裏的作法就是一個個字符解析,遇到了「%」表示可能遇到了佔位符。記得上面講過,若是連續兩個%%,那麼就表示咱們須要的真的就是一個「%」而已(上面代碼中的282行到285行)。

雜事略過,反正該方法查看到數字(上述代碼第286行),就知道了咱們填入了一個參數之後在Query:: store、Query:: exec等方法中傳入的參數所對應的位置索引(就是下面代碼中的num),此時他不急着去更改Query類型內部變量,而是繼續找那個option。

clip_image013

等這些信息都有了以後,該方法作了以下工做

parse_elems_.push_back(SQLParseElement(str, option, n));

你徹底能夠把SQLParseElement當作一個結構體,他就是保存了一些信息(包括了在此param以前的全部語句(截止到上一個param,其實就是以形參爲斷點,把plain text分離,例如。。。。)——保存在str中,具體的option以及相對應的index)。

隨後,parse()方法又去看了那個name。若是有的話(其實他主要看在module以後的那個冒號),解析出來以後成對地加入到Query自身的變量中。

clip_image014

注意,這裏略去了一些不是很重要的細節。例如parsed_names_是一個vector而不是一個map,因此上面的片斷以前會有根據n(即剛纔根據佔位符解析出來的數字)和parsed_names_.size()的比較的過程,若是n大的話,就擴充這個vector,這樣作的目的其實就是爲了讓param在其對應的位置上(並且容許用戶隨意排放param位置)。

在parse()方法的最後,做者對parse_elems_作了一個標記,表示這是最後一個param。

clip_image016

爲何必定要多一個標誌?立刻來解釋。

 

      • 執行template query

上面講到了template query的兩種執行方法,其實殊途同歸,異曲同工。劇透一下,最終調用的都是Query:: store(SQLQueryParms& p)。

剛纔已經解析過了Query:: parse(),對於執行而言,就是Query:: store(const SQLTypeAdapter&) (或者Query:: store(SQLQueryParms& p))。

clip_image018

一看到這裏,其實個人第一反應是,爲何505行會有這個size等於2的判斷?從代碼中來看,咱們確實能夠直接像下面這樣用store.

mysqlpp:: Query query = con.query();
 
query.store(「select * from stock」);
 

順便說一句爲何能夠有這麼直白的寫法,mysqlpp:: Connection有一個query方法(上面第一行代碼),它的參數有默認參數

clip_image019

可是他的實現仍是若無其事地傳遞給了Query

clip_image020

惋惜Query:: Query方法會檢查這個指針,若是爲0,那麼直接就不填寫sbuffer_。

clip_image022

言歸正傳,根據上面的例子,咱們能夠看到Query:: Store方法既能夠表示template query也能夠表示non-template query。這二者怎麼區別?回到Query:: store(const SQLTypeAdapter&)的源代碼(爲了方便,這裏再展現一次)

clip_image023

他首先檢查size。爲何是與2比較?記得以前在講Query:: parse的最後專門會多往parse_elems_中插入一個表示結束的標記?這個標記就佔了一個坑,若是是template query,那麼至少還會有一個佔位符,因此這個佔位符又是一個坑?因此通了嘛。若是真的是template query,你又用了單參數的Query:: store(好比這裏所展現的這個),那麼根據用法你傳入的必定就是這個param的實參。

順便說一句,如今明着的Query::store版本只有傳入單個const SQLTypeAdapter&的版本,以及傳入SQLQueryParms的版本,若是你有多個params怎麼利用Query:: store傳入?哦!咱們能夠申請SQLQueryParms,而後一個個「operator << 」進來。可是從manual中能夠看到,貌似是有支持多個const SQLTypeAdapter&的版本的,他們在哪裏?他們應該使用perl腳本生成的。詳見http://tangentsoft.net/mysql++/doc/html/userman/configuration.html#max-fields。其中的代碼和這裏很像,只不過那個size==2的檢測變成了size==3,size==4等。

再回到咱們的主線,若是調用Query:: store(const SQLTypeAdapter&)的時候其實只是想做爲non-template query來處理的,那麼請見介紹mysql:: Query的相關章節,咱們這裏只關注template query。

看到上面的代碼的510行,他直接經過一個RAII機制把processing_標誌置爲true(當過了這個AutoFlag的做用域,這個標誌會被置爲false的)。而後在511行,調用了SQLQueryParms:: operator << ()方法(這也就是我爲何說兩個Query:: store() 異曲同工了)。

clip_image025

以前介紹過,SQLQueryParms繼承自vector,因此很容易理解下面的代碼,須要注意的是返回值仍是一個SQLQueryParms。

因此,第511行最後調用的是Query::store(SQLQueryParms& p)。那咱們來看這裏

clip_image027

又要看一下Query:: str(SQLQueryParms& p)

clip_image029

很好猜,這裏必定對sbuffer作了什麼。來看一下Query:: proc。

clip_image031

首先,他直接把sbuffer_給清空了!這裏應該是要從新塑造這句query吧。而後遍歷全部的parse_elems_。

注意到,在433行,MYSQL++先行將那些plain query部分(即那些固定的template)先寫進本身的緩存(以前有講過,在作parse()的時候,會根據params把證據template進行拆分,在某個param以前,前一個param以後的部分都會被插入到SQLParseElement的before變量中,方便這裏的順序拼接)。

那麼436到447在幹什麼?想一下,template query是支持默認參數的,並且設置默認參數和設置template的語句是分開的,因此若是咱們須要從新拼接query(即把template 和對應的param放到一塊兒去)必需要有必定的機制來找到傳入的實參吧,這些實參多是傳入的參數也多是默認參數!因此這麼幾行的做用就是用來判斷拼接語句所須要的實參究竟是在哪裏。

如何斷定?記得在Query:: parse()中,咱們根據template中的參數的看見順序填寫到parse_elems_對應位置上(例如,若是有%8q,那麼在parse_elems_[7]上才存了這個變量),同時相似的概念也出如今template_defaults上,這是一個SQLQueryParms變量,他也有自動擴充的功能(見上文)。在Query:: parse()時,咱們記錄下了這個數字(例如,%8q中的這個8),讓他與咱們傳入的全部參數的數量進行比較。因爲咱們老是假設用戶給過來的順序插入SQLQueryParms的params就是按照對應%0,%1,%2。。。的順序來的,因此能夠斷定若是該數字大於傳入的全部參數的size,則它就在template_defaults中。

順便說一句如何去理解445行的"Not enough parameters to fill the template."這個異常。若是咱們有以下的語句

Query q = con.query(「select * from %9q」);
 
q.store(「abc」);
 

顯然,在434行獲得的num就是9,而實際上實參的size爲1,且template_defaults.size爲0,因此,這個時候顯然就是"Not enough parameters to fill the template."。

從449行開始到最後就是真正的填寫參數的過程。

再來囉嗦一句,爲何要判一句if (param.is_null()),由於SQL中能夠容許NULL,而C++的字符串的」NULL」和SQL的NULL的含義是不同的,爲了解決這個問題,MYSQL++提供了一個Null<T>類型,能夠參看Null<T>的說明。若是咱們須要在SQL語句中寫入

select * fromwhere item = NULL

這句話的含義徹底不一樣於

select * fromwhere item = 「NULL

爲了讓這種SQL NULL在template query中也可行,咱們能夠這樣寫

Query q = con.query(「select * from tblA where item = %0」」 「);
 
q.store(mysqlpp::null);
 

 

再來講一下Query::pprepare(char option, SQLTypeAdapter& S, bool replace),爲何在處理非SQL NULL實參的時候須要通過這個函數過濾?由於咱們尚未處理option呢,也就是」%xxxQ」中的這個Q(另外還有q,」」等)。在這個函數中就是處理這些option,爲該作escape和quote的地方按照要求決定是MYSQL++爲調用者加(若是option是q,Q)仍是不管如何都放棄(若是option是」」)。固然,該方法也會把在形參(例如%xxxQ)以前的plain text也一同返回出來拼出一句合格的SQL語句。因爲展開該方法會對主線有點偏離,因此你們能夠自行看該函數的處理過程(在query.cpp中)。須要注意的是,在這個方法中可能會new出一些變量,須要在外面delete(例如456到460所示那樣)。

最後來講一下什麼是MYSQLPP_QUERY_THISPTR,這個宏真正的定義是#define MYSQLPP_QUERY_THISPTR dynamic_cast<std::ostream&>(*this)。

那麼455行的MYSQLPP_QUERY_THISPTR << *ss;也就好追蹤了。這個宏把Query對象給強制轉換爲其父類std::ostream,而後經過operator <<() 方法給*ss所表明的字符串放入自身的緩存。這個緩存其實就是sbuffer_,由於在Query的構造函數中就有對於ostream:: init(sbuffer_)的操做。

當這個proc()返回以後,就填寫完了sbuffer_,隨後就回到了那個non-template query的Query:: store()調用了。世界終於清靜了……

 

 

原創做品,轉載請註明出處www.cnblogs.com/aicro

相關文章
相關標籤/搜索