目录文档:MySQL 源码|源码剖析文档目录
源码位置(版本 = MySQL 8.0.37):sql/sql_class.cc;sql/sql_yacc.yy
前置文档:
在 sql/sql_yacc.yy 中定义了 %define api.prefix {my_sql_parser_}
,详见 Bison 官方文档 - 3.8 Multiple Parsers in the Same Program,其中描述如下(AI 翻译,如有疑问可查阅原文):
大多数使用 Bison 的程序只解析一种语言,因此只包含一个 Bison 解析器。但是如果同一个程序想要解析多种语言该怎么办呢?这时你需要避免不同语言定义的函数和变量(如
yyparse
、yylval
)之间的名称冲突。为了从同一个编译单元中使用不同的解析器,你还需要避免在生成的头文件中导出的类型和宏(例如YYSTYPE
)产生的冲突。实现这一点的简便方法是定义
%define
变量api.prefix
。通过使用不同的api.prefix
,可以确保即使一起包含这些头文件也不会产生冲突,并且编译后的对象也可以被链接在一起。指定%define api.prefix {prefix}
(或者通过选项-Dapi.prefix={prefix}
调用 Bison,参见 Bison 官方文档 - 9 Invoking Bison)会将 Bison 解析器的接口函数和变量重命名为以prefix
开头而非yy
,并且所有的宏也会以PREFIX
(即prefix
全部大写)开头而非YY
。重命名的符号包括
yyparse
,yylex
,yyerror
,yynerrs
,yylval
,yylloc
,yychar
和yydebug
。如果你使用了推送解析器,yypush_parse
,yypull_parse
,yypstate
,yypstate_new
和yypstate_delete
也将被重命名。重命名的宏包括YYSTYPE
,YYLTYPE
,以及YYDEBUG
,后者会被特别处理——下面会有更多关于这方面的说明。
因此,MySQL 主解析器的函数 yyparse
、yylex
、yyerror
、yynerrs
、yylval
、yylloc
、yychar
和 yydebug
,以及宏指令名称 YYSTYPE
、YYLTYPE
和 YYDEBUG
均被重命名,具体如下:
默认名称 | 重命名后的名称 |
---|---|
yyparse | my_sql_parser_parse |
yylex | my_sql_parser_lex |
yyerror | my_sql_parser_error |
yynerrs | my_sql_parser_nerrs |
yylval | my_sql_parser_lval |
yylloc | my_sql_parser_lloc |
yychar | my_sql_parser_char |
yydebug | my_sql_parser_debug |
YYSTYPE | MY_SQL_PARSER_STYPE |
YYLTYPE | MY_SQL_PARSER_LTYPE |
YYDEBUG | MY_SQL_PARSER_DEBUG |
THD::sql_parser()
函数:调用 yyparse
函数
在 sql/sql_class.cc 文件中的 THE::sql_parser()
函数调用了由 YACC 生成的 my_sql_parser_parse
函数(即 yyparse
函数)。该函数首先调用 my_sql_parser_parse
函数将 SQL 表达式解析为解析树,然后再调用 lex->make_sql_cmd
函数将解析树解析为 AST 语法树。源码如下:
/**
Call parser to transform statement into a parse tree.
Then, transform the parse tree further into an AST, ready for resolving.
*/
bool THD::sql_parser() {
/*
SQL parser function generated by YACC from sql_yacc.yy.
In the case of success returns 0, and THD::is_error() is false.
Otherwise returns 1, or THD::>is_error() is true.
The second (output) parameter "root" returns the new parse tree.
It is undefined (unchanged) on error. If "root" is NULL on success,
then the parser has already called lex->make_sql_cmd() internally.
*/
extern int my_sql_parser_parse(class THD * thd,
class Parse_tree_root * *root);
Parse_tree_root *root = nullptr;
if (my_sql_parser_parse(this, &root) || is_error()) {
/*
Restore the original LEX if it was replaced when parsing
a stored procedure. We must ensure that a parsing error
does not leave any side effects in the THD.
*/
cleanup_after_parse_error();
return true;
}
if (root != nullptr && lex->make_sql_cmd(root)) {
return true;
}
return false;
}
其中 my_sql_parser_parse
函数由 YACC 根据 sql/sql_yacc.yy 中的配置构造,如果解析成功则返回 0 且 THE::is_error()
返回 false,如果解析失败则返回 1 或 THE::is_error()
返回 true。
该函数接受 class THD*
类型的参数 thd
和 class Parse_tree_root**
类型的参数 root
,这两个参数在 sql/sql_yacc.yy 中通过 %parse-param
标记定义,源码如下:
%parse-param { class THD *YYTHD }
%parse-param { class Parse_tree_root **parse_tree }
如果解析成功,第二个参数 root
指向的将成为一个新的解析树;如果解析失败,则不会构造解析树。如果 root
参数为 NULL 且解析成功,则说明解析器已经在内部调用了 lex->make_sql_cmd()
函数。
MY_SQL_PARSER_STYPE
:指定返回值类型
在 MySQL 的 SQL 语法解析逻辑中,终结符和语义组需要返回不同的类型。
实现多种返回值的 Bison 语法详见 Bison 官方文档 - 3.4.2 More Than One Value Type,其中描述如下(人工翻译,如有疑问可查阅原文):
在大多数程序中,都可能需要为终结符(kind of tokens)和语义组(grouping)使用不同的数据类型。例如,数值常量可能需要
int
或long
类型,而字符串常量则需要char *
类型,而标识符可能需要指向符号表中元素的指针。要在单个解析器中使用不止一种语义值的数据类型,Bison 语法有如下两项要求:
(1) 指定所有可能的数据类型的集合,具体方法如下:
(1.1) 让 Bison 从根据分配给 Symbol 的标签计算出联合类型
(1.2) 使用
%union
语法指定类型,详见 Bison 官方文档 - 3.4.4 The Union Declaration(1.3) 定义
%define variable api.value.typer
为一个联合类型,其成员是类型标签,详见 Bison 官方文档 - 3.4.5 Providing a Structured Semantic Value Type(1.4) 使用
typedef
或#define
来定义YYSTYPE
为一个联合类型,其成员是类型标签(2) 为使用语义值的每个符号(symbol),包括终结符和非终结符选择其中的一种类型。这可以通过在
%token
语法指定,或使用%nterm
或%type
语法指定。
在 sql/sql_yacc.yy 文件中,没有使用 %union
语法或 %define variable api.value.typer
语法,而是定义了 MY_SQL_PARSER_STYPE
联合体(已配置将 YY
前缀替换为 MY_SQL_PARSER_
前缀)。