cmu15445 2023spring Project #3 Query Execution

背景

建议看这位大佬的文章,讲解的挺清楚。
project3就是实现一系列算子,本身编码并不难,难的是看懂各个接口的使用,以及许多细节处理。笔者在实验的过程中遇到了一些bug,本文会主要说明这些bug及其处理。

Task 1

SeqScan

直接用TableIterator,注意区分前置++和后置++,这个实验只定义了前置++。
由于TableIterator只能用移动构造,所以可以直接在SeqScanExecutor构造函数初始化列表中使用TableIterator移动构造函数,或者使用unique_ptr(推荐)。

Insert

大部分接口都提供了,直接用。
这里讲一个坑,insert的返回值tuple是插入个数,将Int转换为Tuple找了半天,最后还是参考projection_executor.cpp的写法,代码如下

  std::vector<Value> values;
  values.emplace_back(INTEGER, count_rows);
  *tuple = Tuple{values, &GetOutputSchema()};

另外,即使只插入0个也要返回true,为了不陷入死循环,又加了个bool类型的数据成员来判断是第一次执行还是第二次执行。

Update

也是返回更新数量,处理同上。另外,不能一条一条地更新(删除与插入),否则会陷入死循环,需要先将更新元组存储再执行更新。

IndexScan

可以直接转成B+Tree索引

tree_ = dynamic_cast<BPlusTreeIndexForTwoIntegerColumn *>(index_info_->index_.get())

对迭代器进行operator*()可以得到<key, rid>,再根据rid再page heap取tuple。

遇到了project2的一个bug,之前B+Tree删除时可能会也空节点,导致这次project出现访问end迭代器的情况。

Task #2 - Aggregation & Join Executors

Aggregation

遇到一个小bug,在第一次执行max操作时,我先将result->aggregates_[i]置为INT32_MIN,然后再执行max操作,但好像执行过程中将INT32_MIN当作最大值了…
最后改成INT32_MIN + 1了。

result->aggregates_[i] = ValueFactory::GetIntegerValue(INT32_MIN + 1);

一个关键点是输出结果是否含有key,如果没有key,则即使输出结果为空也要输出一行,但是如果有key,则输出为空。

NestedLoopJoin

  • inner join: 每次遍历完right_executor_,需要执行right_executor_->Init(),对于之前实现的算子,要特别注意Init(),别忘记初始化元数据。

  • left join: 如果right_executor_没有与left_tuple对应的,则应该与null结合。关键代码如下:

std::vector<Value> values;
for (uint32_t i = 0; i < left_schema.GetColumns().size(); ++i) {
  values.emplace_back(current_left_tuple_.GetValue(&left_schema, i));
}
for (uint32_t i = 0; i < right_schema.GetColumns().size(); ++i) {
  values.emplace_back(ValueFactory::GetNullValueByType(right_schema.GetColumn(i).GetType()));
}
*tuple = Tuple(values, &GetOutputSchema());

HashJoin & Optimizing NestedLoopJoin to HashJoin

哈希表的key一开始选为value,但是没有定义hash,会报错,可以参考聚合算子的聚合key写法,写一个HashKey。

Optimizing一开始不知道怎么下手,可以参考/src/optimizer/merge_projection.cpp。

我贴下关键代码:

  if (type == PlanType::NestedLoopJoin) { 
    ...
    if (dynamic_cast<ColumnValueExpression *>(key_expressions[0].get()) != nullptr) {  // t1.a = t2.a
      for (auto const &expr : key_expressions) {
        if (dynamic_cast<ColumnValueExpression *>(expr.get())->GetTupleIdx() == 0) {
          left_exprs.emplace_back(expr);
        } else {
          right_exprs.emplace_back(expr);
        }
      }
    } else {  // t1.a = t2.a and t1.b = t2.b and ...
    ...

    return std::make_shared<HashJoinPlanNode>(nlj_plan.output_schema_, left_child_plan, right_child_plan, left_exprs,
                                                right_exprs, nlj_plan.GetJoinType());
  }

哈希join只支持=,所以不用在乎nested的predicate。以及注意递归调用。

另外,expr属于左join还是右join需要根据tuple_index的值判断。如果有多个等式,key_expressions还会嵌套。

Task #3

Sort and limit

非常简单,但是记得init时元数据初始化,比如sort中,vector需要清空,limit中index需要置零。

贴一个排序参考

case OrderByType::ASC: {
  auto value_a = expr->Evaluate(&a, schema);
  auto value_b = expr->Evaluate(&b, schema);

  if (value_a.CompareLessThan(value_b) == CmpBool::CmpTrue) {
    return true;
  }
  if (value_a.CompareGreaterThan(value_b) == CmpBool::CmpTrue) {
    return false;
  }
}

Top-N

实现一个堆,这也不难,priority_queue走起。将sort中的比较函数实现成一个类。

将sort+limit优化成top-n别忘记递归优化。

优化

暂时没空。

评测与总结

截止提交时间(2023.05-18),gradescope上有25人完成了project2。

在这里插入图片描述
这次实验略微加深了对sql执行的理解,增加了vscode debug经验。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值