hillpig的个人博客分享 http://blog.sciencenet.cn/u/hillpig 畅想ing,思考ing,前行ing Email:bluevaley@gmail.com

博文

postgresql中query tree内存结构

已有 8525 次阅读 2010-4-7 23:52 |个人分类:postgresql|系统分类:科研笔记| postgresql, querytree, query


(由于picasa web的一些更新导致某些大图看不到,现已将文中大图已重新打包,见页面底部)


话说上次分析了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如下图:


至此,结束。

文中大图已重新打包:

postgresql中query tree内存结构.zip


加我私人微信,交流技术。






http://blog.sciencenet.cn/blog-419883-309910.html

上一篇:一天之内不再畏惧lex&yacc之必备参考资料
下一篇:Postmaster 命令 -W 参数使用

0

该博文允许注册用户评论 请点击登录 评论 (0 个评论)

数据加载中...

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2021-4-18 11:59

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部