2025年3月12日 星期三 甲辰(龙)年 月十一 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > Other

MySQL 源码解读之-语法解析(三)

时间:09-06来源:作者:点击数:36
CDSY,CDSY.XYZ

在前两篇文章中已经讲述了 bison 如何解析 sql 语句并生成 AST 树。那么 MySQL是如何和 bison 的程序关联起来的呢,并通过gdb 调试一下。在MySQL 源码解读之-语法解析(二)中我们用到了许多词法解析和语法解析的术语概念,例如 DFA, LALR。了解这些概念建议学习一下编译原理课程。

mysql 用到的 bison 关键字

token 标志0-255被保留作为字符值,⾃定义产⽣的token标志从258开始
type 为非终结符指明类型
left  表示左结合
right  表示右结合
nonassoc  表示无结合
parse-param  通过%parse-param {param} 来给 yyparse(param)传参数 
lex-param 通过%lex-param {param} 来给 yylex(param)传参数
start 指定起始符号
pure-parser   指定希望解析器是可重⼊的
expect  告诉编译器预⻅多少次的移动归约冲突

token 用来定义终结符,例如 SELECT_SYM

left right  nonassoc 指定运算符的结合方式,例如 + -等。优先级按照定义从上到下依次降低

start 起始符号,所有的文法从 start 开始,mysql中即为 start_entry

其他符号可以自查手册,建议参考书籍《flex 与 bison》

分析并调试语法解析过程

我们以最简单的 select * from bank; 为例,通过词法分析,该语句会被解析为 4 个token。我们在函数 THD::sql_parser 出打断点:然后执行 select 语句

可以看到,此处定义了根节点root,并作为参数传给 MYSQLparse。因为 yyparse 是 MYSQLparse 的宏定义,该函数由bison 自动生成比较复杂,此处不再跟进去调试。我们直接分析sql_yacc.yy文件。

可以看到,线程描述符和根节点 root被作为参数传了进来。 接下来就是根据文法进行匹配,我们可以通过 bison 的debug 进行分析。

可以先删除 gdb 断点,执行如下语句打开 bison 的debug 模式:

  • set debug = "d,parser_debug";

