代码实现sql编译器_添加一个新的SQL语句

前  言

45a4ef096916dff9727fb4813fc1c7e1.png

CockroachDB是著名的开源NewSQL数据库,对外提供了标准的SQL接口。上一篇文章《CockroachDB的Parser模块实现》介绍了CockroachDB中Parser模块,主要通过词法解析器将SQL语句解析成Token,然后通过语法解析器生成抽象语法树。本文将介绍如何在CockroachDB中添加一个新的SQL语法类型,来实现用户自定义的功能,并添加相应的测试,从而加深对相应模块的代码及原理的理解。

添加一个新的SQL语句

45a4ef096916dff9727fb4813fc1c7e1.png

添加一个新的SQL语句,首先需要在SQL parser中添加必要的语法规则。CockroachDB的parser是通过 goyacc (go语言构建的一个yacc编译器)解析语法规则文件(pkg/sql/parser/sql.y)生成的。parser生成一颗抽象语法树(AST),其树节点的定义在代码目录 pkg/sql/sem/tree 下。

在parser中添加一个新的SQL语句有三个关键部分:

  1. 添加新的关键字;

  2. 添加语法解析规则;

  3. 添加新的语法节点类型

我们将尝试在CockroachDB v2.1中添加一个新的SQL语句:FROBNICATE ,这个SQL语句支持三种语法,功能如下:

  1. FROBNICATE CLUSTER :在服务端打印 “It’s FrobnicateModeCluster

  2. FROBNICATE SESSION:在服务端打印 “It’s FrobnicateModeSession

  3. FROBNICATE ALL:在服务端打印 “It’s FrobnicateModeALL

添加新的关键字

第一步需要先定义关键字。在pkg/sql/parser/sql.y 文件中搜索”%token”,可以看到声明了许多token,例如我们语法中需要用到的SESSIONCLUSTERALL都已经存在了,因此我们只需要添加关键字 FROBNICATE 即可,如下所示:

    %token <str> FROBNICATE

如果关键字可以出现在标识符选项中,则必须保留该关键字(在需要使用它的地方,例如在列名中,必须使用双引号引起来),因此我们还需要将该关键字添加到”unreserved keywords”列表中,避免与标识符混淆。

    unreserved_keyword:     ....    | FROBNICATE    ...

至此词法分析器已经能识别我们所有的关键字了,接下来需要告诉语法解析器如何处理新的语句。

添加语法解析规则

添加语法解析规则涉及到下列三个部分:

  1. 类型列表

  2. 语法case列表

  3. 子句解析规则

sql.y 文件中搜索”tree.Statement”,可以看到定义好的类型列表,在这里添加一个新的语法类型,如下所示:

    %type  frobnicate_stmt

然后搜索 “stmt: “,为新的语句类型添加一个case:

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

最后,我们需要在stmt中为新的语句添加语法规则及相应的帮助信息,如下所示:

    // %Help: FROBNICATE - show the simple message    // %Category: Misc    // %Text: FROBNICATE { CLUSTER | SESSION | ALL }    frobnicate_stmt:      FROBNICATE CLUSTER { return unimplemented(sqllex, "frobnicate cluster") }    | FROBNICATE SESSION { return unimplemented(sqllex, "frobnicate session") }    | FROBNICATE ALL { return unimplemented(sqllex, "frobnicate all") }

至此,parser已经能够识别新的语法类型并提示相应信息了。此处我们暂时使用unimplemented来代替该语法具体的操作,后续我们会继续完善该部分内容。

我们来尝试使用新的语法,首先需要产生 sql.go 文件:

    ~/go/src/github.com/cockroachdb/cockroach$ make generate    ...    Type checking sql.y    Compiling sql.go    ...

然后重新编译:

    ~/go/src/github.com/cockroachdb/cockroach$ make build    ...    github.com/cockroachdb/cockroach

使用新编译的二进制文件,起一个CockroachDB单节点实例:

    $ rm -fr cockroach-data/ && ./cockroach start --insecure    ...    status:     initialized new cluster    ...

另起一个终端,运行刚才添加的新语句:2fbc08243ccafd96d62751797090237a.png

这里虽然出现了umimplemented 报错,提示该语句还未完成,不过这时已经可以解析该语法了。如果执行一个不存在的语句,提示的错误则是 syntax error

e15b7f798132f8a7a1ba15734ba48c9e.png

添加新的语法节点类型

