热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

Mysql源码学习――词法分析MYSQLlex_MySQL

Mysql源码学习――词法分析MYSQLlex
bitsCN.com

词法分析MYSQLlex

客户端向服务器发送过来SQL语句后,服务器首先要进行词法分析,而后进行语法分析,语义分析,构造执行树,生成执行计划。词法分析是第一阶段,虽然在理解Mysql实现上意义不是很大,但作为基础还是学习下比较好。

词法分析即将输入的语句进行分词(token),解析出每个token的意义。分词的本质便是正则表达式的匹配过程,比较流行的分词工具应该是lex,通过简单的规则制定,来实现分词。Lex一般和yacc结合使用。关于lex和yacc的基础知识请参考Yacc 与Lex 快速入门- IBM。如果想深入学习的话,可以看下《LEX与YACC》。

然而Mysql并没有使用lex来实现词法分析,但是语法分析却用了yacc,而yacc需要词法分析函数yylex,故在sql_yacc.cc文件最前面我们可以看到如下的宏定义:

/* Substitute the variable and function names. */

#define yyparse MYSQLparse

#define yylex MYSQLlex

  这里的MYSQLlex也就是本文的重点,即MYSQL自己的词法分析程序。源码版本5.1.48。源码太长,贴不上来,算啦..在sql_lex.cc里面。

  我们第一次进入词法分析,state默认值为MY_LEX_START,就是开始状态了,其实state的宏的意义可以从名称上猜个差不多,再比如MY_LEX_IDEN便是标识符。对START状态的处理伪代码如下:

case MY_LEX_START:

{

Skip空格

获取第一个有效字符c

state = state_map[c];

Break;

}

  我困惑了,这尼玛肿么出来个state_map?找到了在函数开始出有个赋值的地方:

uchar *state_map= cs->state_map;

  cs?!不会是反恐精英吧!!快速监视下cs为my_charset_latin1,哥了然了,原来cs是latin字符集,character set的缩写吧。那么为神马state_map可以直接决定状态?找到其赋值的地方,在init_state_maps函数中,代码如下所示:

/* Fill state_map with states to get a faster parser */

for (i=0; i <256 ; i++)

{

if (my_isalpha(cs,i))

state_map[i]=(uchar) MY_LEX_IDENT;

else if (my_isdigit(cs,i))

state_map[i]=(uchar) MY_LEX_NUMBER_IDENT;

#if defined(USE_MB) && defined(USE_MB_IDENT)

else if (my_mbcharlen(cs, i)>1)

state_map[i]=(uchar) MY_LEX_IDENT;

#endif

else if (my_isspace(cs,i))

state_map[i]=(uchar) MY_LEX_SKIP;

else

state_map[i]=(uchar) MY_LEX_CHAR;

}