如下是 debug 日志:

  • Starting parse # 开始解析
  • Entering state 0 # 进入状态 0
  • Reading a token: Next token is token SELECT_SYM (: ) # 预读下一个 token SELECT_SYM(748)
  • Shifting token SELECT_SYM (: ) # 移进 SELECT_SYM
  • Entering state 42 # 进入状态 42
  • Reading a token: Next token is token '*' (: ) # 预读下一个 token *(42)
  • Reducing stack by rule 1380 (line 10081): # 根据第1380个规则进行归约 (10081行)
  • -> $$ = nterm select_options (: ) # select_options被归约为空串 ε
  • Stack now 0 42 # 当前栈为 0 42
  • Entering state 1022 # 进入状态 1022
  • Next token is token '*' (: ) # 下一个 token *
  • Shifting token '*' (: ) # 移进 *
  • Entering state 874 # 进入状态 874
  • Reducing stack by rule 1400 (line 10171): # 根据第 1400 个规则 (10171行)
  • $1 = token '*' (: ) # 终结符的值为 *
  • -> $$ = nterm select_item_list (: ) # 把*归约成 select_item_list ($$ = NEW_PTN PT_select_item_list)
  • Stack now 0 42 1022 # 当前栈 0 42 1022
  • Entering state 1509 # 进入状态 1509
  • Reading a token: Next token is token FROM (: ) # 读取下一个 token FROM
  • Shifting token FROM (: ) # 移进 FROM
  • Entering state 2250 # 进入状态 2250
  • Reading a token: Next token is token IDENT_QUOTED (: ) # 读取下一个token IDENT_QUOTED
  • Shifting token IDENT_QUOTED (: ) # 移进 IDENT_QUOTED
  • Entering state 368 # 进入状态368
  • Reducing stack by rule 2298 (line 14842): # 根据第2298个规则 (14842行)
  • $1 = token IDENT_QUOTED (: ) # $1(即表名 bank) 为 IDENT_QUOTED
  • -> $$ = nterm IDENT_sys (: ) # 把IDENT_QUOTED归约成 IDENT_sys
  • Stack now 0 42 1022 1509 2250 # 当前栈 0 42 1022 1509 2250
  • Entering state 722 # 进入状态 722 ($$= $1)
  • Reducing stack by rule 2308 (line 14975): # 根据第 2308 个规则 (14975行)
  • $1 = nterm IDENT_sys (: ) # $1为IDENT_sys(表名 bank)
  • -> $$ = nterm ident (: ) # 把IDENT_sys归约为 ident ($$=$1, 此时ident的值为 bank)
  • Stack now 0 42 1022 1509 2250 # 当前状态栈为 0 42 1022 1509 2250
  • Entering state 1099 # 进入状态 1099
  • Reading a token: Next token is token END_OF_INPUT (: ) # 读取下一个 token 为 END_OF_INPUT
  • Reducing stack by rule 2293 (line 14807): # 根据第 2293 个规则进行归约 (14807行)
  • $1 = nterm ident (: ) # $1为 ident(bank)
  • -> $$ = nterm table_ident (: ) # 把 ident 归约成 table_ident -> $$= NEW_PTN Table_ident(to_lex_cstring($1))
  • Stack now 0 42 1022 1509 2250 # 当前状态栈 0 42 1022 1509 2250
  • Entering state 2998 # 进入状态 2998
  • Next token is token END_OF_INPUT (: ) # 下一个 token 为 END_OF_INPUT
  • Reducing stack by rule 1771 (line 11993): # 根据第 1771 个规则进行归约 (11993行)
  • -> $$ = nterm opt_use_partition (: ) # opt_use_partition 归约为空串 ε
  • Stack now 0 42 1022 1509 2250 2998 # 当前状态栈 0 42 1022 1509 2250 2998
  • Entering state 3663 # 进入状态 3663
  • Next token is token END_OF_INPUT (: ) # 读取下一个 token END_OF_INPUT
  • Reducing stack by rule 1858 (line 12370): # 根据第 1858 个规则进行归约 (12370行)
  • -> $$ = nterm opt_table_alias (: ) # opt_table_alias 归约为空串 ε
  • Stack now 0 42 1022 1509 2250 2998 3663 # 当前状态栈 0 42 1022 1509 2250 2998 3663
  • Entering state 4210 # 进入状态 4210
  • Next token is token END_OF_INPUT (: ) # 读取下一个 token END_OF_INPUT
  • Reducing stack by rule 1819 (line 12257): # 根据第 1819 个规则进行归约 (12257行)
  • -> $$ = nterm opt_index_hints_list (: ) # opt_index_hints_list 归约为空串 ε
  • Stack now 0 42 1022 1509 2250 2998 3663 4210 # 当前状态栈 0 42 1022 1509 2250 2998 3663 4210
  • Entering state 4588 # 进入状态 4588
  • Reducing stack by rule 1821 (line 12262): # 根据第 1821 个规则进行归约 (12262行)
  • $1 = nterm opt_index_hints_list (: ) # $1 为 opt_index_hints_list 即 空串 ε
  • -> $$ = nterm opt_key_definition (: ) # opt_index_hints_list 归约成 opt_key_definition 所以opt_key_definition也是空串 ε
  • Stack now 0 42 1022 1509 2250 2998 3663 4210 # 当前状态栈 0 42 1022 1509 2250 2998 3663 4210
  • Entering state 4589 # 进入状态 4589
  • Reducing stack by rule 1784 (line 12053): # 根据第 1784 个规则进行归约 (12053行)
  • $1 = nterm table_ident (: ) # $1 为table_ident (表名 back)
  • $2 = nterm opt_use_partition (: ) # $2 为opt_use_partition 空串 ε
  • $3 = nterm opt_table_alias (: ) # $3 为opt_table_alias 空串 ε
  • $4 = nterm opt_key_definition (: ) # $4 为opt_key_definition 空串 ε
  • -> $$ = nterm single_table (: ) # 归约成single_table $$= NEW_PTN PT_table_factor_table_ident($1, $2, $3, $4)
  • Stack now 0 42 1022 1509 2250 # 当前状态栈 0 42 1022 1509 2250
  • Entering state 2994 # 进入状态 2994
  • Reducing stack by rule 1774 (line 12027): # 根据第 1774 个规则 (12027行)
  • $1 = nterm single_table (: ) # $1为 single_table 当前已经是一个PT_table_factor_table_ident类的实例化对象
  • -> $$ = nterm table_factor (: ) # 把 single_table直接归约成table_factor (默认规则,等同于 $$ = $1)
  • Stack now 0 42 1022 1509 2250 # 当前栈状态 0 42 1022 1509 2250
  • Entering state 2991 # 进入状态 2991
  • Reducing stack by rule 1747 (line 11831): # 根据第 1747 个规则进行归约 (11831行)
  • $1 = nterm table_factor (: ) # $1为table_factor
  • -> $$ = nterm table_reference (: ) # 把 table_factor 归约成 table_reference $$ = $1
  • Stack now 0 42 1022 1509 2250 # 当前状态栈 0 42 1022 1509 2250
  • Entering state 2989 # 进入状态 2989
  • Next token is token END_OF_INPUT (: ) # 下一个 token 为 END_OF_INPUT
  • Reducing stack by rule 1376 (line 10047): # 根据第 1376 个规则进行归约 (10047行)
  • $1 = nterm table_reference (: ) # $1为 table_reference 当前为 PT_table_factor_table_ident类的实例化对象
  • -> $$ = nterm table_reference_list (: ) # 把 table_reference归约为table_reference_list 执行$$.init(YYMEM_ROOT);
  • Stack now 0 42 1022 1509 2250 # 当前栈状态 0 42 1022 1509 2250
  • Entering state 2988 # 进入状态 2988
  • Next token is token END_OF_INPUT (: ) # 下一个 token 为 END_OF_INPUT
  • Reducing stack by rule 1375 (line 10043): # 根据第 1375 个规则进行归约 (10043行)
  • $1 = nterm table_reference_list (: ) # $1 为 table_reference_list
  • -> $$ = nterm from_tables (: ) # 直接把 table_reference_list 归约成 from_tables
  • Stack now 0 42 1022 1509 2250 # 当前栈状态为 0 42 1022 1509 2250
  • Entering state 2987 # 进入状态 2987
  • Reducing stack by rule 1373 (line 10038): # 根据第 1373 个规则进行归约 (10038行)
  • $1 = token FROM (: ) # $1为 token FROM
  • $2 = nterm from_tables (: ) # $2 为 from_tables
  • -> $$ = nterm from_clause (: ) # 归约成 from_clause $$= $2
  • Stack now 0 42 1022 1509 # 当前栈状态为 0 42 1022 1509
  • Entering state 2252 # 进入状态 2252
  • Reducing stack by rule 1372 (line 10034): # 根据第 1372 个规则进行归约 (10034行)
  • $1 = nterm from_clause (: ) # $1 为 from_clause
  • -> $$ = nterm opt_from_clause (: ) # 直接把 from_clause 归约成 opt_from_clause
  • Stack now 0 42 1022 1509 # 当前栈状态 0 42 1022 1509
  • Entering state 2251 # 进入状态 2251
  • Next token is token END_OF_INPUT (: ) # 下一个 token END_OF_INPUT
  • Reducing stack by rule 1862 (line 12380): # 根据第 1862 个规则进行归约 (12380行)
  • -> $$ = nterm opt_where_clause (: ) # opt_where_clause 归约成空串 ε
  • Stack now 0 42 1022 1509 2251 # 当前状态栈为 0 42 1022 1509 2251
  • Entering state 3000 # 进入状态 3000
  • Next token is token END_OF_INPUT (: ) # 下一个 token 为 END_OF_INPUT
  • Reducing stack by rule 1881 (line 12509): # 根据第 1881 个规则进行归约 (12509行)
  • -> $$ = nterm opt_group_clause (: ) # opt_group_clause 归约成空串 ε $$ = NULL
  • Stack now 0 42 1022 1509 2251 3000 # 当前栈状态 0 42 1022 1509 2251 3000
  • Entering state 3666 # 进入状态 3666
  • Next token is token END_OF_INPUT (: ) # 下一个 token END_OF_INPUT
  • Reducing stack by rule 1865 (line 12389): # 根据第 1865 个规则进行归约 (12389行)
  • -> $$ = nterm opt_having_clause (: ) # opt_having_clause 归约成空串 ε $$ = NULL
  • Stack now 0 42 1022 1509 2251 3000 3666 # 当前栈状态 42 1022 1509 2251 3000 3666
  • Entering state 4214 # 进入状态 4214
  • Next token is token END_OF_INPUT (: ) # 下一个 token END_OF_INPUT
  • Reducing stack by rule 1876 (line 12470): # 根据第 1876 个规则进行归约 (12470行)
  • -> $$ = nterm opt_window_clause (: ) # opt_window_clause 归约成空串 ε $$ = NULL
  • Stack now 0 42 1022 1509 2251 3000 3666 4214 # 当前栈状态 0 42 1022 1509 2251 3000 3666 4214
  • Entering state 4595 # 进入状态 4595
  • Reducing stack by rule 1370 (line 10009): # 根据第 1370 个规则进行归约 (10089行)
  • $1 = token SELECT_SYM (: ) # $1 为终结符 SELECT_SYM
  • $2 = nterm select_options (: ) # $2 为 select_options 空串 ε
  • $3 = nterm select_item_list (: ) # $3 为 select_item_list *
  • $4 = nterm opt_from_clause (: ) # $4 为表名,此处表现为一个类对象
  • $5 = nterm opt_where_clause (: ) # $5 为 opt_where_clause 空串 ε
  • $6 = nterm opt_group_clause (: ) # $6 为 opt_group_clause 空串 ε
  • $7 = nterm opt_having_clause (: ) # $7 为 opt_having_clause 空串 ε
  • $8 = nterm opt_window_clause (: ) # $8 为 opt_window_clause 空串 ε
  • -> $$ = nterm query_specification (: ) # 该文法归约成query_specification $$= NEW_PTN PT_query_specification(....)
  • Stack now 0 # 当前栈为 0
  • Entering state 118 # 进入状态 118
  • Reducing stack by rule 1366 (line 9966): # 根据第 1366 个规则进行归约 (9966行)
  • $1 = nterm query_specification (: ) # $1 为 query_specification
  • -> $$ = nterm query_primary (: ) # 把 query_specification 归约成 query_primary $$= $1
  • Stack now 0 # 当前栈为 0
  • Entering state 117 # 进入状态 117
  • Reducing stack by rule 1358 (line 9933): # 根据第 1358 个规则进行归约 (9933行)
  • $1 = nterm query_primary (: ) # $1 为 query_primary
  • -> $$ = nterm query_expression_body (: ) # 把 query_primary 归约成 query_expression_body $$= $1
  • Stack now 0 # 当前栈状态 0
  • Entering state 115 # 进入状态 115
  • Next token is token END_OF_INPUT (: ) # 下一个 token END_OF_INPUT
  • Reducing stack by rule 1890 (line 12571): # 根据第 1890 个规则进行归约 (12571行)
  • -> $$ = nterm opt_order_clause (: ) # 把opt_order_clause归约为 空串 ε $$ = NULL
  • Stack now 0 115 # 当前栈状态 0 115
  • Entering state 1169 # 进入状态 1169
  • Next token is token END_OF_INPUT (: ) # 读取下一个 token END_OF_INPUT
  • Reducing stack by rule 1899 (line 12608): # 根据第 1899 个规则进行归约 (12608行)
  • -> $$ = nterm opt_limit_clause (: ) # 把 opt_limit_clause 归约为 空串 ε $$ = NULL
  • Stack now 0 115 1169 # 当前栈状态 0 115 1169
  • Entering state 1713 # 进入状态 1713
  • Reducing stack by rule 1351 (line 9888): # 根据第 1351 个规则进行归约 (9888行)
  • $1 = nterm query_expression_body (: ) # $1为 query_expression_body 目前是一个类对象
  • $2 = nterm opt_order_clause (: ) # $2为 opt_order_clause 空串 ε
  • $3 = nterm opt_limit_clause (: ) # $3为 opt_limit_clause 空串 ε
  • -> $$ = nterm query_expression (: ) # 根据该文法归约成 query_expression $$ = NEW_PTN PT_query_expression($1, $2, $3)
  • Stack now 0 # 当前栈状态 0
  • Entering state 114 # 进入状态 114
  • Next token is token END_OF_INPUT (: ) # 下一个 token END_OF_INPUT
  • Reducing stack by rule 1342 (line 9784): # 根据第 1342 个规则进行归约 (9784行)
  • $1 = nterm query_expression (: ) # $1为 query_expression
  • -> $$ = nterm select_stmt (: ) # 把query_expression 归约成select_stmt $$ = NEW_PTN PT_select_stmt($1)
  • Stack now 0 # 当前栈状态 0
  • Entering state 112 # 进入状态 112
  • Reducing stack by rule 91 (line 2355): # 根据第 91 个规则进行归约 (2355行)
  • $1 = nterm select_stmt (: ) # $1为 select_stmt
  • -> $$ = nterm simple_statement (: ) # 把select_stmt归约成simple_statement
  • Stack now 0 # 当前栈状态 0
  • Entering state 68 # 进入状态 68
  • Reducing stack by rule 13 (line 2273): # 根据第 13 个规则 (2273行)
  • $1 = nterm simple_statement (: ) # $1为 simple_statement
  • -> $$ = nterm simple_statement_or_begin (: ) # 把simple_statement归约成 simple_statement_or_begin { *parse_tree= $1 }
  • Stack now 0 # 当前栈状态 0
  • Entering state 67 # 进入状态 67
  • Next token is token END_OF_INPUT (: ) # 下一个 token END_OF_INPUT
  • Shifting token END_OF_INPUT (: ) # 移进END_OF_INPUT
  • Entering state 1138 # 进入状态 1138
  • Reducing stack by rule 10 (line 2260): # 根据第 10 个规则进行归约 (2260行)
  • $1 = nterm simple_statement_or_begin (: ) # $1为 simple_statement_or_begin
  • $2 = token END_OF_INPUT (: ) # $2为 token END_OF_INPUT
  • -> $$ = nterm sql_statement (: ) # 归约成 sql_statement YYLIP->found_semicolon= NULL
  • Stack now 0 # 当前栈状态 0
  • Entering state 66 # 进入状态 66
  • Reducing stack by rule 1 (line 2177): # 根据第 1 个规则进行归约 (2177行)
  • $1 = nterm sql_statement (: ) # $1为 sql_statement
  • -> $$ = nterm start_entry (: ) # 直接把 sql_statement 归约成start_entry
  • Stack now 0 # 当前栈状态 0
  • Entering state 65 # 进入状态 65
  • Reading a token: Now at end of input. # 读取一个token, 现在已经位于输入流的末端
  • Shifting token $end (: ) # 移进 $end
  • Entering state 1137 # 进入状态 1137
  • Stack now 0 65 1137 # 当前栈状态 0 65 1137
  • Cleanup: popping token $end (: ) # 弹出 token $end
  • Cleanup: popping nterm start_entry (: ) # 弹出 start_entry 语法解析结束,该 sql 被解析成一个正确的 sql 语法

