diff options
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r-- | src/backend/optimizer/path/allpaths.c | 5 | ||||
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 70 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planmain.c | 42 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 142 | ||||
-rw-r--r-- | src/backend/optimizer/plan/setrefs.c | 12 | ||||
-rw-r--r-- | src/backend/optimizer/plan/subselect.c | 63 | ||||
-rw-r--r-- | src/backend/optimizer/prep/prepkeyset.c | 4 | ||||
-rw-r--r-- | src/backend/optimizer/prep/preptlist.c | 13 | ||||
-rw-r--r-- | src/backend/optimizer/prep/prepunion.c | 543 | ||||
-rw-r--r-- | src/backend/optimizer/util/clauses.c | 111 | ||||
-rw-r--r-- | src/backend/optimizer/util/var.c | 8 |
11 files changed, 675 insertions, 338 deletions
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 8ab2aeec918..7e017a746f1 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.65 2000/09/29 18:21:31 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.66 2000/10/05 19:11:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -98,7 +98,8 @@ set_base_rel_pathlist(Query *root) */ /* Generate the plan for the subquery */ - rel->subplan = planner(rte->subquery); + rel->subplan = subquery_planner(rte->subquery, + -1.0 /* default case */ ); /* Copy number of output rows from subplan */ rel->tuples = rel->subplan->plan_rows; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index cc308b4fc96..eb005121cd5 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.97 2000/09/29 18:21:33 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.98 2000/10/05 19:11:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -68,8 +68,6 @@ static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid, ScanDirection indexscandir); static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tideval); -static SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual, - Index scanrelid, Plan *subplan); static NestLoop *make_nestloop(List *tlist, List *joinclauses, List *otherclauses, Plan *lefttree, Plan *righttree, @@ -86,7 +84,6 @@ static MergeJoin *make_mergejoin(List *tlist, Plan *lefttree, Plan *righttree, JoinType jointype); static void copy_path_costsize(Plan *dest, Path *src); -static void copy_plan_costsize(Plan *dest, Plan *src); /* * create_plan @@ -1109,7 +1106,7 @@ copy_path_costsize(Plan *dest, Path *src) * but it helps produce more reasonable-looking EXPLAIN output. * (Some callers alter the info after copying it.) */ -static void +void copy_plan_costsize(Plan *dest, Plan *src) { if (src) @@ -1206,7 +1203,7 @@ make_tidscan(List *qptlist, return node; } -static SubqueryScan * +SubqueryScan * make_subqueryscan(List *qptlist, List *qpqual, Index scanrelid, @@ -1593,6 +1590,67 @@ make_unique(List *tlist, Plan *lefttree, List *distinctList) return node; } +/* + * distinctList is a list of SortClauses, identifying the targetlist items + * that should be considered by the SetOp filter. + */ + +SetOp * +make_setop(SetOpCmd cmd, List *tlist, Plan *lefttree, + List *distinctList, AttrNumber flagColIdx) +{ + SetOp *node = makeNode(SetOp); + Plan *plan = &node->plan; + int numCols = length(distinctList); + int keyno = 0; + AttrNumber *dupColIdx; + List *slitem; + + copy_plan_costsize(plan, lefttree); + + /* + * Charge one cpu_operator_cost per comparison per input tuple. We + * assume all columns get compared at most of the tuples. + */ + plan->total_cost += cpu_operator_cost * plan->plan_rows * numCols; + + /* + * As for Group, we make the unsupported assumption that there will be + * 10% as many tuples out as in. + */ + plan->plan_rows *= 0.1; + if (plan->plan_rows < 1) + plan->plan_rows = 1; + + plan->state = (EState *) NULL; + plan->targetlist = tlist; + plan->qual = NIL; + plan->lefttree = lefttree; + plan->righttree = NULL; + + /* + * convert SortClause list into array of attr indexes, as wanted by + * exec + */ + Assert(numCols > 0); + dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols); + + foreach(slitem, distinctList) + { + SortClause *sortcl = (SortClause *) lfirst(slitem); + TargetEntry *tle = get_sortgroupclause_tle(sortcl, tlist); + + dupColIdx[keyno++] = tle->resdom->resno; + } + + node->cmd = cmd; + node->numCols = numCols; + node->dupColIdx = dupColIdx; + node->flagColIdx = flagColIdx; + + return node; +} + Result * make_result(List *tlist, Node *resconstantqual, diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 29cfccfef7b..a9747b32799 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.60 2000/09/29 18:21:33 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.61 2000/10/05 19:11:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -174,8 +174,6 @@ subplanner(Query *root, List *brel; RelOptInfo *final_rel; Plan *resultplan; - MemoryContext mycontext; - MemoryContext oldcxt; Path *cheapestpath; Path *presortedpath; @@ -228,24 +226,6 @@ subplanner(Query *root, root->query_pathkeys = canonicalize_pathkeys(root, root->query_pathkeys); /* - * We might allocate quite a lot of storage during planning (due to - * constructing lots of Paths), but all of it can be reclaimed after - * we generate the finished Plan tree. Work in a temporary context - * to let that happen. We make the context a child of - * TransactionCommandContext so it will be freed if error abort. - * - * Note: beware of trying to move this up to the start of this routine. - * Some of the data structures built above --- notably the pathkey - * equivalence sets --- will still be needed after this routine exits. - */ - mycontext = AllocSetContextCreate(TransactionCommandContext, - "Planner", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); - oldcxt = MemoryContextSwitchTo(mycontext); - - /* * Ready to do the primary planning. */ final_rel = make_one_rel(root); @@ -355,25 +335,5 @@ subplanner(Query *root, plan_built: - /* - * Must copy the completed plan tree and its pathkeys out of temporary - * context. We also have to copy the rtable in case it contains any - * subqueries. (If it does, they'll have been modified during the - * recursive invocation of planner.c, and hence will contain substructure - * allocated in my temporary context...) - */ - MemoryContextSwitchTo(oldcxt); - - resultplan = copyObject(resultplan); - - root->query_pathkeys = copyObject(root->query_pathkeys); - - root->rtable = copyObject(root->rtable); - - /* - * Now we can release the Path storage. - */ - MemoryContextDelete(mycontext); - return resultplan; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 937628121b3..d73ca9a34ac 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.91 2000/09/29 18:21:33 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.92 2000/10/05 19:11:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -48,7 +48,6 @@ static List *make_subplanTargetList(Query *parse, List *tlist, static Plan *make_groupplan(List *group_tlist, bool tuplePerGroup, List *groupClause, AttrNumber *grpColIdx, bool is_presorted, Plan *subplan); -static Plan *make_sortplan(List *tlist, Plan *plannode, List *sortcls); /***************************************************************************** * @@ -60,43 +59,32 @@ planner(Query *parse) { Plan *result_plan; Index save_PlannerQueryLevel; - List *save_PlannerInitPlan; List *save_PlannerParamVar; - int save_PlannerPlanId; /* - * The outer planner can be called recursively, for example to process - * a subquery in the rangetable. (A less obvious example occurs when - * eval_const_expressions tries to simplify an SQL function.) - * So, global state variables must be saved and restored. + * The planner can be called recursively (an example is when + * eval_const_expressions tries to simplify an SQL function). + * So, these global state variables must be saved and restored. * - * (Perhaps these should be moved into the Query structure instead?) + * These vars cannot be moved into the Query structure since their + * whole purpose is communication across multiple sub-Queries. + * + * 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 sublinks) */ + /* 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); @@ -106,9 +94,7 @@ planner(Query *parse) /* restore state for outer planner, if any */ PlannerQueryLevel = save_PlannerQueryLevel; - PlannerInitPlan = save_PlannerInitPlan; PlannerParamVar = save_PlannerParamVar; - PlannerPlanId = save_PlannerPlanId; return result_plan; } @@ -125,14 +111,9 @@ planner(Query *parse) * * 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. - * - * 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. + * recursively on the same Query node in order to handle inheritance. + * subquery_planner will be called recursively to handle sub-Query nodes + * found within the query's expressions and rangetable. * * Returns a query plan. *-------------------- @@ -140,6 +121,20 @@ planner(Query *parse) Plan * subquery_planner(Query *parse, double tuple_fraction) { + List *saved_initplan = PlannerInitPlan; + int saved_planid = PlannerPlanId; + Plan *plan; + 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 + /* * Check to see if any subqueries in the rangetable can be merged into * this query. @@ -179,9 +174,9 @@ subquery_planner(Query *parse, double tuple_fraction) EXPRKIND_HAVING); /* - * Do the main planning (potentially recursive) + * Do the main planning (potentially recursive for inheritance) */ - return union_planner(parse, tuple_fraction); + plan = union_planner(parse, tuple_fraction); /* * XXX should any more of union_planner's activity be moved here? @@ -190,6 +185,35 @@ subquery_planner(Query *parse, double tuple_fraction) * but I suspect it would pay off in simplicity and avoidance of * wasted cycles. */ + + /* + * 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) + { + (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! */ + + return plan; } /* @@ -320,9 +344,10 @@ is_simple_subquery(Query *subquery) if (subquery->limitOffset || subquery->limitCount) elog(ERROR, "LIMIT is not supported in subselects"); /* - * Can't currently pull up a union query. Maybe after querytree redesign. + * Can't currently pull up a query with setops. + * Maybe after querytree redesign... */ - if (subquery->unionClause) + if (subquery->setOperations) return false; /* * Can't pull up a subquery involving grouping, aggregation, or sorting. @@ -573,7 +598,7 @@ preprocess_qual_conditions(Query *parse, Node *jtnode) /*-------------------- * union_planner - * Invokes the planner on union-type queries (both regular UNIONs and + * Invokes the planner on union-type queries (both set operations and * appends produced by inheritance), recursing if necessary to get them * all, then processes normal plans. * @@ -606,24 +631,31 @@ union_planner(Query *parse, Index rt_index; List *inheritors; - 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); + /* + * Construct the plan for set operations. The result will + * not need any work except perhaps a top-level sort. + */ + result_plan = plan_set_operations(parse); + + /* + * We should not need to call preprocess_targetlist, since we must + * be in a SELECT query node. + */ + Assert(parse->commandType == CMD_SELECT); /* * 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. + * 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 --- future improvement! */ /* * Calculate pathkeys that represent grouping/ordering - * requirements + * requirements (grouping should always be null, but...) */ group_pathkeys = make_pathkeys_for_sortclauses(parse->groupClause, tlist); @@ -886,7 +918,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); @@ -1176,7 +1208,7 @@ make_groupplan(List *group_tlist, * make_sortplan * Add a Sort node to implement an explicit ORDER BY clause. */ -static Plan * +Plan * make_sortplan(List *tlist, Plan *plannode, List *sortcls) { List *sort_tlist; diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index ad1b47aaeb6..14c9dad3ef3 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.66 2000/09/29 18:21:33 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.67 2000/10/05 19:11:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -103,9 +103,15 @@ set_plan_references(Plan *plan) fix_expr_references(plan, (Node *) plan->qual); break; case T_SubqueryScan: + /* + * We do not do set_uppernode_references() here, because + * a SubqueryScan will always have been created with correct + * references to its subplan's outputs to begin with. + */ fix_expr_references(plan, (Node *) plan->targetlist); fix_expr_references(plan, (Node *) plan->qual); - /* No need to recurse into the subplan, it's fixed already */ + /* Recurse into subplan too */ + set_plan_references(((SubqueryScan *) plan)->subplan); break; case T_NestLoop: set_join_references((Join *) plan); @@ -132,6 +138,7 @@ set_plan_references(Plan *plan) case T_Material: case T_Sort: case T_Unique: + case T_SetOp: case T_Hash: /* @@ -170,6 +177,7 @@ set_plan_references(Plan *plan) * Append, like Sort et al, doesn't actually evaluate its * targetlist or quals, and we haven't bothered to give it * its own tlist copy. So, don't fix targetlist/qual. + * But do recurse into subplans. */ foreach(pl, ((Append *) plan)->appendplans) set_plan_references((Plan *) lfirst(pl)); diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 182d1384aa1..03e38371df5 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.42 2000/09/29 18:21:33 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.43 2000/10/05 19:11:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -29,7 +29,8 @@ Index PlannerQueryLevel; /* level of current query */ List *PlannerInitPlan; /* init subplans for current query */ List *PlannerParamVar; /* to get Var from Param->paramid */ -int PlannerPlanId; /* to assign unique ID to subquery plans */ + +int PlannerPlanId = 0; /* to assign unique ID to subquery plans */ /*-------------------- * PlannerParamVar is a list of Var nodes, wherein the n'th entry @@ -81,7 +82,7 @@ replace_var(Var *var) /* * If there's already a PlannerParamVar entry for this same Var, just - * use it. NOTE: in situations involving UNION or inheritance, it is + * use it. NOTE: in sufficiently complex querytrees, it is * possible for the same varno/varlevel to refer to different RTEs in * different parts of the parsetree, so that different fields might * end up sharing the same Param number. As long as we check the @@ -128,11 +129,6 @@ make_subplan(SubLink *slink) Plan *plan; List *lst; Node *result; - List *saved_ip = PlannerInitPlan; - - PlannerInitPlan = NULL; - - PlannerQueryLevel++; /* we become child */ /* * Check to see if this node was already processed; if so we have @@ -181,45 +177,30 @@ make_subplan(SubLink *slink) else tuple_fraction = -1.0; /* default behavior */ - node->plan = plan = subquery_planner(subquery, tuple_fraction); - /* - * Assign subPlan, extParam and locParam to plan nodes. At the moment, - * SS_finalize_plan doesn't handle initPlan-s and so we assign them to - * the topmost plan node and take care about its extParam too. + * Generate the plan for the subquery. */ - (void) SS_finalize_plan(plan); - plan->initPlan = PlannerInitPlan; - - /* Create extParam list as union of InitPlan-s' lists */ - foreach(lst, PlannerInitPlan) - { - List *lp; - - foreach(lp, ((SubPlan *) lfirst(lst))->plan->extParam) - { - if (!intMember(lfirsti(lp), plan->extParam)) - plan->extParam = lappendi(plan->extParam, lfirsti(lp)); - } - } + node->plan = plan = subquery_planner(subquery, tuple_fraction); - /* and now we are parent again */ - PlannerInitPlan = saved_ip; - PlannerQueryLevel--; + node->plan_id = PlannerPlanId++; /* Assign unique ID to this SubPlan */ - node->plan_id = PlannerPlanId++; node->rtable = subquery->rtable; node->sublink = slink; + slink->subselect = NULL; /* cool ?! see error check above! */ - /* make parParam list of params coming from current query level */ + /* + * Make parParam list of params that current query level will pass + * to this child plan. + */ foreach(lst, plan->extParam) { - Var *var = nth(lfirsti(lst), PlannerParamVar); + int paramid = lfirsti(lst); + Var *var = nth(paramid, PlannerParamVar); /* note varlevelsup is absolute level number */ if (var->varlevelsup == PlannerQueryLevel) - node->parParam = lappendi(node->parParam, lfirsti(lst)); + node->parParam = lappendi(node->parParam, paramid); } /* @@ -625,6 +606,11 @@ SS_finalize_plan(Plan *plan) SS_finalize_plan((Plan *) lfirst(lst))); break; + case T_SubqueryScan: + results.paramids = set_unioni(results.paramids, + SS_finalize_plan(((SubqueryScan *) plan)->subplan)); + break; + case T_IndexScan: finalize_primnode((Node *) ((IndexScan *) plan)->indxqual, &results); @@ -667,10 +653,10 @@ SS_finalize_plan(Plan *plan) case T_Agg: case T_SeqScan: - case T_SubqueryScan: case T_Material: case T_Sort: case T_Unique: + case T_SetOp: case T_Group: break; @@ -689,17 +675,18 @@ SS_finalize_plan(Plan *plan) foreach(lst, results.paramids) { - Var *var = nth(lfirsti(lst), PlannerParamVar); + int paramid = lfirsti(lst); + Var *var = nth(paramid, PlannerParamVar); /* note varlevelsup is absolute level number */ if (var->varlevelsup < PlannerQueryLevel) - extParam = lappendi(extParam, lfirsti(lst)); + extParam = lappendi(extParam, paramid); else if (var->varlevelsup > PlannerQueryLevel) elog(ERROR, "SS_finalize_plan: plan shouldn't reference subplan's variable"); else { Assert(var->varno == 0 && var->varattno == 0); - locParam = lappendi(locParam, lfirsti(lst)); + locParam = lappendi(locParam, paramid); } } diff --git a/src/backend/optimizer/prep/prepkeyset.c b/src/backend/optimizer/prep/prepkeyset.c index 60166289a59..764697636c7 100644 --- a/src/backend/optimizer/prep/prepkeyset.c +++ b/src/backend/optimizer/prep/prepkeyset.c @@ -20,6 +20,8 @@ bool _use_keyset_query_optimizer = FALSE; +#ifdef ENABLE_KEY_SET_QUERY + static int inspectOpNode(Expr *expr); static int inspectAndNode(Expr *expr); static int inspectOrNode(Expr *expr); @@ -213,3 +215,5 @@ inspectOpNode(Expr *expr) secondExpr = lsecond(expr->args); return (firstExpr && secondExpr && nodeTag(firstExpr) == T_Var && nodeTag(secondExpr) == T_Const); } + +#endif /* ENABLE_KEY_SET_QUERY */ diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index e3b9803d0ab..822c0c79f05 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.38 2000/08/08 15:41:48 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.39 2000/10/05 19:11:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -49,6 +49,17 @@ preprocess_targetlist(List *tlist, Index result_relation, List *range_table) { + /* + * Sanity check: if there is a result relation, it'd better be a + * real relation not a subquery. Else parser or rewriter messed up. + */ + if (result_relation) + { + RangeTblEntry *rte = rt_fetch(result_relation, range_table); + + if (rte->subquery != NULL || rte->relid == InvalidOid) + elog(ERROR, "preprocess_targetlist: subquery cannot be result relation"); + } /* * for heap_formtuple to work, the targetlist must match the exact diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index d3df8863270..0c91631563d 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1,27 +1,32 @@ /*------------------------------------------------------------------------- * * prepunion.c - * Routines to plan inheritance, union, and version queries + * Routines to plan set-operation and inheritance queries. The filename + * is a leftover from a time when only UNIONs were handled. * * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.53 2000/09/29 18:21:34 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.54 2000/10/05 19:11:30 tgl Exp $ * *------------------------------------------------------------------------- */ -#include <sys/types.h> - #include "postgres.h" +#include <sys/types.h> + +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/tlist.h" #include "parser/parse_clause.h" +#include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" @@ -33,221 +38,398 @@ typedef struct Oid new_relid; } fix_parsetree_attnums_context; +static Plan *recurse_set_operations(Node *setOp, Query *parse, + List *colTypes, int flag, + List *refnames_tlist); +static Plan *generate_union_plan(SetOperationStmt *op, Query *parse, + List *refnames_tlist); +static Plan *generate_nonunion_plan(SetOperationStmt *op, Query *parse, + List *refnames_tlist); +static List *recurse_union_children(Node *setOp, Query *parse, + SetOperationStmt *top_union, + List *refnames_tlist); +static List *generate_setop_tlist(List *colTypes, int flag, + List *input_tlist, + List *refnames_tlist); +static bool tlist_same_datatypes(List *tlist, List *colTypes); static void fix_parsetree_attnums(Index rt_index, Oid old_relid, Oid new_relid, Query *parsetree); static bool fix_parsetree_attnums_walker(Node *node, fix_parsetree_attnums_context *context); static RangeTblEntry *new_rangetable_entry(Oid new_relid, RangeTblEntry *old_entry); -static Append *make_append(List *appendplans, List *unionrtables, - Index rt_index, - List *inheritrtable, List *tlist); +static Append *make_append(List *appendplans, Index rt_index, + List *inheritrtable, List *tlist); /* - * plan_union_queries + * plan_set_operations * - * Plans the queries for a given UNION. + * Plans the queries for a tree of set operations (UNION/INTERSECT/EXCEPT) * - * Returns an Append plan that combines the results of the unioned queries. - * Note that Append output is correct for UNION ALL, but caller still needs - * to take care of sort/unique processing if it's a plain UNION. We set or - * clear the Query's fields so that the right things will happen back in - * union_planner. (This control structure is an unholy mess...) + * This routine only deals with the setOperations tree of the given query. + * Any top-level ORDER BY requested in parse->sortClause will be added on + * back in union_planner. */ Plan * -plan_union_queries(Query *parse) +plan_set_operations(Query *parse) { - List *union_plans = NIL, - *ulist, - *union_all_queries, - *union_rts, - *last_union = NIL, - *hold_sortClause = parse->sortClause; - bool union_all_found = false, - union_found = false, - last_union_all_flag = false; - - /*------------------------------------------------------------------ - * - * Do we need to split up our unions because we have UNION and UNION - * ALL? - * - * We are checking for the case of: SELECT 1 UNION SELECT 2 UNION SELECT - * 3 UNION ALL SELECT 4 UNION ALL SELECT 5 - * - * where we have to do a DISTINCT on the output of the first three - * queries, then add the rest. If they have used UNION and UNION ALL, - * we grab all queries up to the last UNION query, make them their own - * UNION with the owner as the first query in the list. Then, we take - * the remaining queries, which is UNION ALL, and add them to the list - * of union queries. - * - * So the above query becomes: - * - * Append Node - * { - * Sort and Unique - * { - * Append Node - * { - * SELECT 1 This is really a sub-UNION. - * unionClause We run a DISTINCT on these. - * { - * SELECT 2 - * SELECT 3 - * } - * } - * } - * SELECT 4 - * SELECT 5 - * } - * - *--------------------------------------------------------------------- + SetOperationStmt *topop = (SetOperationStmt *) parse->setOperations; + Node *node; + Query *leftmostQuery; + + Assert(topop && IsA(topop, SetOperationStmt)); + + /* + * Find the leftmost component Query. We need to use its column names + * for all generated tlists (else SELECT INTO won't work right). */ + node = topop->larg; + while (node && IsA(node, SetOperationStmt)) + node = ((SetOperationStmt *) node)->larg; + Assert(node && IsA(node, RangeTblRef)); + leftmostQuery = rt_fetch(((RangeTblRef *) node)->rtindex, + parse->rtable)->subquery; + Assert(leftmostQuery != NULL); - foreach(ulist, parse->unionClause) + /* + * Recurse on setOperations tree to generate plans for set ops. + * The final output plan should have just the column types shown + * as the output from the top-level node. + */ + return recurse_set_operations((Node *) topop, parse, + topop->colTypes, -1, + leftmostQuery->targetList); +} + +/* + * recurse_set_operations + * Recursively handle one step in a tree of set operations + * + * colTypes: integer list of type OIDs of expected output columns + * flag: if >= 0, add a resjunk output column indicating value of flag + * refnames_tlist: targetlist to take column names from + */ +static Plan * +recurse_set_operations(Node *setOp, Query *parse, + List *colTypes, int flag, + List *refnames_tlist) +{ + if (IsA(setOp, RangeTblRef)) { - Query *union_query = lfirst(ulist); + RangeTblRef *rtr = (RangeTblRef *) setOp; + RangeTblEntry *rte = rt_fetch(rtr->rtindex, parse->rtable); + Query *subquery = rte->subquery; + Plan *subplan, + *plan; - if (union_query->unionall) - union_all_found = true; - else - { - union_found = true; - last_union = ulist; - } - last_union_all_flag = union_query->unionall; + Assert(subquery != NULL); + /* + * Generate plan for primitive subquery + */ + subplan = subquery_planner(subquery, + -1.0 /* default case */ ); + /* + * Add a SubqueryScan with the caller-requested targetlist + */ + plan = (Plan *) + make_subqueryscan(generate_setop_tlist(colTypes, flag, + subplan->targetlist, + refnames_tlist), + NIL, + rtr->rtindex, + subplan); + copy_plan_costsize(plan, subplan); + return plan; } - - /* Is this a simple one */ - if (!union_all_found || - !union_found || - /* A trailing UNION negates the effect of earlier UNION ALLs */ - !last_union_all_flag) + else if (IsA(setOp, SetOperationStmt)) { - List *hold_unionClause = parse->unionClause; - double tuple_fraction = -1.0; /* default processing */ - - /* we will do sorting later, so don't do it now */ - if (!union_all_found || - !last_union_all_flag) - { - parse->sortClause = NIL; - parse->distinctClause = NIL; - - /* - * force lower-level planning to assume that all tuples will - * be retrieved, even if it sees a LIMIT in the query node. - */ - tuple_fraction = 0.0; - } - - parse->unionClause = NIL; /* prevent recursion */ - union_plans = lcons(union_planner(parse, tuple_fraction), NIL); - union_rts = lcons(parse->rtable, NIL); + SetOperationStmt *op = (SetOperationStmt *) setOp; + Plan *plan; - foreach(ulist, hold_unionClause) + /* UNIONs are much different from INTERSECT/EXCEPT */ + if (op->op == SETOP_UNION) + plan = generate_union_plan(op, parse, refnames_tlist); + else + plan = generate_nonunion_plan(op, parse, refnames_tlist); + /* + * If necessary, add a Result node to project the caller-requested + * output columns. + * + * XXX you don't really want to know about this: setrefs.c will apply + * replace_vars_with_subplan_refs() to the Result node's tlist. + * This would fail if the input plan's non-resjunk tlist entries were + * not all simple Vars equal() to the referencing Vars generated by + * generate_setop_tlist(). However, since the input plan was + * generated by generate_union_plan() or generate_nonunion_plan(), + * the referencing Vars will equal the tlist entries they reference. + * Ugly but I don't feel like making that code more general right now. + */ + if (flag >= 0 || ! tlist_same_datatypes(plan->targetlist, colTypes)) { - Query *union_query = lfirst(ulist); - - /* - * use subquery_planner here because the union'd queries have - * not been preprocessed yet. My goodness this is messy... - */ - union_plans = lappend(union_plans, - subquery_planner(union_query, - tuple_fraction)); - union_rts = lappend(union_rts, union_query->rtable); + plan = (Plan *) + make_result(generate_setop_tlist(colTypes, flag, + plan->targetlist, + refnames_tlist), + NULL, + plan); } + return plan; } else { + elog(ERROR, "recurse_set_operations: unexpected node %d", + (int) nodeTag(setOp)); + return NULL; /* keep compiler quiet */ + } +} - /* - * We have mixed unions and non-unions - * - * We need to restructure this to put the UNIONs on their own so we - * can do a DISTINCT. - */ +/* + * Generate plan for a UNION or UNION ALL node + */ +static Plan * +generate_union_plan(SetOperationStmt *op, Query *parse, + List *refnames_tlist) +{ + List *planlist; + Plan *plan; + + /* + * If any of my children are identical UNION nodes (same op, all-flag, + * and colTypes) then they can be merged into this node so that we + * generate only one Append and Sort for the lot. Recurse to find + * such nodes and compute their children's plans. + */ + planlist = nconc(recurse_union_children(op->larg, parse, + op, refnames_tlist), + recurse_union_children(op->rarg, parse, + op, refnames_tlist)); + /* + * Append the child results together. + * + * The tlist for an Append plan isn't important as far as the Append + * is concerned, but we must make it look real anyway for the benefit + * of the next plan level up. + */ + plan = (Plan *) + make_append(planlist, + 0, + NIL, + generate_setop_tlist(op->colTypes, -1, + ((Plan *) lfirst(planlist))->targetlist, + refnames_tlist)); + /* + * For UNION ALL, we just need the Append plan. For UNION, + * need to add Sort and Unique nodes to produce unique output. + */ + if (! op->all) + { + List *tlist, + *sortList; - /* save off everthing past the last UNION */ - union_all_queries = lnext(last_union); + tlist = new_unsorted_tlist(plan->targetlist); + sortList = addAllTargetsToSortList(NIL, tlist); + plan = make_sortplan(tlist, plan, sortList); + plan = (Plan *) make_unique(tlist, plan, copyObject(sortList)); + } + return plan; +} - /* clip off the list to remove the trailing UNION ALLs */ - lnext(last_union) = NIL; +/* + * Generate plan for an INTERSECT, INTERSECT ALL, EXCEPT, or EXCEPT ALL node + */ +static Plan * +generate_nonunion_plan(SetOperationStmt *op, Query *parse, + List *refnames_tlist) +{ + Plan *lplan, + *rplan, + *plan; + List *tlist, + *sortList; + SetOpCmd cmd; + + /* Recurse on children, ensuring their outputs are marked */ + lplan = recurse_set_operations(op->larg, parse, + op->colTypes, 0, + refnames_tlist); + rplan = recurse_set_operations(op->rarg, parse, + op->colTypes, 1, + refnames_tlist); + /* + * Append the child results together. + * + * The tlist for an Append plan isn't important as far as the Append + * is concerned, but we must make it look real anyway for the benefit + * of the next plan level up. + */ + plan = (Plan *) + make_append(makeList2(lplan, rplan), + 0, + NIL, + generate_setop_tlist(op->colTypes, 0, + lplan->targetlist, + refnames_tlist)); + /* + * Sort the child results, then add a SetOp plan node to + * generate the correct output. + */ + tlist = new_unsorted_tlist(plan->targetlist); + sortList = addAllTargetsToSortList(NIL, tlist); + plan = make_sortplan(tlist, plan, sortList); + switch (op->op) + { + case SETOP_INTERSECT: + cmd = op->all ? SETOPCMD_INTERSECT_ALL : SETOPCMD_INTERSECT; + break; + case SETOP_EXCEPT: + cmd = op->all ? SETOPCMD_EXCEPT_ALL : SETOPCMD_EXCEPT; + break; + default: + elog(ERROR, "generate_nonunion_plan: bogus operation code"); + cmd = SETOPCMD_INTERSECT; /* keep compiler quiet */ + break; + } + plan = (Plan *) make_setop(cmd, tlist, plan, sortList, + length(op->colTypes)+1); + return plan; +} - /* - * Recursion, but UNION only. The last one is a UNION, so it will - * not come here in recursion. - * - * XXX is it OK to pass default -1 to union_planner in this path, or - * should we force a tuple_fraction value? - */ - union_plans = lcons(union_planner(parse, -1.0), NIL); - union_rts = lcons(parse->rtable, NIL); +/* + * Pull up children of a UNION node that are identically-propertied UNIONs. + * + * NOTE: we can also pull a UNION ALL up into a UNION, since the distinct + * output rows will be lost anyway. + */ +static List * +recurse_union_children(Node *setOp, Query *parse, + SetOperationStmt *top_union, + List *refnames_tlist) +{ + if (IsA(setOp, SetOperationStmt)) + { + SetOperationStmt *op = (SetOperationStmt *) setOp; - /* Append the remaining UNION ALLs */ - foreach(ulist, union_all_queries) + if (op->op == top_union->op && + (op->all == top_union->all || op->all) && + equali(op->colTypes, top_union->colTypes)) { - Query *union_all_query = lfirst(ulist); - - /* - * use subquery_planner here because the union'd queries have - * not been preprocessed yet. My goodness this is messy... - */ - union_plans = lappend(union_plans, - subquery_planner(union_all_query, -1.0)); - union_rts = lappend(union_rts, union_all_query->rtable); + /* Same UNION, so fold children into parent's subplan list */ + return nconc(recurse_union_children(op->larg, parse, + top_union, refnames_tlist), + recurse_union_children(op->rarg, parse, + top_union, refnames_tlist)); } } + /* Not same, so plan this child separately */ + return makeList1(recurse_set_operations(setOp, parse, + top_union->colTypes, -1, + refnames_tlist)); +} - /* We have already split UNION and UNION ALL and we made it consistent */ - if (!last_union_all_flag) - { +/* + * Generate targetlist for a set-operation plan node + */ +static List * +generate_setop_tlist(List *colTypes, int flag, + List *input_tlist, + List *refnames_tlist) +{ + List *tlist = NIL; + int resno = 1; + List *i; + Resdom *resdom; + Node *expr; + foreach(i, colTypes) + { + Oid colType = (Oid) lfirsti(i); + TargetEntry *inputtle = (TargetEntry *) lfirst(input_tlist); + TargetEntry *reftle = (TargetEntry *) lfirst(refnames_tlist); + + Assert(inputtle->resdom->resno == resno); + Assert(reftle->resdom->resno == resno); + Assert(!inputtle->resdom->resjunk); + Assert(!reftle->resdom->resjunk); /* - * Need SELECT DISTINCT behavior to implement UNION. Put back the - * held sortClause, add any missing columns to the sort clause, - * and set distinctClause properly. + * Generate columns referencing input columns and having + * appropriate data types and column names. Insert datatype + * coercions where necessary. + * + * HACK: constants in the input's targetlist are copied up as-is + * rather than being referenced as subquery outputs. This is mainly + * to ensure that when we try to coerce them to the output column's + * datatype, the right things happen for UNKNOWN constants. */ - List *slitem; - - parse->sortClause = addAllTargetsToSortList(hold_sortClause, - parse->targetList); - parse->distinctClause = NIL; - foreach(slitem, parse->sortClause) - { - SortClause *scl = (SortClause *) lfirst(slitem); - TargetEntry *tle = get_sortgroupclause_tle(scl, parse->targetList); - - if (!tle->resdom->resjunk) - parse->distinctClause = lappend(parse->distinctClause, - copyObject(scl)); - } + resdom = makeResdom((AttrNumber) resno++, + colType, + -1, + pstrdup(reftle->resdom->resname), + false); + if (inputtle->expr && IsA(inputtle->expr, Const)) + expr = inputtle->expr; + else + expr = (Node *) makeVar(0, + inputtle->resdom->resno, + inputtle->resdom->restype, + inputtle->resdom->restypmod, + 0); + expr = coerce_to_common_type(NULL, + expr, + colType, + "UNION/INTERSECT/EXCEPT"); + tlist = lappend(tlist, makeTargetEntry(resdom, expr)); + input_tlist = lnext(input_tlist); + refnames_tlist = lnext(refnames_tlist); } - else + + if (flag >= 0) { - /* needed so we don't take SELECT DISTINCT from the first query */ - parse->distinctClause = NIL; + /* Add a resjunk column yielding specified flag value */ + resdom = makeResdom((AttrNumber) resno++, + INT4OID, + -1, + pstrdup("flag"), + true); + expr = (Node *) makeConst(INT4OID, + sizeof(int4), + Int32GetDatum(flag), + false, + true, + false, + false); + tlist = lappend(tlist, makeTargetEntry(resdom, expr)); } - /* - * Make sure we don't try to apply the first query's grouping stuff to - * the Append node, either. Basically we don't want union_planner to - * do anything when we return control, except add the top sort/unique - * nodes for DISTINCT processing if this wasn't UNION ALL, or the top - * sort node if it was UNION ALL with a user-provided sort clause. - */ - parse->groupClause = NULL; - parse->havingQual = NULL; - parse->hasAggs = false; + return tlist; +} - return (Plan *) make_append(union_plans, - union_rts, - 0, - NIL, - parse->targetList); +/* + * Does tlist have same datatypes as requested colTypes? + * + * Resjunk columns are ignored. + */ +static bool +tlist_same_datatypes(List *tlist, List *colTypes) +{ + List *i; + + foreach(i, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(i); + + if (!tle->resdom->resjunk) + { + if (colTypes == NIL) + return false; + if (tle->resdom->restype != (Oid) lfirsti(colTypes)) + return false; + colTypes = lnext(colTypes); + } + } + if (colTypes != NIL) + return false; + return true; } @@ -372,7 +554,6 @@ plan_inherit_queries(Query *root, List *tlist, /* Construct the finished Append plan. */ return (Plan *) make_append(union_plans, - NIL, rt_index, union_rtentries, ((Plan *) lfirst(union_plans))->targetlist); @@ -530,7 +711,8 @@ fix_parsetree_attnums(Index rt_index, query_tree_walker(parsetree, fix_parsetree_attnums_walker, - (void *) &context); + (void *) &context, + true); } /* @@ -570,7 +752,8 @@ fix_parsetree_attnums_walker(Node *node, context->sublevels_up++; result = query_tree_walker((Query *) node, fix_parsetree_attnums_walker, - (void *) context); + (void *) context, + true); context->sublevels_up--; return result; } @@ -580,7 +763,6 @@ fix_parsetree_attnums_walker(Node *node, static Append * make_append(List *appendplans, - List *unionrtables, Index rt_index, List *inheritrtable, List *tlist) @@ -589,7 +771,6 @@ make_append(List *appendplans, List *subnode; node->appendplans = appendplans; - node->unionrtables = unionrtables; node->inheritrelid = rt_index; node->inheritrtable = inheritrtable; node->plan.startup_cost = 0; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 06c67daebfc..f52ec6d2d87 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.76 2000/09/29 18:21:23 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.77 2000/10/05 19:11:32 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -1636,8 +1636,8 @@ simplify_op_or_func(Expr *expr, List *args) * so that a scan of a target list can be handled without additional code. * (But only the "expr" part of a TargetEntry is examined, unless the walker * chooses to process TargetEntry nodes specially.) Also, RangeTblRef, - * FromExpr, and JoinExpr nodes are handled, so that qual expressions in a - * jointree can be processed without additional code. + * FromExpr, JoinExpr, and SetOperationStmt nodes are handled, so that query + * jointrees and setOperation trees can be processed without additional code. * * expression_tree_walker will handle SubLink and SubPlan nodes by recursing * normally into the "lefthand" arguments (which belong to the outer plan). @@ -1654,7 +1654,8 @@ simplify_op_or_func(Expr *expr, List *args) * if (IsA(node, Query)) * { * adjust context for subquery; - * result = query_tree_walker((Query *) node, my_walker, context); + * result = query_tree_walker((Query *) node, my_walker, context, + * true); // to visit subquery RTEs too * restore context if needed; * return result; * } @@ -1827,6 +1828,16 @@ expression_tree_walker(Node *node, */ } break; + case T_SetOperationStmt: + { + SetOperationStmt *setop = (SetOperationStmt *) node; + + if (walker(setop->larg, context)) + return true; + if (walker(setop->rarg, context)) + return true; + } + break; default: elog(ERROR, "expression_tree_walker: Unexpected node type %d", nodeTag(node)); @@ -1843,11 +1854,17 @@ expression_tree_walker(Node *node, * for starting a walk at top level of a Query regardless of whether the * walker intends to descend into subqueries. It is also useful for * descending into subqueries within a walker. + * + * If visitQueryRTEs is true, the walker will also be called on sub-Query + * nodes present in subquery rangetable entries of the given Query. This + * is optional since some callers handle those sub-queries separately, + * or don't really want to see subqueries anyway. */ bool query_tree_walker(Query *query, bool (*walker) (), - void *context) + void *context, + bool visitQueryRTEs) { Assert(query != NULL && IsA(query, Query)); @@ -1855,11 +1872,23 @@ query_tree_walker(Query *query, return true; if (walker((Node *) query->jointree, context)) return true; + if (walker(query->setOperations, context)) + return true; if (walker(query->havingQual, context)) return true; - /* - * XXX for subselect-in-FROM, may need to examine rtable as well? - */ + if (visitQueryRTEs) + { + List *rt; + + foreach(rt, query->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt); + + if (rte->subquery) + if (walker(rte->subquery, context)) + return true; + } + } return false; } @@ -2158,6 +2187,17 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_SetOperationStmt: + { + SetOperationStmt *setop = (SetOperationStmt *) node; + SetOperationStmt *newnode; + + FLATCOPY(newnode, setop, SetOperationStmt); + MUTATE(newnode->larg, setop->larg, Node *); + MUTATE(newnode->rarg, setop->rarg, Node *); + return (Node *) newnode; + } + break; default: elog(ERROR, "expression_tree_mutator: Unexpected node type %d", nodeTag(node)); @@ -2166,3 +2206,58 @@ expression_tree_mutator(Node *node, /* can't get here, but keep compiler happy */ return NULL; } + + +/* + * query_tree_mutator --- initiate modification of a Query's expressions + * + * This routine exists just to reduce the number of places that need to know + * where all the expression subtrees of a Query are. Note it can be used + * for starting a walk at top level of a Query regardless of whether the + * mutator intends to descend into subqueries. It is also useful for + * descending into subqueries within a mutator. + * + * The specified Query node is modified-in-place; do a FLATCOPY() beforehand + * if you don't want to change the original. All substructure is safely + * copied, however. + * + * If visitQueryRTEs is true, the mutator will also be called on sub-Query + * nodes present in subquery rangetable entries of the given Query. This + * is optional since some callers handle those sub-queries separately, + * or don't really want to see subqueries anyway. + */ +void +query_tree_mutator(Query *query, + Node *(*mutator) (), + void *context, + bool visitQueryRTEs) +{ + Assert(query != NULL && IsA(query, Query)); + + MUTATE(query->targetList, query->targetList, List *); + MUTATE(query->jointree, query->jointree, FromExpr *); + MUTATE(query->setOperations, query->setOperations, Node *); + MUTATE(query->havingQual, query->havingQual, Node *); + if (visitQueryRTEs) + { + List *newrt = NIL; + List *rt; + + foreach(rt, query->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt); + + if (rte->subquery) + { + RangeTblEntry *newrte; + + FLATCOPY(newrte, rte, RangeTblEntry); + CHECKFLATCOPY(newrte->subquery, rte->subquery, Query); + MUTATE(newrte->subquery, newrte->subquery, Query *); + rte = newrte; + } + newrt = lappend(newrt, rte); + } + query->rtable = newrt; + } +} diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index ec9f9dafd0b..a18db71ec55 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.27 2000/09/12 21:06:59 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.28 2000/10/05 19:11:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -67,7 +67,7 @@ pull_varnos(Node *node) */ if (node && IsA(node, Query)) query_tree_walker((Query *) node, pull_varnos_walker, - (void *) &context); + (void *) &context, true); else pull_varnos_walker(node, &context); @@ -108,12 +108,12 @@ pull_varnos_walker(Node *node, pull_varnos_context *context) } if (IsA(node, Query)) { - /* Recurse into not-yet-planned subquery */ + /* Recurse into RTE subquery or not-yet-planned sublink subquery */ bool result; context->sublevels_up++; result = query_tree_walker((Query *) node, pull_varnos_walker, - (void *) context); + (void *) context, true); context->sublevels_up--; return result; } |