当前位置: 首页 > news >正文

SQL 解析 — 如何轻松实现新增语句

KaiwuDB 支持多种不同类型的 SQL 语句,例如 create、insert 等。本文将介绍在 KaiwuDB SQL Parser(下文统称解析器)中添加新语句的过程及其实现。我们将了解如何使用 goyacc 工具更新解析器,以及执行器和查询计划器(planner)如何协同执行这条语句。

一、语法和关键字

添加新的 SQL 语句需要从向 SQL 解析器添加必要的语法开始。解析器是通过 goyacc 生成的,它是流行的 yacc 编译器的 Go 版本。语法定义位于 pkg/sql/parser/sql.y 文件中。解析器的输出是一个抽象语法树(AST),其中的节点类型(node)在 pkg/sql/sem/tree 目录下的各个文件中定义。

向 SQL 解析器添加新的语句主要由三个组成部分:添加新的关键字、向语句解析器添加语法,以及添加新的语法节点类型。

二、FROBNICATE STATEMENT

本文以在 KaiwuDB 新增一个新的语句为例:FROBNICATE。这个语句将会随机修改数据库的设置。它将有三个选项:FROBNICATE CLUSTER,用于操作集群设置;FROBNICATE SESSION,用于操作会话设置;FROBNICATE ALL,用于同时处理两者。

让我们从检查所有关键字是否已定义开始。打开 pkg/sql/parser/sql.y 文件并搜索"Ordinary key words"。你会看到一系列按字母顺序排列的标记定义。由于其他语法已经定义了 SESSION、CLUSTER 和 ALL 关键字,我们不需要添加它们,但我们需要为 FROBNICATE 创建一个关键字。它应该如下所示:

%token <str> FROBNICATE

这告诉词法分析器识别关键字,但我们仍然需要将它添加到其中一个类别列表中。如果关键字可以出现在标识符位置,它必须是保留的(reserved_keyword, 这要求对其的其他用途,例如作为列名,必须使用引号引用)。由于我们的新关键字作为一条 SQL 语句的开始,它不能被误认为是标识符,所以我们可以安全地将它添加到非保留关键字列表中。在 pkg/sql/parser/sql.y 文件中搜索 unreserved_keyword:,并按照以下方式添加 | FROBNICATE:

unreserved_keyword:
...
| FROBNICATE
...

既然词法分析器已经知道了我们所有的关键字,我们需要教会解析器如何处理我们的新语句。有三个地方我们需要添加引用:语句类型列表语句情况列表解析子句

在语法文件(pkg/sql/parser/sql.y)中搜索<tree.Statement>,你将找到类型列表。添加一行关于我们新语句类型的内容,类似于:

%type <tree.Statement> frobnicate_stmt

这样我们就为新语句类型"frobnicateStmt"添加了一个类型声明。请注意,"frobnicateStmt"只是一个示例名称,你可以根据实际情况自定义。

接下来,我们需要将新语句类型添加到语句情况列表中。继续搜索语法文件,找到以"stmt"开头的规则(如 stmt_select、stmt_insert 等)。在这些规则中添加以下情况:

stmt:
...
| frobnicate_stmt // EXTEND WITH HELP: FROBNICATE
...

最后,我们需要为我们的语句添加一个产生式规则。在 pkg/sql/parser/sql.y 文件中添加以下规则:

frobnicate_stmt:FROBNICATE CLUSTER { return unimplemented(sqllex, "frobnicate cluster") }
| FROBNICATE SESSION { return unimplemented(sqllex, "frobnicate session") }
| FROBNICATE ALL { return unimplemented(sqllex, "frobnicate all") }

这里列出了我们允许的三种表达形式,用竖线字符分隔。每个产生式还有一个用大括号括起来的实现(暂时报错并显示“未实现”的错误信息)。

最后为我们的语句添加 help 文档。在我们刚刚添加的产生式规则上方,添加以下注释:

// %Help: FROBNICATE - twiddle the various settings
// %Category: Misc
// %Text: FROBNICATE { CLUSTER | SESSION | ALL }

