Mysql源码学习――词法分析MYSQLlex_MySQL
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
{
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)'_']=state_map[(uchar)'$']=(uchar) MY_LEX_IDENT;
state_map[(uchar)'/'']=(uchar) MY_LEX_STRING;
state_map[(uchar)'.']=(uchar) MY_LEX_REAL_OR_POINT;
state_map[(uchar)'>']=state_map[(uchar)'=']=state_map[(uchar)'!']= (uchar) MY_LEX_CMP_OP;
state_map[(uchar)'
state_map[(uchar)'&']=state_map[(uchar)'|']=(uchar) MY_LEX_BOOL;
state_map[(uchar)'#']=(uchar) MY_LEX_COMMENT;
state_map[(uchar)';']=(uchar) MY_LEX_SEMICOLON;
state_map[(uchar)':']=(uchar) MY_LEX_SET_VAR;
state_map[0]=(uchar) MY_LEX_EOL;
state_map[(uchar)'//']= (uchar) MY_LEX_ESCAPE;
state_map[(uchar)'/']= (uchar) MY_LEX_LONG_COMMENT;
state_map[(uchar)'*']= (uchar) MY_LEX_END_LONG_COMMENT;
state_map[(uchar)'@']= (uchar) MY_LEX_USER_END;
state_map[(uchar) '`']= (uchar) MY_LEX_USER_VARIABLE_DELIMITER;
state_map[(uchar)'"']= (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 */
' ', /* 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码的具体意义进行置位的。比如字符'A',其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 '@' 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 '@'
lip->next_state= (state_map[lip->yyPeek()] ==
MY_LEX_USER_VARIABLE_DELIMITER ?
MY_LEX_OPERATOR_OR_IDENT :
MY_LEX_IDENT_OR_KEYWORD);
return((int) '@');
所作的操作是略过@,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

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

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

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)
![VMware Horizon Client無法開啟[已修復]](https://img.php.cn/upload/article/000/887/227/170835607042441.jpg?x-oss-process=image/resize,m_fill,h_207,w_330)
VMwareHorizon用戶端可協助您便利地存取虛擬桌面。然而,有時虛擬桌面基礎架構可能會遇到啟動問題。本文將討論當VMwareHorizon客戶端未能成功啟動時,您可以採取的解決方法。為什麼我的VMwareHorizon客戶端無法開啟?在設定VDI時,如果未開啟VMWareHorizon客戶端,可能會發生錯誤。請確認您的IT管理員提供了正確的URL和憑證。如果一切正常,請按照本指南中提到的解決方案解決問題。修復未開啟的VMWareHorizon用戶端如果您的Windows電腦上未開啟VMW
![VMware Horizon客戶端在連線時凍結或停滯[修復]](https://img.php.cn/upload/article/000/887/227/170942987315391.jpg?x-oss-process=image/resize,m_fill,h_207,w_330)
在使用VMWareHorizon用戶端連線到VDI時,我們可能會遇到應用程式在驗證過程中凍結或連線阻塞的情況。本文將探討這個問題,並提供解決這種情況的方法。當VMWareHorizon用戶端出現凍結或連線問題時,您可以採取一些措施來解決這個問題。修復VMWareHorizon用戶端在連接時凍結或卡住如果VMWareHorizon客戶端在Windows11/10上凍結或無法連接,請執行下面提到的解決方案:檢查網路連接重新啟動Horizon用戶端檢查Horizon伺服器狀態清除客戶端緩存修復Ho

解決方法:1、檢查電驢設置,確保已輸入正確的伺服器位址和連接埠號碼;2、檢查網路連接,確保電腦已連接到互聯網,並重置路由器;3、檢查伺服器是否在線,如果您的設定和網路連線都沒有問題,則需要檢查伺服器是否在線上;4、更新電驢版本,造訪電驢官方網站,下載最新版本的電驢軟體;5、尋求協助。

RPC伺服器不可用進不了桌面怎麼辦近年來,電腦和網路已經深入到我們的生活中的各個角落。作為一種集中運算和資源共享的技術,遠端過程呼叫(RPC)在網路通訊中起著至關重要的作用。然而,有時我們可能會遇到RPC伺服器無法使用的情況,導致無法進入桌面。本文將介紹一些可能導致此問題的原因,並提供解決方案。首先,我們需要了解RPC伺服器不可用的原因。 RPC伺服器是一種

身為LINUX用戶,我們經常需要在CentOS上安裝各種軟體和伺服器,本文將詳細介紹如何在CentOS上安裝fuse和建置伺服器的過程,幫助您順利完成相關操作。 CentOS安裝fuseFuse是一個使用者空間檔案系統框架,允許非特權使用者透過自訂檔案系統實現對檔案系統的存取和操作,在CentOS上安裝fuse非常簡單,只需按照以下步驟操作:1.開啟終端,以root用戶登入。 2.使用下列指令安裝fuse軟體包:```yuminstallfuse3.確認安裝過程中的提示,輸入`y`繼續。 4.安裝完

DHCP中繼的作用是將接收到的DHCP封包轉送到網路上的另一個DHCP伺服器,即使這兩台伺服器位於不同的子網路中。透過使用DHCP中繼,您可以實現在網路中心部署集中式的DHCP伺服器,並利用它為所有網路子網路/VLAN動態分配IP位址。 Dnsmasq是一種常用的DNS和DHCP協定伺服器,可設定為DHCP中繼伺服器,以協助管理網路中的動態主機設定。在本文中,我們將向您展示如何將dnsmasq配置為DHCP中繼伺服器。內容主題:網路拓樸在DHCP中繼上設定靜態IP位址集中式DHCP伺服器上的D

在網路資料傳輸中,IP代理伺服器扮演著重要的角色,能夠幫助使用者隱藏真實IP位址,保護隱私、提升存取速度等。在本篇文章中,將介紹如何用PHP建立IP代理伺服器的最佳實務指南,並提供具體的程式碼範例。什麼是IP代理伺服器? IP代理伺服器是位於使用者與目標伺服器之間的中間伺服器,它可作為使用者與目標伺服器之間的中轉站,將使用者的請求和回應轉發。透過使用IP代理伺服器

MQTT(MessageQueuingTelemetryTransport)是一種輕量級的訊息傳輸協議,通常用於物聯網設備之間的通訊。 PHP是一種常用的伺服器端程式語言,可以用來開發MQTT客戶端。本文將介紹如何使用PHP開發MQTT客戶端,並包含以下內容:MQTT協定的基本概念PHPMQTT客戶端程式庫的選取和使用實例:使用PHPMQTT客戶端發布和
