上一篇中,介紹了 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,不過好像區別也不是很大,這篇但願能夠給你們一個參考。