TiDB源码阅读(二) 简单理解一下 Lex & Yacc

栏目: IT技术 · 发布时间: 4年前

内容简介:上一篇中,介绍了 TiDB 的入口,从根据配置启动 TiDB 到匹配 MySQL 协议,再到开始做 parser。那接下来我们就简单了解下 SQL 解析处理这一块的内容。当我还是萌新的时候,参与过 Java SQL 解析、优化器 demo 的编写,不过也只是聊到用的技术是最新学习 TiDB 解析优化 SQL 的流程,深觉还是要先至少简单的了解

上一篇中,介绍了 TiDB 的入口,从根据配置启动 TiDB 到匹配 MySQL 协议,再到开始做 parser。那接下来我们就简单了解下 SQL 解析处理这一块的内容。

当我还是萌新的时候,参与过 Java SQL 解析、优化器 demo 的编写,不过也只是聊到用的技术是 ANTRL ,甚至不知道为什么要做解析、优化,也不大了解是什么原理实现。

最新学习 TiDB 解析优化 SQL 的流程,深觉还是要先至少简单的了解 Lex & Yacc

它们能够让你更容易的解析复杂的语言,达成解析字符串的目的。

Lex & Yacc

输入字符流 ,发现某一段字符能够匹配一个关键字,就根据定义好的动作来执行。

例 1 打印

%%
begin    printf("BEGIN;\n");
executeSql    printf("SELECT * FROM t1;\n");   
commit   printf("COMMIT;\n");
%%

Lex 的每一段是通过 %% 分割的,这里设置了 3 个关键字 :

begin
executeSql
commit

读取字符流时,遇到关键字 ,就会根据后面的指令去执行动作。比如遇到 executeSql ,会print " SELECT * FROM t1 ; " 如果匹配不到关键字 ,会正常输出。

例 2 解析日志

[2020/07/31 09:43:01] 
[INFO] 
[server.go:391] 
["connection closed"] 
[conn=4]

根据日志中的元素,定义如下关键字

WORD > connection|conn|INFO|closed
DATE > 2020/07/31 09:43:01
FILENAME > server.go
NUM > 391|4
LEFTBRACKET > [
RIGHTBRACKET > ]
COLON > :
SLASH > /
EQUALSIGN > =
QUOTATIONMARK > "

Lex 分词器

%%
[a−zA−Z][a−zA−Z0−9]*        return WORD 
日期的正则表达式.手动狗头       return DATE
\[a−zA−Z0−9\/.−]+           return FILENAME
\[0123456789]+              return NUM 
\[                          return LEFTBRACKET
\]                          return RIGHTBRACKET
\:                          return COLON 
\/                          return SLASH 
\=                          return EQUALSIGN 
\"                          return QUOTATIONMARK 
%%

经过 Lex 分词的结果就是

LEFTBRACKET DATE RIGHTBRACKET
LEFTBRACKET WORD RIGHTBRACKET
LEFTBRACKET FILENAME COLON NUM RIGHTBRACKET
LEFTBRACKET QUOTATIONMARK WORD WORD QUOTATIONMARK RIGHTBRACKET 
LEFTBRACKET WORD EQUALSIGN NUM RIGHTBRACKET

在 TiDB 中,类似的结构都存放在 parser.y 中,

结构如下,

第一部分主要是定义 Token 的类型、优先级、结合性等。

%{
package parser
import ( 
    "strings"
    "github.com/pingcap/parser/mysql"
    "github.com/pingcap/parser/types"
)
%}
%union {
    offset int // offset
    item interface{}
    ident string
    expr ast.ExprNode
    statement ast.StmtNode
}
%token <ident>
%type  <expr>
%precedence empty
%left join straightJoin inner cross left right full natural
%start    Start

通过 %% 分割,以上是第一部分,即定义段

%%

下部分是 SQL 语法的产生式和每个规则对应的 action ,我们找一个简单的看看,

这应该是 Drop Table 的 分词结构,生成 ast.DropTableStmt 语法树来执行

DropTableStmt:
"DROP" OptTemporary TableOrTables IfExists TableNameList RestrictOrCascadeOpt
{
    $$ = *.DropTableStmt{IfExists: $4.(bool), Tables: $5.([]*ast.TableName), IsView: false, IsTemporary: $2.(bool)}
}

这里有 5 个 Token ,分别是

OptTemporary
TableOrTables
IfExists
TableNameList
RestrictOrCascadeOpt

分别看一下这些 Token 的定义,那两个 Table 巴拉巴拉就不看了

OptTemporary

//应该是临时表的 Token ,如果有这个 Token ,则会被解析。
//但也如逻辑中写的,“TiDB 目前不支持临时表,虽然会被解析,但是不生效。”
OptTemporary:
    /* empty */
    {
        $$ = false
    }
|    "TEMPORARY"
    {
        $$ = true
        yylex.AppendError(yylex.Errorf("TiDB doesn't support TEMPORARY TABLE, TEMPORARY will be parsed but ignored."))
        parser.lastErrorAsWarn()
    }

if exists

// 是否有 if exists
IfExists:
    {
        $$ = false
    }
|    "IF" "EXISTS"
    {
        $$ = true
    }

restrict: 确保只有不存在相关视图和完整性约束的表才能删除

RestrictOrCascadeOpt:
    {}
|    "RESTRICT"
|    "CASCADE"

所以可以看出,在 drop table 的时候,在这个语法结构中," 丰满 " 的语句大概是

drop temporary table Ifexists tablename restrict/cascade

之后就会生成一棵 ast 抽象语法 ast.DropTableStmt 。

ast/ddl.go

type DropTableStmt struct {
    ddlNode

    IfExists    bool
    Tables      []*TableName
    IsView      bool
    IsTemporary bool // make sense ONLY if/when IsView == false
}

这个具体的实现,比如这个

func (n *DropTableStmt) Restore(ctx *format.RestoreCtx) error {
    if n.IsView {
        ctx.WriteKeyWord("DROP VIEW ")
    } else {
        if n.IsTemporary {
            ctx.WriteKeyWord("DROP TEMPORARY TABLE ")
        } else {
            ctx.WriteKeyWord("DROP TABLE ")
        }
    }
    if n.IfExists {
        ctx.WriteKeyWord("IF EXISTS ")
    }
    for index, table := range n.Tables {
        if index != 0 {
            ctx.WritePlain(", ")
        }
        if err := table.Restore(ctx); err != nil {
            return errors.Annotate(err, "An error occurred while restore DropTableStmt.Tables "+string(index))
        }
    }

    return nil
}

先判断了 drop 的是 view 还是 table , 如果走进了 table 分支,也就大概判断了是否是临时表,是否有各种特殊的语法。

到这里基本了解了 TiDB 中对于 SQL 解析的方式,当然,和行文的区别, TiDB 用的是 goyacc,不过好像区别也不是很大,这篇希望可以给大家一个参考。

有疑问加站长微信联系

TiDB源码阅读(二) 简单理解一下 Lex & Yacc

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

公众号运营实战手册

公众号运营实战手册

中信出版社 / 2018-11 / 58

作者粥左罗在刚入行做新媒体的一年时间里,就写了100篇阅读量10万+的公众号文章,但是在此之前,他足足花了两个月的时间研究公众号运营和爆款文章的逻辑和打法。 这本书就是他总结和归纳自己公众号写作和运营的全部秘诀和技巧,是一本行之有效的实战指南。 从如何注册一个公号,给公号起什么名字? 多长时间更新一次为好? 到如何找选题,如何积累爆款素材? 如何编辑内容,如何做版面设......一起来看看 《公众号运营实战手册》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具