postgresql中query tree内存结构

来源:互联网 发布:哪种理财软件好 编辑:程序博客网 时间:2024/05/01 17:22
话说上次分析了parsetree的内存结构(参看:postgresql中parsetree内存结构http://www.sciencenet.cn/m/user_content.aspx?id=309594 ),那么在postgresql的执行流程中接下来就是生成querytree了,那么querytree在内存中是什么样子的呢?先看调用堆栈:
Thread [1] (Suspended)   
    9 parse_analyze() /home/postgres/develop/postgresql-snapshot/src/backend/parser/analyze.c:82  
    8 pg_analyze_and_rewrite() /home/postgres/develop/postgresql-snapshot/src/backend/tcop/postgres.c:606
    7 exec_simple_query() /home/postgres/develop/postgresql-snapshot/src/backend/tcop/postgres.c:918
    6 PostgresMain() /home/postgres/develop/postgresql-snapshot/src/backend/tcop/postgres.c:3616
    5 BackendRun() /home/postgres/develop/postgresql-snapshot/src/backend/postmaster/postmaster.c:3449
    4 BackendStartup() /home/.../src/backend/postmaster/postmaster.c:3063
    3 ServerLoop() /home/postgres/develop/postgresql-snapshot/src/backend/postmaster/postmaster.c:1387
    2 PostmasterMain() /home/postgres/.../postmaster/postmaster.c:1040
    1 main() /home/postgres/develop/postgresql-snapshot/src/backend/main/main.c:188 0x081e7ee7  

再把我们的前提条件摆出来一下:
假定我们数据库中已经有如下表,并填充了数据:
CREATE TABLE pois
(
   uid integer not null,
   catcode VARCHAR(32)  not null,
);
现在我们用psql发送请求:select catcode from pois;
程序已经执行完了 parsetree_list = pg_parse_query(query_string);

接下来这位老先生querytree_list = pg_analyze_and_rewrite(parsetree, query_string, NULL, 0); 就是我们要拜访的主了。
推荐你最好知道到这一步之前,系统在内存中是什么样子的?都初始化了哪些数据结构?具体可参照:PostgresMain()中重要的几个初始化
好了,我们开工。
我们先看看:
Query *parse_analyze(Node *parseTree, const char *sourceText, Oid *paramTypes, int numParams){
    ParseState *pstate = make_parsestate(NULL);
    Query       *query;
    Assert(sourceText != NULL); /* required as of 8.4 */
    ...
    query = transformStmt(pstate, parseTree);
    free_parsestate(pstate);
    return query;
}
看样子,ParseState和Query 我们是非要看看不可了。
先看Query,如下图:

看样子,Querytree是集大成者,无论你是delete,还是update,还是select,经过parsetree之后都要在他老人家这个山头路过。从图中看来,我们的parsetree看样子要和Querytree里的targetlist和rtable字段有个对应了。
我们再来简单看看
ParseState,如下图:

如同postgresql中很多个其他类似的*state一样,
ParseState主要用于记录生成Querytree这个阶段的状态。其中很有意思的一个数据结构是RangeTbleEntry,那么这个RangeTbleEntry是何方神圣呢?先别急,我们根据我们的调用流程慢慢分析一下。这里先把RangeTableEntry图画出来:

我们一路跟踪执行流程,看看
query = transformStmt(pstate, parseTree) --> result = transformSelectStmt(pstate, n);在该函数中,我们比较感兴趣的是把from和target都做了哪些手脚。具体调用函数就是:
/* process the FROM clause */
    transformFromClause(pstate, stmt->fromClause);

    /* transform targetlist */
    qry->targetList = transformTargetList(pstate, stmt->targetList);

1.我们先来分析 transformFromClause(pstate, stmt->fromClause);
该函数中对每一个from clause中的Rangevar:Node       *n = lfirst(fl); 执行:
n = transformFromClauseItem(pstate, n, &rte, &rtindex,&relnamespace, &containedRels);
并赋值:
pstate->p_joinlist = lappend(pstate->p_joinlist, n);
pstate->p_relnamespace = list_concat(pstate->p_relnamespace,relnamespace);
pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
所以我们来看看 transformFromClauseItem();函数。
该函数中,又调用rte = transformTableEntry(pstate, rv);函数来执行具体的转换工作。转换完之后,赋值:
       *top_rte = rte;
        *top_rti = rtindex;
        *relnamespace = list_make1(rte);
        *containedRels = bms_make_singleton(rtindex);
        rtr = makeNode(RangeTblRef);
        rtr->rtindex = rtindex;
        return (Node *) rtr;
返回。
所以我们再来看 transformTableEntry(pstate, rv)函数。
该函数中继而调用:
RangeTblEntry *rte=addRangeTableEntry(pstate, r, r->alias,interpretInhOption(r->inhOpt), true);
该函数中才是主要创建RangeTblEntry的地方,主要是调用了rel = parserOpenTable(pstate, relation, lockmode);函数。从字面上看该函数无非是在parser阶段打开一个relation,因为这里的RangeVar只有relname ='pois',故我们猜想,只能通过relname来打开一个relation了。只能通过查询pg_class里面的该relname对应的tuple继而找到oid,继而读入该relation了。好,猜想归猜想,我们来看看parserOpenTable(pstate, relation, lockmode);里面有哪些调用,主要是rel = try_heap_openrv(relation, lockmode);
好,那么我们就来看看这个函数。
该函数只是对Relation    r = try_relation_openrv(relation, lockmode);的封装。让我们来看看该函数:
relOid = RangeVarGetRelid(relation, true);
return relation_open(relOid, lockmode); 里面继而调用r = RelationIdGetRelation(relationId);这个是不是很熟悉,我们在load_critical_index(ClassOidIndexId,  RelationRelationId);早就打过交道了。该函数主要是通过查询pg_class表和pg_attribute系统表生成relation->rd_rel  和 relation->rd_att属性。
好,我们主要就来看看RangeVarGetRelid(relation, true);了。
该函数主要调用relId = RelnameGetRelid(relation->relname);来获取该relation的oid。该函数很有意思,我们来详细看看:
Oid RelnameGetRelid(const char *relname){
    Oid            relid;
    ListCell   *l;
    recomputeNamespacePath();
    foreach(l, activeSearchPath)  {
        Oid            namespaceId = lfirst_oid(l);
        relid = get_relname_relid(relname, namespaceId);
        if (OidIsValid(relid))
            return relid;
    }
    /* Not found in path */
    return InvalidOid;
}
这里,我们提前先把namespace的概念再引用一下:
根据postgresql 8.4的文档44.25. pg_namespace,我们有如下定义:
The catalog pg_namespace stores namespaces. A namespace is the structure underlying SQL schemas: each namespace can have a separate collection of relations, types, etc. without name conflicts.
我们知道,namespace是出于schema(schema我们暂时认为一个用户对应一个schema)之下的用于组织relation的逻辑结构。例如和收费相关的relation我们组织成一个namespace,和学生相关的所有relation我们组织成一个namespace等等。
recomputeNamespacePath()中,根据默认的namepace搜索路径"\"$user\",public",扫描pg_namespace表,获得相应的namespace oid。
我们先看看pg_namespace表中有哪些namespace:
mydb=# select oid,* from pg_namespace;
  oid  |      nspname       | nspowner |               nspacl               
-------+--------------------+----------+-------------------------------------
    11 | pg_catalog         |       10 | {postgres=UC/postgres,=U/postgres}
    99 | pg_toast           |       10 |
  2200 | public             |       10 | {postgres=UC/postgres,=UC/postgres}
 11061 | pg_temp_1          |       10 |
 11062 | pg_toast_temp_1    |       10 |
 11326 | information_schema |       10 | {postgres=UC/postgres,=U/postgres}
(6 rows)
看样子,只有对应
public  的oid=2200一个了。
然后再增加一个系统默认的
11 | pg_catalog ,所以最终有两项namespace oid赋给 static List *activeSearchPath
11 和2200。
然后我们看看relid = get_relname_relid(relname, namespaceId);调用。
在该函数中调用return GetSysCacheOid(RELNAMENSP,
                          PointerGetDatum(relname),
                          ObjectIdGetDatum(relnamespace),
                          0, 0);
参数RELNAMENSP 对应syscache中的第37项,一查我们发现就是pg_class项嘛。
这样子我们就在pg_class系统表中根据relname和namespace oid来查找相应的rel oid了。
显然:
relname namespace
'pois'       '11' 对应 pg_catalog   不存在,因为我们的pois表不在系统namespace中

'pois'       '2200' 对应 public 存在,系统默认把我们的表创建在该public的namespace中。
这样子千辛万苦得到对应relname 为'pois'的 relation oid为17229.
至此,我们的分析告一段落,先看看我们生成的pois表在内存中的Relation是什么样子,如下图:

下图表明我们的ParseState中到现在都生成了哪些属性项。此时还没把Query赋值。



2.再来看看 qry->targetList = transformTargetList(pstate, stmt->targetList);
这里的targetlist就是对应sql语句“select catcode from pois”中的catcode 了。
该函数主要调用transformTargetEntry(pstate,
                                                res->val,
                                                NULL,
                                                res->name,
                                                false)
我们来看看这个函数。该函数主要包含:
expr = transformExpr(pstate, node);
colname = FigureColname(node);
makeTargetEntry((Expr *) expr,
                           (AttrNumber) pstate->p_next_resno++,
                           colname,
                           resjunk);
我们先来看看函数
transformExpr(),其主要调用:
result = transformColumnRef(pstate, (ColumnRef *) expr);
进而调用Node * colNameToVar(ParseState *pstate, char *colname, bool localonly, int location)
进而调用
Node * scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,  int location)
最终创建Var数据结构返回。
该变量经debug得到的各项属性为:
expr    0x0a082bc4   
    xpr    {...}   
    varno    1   
    varattno    1   
    vartype    1043   
    vartypmod    36   
    varlevelsup    0   
    varnoold    1   
    varoattno    3   
    location    7   
接下来我们看看
colname = FigureColname(node);调用。该函数主要抽取colname ='pois'
接下来makeTargetEntry((Expr *) expr,
                           (AttrNumber) pstate->p_next_resno++,
                           colname,
                           resjunk);

该函数只是用输入的参数填充新创建的TargetEntry。
回到最开始的pg_analyze_and_rewrite() 函数中的第二步。
/*
     * (2) Rewrite the queries, as necessary
     */
    querytree_list = pg_rewrite_query(query);
由于对rules的rewrite规则仅限于对view的重写,故我们的pois 表经过pg_rewrite_query(query);之后的query还是原来的query,debug结果表明我们的分析也是对的,故此处不再分析rules规则重写。

至此,关于Query我们全部分析完,形成的Query如下图:


至此,结束。





http://blog.sciencenet.cn/home.php?mod=space&uid=419883&do=blog&id=309910