上几节我们完成了 sql 解释器的实现。通过解析 sql 语句,我们能知道 sql 语句想做什么,接下来就需要执行 sql 语句的意图,也就是从给定表中抽取所所需要的数据。要执行 sql 语句,我们需要了解所谓的“关系代数”,所谓代数本质上就是定义操作符和操作对象,在关系代数里,操作符有三种,分别为 select, project 和 product,操作对象就是数据库表。
select 对应的操作就是从给定的数据表中抽出满足条件的行,同时保持每行的字段没有变化。例如语句"select * from customer where id=1234",这条语句执行后,会将 customer 表中所有记录中 id 字段等于 1234 的行给抽取出来形成一个新的表。
project 对应的操作是,从给定数据表中选取若干个字段形成新表,新表的列发生变化,但是行的数量跟原表一样,例如语句"select name, age from customer",这条语句从原表中抽取出两个字段 name,age 形成新的表,新表的列数比原表少,但行数不标。
product,它对应笛卡尔积,它的操作对象是两个表,它从依次从左边表抽取出一行,跟右边表所有行组合,因此如果左边表的行数和列数是 Lr,Lc, 右边表的行数和列数是 Rr,Rc,那边操作结果的新表中,行数为 Lr * Rr, 列数为 Lc+Rc。
结合上面的关系代数,在解析给定 sql 语句后,要想执行相应操作,我们需要构造一种特定数据结构叫查询树,查询树的特点是,它的叶子节点对应数据库表,它 的父节点对应我们上面说的关系代数操作,我们看一个具体例子:“select name, age from customer where salary>2000"
这条语句对应两种关系代数操作也就是 select 和 project,它可以对应两种查询树,第一种为:
这个查询树的意思是,先对数据表 customer 做 project 操作,也就是先从表中把 name,age 这两列选出,并保证行数不变,然后在此结果上过滤每一行,将字段salary 大于 2000的行再选出来。不难想象我们还可以有另一种查询树,那就是先做 select 操作,也就是先把表中所有满足 salary>2000 的行全部选出来,然后在此基础上,再将 name,age 这两列抽出来,对应查询树如下:
大家可能感觉不同查询树本质上一样,事实上不同查询树对数据操作的效率影响很大,一种查询树对应的操作其效率可能比另一种好上十倍,乃至百倍,因此我们构造出所有可能的查询树后,还需要计算不同查询树的执行效率,然后选出最好的那种,这个步骤叫 planning。
在前面章节中,我们实现过 Scan 接口,这个接口可以套用到前面描述的几个操作符上,所以前面章节我们分别实现了 TableScan, SelectScan, ProjectScan, ProductScan,需要注意的是后面三个 Scan 对象在初始化时都要输入一个实现了 Scan 接口的对象,这就能对应到上面的查询树结构,最底部的叶子节点对应 TableScan,上一层的 SelectScan 在初始化时输入的 Scan 对象就是 TableScan,最上层节点对应 ProjectScan,它在初始化时输入的 Scan 对象就是 SelectScan。
下面我们把这几节内容结合起来用代码实现看看,先获得感性认知,后面我们再针对前面说过的 planning 做进一步分析。在前面的解析过程中,我们解析过 select 语句,它最后构造了一个 QueryData 对象,这个对象包含三部分,首先就是 fields,这部分可以用来实现 project 操作,第二部分是表名,它可以用来构造 TableScan 对象,最后的 pred 对象可以跟 TableScan 对象结合起来构造 SelectScan 对象。
在前面我们研究 record_manager 的时候实现过 TableScan,同时还提供了一个测试文件实现叫 table_scan_test.go,里面有个函数叫TestTableScanInsertAndDelete,它构造了一个数据表的数据存储,然后使用 TableScan 对象对这个表进行遍历操作,这里我们模仿当时的做法先构造一个 student 表,设置这个表只有 3 个字段,分别为 name,它为字符串类型,age,它为int 类型,最后是 id,它也是数字类型,然后我们给这个表添加几行数据,在 main.go 中增加代码如下:
func main() {
//构造 student 表
file_manager, _ := fm.NewFileManager("recordtest", 400)
log_manager, _ := lm.NewLogManager(file_manager, "logfile.log")
buffer_manager := bmg.NewBufferManager(file_manager, log_manager, 3)
tx := tx.NewTransation(file_manager, log_manager, buffer_manager)
sch := NewSchema()
//name 字段有 16 个字符长
sch.AddStringField("name", 16)
//age,id 字段为 int 类型
sch.AddIntField("age")
sch.AddIntField("id")
layout := NewLayoutWithSchema