现在我们已经可以解析相关语法,接下来还需要为该语句添加适当的语义。我们需要一个AST(抽象语法树)节点来将语句的结构信息从parser传递到runtime。

上文中我们在sql.y中添加过%type ,因此我们还需要实现tree.Statement接口,该接口的定义在pkg/sql/sem/tree/stmt.go中,它有4个方法需要实现:

  1. fmt.Stringer

  2. NodeFormatter

  3. StatementType()

  4. StatementTag()

首先创建一个新文件pkg/sql/sem/tree/frobnicate.go,在该文件中实现相关AST节点:

    package parser    import "bytes"    type Frobnicate struct {        Mode FrobnicateMode    }    var _ Statement = &Frobnicate{}    type FrobnicateMode int    const (        FrobnicateModeAll FrobnicateMode = iota        FrobnicateModeCluster        FrobnicateModeSession    )    func (node *Frobnicate) StatementType() StatementType { return Ack }    func (node *Frobnicate) StatementTag() string { return "FROBNICATE" }    func (node *Frobnicate) Format(buf *bytes.Buffer, f FmtFlags) {        buf.WriteString("FROBNICATE ")        switch node.Mode {        case FrobnicateModeAll:            buf.WriteString("ALL")        case FrobnicateModeCluster:            buf.WriteString("CLUSTER")        case FrobnicateModeSession:            buf.WriteString("SESSION")        default:            panic(fmt.Errorf("Unknown FROBNICATE mode %v!", node.Mode))        }    }    func (node *Frobnicate) String() string {        return AsString(node)    }

接下来我们需要更新parser,让它在遇到该语句时返回相应的Frobnicate节点。

上文在 sql.y 文件中添加frobnicate_stmt规则时,使用了unimplemented来暂时代替具体实现,现在我们来实现这部分内容:

    // %Help: FROBNICATE - twiddle the various settings    // %Category: Misc    // %Text: FROBNICATE { CLUSTER | SESSION | ALL }    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} }

重新编译,然后通过客户端运行该语句:566cac7de29280cb1b8ccb35b29e4aab.png

这里返回了一个错误,不过这是一个来自SQL planner的错误,当SQL planner识别到新的语法节点但是不知道该如何处理时就会报这个错误。

我们需要告诉planner如何处理这个语法,相应的代码在 pkg/sql/plan.go 中,我们在newPlan函数中为其添加一个case:

    switch n := stmt.(type) {    case *tree.Frobnicate:        return p.Frobnicate(ctx, n)

然后我们在 pkg/sql/frobnicate.go 中实现对应的方法:

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

再次编译并运行:c63ea486cb6a431b3313d329935d1bd6.png

这里已经根据相应函数内的代码返回了一个错误,接下来我们修改该函数,实现打印出对应信息的功能:

    func (p *planner) Frobnicate(ctx context.Context, stmt *tree.Frobnicate) (planNode, error) {        switch stmt.Mode {        case tree.FrobnicateModeCluster:            fmt.Println("It's FrobnicateModeCluster")        case tree.FrobnicateModeSession:            fmt.Println("It's FrobnicateModeSession")        case tree.FrobnicateModeAll:            fmt.Println("It's FrobnicateModeAll")        default:            return nil, fmt.Errorf("Unhandled FROBNICATE mode %v!", stmt.Mode)        }        return &zeroNode{}, nil    }

注意:这里直接返回zeroNode,它是一个不包含任何行和列的planNode,所以客户端将看不到任何数据返回。如果你需要返回一些内容给客户端,可以查看 pkg/sql 包下是否有合适的planNode,或者使用自定义的planNode。

重新编译并运行:

    ./cockroach sql --insecure -e "frobnicate cluster"    ./cockroach sql --insecure -e "frobnicate session"    ./cockroach sql --insecure -e "frobnicate all"

这时在服务端屏幕可以观察到已经打印出对应的内容:035aeec2c7fb9692acd17ea6bde23a9c.png

至此我们已经实现了一个简单的 frobnicate 语法,别忘了最后还有一个重要步骤,添加相应的测试用例。

添加测试用例

此处需要为该语法解析添加测试用例,相关测试代码位于 pkg/sql/parser/parse_test.go ,我们只需要在对应的地方加入需要测试的语法即可:

    {`FROBNICATE CLUSTER`},    {`FROBNICATE SESSION`},    {`FROBNICATE ALL`},

然后运行测试:

$ make test

如果在上述过程中可以顺利添加语句并成功编译构建,则此处应当可以成功跑通相应的测试用例。

关于SQL语句的功能测试代码在 pkg/sql/logictest/ 下相应文件中,只需要在对应的地方添加新的SQL语句和期望的返回结果即可。由于此处我们仅实现了服务端打印的测试功能,便不具体在此处进行测试,感兴趣的同学可以自行查看对应的测试文件。

给SQL语句添加别名

如果我们需要经常运行Frobnicate语句,希望它可以更简洁的话,我们也可以给它添加一个别名FROB,实现过程很简单,只需要在sql.y中添加几行规则即可:

    unreserved_keyword:    ...    + | FROB    | 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} }    + | FROB CLUSTER { $$.val = &tree.Frobnicate{Mode: tree.FrobnicateModeCluster} }    + | FROB SESSION { $$.val = &tree.Frobnicate{Mode: tree.FrobnicateModeSession} }    + | FROB ALL { $$.val = &tree.Frobnicate{Mode: tree.FrobnicateModeAll} }

