Postgres数据库词法分析和语法分析源码解析

学习参考书籍、网站或博文:

  1. 参考书籍:《PostgreSQL数据库内核分析》
  2. Lex & Yacc 点击前往
  3. Postgresql源码学习之词法和语法分析 点击前往


概述

postgreSQL数据库将用户输入的sql命令作为字符串传递给查询分析器,并对其进行词法分析和语法分析生成分析树。词法分析和语法分析是由Unix工具Yacc和Lex制作的。使用的是 Bison 和Flex。
词法分析和语法分析依赖的文件定义在src\backend\parser下的scan.lgram.y中。
词法分析器 scan.l负责识别标识符,SQL 关键字等,对于发现的每个关键字或者标识符都会生成一个记号并且传递给分析器;语法分析器 gram.y包含一套语法规则和触发规则时执行的动作;raw_parser函数(在src/backend/parser/parser.c下)主要通过调用采用Lex和Yacc配合预生成的base_yyparse函数来实现词法分析和语法分析的工作。


重要源码文件及调用关系:

  • kwlist.h:SQL关键字定义,注意:关键字名要小写,按照字符串值顺序定义
  • kwlookup.h:定义结构体ScanKeyword;
  • kwlookup.c:使用kwlist.h初始化关键字数组ScanKeywords,提供ScanKeywordLookup函数,该函数判断输入的字符串是否是关键字,若是则返回当前标识符指向关键字列表中对应单词的指针,采用二分法查找;
  • scanup.c:提供几个词法分析时常用的函数。scanstr函数处理转义字符,downcase_truncate_identifier函数将大写英文字符转换为小写字符,truncate_identifier函数截断超过最大标识符长度的标识符,scanner_isspace函数判断输入字符是否为空白字符。
  • scan.l:定义词法结构,编译生成scan.c;这里会忽略comment等无用信息。
  • gram.y:定义语法结构,编译生成gram.c;分析后生成语法分析树。
  • gram.h:定义关键字的数值编号。
    辅助脚本:
  • check_keywords.pl:检查在gram.y 和 kwlist.h 中定义的关键字列表是否一致。

在这里插入图片描述

接口

接口名称功能描述
raw_parser调用gram.y生成的语法分析函数base_yyparse和scan.l生成的词法分析函数
base_yyparse实现语法分析,并返回语法分析树
base_yylex实现词法分析
ScanKeywordLookup采用二分法查找关键字
truncate_identifier截断超过最大标识符长度的标识符

Lex和Yacc


词法分析器

Flex:生成词法分析器的编译工具,生成词法分析器代码,将.l文件编译后生成.c和.h文件。
词法分析通常所做的就是在输入中寻找字符的模式(pattern)。它使用正则表达式匹配输入的字符串并且把它们转换成对应的标记,正则表达式就是一种对模式的简介明了的描述方式。匹配正则表达式的规则,然后执行对应的动作。其实就是提取编程语言占用的各种保留字、操作符、特殊符号等等语言的元素。

.l文件的结构

