C++开源项目:mysqlpp Result类

732 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

本文是我之前在微信公众号上的一篇文章记录。原链接为:MySQL++学习笔记:Result类

今天这章节我们学习一下mysql++的查询结果类,我们之前的章节有提到过,主要分为三种:

  • SimpleResult

    并非所有 SQL 查询都返回数据。 一个例子是创建表。 对于这些类型的查询,有一种特殊的结果类型 (SimpleResult),它只报告查询产生的状态:查询是否成功,它影响了多少行(如果有)等。

  • UseQueryResult

    由于use的语义类似于使用游标,也就是支持一行一行地拉出内容,所以UseQueryResult也就自然而然地支持一些关于fetch row的功能。

  • StoreQueryResult

    StoreQueryResult它本身就是从vector继承而来,所以它就是vector。所以用户程序可以直接使用下标的形式来获取所有的ROW。这也就是说在这个store之后,所有的ROW的内容都在了这个vecor里面了。

SimpleResult

这个类非常简单,主要有三个成员变量记录了插入id,影响行及额外信息:

// last value used for an AUTO_INCREMENT field
ulonglong insert_id_;
​
// the number of rows affected by the query
ulonglong rows_;
​
// any additional information about the query returned by the server
std::string info_;

两个构造函数也特别简单,就是对这几个成员变量赋值:

/// \brief Default ctor
SimpleResult() :
copacetic_(false),
insert_id_(0),
rows_(0)
{
}
​
/// \brief Initialize object
SimpleResult(bool copacetic, ulonglong insert_id,
        ulonglong rows, const std::string& info) :
copacetic_(copacetic),
insert_id_(insert_id),
rows_(rows),
info_(info)
{
}

成员方法就是对上面几个成员变量的GET:

/// \brief Get the last value used for an AUTO_INCREMENT field
ulonglong insert_id() const { return insert_id_; }
​
/// \brief Get the number of rows affected by the query
ulonglong rows() const { return rows_; }
​
/// \brief Get any additional information about the query returned
/// by the server.
const char* info() const { return info_.c_str(); }

下面我们看下在Query中如何创建SimpleResult类型并返回的:

SimpleResult
Query::execute(const char* str, size_t len)
{
    if ((parse_elems_.size() == 2) && !template_defaults.processing_) {
        // We're a template query and this isn't a recursive call, so
        // take s to be a lone parameter for the query.  We will come
        // back in here with a completed query, but the processing_
        // flag will be set, allowing us to avoid an infinite loop.
        AutoFlag<> af(template_defaults.processing_);
        return execute(SQLQueryParms() << str << len );
    }
    if ((copacetic_ = conn_->driver()->execute(str, len)) == true) {
        if (parse_elems_.size() == 0) {
            // Not a template query, so auto-reset
            reset();
        }
        return SimpleResult(conn_, insert_id(), affected_rows(), info());
    }
    else if (throw_exceptions()) {
        throw BadQuery(error(), errnum());
    }
    else {
        return SimpleResult();
    }
}

从Query中的execute成员方法看到创建SimpleResult非常简单,就用了Query::insert_id( ), Query::affected_rows( ), Query::info( )这三个方法,而这三个方法就是DBDriver类的相关函数,最终其实就是直接调用MySQL C API的方法。

ResultBase

ResultBase是StoreQueryResult和UseQueryResult的父类,所以这里我们先简单讲一下。

ResultBase中主要实现了跟field相关的操作,比如根据index查找field,根据name查找field等,核心成员变量有:

/// std::vector<Field>
Fields fields_;     ///< list of fields in result/// \brief list of field names in result
/// std::vector<std::string>
RefCountedPointer<FieldNames> names_;
​
/// \brief list of field types in result
/// std::vector<mysql_type_info>
RefCountedPointer<FieldTypes> types_;
​
/// \brief Default field index used by fetch_field()
///
/// It's mutable because it's just internal housekeeping: it's
/// changed by fetch_field(void), but it doesn't change the "value"
/// of the result.  See mutability justification for
/// UseQueryResult::result_: this field provides functionality we
/// used to get through result_, so it's relevant here, too.
mutable Fields::size_type current_field_;

从上面成员变量可以看到names_ 和 types_ 是用RefCountedPointer包裹的,它可以理解为是一个智能指针,有一个引用计数,当计数为0时会自动delete该指针,所以names_ 和 types_ 不需要显示的delete。

下面来看下它的核心构造函数:

