背景
He3DB for PostgreSQL是受Aurora论文启发,基于开源数据库PostgreSQL 改造的数据库产品。架构上实现计算存储分离,并进一步支持数据的冷热分层,大幅提升产品的性价比。
He3DB for PostgreSQL中查询编译的主要任务是根据用户的查询语句生成数据库中最优执行计划,在此过程中要考虑视图、规则以及表的连接路径等问题。
一、概述
查询分析中词法分析和语法分析分别借助词法分析工具Lex和语法分析工具 Yacc来完成各自的工作。涉及的主要函数如下所示:
1.exec_simple_query;
2. |->pg_parse_query;
3. |->raw_parser;
- exec_simple_query函数(在src/backend/tcop/postgres.c下)调用函数pg_parse_query进入词法分析和语法分析的主过程,函数pg_parse_query再调用词法分析和语法分析的入口函数raw_parser生成分析树;
- 函数pg_parse_query返回分析树(raw_parsetree_list)给exec_simple_query;
postgres命令的词法分析和语法分析是由Unix工具Lex和Yacc制作的。
1)Lex为词法分析工具,用来识别一个一个模式,例如数字、字符串、特殊符号等。
2)Yacc为语法分析工具,用来识别数字、字符串、特殊符号的组合
它们依赖的文件定义在src\backend\parser下的scan.l和gram.y。
其中:Lex词法器在文件 scan.l里定义。负责识别标识符,SQL 关键字等,对于发现的每个关键字或者标识符都会生成一个记号并且传递给分析器;
Yacc分析器在文件 gram.y里定义。包含一套语法规则和触发规则时执行的动作.
在raw_parser函数(在src/backend/parser/parser.c下)中,主要通过调用Lex和Yacc配合生成的base_yyparse函数来实现词法分析和语法分析的工作。
重要的文件如下:
文件间的调用关系,如下图所示:
二、scan.l与gram.y文件
词法分析和语法分析主要通过Flex
和Bison
配合实现的。Flex负责做sql中关键字的事别,然后转换成token
给Bison使用。Bison 根据token,定义语法并匹配语法,转换成parsetree.
在解析之前,我们先来了解一下 token 的意义,这对我们理解为何要用 Flex 生成一个词法分析器有着莫大意义。当 Flex 读进一个代表词法分析器规则的输入字符串流,然后输出以C语言编写的词法分析器源代码。利用这个词法分析器可以将一个文本中的单词提取出来,而这其实就是提取编程语言占用的各种保留字、操作符等等语言的元素,我们也将这些元素称为令牌(token)。词法分析器一般以函数(yylex()函数)的形式存在,供语法分析器调用。
令牌可以是关键字、标识符、常量、字符串值,或者是一个符号。例如,下面的 SQL 语句包括五个令牌:
SELECE name,id FROM student;
SELECT | name | id | FROM | student | ; | |
---|---|---|---|---|---|---|
token类型 | 关键字 | 标识符 | 标识符 | 关键字 | 标识符 | 终结符 |
描述 | 命令 | 列名 | 列名 | 条款 | 表名 | 结束语句 |
对于一种编程语言来说,token 的类型是有限的。Flex 工具会帮我们生成一个 yylex() 函数,Bison 通过调用这个函数来得知拿到的 token 是什么类型的,而这,便是 scan.l 和 Flex 的关联之处。Flex 的输入文件一般会被命名成 .l 文件,通过 scan.l 我们得到输出的文件是 scan.cpp,yylex() 函数就在这个文件中。这个过程的结构图大概是这样的,yylex() 函数就是我们所说的词法分析器的主体了:
2.1 scan.l文件
Flex 文件即scan.l,由%%分为三段:
定义段
:此段可以通过%{… %}包含C的代码,这部分代码也会被原样copy到生成的C文件中,还有一些参数项通过%option来设置,还有一些代理项,通过代理可以简化规则段的书写。
规则段
:此段的规则会对应到生成的yylex函数中。
代码段
:此段被原样copy到生成的C源码文件中即scan.c。
2.1.1 定义段
-
%top{ ... }
这部分,括号中内容将被原样copy到生成的scan.c中,并且位于C文件的最顶部。此部分的作用就是加入一些此文件描述注释,以及需要include的头文件。 -
%{ ... %}
这部分是的代码会被原样copy到生成的C文件中。在这里可以重定义一些Flex中的宏,如YYSTYPE, 以及一些在规则段使用的函数声明,和结构体声明和定义。 -
%option
此部分是Flex支持的一些参数,通过%option 来设置
%option reentrant
可重入词法分析器,传统词法分析器只能一次处理一个输入流,所以很多变量都定义的为静态变量这样分析器才能记住上次分析的地方继而可以继续分析。但是不能同时处理多个输入流。为了解决这个问题引入了可重入词法分析器。通过参数reentrant来控制。这样通过一个yyscan_t类型变量来保存中间状态。yyscan_t为void ,但是在yylex_init初始化的时候是分配了sizeof(yyguts_t)大小的空间,yyguts_t 是一个结构体,保存了所有需要记录的中间的状态。通过这个结构体里的变量替换了原来的静态变量。即在yylex中这个yygut_t类型的变量被传入,这样就通过这个结构体保存了所有原来很多静态变量保存的值。实现了多个输入流同时被分析。只要针对每个输入流,创建一个yyscan_t类型的scanner.传入到yylex中。
%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的一个拷贝。 -
%x
定义 开始状态,开始状态代表进入一个特定的状态,在规则段只有定义了特定状态的规则才会匹配,这种规则通
< start stat >来标识。例如 定义段定义了 %x xb 则在规则段只有开头的规则才会匹配,其他的的规则则不会被匹配。 -
代理器
, 代理器可以为一些要匹配的表达式命名,这样在规则段可以用这个代理名子,来代替这个表达式。例如space [ \t\n\r\f],给[ \t\n\r\f] 命名为space, 后面在规则段即可使用{space}来代替[ \t\n\r\f],同时代理也可以被嵌套,如whitespace ({space}+|{comment}) 这里定义了新的代理whitespace, 它代理了 ({space}+|{comment}) 其中{space}就被嵌套代理了。
2.1.2 规则段
{whitespace} {/* ignore */} //这里的意思是凡是whitespace, 则忽略{xcstart} { //这里的意思是当匹配xcstart时,要做后面的操作。/* Set location in case of syntax error in comment */SET_YYLLOC();xcdepth = 0;BEGIN(xc);/* Put back any characters past slash-star; see above */yyless(2);}<xc>{xcstart} {xcdepth++;/* Put back any characters past slash-star; see above */yyless(2);}
-
通过{代理器} 来表示要匹配的字符, 后面跟着{ ... }大括号中的内容就是要生成的C代码
,即前面的字符配匹配后,则会对应调用后面大括号内的C代码。同时后面大括号内容可以为空,即表示忽略这个匹配的字符。这些规则将被生成scan.c中的yylex函数。 -
BEGIN
, Flex 初始状态为 INITIAL, BEGIN (start stat) 意思是开始进入一个新的状态. 进入这个状态后,只有定义了对应状态的规则才会被匹配。例如:
上面调用BEGIN(xc)之后,即匹配xcstart之后,进入xc状态,所以只有{xcstart} { … }等开头带的代理器才会被匹配。其他的任何规则都不会被匹配。 -
yyless()
, 此函数意思是保留参数个数的字符流,其他的返回给输入流。但是匹配的字符流长度,为匹配的总长度。例如:
如果yyless(2),这个时候匹配的字符是5个字符,则保留前两个字符,后面三个字符返回给输入流。 -
yyextra
, 此变量是用户传入的参数数据,其类型为YY_EXTRA_TYPE定义,其值默认为void*, 但是可以根据需要自定义,这里定义为了core_yy_extra_type *, 此参数是通过两个途径传入,通过yylex_init_extra(yyext, &scanner)定义,或者调用yylex_init(&scanner)之后,调用yyset_extra(yyext, scanner);设置进去,其实就是把这个extra data 赋给了scanner中的成员变量yyextra_r。这里yyext即是extra data.此方法是在yylex函数外调用的。在yylex中可以直接通过yyextra宏(这个宏展开后就是scanner的成员变量yyextra_r)直接读取这个参数值,也可以把结果作为此参数的一部分返回。此参数必须是 Reentrant Scanner中使用即打开了参数%option reentrant. -
yylval
yylex(在Bison yyparse 函数中调用)返回值可以理解分为两部分,一个是在规则中的return 值,此为返回的token, 另一个是与之一一对应的yylval.yylval 的类型为YYSTYPE, 可以根据用户需要重新定义。一般定义为一个union类型,在Bison中将token和这个union中的一个变量绑定。然后在scan.l中直接将对应值设置到yylval->(union 的成员变量).如此这般,在yyparse中即可获取到,token 和 其对应的值。 -
yytext
其为一个字符数组,里面存放的是匹配的字符串。 -
yylloc
其为位置信息,即匹配的token在整个输入流中的位置。通过这个值反馈给Bison中的yyparse. -
yyalloc
默认是malloc,这里通过%opiton noyylloc 屏蔽掉了,并定义了自己的版本为palloc -
yyrealloc
默认realloc, 通过%option noyyrealloc屏蔽,并定义了自己的版本 repalloc -
yyfree
默认为free, 通过%option noyyfree 屏蔽,并定义了自己的版本pfree -
yyleng
是匹配token对应字符串的长度。
2.1.3 代码段
这一部分通常包括从规则中调用的例程。具体内容会被lex逐字拷贝到C文件。
Scanner_errposition
如果可能,报告一个词法分析器或语法错误的光标位置。预期将在ereport()调用中使用它。 返回值是一个虚拟值(实际上始终为0)。请注意,这只能用于原始解析期间发出的消息(本质上是scan.l和gram.y),因为它要求scanbuf仍然有效。yyerror
报告词法分析器或语法错误。
消息的光标位置标识了最近词汇化的标记。 对于来自Bison解析器的语法错误消息,这是可以的,因为一旦到达第一个不可解析的令牌,Bison解析器就会报告错误。 注意不要将yyerror用于其他目的,因为光标位置可能会引起误解!
2.2 gram.y文件
Bison是yacc在GNU的实现。用来做语法分析,可以同Flex一起合作实现对sql语句的解析。
同样分为三段,定义段
,规则段
和代码段
。也是通过%%
做三个段的分割。源码文件为gram.y
, 最后通过Bison 编译源文件生成 gram.c.
Bison的工作原理主要通过两个堆栈,和shift,reduce
操作来完成。一个堆栈是负责处理符号的包括终结符号,非终结符符号。另一个堆栈是处理与之一一对应的值堆栈。
shift操作
,是从一个规则中冒号右侧的终结符号一个一个移动。例如 stmt1 : stmt2 OR stmt3 { … }; 这个规则中先匹配stmt2,然后做shift,匹配OR,直到最后shift到最后一个终结符号stmt3.每次shift操作,会将对应终结符号存入符号堆栈,匹配的值存入值堆栈。
reduce 规约操作
,当匹配到最后一个终结符号后,将符号堆栈和值堆栈中的所有值一起出栈,根据后面{ … }对应的运算处理,做完运算,将运算结果压入值堆栈,与之对应的符号压入符号堆栈。例如上面的例子中将{ … }中的运算结果压入值堆栈,将stmt1 压入符号堆栈。代表的意思就是stmt1 的值 为{ … }对应的运算结果。
2.2.1 定义段
-
{% ... %}
中的代码将被原样copy到生成的文件gram.c中.其中包含头文件包含,结构体定义和函数声明等 -
%pure-parser
声明此语法分析器是纯语法分析器。这样可以实现可重入。同时需要%parse-param {core_yyscan_t yyscanner} %lex-param {core_yyscan_t yyscanner}配合使用,即为了调用纯词法分析器flex,需要scanner实例,即需要传入这个参数.通过定义%parse-param 即可给yyparse()函数传入参数。定义%lex-param.即可把parse-param中定义的参数传递给yylex.
3)%expect 0
,意思是期待0个冲突。即不希望有任何冲突出现
4) %name-prefix="base_yy"
代表生成的函数和变量名从yy改成base_yy,同flex,为了在一个产品里使用多个语法分析器,分析不同的数据类。
-
%locations
声明使用位置信息。 -
%union{}
定义yylval类型,在flex中通过yylval的返回匹配的值。 -
%type< union
中的变量名 > 非终结符 ,此语法是定义非终结符(在规则段,: 左边是非终结符,右边是终结符,终结符)和union中变量的绑定。在Bison中,每个符号(终结符和非终结符)都有一个值与之对应。默认是一个整数值,为了扩展,可以定义union类型。 代表非终结符的值。这里的意思就是把 代表非终结符的值。这里的意思就是把 代表非终结符的值。这里的意思就是把和union中某一个变量绑定。 -
%token< union 中的变量名 > 总结符
, 此语法是定义终结符和union中变量的绑定。这样就可以在flex中直接通过yylval->(union中的变量名)返回匹配的值 -
%nonassoc symbol
, 用来定义有限集的。同%prec 联合使用可以定义某个表达式的优先级。例如
%nonassoc UMINUS 定义了个UMINUS.
%%
exp: '-' exp %prec UMINUS{ ... }这里的意思就是的('-' exp)运算优先级同UMINUS的优先级。
-
%left
代表操作符左匹配 例如:%left AND -
%right
代表操作符右匹配 例如:%right NOT -
优先级是通过由低到高,例如:
%nonassoc SET /* see relation_expr_opt_alias */%left UNION EXCEPT//在同一行代表优先级相同。%left INTERSECT%left OR%left AND//这里优先级最高 依次大于上面的OR INTERSECT... SET 优先级最低。
2.2.2 规则段
stmtblock: stmtmulti //stmtblock是非终结符号,冒号语法分隔符, stmtmulti代表终结符号,其值通过位置获取,因只有一个终结符,所以其值为$1. stmtblock的值,用$$表示。
{pg_yyget_extra(yyscanner)->parsetree = $1; //在生成的C文件中,用{}括起来的内容是用来替换前面匹配的此规则,
} ;//使用; 作为此规则的结束符。
-
通过@来获取终结符在规则中的位置信息. 例如:上面的规则中@1,代表的是stmtmulti对应的位置信息。这个位置信息是flex设置的。
-
$$ 代表非终结符对应的值,即规则中冒号左边符号对应的值。例如:
CreateOptRoleElem:AlterOptRoleElem { $$ = $1; }//$$代表冒号左边非终结符号的值,即CreateOptRoleElem的值。
- $NUM 代表终结符对应的值.例如:
CreateOptRoleElem:AlterOptRoleElem { $$ = $1; }//$1代表冒号右边第一个终结符的值即AlterOptRoleElem的值。
- ‘|’ 代表 or 的关系, 例如:
opt_unique:UNIQUE { $$ = TRUE; }//如果匹配到UNIQUE 这个token,会reduce为opt_unique| /*EMPTY*/ { $$ = FALSE; }//如果匹配到空,也会reduce为opt_unique.
三、语法分析树
查询命令在经过此法与与语法分析后会生成语法分析树、语法分析树的根节点是一个定义在parsenodes.h中的SelectStmt数据结构。图(a)展示了一个查询,而图(b)则是该查询对应的语法解析树(抽象语法树AST)。
1.typedef struct SelectStmt
2.{
3. NodeTag type;
4.
5. /* 这些字段只会在SelectStmts“叶节点”中使用 */
6. List *distinctClause; /* NULL, DISTINCT ON表达式列表, 或
7. 对所有的(SELECT DISTINCT)为lcons(NIL,NIL) */
8. IntoClause *intoClause; /* SELECT INTO 的目标 */
9. List *targetList; /* 结果目标列表 (ResTarget) */
10. List *fromClause; /* FROM 子句 */
11. Node *whereClause; /* WHERE 限定条件 */
12. List *groupClause; /* GROUP BY 子句 */
13. Node *havingClause; /* HAVING 条件表达式 */
14. List *windowClause; /* WINDOW window_name AS (...), ... */
15.
16. /* 在一个表示值列表的叶节点中,上面的字段全都为空,而这个字段会被设置。
17. * 注意这个子列表中的元素仅仅是表达式,没有ResTarget的修饰,还需要注意列表元素可能为
18. * DEFAULT (表示一个 SetToDefault 节点),而无论值列表的上下文。
19. * 由分析阶段决定否合法并拒绝。 */
20. List *valuesLists; /* 未转换的表达式列表 */
21.
22. /* 这些字段会同时在SelectStmts叶节点与SelectStmts上层节点中使用 */
23. List *sortClause; /* 排序子句 (排序依据的列表) */
24. Node *limitOffset; /* 需要跳过的元组数目 */
25. Node *limitCount; /* 需要返回的元组数目 */
26. List *lockingClause; /* FOR UPDATE (锁子句的列表) */
27. WithClause *withClause; /* WITH 子句 */
28.
29. /* 这些字段只会在上层的 SelectStmts 中出现 */
30. SetOperation op; /* set 操作的类型 */
31. bool all; /* 是否指明了 ALL 选项? */
32. struct SelectStmt *larg; /* 左子节点 */
33. struct SelectStmt *rarg; /* 右子节点 */
34.} SelectStmt;
SELECT查询中的元素和语法解析树中的元素有着对应关系。比如,(1)是目标列表中的一个元素,与目标表的’id’列相对应,(4)是一个WHERE子句,诸如此类。
当解析器生成语法分析树时只会检查语法,只有当查询中出现语法错误时才会返回错误。解析器并不会检查输入查询的语义,举个例子,如果查询中包含一个不存在的表名,解析器并不会报错,语义检查由分析器负责。
四、具体函数分析
4.1 raw_parser
1.List *raw_parser(const char *str, RawParseMode mode) {
2. // 定义Flex扫描器的上下文
3. core_yyscan_t yyscanner;
4. // 定义Bison解析器的额外数据结构
5. base_yy_extra_type yyextra;
6. // 定义变量,用于存储Bison解析器的返回结果
7. int yyresult;
8. // 初始化Flex扫描器
9. yyscanner = scanner_init(str, &yyextra.core_yy_extra, &ScanKeywords, ScanKeywordTokens);
10. // 根据传入的解析模式设置前瞻标记
11. if (mode == RAW_PARSE_DEFAULT) {
12. // 默认模式,不使用前瞻标记
13. yyextra.have_lookahead = false;
14. } else {
15. // 非默认模式,使用前瞻标记
16. yyextra.have_lookahead = true;
17. // 定义一个静态数组,存储不同模式对应的标记
18. static const int mode_token[] = {
19. 0, // RAW_PARSE_DEFAULT
20. MODE_TYPE_NAME, // RAW_PARSE_TYPE_NAME
21. MODE_PLPGSQL_EXPR, // RAW_PARSE_PLPGSQL_EXPR
22. MODE_PLPGSQL_ASSIGN1, // RAW_PARSE_PLPGSQL_ASSIGN1
23. MODE_PLPGSQL_ASSIGN2, // RAW_PARSE_PLPGSQL_ASSIGN2
24. MODE_PLPGSQL_ASSIGN3 // RAW_PARSE_PLPGSQL_ASSIGN3
25. };
26. // 设置前瞻标记为对应模式的值
27. yyextra.lookahead_token = mode_token[mode];
28. // 初始化前瞻标记的位置信息
29. yyextra.lookahead_yylloc = 0;
30. // 初始化前瞻标记的结束位置
31. yyextra.lookahead_end = NULL;
32. }
33. // 初始化Bison解析器,传入额外数据结构的地址
34. parser_init(&yyextra);
35. // 调用Bison解析器进行解析,并将结果存储在yyresult中
36. yyresult = base_yyparse(yyscanner);
37. // 清理Flex扫描器使用的资源
38. scanner_finish(yyscanner);
39. // 如果解析失败(yyresult非零),返回NIL表示没有解析树
40. if (yyresult) return NIL;
41. // 如果解析成功,返回解析生成的解析树
42. return yyextra.parsetree;
43.}
1.
List *raw_parser(const char *str, RawParseMode mode)
函数声明,返回一个列表指针,接收两个参数:一个字符串str和一个枚举类型RawParseMode的mode。
(1) RawParseMode 是一个枚举类型,它在 raw_parser 函数中定义了不同的解析模式。
(2) RAW_PARSE_DEFAULT: 默认解析模式,不使用前瞻标记,解析器将根据输入的文本内容进行标准解析。
(3) RAW_PARSE_TYPE_NAME: 用于解析类型名称的模式。这可能涉及到特定的语法规则,比如在某些语言中类型名称可能有特定的格式或关键字。
(4) RAW_PARSE_PLPGSQL_EXPR: 用于解析 PL/pgSQL 表达式的模式。PL/pgSQL 是 PostgreSQL 数据库的过程语言,这个模式可能用于解析特定的 SQL 表达式结构。
(5) RAW_PARSE_PLPGSQL_ASSIGN1,RAW_PARSE_PLPGSQL_ASSIGN2, RAW_PARSE_PLPGSQL_ASSIGN3: 这些模式可能用于解析 PL/pgSQL 中的赋值语句,每个模式可能对应不同的赋值语法或上下文。
例子:
本文使用如下例子作为相关示例
CREATE TABLE tbl_a (id INT PRIMARY KEY,data VARCHAR(255) -- 假设data是字符串,长度限制为255
);
INSERT INTO tbl_a (id, data) VALUES
(1, 'Apple'),
(50, 'Banana'),
(150, 'Cherry'),
(250, 'Date'),
(299, 'Elderberry');
SELECT id, data FROM tbl_a WHERE id < 300 ORDER BY data;
2.
core_yyscan_t yyscanner;
base_yy_extra_type yyextra;
core_yyscan_t为void *,base_yy_extra_type为一个结构体。
Flex和Bison是两个在编译领域广泛使用的工具,它们通常配合使用来构建编译器的前端。Flex是一个用于生成词法分析器(lexical analyzer)的工具对应于Lex,而Bison是一个用于生成语法分析器(parser)的工具,对应于Yacc。
3.
yyscanner = scanner_init(...)
调用scanner_init函数位于scan.c文件中,用于初始化Flex扫描器,传入字符串str,yyextra的地址,以及关键词和关键字对应的标记。
4.
if (mode == RAW_PARSE_DEFAULT) { // 默认模式,不使用前瞻标记 yyextra.have_lookahead = false; } else { // 非默认模式,使用前瞻标记 yyextra.have_lookahead = true; // 定义一个静态数组,存储不同模式对应的标记 static const int mode_token[] = { 0, // RAW_PARSE_DEFAULT MODE_TYPE_NAME, // RAW_PARSE_TYPE_NAME MODE_PLPGSQL_EXPR, // RAW_PARSE_PLPGSQL_EXPR MODE_PLPGSQL_ASSIGN1, // RAW_PARSE_PLPGSQL_ASSIGN1 MODE_PLPGSQL_ASSIGN2, // RAW_PARSE_PLPGSQL_ASSIGN2 MODE_PLPGSQL_ASSIGN3 // RAW_PARSE_PLPGSQL_ASSIGN3
};
// 设置前瞻标记为对应模式的值yyextra.lookahead_token = mode_token[mode];
// 初始化前瞻标记的位置信息 yyextra.lookahead_yylloc = 0; // 初始化前瞻标记的结束位置 yyextra.lookahead_end = NULL;
代码片段中,yyextra.have_lookahead是一个标志,用于指示是否使用前瞻标记。如果mode设置为RAW_PARSE_DEFAULT,则不使用前瞻标记,yyextra.have_lookahead被设置为false。否则,将使用前瞻标记,并且yyextra.have_lookahead被设置为true。
static const int mode_token[]是一个静态数组,它定义了不同模式对应的标记。这个数组用于根据当前的解析模式来确定应该使用哪种类型的token作为前瞻。例如,如果当前模式是RAW_PARSE_TYPE_NAME,则mode_token[RAW_PARSE_TYPE_NAME]将提供对应的token类型。
使用前瞻标记的一些好处包括:
a.解决歧义:前瞻可以帮助解析器解决语法中的歧义问题,特别是在复杂的语言结构中。
b.提高效率:在某些情况下,使用前瞻可以避免回溯,提高解析的效率。
c.增强灵活性:前瞻允许解析器根据更多的上下文信息来做出决策,从而增强解析器的灵活性。
在实际应用中,前瞻标记通常与解析器的设计紧密相关,它们是构建高效、健壮解析器的关键技术之一。
5. 最终输出
4.2 pg_parse_query
1.List *pg_parse_query(const char *query_string)
2.{
3. List *raw_parsetree_list; /* 用于存储原始分析树的列表 */
4. /*
5. * 该宏用于跟踪和记录查询解析过程的开始
6. */
7. TRACE_POSTGRESQL_QUERY_PARSE_START(query_string);
8. /*
9. * 用来指示是否应该记录解析器的统计信息,log_parser_stats默认为false
10. * ResetUsage用来重置某些统计信息的使用计数器或状态。
11. */
12. if (log_parser_stats)
13. ResetUsage();
14. /*
15. * 调用 raw_parser 函数分析查询字符串。
16. * RAW_PARSE_DEFAULT 表示使用默认的分析器标志。
17. */
18. raw_parsetree_list = raw_parser(query_string, RAW_PARSE_DEFAULT);
19. /*
20. * 如果记录分析器统计信息,显示统计结果。
21. */
22. if (log_parser_stats)
23. ShowUsage("PARSER STATISTICS");
24. /*
25. * 如果定义了 COPY_PARSE_PLAN_TREES,则执行以下调试检查:
26. *使用 copyObject 函 数复制 raw_parsetree_list 并存储在 new_list 中。
27. *使用 equal 函数比较 new_list 和 raw_parsetree_list 是否相等。
28. *如果不相等,使用 elog 函数记录一个警告。
29. *如果相等,将 raw_parsetree_list 指向 new_list。
30. */
31. #ifdef COPY_PARSE_PLAN_TREES
32. {
33. List *new_list = copyObject(raw_parsetree_list);
34. if (!equal(new_list, raw_parsetree_list))
35. elog(WARNING, "copyObject() failed to produce an equal raw parse tree");
36. else
37. raw_parsetree_list = new_list; }
38.#endif
39. /*
40. * 该宏用于跟踪和记录查询解析过程的结束
41. */
42. TRACE_POSTGRESQL_QUERY_PARSE_DONE(query_string);
43. /*
44. * 返回分析树列表。
45. */
46. return raw_parsetree_list;
47.}
总的来说,pg_parse_query函数是用于解析SQL查询字符串,它首先记录解析开始,然后调用解析器获取解析树,如果启用了统计信息记录,会显示解析统计结果。此外,如果启用了调试宏COPY_PARSE_PLAN_TREES,函数还会进行解析树的复制和比较,确保复制操作的正确性。最后,记录解析结束并返回解析树列表。
作者介绍
葛文龙,移动云数据库助理工程师,负责云原生数据库He3DB的研发。