scan.l文件包含定义段、规则段、C程序代码段三部分,每个部分之间用“%%”分割。

  • 定义段:
  1. %top{ ... } 这部分,括号中内容将被原样copy到生成的scan.c中,并且位于C文件的最顶部。主要包含此文件描述、注释,以及需要的C头文件。
  2. %{ ... %}这部分的代码会被原样copy到生成的C文件中。这里可以重定义一些Flex中的宏,如YYSTYPE, 以及一些在规则段使用的函数声明、结构体声明和定义等
  3. %option 此部分是Flex支持的一些参数,通过%option 来设置
  • %option reentrant 可重入词法分析器,传统词法分析器只能一次处理一个输入流,所以很多变量都定义的为静态变量,这样分析器才能记住上次分析的地方继而可以继续分析。但是不能同时处理多个输入流。为了解决这个问题引入了可重入词法分析器。通过参数reentrant来控制。
  • %option prefix="core_yy" 通过加入前缀,可以将原来的yylex等函数 变成core_yylex.这样可以在一个程序中建立多个词法分析器。用来分析不同的输入流。
  • %option bison-bridge ,bison桥模式,为什么会有这个模式呢,因为bison的发展和flex的发展沟通并不是很密切,导致了一个不好的情况,即在bison调用yylex的时候是yylex(YYSTYPE *yylvalp); 即必须传入一个yylval的指针,但是flex中定义的yylex函数为int yylex(yyscan_t scaninfo).这样两者就不一样了。就无法互相协作的工作了。所以在flex中提拱了桥模式,如果按%option bison-bridge做了声明,那么在flex中yylex将被声明为int yylex(YYSTYPE* lvalp, yyscan_t scaninfo);这样就兼容了bison.
  • %option bison-locations 此模式同上面参数同时使用,如果做了此声明,yylex 将被声明为int yylex (YYSTYPE* lvalp, YYLTYPE* llocp, yyscan_t scaninfo);加入了location参数。而在flex中yylex 中宏yylval 和 yylloc其实就是lvalp 和llocp的一个拷贝。
  1. %x 声明起始状态(有两种模式:%x声明独占模式 %s声明共享模式),起始状态代表进入一个特定的状态,在规则段只有定义了特定状态的规则才会匹配,这种规则通过<start stat>来标识。例如 定义段定义了 %x xb 则在规则段只有开头的规则才会匹配,其他的的规则则不会被匹配。Lex开始时的标准状态是状态零,也称为INITIAL
  2. 预定义正则表达式的代理,可以为一些要匹配的正则表达式命名,这样在规则段可以用这个代理名字,来代替这个表达式。例如space [ \t\n\r\f],给[ \t\n\r\f] 命名为space, 后面在规则段即可使用{space}来代替[ \t\n\r\f],同时代理也可以被嵌套,如whitespace ({space}+|{comment}) 这里定义了新的代理whitespace, 它代理了 ({space}+|{comment}) 其中{space}就被嵌套代理了。
  • 规则段 %%

    {规则名} {
    该规则对应的执行的动作
    }
    例如:

    {whitespace}	{
    					/* ignore */
    				}
    
    {xcstart}		{
    					/* Set location in case of syntax error in comment */
    					SET_YYLLOC(); /*  用于标记起始位置 */
    					yyextra->xcdepth = 0; /* 将当前深度设置为0 */
    					BEGIN(xc); /* 接下来要进入注释的状态 */
    					/* Put back any characters past slash-star; see above */
    					yyless(2);  
    				}
    <xc>{
    {xcstart}		{
    				(yyextra->xcdepth)++; /*  如果再次遇到xcstart,那么当前深度+1*/
    				/* Put back any characters past slash-star; see above */
    				yyless(2);
    				}
    
    {xcstop}		{
    				if (yyextra->xcdepth <= 0) /* 深度<=0,表示当前处于最外层的注释结束位置  */
    					BEGIN(INITIAL); /* 开始最初的0状态 */
    				else
    					(yyextra->xcdepth)--; /* 否则说明当前处于多个注释的嵌套内层注释的结束位置,深度-1 */
    				}
    
    {xcinside}		{
    				/* ignore */  /* 当前处于注释内部,什么也不做 */
    				}
    
    {op_chars}		{
    				/* ignore */
    				}
    
    \*+				{
    				/* ignore */
    				}
    
    <<EOF>>			{
    				yyerror("unterminated /* comment");
    				}
    } /* <xc> */
    

    yyless(), 此函数意思是保留参数个数的字符流,其他的返回给输入流。但是匹配的字符流长度,为匹配的总长度。例如:如果yyless(2),这个时候匹配的字符是5个字符,则保留前两个字符,后面三个字符返回给输入流。

    词法解析器的规则段还会匹配特殊符号(如代表隐式类型转换的::<=>=!=等),操作符、部分类型以及identifier

{typecast}		{
					SET_YYLLOC();
					return TYPECAST;
				}

{dot_dot}		{
					SET_YYLLOC();
					return DOT_DOT;
				}

{colon_equals}	{
					SET_YYLLOC();
					return COLON_EQUALS;
				}

{equals_greater} {
					SET_YYLLOC();
					return EQUALS_GREATER;
				}

{less_equals}	{
					SET_YYLLOC();
					return LESS_EQUALS;
				}
......

{identifier}	{
					int			kwnum;
					char	   *ident;

					SET_YYLLOC();

					/* 扫描是否是关键字 */
					kwnum = ScanKeywordLookup(yytext,
											  yyextra->keywordlist);
					if (kwnum >= 0)
					{
						yylval->keyword = GetScanKeyword(kwnum,
														 yyextra->keywordlist);
						return yyextra->keyword_tokens[kwnum];
					}

					/*
					 * No.  Convert the identifier to lower case, and truncate
					 * if necessary.
					 */
					 /* 不是关键字则将其转换成小写,PG是默认小写 */
					ident = downcase_truncate_identifier(yytext, yyleng, true);
					yylval->str = ident;
					return IDENT; 
				}
......