ResultBase::ResultBase(MYSQL_RES* res, DBDriver* dbd, bool te) :
OptionalExceptions(te),
driver_(res ? dbd : 0),
fields_(Fields::size_type(res ? dbd->num_fields(res) : 0)),
current_field_(0)
{
    if (res) {
        Fields::size_type i = 0;
        const MYSQL_FIELD* pf;
        while ((i < fields_.size()) && (pf = dbd->fetch_field(res))) {
            fields_[i++] = pf;
        }
        dbd->field_seek(res, 0);        // semantics break otherwise!
​
        names_ = new FieldNames(this);
        types_ = new FieldTypes(this);
    }
}

将DBDriver中的field result遍历保存,然后再解析names和types保存就完事了。

要注意:为什么要重新将指示field_当前index的指针重置的过程(dbd->field_seek(res, 0))?是因为方便DBDriver下次(它的继承类还需要遍历row)再次从头开始循环访问这些fields。

StoreQueryResult

先看下它的声明及官方说明:

/// \brief StoreQueryResult set type for "store" queries
///
/// This is the obvious C++ implementation of a class to hold results 
/// from a SQL query that returns rows: a specialization of std::vector
/// holding Row objects in memory so you get random-access semantics.
/// MySQL++ also supports UseQueryResult which is less friendly, but has
/// better memory performance.  See the user manual for more details on
/// the distinction and the usage patterns required.class MYSQLPP_EXPORT StoreQueryResult :
        public ResultBase,
        public std::vector<Row>

同时继承ResultBase和std::vector。它其实就是一个“a specialization of std::vector holding Row objects”,说白了就是一个vector。

下面看下它的核心构造函数:

StoreQueryResult::StoreQueryResult(MYSQL_RES* res, DBDriver* dbd,
        bool te) :
ResultBase(res, dbd, te),
list_type(list_type::size_type(res && dbd ? dbd->num_rows(res) : 0)),
copacetic_(res && dbd)
{
    if (copacetic_) {
        iterator it = begin();
        while (MYSQL_ROW row = dbd->fetch_row(res)) {
            if (const unsigned long* lengths = dbd->fetch_lengths(res)) {
                *it = Row(row, this, lengths, throw_exceptions());
                ++it;
            }
        }
        dbd->free_result(res);
        if (throw_exceptions() && dbd->errnum() != 0) {
            throw UseQueryError(dbd->error());
        }
    }
}

确实没错了,就是不停的往vector里放Row。将DBDriver所有的res copy完之后就直接free掉了。

所以所有的result信息基本都在Row这个类型中存储了。

UseQueryResult

同样该类继承自ResultBase,上面说了ResultBase主要实现了列相关操作,而StoreQueryResult和UseQueryResult主要是行操作。

同时UseQueryResult这个基本上是直接保持mysql C API的直接结果,可以看到它唯一的成员变量:

/// \brief Reference to underlying C API result set
///
/// This is mutable because so many methods in this class are
/// are justifiably const because they don't modify the result
/// set's "value" but they call C API methods that take non-const
/// MYSQL_RES* so they can only be const if this is mutable.  It's
/// quite likely that these API functions do modify the MYSQL_RES
/// object, so strict constness says this object changed, too, but
/// this has always been mutable and the resulting behavior hasn't 
/// confused anyone yet.
mutable RefCountedPointer<MYSQL_RES> result_;

值得注意的是,这个RefCountedPointer<MYSQL_RES>智能指针它的析构调用不是delete,而是调用mysql C API的free,所以UseQueryResult类重载了RefCountedPointer这个智能指针的析构函数,如下:

/// \brief Functor to call mysql_free_result() on the pointer you pass
/// to it.
///
/// This overrides RefCountedPointer's default destroyer, which uses
/// operator delete; it annoys the C API when you nuke its data
/// structures this way. :)
template <>
struct RefCountedPointerDestroyer<MYSQL_RES>
{
    /// \brief Functor implementation
    void operator()(MYSQL_RES* doomed) const
    {
        if (doomed) {
            mysql_free_result(doomed);
        }
    }
};

下面再看它的构造函数,简洁明了,直接赋值完事。

UseQueryResult::UseQueryResult(MYSQL_RES* res, DBDriver* dbd, bool te) :
ResultBase(res, dbd, te)
{
	if (res) {
		result_ = res;
	}
}

剩下的几个成员方法也是相当于直接封装mysql C API的fetch_field,fetch_lengths,fetch_row等,主要功能如下:

  • 顺序读取行(mysqlpp::Row fetch_row() const;或者MYSQL_ROW fetch_raw_row() const;)
  • 获取当前行的各个field的信息(const Field& fetch_field() const; const Field& fetch_field(Fields::size_type i) const)
  • 获取当前行的所有fields的长度(const unsigned long* fetch_lengths() const;)

好了,关于result的就讲这么多了。