如上所示 select * from bank; 最后归约成 start_entry。则说明该语句是一个正确的 sql 语法。我们再举一个反例,执行 selectt 1; 我们知道这是一个错误的语法,此时我们再看一下解析器的 debug 日志:

因此我们可以看到,如果 sql 出现语法错误是在语法解析阶段判断出来的。

前文中我们讲到,bison 做完语法解析后会返回一颗 AST 树,现在我们通过p命令,逐步打印出整个解析树。前边我们可以看到源代码中对根节点的定义为 root。然后根据 bison 的 debug 日志,从 start_entry 一步步推导出所有叶子节点。

1)、我们先打印下 root 节点,如下图所示:root 节点是 PT_select_stmt。 通过查看 PT_select_stmt类的定义,我们可以PT_select_stmt类的私有成员 m_qe 的类型为 PT_query_expression_body *。通过查看bison 的 debug 日志,我们也可以分析到最终是由 select_stmt 归约得到的。select_stamt 的执行码为 $$ = NEW_PTN PT_select_stmt($1)。

$1是类型PT_query_expression。PT_query_expression是PT_query_expression_body 的子类。此处可以通过查看 类PT_select_stmt的构造函数代码查看该类的实例化过程

2)、我们继续反向分析 bison 的 debug 日志。PT_select_stmt是由PT_query_expression归约得到的,我们看 query_expression的执行码  $$ = NEW_PTN PT_query_expression($1, $2, $3)。 PT_query_expression对象是由 query_expression_body作为入参得到的。 而query_expression_body是由query_specification先归约成query_primary,query_primary再归约成query_expression_body。

