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::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以后,想要从新利用这个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以前,咱们须要先从他会用到的一些帮手入手,了解一些蛛丝马迹。
从做者的说明上来看,该类型的做用就是为了给Template Query作参数填入的。因此猜想该类型的功能不该该不少,更多的应该是一些辅助功能。
从实现上来看,该类型继承了vector<mysqlpp:: SQLTypeAdapter>,并从新实现了一系列的set方法(下图的代码片断中所用到的operator<<是被SQLQueryParms override的,实际上就是一句push_back)
另外,该类型还提供了对于参数的escape操做,
而上面的parent_是一个Query*类型,这个在后面讲mysqlpp:: Query构造器的时候会很容易看到的。
这个类型还有一些东西是须要关注的,主要是覆盖(overwrite,不是重载override)了std::vector的一些如operator[ ]等定位、置放的方法,举例以下
其实主要就是对于不在size范围内的索引,不是返回错误,而是进行拓展。为何须要这么作?考虑一下template query的默认参数。若是template总共有4个占位符(params),只有最后一个param是有默参的。因此很显然,咱们会有
my_query.template_defaults[3] = “XXXYYY”
这样,咱们就须要拓展这个template_defaults(他就是一个SQLQueryParms类型)的容量到4,并且须要把第四个元素替换成“XXXYYY”。
我认为要理解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);
从上面的代码,线索在于Connection:: query( )方法。
看一下Query的构造函数,其实就是把mysqlpp::Connection放入到Query本身的变量中,把qstr表明的语句放入到本身的缓存(sbuffer_)中,最后再初始化了一个SQLQueryParms变量。
具体来看一下这个sbuffer_吧,他其实就是std::stringbuf。而这个SQLQueryParms类型的template_defaults变量的构造器调用上文在介绍SQLQueryParms的时候已经解释过了。再也不赘述。
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)(:))!
其实这里的作法就是一个个字符解析,遇到了“%”表示可能遇到了占位符。记得上面讲过,若是连续两个%%,那么就表示咱们须要的真的就是一个“%”而已(上面代码中的282行到285行)。
杂事略过,反正该方法查看到数字(上述代码第286行),就知道了咱们填入了一个参数之后在Query:: store、Query:: exec等方法中传入的参数所对应的位置索引(就是下面代码中的num),此时他不急着去更改Query类型内部变量,而是继续找那个option。
等这些信息都有了以后,该方法作了以下工做
parse_elems_.push_back(SQLParseElement(str, option, n));
你彻底能够把SQLParseElement当作一个结构体,他就是保存了一些信息(包括了在此param以前的全部语句(截止到上一个param,其实就是以形参为断点,把plain text分离,例如。。。。)——保存在str中,具体的option以及相对应的index)。
随后,parse()方法又去看了那个name。若是有的话(其实他主要看在module以后的那个冒号),解析出来以后成对地加入到Query自身的变量中。
注意,这里略去了一些不是很重要的细节。例如parsed_names_是一个vector而不是一个map,因此上面的片断以前会有根据n(即刚才根据占位符解析出来的数字)和parsed_names_.size()的比较的过程,若是n大的话,就扩充这个vector,这样作的目的其实就是为了让param在其对应的位置上(并且容许用户随意排放param位置)。
在parse()方法的最后,做者对parse_elems_作了一个标记,表示这是最后一个param。
为何必定要多一个标志?立刻来解释。
上面讲到了template query的两种执行方法,其实殊途同归,异曲同工。剧透一下,最终调用的都是Query:: store(SQLQueryParms& p)。
刚才已经解析过了Query:: parse(),对于执行而言,就是Query:: store(const SQLTypeAdapter&) (或者Query:: store(SQLQueryParms& p))。
一看到这里,其实个人第一反应是,为何505行会有这个size等于2的判断?从代码中来看,咱们确实能够直接像下面这样用store.
mysqlpp:: Query query = con.query();
query.store(“select * from stock”);
顺便说一句为何能够有这么直白的写法,mysqlpp:: Connection有一个query方法(上面第一行代码),它的参数有默认参数
可是他的实现仍是若无其事地传递给了Query
惋惜Query:: Query方法会检查这个指针,若是为0,那么直接就不填写sbuffer_。
言归正传,根据上面的例子,咱们能够看到Query:: Store方法既能够表示template query也能够表示non-template query。这二者怎么区别?回到Query:: store(const SQLTypeAdapter&)的源代码(为了方便,这里再展现一次)
他首先检查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() 异曲同工了)。
以前介绍过,SQLQueryParms继承自vector,因此很容易理解下面的代码,须要注意的是返回值仍是一个SQLQueryParms。
因此,第511行最后调用的是Query::store(SQLQueryParms& p)。那咱们来看这里
又要看一下Query:: str(SQLQueryParms& p)
很好猜,这里必定对sbuffer作了什么。来看一下Query:: proc。
首先,他直接把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 * from … where item = NULL
这句话的含义彻底不一样于
select * from … where 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。