博客园  :: 首页  :: 新随笔  :: 订阅 订阅  :: 管理

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

Posted on 2022-11-15 19:01  面具下的戏命师  阅读(821)  评论(0编辑  收藏  举报

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

在前两篇文章中已经讲述了 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 树就是这个样子的