从之前所介绍的SSQLS的介绍中我们可以感受到,SSQLS的精髓应该在sql_create_#这个宏,他所创建出来的这个结构体将会是突破的关键,所以我将会从以下顺序入手。

1. sql_create_#

先再来重复一下我们需要使用的examples/stock.h中的内容

sql_create_6(    
    stock, 1, 6,        
    mysqlpp::sql_char, item,        
    mysqlpp::sql_bigint, num,        
    mysqlpp::sql_double, weight,        
    mysqlpp::sql_decimal, price,        
    mysqlpp::sql_date, sdate,        
    mysqlpp::Null<mysqlpp::sql_mediumtext>, description)

最直截了当的方式是直接去看这个宏的实现,但是作者也考虑到这个宏可能过于庞大,看起来不舒服(我看过一下,确实太麻烦了),所以在manual里面专门有一节是“Expanding SSQLS Macros”。通过以下语句可以看到sql_create_#到底做了什么(假设我们要看的就是MYSQL++自带的那个emamples/stock.h这个头文件中定义的sql_create_#,且当前环境是linux,当前目录是MYSQL++源代码根目录)。

doc/ssqls-pretty < examples/stock.h > stock_expand.txt

当我打开这个stock_expand.txt之后我碉堡了,居然有1w多行。仔细看一下,发现好多是mysql的头文件里的东西,再回想一下作者在manual里面讲的话,说这个ssqls-pretty程序会调用编译器的preprocess的过程,然后把内容完整输出。去看一下这个stock.h,刚开始两行是#include <mysql++.h>,这也就对了,这个头文件可是包含了太多的其他头文件呀。想想在编译原理里面学过的知识,所有的头文件的内容都被复制过来了,那不长才怪了呢。所以下面我们就抓重点看。

在1w多行里面找重点其实也真不简单,线索是什么?先来回顾一下看到的stock.h

#include <mysql++.h>
#include <ssqls.h>
sql_create_6(    
    stock, 1, 6,        
    mysqlpp::sql_char, item,        
    mysqlpp::sql_bigint, num,        
    mysqlpp::sql_double, weight,        
    mysqlpp::sql_decimal, price,        
    mysqlpp::sql_date, sdate,        
    mysqlpp::Null<mysqlpp::sql_mediumtext>, description)

粗看看,只有stock这个class名字(也有可能是struct),联想到SSQLS的用法(见之前的分析),线索只能是class stock或者struct stock。全局搜,果然查到了struct stock。

  • 主要成员变量
struct stock {
 
mysqlpp::sql_char item;
 
mysqlpp::sql_bigint num;
 
mysqlpp::sql_double weight;
 
mysqlpp::sql_double_null price;
 
mysqlpp::sql_date sDate;
 
mysqlpp::sql_mediumtext_null description;
 
...
}

显然,这个就是根据sql_create_#的几个变量直接生成的。

  • 构造函数与set设置函数

首先我们先来验证一下之前在介绍sql_create_#的时候说的构造函数和set方法(为了看上去方便,我略微调整了代码顺序并把部分的实现放到了声明一起)。

stock() : table_override_(0) {}
 
stock(const mysqlpp::Row& row)
: table_override_(0)
{
    populate_stock<mysqlpp::sql_dummy>(this, row);
}
 
stock(const mysqlpp::sql_char &p1) : item (p1), table_override_(0) {}
 
stock(
const mysqlpp::sql_char &p1, 
const mysqlpp::sql_bigint &p2, 
const mysqlpp::sql_double &p3, 
const mysqlpp::sql_double_null &p4, 
const mysqlpp::sql_date &p5, 
const mysqlpp::sql_mediumtext_null &p6) :
    item (p1), 
    num (p2), 
    weight (p3), 
    price (p4), 
    sDate (p5), 
    description (p6), 
    table_override_(0) 
{ }
 
void set(const mysqlpp::Row &row);
 
void set(const mysqlpp::sql_char &p1);
 
void set(
    const mysqlpp::sql_char &p1, 
    const mysqlpp::sql_bigint &p2, 
    const mysqlpp::sql_double &p3, 
    const mysqlpp::sql_double_null &p4, 
    const mysqlpp::sql_date &p5, 
    const mysqlpp::sql_mediumtext_null &p6);
 

基本上和之前在介绍sql_create_#的时候所说的SETCOUNT和COMPCOUNT的用法类似。其实实现不用深究,无非就是按照你在sql_create_#的时候所设立的顺序逐个赋值而已。

