PostgreSQL 源码解读(41)- 查询语句#26(query_planner函数#4)
上一小节介绍了函数query_planner中子函数add_base_rels_to_query的实现逻辑,本节继续介绍其中的子函数build_base_rel_tlists/find_placeholders_in_jointree/find_lateral_references,这几个子函数的目的仍然是完善RelOptInfo结构体信息。
query_planner代码片段:
//... /* * Examine the targetlist and join tree, adding entries to baserel * targetlists for all referenced Vars, and generating PlaceHolderInfo * entries for all referenced PlaceHolderVars. Restrict and join clauses * are added to appropriate lists belonging to the mentioned relations. We * also build EquivalenceClasses for provably equivalent expressions. The * SpecialJoinInfo list is also built to hold information about join order * restrictions. Finally, we form a target joinlist for make_one_rel() to * work from. */ build_base_rel_tlists(root, tlist);//构建"base rels"的投影列 find_placeholders_in_jointree(root);//处理jointree中的PHI find_lateral_references(root);//处理jointree中Lateral依赖 //...
一、重要的数据结构
PlannerInfo
PlannerInfo贯穿整个构建查询计划的全过程.
build_base_rel_tlists、find_placeholders_in_jointree和find_lateral_references函数完善了PlannerInfo->placeholder_list链表.
/*---------- * PlannerInfo * Per-query information for planning/optimization * * This struct is conventionally called "root" in all the planner routines. * It holds links to all of the planner's working state, in addition to the * original Query. Note that at present the planner extensively modifies * the passed-in Query data structure; someday that should stop. *---------- */ struct AppendRelInfo; typedef struct PlannerInfo { NodeTag type;//Node标识 Query *parse; /* 查询树,the Query being planned */ PlannerGlobal *glob; /* 当前的planner全局信息,global info for current planner run */ Index query_level; /* 查询层次,1标识最高层,1 at the outermost Query */ struct PlannerInfo *parent_root; /* 如为子计划,则这里存储父计划器指针,NULL标识最高层,NULL at outermost Query */ /* * plan_params contains the expressions that this query level needs to * make available to a lower query level that is currently being planned. * outer_params contains the paramIds of PARAM_EXEC Params that outer * query levels will make available to this query level. */ List *plan_params; /* list of PlannerParamItems, see below */ Bitmapset *outer_params; /* * simple_rel_array holds pointers to "base rels" and "other rels" (see * comments for RelOptInfo for more info). It is indexed by rangetable * index (so entry 0 is always wasted). Entries can be NULL when an RTE * does not correspond to a base relation, such as a join RTE or an * unreferenced view RTE; or if the RelOptInfo hasn't been made yet. */ /* RelOptInfo数组,存储"base rels",比如基表/子查询等.该数组与RTE的顺序一一对应,而且是从1开始,因此[0]无用 */ struct RelOptInfo **simple_rel_array; /* All 1-rel RelOptInfos */ int simple_rel_array_size; /* 数组大小,allocated size of array */ /* * simple_rte_array is the same length as simple_rel_array and holds * pointers to the associated rangetable entries. This lets us avoid * rt_fetch(), which can be a bit slow once large inheritance sets have * been expanded. */ RangeTblEntry **simple_rte_array; /* RTE数组,rangetable as an array */ /* * append_rel_array is the same length as the above arrays, and holds * pointers to the corresponding AppendRelInfo entry indexed by * child_relid, or NULL if none. The array itself is not allocated if * append_rel_list is empty. */ struct AppendRelInfo **append_rel_array;//先前已介绍,在处理集合操作如UNION ALL时使用 /* * all_baserels is a Relids set of all base relids (but not "other" * relids) in the query; that is, the Relids identifier of the final join * we need to form. This is computed in make_one_rel, just before we * start making Paths. */ Relids all_baserels;//"base rels" /* * nullable_baserels is a Relids set of base relids that are nullable by * some outer join in the jointree; these are rels that are potentially * nullable below the WHERE clause, SELECT targetlist, etc. This is * computed in deconstruct_jointree. */ Relids nullable_baserels;//Nullable-side端的"base rels" /* * join_rel_list is a list of all join-relation RelOptInfos we have * considered in this planning run. For small problems we just scan the * list to do lookups, but when there are many join relations we build a * hash table for faster lookups. The hash table is present and valid * when join_rel_hash is not NULL. Note that we still maintain the list * even when using the hash table for lookups; this simplifies life for * GEQO. */ List *join_rel_list; /* 参与连接的Relation的RelOptInfo链表,list of join-relation RelOptInfos */ struct HTAB *join_rel_hash; /* 可加快链表访问的hash表,optional hashtable for join relations */ /* * When doing a dynamic-programming-style join search, join_rel_level[k] * is a list of all join-relation RelOptInfos of level k, and * join_cur_level is the current level. New join-relation RelOptInfos are * automatically added to the join_rel_level[join_cur_level] list. * join_rel_level is NULL if not in use. */ List **join_rel_level; /* RelOptInfo指针链表数组,k层的join存储在[k]中,lists of join-relation RelOptInfos */ int join_cur_level; /* 当前的join层次,index of list being extended */ List *init_plans; /* 查询的初始化计划链表,init SubPlans for query */ List *cte_plan_ids; /* CTE子计划ID链表,per-CTE-item list of subplan IDs */ List *multiexpr_params; /* List of Lists of Params for MULTIEXPR * subquery outputs */ List *eq_classes; /* 活动的等价类链表,list of active EquivalenceClasses */ List *canon_pathkeys; /* 规范化PathKey链表,list of "canonical" PathKeys */ List *left_join_clauses; /* 外连接约束条件链表(左),list of RestrictInfos for mergejoinable * outer join clauses w/nonnullable var on * left */ List *right_join_clauses; /* 外连接约束条件链表(右),list of RestrictInfos for mergejoinable * outer join clauses w/nonnullable var on * right */ List *full_join_clauses; /* 全连接约束条件链表,list of RestrictInfos for mergejoinable * full join clauses */ List *join_info_list; /* 特殊连接信息链表,list of SpecialJoinInfos */ List *append_rel_list; /* AppendRelInfo链表,list of AppendRelInfos */ List *rowMarks; /* list of PlanRowMarks */ List *placeholder_list; /* PHI链表,list of PlaceHolderInfos */ List *fkey_list; /* 外键信息链表,list of ForeignKeyOptInfos */ List *query_pathkeys; /* uery_planner()要求的PathKeys,desired pathkeys for query_planner() */ List *group_pathkeys; /* groupClause pathkeys, if any */ List *window_pathkeys; /* pathkeys of bottom window, if any */ List *distinct_pathkeys; /* distinctClause pathkeys, if any */ List *sort_pathkeys; /* sortClause pathkeys, if any */ List *part_schemes; /* 已规范化的分区Schema,Canonicalised partition schemes used in the * query. */ List *initial_rels; /* 尝试连接的RelOptInfo链表,RelOptInfos we are now trying to join */ /* Use fetch_upper_rel() to get any particular upper rel */ List *upper_rels[UPPERREL_FINAL + 1]; /* 上层的RelOptInfo链表, upper-rel RelOptInfos */ /* Result tlists chosen by grouping_planner for upper-stage processing */ struct PathTarget *upper_targets[UPPERREL_FINAL + 1];// /* * grouping_planner passes back its final processed targetlist here, for * use in relabeling the topmost tlist of the finished Plan. */ List *processed_tlist;//最后需处理的投影列 /* Fields filled during create_plan() for use in setrefs.c */ AttrNumber *grouping_map; /* for GroupingFunc fixup */ List *minmax_aggs; /* List of MinMaxAggInfos */ MemoryContext planner_cxt; /* 内存上下文,context holding PlannerInfo */ double total_table_pages; /* 所有的pages,# of pages in all tables of query */ double tuple_fraction; /* query_planner输入参数:元组处理比例,tuple_fraction passed to query_planner */ double limit_tuples; /* query_planner输入参数:limit_tuples passed to query_planner */ Index qual_security_level; /* 表达式的最新安全等级,minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ InheritanceKind inhTargetKind; /* indicates if the target relation is an * inheritance child or partition or a * partitioned table */ bool hasJoinRTEs; /* 存在RTE_JOIN的RTE,true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* 存在标记为LATERAL的RTE,true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* 存在已在jointree删除的RTE,true if any RTE was deleted from jointree */ bool hasHavingQual; /* 存在Having子句,true if havingQual was non-null */ bool hasPseudoConstantQuals; /* true if any RestrictInfo has * pseudoconstant = true */ bool hasRecursion; /* 递归语句,true if planning a recursive WITH item */ /* These fields are used only when hasRecursion is true: */ int wt_param_id; /* PARAM_EXEC ID for the work table */ struct Path *non_recursive_path; /* a path for non-recursive term */ /* These fields are workspace for createplan.c */ Relids curOuterRels; /* outer rels above current node */ List *curOuterParams; /* not-yet-assigned NestLoopParams */ /* optional private data for join_search_hook, e.g., GEQO */ void *join_search_private; /* Does this query modify any partition key columns? */ bool partColsUpdated; } PlannerInfo;
RelOptInfo
RelOptInfo结构体贯彻逻辑优化和物理优化过程的始终.
build_base_rel_tlists完善了结构体中attr_needed和reltarget变量,find_lateral_references函数完善了结构体中lateral_vars变量.
typedef struct RelOptInfo { NodeTag type;//节点标识 RelOptKind reloptkind;//RelOpt类型 /* all relations included in this RelOptInfo */ Relids relids; /*Relids(rtindex)集合 set of base relids (rangetable indexes) */ /* size estimates generated by planner */ double rows; /*结果元组的估算数量 estimated number of result tuples */ /* per-relation planner control flags */ bool consider_startup; /*是否考虑启动成本?是,需要保留启动成本低的路径 keep cheap-startup-cost paths? */ bool consider_param_startup; /*是否考虑参数化?的路径 ditto, for parameterized paths? */ bool consider_parallel; /*是否考虑并行处理路径 consider parallel paths? */ /* default result targetlist for Paths scanning this relation */ struct PathTarget *reltarget; /*扫描该Relation时默认的结果 list of Vars/Exprs, cost, width */ /* materialization information */ List *pathlist; /*访问路径链表 Path structures */ List *ppilist; /*路径链表中使用参数化路径进行 ParamPathInfos used in pathlist */ List *partial_pathlist; /* partial Paths */ struct Path *cheapest_startup_path;//代价最低的启动路径 struct Path *cheapest_total_path;//代价最低的整体路径 struct Path *cheapest_unique_path;//代价最低的获取唯一值的路径 List *cheapest_parameterized_paths;//代价最低的参数化?路径链表 /* parameterization information needed for both base rels and join rels */ /* (see also lateral_vars and lateral_referencers) */ Relids direct_lateral_relids; /*使用lateral语法,需依赖的Relids rels directly laterally referenced */ Relids lateral_relids; /* minimum parameterization of rel */ /* information about a base rel (not set for join rels!) */ //reloptkind=RELOPT_BASEREL时使用的数据结构 Index relid; /* Relation ID */ Oid reltablespace; /* 表空间 containing tablespace */ RTEKind rtekind; /* 基表?子查询?还是函数等等?RELATION, SUBQUERY, FUNCTION, etc */ AttrNumber min_attr; /* 最小的属性编号 smallest attrno of rel (often <0) */ AttrNumber max_attr; /* 最大的属性编号 largest attrno of rel */ Relids *attr_needed; /* 数组 array indexed [min_attr .. max_attr] */ int32 *attr_widths; /* 属性宽度 array indexed [min_attr .. max_attr] */ List *lateral_vars; /* 关系依赖的Vars/PHVs LATERAL Vars and PHVs referenced by rel */ Relids lateral_referencers; /*依赖该关系的Relids rels that reference me laterally */ List *indexlist; /* 该关系的IndexOptInfo链表 list of IndexOptInfo */ List *statlist; /* 统计信息链表 list of StatisticExtInfo */ BlockNumber pages; /* 块数 size estimates derived from pg_class */ double tuples; /* 元组数 */ double allvisfrac; /* ? */ PlannerInfo *subroot; /* 如为子查询,存储子查询的root if subquery */ List *subplan_params; /* 如为子查询,存储子查询的参数 if subquery */ int rel_parallel_workers; /* 并行执行,需要多少个workers? wanted number of parallel workers */ /* Information about foreign tables and foreign joins */ //FWD相关信息 Oid serverid; /* identifies server for the table or join */ Oid userid; /* identifies user to check access as */ bool useridiscurrent; /* join is only valid for current user */ /* use "struct FdwRoutine" to avoid including fdwapi.h here */ struct FdwRoutine *fdwroutine; void *fdw_private; /* cache space for remembering if we have proven this relation unique */ //已知的,可保证唯一的Relids链表 List *unique_for_rels; /* known unique for these other relid * set(s) */ List *non_unique_for_rels; /* 已知的,不唯一的Relids链表 known not unique for these set(s) */ /* used by various scans and joins: */ List *baserestrictinfo; /* 如为基本关系,存储约束条件 RestrictInfo structures (if base rel) */ QualCost baserestrictcost; /* 解析约束表达式的成本? cost of evaluating the above */ Index baserestrict_min_security; /* 最低安全等级 min security_level found in * baserestrictinfo */ List *joininfo; /* 连接语句的约束条件信息 RestrictInfo structures for join clauses * involving this rel */ bool has_eclass_joins; /* 是否存在等价类连接? T means joininfo is incomplete */ /* used by partitionwise joins: */ bool consider_partitionwise_join; /* 分区? consider partitionwise * join paths? (if * partitioned rel) */ Relids top_parent_relids; /* Relids of topmost parents (if "other" * rel) */ /* used for partitioned relations */ //分区表使用 PartitionScheme part_scheme; /* 分区的schema Partitioning scheme. */ int nparts; /* 分区数 number of partitions */ struct PartitionBoundInfoData *boundinfo; /* 分区边界信息 Partition bounds */ List *partition_qual; /* 分区约束 partition constraint */ struct RelOptInfo **part_rels; /* 分区的RelOptInfo数组 Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* 非空分区键表达式 Non-nullable partition key expressions. */ List **nullable_partexprs; /* 可为空的分区键表达式 Nullable partition key expressions. */ List *partitioned_child_rels; /* RT Indexes链表 List of RT indexes. */ } RelOptInfo;
二、源码解读
基本概念
PlaceHolder
PlaceHolder即占位符,常用于减少SQL的parse过程提高性能.
如JDBC中常用的PreparedStatement:
String sql = "select * from t_dwxx where dwbh = ? and dwmc = ?";PreparedStatement pstmt = connection.preparestatement(sql);pstmt.setstring(1,'1001');pstmt.setstring(2,'测试');resultset rs = ps.executequery();
可以认为,其中的?所代表的是占位符.
在psql中,使用set命令定义变量,在SQL语句中使用占位符:
testdb=# \set v1 '\'1001\''testdb=# select * from t_dwxx where dwbh = :v1; dwmc | dwbh | dwdz -----------+------+-------------------- X有限公司 | 1001 | 广东省广州市荔湾区(1 row)
build_base_rel_tlists
/********************************* build_base_rel_tlists ******************//* * build_base_rel_tlists * Add targetlist entries for each var needed in the query's final tlist * (and HAVING clause, if any) to the appropriate base relations. * * 把最终的投影列信息添加到合适的"base rels"中. * * We mark such vars as needed by "relation 0" to ensure that they will * propagate up through all join plan steps. */ void build_base_rel_tlists(PlannerInfo *root, List *final_tlist) { List *tlist_vars = pull_var_clause((Node *) final_tlist, PVC_RECURSE_AGGREGATES | PVC_RECURSE_WINDOWFUNCS | PVC_INCLUDE_PLACEHOLDERS);//获取投影列 if (tlist_vars != NIL) { //添加到相应的Relation's targetlist(如不存在) //标记其为连接或者最终输出所需要 add_vars_to_targetlist(root, tlist_vars, bms_make_singleton(0), true); list_free(tlist_vars); } /* * If there's a HAVING clause, we'll need the Vars it uses, too. Note * that HAVING can contain Aggrefs but not WindowFuncs. */ if (root->parse->havingQual)//如存在Having子句,顶层的Having是在查询语句的后期才执行,需保留需要的Vars { List *having_vars = pull_var_clause(root->parse->havingQual, PVC_RECURSE_AGGREGATES | PVC_INCLUDE_PLACEHOLDERS); if (having_vars != NIL) { add_vars_to_targetlist(root, having_vars, bms_make_singleton(0), true); list_free(having_vars); } } } /* * add_vars_to_targetlist * For each variable appearing in the list, add it to the owning * relation's targetlist if not already present, and mark the variable * as being needed for the indicated join (or for final output if * where_needed includes "relation 0"). * * The list may also contain PlaceHolderVars. These don't necessarily * have a single owning relation; we keep their attr_needed info in * root->placeholder_list instead. If create_new_ph is true, it's OK * to create new PlaceHolderInfos; otherwise, the PlaceHolderInfos must * already exist, and we should only update their ph_needed. (This should * be true before deconstruct_jointree begins, and false after that.) */ void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed, bool create_new_ph) { ListCell *temp; Assert(!bms_is_empty(where_needed)); foreach(temp, vars) { Node *node = (Node *) lfirst(temp); if (IsA(node, Var)) { Var *var = (Var *) node;//属性Var RelOptInfo *rel = find_base_rel(root, var->varno);//找到相应的RelOptInfo int attno = var->varattno;//属性编号 if (bms_is_subset(where_needed, rel->relids))//where_needed是否rel的子集? continue;//是,继续循环 Assert(attno >= rel->min_attr && attno <= rel->max_attr); attno -= rel->min_attr; if (rel->attr_needed[attno] == NULL) { /* Variable not yet requested, so add to rel's targetlist */ /* XXX is copyObject necessary here? */ rel->reltarget->exprs = lappend(rel->reltarget->exprs, copyObject(var));//TODO,添加到rel->reltarget->exprs 中 /* reltarget cost and width will be computed later */ } rel->attr_needed[attno] = bms_add_members(rel->attr_needed[attno], where_needed);//where_needed添加到bitmapset中 } else if (IsA(node, PlaceHolderVar)) { PlaceHolderVar *phv = (PlaceHolderVar *) node; PlaceHolderInfo *phinfo = find_placeholder_info(root, phv, create_new_ph); phinfo->ph_needed = bms_add_members(phinfo->ph_needed, where_needed);//添加PHV } else elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); } }
find_placeholders_in_jointree
/********************************* find_placeholders_in_jointree ******************/ /* * find_placeholders_in_jointree * Search the jointree for PlaceHolderVars, and build PlaceHolderInfos * * 搜索jointree中的PHV,并且构建PHInfos * * We don't need to look at the targetlist because build_base_rel_tlists() * will already have made entries for any PHVs in the tlist. * * This is called before we begin deconstruct_jointree. Once we begin * deconstruct_jointree, all active placeholders must be present in * root->placeholder_list, because make_outerjoininfo and * update_placeholder_eval_levels require this info to be available * while we crawl up the join tree. */ void find_placeholders_in_jointree(PlannerInfo *root) { /* We need do nothing if the query contains no PlaceHolderVars */ if (root->glob->lastPHId != 0) { /* Start recursion at top of jointree */ Assert(root->parse->jointree != NULL && IsA(root->parse->jointree, FromExpr)); find_placeholders_recurse(root, (Node *) root->parse->jointree);//递归处理 } } /* * find_placeholders_recurse * One recursion level of find_placeholders_in_jointree. * * jtnode is the current jointree node to examine. */ static void find_placeholders_recurse(PlannerInfo *root, Node *jtnode) { if (jtnode == NULL) return; if (IsA(jtnode, RangeTblRef))//RTR { /* No quals to deal with here */ } else if (IsA(jtnode, FromExpr))//FromExpr { FromExpr *f = (FromExpr *) jtnode; ListCell *l; /* * First, recurse to handle child joins. */ foreach(l, f->fromlist) { find_placeholders_recurse(root, lfirst(l)); } /* * Now process the top-level quals. */ find_placeholders_in_expr(root, f->quals);//在表达式中搜索 } else if (IsA(jtnode, JoinExpr))//JoinExpr { JoinExpr *j = (JoinExpr *) jtnode; /* * First, recurse to handle child joins. */ find_placeholders_recurse(root, j->larg); find_placeholders_recurse(root, j->rarg); /* Process the qual clauses */ find_placeholders_in_expr(root, j->quals);//在表达式中搜索 } else elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode)); } /* * find_placeholders_in_expr * Find all PlaceHolderVars in the given expression, and create * PlaceHolderInfo entries for them. */ static void find_placeholders_in_expr(PlannerInfo *root, Node *expr) { List *vars; ListCell *vl; /* * pull_var_clause does more than we need here, but it'll do and it's * convenient to use. */ vars = pull_var_clause(expr, PVC_RECURSE_AGGREGATES | PVC_RECURSE_WINDOWFUNCS | PVC_INCLUDE_PLACEHOLDERS);//遍历Vars,得到PH链表 foreach(vl, vars) { PlaceHolderVar *phv = (PlaceHolderVar *) lfirst(vl); /* Ignore any plain Vars */ if (!IsA(phv, PlaceHolderVar)) continue; /* Create a PlaceHolderInfo entry if there's not one already */ (void) find_placeholder_info(root, phv, true);//创建PHInfo } list_free(vars); } /* * find_placeholder_info * Fetch the PlaceHolderInfo for the given PHV * * If the PlaceHolderInfo doesn't exist yet, create it if create_new_ph is * true, else throw an error. * * This is separate from make_placeholder_expr because subquery pullup has * to make PlaceHolderVars for expressions that might not be used at all in * the upper query, or might not remain after const-expression simplification. * We build PlaceHolderInfos only for PHVs that are still present in the * simplified query passed to query_planner(). * * Note: this should only be called after query_planner() has started. Also, * create_new_ph must not be true after deconstruct_jointree begins, because * make_outerjoininfo assumes that we already know about all placeholders. */ PlaceHolderInfo * find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv, bool create_new_ph) { PlaceHolderInfo *phinfo;//结果 Relids rels_used; ListCell *lc; /* if this ever isn't true, we'd need to be able to look in parent lists */ Assert(phv->phlevelsup == 0); foreach(lc, root->placeholder_list)//已存在,返回 { phinfo = (PlaceHolderInfo *) lfirst(lc); if (phinfo->phid == phv->phid) return phinfo; } /* Not found, so create it */ if (!create_new_ph) elog(ERROR, "too late to create a new PlaceHolderInfo"); phinfo = makeNode(PlaceHolderInfo);//构建PHInfo phinfo->phid = phv->phid; phinfo->ph_var = copyObject(phv); /* * Any referenced rels that are outside the PHV's syntactic scope are * LATERAL references, which should be included in ph_lateral but not in * ph_eval_at. If no referenced rels are within the syntactic scope, * force evaluation at the syntactic location. */ rels_used = pull_varnos((Node *) phv->phexpr); phinfo->ph_lateral = bms_difference(rels_used, phv->phrels); if (bms_is_empty(phinfo->ph_lateral)) phinfo->ph_lateral = NULL; /* make it exactly NULL if empty */ phinfo->ph_eval_at = bms_int_members(rels_used, phv->phrels); /* If no contained vars, force evaluation at syntactic location */ if (bms_is_empty(phinfo->ph_eval_at)) { phinfo->ph_eval_at = bms_copy(phv->phrels); Assert(!bms_is_empty(phinfo->ph_eval_at)); } /* ph_eval_at may change later, see update_placeholder_eval_levels */ phinfo->ph_needed = NULL; /* initially it's unused */ /* for the moment, estimate width using just the datatype info */ phinfo->ph_width = get_typavgwidth(exprType((Node *) phv->phexpr), exprTypmod((Node *) phv->phexpr)); root->placeholder_list = lappend(root->placeholder_list, phinfo);//添加到优化器信息中 /* * The PHV's contained expression may contain other, lower-level PHVs. We * now know we need to get those into the PlaceHolderInfo list, too, so we * may as well do that immediately. */ find_placeholders_in_expr(root, (Node *) phinfo->ph_var->phexpr);//如存在子表达式,递归进去 return phinfo; }
find_lateral_references
/************************* find_lateral_references ******************************/ /* * find_lateral_references * For each LATERAL subquery, extract all its references to Vars and * PlaceHolderVars of the current query level, and make sure those values * will be available for evaluation of the subquery. * * 对于LATERAL子查询,获取当前查询层次的Vars&PHVars,并且确保这些值在解析子查询时是可用的 * * While later planning steps ensure that the Var/PHV source rels are on the * outside of nestloops relative to the LATERAL subquery, we also need to * ensure that the Vars/PHVs propagate up to the nestloop join level; this * means setting suitable where_needed values for them. * * Note that this only deals with lateral references in unflattened LATERAL * subqueries. When we flatten a LATERAL subquery, its lateral references * become plain Vars in the parent query, but they may have to be wrapped in * PlaceHolderVars if they need to be forced NULL by outer joins that don't * also null the LATERAL subquery. That's all handled elsewhere. * * This has to run before deconstruct_jointree, since it might result in * creation of PlaceHolderInfos. */ void find_lateral_references(PlannerInfo *root) { Index rti; /* We need do nothing if the query contains no LATERAL RTEs */ if (!root->hasLateralRTEs) return; /* * Examine all baserels (the rel array has been set up by now). */ for (rti = 1; rti < root->simple_rel_array_size; rti++)//遍历RelOptInfo { RelOptInfo *brel = root->simple_rel_array[rti]; /* there may be empty slots corresponding to non-baserel RTEs */ if (brel == NULL) continue; Assert(brel->relid == rti); /* sanity check on array */ /* * This bit is less obvious than it might look. We ignore appendrel * otherrels and consider only their parent baserels. In a case where * a LATERAL-containing UNION ALL subquery was pulled up, it is the * otherrel that is actually going to be in the plan. However, we * want to mark all its lateral references as needed by the parent, * because it is the parent's relid that will be used for join * planning purposes. And the parent's RTE will contain all the * lateral references we need to know, since the pulled-up member is * nothing but a copy of parts of the original RTE's subquery. We * could visit the parent's children instead and transform their * references back to the parent's relid, but it would be much more * complicated for no real gain. (Important here is that the child * members have not yet received any processing beyond being pulled * up.) Similarly, in appendrels created by inheritance expansion, * it's sufficient to look at the parent relation. */ /* ignore RTEs that are "other rels" */ if (brel->reloptkind != RELOPT_BASEREL) continue; extract_lateral_references(root, brel, rti);//获取LATERAL依赖 } } static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex) { RangeTblEntry *rte = root->simple_rte_array[rtindex];//相应的RTE List *vars; List *newvars; Relids where_needed; ListCell *lc; /* No cross-references are possible if it's not LATERAL */ if (!rte->lateral)//非LATERAL,退出 return; /* Fetch the appropriate variables */ //获取相应的Vars if (rte->rtekind == RTE_RELATION)//基表 vars = pull_vars_of_level((Node *) rte->tablesample, 0); else if (rte->rtekind == RTE_SUBQUERY)//子查询 vars = pull_vars_of_level((Node *) rte->subquery, 1); else if (rte->rtekind == RTE_FUNCTION)//函数 vars = pull_vars_of_level((Node *) rte->functions, 0); else if (rte->rtekind == RTE_TABLEFUNC)//TABLEFUNC vars = pull_vars_of_level((Node *) rte->tablefunc, 0); else if (rte->rtekind == RTE_VALUES)//VALUES vars = pull_vars_of_level((Node *) rte->values_lists, 0); else { Assert(false); return; /* keep compiler quiet */ } if (vars == NIL) return; /* nothing to do */ /* Copy each Var (or PlaceHolderVar) and adjust it to match our level */ newvars = NIL; foreach(lc, vars)//遍历Vars { Node *node = (Node *) lfirst(lc); node = copyObject(node); if (IsA(node, Var))//Var { Var *var = (Var *) node; /* Adjustment is easy since it's just one node */ var->varlevelsup = 0; } else if (IsA(node, PlaceHolderVar))//PHVar { PlaceHolderVar *phv = (PlaceHolderVar *) node; int levelsup = phv->phlevelsup; /* Have to work harder to adjust the contained expression too */ if (levelsup != 0) IncrementVarSublevelsUp(node, -levelsup, 0);//调整其中的表达式 /* * If we pulled the PHV out of a subquery RTE, its expression * needs to be preprocessed. subquery_planner() already did this * for level-zero PHVs in function and values RTEs, though. */ if (levelsup > 0) phv->phexpr = preprocess_phv__expression(root, phv->phexpr);//预处理PHVar表达式 } else Assert(false); newvars = lappend(newvars, node);//添加到新的结果Vars中 } list_free(vars); /* * We mark the Vars as being "needed" at the LATERAL RTE. This is a bit * of a cheat: a more formal approach would be to mark each one as needed * at the join of the LATERAL RTE with its source RTE. But it will work, * and it's much less tedious than computing a separate where_needed for * each Var. */ where_needed = bms_make_singleton(rtindex);//获取Rel编号 /* * Push Vars into their source relations' targetlists, and PHVs into * root->placeholder_list. */ add_vars_to_targetlist(root, newvars, where_needed, true);//添加到相应的Rel中 /* Remember the lateral references for create_lateral_join_info */ brel->lateral_vars = newvars;//RelOptInfo赋值 } /* * pull_vars_of_level * Create a list of all Vars (and PlaceHolderVars) referencing the * specified query level in the given parsetree. * * Caution: the Vars are not copied, only linked into the list. */ List * pull_vars_of_level(Node *node, int levelsup) { pull_vars_context context; context.vars = NIL; context.sublevels_up = levelsup; /* * Must be prepared to start with a Query or a bare expression tree; if * it's a Query, we don't want to increment sublevels_up. */ query_or_expression_tree_walker(node, pull_vars_walker, (void *) &context, 0);//调用XX_walker函数遍历 return context.vars; } static bool pull_vars_walker(Node *node, pull_vars_context *context)//遍历函数 { if (node == NULL) return false; if (IsA(node, Var)) { Var *var = (Var *) node; if (var->varlevelsup == context->sublevels_up) context->vars = lappend(context->vars, var); return false; } if (IsA(node, PlaceHolderVar)) { PlaceHolderVar *phv = (PlaceHolderVar *) node; if (phv->phlevelsup == context->sublevels_up) context->vars = lappend(context->vars, phv); /* we don't want to look into the contained expression */ return false; } if (IsA(node, Query)) { /* Recurse into RTE subquery or not-yet-planned sublink subquery */ bool result; context->sublevels_up++; result = query_tree_walker((Query *) node, pull_vars_walker, (void *) context, 0); context->sublevels_up--; return result; } return expression_tree_walker(node, pull_vars_walker, (void *) context); }
公共部分
/************************* 公共 ******************************/ /* * pull_var_clause * Recursively pulls all Var nodes from an expression clause. * * 递归的方式推送(pull)所有的Var节点 * * Aggrefs are handled according to these bits in 'flags': * PVC_INCLUDE_AGGREGATES include Aggrefs in output list * PVC_RECURSE_AGGREGATES recurse into Aggref arguments * neither flag throw error if Aggref found * Vars within an Aggref's expression are included in the result only * when PVC_RECURSE_AGGREGATES is specified. * * WindowFuncs are handled according to these bits in 'flags': * PVC_INCLUDE_WINDOWFUNCS include WindowFuncs in output list * PVC_RECURSE_WINDOWFUNCS recurse into WindowFunc arguments * neither flag throw error if WindowFunc found * Vars within a WindowFunc's expression are included in the result only * when PVC_RECURSE_WINDOWFUNCS is specified. * * PlaceHolderVars are handled according to these bits in 'flags': * PVC_INCLUDE_PLACEHOLDERS include PlaceHolderVars in output list * PVC_RECURSE_PLACEHOLDERS recurse into PlaceHolderVar arguments * neither flag throw error if PlaceHolderVar found * Vars within a PHV's expression are included in the result only * when PVC_RECURSE_PLACEHOLDERS is specified. * * GroupingFuncs are treated mostly like Aggrefs, and so do not need * their own flag bits. * * CurrentOfExpr nodes are ignored in all cases. * * Upper-level vars (with varlevelsup > 0) should not be seen here, * likewise for upper-level Aggrefs and PlaceHolderVars. * * Returns list of nodes found. Note the nodes themselves are not * copied, only referenced. * * Does not examine subqueries, therefore must only be used after reduction * of sublinks to subplans! */ List * pull_var_clause(Node *node, int flags) { pull_var_clause_context context;//上下文 //互斥选项检测 /* Assert that caller has not specified inconsistent flags */ Assert((flags & (PVC_INCLUDE_AGGREGATES | PVC_RECURSE_AGGREGATES)) != (PVC_INCLUDE_AGGREGATES | PVC_RECURSE_AGGREGATES)); Assert((flags & (PVC_INCLUDE_WINDOWFUNCS | PVC_RECURSE_WINDOWFUNCS)) != (PVC_INCLUDE_WINDOWFUNCS | PVC_RECURSE_WINDOWFUNCS)); Assert((flags & (PVC_INCLUDE_PLACEHOLDERS | PVC_RECURSE_PLACEHOLDERS)) != (PVC_INCLUDE_PLACEHOLDERS | PVC_RECURSE_PLACEHOLDERS)); context.varlist = NIL; context.flags = flags; pull_var_clause_walker(node, &context);//调用XX_walker函数遍历,结果保存在context.varlist中 return context.varlist; } static bool pull_var_clause_walker(Node *node, pull_var_clause_context *context) { if (node == NULL) return false; if (IsA(node, Var))//Var类型 { if (((Var *) node)->varlevelsup != 0)//非本级Var elog(ERROR, "Upper-level Var found where not expected"); context->varlist = lappend(context->varlist, node);//添加到结果链表中 return false; } else if (IsA(node, Aggref))//聚合 { if (((Aggref *) node)->agglevelsup != 0) elog(ERROR, "Upper-level Aggref found where not expected"); if (context->flags & PVC_INCLUDE_AGGREGATES)//包含聚合 { context->varlist = lappend(context->varlist, node);//添加到结果 /* we do NOT descend into the contained expression */ return false; } else if (context->flags & PVC_RECURSE_AGGREGATES)//递归搜索 { /* fall through to recurse into the aggregate's arguments */ } else elog(ERROR, "Aggref found where not expected"); } else if (IsA(node, GroupingFunc))//分组 { if (((GroupingFunc *) node)->agglevelsup != 0) elog(ERROR, "Upper-level GROUPING found where not expected"); if (context->flags & PVC_INCLUDE_AGGREGATES)//包含标记 { context->varlist = lappend(context->varlist, node); /* we do NOT descend into the contained expression */ return false; } else if (context->flags & PVC_RECURSE_AGGREGATES)//递归标记 { /* * We do NOT descend into the contained expression, even if the * caller asked for it, because we never actually evaluate it - * the result is driven entirely off the associated GROUP BY * clause, so we never need to extract the actual Vars here. */ return false;//直接返回,需与GROUP BY语句一起 } else elog(ERROR, "GROUPING found where not expected"); } else if (IsA(node, WindowFunc))//窗口函数 { /* WindowFuncs have no levelsup field to check ... */ if (context->flags & PVC_INCLUDE_WINDOWFUNCS)//包含标记 { context->varlist = lappend(context->varlist, node); /* we do NOT descend into the contained expressions */ return false; } else if (context->flags & PVC_RECURSE_WINDOWFUNCS)//递归标记 { /* fall through to recurse into the windowfunc's arguments */ } else elog(ERROR, "WindowFunc found where not expected"); } else if (IsA(node, PlaceHolderVar))//PH { if (((PlaceHolderVar *) node)->phlevelsup != 0) elog(ERROR, "Upper-level PlaceHolderVar found where not expected"); if (context->flags & PVC_INCLUDE_PLACEHOLDERS) { context->varlist = lappend(context->varlist, node); /* we do NOT descend into the contained expression */ return false; } else if (context->flags & PVC_RECURSE_PLACEHOLDERS) { /* fall through to recurse into the placeholder's expression */ } else elog(ERROR, "PlaceHolderVar found where not expected"); } return expression_tree_walker(node, pull_var_clause_walker, (void *) context);//表达式解析 }
三、跟踪分析
重点考察root->simple_rel_array[n]->attr_needed、root->simple_rel_array[n]->reltarget、root->placeholder_list、root->simple_rel_array[n]->lateral_vars.
启动gdb跟踪
(gdb) b build_base_rel_tlistsBreakpoint 1 at 0x76551b: file initsplan.c, line 153.(gdb) cContinuing.Breakpoint 1, build_base_rel_tlists (root=0x171ae40, final_tlist=0x1734750) at initsplan.c:153153 List *tlist_vars = pull_var_clause((Node *) final_tlist,(gdb)
final_tlist是最终的输出列(投影列),一共有5个,分别是t_dwxx.dwmc/dwbh/dwdz,t_grxx.grbh,t_jfxx.je
(gdb) p *final_tlist$1 = {type = T_List, length = 5, head = 0x1734730, tail = 0x1734a60}(gdb) p *(Node *)final_tlist->head->data.ptr_value$2 = {type = T_TargetEntry}(gdb) p *(TargetEntry *)final_tlist->head->data.ptr_value$3 = {xpr = {type = T_TargetEntry}, expr = 0x17346e0, resno = 1, resname = 0x171a5a0 "dwmc", ressortgroupref = 0, resorigtbl = 16394, resorigcol = 1, resjunk = false}
跟踪函数build_base_rel_tlists
(gdb) n158 if (tlist_vars != NIL)(gdb) 160 add_vars_to_targetlist(root, tlist_vars, bms_make_singleton(0), true);#5个Vars(gdb) p *tlist_vars$4 = {type = T_List, length = 5, head = 0x17369e0, tail = 0x1737cb8}(gdb) p *(Var *)tlist_vars->head->data.ptr_value$5 = {xpr = {type = T_Var}, varno = 1, varattno = 1, vartype = 1043, vartypmod = 104, varcollid = 100, varlevelsup = 0, varnoold = 1, varoattno = 1, location = 7}
执行函数build_base_rel_tlists,检查final_rel->attr_needed和final_rel->reltarget
(gdb) finishRun till exit from #0 build_base_rel_tlists (root=0x171ae40, final_tlist=0x1734750) at initsplan.c:160query_planner (root=0x171ae40, tlist=0x1734750, qp_callback=0x76e97d , qp_extra=0x7ffe20fc33d0) at planmain.c:152152 find_placeholders_in_jointree(root);
检查root内存结构
(gdb) p *root$15 = {type = T_PlannerInfo, parse = 0x1711680, glob = 0x1732118, query_level = 1, parent_root = 0x0, plan_params = 0x0, outer_params = 0x0, simple_rel_array = 0x1736578, simple_rel_array_size = 6, simple_rte_array = 0x17365c8, all_baserels = 0x0, nullable_baserels = 0x0, join_rel_list = 0x0, join_rel_hash = 0x0, join_rel_level = 0x0, join_cur_level = 0, init_plans = 0x0, cte_plan_ids = 0x0, multiexpr_params = 0x0, eq_classes = 0x0, canon_pathkeys = 0x0, left_join_clauses = 0x0, right_join_clauses = 0x0, full_join_clauses = 0x0, join_info_list = 0x0, append_rel_list = 0x0, rowMarks = 0x0, placeholder_list = 0x0, fkey_list = 0x0, query_pathkeys = 0x0, group_pathkeys = 0x0, window_pathkeys = 0x0, distinct_pathkeys = 0x0, sort_pathkeys = 0x0, part_schemes = 0x0, initial_rels = 0x0, upper_rels = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, upper_targets = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, processed_tlist = 0x1734750, grouping_map = 0x0, minmax_aggs = 0x0, planner_cxt = 0x165a040, total_table_pages = 0, tuple_fraction = 0, limit_tuples = -1, qual_security_level = 0, inhTargetKind = INHKIND_NONE, hasJoinRTEs = true, hasLateralRTEs = true, hasDeletedRTEs = false, hasHavingQual = false, hasPseudoConstantQuals = false, hasRecursion = false, wt_param_id = -1, non_recursive_path = 0x0, curOuterRels = 0x0, curOuterParams = 0x0, join_search_private = 0x0, partColsUpdated = false}
RelOptInfo数组,注意数组的第0(下标)个元素为NULL(无用),有用的元素下标从1开始:
第1个元素是基础关系(相应的RTE=t_dwxx),第2个元素为NULL(相应的RTE=子查询),第3个元素为基础关系(相应的RTE=t_grxx),第4个元素为基础关系(相应的RTE=t_jfxx),第5个元素为NULL(相应的RTE=连接)
(gdb) p *root->simple_rel_array[0]Cannot access memory at address 0x0(gdb) p *root->simple_rel_array[1]$31 = {type = T_RelOptInfo, reloptkind = RELOPT_BASEREL, relids = 0x1736828, rows = 0, consider_startup = false, consider_param_startup = false, consider_parallel = false, reltarget = 0x1736840, pathlist = 0x0, ppilist = 0x0, partial_pathlist = 0x0, cheapest_startup_path = 0x0, cheapest_total_path = 0x0, cheapest_unique_path = 0x0, cheapest_parameterized_paths = 0x0, direct_lateral_relids = 0x0, lateral_relids = 0x0, relid = 1, reltablespace = 0, rtekind = RTE_RELATION, min_attr = -7, max_attr = 3, attr_needed = 0x1736890, attr_widths = 0x1736920, lateral_vars = 0x0, lateral_referencers = 0x0, indexlist = 0x1736cc8, statlist = 0x0, pages = 1, tuples = 3, allvisfrac = 0, subroot = 0x0, subplan_params = 0x0, rel_parallel_workers = -1, serverid = 0, userid = 0, useridiscurrent = false, fdwroutine = 0x0, fdw_private = 0x0, unique_for_rels = 0x0, non_unique_for_rels = 0x0, baserestrictinfo = 0x0, baserestrictcost = {startup = 0, per_tuple = 0}, baserestrict_min_security = 4294967295, joininfo = 0x0, has_eclass_joins = false, top_parent_relids = 0x0, part_scheme = 0x0, nparts = 0, boundinfo = 0x0, partition_qual = 0x0, part_rels = 0x0, partexprs = 0x0, nullable_partexprs = 0x0, partitioned_child_rels = 0x0}(gdb) p *root->simple_rel_array[2]Cannot access memory at address 0x0(gdb) p *root->simple_rel_array[3]$32 = {type = T_RelOptInfo, reloptkind = RELOPT_BASEREL, relids = 0x17377f8, rows = 0, consider_startup = false, consider_param_startup = false, consider_parallel = false, reltarget = 0x1737810, pathlist = 0x0, ppilist = 0x0, partial_pathlist = 0x0, cheapest_startup_path = 0x0, cheapest_total_path = 0x0, cheapest_unique_path = 0x0, cheapest_parameterized_paths = 0x0, direct_lateral_relids = 0x0, lateral_relids = 0x0, relid = 3, reltablespace = 0, rtekind = RTE_RELATION, min_attr = -7, max_attr = 5, attr_needed = 0x1737860, attr_widths = 0x17378f0, lateral_vars = 0x0, lateral_referencers = 0x0, indexlist = 0x0, statlist = 0x0, pages = 10, tuples = 400, allvisfrac = 0, subroot = 0x0, subplan_params = 0x0, rel_parallel_workers = -1, serverid = 0, userid = 0, useridiscurrent = false, fdwroutine = 0x0, fdw_private = 0x0, unique_for_rels = 0x0, non_unique_for_rels = 0x0, baserestrictinfo = 0x0, baserestrictcost = {startup = 0, per_tuple = 0}, baserestrict_min_security = 4294967295, joininfo = 0x0, has_eclass_joins = false, top_parent_relids = 0x0, part_scheme = 0x0, nparts = 0, boundinfo = 0x0, partition_qual = 0x0, part_rels = 0x0, partexprs = 0x0, nullable_partexprs = 0x0, partitioned_child_rels = 0x0}(gdb) p *root->simple_rel_array[4]$33 = {type = T_RelOptInfo, reloptkind = RELOPT_BASEREL, relids = 0x1737b50, rows = 0, consider_startup = false, consider_param_startup = false, consider_parallel = false, reltarget = 0x1737b68, pathlist = 0x0, ppilist = 0x0, partial_pathlist = 0x0, cheapest_startup_path = 0x0, cheapest_total_path = 0x0, cheapest_unique_path = 0x0, cheapest_parameterized_paths = 0x0, direct_lateral_relids = 0x0, lateral_relids = 0x0, relid = 4, reltablespace = 0, rtekind = RTE_RELATION, min_attr = -7, max_attr = 3, attr_needed = 0x1737bb8, attr_widths = 0x1737c48, lateral_vars = 0x0, lateral_referencers = 0x0, indexlist = 0x0, statlist = 0x0, pages = 10, tuples = 720, allvisfrac = 0, subroot = 0x0, subplan_params = 0x0, rel_parallel_workers = -1, serverid = 0, userid = 0, useridiscurrent = false, fdwroutine = 0x0, fdw_private = 0x0, unique_for_rels = 0x0, non_unique_for_rels = 0x0, baserestrictinfo = 0x0, baserestrictcost = {startup = 0, per_tuple = 0}, baserestrict_min_security = 4294967295, joininfo = 0x0, has_eclass_joins = false, top_parent_relids = 0x0, part_scheme = 0x0, nparts = 0, boundinfo = 0x0, partition_qual = 0x0, part_rels = 0x0, partexprs = 0x0, nullable_partexprs = 0x0, partitioned_child_rels = 0x0}(gdb) p *root->simple_rel_array[5]Cannot access memory at address 0x0
查看root->simple_rel_array[n]->attr_needed、root->simple_rel_array[n]->reltarget的内存结构,以第1个元素为例:
#attr_needed(类型为Relids)为NULL(gdb) p *root->simple_rel_array[1]->attr_needed$47 = (Relids) 0x0#reltarget->exprs为3个Var的链表(gdb) p *root->simple_rel_array[1]->reltarget->exprs$38 = {type = T_List, length = 3, head = 0x1737d40, tail = 0x1737e80}(gdb) p *(Var *)root->simple_rel_array[1]->reltarget->exprs->head->data.ptr_value$40 = {xpr = {type = T_Var}, varno = 1, varattno = 1, vartype = 1043, vartypmod = 104, varcollid = 100, varlevelsup = 0, varnoold = 1, varoattno = 1, location = 7}
继续执行,调用函数find_placeholders_in_jointree
152 find_placeholders_in_jointree(root);(gdb) stepfind_placeholders_in_jointree (root=0x171ae40) at placeholder.c:148148 if (root->glob->lastPHId != 0)(gdb) n155 }(gdb) n154 find_lateral_references(root);#链表为NULL#psql中的:v1似乎不是占位符,理解有偏差//TODO...(gdb) p root->placeholder_list$48 = (List *) 0x0
下面调用子函数find_lateral_references,由于RelOptInfo中的3个不为NULL的元素对应的lateral均为FALSE(只有子查询的lateral为true,但子查询对应的RelOptInfo为NULL),因此root->simple_rel_array[1/3/4/5]->lateral_vars均为NULL
四、参考资料
initsplan.c