本文是CryptDB代码分析的第三篇。在CryptDB中,需要对加密过程进行记录:比如某个表的原始名字和加密以后的名字,表中有多少列,每列用了什么样的加密算法。这些信息被记录在mysql-proxy端的embedded MySQL中。CryptDB使用了元数据管理的模块处理这些信息,相关代码主要位于main/dbobject.hh以及main/schema.cc。
层次化的结构
在介绍元数据相关的类层次之前,我们首先考虑什么样的元信息需要被记录。我们创建一个数据库,需要在元信息里面记录新添加了这个数据库db。我们进一步在这个数据库里面建立一个表student,使用的语句是CREATE TABLE student (id integer),这样的话,元信息里面就需要记录新加入的表student,并且需要知道这个表包含一个整数列id。由于要对数据做加密,这个整数列会被多种不同的洋葱加密,元信息里面也要包含这些内容。由于洋葱有很多的层次,那么每个洋葱处于哪一层也要被记录下来,这样才可以完成正确的加解密流程。
由此可以看到,元信息需要记录是一个层次化的结构,最上层的是db(数据库),依次往下走,分别是table(表),field(列),onion(洋葱),以及layer(洋葱层次)。CryptDB用了一组相关的数据结构来表示这个信息,分别是DatabaseMeta,TableMeta,FieldMeta,OnionMeta,Enclayer,下面依次介绍。
DatabaseMeta
当我们使用语句CREATE DATABASE db创建一个数据库db的时候,CryptDB会生成一个DatabaseMeta结构来表示这个新的数据库,并把这个信息序列化以后写入到embedded MySQL中。该类的结构如下:
可以看到DatabaseMeta继承了模板类MappedDBMeta,而MappedDBMeta又继承了DBMeta类,下面分别介绍。
1)MappedDBMeta
MappedDBMeta是一个类模板,实例化以后被DatabaseMeta等一系列的类继承,其内部包含了std::map类型的成员,用于保存元数据的层次化关系。举例来说,一个数据库db下面,会建立很多的表,如table1,table2,table3…,这样的话可以通过一个如下的map来保存这种关系:
std::map<KeyType, std::unique_ptr<ChildType> > children;
在CryptDB中,下层结构被称为child,上层和下层是包含关系,比如一个DatabaseMeta中就包含多个TableMeta。对于DatabaseMeta来说,map中的KeyType是IdentityMetaKey,是对表名字如table1的封装,而ChildType则是TableMeta,代表了一个表的元数据。所有继承了MappedDBMeta的元数据管理相关的类,都是通过map结构用Key-Value的方式来保存这种层次关系的。此外,MappedDBMeta还实现了继承自DBMeta的一些和child操作相关的函数,如:
std::vector<DBMeta *>
fetchChildren(const std::unique_ptr<Connect> &e_conn);
bool
applyToChildren(std::function<bool(const DBMeta &)>);
AbstractMetaKey const &
getKey(const DBMeta &child);
分别用于获取children,对每个child执行一个函数操作,以及通过child来获得child对应的key。
2)DBMeta:
DBMeta类继承了DBOjbect和NormalAlloc,其中提供功能的是DBObject,其作用是给给元数据相关的类记录一个id。本文介绍的所有元数据相关的类,都从DBObject中得到了id这个成员。此外,DBMeta类还定义了MappedDBMeta中用于对child做处理的纯虚函数。这样,各种常见的元数据相关的类都可以通过DBMeta的指针来保存,并执行相应的操作对内部保存在Map中的children进行处理。
除此之外,其中还定义了纯虚函数:serialize,各个下层的类实现这个函数,对自身的结构做序列化,并存储在数据库中。
最后,DBMeta中还定义了函数doFetchChildren,该函数会执行SQL语句,从数据库中读取序列化后的元数据管理类,做反序列化操作,然后以vector的形式返回结果。
3)DatabaseMeta:
有了上面的基础,就可以介绍DataBaseMeta了。DatabaseMeta代表了一个新的数据库,其通过继承模板类,用TableMeta和IdentityMetaKey来实例化模板参数来以Key-Value的形式保存数据库和表的关系。并且实现了继承自DBMeta的serialize函数来实现序列化,定义了deserialize函数实现反序列化,主要代码如下:
class DatabaseMeta : public MappedDBMeta<TableMeta, IdentityMetaKey> {
static std::unique_ptr<DatabaseMeta>
deserialize(unsigned int id, const std::string &serial);
std::string serialize(const DBObject &parent) const;
}
std::unique_ptr<DatabaseMeta>
DatabaseMeta::deserialize(unsigned int id, const std::string &serial) {
assert(id != 0);
return std::unique_ptr<DatabaseMeta>(new DatabaseMeta(id));
}
std::string
DatabaseMeta::serialize(const DBObject &parent) const{
const std::string &serial =
"Serialize to associate database name with DatabaseMeta";
return serial;
}
可以看到,对于database来说,序列化只要写固定的一个字符串下去就可以,而反序列化的时候,这个字符串也没有用到,而是直接使用DatabaseMeta对应的id来做反序列化。
TableMeta
和DatabaseMeta类似,TableMeta保存了一个table的信息。上图给出了TableMeta的继承关系。Table中包含了很多的列,每个列都有自己的名字,所以其用于实例化模板的类型分别是FieldMeta和IdentityMetaKey。前者代表了表中的一个列,后者则是列名的封装。TableMeta的主要定义如下:
class TableMeta : public MappedDBMeta<FieldMeta, IdentityMetaKey>,
public UniqueCounter {
static std::unique_ptr<TableMeta>
deserialize(unsigned int id, const std::string &serial);
std::string serialize(const DBObject &parent) const;
std::string getAnonTableName() const;
std::vector<FieldMeta *> orderedFieldMetas() const;
private:
const std::string anon_table_name;
uint64_t counter;
//from UniqueCounter
uint64_t &getCounter_() {
return counter;}
}
首先看其成员anno_table_name。在CryptDB中,每个明文的表名都被替换成了密文的表名。其中明文的表名被封装成了IdentityMetaKey,存储在DatabaseMeta内部的Map中作为key,加密替换以后的表名则存储在T