MySQL 源码|15 - 词法解析:状态及状态转移规则(3)

MySQL 源码|词法解析:状态及状态转移规则(3)

源码位置:(版本 = MySQL 8.0.37)

前置文档:

MY_LEX_IDENT:普通语法元素

首先,来看 switch 语法块。这个语法块,用于在存在多字节字符的字符集中,获取当前的一个单字节字符或多字节字符。具体地:

  • 调用 my_mbcharlen(cs, lip>yyGetLast()) 获取指针指向的前一个字符,判断该字符是否是多字节字符的开始字节
    • 如果不是的话,即 case = 1,或 case 0my_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_MODEIGNORE_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)
  • 36
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值