|||
(由于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如下图:
至此,结束。
文中大图已重新打包:
加我私人微信,交流技术。
Archiver|手机版|科学网 ( 京ICP备07017567号-12 )
GMT+8, 2024-12-19 00:13
Powered by ScienceNet.cn
Copyright © 2007- 中国科学报社