openGauss内核分析(三):SQL解析

在传统数据库中SQL引擎一般指对用户输入的SQL语句进行解析、优化的软件模块。

SQL的解析过程主要分为:

词法分析Lexical Analysis:将用户输入的SQL语句拆解成单词(Token)序列,并识别出关键字、标识、常量等。

语法分析Syntax Analysis:分析器对词法分析器解析出来的单词(Token)序列在语法上是否满足SQL语法规则。

语义分析Semantic Analysis:语义分析是SQL解析过程的一个逻辑阶段,主要任务是在语法正确的基础上进行上下文有关性质的审查,在SQL解析过程中该阶段完成表名、操作符、类型等元素的合法性判断,同时检测语义上的二义性。

openGauss在pg_parse_query中调用raw_parser函数对用户输入的SQL命令进行词法分析和语法分析,生成语法树添加到链表parsetree_list中。完成语法分析后,对于parsetree_list中的每一颗语法树parsetree,会调用parse_analyze函数进行语义分析,根据SQL命令的不同,执行对应的入口函数,最终生成查询树


词法分析Lexical Analysis

openGauss使用flex工具进行词法分析。flex工具通过对已经定义好的词法文件进行编译,生成词法分析的代码。词法文件是scan.l,它根据SQL语言标准对SQL语言中的关键字、标识符、操作符、常量、终结符进行了定义和识别。在kwlist.h中定义了大量的关键字,按照字母的顺序排列,方便在查找关键字时通过二分法进行查找。 在scan.l中处理“标识符”时,会到关键字列表中进行匹配,如果一个标识符匹配到关键字,则认为是关键字,否则才是标识符,即关键字优先. 以“select a, b from item”为例说明词法分析结果。

名称词性内容说明

关键字keywordSELECT,FROM如SELECT/FROM/WHERE等,对大小写不敏感

标识符IDENTa,b,item用户自己定义的名字、常量名、变量名和过程名,若无括号修饰则对大小写不敏感

语法分析Syntax Analysis

openGauss中定义了bison工具能够识别的语法文件gram.y,根据SQL语言的不同定义了一系列表达Statement的结构体(这些结构体通常以Stmt作为命名后缀),用来保存语法分析结果。以SELECT查询为例,它对应的Statement结构体如下。

typedef struct SelectStmt

{

NodeTag type;

List   *distinctClause; /* NULL, list of DISTINCT ON exprs, or

* lcons(NIL,NIL) for all (SELECT DISTINCT) */

IntoClause *intoClause; /* target for SELECT INTO */

List   *targetList; /* the target list (of ResTarget) */

List   *fromClause; /* the FROM clause */

Node   *whereClause; /* WHERE qualification */

List   *groupClause; /* GROUP BY clauses */

Node   *havingClause; /* HAVING conditional-expression */

List   *windowClause; /* WINDOW window_name AS (...), ... */

WithClause *withClause; /* WITH clause */

List   *valuesLists; /* untransformed list of expression lists */

List   *sortClause; /* sort clause (a list of SortBy's) */

Node   *limitOffset; /* # of result tuples to skip */

Node   *limitCount; /* # of result tuples to return */

    ……

} SelectStmt;

这个结构体可以看作一个多叉树,每个叶子节点都表达了SELECT查询语句中的一个语法结构,对应到gram.y中,它会有一个SelectStmt。代码如下:


从simple_select语法分析结构可以看出,一条简单的查询语句由以下子句组成:去除行重复的distinctClause、目标属性targetList、SELECT INTO子句intoClause、FROM子句fromClause、WHERE子句whereClause、GROUP BY子句groupClause、HAVING子句havingClause、窗口子句windowClause和plan_hint子句。在成功匹配simple_select语法结构后,将会创建一个Statement结构体,将各个子句进行相应的赋值。对simple_select而言,目标属性、FROM子句、WHERE子句是最重要的组成部分。SelectStmt与其他结构体的关系如下:


下面以“select a, b from item”为例说明简单select语句的解析过程,函数exec_simple_query调用pg_parse_query执行解析,解析树中只有一个元素。


(gdb) p *parsetree_list

$47 = {type = T_List, length = 1, head = 0x7f5ff986c8f0, tail = 0x7f5ff986c8f0}

List中的节点类型为T_SelectStmt

(gdb) p *(Node *)(parsetree_list->head.data->ptr_value)

$45 = {type = T_SelectStmt}

查看SelectStmt结构体,targetList 和fromClause非空

(gdb)set$stmt = (SelectStmt *)(parsetree_list->head.data->ptr_value)(gdb) p *$stmt$50= {type= T_SelectStmt, distinctClause =0x0, intoClause =0x0, targetList =0x7f5ffa43d588, fromClause =0x7f5ff986c888, startWithClause =0x0, whereClause =0x0, groupClause =0x0,  havingClause =0x0, windowClause =0x0, withClause =0x0, valuesLists =0x0, sortClause =0x0, limitOffset =0x0, limitCount =0x0, lockingClause =0x0, hintState =0x0, op = SETOP_NONE,all=false,  larg =0x0, rarg =0x0, hasPlus =false}

查看SelectStmt的targetlist,有两个ResTarget

(gdb) p *($stmt->targetList)

$55 = {type = T_List, length = 2, head = 0x7f5ffa43d540, tail = 0x7f5ffa43d800}

(gdb) p *(Node *)($stmt->targetList->head.data->ptr_value)

$57 = {type = T_ResTarget}

