From 8b109ebf14cf449d36f293a4cb8188360fd5aec6 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 3 Feb 2006 21:08:49 +0000 Subject: [PATCH] Teach planner to convert simple UNION ALL subqueries into append relations, thereby sharing code with the inheritance case. This puts the UNION-ALL-view approach to partitioned tables on par with inheritance, so far as constraint exclusion is concerned: it works either way. (Still need to update the docs to say so.) The definition of "simple UNION ALL" is a little simpler than I would like --- basically the union arms can only be SELECT * FROM foo --- but it's good enough for partitioned-table cases. --- src/backend/optimizer/path/allpaths.c | 80 ++- src/backend/optimizer/path/joinpath.c | 29 +- src/backend/optimizer/plan/planner.c | 4 +- src/backend/optimizer/prep/prepjointree.c | 830 ++++++++++++++++------ src/backend/optimizer/prep/prepunion.c | 129 +--- src/backend/optimizer/util/relnode.c | 144 +++- src/backend/optimizer/util/tlist.c | 38 +- src/include/optimizer/pathnode.h | 9 +- src/include/optimizer/prep.h | 9 +- src/include/optimizer/tlist.h | 4 +- 10 files changed, 888 insertions(+), 388 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 90add90e4e..890bb59a12 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.140 2006/01/31 21:39:23 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.141 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,6 +42,8 @@ int geqo_threshold; static void set_base_rel_pathlists(PlannerInfo *root); +static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, @@ -133,7 +135,6 @@ set_base_rel_pathlists(PlannerInfo *root) for (rti = 1; rti < root->simple_rel_array_size; rti++) { RelOptInfo *rel = root->simple_rel_array[rti]; - RangeTblEntry *rte; /* there may be empty slots corresponding to non-baserel RTEs */ if (rel == NULL) @@ -145,33 +146,44 @@ set_base_rel_pathlists(PlannerInfo *root) if (rel->reloptkind != RELOPT_BASEREL) continue; - rte = rt_fetch(rti, root->parse->rtable); + set_rel_pathlist(root, rel, rti, + rt_fetch(rti, root->parse->rtable)); + } +} - if (rte->inh) - { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); - } - else if (rel->rtekind == RTE_SUBQUERY) - { - /* Subquery --- generate a separate plan for it */ - set_subquery_pathlist(root, rel, rti, rte); - } - else if (rel->rtekind == RTE_FUNCTION) - { - /* RangeFunction --- generate a separate plan for it */ - set_function_pathlist(root, rel, rte); - } - else - { - /* Plain relation */ - set_plain_rel_pathlist(root, rel, rte); - } +/* + * set_rel_pathlist + * Build access paths for a base relation + */ +static void +set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + if (rte->inh) + { + /* It's an "append relation", process accordingly */ + set_append_rel_pathlist(root, rel, rti, rte); + } + else if (rel->rtekind == RTE_SUBQUERY) + { + /* Subquery --- generate a separate plan for it */ + set_subquery_pathlist(root, rel, rti, rte); + } + else if (rel->rtekind == RTE_FUNCTION) + { + /* RangeFunction --- generate a separate plan for it */ + set_function_pathlist(root, rel, rte); + } + else + { + /* Plain relation */ + Assert(rel->rtekind == RTE_RELATION); + set_plain_rel_pathlist(root, rel, rte); + } #ifdef OPTIMIZER_DEBUG - debug_print_rel(root, rel); + debug_print_rel(root, rel); #endif - } } /* @@ -181,9 +193,6 @@ set_base_rel_pathlists(PlannerInfo *root) static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { - Assert(rel->rtekind == RTE_RELATION); - Assert(!rte->inh); - /* Mark rel with estimated output rows, width, etc */ set_baserel_size_estimates(root, rel); @@ -265,6 +274,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RelOptInfo *childrel; RangeTblEntry *childrte; + Path *childpath; ListCell *parentvars; ListCell *childvars; @@ -346,10 +356,20 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, /* * Compute the child's access paths, and save the cheapest. + * + * It's possible that the child is itself an appendrel, in which + * case we can "cut out the middleman" and just add its child + * paths to our own list. (We don't try to do this earlier because + * we need to apply both levels of transformation to the quals.) */ - set_plain_rel_pathlist(root, childrel, childrte); + set_rel_pathlist(root, childrel, childRTindex, childrte); - subpaths = lappend(subpaths, childrel->cheapest_total_path); + childpath = childrel->cheapest_total_path; + if (IsA(childpath, AppendPath)) + subpaths = list_concat(subpaths, + ((AppendPath *) childpath)->subpaths); + else + subpaths = lappend(subpaths, childpath); /* * Propagate size information from the child back to the parent. For diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 137be08e75..df3bd12874 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.99 2006/01/31 21:39:23 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.100 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,7 +20,6 @@ #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" -#include "optimizer/prep.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" @@ -856,8 +855,6 @@ join_before_append(PlannerInfo *root, int childRTindex; RelOptInfo *childrel; Path *bestinnerjoin; - Relids joinrelids; - Relids *save_attr_needed; RelOptInfo *this_joinrel; List *this_restrictlist; @@ -899,27 +896,9 @@ join_before_append(PlannerInfo *root, * in joinrels.c, it provides necessary context for the Path, * such as properly-translated target and quals lists. */ - joinrelids = bms_copy(joinrel->relids); - joinrelids = bms_del_member(joinrelids, parentRTindex); - joinrelids = bms_add_member(joinrelids, childRTindex); - - /* - * Kluge: temporarily adjust the outer rel's attr_needed info so - * that it references the member rel instead of the appendrel. - * This is needed to build the correct targetlist for the joinrel. - */ - save_attr_needed = outerrel->attr_needed; - outerrel->attr_needed = - adjust_other_rel_attr_needed(outerrel, appinfo, - outerrel->min_attr, - outerrel->max_attr); - - this_joinrel = build_join_rel(root, joinrelids, outerrel, childrel, - jointype, &this_restrictlist); - - /* Now we can undo the hack on attr_needed */ - pfree(outerrel->attr_needed); - outerrel->attr_needed = save_attr_needed; + this_joinrel = translate_join_rel(root, joinrel, appinfo, + outerrel, childrel, jointype, + &this_restrictlist); /* Build Path for join and add to result list */ append_paths = lappend(append_paths, diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index e007e4e594..e530a1ac6e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.197 2006/01/31 21:39:24 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.198 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -222,7 +222,7 @@ subquery_planner(Query *parse, double tuple_fraction, * this query. */ parse->jointree = (FromExpr *) - pull_up_subqueries(root, (Node *) parse->jointree, false); + pull_up_subqueries(root, (Node *) parse->jointree, false, false); /* * Detect whether any rangetable entries are RTE_JOIN kind; if not, we can diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 546c4bd275..a955c7365e 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -15,16 +15,19 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.34 2006/01/31 21:39:24 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.35 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/prep.h" #include "optimizer/subselect.h" +#include "optimizer/tlist.h" #include "optimizer/var.h" +#include "parser/parse_expr.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -37,8 +40,23 @@ typedef struct reduce_outer_joins_state List *sub_states; /* List of states for subtree components */ } reduce_outer_joins_state; +static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, + RangeTblEntry *rte, + bool below_outer_join, + bool append_rel_member); +static Node *pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, + RangeTblEntry *rte); +static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, + int parentRTindex, Query *setOpQuery); +static void make_setop_translation_lists(Query *query, + Index newvarno, + List **col_mappings, List **translated_vars); static bool is_simple_subquery(Query *subquery); +static bool is_simple_union_all(Query *subquery); +static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, + List *colTypes); static bool has_nullable_targetlist(Query *subquery); +static bool is_safe_append_member(Query *subquery); static void resolvenew_in_jointree(Node *jtnode, int varno, RangeTblEntry *rte, List *subtlist); static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode); @@ -48,6 +66,8 @@ static void reduce_outer_joins_pass2(Node *jtnode, Relids nonnullable_rels); static void fix_in_clause_relids(List *in_info_list, int varno, Relids subrelids); +static void fix_append_rel_relids(List *append_rel_list, int varno, + Relids subrelids); static Node *find_jointree_node_for_rel(Node *jtnode, int relid); @@ -110,10 +130,16 @@ pull_up_IN_clauses(PlannerInfo *root, Node *node) * Look for subqueries in the rangetable that can be pulled up into * the parent query. If the subquery has no special features like * grouping/aggregation then we can merge it into the parent's jointree. + * Also, subqueries that are simple UNION ALL structures can be + * converted into "append relations". * * below_outer_join is true if this jointree node is within the nullable * side of an outer join. This restricts what we can do. * + * append_rel_member is true if we are looking at a member subquery of + * an append relation. This puts some different restrictions on what + * we can do. + * * A tricky aspect of this code is that if we pull up a subquery we have * to replace Vars that reference the subquery's outputs throughout the * parent query, including quals attached to jointree nodes above the one @@ -124,16 +150,15 @@ pull_up_IN_clauses(PlannerInfo *root, Node *node) * copy of the tree; we have to invoke it just on the quals, instead. */ Node * -pull_up_subqueries(PlannerInfo *root, Node *jtnode, bool below_outer_join) +pull_up_subqueries(PlannerInfo *root, Node *jtnode, + bool below_outer_join, bool append_rel_member) { if (jtnode == NULL) return NULL; if (IsA(jtnode, RangeTblRef)) { int varno = ((RangeTblRef *) jtnode)->rtindex; - Query *parse = root->parse; - RangeTblEntry *rte = rt_fetch(varno, parse->rtable); - Query *subquery = rte->subquery; + RangeTblEntry *rte = rt_fetch(varno, root->parse->rtable); /* * Is this a subquery RTE, and if so, is the subquery simple enough to @@ -148,260 +173,74 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode, bool below_outer_join) * expressions; we'd have to figure out how to get the pseudo- * variables evaluated at the right place in the modified plan tree. * Fix it someday. + * + * If we are looking at an append-relation member, we can't pull + * it up unless is_safe_append_member says so. */ if (rte->rtekind == RTE_SUBQUERY && - is_simple_subquery(subquery) && - (!below_outer_join || has_nullable_targetlist(subquery))) - { - PlannerInfo *subroot; - int rtoffset; - List *subtlist; - ListCell *rt; + is_simple_subquery(rte->subquery) && + (!below_outer_join || has_nullable_targetlist(rte->subquery)) && + (!append_rel_member || is_safe_append_member(rte->subquery))) + return pull_up_simple_subquery(root, jtnode, rte, + below_outer_join, + append_rel_member); - /* - * Need a modifiable copy of the subquery to hack on. Even if we - * didn't sometimes choose not to pull up below, we must do this - * to avoid problems if the same subquery is referenced from - * multiple jointree items (which can't happen normally, but might - * after rule rewriting). - */ - subquery = copyObject(subquery); - - /* - * Create a PlannerInfo data structure for this subquery. - * - * NOTE: the next few steps should match the first processing in - * subquery_planner(). Can we refactor to avoid code duplication, - * or would that just make things uglier? - */ - subroot = makeNode(PlannerInfo); - subroot->parse = subquery; - subroot->in_info_list = NIL; - subroot->append_rel_list = NIL; - - /* - * Pull up any IN clauses within the subquery's WHERE, so that we - * don't leave unoptimized INs behind. - */ - if (subquery->hasSubLinks) - subquery->jointree->quals = pull_up_IN_clauses(subroot, - subquery->jointree->quals); - - /* - * Recursively pull up the subquery's subqueries, so that this - * routine's processing is complete for its jointree and - * rangetable. - * - * Note: 'false' is correct here even if we are within an outer - * join in the upper query; the lower query starts with a clean - * slate for outer-join semantics. - */ - subquery->jointree = (FromExpr *) - pull_up_subqueries(subroot, (Node *) subquery->jointree, - false); - - /* - * Now we must recheck whether the subquery is still simple enough - * to pull up. If not, abandon processing it. - * - * We don't really need to recheck all the conditions involved, - * but it's easier just to keep this "if" looking the same as the - * one above. - */ - if (is_simple_subquery(subquery) && - (!below_outer_join || has_nullable_targetlist(subquery))) - { - /* good to go */ - } - else - { - /* - * Give up, return unmodified RangeTblRef. - * - * Note: The work we just did will be redone when the subquery - * gets planned on its own. Perhaps we could avoid that by - * storing the modified subquery back into the rangetable, but - * I'm not gonna risk it now. - */ - return jtnode; - } - - /* - * Adjust level-0 varnos in subquery so that we can append its - * rangetable to upper query's. We have to fix the subquery's - * in_info_list and append_rel_list, as well. - */ - rtoffset = list_length(parse->rtable); - OffsetVarNodes((Node *) subquery, rtoffset, 0); - OffsetVarNodes((Node *) subroot->in_info_list, rtoffset, 0); - OffsetVarNodes((Node *) subroot->append_rel_list, rtoffset, 0); - - /* - * Upper-level vars in subquery are now one level closer to their - * parent than before. - */ - IncrementVarSublevelsUp((Node *) subquery, -1, 1); - IncrementVarSublevelsUp((Node *) subroot->in_info_list, -1, 1); - IncrementVarSublevelsUp((Node *) subroot->append_rel_list, -1, 1); - - /* - * Replace all of the top query's references to the subquery's - * outputs with copies of the adjusted subtlist items, being - * careful not to replace any of the jointree structure. (This'd - * be a lot cleaner if we could use query_tree_mutator.) - */ - subtlist = subquery->targetList; - parse->targetList = (List *) - ResolveNew((Node *) parse->targetList, - varno, 0, rte, - subtlist, CMD_SELECT, 0); - resolvenew_in_jointree((Node *) parse->jointree, varno, - rte, subtlist); - Assert(parse->setOperations == NULL); - parse->havingQual = - ResolveNew(parse->havingQual, - varno, 0, rte, - subtlist, CMD_SELECT, 0); - root->in_info_list = (List *) - ResolveNew((Node *) root->in_info_list, - varno, 0, rte, - subtlist, CMD_SELECT, 0); - root->append_rel_list = (List *) - ResolveNew((Node *) root->append_rel_list, - varno, 0, rte, - subtlist, CMD_SELECT, 0); - - foreach(rt, parse->rtable) - { - RangeTblEntry *otherrte = (RangeTblEntry *) lfirst(rt); - - if (otherrte->rtekind == RTE_JOIN) - otherrte->joinaliasvars = (List *) - ResolveNew((Node *) otherrte->joinaliasvars, - varno, 0, rte, - subtlist, CMD_SELECT, 0); - } - - /* - * Now append the adjusted rtable entries to upper query. (We hold - * off until after fixing the upper rtable entries; no point in - * running that code on the subquery ones too.) - */ - parse->rtable = list_concat(parse->rtable, subquery->rtable); - - /* - * Pull up any FOR UPDATE/SHARE markers, too. (OffsetVarNodes - * already adjusted the marker values, so just list_concat the - * list.) - * - * Executor can't handle multiple FOR UPDATE/SHARE/NOWAIT flags, - * so complain if they are valid but different - */ - if (parse->rowMarks && subquery->rowMarks) - { - if (parse->forUpdate != subquery->forUpdate) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot use both FOR UPDATE and FOR SHARE in one query"))); - if (parse->rowNoWait != subquery->rowNoWait) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot use both wait and NOWAIT in one query"))); - } - parse->rowMarks = list_concat(parse->rowMarks, subquery->rowMarks); - if (subquery->rowMarks) - { - parse->forUpdate = subquery->forUpdate; - parse->rowNoWait = subquery->rowNoWait; - } - - /* - * We also have to fix the relid sets of any parent InClauseInfo - * nodes. (This could perhaps be done by ResolveNew, but it would - * clutter that routine's API unreasonably.) - */ - if (root->in_info_list) - { - Relids subrelids; - - subrelids = get_relids_in_jointree((Node *) subquery->jointree); - fix_in_clause_relids(root->in_info_list, varno, subrelids); - } - - /* - * And now append any subquery InClauseInfos to our list. - */ - root->in_info_list = list_concat(root->in_info_list, - subroot->in_info_list); - - /* - * XXX need to do something about adjusting AppendRelInfos too - */ - Assert(root->append_rel_list == NIL); - - /* Also pull up any subquery AppendRelInfos */ - root->append_rel_list = list_concat(root->append_rel_list, - subroot->append_rel_list); - - /* - * We don't have to do the equivalent bookkeeping for outer-join - * info, because that hasn't been set up yet. - */ - Assert(root->oj_info_list == NIL); - Assert(subroot->oj_info_list == NIL); - - /* - * Miscellaneous housekeeping. - */ - parse->hasSubLinks |= subquery->hasSubLinks; - /* subquery won't be pulled up if it hasAggs, so no work there */ - - /* - * Return the adjusted subquery jointree to replace the - * RangeTblRef entry in my jointree. - */ - return (Node *) subquery->jointree; - } + /* + * Alternatively, is it a simple UNION ALL subquery? If so, flatten + * into an "append relation". We can do this regardless of nullability + * considerations since this transformation does not result in + * propagating non-Var expressions into upper levels of the query. + * + * It's also safe to do this regardless of whether this query is + * itself an appendrel member. (If you're thinking we should try + * to flatten the two levels of appendrel together, you're right; + * but we handle that in set_append_rel_pathlist, not here.) + */ + if (rte->rtekind == RTE_SUBQUERY && + is_simple_union_all(rte->subquery)) + return pull_up_simple_union_all(root, jtnode, rte); } else if (IsA(jtnode, FromExpr)) { FromExpr *f = (FromExpr *) jtnode; ListCell *l; + Assert(!append_rel_member); foreach(l, f->fromlist) lfirst(l) = pull_up_subqueries(root, lfirst(l), - below_outer_join); + below_outer_join, false); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; + Assert(!append_rel_member); /* Recurse, being careful to tell myself when inside outer join */ switch (j->jointype) { case JOIN_INNER: j->larg = pull_up_subqueries(root, j->larg, - below_outer_join); + below_outer_join, false); j->rarg = pull_up_subqueries(root, j->rarg, - below_outer_join); + below_outer_join, false); break; case JOIN_LEFT: j->larg = pull_up_subqueries(root, j->larg, - below_outer_join); + below_outer_join, false); j->rarg = pull_up_subqueries(root, j->rarg, - true); + true, false); break; case JOIN_FULL: j->larg = pull_up_subqueries(root, j->larg, - true); + true, false); j->rarg = pull_up_subqueries(root, j->rarg, - true); + true, false); break; case JOIN_RIGHT: j->larg = pull_up_subqueries(root, j->larg, - true); + true, false); j->rarg = pull_up_subqueries(root, j->rarg, - below_outer_join); + below_outer_join, false); break; case JOIN_UNION: @@ -425,6 +264,379 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode, bool below_outer_join) return jtnode; } +/* + * pull_up_simple_subquery + * Attempt to pull up a single simple subquery. + * + * jtnode is a RangeTblRef that has been tentatively identified as a simple + * subquery by pull_up_subqueries. We return the replacement jointree node, + * or jtnode itself if we determine that the subquery can't be pulled up after + * all. + */ +static Node * +pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, + bool below_outer_join, bool append_rel_member) +{ + Query *parse = root->parse; + int varno = ((RangeTblRef *) jtnode)->rtindex; + Query *subquery; + PlannerInfo *subroot; + int rtoffset; + List *subtlist; + ListCell *rt; + + /* + * Need a modifiable copy of the subquery to hack on. Even if we + * didn't sometimes choose not to pull up below, we must do this + * to avoid problems if the same subquery is referenced from + * multiple jointree items (which can't happen normally, but might + * after rule rewriting). + */ + subquery = copyObject(rte->subquery); + + /* + * Create a PlannerInfo data structure for this subquery. + * + * NOTE: the next few steps should match the first processing in + * subquery_planner(). Can we refactor to avoid code duplication, + * or would that just make things uglier? + */ + subroot = makeNode(PlannerInfo); + subroot->parse = subquery; + subroot->in_info_list = NIL; + subroot->append_rel_list = NIL; + + /* + * Pull up any IN clauses within the subquery's WHERE, so that we + * don't leave unoptimized INs behind. + */ + if (subquery->hasSubLinks) + subquery->jointree->quals = pull_up_IN_clauses(subroot, + subquery->jointree->quals); + + /* + * Recursively pull up the subquery's subqueries, so that + * pull_up_subqueries' processing is complete for its jointree and + * rangetable. + * + * Note: below_outer_join = false is correct here even if we are within an + * outer join in the upper query; the lower query starts with a clean + * slate for outer-join semantics. Likewise, we say we aren't handling + * an appendrel member. + */ + subquery->jointree = (FromExpr *) + pull_up_subqueries(subroot, (Node *) subquery->jointree, false, false); + + /* + * Now we must recheck whether the subquery is still simple enough + * to pull up. If not, abandon processing it. + * + * We don't really need to recheck all the conditions involved, + * but it's easier just to keep this "if" looking the same as the + * one in pull_up_subqueries. + */ + if (is_simple_subquery(subquery) && + (!below_outer_join || has_nullable_targetlist(subquery)) && + (!append_rel_member || is_safe_append_member(subquery))) + { + /* good to go */ + } + else + { + /* + * Give up, return unmodified RangeTblRef. + * + * Note: The work we just did will be redone when the subquery + * gets planned on its own. Perhaps we could avoid that by + * storing the modified subquery back into the rangetable, but + * I'm not gonna risk it now. + */ + return jtnode; + } + + /* + * Adjust level-0 varnos in subquery so that we can append its + * rangetable to upper query's. We have to fix the subquery's + * in_info_list and append_rel_list, as well. + */ + rtoffset = list_length(parse->rtable); + OffsetVarNodes((Node *) subquery, rtoffset, 0); + OffsetVarNodes((Node *) subroot->in_info_list, rtoffset, 0); + OffsetVarNodes((Node *) subroot->append_rel_list, rtoffset, 0); + + /* + * Upper-level vars in subquery are now one level closer to their + * parent than before. + */ + IncrementVarSublevelsUp((Node *) subquery, -1, 1); + IncrementVarSublevelsUp((Node *) subroot->in_info_list, -1, 1); + IncrementVarSublevelsUp((Node *) subroot->append_rel_list, -1, 1); + + /* + * Replace all of the top query's references to the subquery's + * outputs with copies of the adjusted subtlist items, being + * careful not to replace any of the jointree structure. (This'd + * be a lot cleaner if we could use query_tree_mutator.) + */ + subtlist = subquery->targetList; + parse->targetList = (List *) + ResolveNew((Node *) parse->targetList, + varno, 0, rte, + subtlist, CMD_SELECT, 0); + resolvenew_in_jointree((Node *) parse->jointree, varno, + rte, subtlist); + Assert(parse->setOperations == NULL); + parse->havingQual = + ResolveNew(parse->havingQual, + varno, 0, rte, + subtlist, CMD_SELECT, 0); + root->in_info_list = (List *) + ResolveNew((Node *) root->in_info_list, + varno, 0, rte, + subtlist, CMD_SELECT, 0); + root->append_rel_list = (List *) + ResolveNew((Node *) root->append_rel_list, + varno, 0, rte, + subtlist, CMD_SELECT, 0); + + foreach(rt, parse->rtable) + { + RangeTblEntry *otherrte = (RangeTblEntry *) lfirst(rt); + + if (otherrte->rtekind == RTE_JOIN) + otherrte->joinaliasvars = (List *) + ResolveNew((Node *) otherrte->joinaliasvars, + varno, 0, rte, + subtlist, CMD_SELECT, 0); + } + + /* + * Now append the adjusted rtable entries to upper query. (We hold + * off until after fixing the upper rtable entries; no point in + * running that code on the subquery ones too.) + */ + parse->rtable = list_concat(parse->rtable, subquery->rtable); + + /* + * Pull up any FOR UPDATE/SHARE markers, too. (OffsetVarNodes + * already adjusted the marker values, so just list_concat the + * list.) + * + * Executor can't handle multiple FOR UPDATE/SHARE/NOWAIT flags, + * so complain if they are valid but different + */ + if (parse->rowMarks && subquery->rowMarks) + { + if (parse->forUpdate != subquery->forUpdate) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use both FOR UPDATE and FOR SHARE in one query"))); + if (parse->rowNoWait != subquery->rowNoWait) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use both wait and NOWAIT in one query"))); + } + parse->rowMarks = list_concat(parse->rowMarks, subquery->rowMarks); + if (subquery->rowMarks) + { + parse->forUpdate = subquery->forUpdate; + parse->rowNoWait = subquery->rowNoWait; + } + + /* + * We also have to fix the relid sets of any parent InClauseInfo + * nodes. (This could perhaps be done by ResolveNew, but it would + * clutter that routine's API unreasonably.) + * + * Likewise, relids appearing in AppendRelInfo nodes have to be fixed + * (but we took care of their translated_vars lists above). We already + * checked that this won't require introducing multiple subrelids into + * the single-slot AppendRelInfo structs. + */ + if (root->in_info_list || root->append_rel_list) + { + Relids subrelids; + + subrelids = get_relids_in_jointree((Node *) subquery->jointree); + fix_in_clause_relids(root->in_info_list, varno, subrelids); + fix_append_rel_relids(root->append_rel_list, varno, subrelids); + } + + /* + * And now add any subquery InClauseInfos and AppendRelInfos to our lists. + */ + root->in_info_list = list_concat(root->in_info_list, + subroot->in_info_list); + root->append_rel_list = list_concat(root->append_rel_list, + subroot->append_rel_list); + + /* + * We don't have to do the equivalent bookkeeping for outer-join + * info, because that hasn't been set up yet. + */ + Assert(root->oj_info_list == NIL); + Assert(subroot->oj_info_list == NIL); + + /* + * Miscellaneous housekeeping. + */ + parse->hasSubLinks |= subquery->hasSubLinks; + /* subquery won't be pulled up if it hasAggs, so no work there */ + + /* + * Return the adjusted subquery jointree to replace the + * RangeTblRef entry in parent's jointree. + */ + return (Node *) subquery->jointree; +} + +/* + * pull_up_simple_union_all + * Pull up a single simple UNION ALL subquery. + * + * jtnode is a RangeTblRef that has been identified as a simple UNION ALL + * subquery by pull_up_subqueries. We pull up the leaf subqueries and + * build an "append relation" for the union set. The result value is just + * jtnode, since we don't actually need to change the query jointree. + */ +static Node * +pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) +{ + int varno = ((RangeTblRef *) jtnode)->rtindex; + Query *subquery = rte->subquery; + + /* + * Recursively scan the subquery's setOperations tree and copy the leaf + * subqueries into the parent rangetable. Add AppendRelInfo nodes for + * them to the parent's append_rel_list, too. + */ + Assert(subquery->setOperations); + pull_up_union_leaf_queries(subquery->setOperations, root, varno, subquery); + + /* + * Mark the parent as an append relation. + */ + rte->inh = true; + + return jtnode; +} + +/* + * pull_up_union_leaf_queries -- recursive guts of pull_up_simple_union_all + * + * Note that setOpQuery is the Query containing the setOp node, whose rtable + * is where to look up the RTE if setOp is a RangeTblRef. This is *not* the + * same as root->parse, which is the top-level Query we are pulling up into. + * parentRTindex is the appendrel parent's index in root->parse->rtable. + */ +static void +pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex, + Query *setOpQuery) +{ + if (IsA(setOp, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) setOp; + RangeTblEntry *rte = rt_fetch(rtr->rtindex, setOpQuery->rtable); + Query *subquery; + int childRTindex; + AppendRelInfo *appinfo; + Query *parse = root->parse; + + /* + * Make a modifiable copy of the child RTE and contained query. + */ + rte = copyObject(rte); + subquery = rte->subquery; + Assert(subquery != NULL); + + /* + * Upper-level vars in subquery are now one level closer to their + * parent than before. We don't have to worry about offsetting + * varnos, though, because any such vars must refer to stuff above + * the level of the query we are pulling into. + */ + IncrementVarSublevelsUp((Node *) subquery, -1, 1); + + /* + * Attach child RTE to parent rtable. + */ + parse->rtable = lappend(parse->rtable, rte); + childRTindex = list_length(parse->rtable); + + /* + * Build a suitable AppendRelInfo, and attach to parent's list. + */ + appinfo = makeNode(AppendRelInfo); + appinfo->parent_relid = parentRTindex; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = InvalidOid; + appinfo->child_reltype = InvalidOid; + make_setop_translation_lists(setOpQuery, childRTindex, + &appinfo->col_mappings, + &appinfo->translated_vars); + appinfo->parent_reloid = InvalidOid; + root->append_rel_list = lappend(root->append_rel_list, appinfo); + + /* + * Recursively apply pull_up_subqueries to the new child RTE. (We + * must build the AppendRelInfo first, because this will modify it.) + * Note that we can pass below_outer_join = false even if we're + * actually under an outer join, because the child's expressions + * aren't going to propagate up above the join. + */ + rtr = makeNode(RangeTblRef); + rtr->rtindex = childRTindex; + (void) pull_up_subqueries(root, (Node *) rtr, false, true); + } + else if (IsA(setOp, SetOperationStmt)) + { + SetOperationStmt *op = (SetOperationStmt *) setOp; + + /* Recurse to reach leaf queries */ + pull_up_union_leaf_queries(op->larg, root, parentRTindex, setOpQuery); + pull_up_union_leaf_queries(op->rarg, root, parentRTindex, setOpQuery); + } + else + { + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(setOp)); + } +} + +/* + * make_setop_translation_lists + * Build the lists of translations from parent Vars to child Vars for + * a UNION ALL member. We need both a column number mapping list + * and a list of Vars representing the child columns. + */ +static void +make_setop_translation_lists(Query *query, + Index newvarno, + List **col_mappings, List **translated_vars) +{ + List *numbers = NIL; + List *vars = NIL; + ListCell *l; + + foreach(l, query->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (tle->resjunk) + continue; + + numbers = lappend_int(numbers, tle->resno); + vars = lappend(vars, makeVar(newvarno, + tle->resno, + exprType((Node *) tle->expr), + exprTypmod((Node *) tle->expr), + 0)); + } + + *col_mappings = numbers; + *translated_vars = vars; +} + /* * is_simple_subquery * Check a subquery in the range table to see if it's simple enough @@ -443,7 +655,8 @@ is_simple_subquery(Query *subquery) elog(ERROR, "subquery is bogus"); /* - * Can't currently pull up a query with setops. Maybe after querytree + * Can't currently pull up a query with setops (unless it's simple UNION + * ALL, which is handled by a different code path). Maybe after querytree * redesign... */ if (subquery->setOperations) @@ -484,6 +697,78 @@ is_simple_subquery(Query *subquery) return true; } +/* + * is_simple_union_all + * Check a subquery to see if it's a simple UNION ALL. + * + * We require all the setops to be UNION ALL (no mixing) and there can't be + * any datatype coercions involved, ie, all the leaf queries must emit the + * same datatypes. + */ +static bool +is_simple_union_all(Query *subquery) +{ + SetOperationStmt *topop; + + /* Let's just make sure it's a valid subselect ... */ + if (!IsA(subquery, Query) || + subquery->commandType != CMD_SELECT || + subquery->resultRelation != 0 || + subquery->into != NULL) + elog(ERROR, "subquery is bogus"); + + /* Is it a set-operation query at all? */ + topop = (SetOperationStmt *) subquery->setOperations; + if (!topop) + return false; + Assert(IsA(topop, SetOperationStmt)); + + /* Can't handle ORDER BY, LIMIT/OFFSET, or locking */ + if (subquery->sortClause || + subquery->limitOffset || + subquery->limitCount || + subquery->rowMarks) + return false; + + /* Recursively check the tree of set operations */ + return is_simple_union_all_recurse((Node *) topop, subquery, + topop->colTypes); +} + +static bool +is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, List *colTypes) +{ + if (IsA(setOp, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) setOp; + RangeTblEntry *rte = rt_fetch(rtr->rtindex, setOpQuery->rtable); + Query *subquery = rte->subquery; + + Assert(subquery != NULL); + + /* Leaf nodes are OK if they match the toplevel column types */ + return tlist_same_datatypes(subquery->targetList, colTypes, true); + } + else if (IsA(setOp, SetOperationStmt)) + { + SetOperationStmt *op = (SetOperationStmt *) setOp; + + /* Must be UNION ALL */ + if (op->op != SETOP_UNION || !op->all) + return false; + + /* Recurse to check inputs */ + return is_simple_union_all_recurse(op->larg, setOpQuery, colTypes) && + is_simple_union_all_recurse(op->rarg, setOpQuery, colTypes); + } + else + { + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(setOp)); + return false; /* keep compiler quiet */ + } +} + /* * has_nullable_targetlist * Check a subquery in the range table to see if all the non-junk @@ -521,6 +806,62 @@ has_nullable_targetlist(Query *subquery) return true; } +/* + * is_safe_append_member + * Check a subquery that is a leaf of a UNION ALL appendrel to see if it's + * safe to pull up. + */ +static bool +is_safe_append_member(Query *subquery) +{ + FromExpr *jtnode; + ListCell *l; + + /* + * It's only safe to pull up the child if its jointree contains + * exactly one RTE, else the AppendRelInfo data structure breaks. + * The one base RTE could be buried in several levels of FromExpr, + * however. + * + * Also, the child can't have any WHERE quals because there's no + * place to put them in an appendrel. (This is a bit annoying...) + * If we didn't need to check this, we'd just test whether + * get_relids_in_jointree() yields a singleton set, to be more + * consistent with the coding of fix_append_rel_relids(). + */ + jtnode = subquery->jointree; + while (IsA(jtnode, FromExpr)) + { + if (jtnode->quals != NULL) + return false; + if (list_length(jtnode->fromlist) != 1) + return false; + jtnode = linitial(jtnode->fromlist); + } + if (!IsA(jtnode, RangeTblRef)) + return false; + + /* + * XXX For the moment we also have to insist that the subquery's tlist + * includes only simple Vars. This is pretty annoying, but fixing it + * seems to require nontrivial changes --- mainly because joinrel + * tlists are presently assumed to contain only Vars. Perhaps a + * pseudo-variable mechanism similar to the one speculated about + * in pull_up_subqueries' comments would help? FIXME someday. + */ + foreach(l, subquery->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (tle->resjunk) + continue; + if (!(tle->expr && IsA(tle->expr, Var))) + return false; + } + + return true; +} + /* * Helper routine for pull_up_subqueries: do ResolveNew on every expression * in the jointree, without changing the jointree structure itself. Ugly, @@ -851,6 +1192,43 @@ fix_in_clause_relids(List *in_info_list, int varno, Relids subrelids) } } +/* + * fix_append_rel_relids: update RT-index fields of AppendRelInfo nodes + * + * When we pull up a subquery, any AppendRelInfo references to the subquery's + * RT index have to be replaced by the substituted relid (and there had better + * be only one). + * + * We assume we may modify the AppendRelInfo nodes in-place. + */ +static void +fix_append_rel_relids(List *append_rel_list, int varno, Relids subrelids) +{ + ListCell *l; + int subvarno = -1; + + /* + * We only want to extract the member relid once, but we mustn't fail + * immediately if there are multiple members; it could be that none of + * the AppendRelInfo nodes refer to it. So compute it on first use. + * Note that bms_singleton_member will complain if set is not singleton. + */ + foreach(l, append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + + /* The parent_relid shouldn't ever be a pullup target */ + Assert(appinfo->parent_relid != varno); + + if (appinfo->child_relid == varno) + { + if (subvarno < 0) + subvarno = bms_singleton_member(subrelids); + appinfo->child_relid = subvarno; + } + } +} + /* * get_relids_in_jointree: get set of base RT indexes present in a jointree */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index a33213ef59..cc44825be0 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -4,13 +4,17 @@ * Routines to plan set-operation queries. The filename is a leftover * from a time when only UNIONs were implemented. * + * There are two code paths in the planner for set-operation queries. + * If a subquery consists entirely of simple UNION ALL operations, it + * is converted into an "append relation". Otherwise, it is handled + * by the general code in this module (plan_set_operations and its + * subroutines). There is some support code here for the append-relation + * case, but most of the heavy lifting for that is done elsewhere, + * notably in prepjointree.c and allpaths.c. + * * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Although inheritance is radically - * different from set operations as far as the parser representation of - * a query is concerned, we try to handle it identically to the UNION ALL - * case during planning: both are converted to "append rels". (Note that - * UNION ALL is special-cased: other kinds of set operations go through - * a completely different code path.) + * inheritance (SELECT FROM foo*). Inheritance trees are converted into + * append relations, and thenceforth share code with the UNION ALL case. * * * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group @@ -18,7 +22,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.129 2006/01/31 21:39:24 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.130 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -64,12 +68,13 @@ static List *generate_setop_tlist(List *colTypes, int flag, static List *generate_append_tlist(List *colTypes, bool flag, List *input_plans, List *refnames_tlist); -static bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); -static void make_translation_lists(Relation oldrelation, Relation newrelation, - Index newvarno, - List **col_mappings, List **translated_vars); +static void make_inh_translation_lists(Relation oldrelation, + Relation newrelation, + Index newvarno, + List **col_mappings, + List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context); static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid); @@ -659,41 +664,6 @@ generate_append_tlist(List *colTypes, bool flag, return tlist; } -/* - * Does tlist have same datatypes as requested colTypes? - * - * Resjunk columns are ignored if junkOK is true; otherwise presence of - * a resjunk column will always cause a 'false' result. - */ -static bool -tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK) -{ - ListCell *l; - ListCell *curColType = list_head(colTypes); - - foreach(l, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(l); - - if (tle->resjunk) - { - if (!junkOK) - return false; - } - else - { - if (curColType == NULL) - return false; - if (exprType((Node *) tle->expr) != lfirst_oid(curColType)) - return false; - curColType = lnext(curColType); - } - } - if (curColType != NULL) - return false; - return true; -} - /* * find_all_inheritors - @@ -896,9 +866,9 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) appinfo->child_relid = childRTindex; appinfo->parent_reltype = oldrelation->rd_rel->reltype; appinfo->child_reltype = newrelation->rd_rel->reltype; - make_translation_lists(oldrelation, newrelation, childRTindex, - &appinfo->col_mappings, - &appinfo->translated_vars); + make_inh_translation_lists(oldrelation, newrelation, childRTindex, + &appinfo->col_mappings, + &appinfo->translated_vars); appinfo->parent_reloid = parentOID; appinfos = lappend(appinfos, appinfo); @@ -933,7 +903,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * make_translation_lists + * make_inh_translation_lists * Build the lists of translations from parent Vars to child Vars for * an inheritance child. We need both a column number mapping list * and a list of Vars representing the child columns. @@ -941,9 +911,9 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) * For paranoia's sake, we match type as well as attribute name. */ static void -make_translation_lists(Relation oldrelation, Relation newrelation, - Index newvarno, - List **col_mappings, List **translated_vars) +make_inh_translation_lists(Relation oldrelation, Relation newrelation, + Index newvarno, + List **col_mappings, List **translated_vars) { List *numbers = NIL; List *vars = NIL; @@ -1123,8 +1093,18 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context) } else { - /* XXX copy some code from ResolveNew */ - Assert(false);/* not done yet */ + /* + * Build a RowExpr containing the translated variables. + */ + RowExpr *rowexpr; + List *fields; + + fields = (List *) copyObject(context->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + return (Node *) rowexpr; } } /* system attributes don't need any other translation */ @@ -1337,45 +1317,6 @@ adjust_appendrel_attr_needed(RelOptInfo *oldrel, AppendRelInfo *appinfo, return new_attr_needed; } -/* - * adjust_other_rel_attr_needed - * Adjust an attr_needed[] array to reference a member rel instead of - * the original appendrel - * - * This is exactly like adjust_appendrel_attr_needed except that we disregard - * appinfo->col_mappings and instead assume that the mapping of user - * attributes is one-to-one. This is appropriate for generating an attr_needed - * array that describes another relation to be joined with a member rel. - */ -Relids * -adjust_other_rel_attr_needed(RelOptInfo *oldrel, AppendRelInfo *appinfo, - AttrNumber new_min_attr, AttrNumber new_max_attr) -{ - Relids *new_attr_needed; - Index parent_relid = appinfo->parent_relid; - Index child_relid = appinfo->child_relid; - int parent_attr; - - /* Create empty result array */ - Assert(new_min_attr <= oldrel->min_attr); - Assert(new_max_attr >= oldrel->max_attr); - new_attr_needed = (Relids *) - palloc0((new_max_attr - new_min_attr + 1) * sizeof(Relids)); - /* Process user attributes and system attributes */ - for (parent_attr = oldrel->min_attr; parent_attr <= oldrel->max_attr; - parent_attr++) - { - Relids attrneeded; - - attrneeded = oldrel->attr_needed[parent_attr - oldrel->min_attr]; - attrneeded = adjust_relid_set(attrneeded, - parent_relid, child_relid); - new_attr_needed[parent_attr - new_min_attr] = attrneeded; - } - - return new_attr_needed; -} - /* * Adjust the targetlist entries of an inherited UPDATE operation * diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index cedb808227..ca258d5381 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.75 2006/01/31 21:39:24 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.76 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,6 +18,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/plancat.h" +#include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parsetree.h" @@ -570,3 +571,144 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel, } } } + +/* + * translate_join_rel + * Returns relation entry corresponding to the union of two given rels, + * creating a new relation entry if none already exists. This is used + * when one of the inputs is an append child relation. In addition to + * data about the input rels themselves, the corresponding joinrel for + * the append parent relation must be provided, plus the AppendRelInfo + * showing the parent-to-child translation. + * + * The reason for having this code, instead of just applying build_join_rel, + * is that we must have corresponding tlist orderings for all joinrels that + * are involved in an Append plan. So we generate the tlist for joinrels + * involving append child relations by translating the parent joinrel's tlist, + * rather than examining the input relations directly. (Another reason for + * doing it this way is that the base relation attr_needed info in relations + * being joined to the appendrel doesn't refer to the append child rel, but + * the append parent, and so couldn't be used directly anyway.) Otherwise + * this is exactly like build_join_rel. + */ +RelOptInfo * +translate_join_rel(PlannerInfo *root, + RelOptInfo *oldjoinrel, + AppendRelInfo *appinfo, + RelOptInfo *outer_rel, + RelOptInfo *inner_rel, + JoinType jointype, + List **restrictlist_ptr) +{ + RelOptInfo *joinrel; + Relids joinrelids; + List *restrictlist; + + /* + * Construct the Relids set for the translated joinrel, and see if + * we've already built it. + */ + joinrelids = bms_copy(oldjoinrel->relids); + joinrelids = bms_del_member(joinrelids, appinfo->parent_relid); + joinrelids = bms_add_member(joinrelids, appinfo->child_relid); + joinrel = find_join_rel(root, joinrelids); + if (joinrel) + { + /* + * Yes, so we only need to figure the restrictlist for this particular + * pair of component relations. + */ + bms_free(joinrelids); + if (restrictlist_ptr) + *restrictlist_ptr = build_joinrel_restrictlist(root, + joinrel, + outer_rel, + inner_rel, + jointype); + return joinrel; + } + + /* + * Nope, so make one. + */ + joinrel = makeNode(RelOptInfo); + joinrel->reloptkind = RELOPT_JOINREL; + joinrel->relids = joinrelids; + joinrel->rows = 0; + joinrel->width = 0; + joinrel->reltargetlist = NIL; + joinrel->pathlist = NIL; + joinrel->cheapest_startup_path = NULL; + joinrel->cheapest_total_path = NULL; + joinrel->cheapest_unique_path = NULL; + joinrel->relid = 0; /* indicates not a baserel */ + joinrel->rtekind = RTE_JOIN; + joinrel->min_attr = 0; + joinrel->max_attr = 0; + joinrel->attr_needed = NULL; + joinrel->attr_widths = NULL; + joinrel->indexlist = NIL; + joinrel->pages = 0; + joinrel->tuples = 0; + joinrel->subplan = NULL; + joinrel->baserestrictinfo = NIL; + joinrel->baserestrictcost.startup = 0; + joinrel->baserestrictcost.per_tuple = 0; + joinrel->joininfo = NIL; + joinrel->index_outer_relids = NULL; + joinrel->index_inner_paths = NIL; + + /* + * Make the tlist by translating oldjoinrel's tlist, to ensure they + * are in compatible orders. Since we don't call build_joinrel_tlist, + * we need another way to set the rel width; for the moment, just + * assume it is the same as oldjoinrel. (The correct value may well be + * less, but it's not clear it's worth the trouble to get it right.) + */ + joinrel->reltargetlist = (List *) + adjust_appendrel_attrs((Node *) oldjoinrel->reltargetlist, + appinfo); + joinrel->width = oldjoinrel->width; + + /* + * Construct restrict and join clause lists for the new joinrel. (The + * caller might or might not need the restrictlist, but I need it anyway + * for set_joinrel_size_estimates().) + */ + restrictlist = build_joinrel_restrictlist(root, + joinrel, + outer_rel, + inner_rel, + jointype); + if (restrictlist_ptr) + *restrictlist_ptr = restrictlist; + build_joinrel_joinlist(joinrel, outer_rel, inner_rel); + + /* + * Set estimates of the joinrel's size. + */ + set_joinrel_size_estimates(root, joinrel, outer_rel, inner_rel, + jointype, restrictlist); + + /* + * Add the joinrel to the query's joinrel list, and store it into the + * auxiliary hashtable if there is one. NB: GEQO requires us to append + * the new joinrel to the end of the list! + */ + root->join_rel_list = lappend(root->join_rel_list, joinrel); + + if (root->join_rel_hash) + { + JoinHashEntry *hentry; + bool found; + + hentry = (JoinHashEntry *) hash_search(root->join_rel_hash, + &(joinrel->relids), + HASH_ENTER, + &found); + Assert(!found); + hentry->join_rel = joinrel; + } + + return joinrel; +} diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index 955aceefff..6cfcc2949a 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.70 2005/10/15 02:49:21 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/tlist.c,v 1.71 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -167,3 +167,39 @@ get_sortgrouplist_exprs(List *sortClauses, List *targetList) } return result; } + + +/* + * Does tlist have same output datatypes as listed in colTypes? + * + * Resjunk columns are ignored if junkOK is true; otherwise presence of + * a resjunk column will always cause a 'false' result. + */ +bool +tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK) +{ + ListCell *l; + ListCell *curColType = list_head(colTypes); + + foreach(l, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (tle->resjunk) + { + if (!junkOK) + return false; + } + else + { + if (curColType == NULL) + return false; /* tlist longer than colTypes */ + if (exprType((Node *) tle->expr) != lfirst_oid(curColType)) + return false; + curColType = lnext(curColType); + } + } + if (curColType != NULL) + return false; /* tlist shorter than colTypes */ + return true; +} diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 84d9f865e3..3ba612c195 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.64 2006/01/31 21:39:25 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.65 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -95,5 +95,12 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root, RelOptInfo *inner_rel, JoinType jointype, List **restrictlist_ptr); +extern RelOptInfo *translate_join_rel(PlannerInfo *root, + RelOptInfo *oldjoinrel, + AppendRelInfo *appinfo, + RelOptInfo *outer_rel, + RelOptInfo *inner_rel, + JoinType jointype, + List **restrictlist_ptr); #endif /* PATHNODE_H */ diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index ca28cbc886..23b07006ee 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.54 2006/01/31 21:39:25 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.55 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -23,7 +23,7 @@ */ extern Node *pull_up_IN_clauses(PlannerInfo *root, Node *node); extern Node *pull_up_subqueries(PlannerInfo *root, Node *jtnode, - bool below_outer_join); + bool below_outer_join, bool append_rel_member); extern void reduce_outer_joins(PlannerInfo *root); extern Relids get_relids_in_jointree(Node *jtnode); extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid); @@ -55,9 +55,4 @@ extern Relids *adjust_appendrel_attr_needed(RelOptInfo *oldrel, AttrNumber new_min_attr, AttrNumber new_max_attr); -extern Relids *adjust_other_rel_attr_needed(RelOptInfo *oldrel, - AppendRelInfo *appinfo, - AttrNumber new_min_attr, - AttrNumber new_max_attr); - #endif /* PREP_H */ diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h index 91418033ae..a282244f96 100644 --- a/src/include/optimizer/tlist.h +++ b/src/include/optimizer/tlist.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/tlist.h,v 1.42 2005/04/06 16:34:07 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/tlist.h,v 1.43 2006/02/03 21:08:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -29,4 +29,6 @@ extern Node *get_sortgroupclause_expr(SortClause *sortClause, extern List *get_sortgrouplist_exprs(List *sortClauses, List *targetList); +extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK); + #endif /* TLIST_H */