您好,欢迎来到六九路网。
搜索
您的当前位置:首页对rewriteHandler.cpp的解析(二)

对rewriteHandler.cpp的解析(二)

来源:六九路网

源码链接

概述

        上一篇博客:,我们主要解析了查询重写模块的入口函数 pg_rewrite_query() 在重写查询树时用到的 QueryRewrite() 函数以及不重写查询树时用到的函数 list_make1()。QueryRewrite() 的复杂程度比 list_make1() 要高,这篇博客我来解析一下 QueryRewrite() 中调用的两个重要函数 RewriteQuery() 和 fireRIRrules()。

解析

RewriteQuery()

//代码清单1
//src/gausskernel/optimizer/rewrite/rewriteHandler.cpp
static List* RewriteQuery(Query* parsetree, List* rewrite_events)
{
    CmdType event = parsetree->commandType;
    bool instead = false;
    bool returning = false;
    Query* qual_product = NULL;
    List* rewritten = NIL;
    ListCell* lc1 = NULL;
······
    foreach (lc1, parsetree->cteList)
    {
        CommonTableExpr* cte = (CommonTableExpr*)lfirst(lc1);
        Query* ctequery = (Query*)cte->ctequery;
        List* newstuff = NIL;

        AssertEreport(IsA(ctequery, Query), MOD_OPT, "");

        if (ctequery->commandType == CMD_SELECT)
            continue;
······
    }

    if (event != CMD_SELECT && event != CMD_UTILITY)
    {
        int result_relation;
        RangeTblEntry* rt_entry = NULL;
        Relation rt_entry_relation;
        List* locks = NIL;

        result_relation = parsetree->resultRelation;

        if (result_relation == 0)
            return rewritten;
······
        if (!instead)
        {
            if (parsetree->commandType == CMD_INSERT) 
            {
                if (qual_product != NULL)
                    rewritten = lcons(qual_product, rewritten);
                else
                    rewritten = lcons(parsetree, rewritten);
            } 
            else 
            {
                if (qual_product != NULL)
                    rewritten = lappend(rewritten, qual_product);
                else
                    rewritten = lappend(rewritten, parsetree);
            }
        }
······    
    }
······
    return rewritten;
}

         在前一篇博客中我们已经知道,RewriteQuery() 被 QueryRewrite() 调用用来对由 INSERT/DELETE/UPDATE 语句构造而来的查询树进行重写,具体的操作我们来看一看代码清单1中的代码。

        首先是代码清单1中第12行开始的 foreach 循环,实际上是 for 循环,关于这个可以看一下我之前写的这篇博客:的代码清单4,在此之前,我们需要明白 CTE 是什么。CTE 全称为 Common Table Experssion,表示公用表表达式,是一个临时命名结果集,始终返回结果集,基本格式如下:

//代码清单3
WITH <cte_name>[(column list)] AS (<inner query defining the CTE>) <outer query: SELECT | INSERT | UPDATE | DELETE | MERGE>

这可能不太好看懂,那我举一个例子,我先创建一个表并且向里面添加一些数据:

//代码清单4
create table student(name text primary key,
sex text,
age integer,
address text);

insert into student values
("小明","男",15,"江西"),
("小黄","男",14,"四川"),
("小黑","男",18,""),
("小红","女",18,"江苏"),
("小紫","女",17,"黑龙江");

然后,我们写一个 CTE,如下:

//代码清单5
with temptable(name,age)
as (select name,age from student where sex="男")
select * from temptable;

看一下执行结果: 

结果很正常,也许有读者要问为什么我用的是 sqlite 而不是 PostgreSQL,这个是小问题,在这里重要的是 SQL,而不是哪个 DBMS,我们对这个 CTE 做一下划分:

这就很清楚了,说到底 CTE 就像是创建了一个临时表,它不会被存到磁盘上,我们正是利用 WITH 子句创建了 CTE。回到这个 foreach 循环中来,我们将要重写的,是 INSERT/UPDATE/DELETE 的 WITH 子句,其实就是 CTE 嘛。如果存在,那么就重写它们。如果想更详细地了解一下其代码,那建议看一下这篇博客:。

        代码清单1第41行开始的 if 判断结构用来处理 RTE,RTE 全名叫 RangeTable Entry,意即范围表,它描述了查询中出现的表,它通常出现在查询语句的 FROM 子句中,范围表中既有常规意义上的堆表,还有子查询、连接表等。

