首頁 資料庫 mysql教程 Mysql源码学习――打造专属语法_MySQL

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

Jun 01, 2016 pm 01:44 PM
學習

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

 

            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:

          '@'

          {

            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);

          }

        | '@' 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]= {

'', 29, 0,

'!', '|', 32, 0,

'

'B', 'Y', 11, 1,

'A', 'W', 147, 2,

'A', 'V', 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:

...

  | '@' 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

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1422
52
Laravel 教程
1316
25
PHP教程
1267
29
C# 教程
1239
24
學會徹底卸載pip,使用Python更有效率 學會徹底卸載pip,使用Python更有效率 Jan 16, 2024 am 09:01 AM

不再需要pip?快來學習如何有效卸載pip!引言:pip是Python的套件管理工具之一,它可以方便地安裝、升級和卸載Python套件。然而,有時候我們可能需要卸載pip,可能是因為我們希望使用其他的套件管理工具,或者因為我們需要完全清除Python環境。本文將介紹如何有效地卸載pip,並提供具體的程式碼範例。一、卸載pip的方法下面將介紹兩種常見的卸載pip的方法

深入研究matplotlib的色彩映射表 深入研究matplotlib的色彩映射表 Jan 09, 2024 pm 03:51 PM

深入學習matplotlib顏色表,需要具體程式碼範例一、引言matplotlib是一個功能強大的Python繪圖庫,它提供了豐富的繪圖函數和工具,可以用於創建各種類型的圖表。而顏色表(colormap)是matplotlib中一個重要的概念,它決定了圖表的配色。深入學習matplotlib色表,將幫助我們更好地掌握matplotlib的繪圖功能,使繪

Pygame入門指南:全面安裝與設定教學課程 Pygame入門指南:全面安裝與設定教學課程 Feb 19, 2024 pm 10:10 PM

從零開始學習Pygame:完整的安裝和配置教程,需要具體程式碼範例引言:Pygame是一個使用Python程式語言開發的開源遊戲開發庫,它提供了豐富的功能和工具,使得開發者可以輕鬆創建各種類型的遊戲。本文將帶您從零開始學習Pygame,並提供完整的安裝和配置教程,以及具體的程式碼範例,讓您快速入門。第一部分:安裝Python和Pygame首先,確保您的電腦上已

揭秘C語言的吸引力: 發掘程式設計師的潛質 揭秘C語言的吸引力: 發掘程式設計師的潛質 Feb 24, 2024 pm 11:21 PM

學習C語言的魅力:解鎖程式設計師的潛力隨著科技的不斷發展,電腦程式設計已經成為了一個備受關注的領域。在眾多程式語言中,C語言一直以來都備受程式設計師的喜愛。它的簡單、高效以及廣泛應用的特點,使得學習C語言成為了許多人進入程式設計領域的第一步。本文將討論學習C語言的魅力,以及如何透過學習C語言來解鎖程式設計師的潛力。首先,學習C語言的魅力在於其簡潔性。相較於其他程式語言而言,C語

一起學習word根號輸入方法 一起學習word根號輸入方法 Mar 19, 2024 pm 08:52 PM

在word編輯文字內容時,有時會需要輸入公式符號。有的小夥子們不知道在word根號輸入的方法,小面就讓小編跟小夥伴們一起分享下word根號輸入的方法教學。希望對小夥伴們有幫助。首先,開啟電腦上的Word軟體,然後開啟要編輯的文件,並將遊標移到需要插入根號的位置,參考下方的圖片範例。 2.選擇【插入】,再選擇符號裡的【公式】。如下方圖片紅色圈的部分內容所示:3.接著選擇下方的【插入新公式】。如下方圖片紅色圈的部分內容所示:4.選擇【根式】,再選擇適當的根號。如下方圖片紅色圈的部分內容所示:

從零開始學習Go語言中的main函數 從零開始學習Go語言中的main函數 Mar 27, 2024 pm 05:03 PM

標題:從零開始學習Go語言中的main函數Go語言作為一種簡潔、高效的程式語言,備受開發者青睞。在Go語言中,main函數是一個入口函數,每個Go程式都必須包含main函數作為程式的入口點。本文將從零開始介紹如何學習Go語言中的main函數,並提供具體的程式碼範例。一、首先,我們需要安裝Go語言的開發環境。可前往官方網站(https://golang.org

學習Go語言文件中的strconv.Atoi函數實作字串轉整數 學習Go語言文件中的strconv.Atoi函數實作字串轉整數 Nov 03, 2023 am 08:55 AM

學習Go語言文件中的strconv.Atoi函數實現字串轉整數Go語言是一門強大而靈活的程式語言,其標準庫中的strconv套件提供了字串轉換的功能。在這篇文章中,我們將學習如何使用strconv.Atoi函數將字串轉換為整數。首先,我們需要了解strconv.Atoi函數的用途和宣告。文件中函數的描述如下:funcAtoi(sstring)(i

快速學會pip安裝,從零開始掌握技巧 快速學會pip安裝,從零開始掌握技巧 Jan 16, 2024 am 10:30 AM

從零開始學習pip安裝,快速掌握技巧,需要具體程式碼範例概述:pip是Python套件管理工具,能夠輕鬆安裝、升級和管理Python套件。對於Python開發者來說,掌握pip的使用技巧是非常重要的。本文將從零開始介紹pip的安裝方法,並給出一些實用的技巧和具體的程式碼範例,幫助讀者快速掌握pip的使用。一、安裝pip在使用pip之前,首先需要安裝pip。 pip的

See all articles