现在我们的解析器将能够识别新的语句类型,并且生成一些新语法相关的注释以为用户提供帮助。重新编译代码后,尝试执行这条语句,得到以下结果:

$ kwbase sql --insecure -e "frobnicate cluster"
ERROR: at or near "cluster": syntax error: unimplemented: this syntax
SQLSTATE: 0A000
DETAIL: source SQL:
frobnicate cluster^HINT: You have attempted to use a feature that is not yet implemented.Please check the public issue tracker to check whether this problem is
already tracked. If you cannot find it there, please report the error
with details by creating a new issue.If you would rather not post publicly, please contact us directly
using the support form.We appreciate your feedback.
Failed running "sql"

这代表我们新加的语法成功解析了,但是由于尚未实现,无法执行任何操作。

三、添加抽象语法树

语法层添加完成后,现在我们需要为新的语句赋予适当的语义。我们需要一个 AST 来从解析器向运行时传递语句的结构。上文说过我们的语句是 %type <tree.Statement>,这意味着它需要实现 tree.Statement接口,可以在 pkg/sql/sem/tree/stmt.go 中找到。

我们需要编写四个函数:三个用于 Statement 接口本身(StatementReturnType、StatementType 和 StatementTag),一个用于 NodeFormatter(Format),还有标准的 fmt.Stringer。

请为我们的语句类型创建一个新文件:pkg/sql/sem/tree/frobnicate.go。在其中,放入我们 AST 节点的格式和定义。

package treetype Frobnicate struct {Mode FrobnicateMode
}var _ Statement = &Frobnicate{}type FrobnicateMode intconst (FrobnicateModeAll FrobnicateMode = iotaFrobnicateModeClusterFrobnicateModeSession
)func (node *Frobnicate) Format(ctx *FmtCtx) {ctx.WriteString("FROBNICATE ")switch node.Mode {case FrobnicateModeAll:ctx.WriteString("ALL")case FrobnicateModeCluster:ctx.WriteString("CLUSTER")case FrobnicateModeSession:ctx.WriteString("SESSION")}
} 

要添加我们 AST 树的语句和字符串表示,打开 pkg/sql/sem/tree/stmt.go 文件并搜索 // StatementReturnType implements the Statement interface。现在你可以看到不同类型 AST 的实现列表。按照字母顺序将以下内容插入其中:

func (node *Frobnicate) StatementReturnType() StatementReturnType { return Ack }// StatementType implements the Statement interface.
func (node *Frobnicate) StatementType() StatementType { return TypeDCL }// StatementTag returns a short string identifying the type of statement.
func (node *Frobnicate) StatementTag() string               { return "FROBNICATE" }

接下来,按字母顺序添加以下内容:

func (n *Frobnicate) String() string            { return AsString(n) }

现在我们需要更新解析器,在遇到我们的语法时返回一个带有适当模式类型 FROBNICATE 节点(AST)。返回到 pkg/sql/parser/sql.y 文件,搜索 // %Help: FROBNICATE,并将语句替换为以下内容:

frobnicate_stmt:FROBNICATE CLUSTER { $$.val = &tree.Frobnicate{Mode: tree.FrobnicateModeCluster} }
| FROBNICATE SESSION { $$.val = &tree.Frobnicate{Mode: tree.FrobnicateModeSession} }
| FROBNICATE ALL { $$.val = &tree.Frobnicate{Mode: tree.FrobnicateModeAll} }

特殊符号 $$.val 表示此规则生成的节点值。还有一些其他的 $ 符号,可以在 yacc 中使用。其中一个更有用的形式是引用子产生式的节点值(例如,在这三个语句中,$1 将是标记 FROBNICATE)。

接下来重新编译 KaiwuDB,重新输入新语法,得到以下结果:

$ kwbase sql --insecure -e "frobnicate cluster"
Error: pq: unknown statement type: *tree.Frobnicate
Failed running "sql"