state_map[(uchar)&#39;_&#39;]=state_map[(uchar)&#39;$&#39;]=(uchar) MY_LEX_IDENT;

state_map[(uchar)&#39;/&#39;&#39;]=(uchar) MY_LEX_STRING;

state_map[(uchar)&#39;.&#39;]=(uchar) MY_LEX_REAL_OR_POINT;

state_map[(uchar)&#39;>&#39;]=state_map[(uchar)&#39;=&#39;]=state_map[(uchar)&#39;!&#39;]= (uchar) MY_LEX_CMP_OP;

state_map[(uchar)&#39;<&#39;]= (uchar) MY_LEX_LONG_CMP_OP;

state_map[(uchar)&#39;&&#39;]=state_map[(uchar)&#39;|&#39;]=(uchar) MY_LEX_BOOL;

state_map[(uchar)&#39;#&#39;]=(uchar) MY_LEX_COMMENT;

state_map[(uchar)&#39;;&#39;]=(uchar) MY_LEX_SEMICOLON;

state_map[(uchar)&#39;:&#39;]=(uchar) MY_LEX_SET_VAR;

state_map[0]=(uchar) MY_LEX_EOL;

state_map[(uchar)&#39;//&#39;]= (uchar) MY_LEX_ESCAPE;

state_map[(uchar)&#39;/&#39;]= (uchar) MY_LEX_LONG_COMMENT;

state_map[(uchar)&#39;*&#39;]= (uchar) MY_LEX_END_LONG_COMMENT;

state_map[(uchar)&#39;@&#39;]= (uchar) MY_LEX_USER_END;

state_map[(uchar) &#39;`&#39;]= (uchar) MY_LEX_USER_VARIABLE_DELIMITER;

state_map[(uchar)&#39;"&#39;]= (uchar) MY_LEX_STRING_OR_DELIMITER;

  先来看这个for循环,256应该是256个字符了,每个字符的处理应该如下规则:如果是字母,则state = MY_LEX_IDENT;如果是数字,则state = MY_LEX_NUMBER_IDENT,如果是空格,则state = MY_LEX_SKIP,剩下的全为MY_LEX_CHAR。 

for循环之后,又对一些特殊字符进行了处理,由于我们的语句“select @@version_comment limit 1”中有个特殊字符@,这个字符的state进行了特殊处理,为MY_LEX_USER_END。

对于my_isalpha等这几个函数是如何进行判断一个字符属于什么范畴的呢?跟进去看下,发现是宏定义:

#define my_isalpha(s, c) (((s)->ctype+1)[(uchar) (c)] & (_MY_U | _MY_L))

Wtf,肿么又来了个ctype,c作为ctype的下标,_MY_U | _MY_L如下所示,

#define _MY_U 01 /* Upper case */

#define _MY_L 02 /* Lower case */

  ctype里面到底存放了什么?在ctype-latin1.c源文件里面,我们找到了my_charset_latin1字符集的初始值:

CHARSET_INFO my_charset_latin1=

{

8,0,0, /* number */

MY_CS_COMPILED | MY_CS_PRIMARY, /* state */

"latin1", /* cs name */

"latin1_swedish_ci", /* name */

"", /* comment */

NULL, /* tailoring */

ctype_latin1,

to_lower_latin1,

to_upper_latin1,

sort_order_latin1,

NULL, /* contractions */

NULL, /* sort_order_big*/

cs_to_uni, /* tab_to_uni */

NULL, /* tab_from_uni */

my_unicase_default, /* caseinfo */

NULL, /* state_map */

NULL, /* ident_map */

1, /* strxfrm_multiply */

1, /* caseup_multiply */

1, /* casedn_multiply */

1, /* mbminlen */

1, /* mbmaxlen */

0, /* min_sort_char */

255, /* max_sort_char */

&#39; &#39;, /* pad char */

0, /* escape_with_backslash_is_dangerous */

&my_charset_handler,

&my_collation_8bit_simple_ci_handler

};

  可以看出ctype = ctype_latin1;而ctype_latin1值为:

static uchar ctype_latin1[] = {

0,

32, 32, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 32, 32,

32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,

72, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,

132,132,132,132,132,132,132,132,132,132, 16, 16, 16, 16, 16, 16,

16,129,129,129,129,129,129, 1, 1, 1, 1, 1, 1, 1, 1, 1,

1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 16, 16, 16, 16,

16,130,130,130,130,130,130, 2, 2, 2, 2, 2, 2, 2, 2, 2,

2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 16, 16, 16, 16, 32,

16, 0, 16, 2, 16, 16, 16, 16, 16, 16, 1, 16, 1, 0, 1, 0,

0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 2, 16, 2, 0, 2, 1,

72, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,

16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,

1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,

1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 2,

2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,

2, 2, 2, 2, 2, 2, 2, 16, 2, 2, 2, 2, 2, 2, 2, 2

};

  看到这里哥再一次了然了,这些值都是经过预计算的,第一个0是无效的,这也是为什么my_isalpha(s, c)定义里面ctype要先+1的原因。通过_MY_U和_MY_L的定义,可以知道,这些值肯定是按照相应的ASCII码的具体意义进行置位的。比如字符&#39;A&#39;,其ASCII码为65,其实大写字母,故必然具有_MY_U,即第0位必然为1,找到ctype里面第66个(略过第一个无意义的0)元素,为129 = 10000001,显然第0位为1(右边起),说明为大写字母。写代码的人确实比较牛X,如此运用位,哥估计这辈子也想不到了,小小佩服下。State的问题点到为止了。

继续进行词法分析,第一个字母为s,其state = MY_LEX_IDENT(IDENTIFIER:标识符的意思),break出来,继续循环,case进入MY_LEX_IDENT分支:

Case MY_LEX_IDENT:

{

由s开始读,直到空格为止

If(读入的单词为关键字)

{

nextstate = MY_LEX_START;

Return tokval; //关键字的唯一标识

}

Else

{

return IDENT_QUOTED 或者IDENT;表示为一般标识符

}

}

  这里SELECT肯定为关键字,至于为什么呢?下节的语法分析会讲。

解析完SELECT后,需要解析@@version_comment,第一个字符为@,进入START分支,state = MY_LEX_USER_END;

进入MY_LEX_USER_END分支,如下:

case MY_LEX_USER_END: // end &#39;@&#39; of user@hostname

switch (state_map[lip->yyPeek()]) {

case MY_LEX_STRING:

case MY_LEX_USER_VARIABLE_DELIMITER:

case MY_LEX_STRING_OR_DELIMITER:

break;

case MY_LEX_USER_END:

lip->next_state=MY_LEX_SYSTEM_VAR;

break;

default:

lip->next_state=MY_LEX_HOSTNAME;

break;

  哥会心的笑了,两个@符号就是系统变量吧~~,下面进入MY_LEX_SYSTEM_VAR分支

case MY_LEX_SYSTEM_VAR:

yylval->lex_str.str=(char*) lip->get_ptr();

yylval->lex_str.length=1;

lip->yySkip(); // Skip &#39;@&#39;

lip->next_state= (state_map[lip->yyPeek()] ==

MY_LEX_USER_VARIABLE_DELIMITER ?

MY_LEX_OPERATOR_OR_IDENT :

MY_LEX_IDENT_OR_KEYWORD);

return((int) &#39;@&#39;);

  所作的操作是略过@,next_state设置为MY_LEX_IDENT_OR_KEYWORD,再之后便是解析MY_LEX_IDENT_OR_KEYWORD了,也就是version_comment了,此解析应该和SELECT解析路径一致,但不是KEYWORD。剩下的留给有心的读者了(想起了歌手经常说的一句话:大家一起来,哈哈)。

Mysql的词法解析的状态还是比较多的,如果细究还是需要点时间的,但这不是Mysql的重点,我就浅尝辄止了。下节会针对上面的SQL语句讲解下语法分析。

PS: 一直想好好学习下Mysql,总是被这样或那样的事耽误,当然都是自己的原因,希望这次能走的远点.....

PS again:本文只代表本人的学习感悟,如有异议,欢迎指正。



摘自 心中无码 bitsCN.com
推荐阅读
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 推荐一个ASP的内容管理框架(ASP Nuke)的优势和适用场景
    本文推荐了一个ASP的内容管理框架ASP Nuke,并介绍了其主要功能和特点。ASP Nuke支持文章新闻管理、投票、论坛等主要内容,并可以自定义模块。最新版本为0.8,虽然目前仍处于Alpha状态,但作者表示会继续更新完善。文章还分析了使用ASP的原因,包括ASP相对较小、易于部署和较简单等优势,适用于建立门户、网站的组织和小公司等场景。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 本文介绍了如何在MySQL中将零值替换为先前的非零值的方法,包括使用内联查询和更新查询。同时还提供了选择正确值的方法。 ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • 在数据分析工作中,我们通常会遇到这样的问题,一个业务部门由若干业务组构成,需要筛选出每个业务组里业绩前N名的业务员。这其实是一个分组排序的 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • Oracle Database 10g许可授予信息及高级功能详解
    本文介绍了Oracle Database 10g许可授予信息及其中的高级功能,包括数据库优化数据包、SQL访问指导、SQL优化指导、SQL优化集和重组对象。同时提供了详细说明,指导用户在Oracle Database 10g中如何使用这些功能。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
author-avatar
月芽2502915393
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有