热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Mysql源码学习――打造专属语法_MySQL

Mysql源码学习――打造专属语法
bitsCN.com

语法分析——YACC

接触过SQL语句的人都会看过这家或者那家的SQL手册,其语法标准应该是从SQL92开始吧,在看SQL92标准的时候,你会发现里面定义的都是一些巴科斯范式(BNF),就是一种语法定义的标准。不管是牛X哄哄的ORACLE,还是不幸被其收购的Mysql,都会遵循里面的标准语法,当然一些扩展的语法除外,比如今天我们就会扩展一个简单的语法^-^。

OK,大家知道了SQL语法的来源,那么如何进行语法解析呢?YACC!!(Yet Another Compiler Compiler),它的书写方式便是BNF,语法解析的利器。YACC接收来自词法分析阶段分解出来的token,然后去匹配那些BNF。今天哥就来揭开它的面纱。(关于YACC的基本使用方法,大家可以看我上一篇中提到IBM的链接,一定要看懂那个先)

继续上一节的语句SELECT @@VERSION_COMMET,为了简单,这里省去后缀limit 1。Mysql的语法文件是sql_yacc.yy,首先给出这条语句涉及到的语法节点(大体浏览下即可):

?

query:

END_OF_INPUT

{...}

|| verb_clause

{...}

| verb_clause END_OF_INPUT

{

/* Single query, not terminated. */

YYLIP->found_semicolon= NULL;

}

verb_clause:

statement

| begin

;

statement:

alter

| analyze

| backup

| binlog_base64_event

| call

| change

| check

| checksum

| commit

| create

| deallocate

| delete

| describe

| do

| drop

| execute

| flush

| grant

| handler

| help

| insert

| install

| kill

| load

| lock

| optimize

| keycache

| partition_entry

| preload

| prepare

| purge

| release

| rename

| repair

| replace

| reset

| restore

| revoke

| rollback

| savepoint

| select

| set

| show

| slave

| start

| truncate

| uninstall

| unlock

| update

| use

| xa

;

select:

select_init

{

LEX *lex= Lex;

lex->sql_command= SQLCOM_SELECT;

}

;

select_init:

SELECT_SYM select_init2

| '(' select_paren ')' union_opt

;

select_init2:

select_part2

{

LEX *lex= Lex;

SELECT_LEX * sel= lex->current_select;

if (lex->current_select->set_braces(0))

{

my_parse_error(ER(ER_SYNTAX_ERROR));

MYSQL_YYABORT;

}

if (sel->linkage == UNION_TYPE &&

sel->master_unit()->first_select()->braces)

{

my_parse_error(ER(ER_SYNTAX_ERROR));

MYSQL_YYABORT;

}

}

union_clause

;

select_part2:

{

LEX *lex= Lex;

SELECT_LEX *sel= lex->current_select;

if (sel->linkage != UNION_TYPE)

mysql_init_select(lex);

lex->current_select->parsing_place= SELECT_LIST;

}

select_options select_item_list

{

Select->parsing_place= NO_MATTER;

}

select_into select_lock_type

;

?

select_item_list:

select_item_list ',' select_item

| select_item

| '*'

{

THD *thd= YYTHD;

Item *item= new (thd->mem_root)

Item_field(&thd->lex->current_select->context,

NULL, NULL, "*");

if (item == NULL)

MYSQL_YYABORT;

if (add_item_to_list(thd, item))

MYSQL_YYABORT;

(thd->lex->current_select->with_wild)++;

}

;

select_item:

remember_name select_item2 remember_end select_alias

{

THD *thd= YYTHD;

DBUG_ASSERT($1 <$3);

if (add_item_to_list(thd, $2))

MYSQL_YYABORT;

if ($4.str)

{

if (Lex->sql_command == SQLCOM_CREATE_VIEW &&

check_column_name($4.str))

{

my_error(ER_WRONG_COLUMN_NAME, MYF(0), $4.str);

MYSQL_YYABORT;

}

$2->is_autogenerated_name= FALSE;

$2->set_name($4.str, $4.length, system_charset_info);

}

else if (!$2->name)

{

$2->set_name($1, (uint) ($3 - $1), thd->charset());

}

}

;

variable:

&#39;@&#39;

{

if (! Lex->parsing_options.allows_variable)

{

my_error(ER_VIEW_SELECT_VARIABLE, MYF(0));

MYSQL_YYABORT;

}

}

variable_aux

{

$$= $3;

}

;

variable_aux:

ident_or_text SET_VAR expr