而PT_query_primaey是query_expression_body的子类。PT_query_specification是PT_query_primary的子类。所有如下图所示,m_body应该是类PT_query_specification的实例化对象。

3、通过查看PT_query_specification类的代码,可以看到该类的 from_clause成员是一个<PT_table_reference *>容器。打印它的 value.table_name 属性即可看到,我们此次查询的表名bank。

4、此处我们虽然看到了表名,依然可以进一步分析这个表名的来源。PT_query_specification是由于 SELECT_SYM select_item_list  opt_from_clause归约形成。opt_from_clause由from_clause归约形成。经过多层归约,opt_from_clause最初是由 single_table  $$= NEW_PTN PT_table_factor_table_ident($1, $2, $3, $4)逐级归约得到。 PT_table_factor_table_ident是PT_table_reference的子类,因此我们可以把m_array[0]强转为 PT_table_factor_table_ident *

此处我们通过PT_table_factor_table_ident的 value属性打印出来了表名,是因为我们在 gdb时候代码运行到了 make_sql_cmd 函数,value 的设置是make_sql_cmd完成的,并不是 MySQLparser 做的,因为yy 文件很难用 gdb 调试,我在这里解析过程中找 value 的初始化找了好久没找到。自己挖坑自己跳,此处留个记号

5、上图我们可以看到类PT_table_factor_table_ident的 value 成员是一个 TABLE_LIST对象,我们一样获取到了表名。TBALE_LIST 我们可以在父类 PT_table_reference 中找到定义:

  • class PT_table_reference : public Parse_tree_node {
  • public:
  • TABLE_LIST *value;
  • virtual PT_joined_table *add_cross_join(PT_cross_join *cj);
  • };