词法分析器调用yylex()函数来读取一小部分输入流匹配相应的规则然后返回一个记号流,每个记号实际上有两个组成部分,记号编号(token number)和记号值(token’s value)。
记号编号是一个较小的整数,数字随意,零值意味着文件的结束。Bison创建的语法分析器,会自动从258开始(目的是为了避免与文字字符记号产生冲突)
PostgreSQL的关键字列表和记号列表如下图:
在这里插入图片描述

  • 代码段

    代码段可以是任意C代码,它们会被复制到lex.yy.c的最末端
    通常用于一些未定义接口的定义,例如
    int yywrap()
    {
    return 1;
    }


语法分析器

Bison:语法分析器的编译工具,生成语法分析器代码,将.y文件编译后生成.c.h文件。
语法分析器的任务是找出输入记号之间的关系。一种常见的关系表达式就是语法分析树(parse tree)。例如,基于通常的算术规则,算术表达式 12+34+5 有如下图的表示。
在这里插入图片描述

这课树的每个分支都显示了记号之间或者记号与下面子树的关系,每一个bison语法分析器在分析其输入时都会构造一棵语法分析树。在有些应用里,它会把整棵树作为一个数据结构创建在内存中以便于后续使用。在其他应用里,语法分析树只是隐式地包含在语法分析器进行的一系列操作中。
Bison的规则基本是基于BNF文法(巴斯克范式(BNF范式)),它是一种书写上下文无关文法的标准格式。每一行就是一条规则,用来说明如何创建语法分析树的分支。BNF 规定是推导规则(产生式)的集合,写为:

<符号> ::= <使用符号的表达式>

在BNF里,::=被读作“是”或者“变成”,|是“或者”,意味着同一个语法符号有两种可能性。规则左边的名称是语法符号(symbol)。

这里的 <符号> 是非终结符,而表达式由一个符号序列,或用指示选择的竖杠'|' 分隔的多个符号序列构成,每个符号序列整体都是左端的符号的一种可能的替代。在输入中出现并且被词法分析器返回的符号是终结符或称记号,而规则左部的语法符号是非终结符。终结符和非终结符必须不同,从未在左端出现的符号叫做终结符。

.y文件的结构

BIison规范与flex规范一样,由三部分组成。定义段、规则段和代码段,其中各个段由“%%”符号分隔

  • 定义段:处理语法解析器的控制信息,建立分析器操作所需要的的执行环境;
    定义段可分为两部分,第一部分是%{…%},内部是C语言变量、类型和宏定义以及函数声明等;第二部分是对文法的终结符和非终结符做相关的声明。
# 以下仅对第二部分相关声明进行解释说明
/* 声明此语法分析器是纯语法分析器。这样可以实现可重入。同时需要
 * %parse-param {core_yyscan_t yyscanner} 和%lex-param   {core_yyscan_t yyscanner}
 * 配合使用,即为了调用纯词法分析器flex,需要scanner实例,即需要传入这个参数.
 * 通过定义%parse-param 即可给yyparse()函数传入参数。
 * 定义%lex-param.即可把parse-param中定义的参数传递给yylex. 
 */
%pure-parser 

/* %expect N表示Bison解析器应该有N个shift/reduse冲突,如果与此处指定的N不匹配,多余的在使用Bison编译时会提示相应的错误 */
%expect 0

/* 
 * 命名函数名称,默认为yy,此处指定base_yy意味着默认的yyxx()会变成base_yyxx(). 
 * eg:yyparse ---> base_yyparse,源码目录下/src/backend/parser/parser.c中
 * 词法语法分析的入口函数raw_parser调用了base_yyparse函数进入语法分析阶段 
 */
%name-prefix="base_yy"

/* 声明使用位置信息 */
%locations

/* %parse-param声明base_yyparse()函数的入参. 
 * 下面的说明该函数的参数类型为core_yyscan_t 参数名为yyscanner.
 * 对应源码目录下/src/backend/parser/parser.c中
 * 词法语法分析的入口函数raw_parser调用的base_yyparse函数
 */
%parse-param {core_yyscan_t yyscanner}
%lex-param   {core_yyscan_t yyscanner}

/* 定义yylval类型,在flex中通过yylval的返回匹配的值。 */
 %union
 {
	core_YYSTYPE		core_yystype;
	/* these fields must match core_YYSTYPE: */
	int					ival;
	char				*str;
	const char			*keyword;

	char				chr;
	bool				boolean;
	JoinType			jtype;
	DropBehavior		dbehavior;
	OnCommitAction		oncommit;
	List				*list;
	Node				*node;
	Value				*value;
	......
 } 

