完成Lab1后,就该开Lab2的新坑了。
实验步骤
阅读lab2.md
那先来看看lab2大致要做些什么。
Here's a rough outline of one way you might proceed with your GoDB implementation; more details on the steps in this outline, including exercises, are given in Section 2 below.
Implement the operators Filter (filter_op.go) and Join (join_op.go) and verify that their corresponding tests work. The comments in these operators contain details about how they should work.
Implement CountAggState, SumAggState, AvgAggState, MinAggState, and MaxAggState in agg_state.go. Here, you will write the logic that actually maintains an aggregation state over a particular field across multiple groups over a running sequence of input tuples. Use integer division for computing the average, since GoDB only supports integers. Only CountAggState, MinAggState, and MaxAggState needs to support both strings and integers, since the other operations do not make sense for strings.
Implement the Aggregate (agg_op.go) operator. As with other operators, aggregates implement the Iterator method so that they can be placed in GoDB query plans. Note that the output of an Aggregate operator is an aggregate value of an entire group for each call to the iterator, and that the aggregate constructor takes the aggregation and grouping fields.
Implement the Insert (insert_op.go) and Delete (delete_op.go) operators. Like all operators, Insert and Delete implement Iterator, accepting a stream of tuples to insert or delete and outputting a single tuple with an integer field that indicates the number of tuples inserted or deleted. Check that the tests for inserting and deleting tuples work properly.
Implement the Project (project_op.go), OrderBy (order_by_op.go), and Limit (limit_op.go) operators.- 首先是Filter和Join两个运算符。Join应该很熟悉了,天天LEFT JOIN啊INNER JOIN的。但是Filter可能比较少见?具体的边做实验边讲。
- aggregation–聚合。那就是实现GROUP BY?
- 各种聚合函数。SUM啊,AVG啊,COUNT啊,这些平时也很熟悉了。
- crud中的c和d。增删功能。
- 投影,排序和limit。project可能不太熟悉但另外两个也很熟悉了。
大致要完成的目标有了。看起来完成lab2就能看到一个比较基础的dbms了。
实验步骤
Exercise 1
join要完成的代码比较多,而且有点难度。那先来看看Filter。
先来看看Filter的结构。T constraints.Ordered表示的是“定义一个类型T,该类型满足constraints.Ordered约束”。而constraints.Ordered约束指的是“该类型的值必须可以进行大小比较”。比如int啊,string(按字典序排序)啊这些。getter func(DBValue) T相当于一个类型转换。
type Filter[T constraints.Ordered] struct {
op BoolOp
left Expr
right Expr
child Operator
getter func(DBValue) T
} 再来看看filter_op_test中是怎么初始化filter的。
_, t1, t2, hf, _, tid := makeTestVars()
hf.insertTuple(&t1, tid)
hf.insertTuple(&t2, tid)
var f FieldType = FieldType{"name", "", StringType}
filt, err := NewStringFilter(&ConstExpr{StringField{"sam"}, StringType}, OpEq, &FieldExpr{f}, hf)func NewIntFilter(constExpr Expr, op BoolOp, field Expr, child Operator) (*Filter[int64], error) {
if constExpr.GetExprType().Ftype != IntType || field.GetExprType().Ftype != IntType {
return nil, GoDBError{IncompatibleTypesError, "cannot apply int filter to non int-types"}
}
f, err := newFilter[int64](constExpr, op, field, child, intFilterGetter)
return f, err
}
继续来直接看ToDo。返回一个Desc?但是Filter里面只有child,作为heapfile有Descriptor方法,那直接返回即可。
// Return a TupleDescriptor for this filter op.
func (f *Filter[T]) Descriptor() *TupleDesc {
// TODO: some code goes here
return f.child.Descriptor()
}自己写个test看看输出结果
func TestDescriptor(t *testing.T) {
_, t1, t2, hf, _, tid := makeTestVars()
hf.insertTuple(&t1, tid)
hf.insertTuple(&t2, tid)
var f FieldType = FieldType{"age", "", IntType}
filt, err := NewIntFilter(&ConstExpr{IntField{25}, IntType}, OpGt, &FieldExpr{f}, hf)
if err != nil {
t.Errorf(err.Error())
}
desc := filt.Descriptor()
fmt.Printf("filter desc: %v\n", desc)
}嗯,没毛病。

实验步骤
Iterator
又要写个迭代器,那先来看看要实现什么吧。
// Filter operator implementation. This function should iterate over
// the results of the child iterator and return a tuple if it satisfies
// the predicate.
// HINT: you can use the evalPred function defined in types.go to compare two values
func (f *Filter[T]) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
// TODO: some code goes here
return nil, nil
}
根据之前在heap file和heap page中编写迭代器的经验,先写个匿名函数。
func (f *Filter[T]) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
// TODO: some code goes here
return func() (*Tuple, error) {
return nil, nil
}, nil
}接下来想下该实现什么功能。Filter,字面意思来理解的话就是筛选出特定的字段。同时注释中又提到了遍历子迭代器的所有结果,并返回合适的tuple。那先写个大致结构出来。
func (f *Filter[T]) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
// TODO: some code goes here
iter, err := f.child.Iterator(tid)
if err != nil {
return nil, err
}
return func() (*Tuple, error) {
for {
tuple, err := iter()
if err != nil || tuple == nil {
return nil, err
}
return tuple, nil
}
}, nil
}
test一下看看能输出什么。

返回的cnt是2,而我们需要1,说明缺少if判断是否匹配的条件。但是if到底应该判断什么呢?
别忘了我们还没用上Filter。我们打个断点看看此时filter中,各个数据成员的值是什么。

op的值是opgt?想到gt那就是greater than。那么为什么要提到op呢?注释中提到一点
// HINT: you can use the evalPred function defined in types.go to compare two values那我们就看看evalPred是干什么的。
func evalPred[T constraints.Ordered](i1 T, i2 T, op BoolOp) bool {
switch op {
case OpEq:
return i1 == i2
case OpNeq:
return i1 != i2
case OpGt:
return i1 > i2
case OpGe:
return i1 >= i2
case OpLt:
return i1 < i2
case OpLe:
return i1 <= i2
case OpLike:
s1, ok := any(i1).(string)
if !ok {
return false
}
regex, ok := any(i2).(string)
if !ok {
return false
}
regex = "^" + regex + "$"
regex = strings.Replace(regex, "%", ".*?", -1)
match, _ := regexp.MatchString(regex, s1)
return match
}
return false
}
这不就是根据输入的op,再进行相应的条件判断嘛。比如op是opgt,左值是user_id,右边是2的话,写成对应的SQL就应该是
WHERE user_id opgt 2
那就好写了。去对应字段的值,然后根据op返回true还是false来筛选最后得到的数据。
func (f *Filter[T]) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
// TODO: some code goes here
iter, err := f.child.Iterator(tid)
if err != nil {
return nil, err
}
return func() (*Tuple, error) {
for {
tuple, err := iter()
if err != nil || tuple == nil {
return nil, err
}
leftVal, _ := f.left.EvalExpr(tuple)
rightVal, _ := f.right.EvalExpr(tuple)
tmp := evalPred(f.getter(leftVal), f.getter(rightVal), f.op)
_ = tmp
if evalPred(f.getter(leftVal), f.getter(rightVal), f.op) {
return tuple, nil
}
}
}, nil
}
run一下test,两个test都能过。

下篇再看看怎么实现join,争取2天内写完。