总  结

45a4ef096916dff9727fb4813fc1c7e1.png

本文通过一个添加SQL语句的案例,介绍了SQL语句在CockroachDB代码中的实际解析和处理流程。本文所使用的案例参考了https://github.com/cockroachdb/cockroach/blob/master/docs/codelabs/01-sql-statement.md 中的案例,为了便于读者理解,在实现具体功能时直接在服务端打印内容并返回zeroNode。关于planner和生成执行计划相关的原理及实现,会在后续的SQL引擎系列文章中进行详细介绍。

感谢某不愿透露姓名的社区志愿者对本教程的贡献,同时也欢迎其他志愿者投稿。


关于我们:我们是百度 DBA 团队,团队有多位 CockroachDB PMC Member 及 Contributor, 目前正积极推动 NewSQL 在百度内部以及外部的发展。除了NewSQL, 我们在MySQL, PostgreSQL, GreenPlum 有多年的内核开发经验及实践经验,对数据库和大数据领域有疑问或者需求欢迎联系我们,同时欢迎有志青年加入我们!

关注我们 

8ed6ae1f54fda1ff07d4d2399631d9f1.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
  屏蔽数据库间的差异,以统一的界面和操作方式来对数据进行处理,将程序员或数据管理员从繁琐的操作模式中解脱出来,使其更加专注的进行Sql语句的编写。   将查询分析器和企业管理器功能合二为一,并融入众多实用功能,并完美支持 sqlserver、oracle、mysql、access。是您编写sql语句和数据分析的绝佳帮手。   功能列表:   1、代码高亮:根据操作数据库的不同区分相应关键字,并高亮显示   2、自动完成:输入不同的表名等信息,将自动提示相关的字段信息等。   3、智能提示:输入相应关键字将出现类似VS中一样的说明提示。   4、跨库操作:可同时跨多个数据库间操作,互不影响。随时切换,随时运行。   5、随意运行:运行选择的代码、运行多个Sql操作代码。如果运行多个Select语句则显示多个结果集,用来对比查看。(快捷键F5)   6、数据库树中查找对象。   7、在Sql语句编辑器中快速查找功能。   8、查看数据库属*   9、查看表结构   10、自动生成Sql语句模板   11、删除表、视图等   12、查看数据库属*,并快速定到至物理文件。   13、生成脚本信息功能   14、结果集导出功能。   15、在结构集中查找   16、删除指定的行(快捷键 ‘delete’)   17、修改制定的数据   18、添加数据   19、复制选择内容(快捷键‘ctrl+C’)   20、将外部数据导入到结果集中(支持txt:以 '|'或tab符号为分割符 和execl: 指定Sheet页名称 和 默认Sheet页 )   21、支持将导入的数据更至数据库。   22、编辑操作时自动错提示功能(如:自动递增字段自动屏蔽编辑功能。必填字段没有填写内   容则提示,数据类型不正确自动提示等。。。)   23、自动标识主键(主键字段标识为-*-字段名-*-)   24、冻结指定行、列功能。使查看操作更加方便   25、数据集更改后,在提交前可选查看所有更改的部分数据。并用颜**分。   26、自定义我的收藏功能。   27、详细数据单窗体查看功能(支持图片字段)   28、*在没有安装SqlServer 客户端及任何组件的情况下仍然可以连接至SqlServer*   29、*判断Oracle的常见错误,并尝试更改或给出提示*   30、增加历史记录功能。   31、增加自动保存用户状态功能。再次打开软件将会保留上次已连接的数据库信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值