TiDB源碼閱讀(二) 簡單理解一下 Lex & Yacc

上一篇中,介紹了 TiDB 的入口,從根據配置啓動 TiDB 到匹配 MySQL 協議,再到開始作 parser。那接下來咱們就簡單瞭解下 SQL 解析處理這一塊的內容。mysql

當我仍是萌新的時候,參與過 Java SQL 解析、優化器 demo 的編寫,不過也只是聊到用的技術是 ANTRL ,甚至不知道爲何要作解析、優化,也不大瞭解是什麼原理實現。git

最新學習 TiDB 解析優化 SQL 的流程,深覺仍是要先至少簡單的瞭解 Lex & Yaccgithub

它們可以讓你更容易的解析複雜的語言,達成解析字符串的目的。

Lex & Yacc

輸入字符流 ,發現某一段字符可以匹配一個關鍵字,就根據定義好的動做來執行。正則表達式

例 1 打印

%%
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

例 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 分詞的結果就是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,不過好像區別也不是很大,這篇但願能夠給你們一個參考。

相關文章
相關標籤/搜索