探索 1|Query_block
和 Query_expression
Step 1|查阅资料: MySQL源码解析之执行计划 - GreatSQL - 博客园,了解到 bool JOIN::optimize()
是优化器的入口函数,阅读其中逻辑,推测 Query_expression
可能是存储 SQL 语句的对象。考虑优先了解 Query_expression
的逻辑。
Step 2|阅读 Query_block
的源码,发现其为 Query_block
相互大量引用,而 Query_block
又是 Query_term
的子类且与 Query_term
相互大量引用。因此,先了解 Query_block
、Query_expression
及 Query_term
共同构成的查询树结构。
- MySQL 源码|1 - Query_block 和 Query_expression 的连接关系
- MySQL 源码|2 - 查询树与 Query_term 节点
- MySQL 源码|3 - 源码涉及类型别名(附录)
- MySQL 源码|4 - Query_expression 类的基本变量和方法
- MySQL 源码|5 - Query_term 及其子类
- MySQL 源码|6 - Query_block 类的基本变量和方法
探索 2|LEX
结构体
Step 1|因为 Query_block
是存储单个 SQL 语句的对象,那么构造该对象的位置应该就是 SQL 语句解析的部分。Query_block
构造方法的两次调用都发生在 LEX
的成员函数 new_empty_query_block
和 create_query_expr_and_block
中。
Step 2|在 LEX
结构体的注释中,介绍了 LEX
对象的声明周期,所以按生命周期梳理各个元素的顺序。
Step 3|在 LEX
结构体中,有成员 m_tok_start
被注释为 “Starting position of the last token parsed”,说明这个成员被用于解析,搜索这个成员的使用位置,发现在 LEX::start_token()
、LEX::restart_token()
、LEX::get_tok_start()
和 LEX::yyLength()
方法中被使用,然后发现这 4 个函数主要在 lex_one_token()
函数或该函数调用的其他方法中被调用,但是在 sql/sql_lex.cc 和 router/src/routing/src/sql_lexer.cc 中有两套 lex_one_token()
解析函数。从引用关系来看,sql_lexer.cc
中的 lex_one_token()
应该是主要解析逻辑。
探索 3|词法解析
Step 1|阅读 lex_one_token()
函数,可以看到 Lex_input_stream
类型指针 lip
存储了解析过程的状态,并在函数中大量使用。
Step 2|在 lex_one_token()
中大量使用了文本扫描器(Lex_input_stream
)和 CHARSET_INFO
- MySQL 源码|12 - 词法解析:Lex_input_stream(文本扫描器)的数据成员
- MySQL 源码|13 - 词法解析的状态存储器(Lex_input_stream)的主要数据成员与函数
- MySQL 源码|14 - 词法解析中的 CHARSET_INFO 结构体及衍生函数
Step 3|lex_one_token()
函数的核心就是词法解析的自动机。
- MySQL 源码|9 - 词法解析:自动机状态转移矩阵
- MySQL 源码|10 - 词法解析:状态及状态转移规则(1)
- MySQL 源码|11 - 词法解析:状态及状态转移规则(2)
- MySQL 源码|15 - 词法解析:状态及状态转移规则(3)
- MySQL 源码|16 - 词法解析:状态及状态转移规则(4)
- MySQL 源码|17 - 词法解析:状态及状态转移规则(5)
- MySQL 源码|18 - 词法解析:状态及状态转移规则(6)
- MySQL 源码|19 - 词法解析:状态及状态转移规则(7)
- MySQL 源码|21 - 词法解析:状态转移逻辑梳理
Step 4|lex_one_token()
函数的调用位置
探索 4|寻找语法解析
Step 1|根据词法解析器,可知句法解析器一定要依托 SqlLexer::iterator
迭代器,全局搜索引入 SqlLexer::iterator
,除了 sql_lexer.cc
外,有如下位置使用:
\router\src\routing\src\classic_query_forwarder.cc
:除DEBUG_DUMP_TOKEN
外,仅在contains_multiple_statements
函数中使用\router\src\routing\src\classic_query_sender.cc
:逻辑均在DEBUG_DUMP_TOKEN
中\router\src\routing\src\sql_parser.h
:使用于SqlParser
类的成员中
其中,直观推断 SQLParser
类更接近语法解析的概率更高。
Step 2|SQLParser
的 4 个子类中应该包含解析逻辑,但是看起来都不是词法解析的主逻辑位置,而应该是 4 个工具类,具体地:
- MySQL 源码|23 - 句法解析:ImplicitCommitParser 的解析方法
- MySQL 源码|24 - 句法解析:SplittingAllowedParser 解析器
- MySQL 源码|25 - 句法解析:StartTransactionParser 解析器
- MySQL 源码|26 - 句法解析:ShowWarningsParser 解析器
Step 3|继续分析这 4 个子类的调用位置,发现都集中到了 classic_query_forwarder.cc
的 QueryForwarder::command()
中。查看 QueryForwarder
类,发现其中有一个名为 Stage
的枚举类,推断是 SQL 命令的执行过程,因此推断 QueryForarder
类是 MySQL 的执行过程。QueryForarder
是 ForwardingProcessor
的子类,ForwardingProcessor
是 Processor
的子类,Processor
是 BasicProcessor
的子类。我们从已经了解的 4 个解析器向调用位置逐步了解逻辑。
- MySQL 源码|27 - ImplicitCommitParser 解析器和 SplittingAllowedParser 解析器的调用位置
- MySQL 源码|28 - StartTransactionParser 解析器和 ShowWarningsParser 解析器的调用位置
- MySQL 源码|29 - 解析过程的 command 函数逻辑
Step 4|QueryForarder::command()
函数在 QueryForwarder::process()
函数中被调用,而 QueryForwarder::process()
函数没有被直接调用,且 QueryForwarder
类在 classic_query_forward.cc
和 classic_query_forward.h
文件外,仅在 CommandProcessor::command()
函数中被调用。下面我们了解 Processor
类的一些基础信息。
Step 5|在 QueryForarder::command()
函数中没有语法解析逻辑。至此,我们已经尝试了如下 3 个方向:
- 通过追溯调用词法解析逻辑的位置,寻找词法解析的逻辑,但是只找到了权限判断、判断是否开启事务、判断是否隐式提交、判断是否发出警告信息的解析器,而没有主要的解析器逻辑
- 通过追溯调用
Query_block
和Query_expression
的逻辑,但是我们也没有在相关位置找到语法解析逻辑 - 通过反查
SELECT_SYM
和UNIQUE_SYM
的全局使用位置,但是也不再有其他包含的位置了
但是,SELECT_SYM
和 UNIQUE_SYM
在 sql_yacc.yy
中还有使用,搜索后发现如下文章 MySQL 源码解读之-语法解析(二),确定 sql_yacc.yy
是 bison 规则文件,正是语法解析逻辑的位置。