Giter Club home page Giter Club logo

miniob-2022's Introduction

Hi there! 👋

GitHub Stats

miniob-2022's People

Contributors

atoomix avatar chrisyuan avatar frankxmx avatar hnwyllmm avatar leauny avatar longdafeng avatar luooofan avatar raygift avatar tomtiao avatar wangqiim avatar xiaoleizi2016 avatar yang-jun-kun avatar yangefei avatar zhang-x0 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

miniob-2022's Issues

Summary

base on: 6d7e170

比赛开始之前

找一些文档看,还有官方的赛前培训

可以做去年的题目,我们开始之前把2021年的题目做了80分

初赛

准备

首先要为后续的开发、测试、版本管理构建一些基础模块,比如说编译安装以及测试的 shell 脚本、代码格式化(clang-format)、代码静态检查(clang-tidy)、Gitee 和 Github 的 Issue 和 PR 模板、Github 的 workflow 等等

涉及到的几个提交如下:

clang-format/tidy

clang-format 和 clang-tidy 模块源自bustub/build_support at master · cmu-db/bustub,我们在此基础上禁用了一些 check,并且限制了被检查的目录

这两个工具除了规范编程以外,也有很实际的好处:

  • clang-format 有助于合并的时候减少因为格式导致的冲突
  • clang-tidy 有助于发现一些隐藏的 bug

使用 clang 编译

e35aeb5 (这个其实没啥用)

版本管理

主要就三个原则:

  • 新功能新分支
  • 合并新功能要提 PR,做 review
  • 提 PR 之前要先 rebase 到 main 分支,并提交官网测试

rebase 主要是为了让提交历史更清晰,尽可能地线性化

这是我们 main 分支最后的一部分提交记录
image

赛题分析

在公布了今年的赛题后,首先有80分的题目我们已经做了:basic、select-meta、drop-table、update、date、select-tables、insert、order-by,虽然有些需要做修改,还有一个改动略大,但起码大体上没啥问题。对于这80分的题目我们基本上是把对应的提交直接 rebase 到了今年的开发分支上,然后做修改提测

然后我们发现对于 update、insert、show-index、multi-index、unique、clog 这几个赛题是一条线上的,这条线交给成林 @Zhang-X0 来做,我只了解一部分,一起调了调 bug,这几个赛题后面就不说了

剩下的由我和琨琨 @yang-jun-kun 做,这部分赛题思路我都熟,以下都会提到

具体的实现可以找对应的 PR 看

赛题思路

drop-table

先看 create table 和 create index 的流程,在 drop table 的时候要处理好各种资源

有一个要注意的地方是 disk buffer pool 的析构,会在 close file 的时候把自己释放掉,但是 buffer pool manager 中还有信息未删除,会导致问题

typecast

先把类型转换的函数单独实现,存一个函数指针的二维数组 cast_to

  • 类型转换的时候会新分配内存空间存储转换后的结果,统一用 malloc 分配空间,返回 void*,这样外部调用完要释放空间的时候直接用 free 释放即可
  • 对于不支持的类型转换写一个 not_support 函数,返回空指针

然后考虑要在哪里用这个模块就行

另外,在 do_predicate 的时候比较的规则是:

  • date 类型不考虑
  • null 类型比较结果全为false
  • 涉及到浮点数,都转成浮点数比较
  • 整数和字符串,都转成浮点数比较

总结来说就是不考虑 date,先处理 null,其他情况都先转成 float

like

  • 用 C++ regex 库来实现
  • 默认是 ignore case
  • 记得要改词法文件中对字符串的正则识别规则,我们最后把单引号和双引号分开来了

select-tables && join-tables

Tuple 是算子执行阶段中非常好的一层抽象:

class Tuple {
public:
Tuple() = default;
virtual ~Tuple() = default;
virtual int cell_num() const = 0;
virtual RC cell_at(int index, TupleCell &cell) const = 0;
virtual RC find_cell(const Field &field, TupleCell &cell) const = 0;
virtual RC cell_spec_at(int index, const TupleCellSpec *&spec) const = 0;
// push back records of the tuple to arg:record
virtual void get_record(CompoundRecord &record) const = 0;
// this func will set all records
// invoke this func will erase begin arg:record
virtual void set_record(CompoundRecord &record) = 0;
// this will not set all records
// invoke this func will erase end arg:record
virtual void set_right_record(CompoundRecord &record) = 0;
};

利用好 Tuple 这个抽象,构造一个 CompoundTuple(一开始叫 JoinedTuple)

最初的实现:

  • 存一个 RowTuple 的数组(一开始考虑的是 JoinOperator 的孩子只能是 JoinOperator 或者是 TableScanOperator)
  • 不考虑缓存右表数据