现在我们看到了一个不同于之前的错误。这个错误来自于 SQL 计划器(planner),在遇到新的语句类型时不知道该怎么处理。我们需要教给它新的语句的含义。尽管我们的语句不会在任何查询计划中起作用,但我们将通过向规划器添加一个方法来实现它。这是集中化语句派发的地方,因此在那里添加语义。

找到我们当前看到的错误的源代码,会发现它在 /pkg/sql/opaque.go 文件中一长串类型选择语句的末尾。让我们在其中添加一个 case:

case *tree.Frobnicate:return p.Frobnicate(ctx, n)

同样,在同一文件 /pkg/sql/opaque.go 的 init() 函数下面添加以下内容:

&tree.Frobnicate{},

这将调用计划器自身上的一个方法(尚未实现)。让我们在 pkg/sql/frobnicate.go 文件中实现该方法。

package sql
import ("context""github.com/kwbasedb/kwbase/pkg/sql/sem/tree""github.com/kwbasedb/errors"
)func (p *planner) Frobnicate(ctx context.Context, stmt *tree.Frobnicate) (planNode, error) {return nil, errors.AssertionFailedf("We're not quite frobnicating yet...")
}

此时重新编译 KaiwuDB,再次执行该语句:

$ kwbase sql --insecure -e "frobnicate cluster"
Error: pq: We're not quite frobnicating yet...
Failed running "sql"

至此为止,我们已经能够让错误传递到 SQL 客户端了。我们只需要上述接口添加功能能代码,让语句生效即可。

相关文章:

  • vite+Vue3学习笔记(3)——界面设计
  • Mybatis Java API - SqlSessionFactoryBuilder
  • 【ROS2】MOMO的鱼香ROS2(三)ROS2入门篇——ROS2第一个节点
  • SSH 端口转发:如何将服务绑定到本地 IP 地址
  • 观察者模式概述
  • 基于rk3568 Android H265推流SRS低延迟网页播放方案
  • Node.js--》node环境配置及nvm和nvm-desktop安装教程
  • 数字IC设计流程
  • MongoDB 数据类型
  • WPF 消息日志打印帮助类:HandyControl+NLog+彩色控制台打印+全局异常捕捉
  • Matlab仿真2ASK/OOK、2FSK、2PSK、QPSK、4QAM在加性高斯白噪声信道中的误码率与归一化信噪比的关系
  • Python入门学习篇(十四)——模块文件操作
  • java之树筛选
  • 判断电话号码是否重复-excel
  • x-cmd pkg | openssl - 密码学开源工具集
  • 【Leetcode】104. 二叉树的最大深度
  • 0x05 Python数据分析,Anaconda八斩刀
  • CSS居中完全指南——构建CSS居中决策树
  • Invalidate和postInvalidate的区别
  • isset在php5.6-和php7.0+的一些差异
  • JavaScript-Array类型
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • Markdown 语法简单说明
  • node和express搭建代理服务器(源码)
  • React-Native - 收藏集 - 掘金
  • vue 配置sass、scss全局变量
  • vue的全局变量和全局拦截请求器
  • zookeeper系列(七)实战分布式命名服务
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 规范化安全开发 KOA 手脚架
  • 记一次删除Git记录中的大文件的过程
  • 检测对象或数组
  • 你真的知道 == 和 equals 的区别吗?
  • 前端存储 - localStorage
  • 算法-插入排序
  • 突破自己的技术思维
  • 微信开源mars源码分析1—上层samples分析
  • 微信小程序:实现悬浮返回和分享按钮
  • 由插件封装引出的一丢丢思考
  • 阿里云重庆大学大数据训练营落地分享
  • ​补​充​经​纬​恒​润​一​面​
  • $L^p$ 调和函数恒为零
  • (2)空速传感器
  • (23)Linux的软硬连接
  • (3)医疗图像处理:MRI磁共振成像-快速采集--(杨正汉)
  • (HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (多级缓存)多级缓存
  • (二)windows配置JDK环境
  • (图)IntelliTrace Tools 跟踪云端程序
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (转)jdk与jre的区别
  • (转载)hibernate缓存
  • (轉貼) UML中文FAQ (OO) (UML)
  • .gitignore不生效的解决方案