{

Item_func_set_user_var *item;

$$= item= new (YYTHD->mem_root) Item_func_set_user_var($1, $3);

if ($$ == NULL)

MYSQL_YYABORT;

LEX *lex= Lex;

lex->uncacheable(UNCACHEABLE_RAND);

lex->set_var_list.push_back(item);

}

| ident_or_text

{

$$= new (YYTHD->mem_root) Item_func_get_user_var($1);

if ($$ == NULL)

MYSQL_YYABORT;

LEX *lex= Lex;

lex->uncacheable(UNCACHEABLE_RAND);

}

| &#39;@&#39; opt_var_ident_type ident_or_text opt_component

{

/* disallow "SELECT @@global.global.variable" */

if ($3.str && $4.str && check_reserved_words(&$3))

{

my_parse_error(ER(ER_SYNTAX_ERROR));

MYSQL_YYABORT;

}

if (!($$= get_system_var(YYTHD, $2, $3, $4)))

MYSQL_YYABORT;

if (!((Item_func_get_system_var*) $$)->is_written_to_binlog())

Lex->set_stmt_unsafe();

}

;

下面我们仔细的来看一下整个SELECT语法节点的执行流程:

?

query->verb_clause->statement->select->select_init->select_init2->select_part2->select_item_list->select_item…->variable

语法是自上而下的,实际的解析过程是自下而上的匹配过程。词法分析首先yacc送来SELECT关键字,上一节说过为什么SELECT是关键字呢?

我们看下sql_yacc.yy,可以找到如下一个定义:

?

%token SELECT_SYM /* SQL-2003-R */

这里其实是定义了一个宏SELECT_SYM,代表一个关键字,宏定义如下:

?

#define SELECT_SYM 687

那么字符串"SELECT"和SELECT_SYM是如何联系在一起的呢?我们回头看下MYSQLlex中的find_keyword这个函数:

?

static int find_keyword(Lex_input_stream *lip, uint len, bool function)

{

const char *tok= lip->get_tok_start();

SYMBOL *symbol= get_hash_symbol(tok, len, function);

if (symbol)

{

lip->yylval->symbol.symbol=symbol;

lip->yylval->symbol.str= (char*) tok;

lip->yylval->symbol.length=len;

if ((symbol->tok == NOT_SYM) &&

(lip->m_thd->variables.sql_mode & MODE_HIGH_NOT_PRECEDENCE))

return NOT2_SYM;

if ((symbol->tok == OR_OR_SYM) &&

!(lip->m_thd->variables.sql_mode & MODE_PIPES_AS_CONCAT))

return OR2_SYM;

return symbol->tok;

}

return 0;

}

static SYMBOL *get_hash_symbol(const char *s,

unsigned int len,bool function)

