上一篇中,介绍了 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 个关键字 :

beginexecuteSqlcommit

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

例 2 解析日志

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

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

WORD > connection|conn|INFO|closedDATE > 2020/07/31 09:43:01FILENAME > server.goNUM > 391|4LEFTBRACKET > [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 RIGHTBRACKETLEFTBRACKET WORD RIGHTBRACKETLEFTBRACKET FILENAME COLON NUM RIGHTBRACKETLEFTBRACKET QUOTATIONMARK WORD WORD QUOTATIONMARK RIGHTBRACKET LEFTBRACKET WORD EQUALSIGN NUM RIGHTBRACKET

在 TiDB 中,相似的构造都寄存在 parser.y 中,

构造如下,

第一局部次要是定义 Token 的类型、优先级、联合性等。

%{package parserimport (     "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 ,别离是

OptTemporaryTableOrTablesIfExistsTableNameListRestrictOrCascadeOpt

别离看一下这些 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 existsIfExists:    {        $$ = false    }|    "IF" "EXISTS"    {        $$ = true    }
restrict: 确保只有不存在相干视图和完整性束缚的表能力删除
cascade: 任何相干视图和完整性束缚都将一并被删除
RestrictOrCascadeOpt:    {}|    "RESTRICT"|    "CASCADE"

所以能够看出,在 drop table 的时候,在这个语法结构中," 饱满 " 的语句大略是

drop temporary table Ifexists tablename restrict/cascade

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

ast/ddl.gotype 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,不过如同区别也不是很大,这篇心愿能够给大家一个参考。