SQL 解析与执行流程

一、前言

在先前的技术博客中,我们已经详细介绍过数据库的 parser 模块与执行流程:用户输入的 SQL 语句通过词法解析器生成 token,再通过语法分析器生成抽象语法树(AST),经过 AST 生成对应的 planNode,最后执行 planNode。本期博客我们将以新增语法为例,重点介绍一条 SQL 语句需要历经的流程,以及如何自定义 SQL 语句和功能。

二、新增 SQL 语法

KaiwuDB 是通过 goyacc 解析 sql.y 中的代码生成 AST,而我们想要新增一个语法,则需要在 sql.y 中添加对应的关键字以及对应的语法解析规则,最后在代码中添加对应的语法节点以及功能实现。我们以创建时序数据库的语法 CREATE TS DATABASE xxx 为例,讲解如何添加新 SQL 语句,以及该语句的执行过程。

  1. 定义新的关键字

词法分析器是通过一个个关键字解析整条语句,所以第一步我们需要将整条语句涉及到的所有关键字定义完毕。

搜索 pkg/sql/parser/sql.y 文件,在文件中找到 %token(这个代表着定义的关键字),我们可以在这里看到已经有 CREATE 和 DATABASE 关键字,所以需要新增一个 TS 关键字。

erlang 复制代码
Go
......
%token <str> TS
......

加好关键字后,我们还需定义其是保留关键字或是非保留关键字,非保留关键字可以作为标识符使用并在使用时需要加上双引号。所以,我们需要将 TS 加到非保留关键字里中。

erlang 复制代码
Go
unreserved_keyword:
......
| TS
......

添加完毕后,词法分析器即可解析整条语句的所有关键字,接下来我们需要定义一个新的语法规则,使得语法解析器可以处理这条新的语句。

  1. 添加新的语法规则

新的语法首先要给它定义一个类型。在 sql.y 中,一条 SQL 语句的类型都是 %type <tree.Statement> 。

erlang 复制代码
Go
......
%type <tree.Statement> create_ts_database_stmt
......

然后将该语法放到语法 case 列表中,这条语法属于 create_ddl_stmt 的一部分,我们将其放在 create_ddl_stmt 下方即可。

less 复制代码
Go
create_ddl_stmt:
......
| create_ts_database_stmt  // EXTEND WITH HELP: CREATE TS_DATABASE
......

接下来需要为该语法添加对应的语法规则和帮助信息:

sql 复制代码
Go
// %Help: CREATE TS_DATABASE - create a new ts database
// %Category: DDL
// %Text: CREATE TS_DATABASE <name>
create_ts_database_stmt:
  CREATE TS DATABASE database_name{
    ......
  }
| CREATE TS DATABASE error // SHOW HELP: CREATE DATABASE

到这里,整个 parser 部分就可以识别这条新的语法并提示相应信息,但现在还没有添加具体的语法操作,所以整个语法还不能完全执行。

  1. 添加执行语法操作

在解析器可以成功解析语法后,我们需要添加对应的语义,来让整条 SQL 语句执行。而这一步就是生成一个抽象语法树(AST),将语句信息从 parser 阶段传到执行阶段。

在上文中我们将 create_ts_database_stmt 添加为 tree.Statement 类型,所以我们还需要实现 tree.Statement 类型的接口,其后还需要实现以下几个方法:

  • fmt.Stringer
  • NodeFormatter
  • StatementType()
  • StatementTag()
  • StatOp()
  • StatTargetType()

为此,所以首先要定义一个结构体,可以作为整条语句解析的返回值,用以实现上述的几种方法。对于我们想要添加的语句,可以复用原来的 CreateDatabase 结构体并在其中添加一个字段 EngineType 用来代表是否为时序数据库。该结构体已经实现了以上几种方法,但我们添加了新的语法,所以要在 Format 方法中将新的语法 Format 方式添加进去。

scss 复制代码
Go
// Format implements the NodeFormatter interface.
func (node *CreateDatabase) Format(ctx *FmtCtx) {
  ctx.WriteString("CREATE ")
  if node.EngineType == EngineTypeTimeseries {
   ctx.WriteString("TS ")
  }
  ......
}

接下来我们将 parser 部分补全,让它返回一个对应的 CreateDatabase 节点。

sql 复制代码
Go
// %Help: CREATE TS_DATABASE - create a new ts database
// %Category: DDL
// %Text: CREATE TS_DATABASE <name>
create_ts_database_stmt:
  CREATE TS DATABASE database_name{
    $$.val = &tree.CreateDatabase{
      Name: tree.Name($4),
      EngineType: 1,
    }
  }
| CREATE TS DATABASE error // SHOW HELP: CREATE DATABASE

至此,整条语句已经可以成功识别并执行。但由于我们是复用已有的 CreateDatabase 结构,所以执行流程还需要对应的修改。如果是新增一个新的结构体,我们需要在 plan.go 中新加一个 planNode,用于生成执行计划,

scss 复制代码
Go
var _ planNode = &createDatabaseNode{}


planNode 也有以下几个接口需要实现:

startExec(params runParams)

Next(params runParams)

Values()

Close(ctx context.Context)

在 buildOpaque 新增一个 case,用于执行时识别 AST 结构,生成对应的 planNode。

go 复制代码
Go
......
switch n := stmt.(type) {
  case *tree.CreateDatabase:
    plan, err = p.CreateDatabase(ctx, n)
  ......

目前我们已有对应的 createDatabaseNode ,所以无需再新增。而在 CreateDatabase 中需要我们将 AST 转成 planNode,并需要做出语义上的检查与限制。

最后一步就是要定义如何执行整条语句,在 startExec 方法中,通过构建好的 planNode 去实现我们所需的语法功能。

go 复制代码
Go
func (n *createDatabaseNode) startExec(params runParams) error {
    ......
}

至此,新增的该语法功能已实现。

相关推荐
市场部需要一个软件开发岗位11 分钟前
JAVA开发常见安全问题:纵向越权
java·数据库·安全
海奥华214 分钟前
mysql索引
数据库·mysql
2601_949593651 小时前
深入解析CANN-acl应用层接口:构建高效的AI应用开发框架
数据库·人工智能
javachen__1 小时前
mysql新老项目版本选择
数据库·mysql
Dxy12393102161 小时前
MySQL如何高效查询表数据量:从基础到进阶的优化指南
数据库·mysql
Dying.Light1 小时前
MySQL相关问题
数据库·mysql
蜡笔小炘2 小时前
LVS -- 利用防火墙标签(FireWall Mark)解决轮询错误
服务器·数据库·lvs
韩立学长2 小时前
基于Springboot泉州旅游攻略平台d5h5zz02(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·旅游
Re.不晚3 小时前
MySQL进阶之战——索引、事务与锁、高可用架构的三重奏
数据库·mysql·架构
老邓计算机毕设3 小时前
SSM智慧社区信息化服务平台4v5hv(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·智慧社区、·信息化平台