相关: 《Postgresql源码(44)server端语法解析流程分析》 《Postgresql源码(50)语法解析时关键字判定原理(函数名不能使用的关键字为例)》
关键字报错场景
关键字不出现,或出现在函数内部:创建成功
代码语言:javascript复制CREATE OR REPLACE FUNCTION fn1(x int) RETURNS int AS $$
BEGIN
RETURN x;
END;
$$ LANGUAGE plpgsql;
postgres=# CREATE FUNCTION
CREATE OR REPLACE FUNCTION fn2(x int) RETURNS int AS $$
DECLARE
normalize int;
BEGIN
RETURN x;
END;
$$ LANGUAGE plpgsql;
postgres=# CREATE FUNCTION
关键字出现在函数名、函数参数中:创建失败
代码语言:javascript复制CREATE OR REPLACE FUNCTION fn2(normalize int) RETURNS int AS $$
BEGIN
RETURN normalize;
END;
$$ LANGUAGE plpgsql;
postgres=# ERROR: syntax error at or near "normalize"
postgres=# LINE 1: CREATE OR REPLACE FUNCTION fn2(normalize int) RETURNS int AS...
CREATE OR REPLACE FUNCTION normalize(x int) RETURNS int AS $$
BEGIN
RETURN x;
END;
$$ LANGUAGE plpgsql;
postgres=# ERROR: syntax error at or near "normalize"
postgres=# LINE 1: CREATE OR REPLACE FUNCTION fn2(normalize int) RETURNS int AS...
解析过程分析
已创建失败的函数normalize为例,分析语法解析过程
CREATE OR REPLACE FUNCTION normalize(x int) RETURNS int AS $$
调试方法参考:《Postgresql源码(44)server端语法解析流程分析》
解析过程总结:
代码语言:javascript复制[lex]
CREATE = 352
OR = 544
REPLACE = 595
FUNCTION = 429
==========================================
[yacc]
opt_or_replace:
OR REPLACE { $$ = true; }
| /*EMPTY*/ { $$ = false; }
;
==========================================
[lex]
IDENT = 258
(
==========================================
[yacc]
type_function_name: IDENT { $$ = $1; } <--------- 走这里
| unreserved_keyword { $$ = pstrdup($1); }
| type_func_name_keyword { $$ = pstrdup($1); }
;
func_name: type_function_name
{ $$ = list_make1(makeString($1)); } <--------- 走这里
...
;
从下面这里开始有问题了,函数名normalize被解析成关键字了,base_yylex返回的是NORMALIZE,如果是普通函数名应该返回IDENT。
lex返回522后,yacc语法树没有匹配项了,返回错误。
代码语言:javascript复制[lex]
NORMALIZE = 522
[yacc]
if (!yyerrstatus)
{
yynerrs;
yyerror (&yylloc, yyscanner, YY_("syntax error"));
...
}
判定原理
base_yylex调用core_yylex解析时,如果匹配到关键字,就会返回gram.c中enum yytokentype
的关键字。
core_yylex需要返回它遇到的标识符类型并将其值存储在yylval中,这些标识符在gram.y中定义:
代码语言:javascript复制gram.y
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
ASENSITIVE ASSERTION ASSIGNMENT ASYMMETRIC ATOMIC AT ATTACH ATTRIBUTE AUTHORIZATION
...
这些标识符主要是给lex使用的,在lex匹配到正则规则时,返回其中一个token。
所有的关键字都在gram.y文件中使用%token
表示了,这些关键字应该都不能用于 表名、列名等对象名等,可能会造成shift/reduce冲突。但其实很多也不会触发冲突,为了使用这些关键字,在gram.y文件后面专门定义了几组语法规则:
- unreserved_keyword:可以用于任意命名场景,如果新增的关键字不会引发shift/reduce冲突,可以放在这个列表中。
- col_name_keyword:可用于列名、表名,但不能用于函数名。
- type_func_name_keyword:可用于函数名、类型名。
- reserved_keyword:只能用于列别名(例如:select name as all from tbl;)
- bare_label_keyword:只能用于列名,但可以省略as(例如:select name all from tbl;)
unreserved_keyword:
ABORT_P
| ABSOLUTE_P
| ACCESS
| ACTION
| ADD_P
...
col_name_keyword:
BETWEEN
| BIGINT
| BIT
| BOOLEAN_P
| CHAR_P
| CHARACTER
...
type_func_name_keyword:
AUTHORIZATION
| BINARY
| COLLATION
| CONCURRENTLY
| CROSS
...
reserved_keyword:
ALL
| ANALYSE
| ANALYZE
| AND
| ANY
| ARRAY
...
bare_label_keyword:
ABORT_P
| ABSOLUTE_P
| ACCESS
| ACTION
| ADD_P
| ADMIN
| AFTER
...
kwlist.h 的增加方法
创建新关键字时需要在kwlist.h中增加PG_KEYWORD。
增加方法:先确定新增关键字会不会造成语法冲突歧义等,加到上面5个list中,然后根据能否用于表名、列名、as等场景,在kwlist中增加即可。
代码语言:javascript复制/* name, value, category, is-bare-label */
PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD, BARE_LABEL)
...