相关: 《Postgresql源码(44)server端语法解析流程分析》 《Postgresql源码(50)语法解析时关键字判定原理(函数名不能使用的关键字为例)》
一、语法解析整体流程
语法解析封装的函数比较多看起来不太容易理解,其实核心逻辑比较简单:
1、raw_parser作为高层入口
2、raw_parser初始化后,通过base_yyparse进入yacc框架
3、yacc框架中调用base_yylex进入lex拿一个token(正常用框架是每次拿一个,PG通过对lex函数的封装可以拿后面多个,有些语法需要看到后面多个一块解析)
4、拿回来token后,进入语法树开始递归(有点像后续遍历,从底层开始向上构造语法节点,实际是用两个堆栈解析每一层语法规则,原理也比较简单,见第二节)。
5、从语法树底层节点向上reduce,识别收集文本中的目标信息,创建对应的stmt结构体,填入数据,返回上层。
执行流程如下图:
二、base_yylex解析实例
1、流程总结
(1)base_yylex函数进入时会优先check有没有预读的token,检查base_yy_extra_type的几个ahead变量即可。
(2)如果有预读的token就直接用了,不再重新解析
(3)如果没有预读的token,调core_yylex从lex拿一个token出来,如果是普通token直接返回yacc继续reduce
(4)如果不是普通token(目前定义了一些即not like、with time等等),再调一次core_yylex把下一个token读出来,同时记录到ahead的几个变量中。
(5)然后把curr token和next token放在一起做一些处理,例如not本来要返回NOT,预读到下一个是like,则本次返回NOT_LA。
2、测试SQL
代码语言:javascript复制select * from sbtest1 where c not like 'h487932199%';
620 42 427 258 708 258 524 588
SELECT * FROM IDENT WHERE IDENT NOT LIKE
3、从524(NOT)开始,会进入函数的Look ahead逻辑,这里做一些分析:
关键数据结构,除了解析过程必须的core_yy_extra、parsetree,中间的几个变量都用来向前看token。
代码语言:javascript复制typedef struct base_yy_extra_type
{
/*
* Fields used by the core scanner.
*/
core_yy_extra_type core_yy_extra;
/*
* State variables for base_yylex().
*/
bool have_lookahead; /* is lookahead info valid? */
int lookahead_token; /* one-token lookahead */
core_YYSTYPE lookahead_yylval; /* yylval for lookahead token */
YYLTYPE lookahead_yylloc; /* yylloc for lookahead token */
char *lookahead_end; /* end of current token */
char lookahead_hold_char; /* to be put back at *lookahead_end */
/*
* State variables that belong to the grammar.
*/
List *parsetree; /* final parse result is delivered here */
} base_yy_extra_type;
函数流程分析,从not like 'h487932199%';
开始:
int
base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
{
base_yy_extra_type *yyextra = pg_yyget_extra(yyscanner);
int cur_token;
int next_token;
int cur_token_length;
YYLTYPE cur_yylloc;
if (yyextra->have_lookahead)
{
cur_token = yyextra->lookahead_token;
lvalp->core_yystype = yyextra->lookahead_yylval;
*llocp = yyextra->lookahead_yylloc;
if (yyextra->lookahead_end)
*(yyextra->lookahead_end) = yyextra->lookahead_hold_char;
yyextra->have_lookahead = false;
}
else
/* 走这个分支拿到`NOT` */
cur_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
/* 只有前面集中情况需要Look ahead,其他情况直接返回给语法树即可 */
/* 这里cur_token=NOT,不直接返回 */
switch (cur_token)
{
case NOT:
cur_token_length = 3;
break;
case NULLS_P:
cur_token_length = 5;
break;
case WITH:
cur_token_length = 4;
break;
case UIDENT:
case USCONST:
cur_token_length = strlen(yyextra->core_yy_extra.scanbuf *llocp);
break;
default:
return cur_token;
}
// (gdb) p yyextra->core_yy_extra.scanbuf
// $39 = 0x2ebfbc8 "select * from sbtest1 where c not"(缓存的字符串)
// (gdb) p *llocp
// $40 = 30(当前token的起始位置!)
// (gdb) p cur_token_length
// $41 = 3(在上面switch中设置的token长度)
yyextra->lookahead_end = yyextra->core_yy_extra.scanbuf
*llocp cur_token_length;
Assert(*(yyextra->lookahead_end) == '