Postgresql提供了十分强大的插件系统,有多强大呢?你是否想过在插件内构造一套自己的语法解析逻辑,实现一套完全自定义的语言?Age插件提供了很好的思路可以借鉴,本篇尝试分析。
1 概要
本篇分享一个插件内自带语法解析的框架——Postgresql图插件AGE,不关注插件的具体功能,只关注插件的框架。
插件通过一个函数cypher,在函数的第二个参数位置,传入了一套openCypher语法(图语法)。 例如:
代码语言:javascript复制SELECT * FROM cypher('graph_name', $$
MATCH (director {name: 'Oliver Stone'})-[]-(movie)
RETURN movie.title
$$) as (title agtype);
插件内部通过钩子的方式,将函数第二个参数的文本传入插件内部的语法、语义解析、优化器进行处理,构造了一套旁路SQL解析流程,很值得借鉴学习。
2 关键步骤分析
插件需要安装在PG11上,安装方法:
代码语言:javascript复制CREATE EXTENSION age;
SET search_path = ag_catalog, "$user", public;
-- 创建图
SELECT * FROM ag_catalog.create_graph('graph_name');
-- 执行图查询MATCH
SELECT * FROM cypher('graph_name', $$
MATCH (v)
RETURN v
$$) as (v agtype);
下面重点分析下面语句的执行关键位点。
代码语言:javascript复制SELECT * FROM cypher('graph_name', $$
MATCH (v)
RETURN v
$$) as (v agtype);
【1】主解析器parse_analyze回调进入插件
主解析器完成语法解析后,进入parse_analyze函数做语义分析:transformTopLevelStmt。
语义分析后回调钩子post_parse_analyze_hook进入插件:
代码语言:javascript复制Query *
parse_analyze(RawStmt *parseTree, const char *sourceText,
Oid *paramTypes, int numParams,
QueryEnvironment *queryEnv)
...
query = transformTopLevelStmt(pstate, parseTree);
// 进入插件
if (post_parse_analyze_hook)
(*post_parse_analyze_hook) (pstate, query);
...
进入插件时Query的结构:
【2】进入插件后递归语义树convert_cypher_walker
使用query_tree_walker函数递归Query树,
**重要**:但是:query_tree_walker函数是可以定制递归方法的
注意下面调用时query_tree_walker的第二个参数传入的convert_cypher_walker:
代码语言:javascript复制post_parse_analyze
convert_cypher_walker
if (IsA(node, Query))
query_tree_walker(query, convert_cypher_walker, pstate, flags) // 调用PG的函数,递归Query树,第二个参数传入递归方法
convert_cypher_walker是插件提供的方法。
这样在query_tree_walker函数递归时,会进入convert_cypher_walker插件中执行新逻辑:
注意在convert_cypher_walker结尾返回时,继续进入PG的表达式递归函数中:同样将递归函数传入插件提供的convert_cypher_walker。
【3】递归遍历到RangeTblEntry节点时进入插件语法解析逻辑
插件的walker在发现RangeTblEntry后,如果满足:
RangeTblEntry
RangeTblEntry->rtekind == RTE_FUNCTION
两点,则开始进入语法解析逻辑:convert_cypher_to_subquery
代码语言:javascript复制static bool convert_cypher_walker(Node *node, ParseState *pstate)
{
if (!node)
return false;
if (IsA(node, RangeTblEntry))
{
RangeTblEntry *rte = (RangeTblEntry *)node;
switch (rte->rtekind)
{
case RTE_SUBQUERY:
// traverse other RTE_SUBQUERYs
return convert_cypher_walker((Node *)rte->subquery, pstate);
case RTE_FUNCTION:
if (is_rte_cypher(rte))
convert_cypher_to_subquery(rte, pstate);
return false;
default:
return false;
}
}
【4】convert_cypher_to_subquery语法解析准备解析文本
从FuncExpr->args
中拿到Const取出需要解析的文本:
【5】convert_cypher_to_subquery语法解析
代码语言:javascript复制convert_cypher_to_subquery
parse_cypher(query_str) // <----- "nMATCH (v)nRETURN vn"
与主语法解析器类似,lex函数:cypher_yylex
代码语言:javascript复制[mingjie@VM-130-23-centos ~/pgroot11/pgsrc/contrib/age/src/backend/parser]$ ll
total 6392
-rw-r--r-- 1 mingjie root 201831 Nov 1 21:31 ag_scanner.c
-rw-r--r-- 1 mingjie root 31606 Nov 1 21:27 ag_scanner.l <----- lex
-rw-r--r-- 1 mingjie root 332168 Nov 1 21:31 ag_scanner.o
-rw-r--r-- 1 mingjie root 23707 Nov 1 21:27 cypher_analyze.c
-rw-r--r-- 1 mingjie root 677488 Nov 1 21:31 cypher_analyze.o
-rw-r--r-- 1 mingjie root 202332 Nov 1 21:27 cypher_clause.c
-rw-r--r-- 1 mingjie root 905720 Nov 1 21:31 cypher_clause.o
-rw-r--r-- 1 mingjie root 43740 Nov 1 21:27 cypher_expr.c
-rw-r--r-- 1 mingjie root 728696 Nov 1 21:31 cypher_expr.o
-rw-r--r-- 1 mingjie root 188380 Nov 1 21:31 cypher_gram.c
-rw-r--r-- 1 mingjie root 585568 Nov 1 21:31 cypher_gram.o
-rw-r--r-- 1 mingjie root 61985 Nov 1 21:27 cypher_gram.y <--- yacc
-rw-r--r-- 1 mingjie root 7968 Nov 1 21:27 cypher_item.c
-rw-r--r-- 1 mingjie root 472472 Nov 1 21:31 cypher_item.o
-rw-r--r-- 1 mingjie root 5637 Nov 1 21:27 cypher_keywords.c
-rw-r--r-- 1 mingjie root 435760 Nov 1 21:31 cypher_keywords.o
-rw-r--r-- 1 mingjie root 29986 Nov 1 21:27 cypher_parse_agg.c
-rw-r--r-- 1 mingjie root 363368 Nov 1 21:31 cypher_parse_agg.o
-rw-r--r-- 1 mingjie root 4827 Nov 1 21:27 cypher_parse_node.c
-rw-r--r-- 1 mingjie root 475848 Nov 1 21:31 cypher_parse_node.o
-rw-r--r-- 1 mingjie root 4076 Nov 1 21:27 cypher_parser.c <----- lex函数封装cypher_yylex
-rw-r--r-- 1 mingjie root 245344 Nov 1 21:31 cypher_parser.o
-rw-r--r-- 1 mingjie root 4078 Nov 1 21:27 cypher_transform_entity.c
-rw-r--r-- 1 mingjie root 468528 Nov 1 21:31 cypher_transform_entity.o
语法解析生成两节点:
代码语言:javascript复制{data = {ptr_value = 0x2774788, int_value = 41371528, oid_value = 41371528}, next = 0x2774a70}
{data = {ptr_value = 0x27749d8, int_value = 41372120, oid_value = 41372120}, next = 0x2774a98}
ExtensibleNode = {type = T_ExtensibleNode, extnodename = 0x7fe1e6b0a462 "cypher_match"}
ExtensibleNode = {type = T_ExtensibleNode, extnodename = 0x7fe1e6b0a448 "cypher_return"}
【5】analyze_cypher_and_coerce语义分析生成Query
插件生成语义树:
【6】语义树递归完毕,退回PG逻辑继续执行
代码语言:javascript复制Query *
parse_analyze(RawStmt *parseTree, const char *sourceText,
Oid *paramTypes, int numParams,
QueryEnvironment *queryEnv)
...
query = transformTopLevelStmt(pstate, parseTree);
// 进入插件
if (post_parse_analyze_hook)
(*post_parse_analyze_hook) (pstate, query); <--- 遍历语义树结束,继续向下执行
...