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

微信扫一扫,分享到朋友圈

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

上一篇中,介绍了 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

Python 为什么会有个奇怪的“...”对象?

上一篇

TiDB源码阅读(一) TiDB的入口

下一篇

你也可能喜欢

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

长按储存图像,分享给朋友