一、背景
很多同学都经历过这样一个应用环境:服务器把客户机的数据请求通过查询数据库得到了结果集,再把结果集返还给客户端。在这里,为了让服务器对客户端数据请求具有更方便的扩展或者收缩,需要让服务器不知道客户端每次返回的数据结果集格式。
那如何才能让服务器在不知道客户端请求数据集格式的情况下还能扩展呢,也就是透明的二进制格式呢?想想,就是让查询的结果数据集进行序列化为一种通用格式,然后客户端再根据需求反序列化为需要的数据格式。
二、解决
在了解windows的ADO后,知道它的Recordset支持把数据序列化到一段内存buffer中,也可以从内存buffer中反序列化成一个Recordset对象。好了,有目标,有方法,当然就可以支持数据结果集的透传了。
现在问题的问题是,我们如何把结果集序列化到一段内存,又可以从内存反序列化成一个数据结果集。人类战胜复杂都是靠抽象分层--隔离复杂度。那针对如何抽象一个数据结果集呢?琢磨了一会儿,有了!
一个数据结果集不就是一张二维表吗,二维表不就是有行、列、单元格吗?行里有单元格,列里有列名(方便快速索引)。
下面是关于行、列、单元格的抽象代码
struct cell { size_t col_index_; size_t row_index_; typedef std::allocator<char> allocator_type; typedef std::basic_string<char, std::char_traits<char>, allocator_type> data_type; data_type data_; cell() : col_index_(0) , row_index_(0) {} cell(size_t col_i, size_t row_i, const void *data, size_t len) : col_index_(col_i) , row_index_(row_i) { data_.resize(len); const char *tmp = static_cast<const char *>(data); std::copy(tmp, tmp + len, data_.begin()); } cell(cell &&rhs) : col_index_(rhs.col_index_) , row_index_(rhs.row_index_) , data_(std::move(rhs.data_)) { } cell &operator=(cell &&rhs) { if( &rhs != this ) { col_index_ = rhs.col_index_; row_index_ = rhs.row_index_; data_ = std::move(rhs.data_); } return *this; } private: cell(const cell &); cell &operator=(const cell &); public: size_t size() const { // 数据属性大小 size_t size = sizeof(row_index_) + sizeof(col_index_) + sizeof(data_.size()) + data_.size(); return size; } void set(size_t col_i, size_t row_i, const void *data, size_t len) { col_index_ = col_i; row_index_ = row_i; data_.resize(len); const char *tmp = static_cast<const char *>(data); std::copy(tmp, tmp + len, data_.begin()); } void clear() { col_index_ = 0; row_index_ = 0; data_.clear(); } };
关于cell,里面带有行号,列好,和数据
class col { friend ostream& operator << (ostream & , const col &); friend istream& operator >> (istream & , col &); friend bool operator==(const col &lhs, const col &rhs); public: typedef std::string name_type; typedef size_t id_type; private: name_type name_; public: col() {} col(const std::string &name) : name_(name) {} col(col &&r) : name_(std::move(r.name_)) { } col &operator=(col &&r) { if( &r != this ) { name_ = std::move(r.name_); } return *this; } private: col(const col &); col &operator=(const col &); public: void name(const name_type &name) { name_ = name; } const name_type &name() const { return name_; } size_t size() const { return sizeof(name_.length()) + name_.length() * sizeof(name_type::value_type); } void clear() { name_.clear(); } };
关于col的,仅包含一个名称属性
class row { typedef std::allocator<cell> allocator_type; typedef std::vector<cell, allocator_type> cells_type; cells_type cells_; friend ostream& operator << (ostream & , const row &); friend istream& operator >> (istream & , row &); friend bool operator==(const row &lhs, const row &rhs); public: row() {} explicit row(size_t cnt) { cells_.resize(cnt); } row(row &&rhs) : cells_(std::move(rhs.cells_)) { } row &operator=(row &&rhs) { if( &rhs != this ) { cells_ = std::move(rhs.cells_); } return *this; } private: row(const row &); row &operator=(const row &); public: cell &operator[](size_t index) { assert(cells_.size() > index); return cells_[index]; } const cell &operator[](size_t index) const { assert(cells_.size() > index); return cells_[index]; } size_t size() const { // 得到所有数据大小 size_t size = sizeof(cells_.size()); size += std::accumulate(cells_.begin(), cells_.end(), 0, [](size_t cnt, const cell &val)->int { cnt += val.size(); return cnt; }); return size; } void reserve(size_t cnt) { cells_.reserve(cnt); } void clear() { std::for_each(cells_.begin(), cells_.end(), [](cell &val) { val.clear(); }); cells_.clear(); } };
关于row的,包含所有cell的集合
有热cell、col、row,就可以组装成一个table了
class table { struct impl; impl *impl_; friend ostream& operator << (ostream & , const table &); friend istream& operator >> (istream & , table &); friend bool operator==(const table &lhs, const table &rhs); friend class table_iterator; public: table(); ~table(); private: table(const table &); table &operator=(const table &); public: void reserve_col(size_t col_cnt); void reserve_row(size_t row_cnt); size_t insert_col(const std::string &name); size_t insert_row(); void set_cell(size_t col_i, size_t row_i, const void *data, size_t size); size_t size() const; void clear(); size_t col_cnt() const; size_t row_cnt() const; const std::string &col_name(size_t index) const; std::pair<const char *, size_t> get_data(size_t row_i, size_t col_i) const; std::pair<const char *, size_t> get_data(size_t row_i, const std::string &name) const; };
在这里,table_iterator是一个前向迭代器,用来遍历table。
ostream、istream是用来序列化table的成员属性的。来看看用例
int _tmain(int argc, _TCHAR* argv[]) { // 列名称 std::string cols_name[] = { "test1", "test2", "test3" }; // 单元格数据 std::string cell_data[] = { "1", "1.01", "test_data", }; database::table t; // 插入列和行 for(size_t i = 0; i != _countof(cols_name); ++i) size_t col_i = t.insert_col(cols_name[i]); const size_t row_num = 5; for(size_t i = 0; i != row_num; ++i) t.insert_row(); // 写入单元格数据 for(size_t i = 0; i != _countof(cols_name); ++i) { for(size_t j = 0; j != row_num; ++j) { t.set_cell(i, j, cell_data[i].c_str(), cell_data[i].size()); } } // 序列化缓冲区 std::vector<char> buffer; buffer.resize(t.size()); // 序列化到缓冲区 database::ostream os(&buffer[0], buffer.size()); os << t; // 反序列化 database::table tt; database::istream in(buffer.data(), buffer.size()); in >> tt; // 比较打印 assert(t == tt); std::copy(database::table_iterator(tt), database::table_iterator(), std::ostream_iterator<std::string>(std::cout, " ")); return 0; }
三、 总结
关于数据结果集的透传,实现方式可以有很多种,也可以选择成熟的解决方案,如google protobuf,可以从数据库序列化到内存,然后通过网络传输,然后就慢慢XXOO吧!
关于这种山寨版的实现方式,我自认为手法并不高明,但是这个抽象过程却值得记录下来,欢迎大家评批指点。
这里是在git上的地址,https://github.com/chenyu2202863/smart_cpp_lib/tree/master/test/database_test