从 TiDB 简单理解 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 ,会打印 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 > 3914
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
{
    $$ = &ast.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: 确保只有不存在相关视图和完整性约束的表才能删除
cascade: 任何相关视图和完整性约束都将一并被删除

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,不过好像区别也不是很大,这篇希望可以给大家一个参考。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值