6、继续分析 bison 的 debug 日志。PT_table_factor_table_ident对象的初始化过程,第一个参数为Table_ident。而非终结符table_ident的执行码为 $$= NEW_PTN Table_ident(to_lex_cstring($1)),$1就是我们读取到的字符 bank

至此为止,我们已经找到了树的最低层。

7、我们再看一下其他节点,目前还差 * 没有分析,我们继续看 bison 的 debug 日志。

  • Reducing stack by rule 1400 (line 10171): # 根据第 1400 个规则 (10171行)
  • $1 = token '*' (: ) # 终结符的值为 *
  • -> $$ = nterm select_item_list (: ) # 把*归约成 select_item_list ($$ = NEW_PTN PT_select_item_list)
  • 对应的 c 执行码如下:
  • Item *item = NEW_PTN Item_asterisk(@$, nullptr, nullptr);
  • $$ = NEW_PTN PT_select_item_list;
  • if ($$ == nullptr || item == nullptr || $$->push_back(item))
  • MYSQL_YYABORT;

*被直接归约成了 select_item_list。 PT_select_item_list类的定义如下:从命令我们不难看出,这是个 item 列表。继续分析 bison 的 debug 日志,select_item_list作为入参被归约成了PT_query_specification。然后最终被归约成PT_select_stmt,和前边表名一样的步骤。

到目前为止,我们已经把整个树的所有节点都分析完成了。下边我们手绘一下整个 AST 树。

到目前为止,经过bison,即MySQLparser 函数解析后的 sql生产的 AST 树就是这个样子的。

CDSY,CDSY.XYZ
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