MIT6.5830 Lab1-GoDB实验记录(三) – WhiteNight's Site
标签:Golang
在分析完tuple.go的组成之后,接下来我们将补全tuple.go的缺失部分,并通过tuple_test.go的测试。
实验步骤
结构体数组的比较,复制与合并
上一篇记录的结尾我们提到了“tuple descriptor”,即“元组描述符”。首先看到第一个TODO
// Compare two tuple descs, and return true iff
// all of their field objects are equal and they
// are the same length
func (d1 *TupleDesc) equals(d2 *TupleDesc) bool {
// TODO: some code goes here
return true
}
“比较两个元组描述符”结构体是否相等。说白了就是比较两组(不是“个”!!!TupleDesc包含的是字段切片,而不是单个字段)属性(列)是否相等。
写者注
数据库这一块的“别名”就和SQL中用到别名的地方一样多。找个时间再好好梳理这些数据库相关的术语吧。
为什么要作这么个比较?我们查找equals的全部引用,并在最后一路找到了tuple_test.go中的
那么接下来就是先补全”比较“的代码,相等返回true,不相等返回false。比较简单
func (d1 *TupleDesc) equals(d2 *TupleDesc) bool {
// TODO: some code goes here
if len(d1.Fields) != len(d2.Fields) {
return false
}
for i := range d1.Fields { //i为下标
if d1.Fields[i] != d2.Fields[i] {
return false
}
}
return true
}
run test一下,能pass就说明OK了。
写者注
第一次发现能单独测试代码中的某个函数,属实是刘姥姥进了大观园。
接下来是拷贝。一个结构体切片拷贝到另一个结构体切片,也比较简单,调用copy拷贝切片即可。最后返回的是TupleDesc,所以要给这个返回的TupleDesc.Fields赋值为newTupleDesc。
func (td *TupleDesc) copy() *TupleDesc {
// TODO: some code goes here
newTupleDesc := make([]FieldType, len(td.Fields))
copy(newTupleDesc, td.Fields)
return &TupleDesc{Fields: newTupleDesc} //replace me
}
跑一下test,pass。
接下来是合并。更简单,直接append完事。不过要记住是把desc2的Fields合并到desc.Fields的后面,顺序不要弄错了。
// Merge two TupleDescs together. The resulting TupleDesc
// should consist of the fields of desc2
// appended onto the fields of desc.
func (desc *TupleDesc) merge(desc2 *TupleDesc) *TupleDesc {
// TODO: some code goes here
desc.Fields = append(desc.Fields, desc2.Fields...)
return &TupleDesc{desc.Fields} //replace me
}
run test一下,过。
实验步骤
观察Tuple Methods
有了DBType–数据库可用的基础数据类型,FieldType–管理字段的结构体,有了TupleDesc–管理元组的结构体,那么接下来实现的就是Tuple–元组本身了。我们先来看看它的数据结构
Tuple有三个数据成员,TupleDesc不用多说,用于描述xxx字段的数据类型是什么;Fields也不用多说,用于存储具体的数据;问题在于Rid是什么。
根据注释,可以得知我们通过RID来追踪该记录是在哪一页的哪一个位置。比如在oracle中的RID,说白了就是每一行都有个内置的标识符,这个标识符你直接SELECT的时候是看不到的,但是你可以通过RID快速的找到某个特定的行。
实验步骤
读写缓冲区
开始抽象起来了。个人感觉应该是本次实验记录中最难的部分。
这里篇幅会很长,得开另一个文章来单独记录,所以这里先跳过。
实验步骤
补全剩余的Tuple函数
元组之间的比较相对来说比较简单,元组描述符的比较直接调用之前写的Desc.equals即可。接下来再对元组的字段进行比较。
但是元组的recordId需要比较吗?每个元组都有自己独特的RID,比较RID的意义在于“A元组是否等于A元组”,不比较的话equals的意义就变成了“A元组是否等于B元组”。这里没说要不要比较RID,那就先不比较。
// Compare two tuples for equality. Equality means that the TupleDescs are equal
// and all of the fields are equal. TupleDescs should be compared with
// the [TupleDesc.equals] method, but fields can be compared directly with equality
// operators.
func (t1 *Tuple) equals(t2 *Tuple) bool {
// TODO: some code goes here
if t1.Desc.equals(&t2.Desc) == false {
return false
}
for i := range t1.Fields {
if t1.Fields[i] != t2.Fields[i] {
return false
}
}
return true
}
接下来是合并。需要注意的是不能直接写成t1.Fields=append(t1,Fields,t2.Fields)。不带”…“的append是直接把整个t2.Fields作为一个对象都合并到t1.Fields中,而我们需要的是枚举Fields中的每个数据再把它合并进行。
// Merge two tuples together, producing a new tuple with the fields of t2 appended to t1.
func joinTuples(t1 *Tuple, t2 *Tuple) *Tuple {
// TODO: some code goes here
t1.Fields = append(t1.Fields, t2.Fields...)
return &Tuple{Fields: t1.Fields}
}
接下来继续观察。只剩最后两个TODO了。
compareField,很明显就是字段之间的比较。在compareField的注释中提到了我们需要用的Expr.EvalExpr函数。我们先不管它是什么,但是在补全compareField之前,我们需要先补全project函数。
这是因为在EvalExpr中我们需要调用project函数去获取某个特定的字段。否则最后project的默认返回值为nil,肯定没法通过测试。
那就先补全project。那么project是什么?项目吗?当然不是。
在数学中,project经常用于表示“投影”。而在SQL中,投影简单来说就是从表中选取特定的行。比如下面这两个SQL语句就将说明投影(Projection)和选择(Selection)的区别。
SELECT * FROM table //选择
SELECT name,id FROM table //投影
明白了project是干什么的,那么接下来就是补全它。
从一个元组中选择出特定的字段,并且被选中的字段应该作为一个新的元组。有点像在MySQL中我们SELECT xxx时会生成一个虚拟表,此时我们得到的是新的结果集,而原表的信息是不会更改的。
当然了,即使是新的元组,它们的描述符还是一样的,只是字段的数量改变了–因为我们要选择出特定字段。同时在这里我们还需要用到前面的findFieldInTd函数。因为元组Desc的字段和我们要选择字段的下标是一致的,它们的关系举例如下
TupleDesc:{"name","age",...}
Tuple:{"Sam","19",...}
//假设只SELECT name
newTupleDesc:{"name"}
newTuple:{"Sam"} //两者对应的下标相等,此处都为0
// Project out the supplied fields from the tuple. Should return a new Tuple
// with just the fields named in fields.
//
// Should not require a match on TableQualifier, but should prefer fields that
// do match on TableQualifier (e.g., a field t1.name in fields should match an
// entry t2.name in t, but only if there is not an entry t1.name in t)
func (t *Tuple) project(fields []FieldType) (*Tuple, error) {
// TODO: some code goes here
newTupleDesc := &TupleDesc{Fields: fields}
newTuple := &Tuple{Desc: *newTupleDesc}
for _, field := range fields {
index, err := findFieldInTd(field, &t.Desc)
if err != nil {
return nil, err
}
newTuple.Fields = append(newTuple.Fields, t.Fields[index])
fmt.Println(t.Fields[index])
}
return newTuple, nil //replace me
}
run test一下,过。不过lab1提供的测试样例太少,我们后续需要自定义一下输入以测试我们的代码中不同情况下都能正常运行。后续的实验记录中会再次提到这点,这里我们先跳过。
project搞定。接下来就是字段之间的比较了。
字段之间的比较分为两种,IntField之间的比较和StringField之间的比较。int类型的很好理解,但是字符串我们该怎么比较大小呢?这里注释也只是叫我们比较字符串的大小,没说按什么方式排序,那我们得自己研究研究。
看看测试样例,输入“sam”和“george jones”,前者比后者大。那么大概率是按照字典序排列的。
那么接下来开始补全代码。我们需要先判断两个字段的类型,相同类型才能进行比较。那么代码如下
// Apply the supplied expression to both t and t2, and compare the results,
// returning an orderByState value.
//
// Takes an arbitrary expressions rather than a field, because, e.g., for an
// ORDER BY SQL may ORDER BY arbitrary expressions, e.g., substr(name, 1, 2)
//
// Note that in most cases Expr will be a [godb.FieldExpr], which simply
// extracts a named field from a supplied tuple.
//
// Calling the [Expr.EvalExpr] method on a tuple will return the value of the
// expression on the supplied tuple.
func (t *Tuple) compareField(t2 *Tuple, field Expr) (orderByState, error) {
// TODO: some code goes here
value, err := field.EvalExpr(t)
if err != nil {
return -1, err
}
value2, err := field.EvalExpr(t2)
if err != nil {
return -1, err
}
switch a := value.(type) {
case IntField:
b, ok := value2.(IntField)
if !ok {
return 0, fmt.Errorf("b is not an IntField")
}
if a.Value > b.Value {
return OrderedGreaterThan, nil
} else if a.Value < b.Value {
return OrderedLessThan, nil
} else if a.Value == b.Value {
return OrderedEqual, nil
}
case StringField:
b, ok := value2.(StringField)
if !ok {
return 0, fmt.Errorf("b is not an StringField")
}
if a.Value > b.Value {
return OrderedGreaterThan, nil
} else if a.Value < b.Value {
return OrderedLessThan, nil
} else if a.Value == b.Value {
return OrderedEqual, nil
}
}
return -1, nil // replace me
}
run test一下,如果能正常运行说明project和compareField没什么问题。