/* 此语法是定义非终结符(在规则段,: 左边是非终结符,右边是终结符)和union中变量的绑定。
 * 在Bison中,每个符号(终结符和非终结符)都有一个值与之对应。
 * 默认是一个整数值,为了扩展,可以定义union类型。$$代表非终结符的值。
 * 这里的意思就是把$$和union中某一个变量绑定。 
 */
%type <union中的变量名> 非终结符

/* 此语法是定义终结符和union中变量的绑定。这样就可以在flex中直接通过yylval->(union中的变量名)返回匹配的值 */
%token <union 中的变量名> 终结符 

/* 优先级: 从低到高,同一行并列代表优先级相同 */
/* 
 * %nonassoc symbol 用来定义非关联操作符。同%prec 联合使用可以定义某个表达式的优先级。
 * %left 用来定义左结合操作符
 * %right 用来定义右结合操作符
 */
%nonassoc	SET				/* see relation_expr_opt_alias */
%left		UNION EXCEPT
%left		INTERSECT
%left		OR
%left		AND
%right		NOT
%nonassoc	IS ISNULL NOTNULL	/* IS sets precedence for IS NULL, etc */
%nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
%nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
%nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
%left		POSTFIXOP		/* dummy for postfix Op rules */
......
%left		JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
/* kluge to keep xml_whitespace_option from causing shift/reduce conflicts */
%right		PRESERVE STRIP_P

  • 规则段:规则段实际上定义了文法的非终结符及产生式集合,以及当归约整个产生式时应执行的操作。
/* 
 * 规则对应的动作使用{}括起来,使用";"作为此规则的结束符
 * stmtblock是非终结符号,冒号语法分隔符, 
 * stmtmulti代表终结符号,其值通过位置获取,因只有一个终结符,所以其值为$1. 
 * stmtblock的值,用$$表示。
 */
stmtblock:	stmtmulti
			{
				pg_yyget_extra(yyscanner)->parsetree = $1; /*这里的结果最终返回给raw_parser函数 */
			}
		;
/*
 * 下面的规则是stmtmulti对应的规则
 * 这里说一下@N的含义:在语法分析器的语义动作中,可以通过@$来使用左部符号的位置信息,通过@N使用
 * 右部符号的位置信息。
 * $N的含义:$$代表冒号:左边记号,$N表示冒号右边第N个位置的记号
 * 词法分析器必须把每个记号的位置信息放到yylloc中,语法分析器会在每次词法分析器返回记号时定义对应的yyloc
 */
stmtmulti:	stmtmulti ';' stmt
				{
					if ($1 != NIL) //$1代表stmtmulti 对应的位置
					{
						/* update length of previous stmt */
						updateRawStmtEnd(llast_node(RawStmt, $1), @2);
					}
					if ($3 != NULL) //$3代表stmt 对应的位置
						$$ = lappend($1, makeRawStmt($3, @2 + 1));
					else
						$$ = $1;
				}
			| stmt
				{
					if ($1 != NULL)
						$$ = list_make1(makeRawStmt($1, 0));//将stmt中的值$1,作为参数生成RawStmt并加入到列表中。
					else
						$$ = NIL;
				}
		;
......
stmt :
			AlterEventTrigStmt
			| AlterCollationStmt
			| AlterDatabaseStmt
			| AlterDatabaseSetStmt
			| AlterDefaultPrivilegesStmt
			| AlterDomainStmt
			| AlterEnumStmt
			| AlterExtensionStmt
			| AlterExtensionContentsStmt
			| AlterFdwStmt
			| AlterForeignServerStmt
			| AlterForeignTableStmt
			| AlterFunctionStmt
			| AlterGroupStmt
			| AlterObjectDependsStmt
			| AlterObjectSchemaStmt
			| AlterOwnerStmt
			| AlterOperatorStmt
			| AlterTypeStmt
			| AlterPolicyStmt
			| AlterSeqStmt
			| AlterSystemStmt
			| AlterTableStmt
			| AlterTblSpcStmt
			......
  • 代码段:一般指在规则段中用到或者在语法分析器的其他部分用到的函数。这一部分一般会被直接拷贝到yacc编译器产生的c源文件中。
    yylex()是词法分析程序,它返回记号。语法分析驱动程序yyparse()将会调用yylex()获取记号。如果不使用lex生成这个函数,则必须在代码段用C语言写这个程序。记号由记号名和属性值构成,记号名一般作为yylex的返回值(注意,记号名是由%token等定义的终结符名,这些终结符名在yacc内部会被宏定义成一些常数。),而属性值则由yacc内部定义的变量yylval来传递。yylval的类型与属性值栈元素的类型相同,即,默认状态下,yylvalint类型,若使用#defineYYSTYPE double将属性值栈元素定义为double类型,则yylval就是double类型,若用%union将属性值栈元素定义为联合类型,则yylval也是联合类型。注意,当yylval是联合类型时,对它的引用要注意。