根据上面的代码,我们可以看到两个地方会有一些疑惑。

    • populate_stock
template <mysqlpp::sql_dummy_type dummy> 
void populate_stock(stock *s, const mysqlpp::Row &row) {
    mysqlpp::NoExceptions ignore_schema_mismatches(row);
 
    s->item = row["item"].conv(mysqlpp::sql_char());
    s->num = row["num"].conv(mysqlpp::sql_bigint());
    s->weight = row["weight"].conv(mysqlpp::sql_double());
    s->price = row["price"].conv(mysqlpp::sql_double_null());
    s->sDate = row["sDate"].conv(mysqlpp::sql_date());
    s->description = row["description"].conv(mysqlpp::sql_mediumtext_null());
}
 

仔细看这个模板函数的模板部分,这里声明了一个叫做mysqlpp::sql_dummy_type的东西,那是什么?如果大家仔细一点看,在这个模板方法里面其实并没有用到这个模板实参。为了完整性,我还是给大家翻出来了这个mysqlpp::sql_dummy_type的真面目,他被定义在了ssqls.h中

enum sql_dummy_type { sql_dummy };

所以说,这里并没有什么特别的,只是先是搞一个NoExceptions防止这段代码抛出异常(是否还记得之前有介绍过MYSQL++支持两种编程方式,即C的返回值式和C++的异常式)。然后逐个转换而已。我觉得,这里之所以要这个NoExceptions,根本原因在于MYSQL++人为保障SSQLS结构和表的完全对应关系应该是用户的责任。

    • table_override_

table_override_是什么玩意儿,为什么上面的代码里面一直把它设置为0?看过代码知道了,

const char* table_override_;

在经过查看这个变量的用法之后我发现了一点端倪——在MYSQL++之中,为了保证跨平台性,作者都使用0来代替我们经常所使用的NULL。

那他是什么意思呢?来看一下最主要用到它的两个地方

const char* table() const {
return table_override_ ? table_override_ : stock::table_;
} void instance_table(const char* t) {
table_override_ = t;
}

之前有讲过,SSQLS有这样的一种功能,即用户可以将一个SSQLS结构体对应于同构的多张不同名表。有两种办法可以修改这个表明,其中之一就是调用instance_table来修改全局表名,于是这个table_override_就是这个区别于默认的与SSQLS结构体同名的表名。

问题又来了,有没有看到上面的table()方法中用到了stock:: table_这个变量,从用法上来看这是已经静态变量,查了一下。

struct stock {
    ....
    static const char* table_;
}
 
const char* stock::table_ = "stock";
 

  • 比较函数

sql_create_#宏里面定义了不少类型的比较函数(基本上涵盖了所有的比较符号),例如

bool operator == (const stock &other) const;
 
bool operator > (const stock &other) const;

实现方式也是比较统一,即使用sql_compare_stock函数

bool operator > (const stock &other) const {
     return sql_compare_stock<mysqlpp::sql_dummy>(*this,other) > 0;
}
 
bool operator < (const stock &other) const {
     return sql_compare_stock<mysqlpp::sql_dummy>(*this,other) < 0;
}
 

那么sql_compare_stock又是如何实现的

template <mysqlpp::sql_dummy_type dummy> 
int sql_compare_stock(const stock &x, const stock &y) {
    return mysqlpp::sql_cmp(x.item , y.item );
}
 


稍等一下,让我们仔细来看一下这个sql_compare_stock,看那唯一的一句语句,sql_cmp的是什么?只是stock::item!为什么只取了item这一个项?回忆一下,sql_create_#中有一个参数是COMPCOUNT,我们在这里例子里面填写的可是1,也就是只有sql_create_#中第一个正式的参数才会被参与到比较,在这里不就是这个item嘛?那如果COMPCOUNT==2该怎么样?

template <mysqlpp::sql_dummy_type dummy> 
int sql_compare_stock(const stock &x, const stock &y) {
    int cmp;
    cmp = mysqlpp::sql_cmp(x.item , y.item );
 
    if (cmp) return cmp;
    return mysqlpp::sql_cmp(x.num , y.num );
}
 

注意到,其实就是一个个进行比较而已。

再去找mysqlpp:: sql_cmp的实现,他们被定义在了ssqls.h中,其实这是一组override,也就是每个类型都有自己的mysqlpp:: sql_cmp,例如:

