Postgresql如何在插件内实现lex/yacc语法解析

2022-11-06 09:29:56 浏览数 (1)

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);   <--- 遍历语义树结束,继续向下执行
...

0 人点赞