2021 CMU-15445/645 Project #3 : Query Execution

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 结果

在这里插入图片描述
通过了所有测试,还有问题的朋友可以留言解答。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值