inline int sql_cmp(const Time& a, const Time& b)
{    
     return a.compare(b);
} 
 
inline int sql_cmp(signed char a, signed char b)
{    
    return a - b;
}
 
// 其他类型的比较方法
....


  • 获取value,field,equal的SQL语句表示

在打开宏之后,我们看到在真正的SSQLS结构体被定制之前,被定义了很多类型。我们还是拿stock作为例子,先看到的就是一个根据我们输入的sql_create_#所创建出来的enum。

enum stock_enum {
 
stock_item, 
 
stock_num, 
 
stock_weight, 
 
stock_price, 
 
stock_sDate, 
 
stock_description ,
 
stock_NULL };
 

这个enum还是相对比较简单,每一列都被定义了进去,最后还标记了一个结束标志stock_NULL,他的作用以后会看到。

接下去的是三组六个类型,分别是一个“全量”,一个“部分量”。

template <class Manip> 
class stock_value_list {
public:
    const stock* obj;
    const char* delim;
    Manip manip;
 
public:
    stock_value_list (const stock* o, const char* d, Manip m) : obj(o), delim(d), manip(m) {}
};
 
template <class Manip> 
class stock_cus_value_list {
public:
    const stock* obj;
    std::vector<bool> *include;
    bool del_vector;
    const char* delim;
    Manip manip;
 
public:
    ~stock_cus_value_list () {
        if (del_vector) delete include;
    }
 
    stock_cus_value_list (const stock* o, const char* d, Manip m, bool i1, bool i2, bool i3, bool i4, bool i5, bool i6);
 
    stock_cus_value_list (const stock* o, const char* d, Manip m, stock_enum i1, stock_enum i2, stock_enum i3, stock_enum i4, stock_enum i5, stock_enum i6);
 
    stock_cus_value_list (const stock* o, const char* d, Manip m ,std::vector<bool>* i) 
         : obj(o), include(i), del_vector(false), delim(d), manip(m) {}
};
 

当然,这里为了节约篇幅,只记录下value的相关类型,还有field,equal等的相关类型。

下表表示了各个类型表示的含义,其中“###”表示SSQLS的类型名,也就是上例中的stock。我们假设当前的实例只有两个列“char(5) item”和“int num”,值分别是“abc”和“1”,当前的分隔符是“,”。

类型名字

含义

备注

###_value_list

###这个SSQLS类型当前实例的依次所有列的值的列表

经过“os << 实例”后的效果

‘abc’ ,1

###_cus_value_list

###这个SSQLS类型当前实例的某些列的值的列表

利用std::vector<bool> *include所表示的位图表确定哪些列是需要的

###_field_list

###这个SSQLS类型当前实例的依次所有列的列名的列表

经过“os << 实例”后的效果

`item` ,`num`

###_cus_ field _list

###这个SSQLS类型当前实例的某些列的列名的列表

利用std::vector<bool> *include所表示的位图表确定哪些列是需要的

###_equal_list

###这个SSQLS类型当前实例的依次所有列的列名和其值所组成等号连接

经过“os << 实例”后的效果

`item` = ‘abc’, `num` = 1

###_cus_equal _list

###这个SSQLS类型当前实例的某些列的列名的列表

利用std::vector<bool> *include所表示的位图表确定哪些列是需要的

