【原創】14. MYSQL++之SSQLS(原理解析)

從以前所介紹的SSQLS的介紹中咱們能夠感覺到,SSQLS的精髓應該在sql_create_#這個宏,他所建立出來的這個結構體將會是突破的關鍵,因此我將會從如下順序入手。html

1. sql_create_#mysql

先再來重複一下咱們須要使用的examples/stock.h中的內容linux

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++源代碼根目錄)。sql

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.hapi

#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。ide

 

  • 主要成員變量
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方法(爲了看上去方便,我略微調整了代碼順序並把部分的實現放到了聲明一塊兒)。this

 

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_#的時候所設立的順序逐個賦值而已。spa

根據上面的代碼,咱們能夠看到兩個地方會有一些疑惑。

    • 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);

clip_image001

mysqlpp:: Query:: str()方法在template query裏面講過了,在上面的例子裏,其實也就直接返回那句SELECT語句了。因爲咱們給入的是vector<T>,因此直接進入到specified template裏面。

clip_image002

clip_image004

接下去就方便了。經過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(),

clip_image006

首先,這個方法是一個模板方法。這也就意味着類型用多了,容易出現代碼膨脹。

而後這裏的重點也挺明顯的,Query:: reset()方法狀態給重置(具體參看template query部分),而後拼接處INSERT語句,注意看1012行的v.field_list()和1013行的v.value_list()方法。這兩個是什麼?就是咱們在sql_create_#最後花了大力氣介紹的那些###_XXX_list啊。因此若是你是從頭看下來的,必定很明白了。

再來看一下更新,

clip_image008

那個MYSQLPP_QUERY_THISPTR就看成是*this吧,回憶一下,Query繼承自std::ostream。

 

 

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

相關文章
相關標籤/搜索