后来因为在实现 OrderBy 的时候必须缓存表数据,所以把这里也改成了缓存右表数据:

  • 用 CompoundRecord 表示一个 Record 的数组
  • 做深拷贝

并且考虑到 JoinOperator 的孩子可能是其他类型的算子(尤其是考虑到 ProjectOperator 这种特殊类型,必须用 ProjectTuple 才能得到正确的信息,虽然今年的赛题里没有这种情况):

  • 把 RowTuple 的数组改成了二叉树的结构,左右各一个 Tuple*

实现 InnerJoinOn 的时候,一开始偷懒做的方案是把 on filter 作为 where filter,等所有的表笛卡尔连接后再统一做 filter,结果超时了

最后的方案是在构造 JoinOperator 的时候尽可能地把 filter 推下去,给 JoinOperator 集成了过滤功能

最终,JoinOperator 的 next 流程为:

  • 如果是第一次调用 next,则预取整张右表并缓存(这里对 Record 做了拷贝)
  • 如果当前已过滤的右表还没遍历完,则设置 record,返回 SUCCESS
  • 如果遍历完了,左表取下一行,对缓存的整张右表做过滤,得到已过滤的右表,然后递归调用 next
  • 如果左表已经取完了,返回 RECORD_EOF

text

发现了官方的彩蛋(或者说卡了一个 bug
直接看 commit msg: 8abfe5a

分隔

到这里为止,第一页的基本都做完了,分析后面的赛题的时候发现存在一个 null 测例,其前后的一些测例都要把 null 考虑进去

另外还发现 aggr-func 赛题排在很前面,group-by 赛题排在很后面,但是我觉得这两个应该一起做,仔细考虑之后,重排了一下做题顺序,具体安排见 #14

order-by

  • 需要缓存整张表的数据,用一个 CompoundRecord 的数组来存
  • 获得表的数据后要排序,写一个 lambda compactor,用 std::sort 排序得到一个排好序的索引数组,next 的时候用这个索引数组来找数据返回

expression

在语法分析阶段根据优先级构造成树的形式,resolve 阶段保留树的结构,执行阶段后序遍历执行

在语法阶段增加了 UnaryExpr、BinaryExpr、Expr,在 resolve 阶段给 Expression 扩充了 BinaryExpression,具体的运算则是对 TupleCell 做扩展

function

对表达式的扩充,语法阶段增加了 FuncExpr,对应到 resolve 后的 FuncExpression

group-by && aggrfunc

重排做题顺序主要是为了把这两一起实现 #14

这块新增了 GroupByUnit GroupByStmt GroupByOperator GroupByTuple,以及 AggrFuncExpr(esssion)

实现的关键在于 GroupByTuple,也是利用了 Tuple 这个抽象

  • 存储 GroupBy 算子的子算子的 tuple,每次从这里取数据然后运算
  • 存储字段表达式和聚集函数表达式,实际上相当于是把投影列全存起来了(但并没有用 GroupByOper 取代 ProjectOper,只要把 GroupByTuple 的 find_cell 函数实现好就行)
  • 存储字段结果,这个与字段表达式是一一对应的
  • 存储聚集函数结果、组内计数(count 和 avg 用)、组内 all null 标志,这些与聚集函数表达式是一一对应的
  • 提供 do aggregate first 函数,用于每个 group 内的第一行数据,初始化聚集函数的运算结果,以及填充该 group 内的字段结果
  • 提供 do aggregate 函数,用于每个 group 内的非第一行数据进行聚集运算
  • 提供 do aggregate done 函数,用于读到了新 group 的数据时,处理当前 group 的结果(考虑 null 类型,count、avg 函数的运算的结果)
  • 实现基类的 find cell 函数,如果要找的字段有聚集函数信息,则从聚集函数结果里找,否则从字段结果里面找

其他的几个点:

  • 有 GroupBy 子句时对投影列是有限制的,要么是 GroupBy 字段,要么带聚集函数
  • 构造算子树的时候要把 select 子句中的投影列信息、having 子句中的字段、聚集函数信息传递给 GroupByOper

具体实现看 #15 过了两周才写这里,忘了不少,但最关键的部分已经写了

null

关键思路:在 Record 中给每个 nullable 的字段加一个 null 标志

实现上,为了简单,给所有字段都加这个标记,具体来说有两种方案:

  • 每个字段旁边加一个 bit,或者一个 byte
  • 每个 record 加一个 __null 字段,是一个标识对应位置是否为 null 的 bitmap

我们选择了第二种做法,这样也方便了索引对 null 的支持,把 bitmap 作为 key 的一部分,然后扩展 key compactor 就行

simple-sub-query && complex-sub-query

子查询首先要考虑如何区分父子查询的 where 谓词条件、join 谓词条件、投影列、order-by 表达式、group-by 表达式、having 谓词条件等,我们的方案是用涉及到的这些归约节点存一个 length 信息

像这样

opt_having:
/* empty */ {
$$ = 0;
}
| HAVING having_condition having_condition_list {
$$ = $3 + 1;
}
;
having_condition_list:
/* empty */ {
$$ = 0;
}
| AND having_condition having_condition_list {
$$ = $3 + 1;
}
;

对于 In 和 Exists 子查询其实也是给表达式做扩充,增加了 ListExpr(ession) 和 SubQueryExpr(ession)

do predicate 的时候:

  • 先处理 Exists,右表达式一定是一个 SubQueryExpression
  • 再处理 In,右表达式可能是 ListExpression 也可能是 SubQueryExpression
  • 再处理其他操作符

对于父子查询表关联,我们给每个 Operator 增加了一个 parent tuple,do predicate 的时候把 parent tuple 和子算子的 tuple 拼成一个 CompoundTuple 传下去

最后对于复杂子查询留了一个 and 和 or 条件支持:

  • 要想支持 and 和 or 嵌套出现,需要像表达式那样处理成树的结构
  • 直接把原来语法模块中的 condition_list 改掉代价太大,所以保留了 condition_list 以及相关的地方,非 select 语句以及 select 语句的 inner join on 用的还是 condition_list
  • 把 AND 和 OR 归属于比较操作符,也就是说 where 子句最后会把所有的 filter 归约为一个树结构的 Condition,而不是原来的 Condition 的数组
  • 针对 select 的 where 子句单独使用了一个 opt_where,本身作为一个 expr(把 Condition 也归属于 expr 的一种)
  • 后面的处理细节看提交 82e9981

alias

给每个表达式存一个别名就行

update-select

有了前面子查询的基础,这里语法阶段只需要把 updates 中原来的 values 拓展成 exprs 即可,在 resolve 阶段生成对应的 Expression,执行阶段(UpdateOperator Open)先 get_value 拿到 TupleCell,根据 TupleCell 构造 Value,以适配 table update_record 的接口

非功能性 fail

basic 挂了:钉钉群里问的镜水老师,说是重启失败了,我们在本地也复现了

本地能过测例,提交后因为段错误 core 了:

  • 可能是变量未初始化的问题:成林那边有一次看了一下午发现是一个 bool 变量没初始化的原因;琨琨这边也有一次语法分析的时候没把指针初始化为 null 导致 Release 编译开 ASAN 后本地测例就 core 了
  • 内存越界:比如访问野指针、数组越界等,可以用 ASAN 检查,可以开 clang-tidy 检查

此外还可以利用评测系统二分找 bug:
可以看看 alias-debugunique-debug 这两个分支的最后几次提交

TODO

编码上主要是内存管理没太做好,第一周还注意着,第二周就不太行了

项目管理上还有不少问题:

  • review:pr 都创建好了,但是因为赶时间基本没有 review,而且确实出现了因此导致的问题 #19
  • kanban issue 和 doc:没时间做,大部分赛题只做了一些零零散散的记录,经常是有了可行的方案后直接开始写代码,发现 bug 就调,没有把这些都记录下来

Last

在满分之前没好好做文档和问题记录,想着之后补起来,但由于时间原因又隔了一个礼拜,等初赛结束的时候才勉强把这篇总结写完。当时的一些细节好多已经忘了,初赛过程中一些零散的记录也不愿再整理了,之后会和这个总结一起放到 wiki 里保存。就先这样吧

Case Failed: primary-unique

base on: original unique impl

diff result:

ID1 | ID2 | COL1 | COL2
3. UPDATE-SELECT
UPDATE unique_table2 SET id1=1,id2=4 where id2=2;
-SUCCESS
+FAILURE RECORD_RECORD_NOT_EXIST
SELECT * FROM unique_table2;
1 | 1 | 1 | 1
-1 | 4 | 1 | 1
+2 | 2 | 1 | 1
2 | 3 | 1 | 1

reproduce:

create table t2(c1 int, c2 int, c3 int);
create unique index i2 on t2(c1,c2);
insert into t2 values(1,2,3);
insert into t2 values(1,2,4); -- should fail ①
insert into t2 values(1,3,3);
select * from t2;

update t2 set c1=2,c2=3 where c2=2; -- should success **reproduce here**
select * from t2;

insert into t2 values(1,1,1),(1,3,4); -- should fail
select * from t2;

reason:
at ①:at first, insert record successly, but failed to insert indexes, so delete indexes entrys

Simple/complex sub query design

语法解析
用 xxx_list 和 from、where、opt_group_by、opt_order_by 等这些语法单元来维护长度信息,从而区分开父子查询的 projects、froms、conditions 等

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.