这几个类型基本上就是被当做struct来做的,没有很多花哨的方法,基本上就这么几个变量(仅以###_cus_value_list为例)。

const ###* obj; // ###变量的实例
 
std::vector<bool> *include; // 位图表,表示哪些列是需要使用的(true)
 
bool del_vector; // 忽略吧,就没见过true的
 
const char* delim; // 分隔符
 
Manip manip; // 这是个template parameter,其实就是用于做escaping和quoting的那些表示需要quote,escaping等的Enum。
 

那么哪些地方会构造他们?在SSQLS类型中,会有一系列的value_list,field_list,equal_list方法,每个都有4个不同的同构,殊途同归,大家最后都生成对应的###_XXX_list,###_cus_XXX_list类型。有些方法的签名带有默认值,如下

template <class Manip> 
stock_cus_value_list<Manip> value_list(const char* d, Manip m, bool i1, bool i2 = false, bool i3 = false, bool i4 = false, bool i5 = false, bool i6 = false) const;
 
template <class Manip>
 stock_cus_value_list<Manip> value_list(const char* d, Manip m, stock_enum i1, stock_enum i2 = stock_NULL, stock_enum i3 = stock_NULL, stock_enum i4 = stock_NULL, stock_enum i5 = stock_NULL, stock_enum i6 = stock_NULL) const;
 

显然,这两个签名其实含义是一致的,无非就是把false和enum的stock_NULL等价起来,然后就去写那个代表位图的vector<bool>。

构造出来了,谁去用它?所有的三组都是通过

ostream & operator << (ostream & s, ###_XXX_list<T> & obj);
 
ostream & operator << (ostream & s, ###_cus_XXX_list<T> & obj);
 

使用的。他们做了什么?各来看一个就懂了(以stock_value_list和stock_cus_value_list为例)

template <class Manip> 
std::ostream& operator <<(std::ostream& s, const stock_value_list<Manip>& obj) {
    s << obj.manip << obj.obj->item << obj.delim;
    s << obj.manip << obj.obj->num << obj.delim;
    s << obj.manip << obj.obj->weight << obj.delim;
    s << obj.manip << obj.obj->price << obj.delim;
    s << obj.manip << obj.obj->sDate << obj.delim;
    s << obj.manip << obj.obj->description;
    
    return s;
}
 
template <class Manip> 
std::ostream& operator <<(std::ostream& s, const stock_cus_value_list<Manip>& obj) 
{
    bool before = false;
 
    if ((*obj.include)[0]) {
        s << obj.manip << obj.obj->item;
        before = true;
    }
 
    if ((*obj.include)[1]) {
        if (before) s << obj.delim;
        s << obj.manip << obj.obj->num;
        before = true;
    }
 
    // 这里我省略了其他的列
    ....
 
    return s;
}
 

那定义了那么多与std::ostream相关的operator <<代码是用来做什么的?什么时候用value_list,什么时候用field_list,什么时候用equal_list?先透露点,value_list至少可以用作INSERT的VALUES内容,field_list至少可以用作INSERT的COLUMNS的地方,equal_list至少可以用在UPDATE的SET中。

2. SELECT至SSQLS

这个问题,我们只从mysqlpp:: Query:: storein入手,其他的方法大致都一样的。即

mysqlpp::Query query = con.query("select item,description from stock");
 
vector<stock> res;
 
query.storein(res);

mysqlpp:: Query:: str()方法在template query里面讲过了,在上面的例子里,其实也就直接返回那句SELECT语句了。由于我们给入的是vector<T>,所以直接进入到specified template里面。

接下去就方便了。通过mysqlpp:: Query:: use方法一条条提取数据,然后直接push_back到vector容器中。根据C++规则,在763行会调用copy构造函数,这里就是stock:: stock(Row &)这个构造函数。然后……我就不赘述了……

3. 更改SSQLS数据

这个问题,我们只从mysqlpp:: Query:: insert和update这两个方法入手。

// 插入
 
stock row(“hello”, …);
 
mysqlpp::Query query = con.query();
 
query.insert(row);
 
query.execute();
 
// 更新
 
stock orig_row = row;
 
row.item = "Nuerenberger Bratwurst";
 
query.update(orig_row, row);
 
query.execute();
 

先来看表示插入的mysqlpp:: Query:: insert(),

首先,这个方法是一个模板方法。这也就意味着类型用多了,容易出现代码膨胀。

然后这里的重点也挺明显的,Query:: reset()方法状态给重置(具体参看template query部分),然后拼接处INSERT语句,注意看1012行的v.field_list()和1013行的v.value_list()方法。这两个是什么?就是我们在sql_create_#最后花了大力气介绍的那些###_XXX_list啊。所以如果你是从头看下来的,一定很明白了。

再来看一下更新,

那个MYSQLPP_QUERY_THISPTR就当作是*this吧,回忆一下,Query继承自std::ostream。

原创作品,转载请注明出处www.cnblogs.com/aicro

【原创】14. MYSQL++之SSQLS(原理解析)的更多相关文章

  1. 数据库之 MySQL --- 视图的原理解析与创建(八)

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 1.什么是视图? 视图:MySQL从5.0.1版本开始提供视图功能.一种虚拟存在的表,行和列的数据来自定 ...

  2. 【原创】MYSQL++源码剖析——前言与目录

    终于完成了! 从第一次想写到现在真的写好大概花了我3个月时间.原来一直读人家的系列文章,总感慨作者的用心良苦和无私奉献,自己在心里总是会觉得有那么些冲动也来写一个. 最开始的麻烦是犹豫该选哪个主题.其 ...

  3. MySQL主从同步原理 部署【转】

    一.主从的作用:1.可以当做一种备份方式2.用来实现读写分离,缓解一个数据库的压力二.MySQL主从备份原理master 上提供binlog ,slave 通过 I/O线程从 master拿取 bin ...

  4. 秋色园QBlog技术原理解析:性能优化篇:缓存总有失效时,构造持续的缓存方案(十四)

    转载自:http://www.cyqdata.com/qblog/article-detail-38993 文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文 ...

  5. pt-online-schema-change原理解析(转)

    pt-online-schema-change原理解析 博客相关需要阅读 - zengkefu - 博客园 .pt-online-schema-change工具的使用限制: ).如果修改表有外键,除非 ...

  6. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  7. MYSQL索引结构原理、性能分析与优化

    [转]MYSQL索引结构原理.性能分析与优化 第一部分:基础知识 索引 官方介绍索引是帮助MySQL高效获取数据的数据结构.笔者理解索引相当于一本书的目录,通过目录就知道要的资料在哪里, 不用一页一页 ...

  8. GeoHash原理解析

    GeoHash 核心原理解析       引子 一提到索引,大家脑子里马上浮现出B树索引,因为大量的数据库(如MySQL.oracle.PostgreSQL等)都在使用B树.B树索引本质上是对索引字段 ...

  9. Android中插件开发篇之----应用换肤原理解析

    一.前言 今天又到周末了,感觉时间过的很快呀.又要写blog了.那么今天就来看看应用的换肤原理解析.在之前的一篇博客中我说道了Android中的插件开发篇的基础:类加载器的相关知识.没看过的同学可以转 ...

随机推荐

  1. atitit.RESTful服务的概览and框架选型

    atitit.RESTful服务的概览and框架选型 1. REST基础概念: 1 2. URL说明: 1 3.  1 4. RESTful框架选型 2 1. spring mvc( recomm) ...

  2. CentOS 6.5 无网环境安装R及Rstudio的方法的方法

    在生产环节,一般是不联网的,下面介绍在无望环境如何安装R及R-studio 1.  安装CentOS for R语言的基础环境 1.1 libpng,X11,libjpeg等支持 yum -y ins ...

  3. JSTL自定义标签

    这节我们总结一下JSTL自定义标签相关内容. 1. 自定义标签简介 自定义标签主要用于移除JSP页面中的Java代码.Jsp页面主要是用来显示给前台的,如果里面有过多的java代码的话,会显得很乱,但 ...

  4. 详解Bootstrap按钮组件(二)

    按钮下拉菜单 按钮下拉菜单仅从外观上看和下拉菜单效果基本上是一样的.它们唯一的不同是外部容器div.dropdown换成了div.btn-group <div class="btn-g ...

  5. transform实现的时钟效果

    又来一个时钟效果了,这个的实现不需要canvas,都是div.ul.li画出的,好玩有真实. 哈哈~ 需要的js才能实现到走动这个效果,但js的内容不多,也不难. 主要是一个css里transform ...

  6. 使用AJAX填充<select>标签下拉项,没有显示指定的option项

    newCarInfo.js代码如下: $(function() {     // 获取燃油种类     url = "basicFuelType_queryAll.action"; ...

  7. 澳洲最大的华资快递公司ACE 签约动软微信商城系统!

    ACE-平安立达快递是澳洲最大的华资快递公司ACE平安立达,总部设在Boxhill,同时在中国成立了进口食品专营连锁加盟店“澳莱优品”,目前已经有近20家加盟店,14年底国内计划开到60家店. 201 ...

  8. Revit中如何将视图过滤器传递到其它项目

    在Revit中采用过滤器控制视图显示,利用过滤器给图元着色,利用过滤器控制视图显示或隐藏等,那么,在不同的项目中是否每次都要设置相同的过滤器,其实,Revit提供了这么一种在不同项目传递信息的方式,在 ...

  9. Swift入门篇-集合

    一:数组 一:可变数组 定义:数组使用有序列表存储相同类型的多重数据. 格式: 第一种格式 var 变量: 类型[] = [变量值,变量值,...] 第二种格式 var 变量 =[变量值,变量值,.. ...

  10. ubuntu bless 16字节每行

    打开Preferences配置 输入路径:/usr/share/bless/bless-16-bytes-per-row.layout 或者使用以下配置 cat /home/scue/.config/ ...