fireRIRrules()

//代码清单6
//src/gausskernel/optimizer/rewrite/rewriteHandler.cpp
static Query* fireRIRrules(Query* parsetree, List* activeRIRs, bool forUpdatePushedDown)
{
    int origResultRelation = parsetree->resultRelation;
    int rt_index;
    ListCell* lc = NULL;
    rt_index = 0;
    while (rt_index < list_length(parsetree->rtable)) 
    {
        RangeTblEntry* rte = NULL;
        Relation rel;
        List* locks = NIL;
        RuleLock* rules = NULL;
        RewriteRule* rule = NULL;
        int i;
        ++rt_index;
        rte = rt_fetch(rt_index, parsetree->rtable);
        if (rte->rtekind == RTE_SUBQUERY) 
        {
            rte->subquery = fireRIRrules(rte->subquery, activeRIRs, (forUpdatePushedDown || get_parse_rowmark(parsetree, rt_index) != NULL));
            continue;
        }
        if (rte->rtekind != RTE_RELATION) {
            continue;
        }
        if (rte->relkind == RELKIND_MATVIEW) {
            continue;
        }
······
        rel = heap_open(rte->relid, NoLock);
        rules = rel->rd_rules;
        if (rules == NULL) {
            heap_close(rel, NoLock);
            continue;
        }
        for (i = 0; i < rules->numLocks; i++) {
            rule = rules->rules[i];
            if (rule->event != CMD_SELECT) {
                continue;
            }

            if (rule->attrno > 0) {
                /* per-attr rule; do we need it? */
                if (!attribute_used((Node*)parsetree, rt_index, rule->attrno, 0))
                    continue;
            }

            locks = lappend(locks, rule);
        }
        if (locks != NIL) {
            ListCell* l = NULL;

            if (list_member_oid(activeRIRs, RelationGetRelid(rel))) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                        errmsg(
                            "infinite recursion detected in rules for relation \"%s\"", RelationGetRelationName(rel))));
            }
            activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);

            foreach (l, locks) {
                rule = (RewriteRule*)lfirst(l);

                parsetree = ApplyRetrieveRule(parsetree, rule, rt_index, rule->attrno == -1, rel, activeRIRs, forUpdatePushedDown);
            }

            activeRIRs = list_delete_first(activeRIRs);
        }

        heap_close(rel, NoLock);
    }

    foreach (lc, parsetree->cteList) {
        CommonTableExpr* cte = (CommonTableExpr*)lfirst(lc);
        cte->ctequery = (Node*)fireRIRrules((Query*)cte->ctequery, activeRIRs, false);
    }

    if (parsetree->hasSubLinks) {
        (void)query_tree_walker(parsetree, (bool (*)())fireRIRonSubLink, (void*)activeRIRs, QTW_IGNORE_RC_SUBQUERIES);
    }
······
    return parsetree;
}

        如果得到的范围表是一个子查询,则递归调用 fireRIRrules() 函数,对它进行重写;如果不是一张表,则直接跳过该 while 循环剩余部分;如果是一个物化视图,则同样直接跳过。

        利用第36行的 heap_open() 来打开范围表,即根据 relid 获取"关系",并且获取关系上我们需要的规则。如果规则不为空,那么我们挑出这些规则中的 SELECT 规则来,之后应用这些规则去重写查询树,待这些工作都做完后,利用 table_close() 释放资源。

        然后,就是调用 fireRIRrules() 对 WITH 子句进行重写操作,之后,如果查询树还有子链接(子查询),则调用 query_tree_walker() 函数对整个查询树的子链接进行遍历,并使用  fireRIRonSubLink() 函数进行重写处理,而 fireRIRonSubLink() 函数实际上也是调用 fireRIRrules() 函数来完成重写工作。

总结

        总地来说,RewriteQuery() 和 fireRIRrules() 还是有着很明显的界限,对于可重写的非功能性语句即查询语句,有 SELECT/DELETE/UPDATE/INSERT 四种,而 RewriteQuery() 用来处理由 INSERT/DELETE/UPDATE 语句构造而来的查询树,fireRIRrules() 则用来处理由 SELECT 语句构造而来的查询树,这便是二者的最大区别。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 69lv.com 版权所有 湘ICP备2023021910号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务