(gdb)set$restarget1=(ResTarget *)($stmt->targetList->head.data->ptr_value)(gdb) p *$restarget1$60= {type= T_ResTarget,name=0x0, indirection =0x0, val =0x7f5ffa43d378, location =7}(gdb) p *$restarget1->val$63= {type= T_ColumnRef}(gdb) p *(ColumnRef *)$restarget1->val$64= {type= T_ColumnRef,fields=0x7f5ffa43d470,prior=false, indnum =0, location =7}(gdb) p *((ColumnRef *)$restarget1->val)->fields$66= {type= T_List,length=1,head=0x7f5ffa43d428, tail =0x7f5ffa43d428}(gdb) p *(Node *)(((ColumnRef *)$restarget1->val)->fields)->head.data->ptr_value$67= {type= T_String}(gdb) p *(Value*)(((ColumnRef *)$restarget1->val)->fields)->head.data->ptr_value$77= {type= T_String, val = {ival =140050197369648,str=0x7f5ffa43d330"a"}}

(gdb)set$restarget2=(ResTarget *)($stmt->targetList->tail.data->ptr_value)(gdb) p *$restarget2$89= {type= T_ResTarget,name=0x0, indirection =0x0, val =0x7f5ffa43d638, location =10}(gdb) p *$restarget2->val$90= {type= T_ColumnRef}(gdb) p *(ColumnRef *)$restarget2->val$91= {type= T_ColumnRef,fields=0x7f5ffa43d730,prior=false, indnum =0, location =10}(gdb) p *((ColumnRef *)$restarget2->val)->fields$92= {type= T_List,length=1,head=0x7f5ffa43d6e8, tail =0x7f5ffa43d6e8}(gdb) p *(Node *)(((ColumnRef *)$restarget2->val)->fields)->head.data->ptr_value$93= {type= T_String}(gdb) p *(Value*)(((ColumnRef *)$restarget2->val)->fields)->head.data->ptr_value$94= {type= T_String, val = {ival =140050197370352,str=0x7f5ffa43d5f0"b"}}

查看SelectStmt的fromClause,有一个RangeVar

(gdb) p *$stmt->fromClause$102 = {type = T_List, length = 1, head = 0x7f5ffa43dfe0, tail = 0x7f5ffa43dfe0}(gdb)set$fromclause=(RangeVar*)($stmt->fromClause->head.data->ptr_value)(gdb) p *$fromclause$103= {type= T_RangeVar, catalogname =0x0, schemaname =0x0, relname =0x7f5ffa43d848"item", partitionname =0x0, subpartitionname =0x0, inhOpt = INH_DEFAULT, relpersistence =112'p',alias=0x0,  location =17, ispartition =false, issubpartition =false, partitionKeyValuesList =0x0, isbucket =false, buckets =0x0,length=0, foreignOid =0, withVerExpr =false}

综合以上分析可以得到语法树结构


语义分析Semantic Analysis

在完成词法分析和语法分析后,parse_analyze函数会根据语法树的类型,调用transformSelectStmt将parseTree改写为查询树


(gdb) p *result

$3 = {type = T_Query, commandType = CMD_SELECT, querySource = QSRC_ORIGINAL, queryId = 0, canSetTag = false, utilityStmt = 0x0, resultRelation = 0, hasAggs = false, hasWindowFuncs = false,

  hasSubLinks = false, hasDistinctOn = false, hasRecursive = false, hasModifyingCTE = false, hasForUpdate = false, hasRowSecurity = false, hasSynonyms = false, cteList = 0x0, rtable = 0x7f5ff5eb8c88,

  jointree = 0x7f5ff5eb9310, targetList = 0x7f5ff5eb9110,…}

(gdb) p *result->targetList

$13 = {type = T_List, length = 2, head = 0x7f5ff5eb90c8, tail = 0x7f5ff5eb92c8}

(gdb) p *(Node *)(result->targetList->head.data->ptr_value)

$8 = {type = T_TargetEntry}

(gdb) p *(TargetEntry*)(result->targetList->head.data->ptr_value)

$9 = {xpr = {type = T_TargetEntry, selec = 0}, expr = 0x7f5ff636ff48, resno = 1, resname = 0x7f5ff5caf330 "a", ressortgroupref = 0, resorigtbl = 24576, resorigcol = 1, resjunk = false}

(gdb) p *(TargetEntry*)(result->targetList->tail.data->ptr_value)

$10 = {xpr = {type = T_TargetEntry, selec = 0}, expr = 0x7f5ff5eb9178, resno = 2, resname = 0x7f5ff5caf5f0 "b", ressortgroupref = 0, resorigtbl = 24576, resorigcol = 2, resjunk = false}

(gdb)

(gdb) p *result->rtable

$14 = {type = T_List, length = 1, head = 0x7f5ff5eb8c40, tail = 0x7f5ff5eb8c40}

(gdb)  p *(Node *)(result->rtable->head.data->ptr_value)

$15 = {type = T_RangeTblEntry}

(gdb) p *(RangeTblEntry*)(result->rtable->head.data->ptr_value)

$16 = {type = T_RangeTblEntry, rtekind = RTE_RELATION, relname = 0x7f5ff636efb0 "item", partAttrNum = 0x0, relid = 24576, partitionOid = 0, isContainPartition = false, subpartitionOid = 0……}

得到的查询树结构如下:


完成词法、语法和语义分析后,SQL解析过程完成,SQL引擎开始执行查询优化。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容