14.查询语言:执行
# 14. 查询语言:执行
# 14.1 引言
为了执行一条语句,需要将该语句转换为对现有数据库接口的函数调用。例如,CREATE TABLE
语句被转换为一个简单的函数调用:
func qlCreateTable(req *QLCreateTable, tx *DBTX) error {
return tx.TableNew(&req.Def)
}
2
3
其他语句没这么简单,但它们本质上仍是粘合代码,本身并没有太多功能。执行 SELECT
语句则更为复杂:
func qlSelect(req *QLSelect, tx *DBReader, out []Record) ([]Record, error) {
// 记录
records, err := qlScan(&req.QLScan, tx, out)
if err != nil {
return nil, err
}
// 输出
for _, irec := range records {
orec := Record{Cols: req.Names}
for _, node := range req.Output {
ctx := QLEvalContex{env: irec}
qlEval(&ctx, node)
if ctx.err != nil {
return nil, ctx.err
}
orec.Vals = append(orec.Vals, ctx.out)
}
out = append(out, orec)
}
return out, nil
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
该函数做了两件事:
- 根据某些条件获取行(
qlScan
)。 - 通过计算表达式将行转换为输出(
qlEval
)。
这两件事也是其他一些语句的组成部分。我们先来看表达式计算。
# 14.2 表达式计算
有3处地方需要根据某一行来计算表达式:SELECT
语句的表达式列表、FILTER
子句中的条件,以及 UPDATE
语句中的值。
qlEval
函数用于完成这些任务。为使代码简洁,计算结果(标量值或错误)和当前行被存放在 QLEvalContex
结构体中。
// 用于计算表达式
type QLEvalContex struct {
env Record // 可选的行值
out Value
err error
}
// 递归计算表达式
func qlEval(ctx *QLEvalContex, node QLNode)
2
3
4
5
6
7
8
9
qlEval
函数递归计算子表达式,然后进行运算符的计算。处理列名和字面值(整数和字符串)很直观。
func qlEval(ctx *QLEvalContex, node QLNode) {
if ctx.err != nil {
return
}
switch node.Type {
// 引用列
case QL_SYM:
if v := ctx.env.Get(string(node.Str)); v != nil {
ctx.out = *v
} else {
qlErr(ctx, "unknown column: %s", node.Str)
}
// 字面值
case QL_I64, QL_STR:
ctx.out = node.Value
// 更多情况;省略...
default:
panic("not implemented")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
处理运算符也很容易。在计算表达式时,我们还会进行类型检查。以 QL_NEG
运算符为例:
// 一元运算符
case QL_NEG:
qlEval(ctx, node.Kids[0])
if ctx.out.Type == TYPE_INT64 {
ctx.out.I64 = -ctx.out.I64
} else {
qlErr(ctx, "QL_NEG type error")
}
2
3
4
5
6
7
8
qlErr
函数将错误保存到 QLEvalContex
中。
# 14.3 获取行
获取行是查询需要做的另一项工作。
// 执行查询
func qlScan(req *QLScan, tx *DBReader, out []Record) ([]Record, error) {
sc := Scanner{}
err := qlScanInit(req, &sc)
if err != nil {
return nil, err
}
err = tx.Scan(req.Table, &sc)
if err != nil {
return nil, err
}
return qlScanRun(req, &sc, out)
}
// 根据INDEX BY子句创建Scanner
func qlScanInit(req *QLScan, sc *Scanner) error
// 从Scanner中获取所有行
func qlScanRun(req *QLScan, sc *Scanner, out []Record) ([]Record, error)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 14.3.1 初始化迭代器
首先,我们需要将 INDEX BY
子句转换为 Record
类型,供 Scanner
迭代器使用。INDEX BY
子句有以下几种情况:
- 点查询。
- 开放式范围(单个比较)。
- 封闭式范围(两个不同方向的比较)。
相应示例如下:
select expr... from table_name index by a = 1;
select expr... from table_name index by a > 1;
select expr... from table_name index by a > 1 and a < 5;
2
3
该子句对应 QLScan
的 Key1
和 Key2
字段。
type QLScan struct {
Table string
Key1 QLNode // 第一个范围或点查询
Key2 QLNode // 第二个范围
Filter QLNode
Offset int64
Limit int64
}
2
3
4
5
6
7
8
这种转换只是一些类型检查和类型转换操作。
// 根据INDEX BY子句创建Scanner
func qlScanInit(req *QLScan, sc *Scanner) error {
if req.Key1.Type == 0 {
// 没有INDEX BY;按主键扫描
sc.Cmp1, sc.Cmp2 = CMP_GE, CMP_LE
return nil
}
var err error
sc.Key1, sc.Cmp1, err = qlEvalScanKey(req.Key1)
if err != nil {
return err
}
if req.Key2.Type != 0 {
sc.Key2, sc.Cmp2, err = qlEvalScanKey(req.Key1)
if err != nil {
return err
}
}
if req.Key1.Type == QL_CMP_EQ && req.Key2.Type != 0 {
return errors.New("bad INDEX BY")
}
if req.Key1.Type == QL_CMP_EQ {
sc.Key2 = sc.Key1
sc.Cmp1, sc.Cmp2 = CMP_GE, CMP_LE
}
return nil
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
qlEvalScanKey
函数用于将比较运算符转换为 Record
类型和 CMP_??
枚举值。这里也没什么复杂的内容。
func qlEvalScanKey(node QLNode) (Record, int, error)
# 14.3.2 迭代行
在迭代行时,我们需要处理 LIMIT
和 FILTER
子句。
// 从Scanner中获取所有行
func qlScanRun(req *QLScan, sc *Scanner, out []Record) ([]Record, error) {
for i := int64(0); sc.Valid(); i++ {
// LIMIT
ok := req.Offset <= i && i < req.Limit
rec := Record{}
if ok {
sc.Deref(&rec)
}
// FILTER
if ok && req.Filter.Type != 0 {
ctx := QLEvalContex{env: rec}
qlEval(&ctx, req.Filter)
if ctx.err != nil {
return nil, ctx.err
}
if ctx.out.Type != TYPE_INT64 {
return nil, errors.New("filter is not of boolean type")
}
ok = (ctx.out.I64 != 0)
}
if ok {
out = append(out, rec)
}
sc.Next()
}
return out, nil
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 14.4 执行语句
SELECT
语句的代码已经列出。我们再来看 DELETE
语句,它和 SELECT
语句差别不大。
func qlDelete(req *QLDelete, tx *DBTX) (uint64, error) {
records, err := qlScan(&req.QLScan, &tx.DBReader, nil)
if err != nil {
return 0, err
}
tdef := getTableDef(&tx.DBReader, req.Table)
pk := tdef.Cols[:tdef.PKeys]
for _, rec := range records {
key := Record{Cols: pk}
for _, col := range pk {
key.Vals = append(key.Vals, *rec.Get(col))
}
deleted, err := tx.Delete(req.Table, key)
assert(err == nil && deleted) // 删除已存在的行
}
return uint64(len(records)), nil
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
此时,其他语句应该也很容易理解了。
# 14.5 后续步骤
如你所见,本章并没有添加太多新内容。查询语言的实现大多是对之前已实现功能的粘合代码。在学习到这里之前,你可能一直不明白数据库是如何将SQL语句转换为行数据的。现在你对数据库有了更深入的理解,或许还想探索其他方面的内容。
你可以尝试为数据库添加更多功能,比如连接(joins)、分组(group bys)和聚合(aggregations),这些在分析型查询中很常见。在当前阶段,实现这些功能应该并不困难。
你还可以为数据库构建客户端API和服务器。为了管理并发访问,服务器进程是必不可少的。为此,你需要学习网络编程。虽然在Go语言中进行网络编程相当简单且抽象层次较高,但如果你愿意深入学习,也可以选择从头开始学习相关知识。