移进/规约冲突

是指一个输入字符存在两种可能的语法分析器,并且其中一个分析器结束一条规则(选择归约),而另外一个并不结束(选择移进)。例如,

%%
e: 'X'
|	e '+' e
;

对于输入字符串X+X+X,有两种可能的语法分析器:(x+x)+x或者x+(x+x)。选择归约将是语法分析器使用第一条规则,而选择移进则使语法分析器使用第二条规则。除非用户使用操作符的优先级声明,否则bison选择移进。参见bison的默认规则,158页的“优先级和结合性声明”。
在这里插入图片描述

在这里插入图片描述
当存在移进/归约冲突时,bison会比较可能移进的记号和可能归约的规则的优先级。如果记号的优先级更高,那么就移进;如果规则的优先级更高,那么就归约。如果两者具有相同的优先级,bison将检查结合性。如果它们是左结合,那么就归约,如果它们是右结合,那么就移进,如果它们没有结合性,那么bison就报告错误。

规约/规约冲突

发生在同一个记号可以结束两条不同规则的时候。例如,

%%
proga: proga | progb ;

proga:	'x';
progb:	'x';

x可以是proga也可以是progb。大多数归约/归约冲突比这个的歧义要小,但它们通常意味着语法存在错误


案例分析

此案例是用于实现PostgreSQL数据库支持DELETELIMIT语法

delete from test where id < 6 limit 3;

先从scan.l中开始分析:

  1. yyparse中通过调用yylex来分析第一个单词这需要注意一下:在gram.y中已经使用了%name-prefix="base_yy",所以这里的yyparseyylex,实际上最后都是调用的base_yyparsebase_yylex,这里需要关心的是base_yylex.因为在scan.l中定义了%option prefix="core_yy",所以在scan.c中生成的yylex的函数名被重定义为来了core_yylex.这里出现了不匹配,PG,是通过自己写了base_yylex函数,其中调用了core_yylex.
    在这里插入图片描述

那为什么要这么做呢,因为Flex解析的时候默认只是向前多看一个token,来做匹配的, 但是在SQL中有些语句匹配是需要向前多看多于一个token的,为了实现这点,PG采用在base_yylex中读取token,然后做替换的方式实现匹配多个token.例如 如果是 WITH TIME,那么这两个token 将被替换成WITH_LA.

  1. 在scan.l的规则段
{identifier}    {
                      const ScanKeyword *keyword;
                      char       *ident;
                      SET_YYLLOC();
                     /* 在关键字列表yyextra->keywords中查找匹配的字符串是否为关键字
                      * ScanKeywordLookup函数被声明在keywords.h中keywords.h被包含在scanner.h中
                      * scanner.h被包含在gramparse.h中,gramparse.h被scan.l引用
                      */
                      /* 变量yyextra->keywords是我们传入的kwlist.h中的所有关键字. */
                      keyword = ScanKeywordLookup(yytext,yyextra->keywords,yyextra->num_keywords);
                      if (keyword != NULL)//如果找到 在kwlist.h中存在PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD)
                      {
                           yylval->keyword = keyword->name;//token 对应的值这里为 “delete”
                           return keyword->value;//返回token, 这里为宏“DELETE_P”
                       }
                      /*
                       * No.  Convert the identifier to lower case, and truncate
                       * if necessary.
                       */
                       ident = downcase_truncate_identifier(yytext, yyleng, true);
                       yylval->str = ident;
                       return IDENT;
        		}
  1. 在yylex返回DELETE_P这个token.然后分析gram.y中这个token 对应的规则 由于flex 默认向前查看一个token, 根据第二部可知第二个token 为FROM.在规则段中找到如下规则,opt_with_clause 可以为空,因此可以匹配到的规则有如下两条;这里我们可以看出当opt_with_clause为空时会产生移进/规约冲突,后面我们会讲到如何来解决该冲突(下面我们以匹配上第二条规则进行讲解)
DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
			using_clause where_or_current_clause returning_clause
				{
					DeleteStmt *n = makeNode(DeleteStmt);
					n->relation = $4;
					n->usingClause = $5;
					n->whereClause = $6;
					n->returningList = $7;
					n->withClause = $1;
					$$ = (Node *)n;
				}
			| DELETE_P FROM relation_expr_opt_alias where_or_current_clause limit_clause
				{
					int i = 0;
					char *relname = NULL;
					relname = psprintf("%s_table_%d", "DEL",i);
					/* 构造子查询select ctid from t1 where id < 7 limit 3 */
					ColumnRef *cr = makeNode(ColumnRef);
					cr->fields = list_make1(makeString("ctid"));
					cr->location = -1;

					ResTarget *rt = makeNode(ResTarget);
					rt->name = NULL;
					rt->indirection = NIL;
					rt->val = (Node *)cr;
					rt->location = -1;
					.......
					
  1. 继续调用yylex分析test,根据第二步返回token IDENT,对应上面第二条规则的relation_expr_opt_alias
    规则的递归调用关系:
# relation_expr_opt_alias---> relation_expr---> qualified_name-->ColId-->IDENT
relation_expr_opt_alias: relation_expr					%prec UMINUS
				{
					$$ = $1;
				}
			......
relation_expr:
			qualified_name
				{
					/* inheritance query, implicitly */
					$$ = $1;
					$$->inh = true;
					$$->alias = NULL;
				}
			......
qualified_name:
			ColId
				{
					$$ = makeRangeVar(NULL, $1, @1);
				}
//创建了一个RangeVar类型node,参考RangeVar结构体。并赋给了$$即qualified_name.				
  1. 继续调用yylex分析,在上面规则DeleteStmt,需要匹配的第二条规则指示下一个需要匹配的项是where_or_current_clause 继续分析输入流where id < 6where将会和delete的解析方式一样,最终返回WHEREid < 6 即对应下面的a_expr,其中"id"
    "<" "6"都会递归地调用a_expr进行解析
#------------------------------"id"--------------------------
# where_or_current_clause ---> a_expr---> c_expr-->columnref
where_or_current_clause:
			WHERE a_expr							{ $$ = $2; }
			......
a_expr:		c_expr									{ $$ = $1; }
			......
c_expr:		columnref								{ $$ = $1; }
			......
columnref:	ColId
				{
					$$ = makeColumnRef($1, NIL, @1, yyscanner);
				}
	// id 最终会构造ColumnRef类型的node,详细参考ColumnRef结构体
#------------------------------"<"--------------------------
# where_or_current_clause ---> a_expr
a_expr:     ......
			| a_expr '<' a_expr
				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", $1, $3, @2); }

#------------------------------"6"--------------------------
# where_or_current_clause ---> a_expr---> c_expr-->AexprConst--->Iconst
where_or_current_clause:
			WHERE a_expr							{ $$ = $2; }
			......
a_expr:		c_expr									{ $$ = $1; }
			......
c_expr:		
			| AexprConst							{ $$ = $1; }
			......
AexprConst: Iconst
				{
					$$ = makeIntConst($1, @1);
				}
// "6"最终会构造出一个Const类型的node				
  1. 继续调用yylex分析,在上面规则DeleteStmt,需要匹配的第二条规则指示下一个需要匹配的项是limit_clause
#-------------------------------"limit"----------------------------------------
/* limit将会和delete的解析方式一样,最终返回LIMIT */
#-------------------------------"3"--------------------------------------------
# limit_clause--->select_limit_value--->a_expr--->c_expr-->AexprConst--->Iconst
limit_clause:
			LIMIT select_limit_value
				{
					SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
					n->limitOffset = NULL;
					n->limitCount = $2;
					n->limitOption = LIMIT_OPTION_COUNT;
					$$ = n;
				}
				......
select_limit_value:
			a_expr									{ $$ = $1; }
			......
a_expr:		c_expr									{ $$ = $1; }
			......
c_expr:		
			| AexprConst							{ $$ = $1; }
			......
AexprConst: Iconst
				{
					$$ = makeIntConst($1, @1);
				}			
  1. 到目前为止,已经分析完输入的sql 语句,结果已经放到DeleteStmt中,后面继续根据以下规则做规约处理,由于在规则段中第一个出现的非终结符号,stmtblock是我们要的结果。通过不断的规约即reduce,最后的分析结果即剩下stmtblock这一个符号。
stmtmulti:	stmtmulti ';' stmt
				{
					if ($1 != NIL) //$1代表stmtmulti 对应的位置
					{
						/* update length of previous stmt */
						updateRawStmtEnd(llast_node(RawStmt, $1), @2);
					}
					if ($3 != NULL) //$3代表stmt 对应的位置
						$$ = lappend($1, makeRawStmt($3, @2 + 1));
					else
						$$ = $1;
				}
			| stmt
				{
					if ($1 != NULL)
						$$ = list_make1(makeRawStmt($1, 0));//将stmt中的值$1,作为参数生成RawStmt并加入到列表中。
					else
						$$ = NIL;
				}
		;
stmt :
			AlterEventTrigStmt
			| AlterCollationStmt
			| AlterDatabaseStmt
			| AlterDatabaseSetStmt
			| AlterDefaultPrivilegesStmt
			| AlterDomainStmt
			| AlterEnumStmt
			| AlterExtensionStmt
			......
			| DeleteStmt //这里要重点分析的语句从前面分析的结果已经放到DeleteStmt 中
			......

移进/规约冲突的分析与解决

上面的案例中我们提到了会出现移进规约冲突,现在我们来看一下编译结果

[postgres@local99:~/postgres → delete_limit]$ make
make -C ./src/backend generated-headers
make[1]: Entering directory `/home/postgres/postgres/src/backend'
make -C parser gram.h
make[2]: Entering directory `/home/postgres/postgres/src/backend/parser'
'/bin/perl' ./check_keywords.pl gram.y ../../../src/include/parser/kwlist.h
/bin/bison -Wno-deprecated  -d -o gram.c gram.y
gram.y: error: shift/reduce conflicts: 12 found, 0 expected
make[2]: *** [gram.c] Error 1
make[2]: Leaving directory `/home/postgres/postgres/src/backend/parser'
make[1]: *** [parser/gram.h] Error 2
make[1]: Leaving directory `/home/postgres/postgres/src/backend'
make: *** [submake-generated-headers] Error 2
[postgres@local99:~/postgres → delete_limit]$ 

从编译信息中可以看出有产生了12个移进/规约冲突
如果想要看具体的移进/规约冲突信息,可以在/home/postgres/postgres/src/backend/parser路径下执行/bin/bison -Wno-deprecated -d -o gram.c gram.y -v命令,会生成gram.output文件,该文件展现了语法分析器的状态以及状态的变迁

Terminals unused in grammar

   UIDENT
   USCONST
   DOT_DOT


State 0 conflicts: 1 shift/reduce
State 22 conflicts: 1 shift/reduce
State 622 conflicts: 1 shift/reduce
State 718 conflicts: 1 shift/reduce
State 840 conflicts: 1 shift/reduce
State 1166 conflicts: 1 shift/reduce
State 1926 conflicts: 1 shift/reduce
State 1980 conflicts: 1 shift/reduce
State 3642 conflicts: 1 shift/reduce
State 5346 conflicts: 1 shift/reduce
State 5424 conflicts: 1 shift/reduce
State 5525 conflicts: 1 shift/reduce

Grammar

    0 $accept: stmtblock $end

    1 stmtblock: stmtmulti

    2 stmtmulti: stmtmulti ';' stmt
    3          | stmt

    4 stmt: AlterEventTrigStmt
    5     | AlterCollationStmt
    6     | AlterDatabaseStmt
    7     | AlterDatabaseSetStmt
    8     | AlterDefaultPrivilegesStmt
    9     | AlterDomainStmt
   10     | AlterEnumStmt
   ......
State 22

  1553 ExplainStmt: EXPLAIN . ExplainableStmt
  1554            | EXPLAIN . analyze_keyword opt_verbose ExplainableStmt
  1555            | EXPLAIN . VERBOSE ExplainableStmt
  1556            | EXPLAIN . '(' explain_option_list ')' ExplainableStmt

    ANALYSE   shift, and go to state 3
    ANALYZE   shift, and go to state 4
    CREATE    shift, and go to state 717
    DECLARE   shift, and go to state 15
    DELETE_P  shift, and go to state 16
    EXECUTE   shift, and go to state 21
    REFRESH   shift, and go to state 33
    SELECT    shift, and go to state 41
    TABLE     shift, and go to state 45
    VALUES    shift, and go to state 49
    VERBOSE   shift, and go to state 718
    WITH      shift, and go to state 50
    WITH_LA   shift, and go to state 51
    '('       shift, and go to state 719

    DELETE_P  [reduce using rule 1680 (opt_with_clause)]
    $default  reduce using rule 1680 (opt_with_clause)

    CreateAsStmt        go to state 720
    CreateMatViewStmt   go to state 721
    RefreshMatViewStmt  go to state 722
    analyze_keyword     go to state 723
    ExplainableStmt     go to state 724
    ExecuteStmt         go to state 725
	......

可以看到文件中指出有12处shift/reduce冲突,查看State 22、State 622
都可看到有如下两条提示

DELETE_P  [reduce using rule 1680 (opt_with_clause)]
$default  reduce using rule 1680 (opt_with_clause)

再去opt_with_clause规则可知,该规则可以为空

在这里插入图片描述
当该条规则为空时,既可以使用opt_with_clause进行规约,也可以采用DELETE_P移进到FROM
在这里插入图片描述
因此我们可以采用拆分语法规则的方法来解决该问题
opt_with_clause拆分为with_clause 和空来重新写DeleteStmt的规则,后面还会遇到using_clause 也是同样的问题

State 1060

  1612 DeleteStmt: DELETE_P FROM relation_expr_opt_alias . using_clause from_list where_or_current_clause returning_clause
  1613           | DELETE_P FROM relation_expr_opt_alias . where_or_current_clause limit_clause

    USING  shift, and go to state 1831
    WHERE  shift, and go to state 1832

    FETCH     reduce using rule 1829 (where_or_current_clause)
    LIMIT     reduce using rule 1829 (where_or_current_clause)
    $default  reduce using rule 1615 (using_clause)

    using_clause             go to state 1833
    where_or_current_clause  go to state 1834

拆分后的语法规则如下:


DeleteStmt: with_clause DELETE_P FROM relation_expr_opt_alias
			using_clause where_or_current_clause returning_clause
				{
					DeleteStmt *n = makeNode(DeleteStmt);
					n->relation = $4;
					n->usingClause = $5;
					n->whereClause = $6;
					n->returningList = $7;
					n->withClause = $1;
					$$ = (Node *)n;
				}
			| DELETE_P FROM relation_expr_opt_alias
			USING from_list where_or_current_clause returning_clause
				{
					DeleteStmt *n = makeNode(DeleteStmt);
					n->relation = $4;
					n->usingClause = $5;
					n->whereClause = $6;
					n->returningList = $7;
					n->withClause = NULL;
					$$ = (Node *)n;
				}
			| DELETE_P FROM relation_expr_opt_alias
			where_or_current_clause returning_clause
				{
					DeleteStmt *n = makeNode(DeleteStmt);
					n->relation = $3;
					n->usingClause = NULL;
					n->whereClause = $4;
					n->returningList = $5;
					n->withClause = NULL;
					$$ = (Node *)n;
				}
			| DELETE_P FROM relation_expr_opt_alias where_or_current_clause limit_clause
				{
					int i = 0;
					char *relname = NULL;
					relname = psprintf("%s_table_%d", "DEL",i);
					/* 构造子查询select ctid from t1 where id < 7 limit 3 */
					ColumnRef *cr = makeNode(ColumnRef);
					cr->fields = list_make1(makeString("ctid"));
					cr->location = -1;

					ResTarget *rt = makeNode(ResTarget);
					rt->name = NULL;
					rt->indirection = NIL;
					rt->val = (Node *)cr;
					rt->location = -1;
					.......
  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PostgreSQL 是一个开源的关系型数据库系统,其查询引擎的源码位于 `src/backend/executor` 目录下。分析 PostgreSQL 的查询引擎源码需要对数据库内部的架构和相关算法有一定的了解。 以下是一个简要的概述,供你开始分析 PostgreSQL 查询引擎源码时的参考: 1. 查询解析和转换:`parse_analyze.c` 文件中包含了将 SQL 查询语句解析为内部据结构(Parse Tree)的代码。在这个阶段,PostgreSQL 还会进行一些语法和语义检查,处理子查询、联接、投影等操作。 2. 查询优化:查询优化器位于 `optimizer/` 目录下,其中最重要的文件是 `optimize.c`。该阶段的目标是根据查询的成本模型、统计信息和索引等,生成最佳的执行计划候选项。优化器会考虑不同的连接方式、索引选择、谓词下推等操作。 3. 执行计划生成:执行计划生成器位于 `executor/` 目录下,重要的文件包括 `execMain.c` 和 `execProcnode.c`。在这个阶段,PostgreSQL 根据最佳执行计划生成相应的执行代码,包括表的访问方式、连接方式、排序方式等信息。 4. 执行计划执行:执行计划执行器也位于 `executor/` 目录下,其中的 `execMain.c` 是入口文件。在这个阶段,PostgreSQL 执行生成的执行计划,并返回结果给用户。 对于更详细的源码分析,你可以深入研究相关文件和函,并参考 PostgreSQL 的官方文档、邮件列表和社区讨论。此外,还有一些在线资源和书籍提供了关于 PostgreSQL 内部架构和查询引擎的深入解析,可以帮助你更好地理解和分析源码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值