MySQL 源码|词法解析:状态转移逻辑梳理
源码位置:(版本 = MySQL 8.0.37)
前置文档:
- MySQL 源码|词法解析的状态存储器(Lex_input_stream)的主要数据成员与函数
- MySQL 源码|词法解析中的 CHARSET_INFO 结构体及衍生函数
- MySQL 源码|词法解析:状态及状态转移规则(1)
- MySQL 源码|词法解析:状态及状态转移规则(2)
- MySQL 源码|词法解析:状态及状态转移规则(3)
- MySQL 源码|词法解析:状态及状态转移规则(4)
- MySQL 源码|词法解析:状态及状态转移规则(5)
- MySQL 源码|词法解析:状态及状态转移规则(6)
- MySQL 源码|词法解析:状态及状态转移规则(7)
自动机整体逻辑
在 lex_one_token()
中有启动了一个无限循环,在循环中根据 state
变量 switch
选择需要执行的逻辑。在这样的设计下:
- 如果当前 token 已经结束,则直接
return
结束函数 - 如果当前 token 没有结束,可以更新
state
变量,然后break
出当前循环的switch
,并在下一次循环中,根据更新后的state
变量重新选择需要执行的逻辑。
for (;;) {
switch (state) {
lex_one_token()
的返回值是当前 token 的类型。
初始状态
在 lex_one_token()
函数启动无限循环之前,从 lip->next_state
获取当前状态并存入 state
变量,并将 lip->next_state
重置为 MY_LEX_START
。如果后续没有重新指定 lip->next_state
的话,那么解析下一个 token 时的初始状态就是 MY_LEX_START
。
这里需要区分 state
变量和 lip->next_state
的区别。state
变量是 lex_one_token()
中的内部变量,记录当前 token 内部的处理状态,用于在无限循环中当前 token;lip->next_state
中的 lip
变量是存储在当前线程对象的 &thd->m_parser_state->m_lip
中的 Lex_input_stream
类型变量,记录 token 之间的处理状态,用于在下次调用 lex_one_token()
函数时使用。
state = lip->next_state;
lip->next_state = MY_LEX_START;
状态转移规则
整理各个状态的转移规则如下:其中标注 “(※)” 表示在代码并不真实存在该状态转移,但等价于存在该状态转移规则。
当前状态 | 转移后状态 | 指针状态 | 转移条件 |
---|---|---|---|
MY_LEX_START (通用开始状态) | MY_LEX_IDENT (token 中只有字母、多字节字符和 _ ) | 向后移动 1 个字节 | 当前字符为字母、多字节字符或 _ |
MY_LEX_NUMBER_IDENT (token 中只有数字,不确定 token 是否结束) | 向后移动 1 个字节 | 当前字符为数字 | |
MY_LEX_START (通用开始状态) | 向后移动 1 个字节 | 当前字符为空格或换行符(※) | |
MY_LEX_CHAR (token 中包含其他字符) | 向后移动 1 个字节 | 当前字符为字母、数字、_ 、空格和其他特殊处理字符以外的其他字符 | |
MY_LEX_STRING (' ) | 向后移动 1 个字节 | 当前字符为 ' | |
MY_LEX_REAL_OR_POINT (. ) | 向后移动 1 个字节 | 当前字符为 . | |
MY_LEX_CMP_OP (> 或 = 或 ! ) | 向后移动 1 个字节 | 当前字符为 > 或 = 或 ! | |
MY_LEX_LONG_CMP_OP (< ) | 向后移动 1 个字节 | 当前字符为 < | |
MY_LEX_BOOL (& 或 ` | `) | 向后移动 1 个字节 | |
MY_LEX_COMMENT (# 或 -- ) | 向后移动 1 个字节 | 当前字符为 # | |
MY_LEX_SEMICOLON (; ) | 向后移动 1 个字节 | 当前字符为 ; | |
MY_LEX_SET_VAR (: ) | 向后移动 1 个字节 | 当前字符为 : | |
MY_LEX_EOL (\x00 ) | 向后移动 1 个字节 | 当前字符为 \x00 (空字符) | |
MY_LEX_LONG_COMMENT (/ ) | 向后移动 1 个字节 | 当前字符为 / | |
MY_LEX_END_LONG_COMMENT (* ) | 向后移动 1 个字节 | 当前字符为 * | |
MY_LEX_USER_END (@ ) | 向后移动 1 个字节 | 当前字符为 @ | |
MY_LEX_USER_VARIABLE_DELIMITER (\x60 ) | 向后移动 1 个字节 | 当前字符为 \x60 (标识符引号、开单引号) | |
MY_LEX_STRING_OR_DELIMITER (" ) | 向后移动 1 个字节 | 当前字符为 " | |
MY_LEX_IDENT_OR_HEX (x 或 X ) | 向后移动 1 个字节 | 当前字符为 x 或 X | |
MY_LEX_IDENT_OR_BIN (b 或 B ) | 向后移动 1 个字节 | 当前字符为 b 或 B | |
MY_LEX_IDENT_OR_NCHAR (n 或 N ) | 向后移动 1 个字节 | 当前字符为 n 或 N | |
MY_LEX_IDENT_OR_DOLLAR_QUOTED_TEXT ($ ) | 向后移动 1 个字节 | 当前字符为 $ | |
MY_LEX_CHAR (token 中包含其他字符) | MY_LEX_COMMENT (# 或 -- ) | 不移动指针(即当前字符为第 1 个 - ) | 当前字符为 -- ,且下一个字符是控制字符或空字符 |
【结束】JSON_UNQUOTED_SEPARATOR_SYM (状态 = MY_LEX_START ) | 向后移动 2 个字节 | 当前字符为 ->> | |
【结束】JSON_SEPARATOR_SYM (状态 = MY_LEX_START ) | 向后移动 1 个字节 | 当前字符为 -> ,且下一个字符不是 > | |
【结束】PARAM_MARKER (状态 = MY_LEX_START ) | 不移动指针 | 当前字符为 ? ,且开启了 stmt_prepare_mode 模式,且下一个字符不是有效字符,即当前 token为占位符 | |
【结束】当前字符 ASCII 码值(状态 = MY_LEX_START ) | 不移动指针 | 当前字符为 - 、) 之外的字符,且当前 token 不是占位符 | |
【结束】当前字符 ASCII 码值(状态不重置) | 不移动指针 | 当前字符为 ) | |
MY_LEX_IDENT_OR_NCHAR (n 或 N ) | 【结束】NCHAR_STRING (状态不重置) | 向后移动指针到匹配的 ' | 当前字符为 ' ,且能够找到匹配的 ' |
MY_LEX_CHAR (token 中包含其他字符) | 向后移动 1 个字节跳过 ' | 当前字符为 ' ,但找不到匹配的 ' | |
MY_LEX_IDENT (token 中只有字母、多字节字符和 _ ) | 不移动指针 | 当前字符不是 ' | |
MY_LEX_IDENT_OR_DOLLAR_QUOTED_TEXT ($ ) | MY_LEX_IDENT (token 中包含其他字符) | 不移动指针 | - |
MY_LEX_IDENT_OR_HEX (x 或 X ) | MY_LEX_HEX_NUMBER (x' 或 X' ) | 不移动指针 | 当前字符为 ' |
MY_LEX_IDENT | 不移动指针 | 当前字符不为 ' (※) | |
MY_LEX_IDENT_OR_BIN (b 或 B ) | MY_LEX_BIN_NUMBER (b' 或 B' ) | 不移动指针 | 当前字符为 ' |
MY_LEX_IDENT | 不移动指针 | 当前字符不为 ' (※) | |
MY_LEX_IDENT (token 中只有字母、多字节字符和 _ ) | 【结束】关键字对应的编码(状态 = MY_LEX_START ) | 移动指针直到结束当前 token | 当前 token 为函数或关键字 |
【结束】UNDERSCORE_CHARSET (状态 = MY_LEX_IDENT_SEP ) | 移动指针直到结束当前 token | 当前 token 以 _ 开头且字符集满足条件;当前 token 之后为 . ,且 . 的下一个字符是下一个 token 的一部分 | |
【结束】UNDERSCORE_CHARSET (状态不重置) | 移动指针直到结束当前 token | 当前 token 以 _ 开头且字符集满足条件;当前 token 之后不为 . ,或 . 的下一个字符不是下一个 token 的一部分 | |
【结束】IDENT_QUOTED 或 IDENT (状态 = MY_LEX_IDENT_SEP ) | 移动指针直到结束当前 token | 当前 token 不以 _ 开头或字符集不满足条件,且当前 token 不是函数或关键字;当前 token 之后为 . ,且 . 的下一个字符是下一个 token 的一部分 | |
【结束】IDENT_QUOTED 或 IDENT (状态不重置) | 移动指针直到结束当前 token | 当前 token 不以 _ 开头或字符集不满足条件,且当前 token 不是函数或关键字;当前 token 之后不为 . ,或 . 的下一个字符不是下一个 token 的一部分 | |
MY_LEX_IDENT_SEP (上一个 token 之后是 . 且 . 的下一个字符为下一个 token) | 【结束】. 的 ASCII 码值 46(状态 = MY_LEX_IDENT_START ) | 指针向后移动 1 个字节 | . 之后中的字符为 token 的一部分 |
【结束】. 的 ASCII 码值 46(状态 = MY_LEX_START ) | 指针向后移动 1 个字节 | . 之后中的字符不是 token 的一部分 | |
MY_LEX_NUMBER_IDENT (token 中只有数字,不确定 token 是否结束) | 【结束】HEX_NUM (状态不重置) | 移动指针直到结束十六进制数 | 0x 开头的十六进制数 |
【结束】BIN_NUM (状态不重置) | 移动指针直到结束二进制数 | 0b 开头的二进制数 | |
MY_LEX_IDENT_START (以数字开头但包含其他字符) | 不移动指针) | 0x 或 0b 开头但不是十六进制数或二进制数 | |
MY_LEX_INT_OR_REAL (token 中只有数字,且 token 已结束) | 移动指针直到结束数字 | 数字之后不是 SQL 字符 | |
【结束】FLOAT_NUM (状态不重置) | 移动指针直到结束浮点数 | 满足类似 1e+10 或 1e-10 形式的浮点数 | |
MY_LEX_IDENT_START (以数字开头但包含其他字符) | 移动指针直到结束数字 | 以数字开头但包含其他字符,且不是十六进制数、二进制数或浮点数(※) | |
MY_LEX_IDENT_START (以数字开头但包含其他字符) | 【结束】IDENT_QUOTED 或 IDENT (状态 = MY_LEX_IDENT_SEP ) | 移动指针直到结束当前 token | 当前 token 之后为 . ,且 . 的下一个字符是下一个 token 的一部分 |
【结束】IDENT_QUOTED 或 IDENT (状态不重置) | 移动指针直到结束当前 token | 当前 token 之后不为 . ,或 . 的下一个字符不是下一个 token 的一部分 | |
MY_LEX_USER_VARIABLE_DELIMITER (\x60 ) | 【结束】ABORT_SYM (状态不重置) | 移动指针直到 \x00 (结束符) | 找不到配对的引号 |
【结束】IDENT_QUOTED (状态 = MY_LEX_START ) | 移动指针到越过配对的引号 | 能够找到配对的引号 | |
MY_LEX_INT_OR_REAL (token 中只有数字,且 token 已结束) | 【结束】NUM 或 LONG_NUM 或 DECIMAL_NUM 或 ULONGLONG_NUM (状态不重置) | 不移动指针 | 当前字符不是 . |
MY_LEX_REAL (数字 + . 或 . ) | 不移动指针 | 当前字符是 . (※) | |
MY_LEX_REAL (数字 + . 或 . ) | MY_LEX_CHAR (token 中包含其他字符) | 移动指针直到结束数字 | 如果数字后为 e 或 E ,且 e 或 E 后不是数字 |
【结束】FLOAT_NUM (状态不重置) | 移动指针直到 e 之后的数字结束 | 如果数字后为 e 或 E ,且 e 或 E 后是数字 | |
【结束】DECIMAL_NUM (状态不重置) | 移动指针直到结束数字 | 数字后不是 e 或 E | |
MY_LEX_HEX_NUMBER (x' 或 X' ) | 【结束】ABORT_SYM (状态不重置) | 移动指针直到十六进制数结束 | 遍历所有十六进制数后,找不到配对的单引号或十六进制数为奇数个 |
【结束】HEX_NUM (状态不重置) | 移动指针直到十六进制数结束 | 遍历所有十六进制数后,能够找到配对的单引号且十六进制数为偶数个 | |
MY_LEX_BIN_NUMBER (b' 或 B' ) | 【结束】ABORT_SYM (状态不重置) | 移动指针直到二进制数结束 | 遍历所有二进制数后,找不到配对的单引号 |
【结束】BIN_NUM (状态不重置) | 移动指针直到二进制数结束 | 遍历所有二进制数后,找得到配对的单引号 | |
MY_LEX_CMP_OP (> 或 = 或 ! ) | 【结束】token 关键字的编码(状态 = MY_LEX_START ) | 如果当前字符是运算符则移动指针 | 当前 token 是关键字 |
MY_LEX_CHAR (token 中包含其他字符) | 如果当前字符是运算符则移动指针 | 当前 token 不是关键字 | |
``MY_LEX_LONG_CMP_OP( <`) | 【结束】当前 token 关键字的编码(状态 = MY_LEX_START ) | 如果当前字符和下一个字符为运算符,则将指针移动 1 个或 2 个字符 | 当前 token 是关键字 |
MY_LEX_CHAR (token 中包含其他字符) | 如果当前字符和下一个字符为运算符,则将指针移动 1 个或 2 个字符 | 当前 token 不是关键字 | |
MY_LEX_BOOL (& 或 ` | `) | MY_LEX_CHAR (token 中包含其他字符) | 不移动指针 |
【结束】当前 token 关键字的编码(状态 = MY_LEX_START ) | 指针向后移动 1 个字符 | & 和 ` | |
MY_LEX_STRING_OR_DELIMITER (" ) | MY_LEX_USER_VARIABLE_DELIMITER (\x60 ) | 不移动指针 | 当前线程 sql_mode 开启 MODE_ANSI_QUOTES |
MY_LEX_STRING (' ) | 不移动指针 | 当前线程 sql_mode 未开启 MODE_ANSI_QUOTES (※) | |
MY_LEX_STRING (' ) | 【结束】TEXT_STRING (状态不重置) | 移动指针到越过配对的引号 | 能够找到配对的引号 |
MY_LEX_CHAR (token 中包含其他字符) | 不移动指针 | 找不到配对的引号 | |
MY_LEX_COMMENT (# 或 -- ) | MY_LEX_START (通用开始状态) | 向后移动指针直到 \n 或 \x00 | - |
MY_LEX_LONG_COMMENT (/ ) | MY_LEX_CHAR (token 中包含其他字符) | 不移动指针 | 当前字符不是 * |
MY_LEX_START (通用开始状态) | 向后移动指针越过 */ | 当前字符是 * ,且能够找到配对的 */ | |
【结束】ABORT_SYM (状态不重置) | - | 当前字符是 * ,但找不到配对的 */ | |
MY_LEX_END_LONG_COMMENT (* ) | MY_LEX_START (通用开始状态) | 向后移动指针越过 */ | 当前在注释中且下一个字符是 / |
MY_LEX_CHAR (token 中包含其他字符) | 不移动指针 | 当前不在注释中或下一个字符不是 / | |
MY_LEX_SET_VAR (: ) | 【结束】SET_VAR (状态不重置) | 指针向后移动 1 个字符 | 当前字符是 = |
MY_LEX_CHAR (token 中包含其他字符) | 不移动指针 | 当前字符不是 = | |
MY_LEX_SEMICOLON (; ) | MY_LEX_CHAR (token 中包含其他字符) | 不移动指针 | - |
MY_LEX_EOL (\x00 ) | 【结束】END_OF_INPUT (状态 = MY_LEX_END ) | 不移动指针 | 原始数据流已遍历结束,且不在注释中 |
【结束】ABORT_SYM | 不移动指针 | 原始数据流已遍历结束,且在注释中 | |
MY_LEX_CHAR (token 中包含其他字符) | 不移动指针 | 原始数据流没有遍历结束 | |
MY_LEX_END (当前语句已经结束) | 【结束】0(状态 = MY_LEX_END ) | 不移动指针 | - |
MY_LEX_REAL_OR_POINT (. ) | MY_LEX_REAL (数字 + . 或 . ) | 不移动指针 | 当前字符为数字 |
MY_LEX_IDENT_SEP (上一个 token 之后是 . 且 . 的下一个字符为下一个 token) | 指针向前移动 1 个字符 | 当前字符不是数字 | |
MY_LEX_USER_END (@ ) | 【结束】@ 的 ASCII 码值 64(状态不重置) | 不移动指针 | 当前字符是单引号、标识符引号或双引号 |
【结束】@ 的 ASCII 码值 64(状态 = MY_LEX_SYSTEM_VAR ) | 不移动指针 | 当前字符是 @ | |
【结束】@ 的 ASCII 码值 64(状态 = MY_LEX_HOSTNAME ) | 不移动指针 | 当前字符不是单引号、标识符引号、双引号或 @ | |
MY_LEX_HOSTNAME (上一个 token 是 @ ) | 【结束】LEX_HOSTNAME (状态不重置) | 将指针移动到结束当前 token | - |
MY_LEX_SYSTEM_VAR (上一个 token 是 @ ,且当前 token 是 @ ) | 【结束】@ 的 ASCII 码值 64(状态 = MY_LEX_START ) | 将指针向后移动 1 个字符越过 @ | 当前字符为 \x60 (标识符引号) |
【结束】@ 的 ASCII 码值 64(状态 = MY_LEX_IDENT_OR_KEYWORD ) | 将指针向后移动 1 个字符越过 @ | 当前字符不是 \x60 (标识符引号) | |
MY_LEX_IDENT_OR_KEYWORD (之前的 token 是 @@ ,且当前 token 是不是 \x60 ) | 【结束】当前关键字的码值(状态 = MY_LEX_IDENT_SEP ) | 将指针移动到结束当前 token | 当前 token 之后的字符为 . ,且 token 中为关键字 |
【结束】IDENT_QUOTED 或 IDENT (状态 = MY_LEX_IDENT_SEP ) | 将指针移动到结束当前 token | 当前 token 之后的字符为 . ,且 token 中不为关键字 | |
【结束】当前关键字的码值(状态不重置) | 将指针移动到结束当前 token | 当前 token 之后的字符不为 . ,且 token 中为关键字 | |
【结束】IDENT_QUOTED 或 IDENT (状态不重置) | 将指针移动到结束当前 token | 当前 token 之后的字符不为 . ,且 token 中不为关键字 |