{
    "componentChunkName": "component---src-templates-blog-blog-detail-tsx",
    "path": "/blog/tidb-source-code-reading-5",
    "result": {"pageContext":{"blog":{"id":"Blogs_91","title":"TiDB 源码阅读系列文章（五）TiDB SQL Parser 的实现","tags":["TiDB 源码阅读","社区"],"category":{"name":"产品技术解读"},"summary":"本文为 TiDB 源码阅读系列文章的第五篇，主要对 SQL Parser 功能的实现进行了讲解。内容来自社区小伙伴——马震（GitHub ID：mz1999 ）的投稿。","body":"> 本文为 TiDB 源码阅读系列文章的第五篇，主要对 SQL Parser 功能的实现进行了讲解，内容来自社区小伙伴——马震（GitHub ID：mz1999 ）的投稿。\n>\n> TiDB 源码阅读系列文章的撰写初衷，就是希望能与数据库研究者、爱好者进行深入交流，我们欣喜于如此短的时间内就收到了来自社区的反馈。后续，也希望有更多小伙伴加入到与 TiDB 『坦诚相见』的阵列中来。\n\nPingCAP 发布了 TiDB 的[源码阅读系列文章](https://pingcap.com/zh/blog/?tag=TiDB%20%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB)，让我们可以比较系统的去学习了解TiDB的内部实现。最近的一篇[《SQL 的一生》](https://pingcap.com/blog-cn/tidb-source-code-reading-3/)，从整体上讲解了一条 SQL 语句的处理流程，从网络上接收数据，MySQL 协议解析和转换，SQL 语法解析，查询计划的制定和优化，查询计划执行，到最后返回结果。\n\n![SQL 语句处理流程](https://img1.www.pingcap.com/prod/1_c4469a4858.png)\n\n其中，`SQL Parser` 的功能是把 SQL 语句按照 SQL 语法规则进行解析，将文本转换成抽象语法树（`AST`），这部分功能需要些背景知识才能比较容易理解，我尝试做下相关知识的介绍，希望能对读懂这部分代码有点帮助。\n\nTiDB 是使用 [goyacc](https://github.com/cznic/goyacc) 根据预定义的 SQL 语法规则文件 [parser.y](https://github.com/pingcap/tidb/blob/source-code/parser/parser.y) 生成 SQL 语法解析器。我们可以在 TiDB 的 [Makefile](https://github.com/pingcap/tidb/blob/50e98f427e7943396dbe38d23178b9f9dc5398b7/Makefile#L50) 文件中看到这个过程，先 build `goyacc` 工具，然后使用 `goyacc` 根据 `parser.y` 生成解析器 `parser.go`：\n\n```makefile\ngoyacc:\n\t$(GOBUILD) -o bin/goyacc parser/goyacc/main.go\n\nparser: goyacc\n\tbin/goyacc -o /dev/null parser/parser.y\n\tbin/goyacc -o parser/parser.go parser/parser.y 2>&1 ...\n```\n\n[goyacc](https://github.com/cznic/goyacc) 是 [yacc](http://dinosaur.compilertools.net/) 的 Golang 版，所以要想看懂语法规则定义文件 [parser.y](https://github.com/pingcap/tidb/blob/source-code/parser/parser.y)，了解解析器是如何工作的，先要对 [Lex & Yacc](http://dinosaur.compilertools.net/) 有些了解。\n\n## Lex & Yacc 介绍\n\n[Lex & Yacc](http://dinosaur.compilertools.net/) 是用来生成词法分析器和语法分析器的工具，它们的出现简化了编译器的编写。`Lex & Yacc` 分别是由贝尔实验室的 [Mike Lesk](https://en.wikipedia.org/wiki/Mike_Lesk) 和 [Stephen C. Johnson](https://en.wikipedia.org/wiki/Stephen_C._Johnson) 在 1975 年发布。对于 Java 程序员来说，更熟悉的是 [ANTLR](http://www.antlr.org/)，`ANTLR 4` 提供了 `Listener`+`Visitor` 组合接口， 不需要在语法定义中嵌入`actions`，使应用代码和语法定义解耦。`Spark` 的 SQL 解析就是使用了 `ANTLR`。`Lex & Yacc` 相对显得有些古老，实现的不是那么优雅，不过我们也不需要非常深入的学习，只要能看懂语法定义文件，了解生成的解析器是如何工作的就够了。我们可以从一个简单的例子开始：\n\n![图例](https://img1.www.pingcap.com/prod/2_3a000040e8.png)\n\n\n上图描述了使用 `Lex & Yacc` 构建编译器的流程。`Lex` 根据用户定义的 `patterns` 生成词法分析器。词法分析器读取源代码，根据 `patterns` 将源代码转换成 `tokens` 输出。`Yacc` 根据用户定义的语法规则生成语法分析器。语法分析器以词法分析器输出的 `tokens` 作为输入，根据语法规则创建出语法树。最后对语法树遍历生成输出结果，结果可以是产生机器代码，或者是边遍历 `AST` 边解释执行。\n\n从上面的流程可以看出，用户需要分别为 `Lex` 提供 `patterns` 的定义，为 `Yacc` 提供语法规则文件，`Lex & Yacc` 根据用户提供的输入文件，生成符合他们需求的词法分析器和语法分析器。这两种配置都是文本文件，并且结构相同：\n\n```\n... definitions ...\n%%\n... rules ...\n%%\n... subroutines ...\n```\n\n文件内容由 `%%` 分割成三部分，我们重点关注中间规则定义部分。对于上面的例子，`Lex` 的输入文件如下：\n\n```\n...\n%%\n/* 变量 */\n[a-z]    {\n            yylval = *yytext - 'a';\n            return VARIABLE;\n         }   \n/* 整数 */\n[0-9]+   {\n            yylval = atoi(yytext);\n            return INTEGER;\n         }\n/* 操作符 */\n[-+()=/*\\n] { return *yytext; }\n/* 跳过空格 */\n[ \\t]    ;\n/* 其他格式报错 */\n.        yyerror(\"invalid character\");\n%%\n...\n```\n\n上面只列出了规则定义部分，可以看出该规则使用正则表达式定义了变量、整数和操作符等几种 `token`。例如整数 `token` 的定义如下：\n\n```\n[0-9]+  {\n            yylval = atoi(yytext);\n            return INTEGER; \n        }\n```\n\n当输入字符串匹配这个正则表达式，大括号内的动作会被执行：将整数值存储在变量 `yylval` 中，并返回 `token` 类型 `INTEGER` 给 `Yacc`。 \n\n再来看看 `Yacc` 语法规则定义文件：\n\n```\n%token INTEGER VARIABLE\n%left '+' '-'\n%left '*' '/'\n...\n%%\n\nprogram:\n        program statement '\\n' \n        |\n        ;\n\nstatement:\n        expr                    { printf(\"%d\\n\", $1); }\n        | VARIABLE '=' expr     { sym[$1] = $3; }\n        ;\n        \nexpr:\n        INTEGER\n        | VARIABLE              { $$ = sym[$1]; }\n        | expr '+' expr         { $$ = $1 + $3; }\n        | expr '-' expr         { $$ = $1 - $3; }\n        | expr '*' expr         { $$ = $1 * $3; }\n        | expr '/' expr         { $$ = $1 / $3; }\n        | '(' expr ')'          { $$ = $2; }\n        ;\n\n%%\n...\n```\n\n第一部分定义了 `token` 类型和运算符的结合性。四种运算符都是左结合，同一行的运算符优先级相同，不同行的运算符，后定义的行具有更高的优先级。\n\n语法规则使用了 `BNF` 定义。`BNF` 可以用来表达上下文无关（*context-free*）语言，大部分的现代编程语言都可以使用 `BNF` 表示。上面的规则定义了三个**产生式**。**产生式**冒号左边的项（例如 `statement`）被称为**非终结符**， `INTEGER` 和 `VARIABLE` 被称为**终结符**,它们是由 `Lex` 返回的 `token` 。**终结符**只能出现在**产生式**的右侧。可以使用**产生式**定义的语法生成表达式：\n\n```\nexpr -> expr * expr\n     -> expr * INTEGER\n     -> expr + expr * INTEGER\n     -> expr + INTEGER * INTEGER\n     -> INTEGER + INTEGER * INTEGER\n```\n\n解析表达式是生成表达式的逆向操作，我们需要归约表达式到一个**非终结符**。`Yacc` 生成的语法分析器使用**自底向上**的归约（*shift-reduce*）方式进行语法解析，同时使用堆栈保存中间状态。还是看例子，表达式 `x + y * z` 的解析过程：\n\n```\n1    . x + y * z\n2    x . + y * z\n3    expr . + y * z\n4    expr + . y * z\n5    expr + y . * z\n6    expr + expr . * z\n7    expr + expr * . z\n8    expr + expr * z .\n9    expr + expr * expr .\n10   expr + expr .\n11   expr .\n12   statement .\n13   program  .\n```\n\n点（`.`）表示当前的读取位置，随着 `.` 从左向右移动，我们将读取的 `token` 压入堆栈，当发现堆栈中的内容匹配了某个**产生式**的右侧，则将匹配的项从堆栈中弹出，将该**产生式**左侧的**非终结符**压入堆栈。这个过程持续进行，直到读取完所有的 `tokens`，并且只有**启始非终结符**（本例为 `program`）保留在堆栈中。\n\n产生式右侧的大括号中定义了该规则关联的动作，例如：\n\n```\nexpr:  expr '*' expr         { $$ = $1 * $3; }\n```\n\n我们将堆栈中匹配该**产生式**右侧的项替换为**产生式**左侧的**非终结符**，本例中我们弹出 `expr '*' expr`，然后把 `expr` 压回堆栈。 我们可以使用 `$position` 的形式访问堆栈中的项，`$1` 引用的是第一项，`$2` 引用的是第二项，以此类推。`$$`  代表的是归约操作执行后的堆栈顶。本例的动作是将三项从堆栈中弹出，两个表达式相加，结果再压回堆栈顶。\n\n上面例子中语法规则关联的动作，在完成语法解析的同时，也完成了表达式求值。一般我们希望语法解析的结果是一棵抽象语法树（`AST`），可以这么定义语法规则关联的动作：\n\n```\n...\n%%\n...\nexpr:\n    INTEGER             { $$ = con($1); }\n    | VARIABLE          { $$ = id($1); }\n    | expr '+' expr     { $$ = opr('+', 2, $1, $3); }\n    | expr '-' expr     { $$ = opr('-', 2, $1, $3); }\n    | expr '*' expr     { $$ = opr('*', 2, $1, $3); } \n    | expr '/' expr     { $$ = opr('/', 2, $1, $3); }\n    | '(' expr ')'      { $$ = $2; }\n    ; \n%%\nnodeType *con(int value) {\n    ...\n}\nnodeType *id(int i) {\n    ...\n}\nnodeType *opr(int oper, int nops, ...) {\n    ...\n}    \n```\n\n上面是一个语法规则定义的片段，我们可以看到，每个规则关联的动作不再是求值，而是调用相应的函数，该函数会返回抽象语法树的节点类型 `nodeType`，然后将这个节点压回堆栈，解析完成时，我们就得到了一颗由 `nodeType` 构成的抽象语法树。对这个语法树进行遍历访问，可以生成机器代码，也可以解释执行。\n\n至此，我们大致了解了 `Lex & Yacc` 的原理。其实还有非常多的细节，例如如何消除语法的歧义，但我们的目的是读懂 TiDB 的代码，掌握这些概念已经够用了。\n\n## goyacc 简介\n\n[goyacc](https://github.com/cznic/goyacc) 是 golang 版的 `Yacc`。和 `Yacc` 的功能一样，`goyacc` 根据输入的语法规则文件，生成该语法规则的 go 语言版解析器。`goyacc` 生成的解析器 `yyParse` 要求词法分析器符合下面的接口：\n\n```\ntype yyLexer interface {\n\tLex(lval *yySymType) int\n\tError(e string)\n}\n```\n\n或者\n\n```\ntype yyLexerEx interface {\n\tyyLexer\n\t// Hook for recording a reduction.\n\tReduced(rule, state int, lval *yySymType) (stop bool) // Client should copy *lval.\n}\n```\n\nTiDB 没有使用类似 `Lex` 的工具生成词法分析器，而是纯手工打造，词法分析器对应的代码是 [parser/lexer.go](https://github.com/pingcap/tidb/blob/source-code/parser/lexer.go)， 它实现了 `goyacc` 要求的接口：\n\n```\n...\n// Scanner implements the yyLexer interface.\ntype Scanner struct {\n\tr   reader\n\tbuf bytes.Buffer\n\n\terrs         []error\n\tstmtStartPos int\n\n\t// For scanning such kind of comment: /*! MySQL-specific code */ or /*+ optimizer hint */\n\tspecialComment specialCommentScanner\n\n\tsqlMode mysql.SQLMode\n}\n// Lex returns a token and store the token value in v.\n// Scanner satisfies yyLexer interface.\n// 0 and invalid are special token id this function would return:\n// return 0 tells parser that scanner meets EOF,\n// return invalid tells parser that scanner meets illegal character.\nfunc (s *Scanner) Lex(v *yySymType) int {\n\ttok, pos, lit := s.scan()\n\tv.offset = pos.Offset\n\tv.ident = lit\n\t...\n}\n// Errors returns the errors during a scan.\nfunc (s *Scanner) Errors() []error {\n\treturn s.errs\n}\n```\n\n\n另外 `lexer` 使用了 `字典树` 技术进行 `token` 识别，具体的实现代码在 [parser/misc.go](https://github.com/pingcap/tidb/blob/source-code/parser/misc.go)\n\n## TiDB SQL Parser 的实现\n\n终于到了正题。有了上面的背景知识，对 TiDB 的 `SQL Parser` 模块会相对容易理解一些。TiDB 的词法解析使用的 [手写的解析器](https://github.com/pingcap/tidb/blob/source-code/parser/lexer.go)（这是出于性能考虑），语法解析采用 `goyacc`。先看 SQL 语法规则文件 [parser.y](https://github.com/pingcap/tidb/blob/source-code/parser/parser.y)，`goyacc` 就是根据这个文件生成SQL语法解析器的。\n\n`parser.y` 有 6500 多行，第一次打开可能会被吓到，其实这个文件仍然符合我们上面介绍过的结构：\n\n```\n... definitions ...\n%%\n... rules ...\n%%\n... subroutines ...\n```\n\n`parser.y` 第三部分 `subroutines` 是空白没有内容的， 所以我们只需要关注第一部分 `definitions` 和第二部分 `rules`。\n\n第一部分主要是定义 `token` 的类型、优先级、结合性等。注意 `union` 这个联合体结构体：\n\n```\n%union {\n\toffset int // offset\n\titem interface{}\n\tident string\n\texpr ast.ExprNode\n\tstatement ast.StmtNode\n}\n```\n\n该联合体结构体定义了在语法解析过程中被压入堆栈的**项**的属性和类型。\n\n压入堆栈的**项**可能是 `终结符`，也就是 `token`，它的类型可以是`item` 或 `ident`；\n\n这个**项**也可能是 `非终结符`，即产生式的左侧，它的类型可以是 `expr` 、 `statement` 、 `item` 或 `ident`。\n\n`goyacc` 根据这个 `union` 在解析器里生成对应的 `struct` 是：\n\n```\ntype yySymType struct {\n\tyys       int\n\toffset    int // offset\n\titem      interface{}\n\tident     string\n\texpr      ast.ExprNode\n\tstatement ast.StmtNode\n}\n```\n\n在语法解析过程中，`非终结符` 会被构造成抽象语法树（`AST`）的节点 [ast.ExprNode](https://github.com/pingcap/tidb/blob/73900c4890dc9708fe4de39021001ca554bc8374/ast/ast.go#L60) 或 [ast.StmtNode](https://github.com/pingcap/tidb/blob/73900c4890dc9708fe4de39021001ca554bc8374/ast/ast.go#L94)。抽象语法树相关的数据结构都定义在 [ast](https://github.com/pingcap/tidb/tree/source-code/ast) 包中，它们大都实现了 [ast.Node](https://github.com/pingcap/tidb/blob/73900c4890dc9708fe4de39021001ca554bc8374/ast/ast.go#L29) 接口：\n\n```\n// Node is the basic element of the AST.\n// Interfaces embed Node should have 'Node' name suffix.\ntype Node interface {\n\tAccept(v Visitor) (node Node, ok bool)\n\tText() string\n\tSetText(text string)\n}\n```\n\n这个接口有一个 `Accept` 方法，接受 `Visitor` 参数，后续对 `AST` 的处理，主要依赖这个 `Accept` 方法，以 `Visitor` 模式遍历所有的节点以及对 `AST` 做结构转换。\n\n```\n// Visitor visits a Node.\ntype Visitor interface {\n\tEnter(n Node) (node Node, skipChildren bool)\n\tLeave(n Node) (node Node, ok bool)\n}\n```\n\n例如 [plan.preprocess](https://github.com/pingcap/tidb/blob/source-code/plan/preprocess.go) 是对 `AST` 做预处理，包括合法性检查以及名字绑定。\n\n`union` 后面是对 `token` 和 `非终结符` 按照类型分别定义：\n\n```\n/* 这部分的 token 是 ident 类型 */\n%token\t<ident>\n    ...\n\tadd\t\t\t\"ADD\"\n\tall \t\t\t\"ALL\"\n\talter\t\t\t\"ALTER\"\n\tanalyze\t\t\t\"ANALYZE\"\n\tand\t\t\t\"AND\"\n\tas\t\t\t\"AS\"\n\tasc\t\t\t\"ASC\"\n\tbetween\t\t\t\"BETWEEN\"\n\tbigIntType\t\t\"BIGINT\"\n    ...\n\n/* 这部分的 token 是 item 类型 */   \n%token\t<item>\n    /*yy:token \"1.%d\"   */\tfloatLit        \"floating-point literal\"\n\t/*yy:token \"1.%d\"   */\tdecLit          \"decimal literal\"\n\t/*yy:token \"%d\"     */\tintLit          \"integer literal\"\n\t/*yy:token \"%x\"     */\thexLit          \"hexadecimal literal\"\n\t/*yy:token \"%b\"     */\tbitLit          \"bit literal\"\n\n\tandnot\t\t\"&^\"\n\tassignmentEq\t\":=\"\n\teq\t\t\"=\"\n\tge\t\t\">=\"\n    ...\n\n/* 非终结符按照类型分别定义 */\n%type\t<expr>\n    Expression\t\t\t\"expression\"\n\tBoolPri\t\t\t\t\"boolean primary expression\"\n\tExprOrDefault\t\t\t\"expression or default\"\n\tPredicateExpr\t\t\t\"Predicate expression factor\"\n\tSetExpr\t\t\t\t\"Set variable statement value's expression\"\n    ...\n\n%type\t<statement>\n\tAdminStmt\t\t\t\"Check table statement or show ddl statement\"\n\tAlterTableStmt\t\t\t\"Alter table statement\"\n\tAlterUserStmt\t\t\t\"Alter user statement\"\n\tAnalyzeTableStmt\t\t\"Analyze table statement\"\n\tBeginTransactionStmt\t\t\"BEGIN TRANSACTION statement\"\n\tBinlogStmt\t\t\t\"Binlog base64 statement\"\n    ...\n\t\n%type   <item>\n\tAlterTableOptionListOpt\t\t\"alter table option list opt\"\n\tAlterTableSpec\t\t\t\"Alter table specification\"\n\tAlterTableSpecList\t\t\"Alter table specification list\"\n\tAnyOrAll\t\t\t\"Any or All for subquery\"\n\tAssignment\t\t\t\"assignment\"\n    ...\n\n%type\t<ident>\n\tKeyOrIndex\t\t\"{KEY|INDEX}\"\n\tColumnKeywordOpt\t\"Column keyword or empty\"\n\tPrimaryOpt\t\t\"Optional primary keyword\"\n\tNowSym\t\t\t\"CURRENT_TIMESTAMP/LOCALTIME/LOCALTIMESTAMP\"\n\tNowSymFunc\t\t\"CURRENT_TIMESTAMP/LOCALTIME/LOCALTIMESTAMP/NOW\"\n    ...\n```\n\n第一部分的最后是对优先级和结合性的定义：\n\n```\n...\n%precedence sqlCache sqlNoCache\n%precedence lowerThanIntervalKeyword\n%precedence interval\n%precedence lowerThanStringLitToken\n%precedence stringLit\n...\n%right   assignmentEq\n%left \tpipes or pipesAsOr\n%left \txor\n%left \tandand and\n%left \tbetween\n...\n```\n\n`parser.y` 文件的第二部分是 `SQL` 语法的产生式和每个规则对应的 `aciton` 。SQL语法非常复杂，`parser.y` 的大部分内容都是产生式的定义。\n\n`SQL` 语法可以参照 MySQL 参考手册的 [SQL Statements](https://dev.mysql.com/doc/refman/5.7/en/sql-statements.html)  部分，例如 [SELECT](https://dev.mysql.com/doc/refman/5.7/en/select.html) 语法的定义如下：\n\n```\nSELECT\n    [ALL | DISTINCT | DISTINCTROW ]\n      [HIGH_PRIORITY]\n      [STRAIGHT_JOIN]\n      [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]\n      [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]\n    select_expr [, select_expr ...]\n    [FROM table_references\n      [PARTITION partition_list]\n    [WHERE where_condition]\n    [GROUP BY {col_name | expr | position}\n      [ASC | DESC], ... [WITH ROLLUP]]\n    [HAVING where_condition]\n    [ORDER BY {col_name | expr | position}\n      [ASC | DESC], ...]\n    [LIMIT {[offset,] row_count | row_count OFFSET offset}]\n    [PROCEDURE procedure_name(argument_list)]\n    [INTO OUTFILE 'file_name'\n        [CHARACTER SET charset_name]\n        export_options\n      | INTO DUMPFILE 'file_name'\n      | INTO var_name [, var_name]]\n    [FOR UPDATE | LOCK IN SHARE MODE]]\n```\n\n我们可以在 `parser.y` 中找到 `SELECT` 语句的产生式：\n\n```\nSelectStmt:\n\t\"SELECT\" SelectStmtOpts SelectStmtFieldList OrderByOptional SelectStmtLimit SelectLockOpt\n    { ... }\n|   \"SELECT\" SelectStmtOpts SelectStmtFieldList FromDual WhereClauseOptional SelectStmtLimit SelectLockOpt\n    { ... }  \n|   \"SELECT\" SelectStmtOpts SelectStmtFieldList \"FROM\"\n\tTableRefsClause WhereClauseOptional SelectStmtGroup HavingClause OrderByOptional\n\tSelectStmtLimit SelectLockOpt\n    { ... } \n```\n\n产生式 `SelectStmt` 和 `SELECT` 语法是对应的。\n\n我省略了大括号中的 `action` ，这部分代码会构建出 `AST` 的 [ast.SelectStmt](https://github.com/pingcap/tidb/blob/3ac2b34a3491e809a96db358ee2ce8d11a66abb6/ast/dml.go#L451) 节点：\n\n```\ntype SelectStmt struct {\n\tdmlNode\n\tresultSetNode\n\n\t// SelectStmtOpts wraps around select hints and switches.\n\t*SelectStmtOpts\n\t// Distinct represents whether the select has distinct option.\n\tDistinct bool\n\t// From is the from clause of the query.\n\tFrom *TableRefsClause\n\t// Where is the where clause in select statement.\n\tWhere ExprNode\n\t// Fields is the select expression list.\n\tFields *FieldList\n\t// GroupBy is the group by expression list.\n\tGroupBy *GroupByClause\n\t// Having is the having condition.\n\tHaving *HavingClause\n\t// OrderBy is the ordering expression list.\n\tOrderBy *OrderByClause\n\t// Limit is the limit clause.\n\tLimit *Limit\n\t// LockTp is the lock type\n\tLockTp SelectLockType\n\t// TableHints represents the level Optimizer Hint\n\tTableHints []*TableOptimizerHint\n}\n```\n\n可以看出，`ast.SelectStmt` 结构体内包含的内容和 `SELECT` 语法也是一一对应的。 \n\n其他的产生式也都是根据对应的 `SQL` 语法来编写的。从 `parser.y` 的注释看到，这个文件最初是用 [工具](https://github.com/cznic/ebnf2y) 从 `BNF` 转化生成的，从头手写这个规则文件，工作量会非常大。\n\n完成了语法规则文件 `parser.y` 的定义，就可以使用 `goyacc` 生成语法解析器：\n\n```\nbin/goyacc -o parser/parser.go parser/parser.y 2>&1\n```\n\nTiDB 对 `lexer` 和 `parser.go` 进行了封装，对外提供 [parser.yy_parser](https://github.com/pingcap/tidb/blob/source-code/plan/preprocess.go) 进行 SQL 语句的解析：\n\n```\n// Parse parses a query string to raw ast.StmtNode.\nfunc (parser *Parser) Parse(sql, charset, collation string) ([]ast.StmtNode, error) {\n    ...\n}\n```\n\n最后，我写了一个简单的例子，使用 TiDB 的 `SQL Parser` 进行 SQL 语法解析，构建出 `AST`，然后利用 `visitor` 遍历 `AST` ：\n\n```golang\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/pingcap/parser\"\n\t\"github.com/pingcap/parser/ast\"\n\t_ \"github.com/pingcap/tidb/types/parser_driver\"\n)\ntype visitor struct{}\n\nfunc (v *visitor) Enter(in ast.Node) (out ast.Node, skipChildren bool) {\n\tfmt.Printf(\"%T\\n\", in)\n\treturn in, false\n}\n\nfunc (v *visitor) Leave(in ast.Node) (out ast.Node, ok bool) {\n\treturn in, true\n}\n\nfunc main() {\n\tp := parser.New()\n\n\tsql := \"SELECT /*+ TIDB_SMJ(employees) */ emp_no, first_name, last_name \" +\n\t\t\"FROM employees USE INDEX (last_name) \" +\n\t\t\"where last_name='Aamodt' and gender='F' and birth_date > '1960-01-01'\"\n\tstmtNodes, _, err := p.Parse(sql, \"\", \"\")\n\n\tif err != nil {\n\t\tfmt.Printf(\"parse error:\\n%v\\n%s\", err, sql)\n\t\treturn\n\t}\n\tfor _, stmtNode := range stmtNodes {\n\t\tv := visitor{}\n\t\tstmtNode.Accept(&v)\n\t}\n}\n```\n\n我实现的 `visitor` 什么也没干，只是输出了节点的类型。 这段代码的运行结果如下，依次输出遍历过程中遇到的节点类型：\n\n```golang\n*ast.SelectStmt\n*ast.TableOptimizerHint\n*ast.TableRefsClause\n*ast.Join\n*ast.TableSource\n*ast.TableName\n*ast.BinaryOperationExpr\n*ast.BinaryOperationExpr\n*ast.BinaryOperationExpr\n*ast.ColumnNameExpr\n*ast.ColumnName\n*ast.ValueExpr\n*ast.BinaryOperationExpr\n*ast.ColumnNameExpr\n*ast.ColumnName\n*ast.ValueExpr\n*ast.BinaryOperationExpr\n*ast.ColumnNameExpr\n*ast.ColumnName\n*ast.ValueExpr\n*ast.FieldList\n*ast.SelectField\n*ast.ColumnNameExpr\n*ast.ColumnName\n*ast.SelectField\n*ast.ColumnNameExpr\n*ast.ColumnName\n*ast.SelectField\n*ast.ColumnNameExpr\n*ast.ColumnName\n```\n\n了解了 TiDB `SQL Parser` 的实现，我们就有可能实现 TiDB 当前不支持的语法，例如添加内置函数，也为我们学习查询计划以及优化打下了基础。希望这篇文章对你能有所帮助。\n\n> 作者介绍：马震，金蝶天燕架构师，曾负责中间件、大数据平台的研发，今年转向了 NewSQL 领域，关注 OLTP/AP 融合，目前在推动金蝶下一代 ERP 引入 TiDB 作为数据库存储服务。\n\n> 点击查看更多 [TiDB 源码阅读系列文章](https://pingcap.com/zh/blog/?tag=TiDB%20%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB)","date":"2018-03-20","author":"马震","fillInMethod":"writeDirectly","customUrl":"tidb-source-code-reading-5","file":null,"relatedBlogs":[]}}},
    "staticQueryHashes": ["1327623483","1820662718","3081853212","3430003955","3649515864","4265596160","63159454"]}