MySQL 源码|词法解析:状态及状态转移规则(3)
源码位置:(版本 = MySQL 8.0.37)
前置文档:
- MySQL 源码|词法解析的状态存储器(Lex_input_stream)的主要数据成员与函数
- MySQL 源码|词法解析中的 CHARSET_INFO 结构体及衍生函数
- MySQL 源码|词法解析:状态及状态转移规则(1)
- MySQL 源码|词法解析:状态及状态转移规则(2)
MY_LEX_IDENT
:普通语法元素
首先,来看 switch
语法块。这个语法块,用于在存在多字节字符的字符集中,获取当前的一个单字节字符或多字节字符。具体地:
- 调用
my_mbcharlen(cs, lip>yyGetLast())
获取指针指向的前一个字符,判断该字符是否是多字节字符的开始字节- 如果不是的话,即
case = 1
,或case 0
且my_mbmaxlenlen(cs) < 2
(不是 GB18030 编码),则不做额外处理 - 否则,判断从指针指向的前一个字符开始的是否为多字节序列:如果不是,则将状态置为
MY_LEX_CHAR
(普通字符);如果是,则将这个多字节序列一起处理。
- 如果不是的话,即
switch (my_mbcharlen(cs, lip->yyGetLast())) {
case 1:
break;
case 0:
if (my_mbmaxlenlen(cs) < 2) break;
[[fallthrough]];
default:
int l =
my_ismbchar(cs, lip->get_ptr() - 1, lip->get_end_of_query());
if (l == 0) {
state = MY_LEX_CHAR;
continue;
}
lip->skip_binary(l - 1);
}
下面,我们来看处理 MY_LEX_INDENT
状态的逻辑:
Step 1|调用 use_mb(cs)
判断当前字符集是否包含多字节字符。如果包含多字节字符,则使用刚才提到的 switch
语法块不断遍历字符,并使用 ident_map
映射判断当前指针位置的字符是否为标识符、数字、特殊字符的一部分,直到进入 SQL 的其他部分(即当前 token 结束)。如果不包含多字节字符,则直接逐字节遍历,并使用 ident_map
映射判断是否进入 SQL 的其他部分,直到当前 token 已经结束。
const char *start;
if (use_mb(cs)) {
result_state = IDENT_QUOTED;
switch (my_mbcharlen(cs, lip->yyGetLast())) {
case 1:
break;
case 0:
if (my_mbmaxlenlen(cs) < 2) break;
[[fallthrough]];
default:
int l =
my_ismbchar(cs, lip->get_ptr() - 1, lip->get_end_of_query());
if (l == 0) {
state = MY_LEX_CHAR;
continue;
}
lip->skip_binary(l - 1);
}
while (ident_map[c = lip->yyGet()]) {
switch (my_mbcharlen(cs, c)) {
case 1:
break;
case 0:
if (my_mbmaxlenlen(cs) < 2) break;
[[fallthrough]];
default:
int l;
if ((l = my_ismbchar(cs, lip->get_ptr() - 1,
lip->get_end_of_query())) == 0)
break;
lip->skip_binary(l - 1);
}
}
} else {
for (result_state = c; ident_map[c = lip->yyGet()]; result_state |= c)
;
/* If there were non-ASCII characters, mark that we must convert */
result_state = result_state & 0x80 ? IDENT_QUOTED : IDENT;
}
Step 2|调用 yyLength()
获取当前 token 在原始数据流中的长度,调用 get_ptr()
获取指向原始输入流中的当前位置的指针。
length = lip->yyLength();
start = lip->get_ptr();
Step 3|如果是 SQL_MODE
为 IGNORE_SPACE
(即 lip->ignore_space
为 true),则跳过当前指针之后的空格和换行。
if (lip->ignore_space) {
/*
If we find a space then this can't be an identifier. We notice this
below by checking start != lex->ptr.
*/
for (; state_map[c] == MY_LEX_SKIP; c = lip->yyGet()) {
if (c == '\n') lip->yylineno++;
}
}
Step 4|如果当前指针位置的元素为 .
,且指针位置的下一个元素是有效 token 的一部分,则将状态置为 MY_LEX_IDENT_SEP
(在 .
之后)。否则,先将指针向前移动 1 个字符,然后判断上一个 token 是否为关键字或函数,如果指针指向的位置是 (
,则检查关键字和函数,否则只检查关键字;如果是函数或关键字,则将状态改为 MY_LEX_START
(等待下一个 token),并返回函数或关键字的编码,否则将指针向后移动 1 个字符,并继续处理当前 token。
【函数含义】
int find_keyword(Lex_input_stream *lip, uint len, bool function)
:如果function
为 true,则判断lip
中的当前 token 是否为关键字或函数;如果function
为 false,则判断lip
中的当前 token 是否为关键字。如果是函数或关键字,则返回关键字或函数的编码,否则返回 0。
if (start == lip->get_ptr() && c == '.' && ident_map[lip->yyPeek()])
lip->next_state = MY_LEX_IDENT_SEP;
else { // '(' must follow directly if function
lip->yyUnget();
if ((tokval = find_keyword(lip, length, c == '('))) {
lip->next_state = MY_LEX_START; // Allow signed numbers
return (tokval); // Was keyword
}
lip->yySkip(); // next state does a unget
}
Step 5|获取当前 token 的字符串
yylval->lex_str = get_token(lip, 0, length);
Step 6|处理当前 token 的开始字符为 _
的情况,如果找匹配到字符集,则返回 UNDERSCORE_CHARSET
(852)
/*
Note: "SELECT _bla AS 'alias'"
_bla should be considered as a IDENT if charset haven't been found.
So we don't use MYF(MY_WME) with get_charset_by_csname to avoid
producing an error.
*/
if (yylval->lex_str.str[0] == '_') {
auto charset_name = yylval->lex_str.str + 1;
const CHARSET_INFO *underscore_cs =
get_charset_by_csname(charset_name, MY_CS_PRIMARY, MYF(0));
if (underscore_cs) {
lip->warn_on_deprecated_charset(underscore_cs, charset_name);
if (underscore_cs == &my_charset_utf8mb4_0900_ai_ci) {
/*
If underscore_cs is utf8mb4, and the collation of underscore_cs
is the default collation of utf8mb4, then update underscore_cs
with a value of the default_collation_for_utf8mb4 system
variable:
*/
underscore_cs = thd->variables.default_collation_for_utf8mb4;
}
yylval->charset = underscore_cs;
lip->m_underscore_cs = underscore_cs;
lip->body_utf8_append(lip->m_cpp_text_start,
lip->get_cpp_tok_start() + length);
return (UNDERSCORE_CHARSET);
}
}
Step 7|调用 body_utf8_append()
将预处理数据流复制到 UTF-8 格式数据流中,调用 body_utf8_append_literal
将特殊字符转化为 UTF-8 格式并添加到 UTF-8 格式数据流中。
lip->body_utf8_append(lip->m_cpp_text_start);
lip->body_utf8_append_literal(thd, &yylval->lex_str, cs,
lip->m_cpp_text_end);
Step 8|返回 IDENT_QUOTED
(484)或 IDENT
(482)
涉及的状态转移规则如下:
当前状态 | 字符类型 | 变更状态 |
---|---|---|
MY_LEX_IDENT | 当前指针位置为 . ,下一个字符为 token 的一部分 | 状态 = MY_LEX_IDENT_SEP |
当前 token 为函数或关键字 | 状态 = MY_LEX_START ;返回 = 关键字对应的编码 | |
当前 token 以 _ 开头 | 【返回】UNDERSCORE_CHARSET (852) | |
其他 | 【返回】IDENT_QUOTED (484)或 IDENT (482) |