0 介绍
21年秋季的15445第3个Project是要完成一些执行器(executor)来实现一些数据库操作比如遍历、插入、更新、删除、连接等等。虽然算法难度上不如Project2,但是需要阅读大量代码并能调试BUG,还是有一些麻烦。在做之前,务必阅读包括但不限于Catalog、TableInfo、IndexInfo、Expression、PlanNode、Schema、Tuple这些类的代码,这样会好做一些。
1 实现
接下来介绍一些有难点的执行器实现。
1.1 Seqential Scan
这个执行器是要我们顺序遍历一个Table,并按要求的格式返回遍历结果,如果有过滤条件,也要进行过滤。相当于
Select somecols From Test_Table Where ...
算法描述不难,但是实现时还是要费点心思,特别作为第一个要实现的executor,读者可能还不太熟悉整个代码框架。
在实现时,需要SeqScanExecutor加入两个成员变量:
TableInfo *table_info_; //要遍历的表的信息
TableIterator iter_; //table_heap_的迭代器
SeqScanExecutor的plannode包含过滤条件即谓词(predicate)信息和输出格式(outputschema)。
Init函数做的工作很简单,把iter_初始化为table_info_的table_heap的Begin就可以了。
在Next函数里,只要iter_不为table_heap的Begin的End,且满足predicate的要求,意味着我们可以按照OutputSchema返回这个iter_对应的Tuple,这一步如何做这里破例展示代码,因为实在不好描述:
std::vector<Value> res;
const Schema *output_schema = plan_->OutputSchema(); //Schema是返回格式
res.reserve(output_schema->GetColumnCount());
for (const Column &column : output_schema->GetColumns()) {
res.push_back(column.GetExpr()->Evaluate(&(*iter_), schema)); //第二个参数schema是Tuple原有的格式
}
*tuple = Tuple(res, output_schema); //调用Tuple的构造函数
*rid = (*iter_).GetRid();
iter_++; //iter_指向下一个Tuple
上述代码在其他执行器中也有可能用到,还是要明白它背后的原理。
1.2 Insert
InsertExecutor是要实现一个插入条目的执行器,要注意的是条目来源可能是Raw Tuple,类似:
Insert Into test_table Values ()...
也有可能来自一个子执行器,像:
Insert Into test_table Select somecols From Another_Table Where .
具体方式要根据plannode来判断。
Init方法不难,看情况执行child_executor的Init即可。
Next方法里拿到需要插入的条目然后使用table_heap的InsertEntry方法即可,然后更新索引即可。更新索引有一个小坑,index的InsertEntry有一个tuple参数,这里的tuple是原tuple中索引对应的key组成的Tuple。
Update、Delete比Insert简单一些,这里就不说了。
1.3 Aggregation
实现聚合函数,最难的部分其实已经写好了,读者请熟悉写好的简易哈希表的实现。这个执行器也不需要加新的类成员,把注释掉的成员取消注释即可。
Init函数 实现是本Executor实现重点,我们需要遍历child_executor,然后把子执行器MakeKey和MakeValue后插入到哈希表即可。
Next函数 是遍历哈希表,当哈希表迭代器对应的Tuple满足谓词条件(就是plannode的having)时,按格式构造Tuple即可。
1.4 Nested loop join
连接操作算法不难,就是一个双重循环扫描两个table,把符合过滤条件的两个Tuple合并罢了。注意构造返回Tuple时使用column.GetExpr()->EvaluateJoin()。另外需要注意的是GradeScope会有IO cost检测,这意味着不能在Init时把所有Tuple缓存下来,而是每次调用Next时读table的tuple。编码难度有点小增加。
1.5 Hash Join
哈希连接我感觉是最难实现的一个。实现时首先要参考Aggregation的实现,自己定义一个Key和Value的struct,并写好Key的==操作符和hash函数(这里有个小坑,hash方法的文本位置必须在Key的后面)。HashJoinExecutor的成员函数定义一个unordered_map<Key,vector<value>>的hash_map。
在Init函数里,将第一个子执行器的Tuple做MakeKey和MakeValue插入哈希表。
在Next函数里,遍历第二个子执行器,如果Tuple做MakeKey后这个key在哈希表里,说明满足连接条件。
顺便说一下Distinct的实现和HashJoin差不多,只不过是使用一个unordered_set罢了
2 结果
通过了所有测试,还有问题的朋友可以留言解答。