上一篇中,介绍了 TiDB 的入口,从根据配置启动 TiDB 到匹配 MySQL 协议,再到开始作 parser。那接下来咱们就简单了解下 SQL 解析处理这一块的内容。mysql
当我仍是萌新的时候,参与过 Java SQL 解析、优化器 demo 的编写,不过也只是聊到用的技术是 ANTRL ,甚至不知道为何要作解析、优化,也不大了解是什么原理实现。git
最新学习 TiDB 解析优化 SQL 的流程,深觉仍是要先至少简单的了解 Lex & Yacc 。github
它们可以让你更容易的解析复杂的语言,达成解析字符串的目的。
输入字符流 ,发现某一段字符可以匹配一个关键字,就根据定义好的动做来执行。正则表达式
%% begin printf("BEGIN;\n"); executeSql printf("SELECT * FROM t1;\n"); commit printf("COMMIT;\n"); %%
Lex 的每一段是经过 %% 分割的,这里设置了 3 个关键字 :sql
begin executeSql commit
读取字符流时,遇到关键字 ,就会根据后面的指令去执行动做。好比遇到 executeSql ,会print " SELECT * FROM t1 ; " 若是匹配不到关键字 ,会正常输出。ide
[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 分词的结果就是rest
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,不过好像区别也不是很大,这篇但愿能够给你们一个参考。