{

register uchar *hash_map;

register const char *cur_str= s;

if (len == 0) {

DBUG_PRINT("warning", ("get_hash_symbol() received a request for a zero-length symbol, which is probably a mistake."));

?

return(NULL);

}

if (function){

if (len>sql_functions_max_len) return 0;

hash_map= sql_functions_map;

register uint32 cur_struct= uint4korr(hash_map+((len-1)*4));

for (;;){

register uchar first_char= (uchar)cur_struct;

if (first_char == 0)

{

register int16 ires= (int16)(cur_struct>>16);

if (ires==array_elements(symbols)) return 0;

register SYMBOL *res;

if (ires>=0)

res= symbols+ires;

else

res= sql_functions-ires-1;

register uint count= (uint) (cur_str - s);

return lex_casecmp(cur_str,res->name+count,len-count) ? 0 : res;

}

register uchar cur_char= (uchar)to_upper_lex[(uchar)*cur_str];

if (cur_char

cur_struct>>=8;

if (cur_char>(uchar)cur_struct) return 0;

cur_struct>>=8;

cur_struct= uint4korr(hash_map+

(((uint16)cur_struct + cur_char - first_char)*4));

cur_str++;

}

}else{

if (len>symbols_max_len) return 0;

hash_map= symbols_map;

register uint32 cur_struct= uint4korr(hash_map+((len-1)*4));

for (;;){

register uchar first_char= (uchar)cur_struct;

if (first_char==0){

register int16 ires= (int16)(cur_struct>>16);

if (ires==array_elements(symbols)) return 0;

register SYMBOL *res= symbols+ires;

register uint count= (uint) (cur_str - s);

return lex_casecmp(cur_str,res->name+count,len-count)!=0 ? 0 : res;

}

register uchar cur_char= (uchar)to_upper_lex[(uchar)*cur_str];

if (cur_char

cur_struct>>=8;

if (cur_char>(uchar)cur_struct) return 0;

cur_struct>>=8;

cur_struct= uint4korr(hash_map+

(((uint16)cur_struct + cur_char - first_char)*4));

cur_str++;

}

}

}

其中的get_hash_symbol便是去系统中查找关键字,第三个参数function代表是否去查找系统函数,我们这里是系统变量,不是函数,故为FALSE。所有的关键字都挂在了hash_map上,即symbols_map上。symbols_maps又是一堆处理过的数据:

?

static uchar symbols_map[11828]= {

&#39;<&#39;, &#39;>&#39;, 29, 0,

&#39;!&#39;, &#39;|&#39;, 32, 0,

&#39;<&#39;, &#39;X&#39;, 150, 0,

&#39;B&#39;, &#39;Y&#39;, 11, 1,

&#39;A&#39;, &#39;W&#39;, 147, 2,

&#39;A&#39;, &#39;V&#39;, 0, 4,

...

看一下这个文件的最上面的注释吧,看看有啥有用的信息,果然被找到了:

?

1

2

/* Do not edit this file! This is generated by gen_lex_hash.cc

that seeks for a perfect hash function */

看到了这个注释,心中豁然开朗,原来lex_hash.h是由gen_lex_hash.cc进行生成的,大家千万不要自己进行编辑此文件啊!!

来gen_lex_hash.cc看下吧,看到了个main函数,里面是一些生成文件的操作,在generate_find_structs函数中找到了insert_symbols,

这应该是初始化我们的symbols_map数组了吧。

?

void insert_symbols()

{

size_t i= 0;

SYMBOL *cur;

for (cur= symbols; i

hash_lex_struct *root=

get_hash_struct_by_len(&root_by_len,cur->length,&max_len);

insert_into_hash(root,cur->name,0,(uint) i,0);

}

}

看到函数的实现是循环取数组symbols,找到symbols定义,在文件lex.h中,看到这个数组,我想大家就会了然了:

?

1

{ "SELECT", SYM(SELECT_SYM)},

这就是将SELECT字符串与SELECT_SYM关联的地方了,bingo!

我们再来捋一下SELECT解析的思路,词法分析解析到SELECT后,执行find_keyword去找是否是关键字,发现SELECT是关键字,

于是给yacc返回SELECT_SYM用于语法分析。note:如果我们想要加关键字,只需在sql_yacc.yy上面添加一个%token xxx,

然后在lex.h里面加入相应的字符串和SYM的对应即可。

下面看下@@version_comment这个系统变量如何解析的,首先给出其语法节点:

?

variable_aux:

...

| &#39;@&#39; opt_var_ident_type ident_or_text opt_component

{

/* disallow "SELECT @@global.global.variable" */

if ($3.str && $4.str && check_reserved_words(&$3))

{

my_parse_error(ER(ER_SYNTAX_ERROR));

MYSQL_YYABORT;

}

if (!($$= get_system_var(YYTHD, $2, $3, $4)))

MYSQL_YYABORT;

if (!((Item_func_get_system_var*) $$)->is_written_to_binlog())

Lex->set_stmt_unsafe();

}

;

这里便是查找系统变量的地方了:get_system_var,我们跟进去看下:

?

Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name,

LEX_STRING component)

{

sys_var *var;

LEX_STRING *base_name, *component_name;

if (component.str)

{

base_name= &component;

component_name= &name;

}

else

{

base_name= &name;

component_name= &component; // Empty string

}

if (!(var= find_sys_var(thd, base_name->str, base_name->length)))

return 0;

if (component.str)

{

if (!var->is_struct())

{

my_error(ER_VARIABLE_IS_NOT_STRUCT, MYF(0), base_name->str);

return 0;

}

}

thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT);

set_if_smaller(component_name->length, MAX_SYS_VAR_LENGTH);

return new Item_func_get_system_var(var, var_type, component_name,

NULL, 0);

}

由find_sys_var函数不断跟进去,我们跟到了set_var.cc,找到了如下定义:

?

1

static sys_var_chain vars = { NULL, NULL };

系统变量都会挂载在次链上。在文件中,搜索到了version_comment:

?

static sys_var_const_str sys_version_comment(&vars, "version_comment",

MYSQL_COMPILATION_COMMENT);

?

1

#define MYSQL_COMPILATION_COMMENT "Source distribution"

这便是将version_comment加载到vars的链表上。

OK,我们也来加一个自己的系统变量:

?

static sys_var_const_str sys_version_comment(&vars, "version_comment",

MYSQL_COMPILATION_COMMENT);

/**add by nocode */

static sys_var_const_str sys_version_comment_test(&vars, "nocode_test_sysvar",

MYSQL_COMPILATION_NOCODE_TEST_SYSVAR);

#define MYSQL_COMPILATION_COMMENT "Source distribution"

#define MYSQL_COMPILATION_NOCODE_TEST_SYSVAR "No code in heart" /*add by nocode*/

?

1

注释add by nocode的地方,即是新添加的系统变量和宏定义,我们的系统变量叫@@nocode_test_sysvar,其值为No code in heartOK,重新编译代码,执行SELECT语句,OK了。

?

mysql> select @@nocode_test_sysvar;

+----------------------+

| @@nocode_test_sysvar |

+----------------------+

| No code in heart |

+----------------------+

1 row in set (0.01 sec)

上面添加了一个系统变量,并没有修改语法文件sql_yacc.yy,为了加深理解,我们添加一个属于自己的语法:nocode语法,为了简单化实现,我们的目标很简单,在客户端输入no_code后显示字符串"MAKE BY NOCODE"。

定义关键字

首先在sql_yacc.yy文件中添加相应的SYMBOL

?

%token NO_SYM /* SQL-2003-R */

%token NO_CODE_SYM /* add by nocode*/

%token NO_WAIT_SYM

然后在lex.h中的symblos数组中添加nocode的字符串和符号的对应关系:

?

{ "NO", SYM(NO_SYM)},

{ "NO_CODE", SYM(NO_CODE_SYM)}, /*add by nocode*/

{ "NO_WAIT", SYM(NO_WAIT_SYM)},

ok,至此我们关键字已经添加进去了

添加语法节点

我们给语法分支节点起名叫nocode,定义如下:

?

/**add by nocode*/

nocode:

NO_CODE_SYM

{

THD *thd= YYTHD;

LEX *lex= Lex;

SELECT_LEX *sel= lex->current_select;

Item_string* field;

LEX_STRING tmp;

CHARSET_INFO *cs_con= thd->variables.collation_connection;

CHARSET_INFO *cs_cli= thd->variables.character_set_client;

if (sel->linkage != UNION_TYPE)

mysql_init_select(lex);

lex->current_select->parsing_place= SELECT_LIST;

uint repertoire= thd->lex->text_string_is_7bit &&

my_charset_is_ascii_based(cs_cli) ? MY_REPERTOIRE_ASCII : MY_REPERTOIRE_UNICODE30;

tmp.str = "MAKE BY NOCODE";

tmp.length = strlen(tmp.str);

field= new (thd->mem_root) Item_string(tmp.str, tmp.length, cs_con,

DERIVATION_COERCIBLE,

repertoire);

if (field== NULL)

MYSQL_YYABORT;

if (add_item_to_list(thd, field))

MYSQL_YYABORT;

Select->parsing_place= NO_MATTER;

lex->sql_command= SQLCOM_SELECT;

}

;

最后要在statement的语法节点上加入nocode分支,我就不贴不来了。只要读到"no_code"便会进行进入这个语法分支。在这个分支里,做了一些操作,首先构造了一个SELECT类型的语句,然后对其添加了一列,这列的名称就是"MAKE BY NOCODE"…具体的细节大家自己研究吧,这都不是本文的重点。

语法添加完之后,我们重新编译项目,值得说明的是,Mysql还是项目组织还是非常好的,修改了语法文件之后,不需要我们自己去用bison编译,项目自动就帮我们编译好了,真是不错。重启服务器,在客户端输入no_code,结果如下:

?

mysql> no_code;

+----------------+

| MAKE BY NOCODE |

+----------------+

| MAKE BY NOCODE |

+----------------+

1 row in set (3.02 sec)

语法分析到此结束。这里只添加了一个很简单的语法分支,没啥用处,主要是介绍下添加分支的步骤,大家添加分支的时候要尽量使用已有的分支,既减少劳动量,同时也会减少语法冲突。 唠叨两句,最近项目太紧张,压力山大,每晚都被噩梦惊醒,噩梦中总会想到算法的各种BUG,写个代码都提心吊胆的,哎,搞IT的真是悲催啊。PS 终于又更新了一篇,oh yeah,-_-ps again: 第一次用windows live writer写博客,感觉比网页方便多了~~,赞一个


摘自 心中无码 bitsCN.com
推荐阅读
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • “你永远都不知道明天和‘公司的意外’哪个先来。”疫情期间,这是我们最战战兢兢的心情。但是显然,有些人体会不了。这份行业数据,让笔者“柠檬” ... [详细]
  • 生成对抗式网络GAN及其衍生CGAN、DCGAN、WGAN、LSGAN、BEGAN介绍
    一、GAN原理介绍学习GAN的第一篇论文当然由是IanGoodfellow于2014年发表的GenerativeAdversarialNetworks(论文下载链接arxiv:[h ... [详细]
author-avatar
手机用户2502889621
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有