X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Foptimizer%2Fplan%2Fplanner.c;h=500297f2155fd87dc238b8078b93d499603e9265;hb=3389a110d40a505951e7c7babdfb8681173bb2ca;hp=7ffbb4666d9d588a7b9bbe2c8adc7445353b8d9b;hpb=ed5003c58401e5727fcdd970505972394c95febb;p=postgresql diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 7ffbb4666d..500297f215 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3,12 +3,12 @@ * planner.c * The query optimizer external interface. * - * Portions Copyright (c) 1996-2000, PostgreSQL, Inc + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.89 2000/09/12 21:06:54 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.117 2002/05/12 23:43:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,6 +17,9 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" +#ifdef OPTIMIZER_DEBUG +#include "nodes/print.h" +#endif #include "optimizer/clauses.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" @@ -25,17 +28,37 @@ #include "optimizer/subselect.h" #include "optimizer/tlist.h" #include "optimizer/var.h" +#include "parser/analyze.h" +#include "parser/parsetree.h" #include "parser/parse_expr.h" +#include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" -static void preprocess_join_conditions(Query *parse, Node *jtnode); +/* Expression kind codes for preprocess_expression */ +#define EXPRKIND_TARGET 0 +#define EXPRKIND_WHERE 1 +#define EXPRKIND_HAVING 2 + + +static Node *pull_up_subqueries(Query *parse, Node *jtnode, + bool below_outer_join); +static bool is_simple_subquery(Query *subquery); +static bool has_nullable_targetlist(Query *subquery); +static void resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist); +static Node *preprocess_jointree(Query *parse, Node *jtnode); +static Node *preprocess_expression(Query *parse, Node *expr, int kind); +static void preprocess_qual_conditions(Query *parse, Node *jtnode); +static Plan *inheritance_planner(Query *parse, List *inheritlist); +static Plan *grouping_planner(Query *parse, double tuple_fraction); static List *make_subplanTargetList(Query *parse, List *tlist, AttrNumber **groupColIdx); -static Plan *make_groupplan(List *group_tlist, bool tuplePerGroup, +static Plan *make_groupplan(Query *parse, + List *group_tlist, bool tuplePerGroup, List *groupClause, AttrNumber *grpColIdx, bool is_presorted, Plan *subplan); -static Plan *make_sortplan(List *tlist, Plan *plannode, List *sortcls); +static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist); + /***************************************************************************** * @@ -47,54 +70,42 @@ planner(Query *parse) { Plan *result_plan; Index save_PlannerQueryLevel; - List *save_PlannerInitPlan; List *save_PlannerParamVar; - int save_PlannerPlanId; /* * The planner can be called recursively (an example is when - * eval_const_expressions tries to simplify an SQL function). - * So, global state variables must be saved and restored. + * eval_const_expressions tries to pre-evaluate an SQL function). So, + * these global state variables must be saved and restored. + * + * These vars cannot be moved into the Query structure since their whole + * purpose is communication across multiple sub-Queries. * - * (Perhaps these should be moved into the Query structure instead?) + * Note we do NOT save and restore PlannerPlanId: it exists to assign + * unique IDs to SubPlan nodes, and we want those IDs to be unique for + * the life of a backend. Also, PlannerInitPlan is saved/restored in + * subquery_planner, not here. */ save_PlannerQueryLevel = PlannerQueryLevel; - save_PlannerInitPlan = PlannerInitPlan; save_PlannerParamVar = PlannerParamVar; - save_PlannerPlanId = PlannerPlanId; - /* Initialize state for subselects */ - PlannerQueryLevel = 1; - PlannerInitPlan = NULL; - PlannerParamVar = NULL; - PlannerPlanId = 0; + /* Initialize state for handling outer-level references and params */ + PlannerQueryLevel = 0; /* will be 1 in top-level subquery_planner */ + PlannerParamVar = NIL; - /* this should go away sometime soon */ - transformKeySetQuery(parse); - - /* primary planning entry point (may recurse for subplans) */ + /* primary planning entry point (may recurse for subqueries) */ result_plan = subquery_planner(parse, -1.0 /* default case */ ); - Assert(PlannerQueryLevel == 1); - - /* if top-level query had subqueries, do housekeeping for them */ - if (PlannerPlanId > 0) - { - (void) SS_finalize_plan(result_plan); - result_plan->initPlan = PlannerInitPlan; - } + Assert(PlannerQueryLevel == 0); /* executor wants to know total number of Params used overall */ result_plan->nParamExec = length(PlannerParamVar); /* final cleanup of the plan */ - set_plan_references(result_plan); + set_plan_references(parse, result_plan); /* restore state for outer planner, if any */ PlannerQueryLevel = save_PlannerQueryLevel; - PlannerInitPlan = save_PlannerInitPlan; PlannerParamVar = save_PlannerParamVar; - PlannerPlanId = save_PlannerPlanId; return result_plan; } @@ -107,18 +118,16 @@ planner(Query *parse) * * parse is the querytree produced by the parser & rewriter. * tuple_fraction is the fraction of tuples we expect will be retrieved. - * tuple_fraction is interpreted as explained for union_planner, below. + * tuple_fraction is interpreted as explained for grouping_planner, below. * * Basically, this routine does the stuff that should only be done once - * per Query object. It then calls union_planner, which may be called - * recursively on the same Query node in order to handle UNIONs and/or - * inheritance. subquery_planner is called recursively from subselect.c - * to handle sub-Query nodes found within the query's expressions. + * per Query object. It then calls grouping_planner. At one time, + * grouping_planner could be invoked recursively on the same Query object; + * that's not currently true, but we keep the separation between the two + * routines anyway, in case we need it again someday. * - * prepunion.c uses an unholy combination of calling union_planner when - * recursing on the primary Query node, or subquery_planner when recursing - * on a UNION'd Query node that hasn't previously been seen by - * subquery_planner. That whole chunk of code needs rewritten from scratch. + * subquery_planner will be called recursively to handle sub-Query nodes + * found within the query's expressions and rangetable. * * Returns a query plan. *-------------------- @@ -126,167 +135,711 @@ planner(Query *parse) Plan * subquery_planner(Query *parse, double tuple_fraction) { + List *saved_initplan = PlannerInitPlan; + int saved_planid = PlannerPlanId; + Plan *plan; + List *newHaving; + List *lst; + + /* Set up for a new level of subquery */ + PlannerQueryLevel++; + PlannerInitPlan = NIL; + +#ifdef ENABLE_KEY_SET_QUERY + /* this should go away sometime soon */ + transformKeySetQuery(parse); +#endif + /* - * A HAVING clause without aggregates is equivalent to a WHERE clause - * (except it can only refer to grouped fields). If there are no aggs - * anywhere in the query, then we don't want to create an Agg plan - * node, so merge the HAVING condition into WHERE. (We used to - * consider this an error condition, but it seems to be legal SQL.) + * Check to see if any subqueries in the rangetable can be merged into + * this query. */ - if (parse->havingQual != NULL && !parse->hasAggs) - { - if (parse->qual == NULL) - parse->qual = parse->havingQual; - else - parse->qual = (Node *) make_andclause(lappend(lcons(parse->qual, - NIL), - parse->havingQual)); - parse->havingQual = NULL; - } + parse->jointree = (FromExpr *) + pull_up_subqueries(parse, (Node *) parse->jointree, false); /* - * Simplify constant expressions in targetlist and quals. - * - * Note that at this point the qual has not yet been converted to - * implicit-AND form, so we can apply eval_const_expressions directly. - * Also note that we need to do this before SS_process_sublinks, - * because that routine inserts bogus "Const" nodes. + * If so, we may have created opportunities to simplify the jointree. + */ + parse->jointree = (FromExpr *) + preprocess_jointree(parse, (Node *) parse->jointree); + + /* + * Do expression preprocessing on targetlist and quals. */ parse->targetList = (List *) - eval_const_expressions((Node *) parse->targetList); - parse->qual = eval_const_expressions(parse->qual); - parse->havingQual = eval_const_expressions(parse->havingQual); + preprocess_expression(parse, (Node *) parse->targetList, + EXPRKIND_TARGET); + + preprocess_qual_conditions(parse, (Node *) parse->jointree); + + parse->havingQual = preprocess_expression(parse, parse->havingQual, + EXPRKIND_HAVING); + + /* + * Check for ungrouped variables passed to subplans in targetlist and + * HAVING clause (but not in WHERE or JOIN/ON clauses, since those are + * evaluated before grouping). We can't do this any earlier because + * we must use the preprocessed targetlist for comparisons of grouped + * expressions. + */ + if (parse->hasSubLinks && + (parse->groupClause != NIL || parse->hasAggs)) + check_subplans_for_ungrouped_vars(parse); /* - * Canonicalize the qual, and convert it to implicit-AND format. + * A HAVING clause without aggregates is equivalent to a WHERE clause + * (except it can only refer to grouped fields). Transfer any + * agg-free clauses of the HAVING qual into WHERE. This may seem like + * wasting cycles to cater to stupidly-written queries, but there are + * other reasons for doing it. Firstly, if the query contains no aggs + * at all, then we aren't going to generate an Agg plan node, and so + * there'll be no place to execute HAVING conditions; without this + * transfer, we'd lose the HAVING condition entirely, which is wrong. + * Secondly, when we push down a qual condition into a sub-query, it's + * easiest to push the qual into HAVING always, in case it contains + * aggs, and then let this code sort it out. * - * XXX Is there any value in re-applying eval_const_expressions after - * canonicalize_qual? + * Note that both havingQual and parse->jointree->quals are in + * implicitly-ANDed-list form at this point, even though they are + * declared as Node *. Also note that contain_agg_clause does not + * recurse into sub-selects, which is exactly what we need here. */ - parse->qual = (Node *) canonicalize_qual((Expr *) parse->qual, true); + newHaving = NIL; + foreach(lst, (List *) parse->havingQual) + { + Node *havingclause = (Node *) lfirst(lst); -#ifdef OPTIMIZER_DEBUG - printf("After canonicalize_qual()\n"); - pprint(parse->qual); -#endif + if (contain_agg_clause(havingclause)) + newHaving = lappend(newHaving, havingclause); + else + parse->jointree->quals = (Node *) + lappend((List *) parse->jointree->quals, havingclause); + } + parse->havingQual = (Node *) newHaving; /* - * Ditto for the havingQual + * Do the main planning. If we have an inherited target relation, + * that needs special processing, else go straight to + * grouping_planner. */ - parse->havingQual = (Node *) canonicalize_qual((Expr *) parse->havingQual, - true); + if (parse->resultRelation && + (lst = expand_inherted_rtentry(parse, parse->resultRelation, false)) + != NIL) + plan = inheritance_planner(parse, lst); + else + plan = grouping_planner(parse, tuple_fraction); - /* Expand SubLinks to SubPlans */ - if (parse->hasSubLinks) + /* + * If any subplans were generated, or if we're inside a subplan, build + * subPlan, extParam and locParam lists for plan nodes. + */ + if (PlannerPlanId != saved_planid || PlannerQueryLevel > 1) { - parse->targetList = (List *) - SS_process_sublinks((Node *) parse->targetList); - parse->qual = SS_process_sublinks(parse->qual); - parse->havingQual = SS_process_sublinks(parse->havingQual); + (void) SS_finalize_plan(plan); + + /* + * At the moment, SS_finalize_plan doesn't handle initPlans and so + * we assign them to the topmost plan node. + */ + plan->initPlan = PlannerInitPlan; + /* Must add the initPlans' extParams to the topmost node's, too */ + foreach(lst, plan->initPlan) + { + SubPlan *subplan = (SubPlan *) lfirst(lst); + + plan->extParam = set_unioni(plan->extParam, + subplan->plan->extParam); + } + } + + /* Return to outer subquery context */ + PlannerQueryLevel--; + PlannerInitPlan = saved_initplan; + /* we do NOT restore PlannerPlanId; that's not an oversight! */ - if (parse->groupClause != NIL) + return plan; +} + +/* + * pull_up_subqueries + * 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. + * + * below_outer_join is true if this jointree node is within the nullable + * side of an outer join. This restricts 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 + * we are currently processing! We handle this by being careful not to + * change the jointree structure while recursing: no nodes other than + * subquery RangeTblRef entries will be replaced. Also, we can't turn + * ResolveNew loose on the whole jointree, because it'll return a mutated + * copy of the tree; we have to invoke it just on the quals, instead. + */ +static Node * +pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join) +{ + if (jtnode == NULL) + return NULL; + if (IsA(jtnode, RangeTblRef)) + { + int varno = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(varno, parse->rtable); + Query *subquery = rte->subquery; + + /* + * Is this a subquery RTE, and if so, is the subquery simple + * enough to pull up? (If not, do nothing at this node.) + * + * If we are inside an outer join, only pull up subqueries whose + * targetlists are nullable --- otherwise substituting their tlist + * entries for upper Var references would do the wrong thing + * (the results wouldn't become NULL when they're supposed to). + * XXX This could be improved by generating pseudo-variables for + * such 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. + * + * Note: even if the subquery itself is simple enough, we can't pull + * it up if there is a reference to its whole tuple result. Perhaps + * a pseudo-variable is the answer here too. + */ + if (rte->rtekind == RTE_SUBQUERY && is_simple_subquery(subquery) && + (!below_outer_join || has_nullable_targetlist(subquery)) && + !contain_whole_tuple_var((Node *) parse, varno, 0)) { + int rtoffset; + List *subtlist; + List *rt; /* - * Check for ungrouped variables passed to subplans. Note we - * do NOT do this for subplans in WHERE; it's legal there - * because WHERE is evaluated pre-GROUP. - * - * An interesting fine point: if we reassigned a HAVING qual into - * WHERE above, then we will accept references to ungrouped - * vars from subplans in the HAVING qual. This is not - * entirely consistent, but it doesn't seem particularly - * harmful... + * First, recursively pull up the subquery's subqueries, so + * that this routine's processing is complete for its jointree + * and rangetable. NB: if the same subquery is referenced + * from multiple jointree items (which can't happen normally, + * but might after rule rewriting), then we will invoke this + * processing multiple times on that subquery. OK because + * nothing will happen after the first time. We do have to be + * careful to copy everything we pull up, however, or risk + * having chunks of structure multiply linked. + */ + subquery->jointree = (FromExpr *) + pull_up_subqueries(subquery, (Node *) subquery->jointree, + below_outer_join); + + /* + * Now make a modifiable copy of the subquery that we can + * run OffsetVarNodes on. + */ + subquery = copyObject(subquery); + + /* + * Adjust varnos in subquery so that we can append its + * rangetable to upper query's. + */ + rtoffset = length(parse->rtable); + OffsetVarNodes((Node *) subquery, rtoffset, 0); + + /* + * 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, subtlist, CMD_SELECT, 0); + resolvenew_in_jointree((Node *) parse->jointree, varno, subtlist); + Assert(parse->setOperations == NULL); + parse->havingQual = + ResolveNew(parse->havingQual, + varno, 0, subtlist, CMD_SELECT, 0); + + foreach(rt, parse->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt); + + if (rte->rtekind == RTE_JOIN) + rte->joinaliasvars = (List *) + ResolveNew((Node *) rte->joinaliasvars, + varno, 0, 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 = nconc(parse->rtable, subquery->rtable); + + /* + * Pull up any FOR UPDATE markers, too. (OffsetVarNodes + * already adjusted the marker values, so just nconc the list.) + */ + parse->rowMarks = nconc(parse->rowMarks, subquery->rowMarks); + + /* + * Miscellaneous housekeeping. */ - check_subplans_for_ungrouped_vars((Node *) parse->targetList, - parse); - check_subplans_for_ungrouped_vars(parse->havingQual, parse); + 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; } } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + List *l; - /* Replace uplevel vars with Param nodes */ - if (PlannerQueryLevel > 1) + foreach(l, f->fromlist) + lfirst(l) = pull_up_subqueries(parse, lfirst(l), + below_outer_join); + } + else if (IsA(jtnode, JoinExpr)) { - parse->targetList = (List *) - SS_replace_correlation_vars((Node *) parse->targetList); - parse->qual = SS_replace_correlation_vars(parse->qual); - parse->havingQual = SS_replace_correlation_vars(parse->havingQual); + JoinExpr *j = (JoinExpr *) jtnode; + + /* Recurse, being careful to tell myself when inside outer join */ + switch (j->jointype) + { + case JOIN_INNER: + j->larg = pull_up_subqueries(parse, j->larg, + below_outer_join); + j->rarg = pull_up_subqueries(parse, j->rarg, + below_outer_join); + break; + case JOIN_LEFT: + j->larg = pull_up_subqueries(parse, j->larg, + below_outer_join); + j->rarg = pull_up_subqueries(parse, j->rarg, + true); + break; + case JOIN_FULL: + j->larg = pull_up_subqueries(parse, j->larg, + true); + j->rarg = pull_up_subqueries(parse, j->rarg, + true); + break; + case JOIN_RIGHT: + j->larg = pull_up_subqueries(parse, j->larg, + true); + j->rarg = pull_up_subqueries(parse, j->rarg, + below_outer_join); + break; + case JOIN_UNION: + + /* + * This is where we fail if upper levels of planner + * haven't rewritten UNION JOIN as an Append ... + */ + elog(ERROR, "UNION JOIN is not implemented yet"); + break; + default: + elog(ERROR, "pull_up_subqueries: unexpected join type %d", + j->jointype); + break; + } } + else + elog(ERROR, "pull_up_subqueries: unexpected node type %d", + nodeTag(jtnode)); + return jtnode; +} - /* Do all the above for each qual condition (ON clause) in the join tree */ - preprocess_join_conditions(parse, (Node *) parse->jointree); +/* + * is_simple_subquery + * Check a subquery in the range table to see if it's simple enough + * to pull up into the parent query. + */ +static bool +is_simple_subquery(Query *subquery) +{ + /* + * Let's just make sure it's a valid subselect ... + */ + if (!IsA(subquery, Query) || + subquery->commandType != CMD_SELECT || + subquery->resultRelation != 0 || + subquery->into != NULL || + subquery->isPortal) + elog(ERROR, "is_simple_subquery: subquery is bogus"); + + /* + * Can't currently pull up a query with setops. Maybe after querytree + * redesign... + */ + if (subquery->setOperations) + return false; - /* Do the main planning (potentially recursive) */ + /* + * Can't pull up a subquery involving grouping, aggregation, sorting, + * or limiting. + */ + if (subquery->hasAggs || + subquery->groupClause || + subquery->havingQual || + subquery->sortClause || + subquery->distinctClause || + subquery->limitOffset || + subquery->limitCount) + return false; - return union_planner(parse, tuple_fraction); + /* + * Don't pull up a subquery that has any set-returning functions in + * its targetlist. Otherwise we might well wind up inserting + * set-returning functions into places where they mustn't go, + * such as quals of higher queries. + */ + if (expression_returns_set((Node *) subquery->targetList)) + return false; /* - * XXX should any more of union_planner's activity be moved here? - * - * That would take careful study of the interactions with prepunion.c, - * but I suspect it would pay off in simplicity and avoidance of - * wasted cycles. + * Hack: don't try to pull up a subquery with an empty jointree. + * query_planner() will correctly generate a Result plan for a + * jointree that's totally empty, but I don't think the right things + * happen if an empty FromExpr appears lower down in a jointree. Not + * worth working hard on this, just to collapse SubqueryScan/Result + * into Result... */ + if (subquery->jointree->fromlist == NIL) + return false; + + return true; } /* - * preprocess_join_conditions - * Recursively scan the query's jointree and do subquery_planner's - * qual preprocessing work on each ON condition found therein. + * has_nullable_targetlist + * Check a subquery in the range table to see if all the non-junk + * targetlist items are simple variables (and, hence, will correctly + * go to NULL when examined above the point of an outer join). + * + * A possible future extension is to accept strict functions of simple + * variables, eg, "x + 1". + */ +static bool +has_nullable_targetlist(Query *subquery) +{ + List *l; + + foreach(l, subquery->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + /* ignore resjunk columns */ + if (tle->resdom->resjunk) + continue; + + /* Okay if tlist item is a simple Var */ + if (tle->expr && IsA(tle->expr, Var)) + continue; + + 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, + * but there's no other way... */ static void -preprocess_join_conditions(Query *parse, Node *jtnode) +resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist) { if (jtnode == NULL) return; - if (IsA(jtnode, List)) + if (IsA(jtnode, RangeTblRef)) + { + /* nothing to do here */ + } + else if (IsA(jtnode, FromExpr)) { + FromExpr *f = (FromExpr *) jtnode; List *l; - foreach(l, (List *) jtnode) - preprocess_join_conditions(parse, lfirst(l)); + foreach(l, f->fromlist) + resolvenew_in_jointree(lfirst(l), varno, subtlist); + f->quals = ResolveNew(f->quals, + varno, 0, subtlist, CMD_SELECT, 0); } - else if (IsA(jtnode, RangeTblRef)) + else if (IsA(jtnode, JoinExpr)) { - /* nothing to do here */ + JoinExpr *j = (JoinExpr *) jtnode; + + resolvenew_in_jointree(j->larg, varno, subtlist); + resolvenew_in_jointree(j->rarg, varno, subtlist); + j->quals = ResolveNew(j->quals, + varno, 0, subtlist, CMD_SELECT, 0); + + /* + * We don't bother to update the colvars list, since it won't be + * used again ... + */ + } + else + elog(ERROR, "resolvenew_in_jointree: unexpected node type %d", + nodeTag(jtnode)); +} + +/* + * preprocess_jointree + * Attempt to simplify a query's jointree. + * + * If we succeed in pulling up a subquery then we might form a jointree + * in which a FromExpr is a direct child of another FromExpr. In that + * case we can consider collapsing the two FromExprs into one. This is + * an optional conversion, since the planner will work correctly either + * way. But we may find a better plan (at the cost of more planning time) + * if we merge the two nodes. + * + * NOTE: don't try to do this in the same jointree scan that does subquery + * pullup! Since we're changing the jointree structure here, that wouldn't + * work reliably --- see comments for pull_up_subqueries(). + */ +static Node * +preprocess_jointree(Query *parse, Node *jtnode) +{ + if (jtnode == NULL) + return NULL; + if (IsA(jtnode, RangeTblRef)) + { + /* nothing to do here... */ + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + List *newlist = NIL; + List *l; + + foreach(l, f->fromlist) + { + Node *child = (Node *) lfirst(l); + + /* Recursively simplify the child... */ + child = preprocess_jointree(parse, child); + /* Now, is it a FromExpr? */ + if (child && IsA(child, FromExpr)) + { + /* + * Yes, so do we want to merge it into parent? Always do + * so if child has just one element (since that doesn't + * make the parent's list any longer). Otherwise we have + * to be careful about the increase in planning time + * caused by combining the two join search spaces into + * one. Our heuristic is to merge if the merge will + * produce a join list no longer than GEQO_RELS/2. + * (Perhaps need an additional user parameter?) + */ + FromExpr *subf = (FromExpr *) child; + int childlen = length(subf->fromlist); + int myothers = length(newlist) + length(lnext(l)); + + if (childlen <= 1 || (childlen + myothers) <= geqo_rels / 2) + { + newlist = nconc(newlist, subf->fromlist); + f->quals = make_and_qual(f->quals, subf->quals); + } + else + newlist = lappend(newlist, child); + } + else + newlist = lappend(newlist, child); + } + f->fromlist = newlist; } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - preprocess_join_conditions(parse, j->larg); - preprocess_join_conditions(parse, j->rarg); + /* Can't usefully change the JoinExpr, but recurse on children */ + j->larg = preprocess_jointree(parse, j->larg); + j->rarg = preprocess_jointree(parse, j->rarg); + } + else + elog(ERROR, "preprocess_jointree: unexpected node type %d", + nodeTag(jtnode)); + return jtnode; +} - /* Simplify constant expressions */ - j->quals = eval_const_expressions(j->quals); +/* + * preprocess_expression + * Do subquery_planner's preprocessing work for an expression, + * which can be a targetlist, a WHERE clause (including JOIN/ON + * conditions), or a HAVING clause. + */ +static Node * +preprocess_expression(Query *parse, Node *expr, int kind) +{ + bool has_join_rtes; + List *rt; - /* Canonicalize the qual, and convert it to implicit-AND format */ - j->quals = (Node *) canonicalize_qual((Expr *) j->quals, true); + /* + * Simplify constant expressions. + * + * Note that at this point quals have not yet been converted to + * implicit-AND form, so we can apply eval_const_expressions directly. + * Also note that we need to do this before SS_process_sublinks, + * because that routine inserts bogus "Const" nodes. + */ + expr = eval_const_expressions(expr); - /* Expand SubLinks to SubPlans */ - if (parse->hasSubLinks) + /* + * If it's a qual or havingQual, canonicalize it, and convert it to + * implicit-AND format. + * + * XXX Is there any value in re-applying eval_const_expressions after + * canonicalize_qual? + */ + if (kind != EXPRKIND_TARGET) + { + expr = (Node *) canonicalize_qual((Expr *) expr, true); + +#ifdef OPTIMIZER_DEBUG + printf("After canonicalize_qual()\n"); + pprint(expr); +#endif + } + + /* Expand SubLinks to SubPlans */ + if (parse->hasSubLinks) + expr = SS_process_sublinks(expr); + + /* Replace uplevel vars with Param nodes */ + if (PlannerQueryLevel > 1) + expr = SS_replace_correlation_vars(expr); + + /* + * If the query has any join RTEs, try to replace join alias variables + * with base-relation variables, to allow quals to be pushed down. + * We must do this after sublink processing, since it does not recurse + * into sublinks. + * + * The flattening pass is expensive enough that it seems worthwhile to + * scan the rangetable to see if we can avoid it. + */ + has_join_rtes = false; + foreach(rt, parse->rtable) + { + RangeTblEntry *rte = lfirst(rt); + + if (rte->rtekind == RTE_JOIN) { - j->quals = SS_process_sublinks(j->quals); - /* - * ON conditions, like WHERE clauses, are evaluated pre-GROUP; - * so we allow ungrouped vars in them. - */ + has_join_rtes = true; + break; } + } + if (has_join_rtes) + expr = flatten_join_alias_vars(expr, parse, false); + + return expr; +} + +/* + * preprocess_qual_conditions + * Recursively scan the query's jointree and do subquery_planner's + * preprocessing work on each qual condition found therein. + */ +static void +preprocess_qual_conditions(Query *parse, Node *jtnode) +{ + if (jtnode == NULL) + return; + if (IsA(jtnode, RangeTblRef)) + { + /* nothing to do here */ + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + List *l; + + foreach(l, f->fromlist) + preprocess_qual_conditions(parse, lfirst(l)); + + f->quals = preprocess_expression(parse, f->quals, EXPRKIND_WHERE); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + preprocess_qual_conditions(parse, j->larg); + preprocess_qual_conditions(parse, j->rarg); - /* Replace uplevel vars with Param nodes */ - if (PlannerQueryLevel > 1) - j->quals = SS_replace_correlation_vars(j->quals); + j->quals = preprocess_expression(parse, j->quals, EXPRKIND_WHERE); } else - elog(ERROR, "preprocess_join_conditions: unexpected node type %d", + elog(ERROR, "preprocess_qual_conditions: unexpected node type %d", nodeTag(jtnode)); } /*-------------------- - * union_planner - * Invokes the planner on union-type queries (both regular UNIONs and - * appends produced by inheritance), recursing if necessary to get them - * all, then processes normal plans. + * inheritance_planner + * Generate a plan in the case where the result relation is an + * inheritance set. + * + * We have to handle this case differently from cases where a source + * relation is an inheritance set. Source inheritance is expanded at + * the bottom of the plan tree (see allpaths.c), but target inheritance + * has to be expanded at the top. The reason is that for UPDATE, each + * target relation needs a different targetlist matching its own column + * set. (This is not so critical for DELETE, but for simplicity we treat + * inherited DELETE the same way.) Fortunately, the UPDATE/DELETE target + * can never be the nullable side of an outer join, so it's OK to generate + * the plan this way. + * + * parse is the querytree produced by the parser & rewriter. + * inheritlist is an integer list of RT indexes for the result relation set. + * + * Returns a query plan. + *-------------------- + */ +static Plan * +inheritance_planner(Query *parse, List *inheritlist) +{ + int parentRTindex = parse->resultRelation; + Oid parentOID = getrelid(parentRTindex, parse->rtable); + List *subplans = NIL; + List *tlist = NIL; + List *l; + + foreach(l, inheritlist) + { + int childRTindex = lfirsti(l); + Oid childOID = getrelid(childRTindex, parse->rtable); + Query *subquery; + Plan *subplan; + + /* Generate modified query with this rel as target */ + subquery = (Query *) adjust_inherited_attrs((Node *) parse, + parentRTindex, parentOID, + childRTindex, childOID); + /* Generate plan */ + subplan = grouping_planner(subquery, 0.0 /* retrieve all tuples */ ); + subplans = lappend(subplans, subplan); + /* Save preprocessed tlist from first rel for use in Append */ + if (tlist == NIL) + tlist = subplan->targetlist; + } + + /* Save the target-relations list for the executor, too */ + parse->resultRelations = inheritlist; + + return (Plan *) make_append(subplans, true, tlist); +} + +/*-------------------- + * grouping_planner + * Perform planning steps related to grouping, aggregation, etc. + * This primarily means adding top-level processing to the basic + * query plan produced by query_planner. * * parse is the querytree produced by the parser & rewriter. * tuple_fraction is the fraction of tuples we expect will be retrieved @@ -304,86 +857,54 @@ preprocess_join_conditions(Query *parse, Node *jtnode) * Returns a query plan. *-------------------- */ -Plan * -union_planner(Query *parse, - double tuple_fraction) +static Plan * +grouping_planner(Query *parse, double tuple_fraction) { List *tlist = parse->targetList; - List *rangetable = parse->rtable; - Plan *result_plan = (Plan *) NULL; - AttrNumber *groupColIdx = NULL; - List *current_pathkeys = NIL; + Plan *result_plan; + List *current_pathkeys; List *group_pathkeys; List *sort_pathkeys; - Index rt_index; - List *inheritors; + AttrNumber *groupColIdx = NULL; - if (parse->unionClause) + if (parse->setOperations) { - result_plan = plan_union_queries(parse); - /* XXX do we need to do this? bjm 12/19/97 */ - tlist = preprocess_targetlist(tlist, - parse->commandType, - parse->resultRelation, - parse->rtable); - /* - * We leave current_pathkeys NIL indicating we do not know sort - * order. This is correct for the appended-together subplan - * results, even if the subplans themselves produced sorted results. + * Construct the plan for set operations. The result will not + * need any work except perhaps a top-level sort and/or LIMIT. */ + result_plan = plan_set_operations(parse); /* - * Calculate pathkeys that represent grouping/ordering - * requirements + * We should not need to call preprocess_targetlist, since we must + * be in a SELECT query node. Instead, use the targetlist + * returned by plan_set_operations (since this tells whether it + * returned any resjunk columns!), and transfer any sort key + * information from the original tlist. */ - group_pathkeys = make_pathkeys_for_sortclauses(parse->groupClause, - tlist); - sort_pathkeys = make_pathkeys_for_sortclauses(parse->sortClause, - tlist); - } - else if (find_inheritable_rt_entry(rangetable, - &rt_index, &inheritors)) - { - List *sub_tlist; + Assert(parse->commandType == CMD_SELECT); - /* - * Generate appropriate target list for subplan; may be different - * from tlist if grouping or aggregation is needed. - */ - sub_tlist = make_subplanTargetList(parse, tlist, &groupColIdx); + tlist = postprocess_setop_tlist(result_plan->targetlist, tlist); /* - * Recursively plan the subqueries needed for inheritance + * Can't handle FOR UPDATE here (parser should have checked + * already, but let's make sure). */ - result_plan = plan_inherit_queries(parse, sub_tlist, - rt_index, inheritors); + if (parse->rowMarks) + elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT"); /* - * Fix up outer target list. NOTE: unlike the case for - * non-inherited query, we pass the unfixed tlist to subplans, - * which do their own fixing. But we still want to fix the outer - * target list afterwards. I *think* this is correct --- doing the - * fix before recursing is definitely wrong, because - * preprocess_targetlist() will do the wrong thing if invoked - * twice on the same list. Maybe that is a bug? tgl 6/6/99 - */ - tlist = preprocess_targetlist(tlist, - parse->commandType, - parse->resultRelation, - parse->rtable); - - if (parse->rowMark != NULL) - elog(ERROR, "SELECT FOR UPDATE is not supported for inherit queries"); - - /* - * We leave current_pathkeys NIL indicating we do not know sort - * order of the Append-ed results. + * We set current_pathkeys NIL indicating we do not know sort + * order. This is correct when the top set operation is UNION + * ALL, since the appended-together results are unsorted even if + * the subplans were sorted. For other set operations we could be + * smarter --- room for future improvement! */ + current_pathkeys = NIL; /* * Calculate pathkeys that represent grouping/ordering - * requirements + * requirements (grouping should always be null, but...) */ group_pathkeys = make_pathkeys_for_sortclauses(parse->groupClause, tlist); @@ -401,33 +922,51 @@ union_planner(Query *parse, parse->rtable); /* - * Add row-mark targets for UPDATE (should this be done in - * preprocess_targetlist?) + * Add TID targets for rels selected FOR UPDATE (should this be + * done in preprocess_targetlist?). The executor uses the TID to + * know which rows to lock, much as for UPDATE or DELETE. */ - if (parse->rowMark != NULL) + if (parse->rowMarks) { List *l; - foreach(l, parse->rowMark) + /* + * We've got trouble if the FOR UPDATE appears inside + * grouping, since grouping renders a reference to individual + * tuple CTIDs invalid. This is also checked at parse time, + * but that's insufficient because of rule substitution, query + * pullup, etc. + */ + CheckSelectForUpdate(parse); + + /* + * Currently the executor only supports FOR UPDATE at top + * level + */ + if (PlannerQueryLevel > 1) + elog(ERROR, "SELECT FOR UPDATE is not allowed in subselects"); + + foreach(l, parse->rowMarks) { - RowMark *rowmark = (RowMark *) lfirst(l); - TargetEntry *ctid; + Index rti = lfirsti(l); + char *resname; Resdom *resdom; Var *var; - char *resname; - - if (!(rowmark->info & ROW_MARK_FOR_UPDATE)) - continue; + TargetEntry *ctid; resname = (char *) palloc(32); - sprintf(resname, "ctid%u", rowmark->rti); + sprintf(resname, "ctid%u", rti); resdom = makeResdom(length(tlist) + 1, TIDOID, -1, resname, true); - var = makeVar(rowmark->rti, -1, TIDOID, -1, 0); + var = makeVar(rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + 0); ctid = makeTargetEntry(resdom, (Node *) var); tlist = lappend(tlist, ctid); @@ -469,9 +1008,9 @@ union_planner(Query *parse, /* * Figure out whether we expect to retrieve all the tuples that - * the plan can generate, or to stop early due to a LIMIT or other - * factors. If the caller passed a value >= 0, believe that - * value, else do our own examination of the query context. + * the plan can generate, or to stop early due to outside factors + * such as a cursor. If the caller passed a value >= 0, believe + * that value, else do our own examination of the query context. */ if (tuple_fraction < 0.0) { @@ -479,75 +1018,121 @@ union_planner(Query *parse, tuple_fraction = 0.0; /* - * Check for a LIMIT clause. + * Check for retrieve-into-portal, ie DECLARE CURSOR. + * + * We have no real idea how many tuples the user will ultimately + * FETCH from a cursor, but it seems a good bet that he + * doesn't want 'em all. Optimize for 10% retrieval (you + * gotta better number? Should this be a SETtable parameter?) */ - if (parse->limitCount != NULL) + if (parse->isPortal) + tuple_fraction = 0.10; + } + + /* + * Adjust tuple_fraction if we see that we are going to apply + * limiting/grouping/aggregation/etc. This is not overridable by + * the caller, since it reflects plan actions that this routine + * will certainly take, not assumptions about context. + */ + if (parse->limitCount != NULL) + { + /* + * A LIMIT clause limits the absolute number of tuples + * returned. However, if it's not a constant LIMIT then we + * have to punt; for lack of a better idea, assume 10% of the + * plan's result is wanted. + */ + double limit_fraction = 0.0; + + if (IsA(parse->limitCount, Const)) { - if (IsA(parse->limitCount, Const)) + Const *limitc = (Const *) parse->limitCount; + int32 count = DatumGetInt32(limitc->constvalue); + + /* + * A NULL-constant LIMIT represents "LIMIT ALL", which we + * treat the same as no limit (ie, expect to retrieve all + * the tuples). + */ + if (!limitc->constisnull && count > 0) { - Const *limitc = (Const *) parse->limitCount; - int count = (int) (limitc->constvalue); - - /* - * The constant can legally be either 0 ("ALL") or a - * positive integer. If it is not ALL, we also need - * to consider the OFFSET part of LIMIT. - */ - if (count > 0) + limit_fraction = (double) count; + /* We must also consider the OFFSET, if present */ + if (parse->limitOffset != NULL) { - tuple_fraction = (double) count; - if (parse->limitOffset != NULL) + if (IsA(parse->limitOffset, Const)) + { + int32 offset; + + limitc = (Const *) parse->limitOffset; + offset = DatumGetInt32(limitc->constvalue); + if (!limitc->constisnull && offset > 0) + limit_fraction += (double) offset; + } + else { - if (IsA(parse->limitOffset, Const)) - { - int offset; - - limitc = (Const *) parse->limitOffset; - offset = (int) (limitc->constvalue); - if (offset > 0) - tuple_fraction += (double) offset; - } - else - { - /* It's a PARAM ... punt ... */ - tuple_fraction = 0.10; - } + /* OFFSET is an expression ... punt ... */ + limit_fraction = 0.10; } } } + } + else + { + /* LIMIT is an expression ... punt ... */ + limit_fraction = 0.10; + } + + if (limit_fraction > 0.0) + { + /* + * If we have absolute limits from both caller and LIMIT, + * use the smaller value; if one is fractional and the + * other absolute, treat the fraction as a fraction of the + * absolute value; else we can multiply the two fractions + * together. + */ + if (tuple_fraction >= 1.0) + { + if (limit_fraction >= 1.0) + { + /* both absolute */ + tuple_fraction = Min(tuple_fraction, limit_fraction); + } + else + { + /* caller absolute, limit fractional */ + tuple_fraction *= limit_fraction; + if (tuple_fraction < 1.0) + tuple_fraction = 1.0; + } + } + else if (tuple_fraction > 0.0) + { + if (limit_fraction >= 1.0) + { + /* caller fractional, limit absolute */ + tuple_fraction *= limit_fraction; + if (tuple_fraction < 1.0) + tuple_fraction = 1.0; + } + else + { + /* both fractional */ + tuple_fraction *= limit_fraction; + } + } else { - - /* - * COUNT is a PARAM ... don't know exactly what the - * limit will be, but for lack of a better idea assume - * 10% of the plan's result is wanted. - */ - tuple_fraction = 0.10; + /* no info from caller, just use limit */ + tuple_fraction = limit_fraction; } } - - /* - * Check for a retrieve-into-portal, ie DECLARE CURSOR. - * - * We have no real idea how many tuples the user will ultimately - * FETCH from a cursor, but it seems a good bet that he - * doesn't want 'em all. Optimize for 10% retrieval (you - * gotta better number?) - */ - if (parse->isPortal) - tuple_fraction = 0.10; } - /* - * Adjust tuple_fraction if we see that we are going to apply - * grouping/aggregation/etc. This is not overridable by the - * caller, since it reflects plan actions that this routine will - * certainly take, not assumptions about context. - */ if (parse->groupClause) { - /* * In GROUP BY mode, we have the little problem that we don't * really know how many input tuples will be needed to make a @@ -566,18 +1151,16 @@ union_planner(Query *parse, * If both GROUP BY and ORDER BY are specified, we will need * two levels of sort --- and, therefore, certainly need to * read all the input tuples --- unless ORDER BY is a subset - * of GROUP BY. (Although we are comparing non-canonicalized - * pathkeys here, it should be OK since they will both contain - * only single-element sublists at this point. See - * pathkeys.c.) + * of GROUP BY. (We have not yet canonicalized the pathkeys, + * so must use the slower noncanonical comparison method.) */ if (parse->groupClause && parse->sortClause && - !pathkeys_contained_in(sort_pathkeys, group_pathkeys)) + !noncanonical_pathkeys_contained_in(sort_pathkeys, + group_pathkeys)) tuple_fraction = 0.0; } else if (parse->hasAggs) { - /* * Ungrouped aggregate will certainly want all the input * tuples. @@ -586,7 +1169,6 @@ union_planner(Query *parse, } else if (parse->distinctClause) { - /* * SELECT DISTINCT, like GROUP, will absorb an unpredictable * number of input tuples per output tuple. Handle the same @@ -596,7 +1178,7 @@ union_planner(Query *parse, tuple_fraction = 0.25; } - /* Generate the (sub) plan */ + /* Generate the basic plan for this Query */ result_plan = query_planner(parse, sub_tlist, tuple_fraction); @@ -608,10 +1190,6 @@ union_planner(Query *parse, current_pathkeys = parse->query_pathkeys; } - /* query_planner returns NULL if it thinks plan is bogus */ - if (!result_plan) - elog(ERROR, "union_planner: failed to create plan"); - /* * We couldn't canonicalize group_pathkeys and sort_pathkeys before * running query_planner(), so do it now. @@ -639,12 +1217,11 @@ union_planner(Query *parse, /* * If there are aggregates then the Group node should just return - * the same set of vars as the subplan did (but we can exclude any - * GROUP BY expressions). If there are no aggregates then the - * Group node had better compute the final tlist. + * the same set of vars as the subplan did. If there are no aggs + * then the Group node had better compute the final tlist. */ if (parse->hasAggs) - group_tlist = flatten_tlist(result_plan->targetlist); + group_tlist = new_unsorted_tlist(result_plan->targetlist); else group_tlist = tlist; @@ -659,7 +1236,6 @@ union_planner(Query *parse, } else { - /* * We will need to do an explicit sort by the GROUP BY clause. * make_groupplan will do the work, but set current_pathkeys @@ -669,7 +1245,8 @@ union_planner(Query *parse, current_pathkeys = group_pathkeys; } - result_plan = make_groupplan(group_tlist, + result_plan = make_groupplan(parse, + group_tlist, tuplePerGroup, parse->groupClause, groupColIdx, @@ -689,6 +1266,11 @@ union_planner(Query *parse, result_plan); /* Note: Agg does not affect any existing sort order of the tuples */ } + else + { + /* If there are no Aggs, we shouldn't have any HAVING qual anymore */ + Assert(parse->havingQual == NULL); + } /* * If we were not able to make the plan come out in the right order, @@ -697,12 +1279,12 @@ union_planner(Query *parse, if (parse->sortClause) { if (!pathkeys_contained_in(sort_pathkeys, current_pathkeys)) - result_plan = make_sortplan(tlist, result_plan, + result_plan = make_sortplan(parse, tlist, result_plan, parse->sortClause); } /* - * Finally, if there is a DISTINCT clause, add the UNIQUE node. + * If there is a DISTINCT clause, add the UNIQUE node. */ if (parse->distinctClause) { @@ -710,6 +1292,16 @@ union_planner(Query *parse, parse->distinctClause); } + /* + * Finally, if there is a LIMIT/OFFSET clause, add the LIMIT node. + */ + if (parse->limitOffset || parse->limitCount) + { + result_plan = (Plan *) make_limit(tlist, result_plan, + parse->limitOffset, + parse->limitCount); + } + return result_plan; } @@ -717,7 +1309,7 @@ union_planner(Query *parse, * make_subplanTargetList * Generate appropriate target list when grouping is required. * - * When union_planner inserts Aggregate and/or Group plan nodes above + * When grouping_planner inserts Aggregate and/or Group plan nodes above * the result of query_planner, we typically want to pass a different * target list to query_planner than the outer plan nodes should have. * This routine generates the correct target list for the subplan. @@ -736,7 +1328,10 @@ union_planner(Query *parse, * where the a+b target will be used by the Sort/Group steps, and the * other targets will be used for computing the final results. (In the * above example we could theoretically suppress the a and b targets and - * use only a+b, but it's not really worth the trouble.) + * pass down only c,d,a+b, but it's not really worth the trouble to + * eliminate simple var references from the subplan. We will avoid doing + * the extra computation to recompute a+b at the outer level; see + * replace_vars_with_subplan_refs() in setrefs.c.) * * 'parse' is the query being processed. * 'tlist' is the query's target list. @@ -830,7 +1425,8 @@ make_subplanTargetList(Query *parse, * first add an explicit Sort node. */ static Plan * -make_groupplan(List *group_tlist, +make_groupplan(Query *parse, + List *group_tlist, bool tuplePerGroup, List *groupClause, AttrNumber *grpColIdx, @@ -841,7 +1437,6 @@ make_groupplan(List *group_tlist, if (!is_presorted) { - /* * The Sort node always just takes a copy of the subplan's tlist * plus ordering information. (This might seem inefficient if the @@ -869,13 +1464,13 @@ make_groupplan(List *group_tlist, { /* OK, insert the ordering info needed by the executor. */ resdom->reskey = ++keyno; - resdom->reskeyop = get_opcode(grpcl->sortop); + resdom->reskeyop = grpcl->sortop; } } Assert(keyno > 0); - subplan = (Plan *) make_sort(sort_tlist, subplan, keyno); + subplan = (Plan *) make_sort(parse, sort_tlist, subplan, keyno); } return (Plan *) make_group(group_tlist, tuplePerGroup, numCols, @@ -886,8 +1481,8 @@ make_groupplan(List *group_tlist, * make_sortplan * Add a Sort node to implement an explicit ORDER BY clause. */ -static Plan * -make_sortplan(List *tlist, Plan *plannode, List *sortcls) +Plan * +make_sortplan(Query *parse, List *tlist, Plan *plannode, List *sortcls) { List *sort_tlist; List *i; @@ -914,11 +1509,49 @@ make_sortplan(List *tlist, Plan *plannode, List *sortcls) { /* OK, insert the ordering info needed by the executor. */ resdom->reskey = ++keyno; - resdom->reskeyop = get_opcode(sortcl->sortop); + resdom->reskeyop = sortcl->sortop; } } Assert(keyno > 0); - return (Plan *) make_sort(sort_tlist, plannode, keyno); + return (Plan *) make_sort(parse, sort_tlist, plannode, keyno); +} + +/* + * postprocess_setop_tlist + * Fix up targetlist returned by plan_set_operations(). + * + * We need to transpose sort key info from the orig_tlist into new_tlist. + * NOTE: this would not be good enough if we supported resjunk sort keys + * for results of set operations --- then, we'd need to project a whole + * new tlist to evaluate the resjunk columns. For now, just elog if we + * find any resjunk columns in orig_tlist. + */ +static List * +postprocess_setop_tlist(List *new_tlist, List *orig_tlist) +{ + List *l; + + foreach(l, new_tlist) + { + TargetEntry *new_tle = (TargetEntry *) lfirst(l); + TargetEntry *orig_tle; + + /* ignore resjunk columns in setop result */ + if (new_tle->resdom->resjunk) + continue; + + Assert(orig_tlist != NIL); + orig_tle = (TargetEntry *) lfirst(orig_tlist); + orig_tlist = lnext(orig_tlist); + if (orig_tle->resdom->resjunk) + elog(ERROR, "postprocess_setop_tlist: resjunk output columns not implemented"); + Assert(new_tle->resdom->resno == orig_tle->resdom->resno); + Assert(new_tle->resdom->restype == orig_tle->resdom->restype); + new_tle->resdom->ressortgroupref = orig_tle->resdom->ressortgroupref; + } + if (orig_tlist != NIL) + elog(ERROR, "postprocess_setop_tlist: resjunk output columns not implemented"); + return new_tlist; }