14.查询语言:执行

# 14. 查询语言:执行

# 14.1 引言

为了执行一条语句,需要将该语句转换为对现有数据库接口的函数调用。例如,CREATE TABLE 语句被转换为一个简单的函数调用:

func qlCreateTable(req *QLCreateTable, tx *DBTX) error {
    return tx.TableNew(&req.Def)
}
1
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

该函数做了两件事:

  1. 根据某些条件获取行(qlScan)。
  2. 通过计算表达式将行转换为输出(qlEval)。

这两件事也是其他一些语句的组成部分。我们先来看表达式计算。

# 14.2 表达式计算

有3处地方需要根据某一行来计算表达式:SELECT 语句的表达式列表、FILTER 子句中的条件,以及 UPDATE 语句中的值。

qlEval 函数用于完成这些任务。为使代码简洁,计算结果(标量值或错误)和当前行被存放在 QLEvalContex 结构体中。

// 用于计算表达式
type QLEvalContex struct {
    env Record // 可选的行值
    out Value
    err error
}

// 递归计算表达式
func qlEval(ctx *QLEvalContex, node QLNode)
1
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")
    }
}
1
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")
    }
1
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)
1
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 子句有以下几种情况:

  1. 点查询。
  2. 开放式范围(单个比较)。
  3. 封闭式范围(两个不同方向的比较)。

相应示例如下:

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;
1
2
3

该子句对应 QLScanKey1Key2 字段。

type QLScan struct {
    Table  string
    Key1   QLNode // 第一个范围或点查询
    Key2   QLNode // 第二个范围
    Filter QLNode
    Offset int64
    Limit  int64
}
1
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
}
1
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)
1

# 14.3.2 迭代行

在迭代行时,我们需要处理 LIMITFILTER 子句。

// 从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
}
1
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
}
1
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语言中进行网络编程相当简单且抽象层次较高,但如果你愿意深入学习,也可以选择从头开始学习相关知识。

上次更新: 2025/04/16, 02:04:00

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>