]> granicus.if.org Git - postgresql/blobdiff - src/backend/optimizer/plan/subselect.c
Re-implement LIMIT/OFFSET as a plan node type, instead of a hack in
[postgresql] / src / backend / optimizer / plan / subselect.c
index b16203d9f7b7b23f56bd936a4a4c58a5e331d853..296164acb8916d9eaa4ec30436663363bad338b8 100644 (file)
 /*-------------------------------------------------------------------------
  *
- * subselect.c--
+ * subselect.c
+ *       Planning routines for subselects and parameters.
+ *
+ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *       $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.44 2000/10/26 21:36:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
-
-#include "nodes/pg_list.h"
-#include "nodes/plannodes.h"
-#include "nodes/parsenodes.h"
-#include "nodes/relation.h"
 #include "nodes/makefuncs.h"
-#include "nodes/nodeFuncs.h"
-
-#include "optimizer/subselect.h"
-#include "optimizer/planner.h"
-#include "optimizer/planmain.h"
-#include "optimizer/internal.h"
-#include "optimizer/paths.h"
 #include "optimizer/clauses.h"
-#include "optimizer/keys.h"
-#include "optimizer/tlist.h"
-#include "optimizer/var.h"
 #include "optimizer/cost.h"
+#include "optimizer/planmain.h"
+#include "optimizer/planner.h"
+#include "optimizer/subselect.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_oper.h"
+#include "utils/lsyscache.h"
 
-int                    PlannerQueryLevel;      /* level of current query */
-List      *PlannerVarParam;    /* correlation Vars to Param mapper */
-List      *PlannerParamVar;    /* to get Var from Param->paramid */
+
+Index          PlannerQueryLevel;      /* level of current query */
 List      *PlannerInitPlan;    /* init subplans for current query */
-int                    PlannerPlanId;
+List      *PlannerParamVar;    /* to get Var from Param->paramid */
+
+int                    PlannerPlanId = 0;      /* to assign unique ID to subquery plans */
+
+/*--------------------
+ * PlannerParamVar is a list of Var nodes, wherein the n'th entry
+ * (n counts from 0) corresponds to Param->paramid = n.  The Var nodes
+ * are ordinary except for one thing: their varlevelsup field does NOT
+ * have the usual interpretation of "subplan levels out from current".
+ * Instead, it contains the absolute plan level, with the outermost
+ * plan being level 1 and nested plans having higher level numbers.
+ * This nonstandardness is useful because we don't have to run around
+ * and update the list elements when we enter or exit a subplan
+ * recursion level.  But we must pay attention not to confuse this
+ * meaning with the normal meaning of varlevelsup.
+ *--------------------
+ */
 
 
+/*
+ * Create a new entry in the PlannerParamVar list, and return its index.
+ *
+ * var contains the data to be copied, except for varlevelsup which
+ * is set from the absolute level value given by varlevel.
+ */
 static int
-_new_param(Var *var, int varlevel)
+new_param(Var *var, Index varlevel)
 {
-       List       *last;
-       int                     i = 0;
+       Var                *paramVar = (Var *) copyObject(var);
 
-       if (PlannerParamVar == NULL)
-               last = PlannerParamVar = makeNode(List);
-       else
-       {
-               for (last = PlannerParamVar;;)
-               {
-                       i++;
-                       if (lnext(last) == NULL)
-                               break;
-                       last = lnext(last);
-               }
-               lnext(last) = makeNode(List);
-               last = lnext(last);
-       }
+       paramVar->varlevelsup = varlevel;
 
-       lnext(last) = NULL;
-       lfirst(last) = makeVar(var->varno, var->varattno, var->vartype,
-                               var->vartypmod, varlevel, var->varnoold, var->varoattno);
+       PlannerParamVar = lappend(PlannerParamVar, paramVar);
 
-       return i;
+       return length(PlannerParamVar) - 1;
 }
 
+/*
+ * Generate a Param node to replace the given Var,
+ * which is expected to have varlevelsup > 0 (ie, it is not local).
+ */
 static Param *
-_replace_var(Var *var)
+replace_var(Var *var)
 {
-       List      **rt = (List **) nth(var->varlevelsup, PlannerVarParam);
-       List       *vpe = rt[var->varno - 1];
+       List       *ppv;
        Param      *retval;
+       Index           varlevel;
        int                     i;
 
-       if (vpe == NULL)
-       {
-               vpe = rt[var->varno - 1] = makeNode(List);
-               lfirsti(vpe) = -1;
-               lnext(vpe) = NULL;
-       }
+       Assert(var->varlevelsup > 0 && var->varlevelsup < PlannerQueryLevel);
+       varlevel = PlannerQueryLevel - var->varlevelsup;
 
-       for (i = ObjectIdAttributeNumber;; i++)
+       /*
+        * If there's already a PlannerParamVar entry for this same Var, just
+        * 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
+        * vartype as well, I believe that this sort of aliasing will cause no
+        * trouble. The correct field should get stored into the Param slot at
+        * execution in each part of the tree.
+        */
+       i = 0;
+       foreach(ppv, PlannerParamVar)
        {
-               if (i == var->varattno)
+               Var                *pvar = lfirst(ppv);
+
+               if (pvar->varno == var->varno &&
+                       pvar->varattno == var->varattno &&
+                       pvar->varlevelsup == varlevel &&
+                       pvar->vartype == var->vartype)
                        break;
-               if (lnext(vpe) == NULL)
-               {
-                       lnext(vpe) = makeNode(List);
-                       vpe = lnext(vpe);
-                       lfirsti(vpe) = -1;
-                       lnext(vpe) = NULL;
-               }
-               else
-                       vpe = lnext(vpe);
+               i++;
        }
 
-       if ((i = lfirsti(vpe)) < 0) /* parameter is not assigned */
-               i = _new_param(var, PlannerQueryLevel - var->varlevelsup);
+       if (!ppv)
+       {
+               /* Nope, so make a new one */
+               i = new_param(var, varlevel);
+       }
 
        retval = makeNode(Param);
        retval->paramkind = PARAM_EXEC;
@@ -102,453 +117,583 @@ _replace_var(Var *var)
        return retval;
 }
 
+/*
+ * Convert a bare SubLink (as created by the parser) into a SubPlan.
+ */
 static Node *
-_make_subplan(SubLink *slink)
+make_subplan(SubLink *slink)
 {
        SubPlan    *node = makeNode(SubPlan);
+       Query      *subquery = (Query *) (slink->subselect);
+       double          tuple_fraction;
        Plan       *plan;
        List       *lst;
        Node       *result;
-       List       *saved_ip = PlannerInitPlan;
 
-       PlannerInitPlan = NULL;
-
-       PlannerQueryLevel++;            /* we becomes child */
-
-       node->plan = plan = union_planner((Query *) slink->subselect);
+       /*
+        * Check to see if this node was already processed; if so we have
+        * trouble.  We check to see if the linked-to Query appears to have
+        * been planned already, too.
+        */
+       if (subquery == NULL)
+               elog(ERROR, "make_subplan: invalid expression structure (SubLink already processed?)");
+       if (subquery->base_rel_list != NIL)
+               elog(ERROR, "make_subplan: invalid expression structure (subquery already processed?)");
 
        /*
-        * Assign subPlan, extParam and locParam to plan nodes. At the moment,
-        * SS_finalize_plan doesn't handle initPlan-s and so we assigne them
-        * to the topmost plan node and take care about its extParam too.
+        * Copy the source Query node.  This is a quick and dirty kluge to resolve
+        * the fact that the parser can generate trees with multiple links to the
+        * same sub-Query node, but the planner wants to scribble on the Query.
+        * Try to clean this up when we do querytree redesign...
         */
-       (void) SS_finalize_plan(plan);
-       plan->initPlan = PlannerInitPlan;
+       subquery = (Query *) copyObject(subquery);
 
-       /* Get extParam from InitPlan-s */
-       foreach(lst, PlannerInitPlan)
-       {
-               List       *lp;
+       /*
+        * For an EXISTS subplan, tell lower-level planner to expect that only
+        * the first tuple will be retrieved.  For ALL and ANY subplans, we
+        * will be able to stop evaluating if the test condition fails, so
+        * very often not all the tuples will be retrieved; for lack of a
+        * better idea, specify 50% retrieval.  For EXPR and MULTIEXPR
+        * subplans, use default behavior (we're only expecting one row out,
+        * anyway).
+        *
+        * NOTE: if you change these numbers, also change cost_qual_eval_walker()
+        * in path/costsize.c.
+        *
+        * XXX If an ALL/ANY subplan is uncorrelated, we may decide to
+        * materialize its result below.  In that case it would've been better
+        * to specify full retrieval.  At present, however, we can only detect
+        * correlation or lack of it after we've made the subplan :-(. Perhaps
+        * detection of correlation should be done as a separate step.
+        * Meanwhile, we don't want to be too optimistic about the percentage
+        * of tuples retrieved, for fear of selecting a plan that's bad for
+        * the materialization case.
+        */
+       if (slink->subLinkType == EXISTS_SUBLINK)
+               tuple_fraction = 1.0;   /* just like a LIMIT 1 */
+       else if (slink->subLinkType == ALL_SUBLINK ||
+                        slink->subLinkType == ANY_SUBLINK)
+               tuple_fraction = 0.5;   /* 50% */
+       else
+               tuple_fraction = -1.0;  /* default behavior */
 
-               foreach(lp, ((SubPlan *) lfirst(lst))->plan->extParam)
-               {
-                       if (!intMember(lfirsti(lp), plan->extParam))
-                               plan->extParam = lappendi(plan->extParam, lfirsti(lp));
-               }
-       }
+       /*
+        * Generate the plan for the subquery.
+        */
+       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 = ((Query *) slink->subselect)->rtable;
+       node->rtable = subquery->rtable;
        node->sublink = slink;
-       slink->subselect = NULL;        /* cool ?! */
 
-       /* make parParam list */
+       slink->subselect = NULL;        /* cool ?! see error check above! */
+
+       /*
+        * 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);
        }
 
        /*
-        * Un-correlated or undirect correlated plans of EXISTS or EXPR types
-        * can be used as initPlans...
+        * Un-correlated or undirect correlated plans of EXISTS, EXPR, or
+        * MULTIEXPR types can be used as initPlans.  For EXISTS or EXPR, we
+        * just produce a Param referring to the result of evaluating the
+        * initPlan.  For MULTIEXPR, we must build an AND or OR-clause of the
+        * individual comparison operators, using the appropriate lefthand
+        * side expressions and Params for the initPlan's target items.
         */
-       if (node->parParam == NULL && slink->subLinkType == EXPR_SUBLINK)
+       if (node->parParam == NIL && slink->subLinkType == EXISTS_SUBLINK)
+       {
+               Var                *var = makeVar(0, 0, BOOLOID, -1, 0);
+               Param      *prm = makeNode(Param);
+
+               prm->paramkind = PARAM_EXEC;
+               prm->paramid = (AttrNumber) new_param(var, PlannerQueryLevel);
+               prm->paramtype = var->vartype;
+               pfree(var);                             /* var is only needed for new_param */
+               node->setParam = lappendi(node->setParam, prm->paramid);
+               PlannerInitPlan = lappend(PlannerInitPlan, node);
+               result = (Node *) prm;
+       }
+       else if (node->parParam == NIL && slink->subLinkType == EXPR_SUBLINK)
+       {
+               TargetEntry *te = lfirst(plan->targetlist);
+
+               /* need a var node just to pass to new_param()... */
+               Var                *var = makeVar(0, 0, te->resdom->restype,
+                                                                 te->resdom->restypmod, 0);
+               Param      *prm = makeNode(Param);
+
+               prm->paramkind = PARAM_EXEC;
+               prm->paramid = (AttrNumber) new_param(var, PlannerQueryLevel);
+               prm->paramtype = var->vartype;
+               pfree(var);                             /* var is only needed for new_param */
+               node->setParam = lappendi(node->setParam, prm->paramid);
+               PlannerInitPlan = lappend(PlannerInitPlan, node);
+               result = (Node *) prm;
+       }
+       else if (node->parParam == NIL && slink->subLinkType == MULTIEXPR_SUBLINK)
        {
+               List       *newoper = NIL;
                int                     i = 0;
 
-               /* transform right side of all sublink Oper-s into Param */
+               /*
+                * Convert oper list of Opers into a list of Exprs, using lefthand
+                * arguments and Params representing inside results.
+                */
                foreach(lst, slink->oper)
                {
-                       List       *rside = lnext(((Expr *) lfirst(lst))->args);
+                       Oper       *oper = (Oper *) lfirst(lst);
+                       Node       *lefthand = nth(i, slink->lefthand);
                        TargetEntry *te = nth(i, plan->targetlist);
+
+                       /* need a var node just to pass to new_param()... */
                        Var                *var = makeVar(0, 0, te->resdom->restype,
-                                                                         te->resdom->restypmod,
-                                                                         PlannerQueryLevel, 0, 0);
+                                                                         te->resdom->restypmod, 0);
                        Param      *prm = makeNode(Param);
+                       Operator        tup;
+                       Form_pg_operator opform;
+                       Node       *left,
+                                          *right;
 
                        prm->paramkind = PARAM_EXEC;
-                       prm->paramid = (AttrNumber) _new_param(var, PlannerQueryLevel);
+                       prm->paramid = (AttrNumber) new_param(var, PlannerQueryLevel);
                        prm->paramtype = var->vartype;
-                       lfirst(rside) = prm;
+                       pfree(var);                     /* var is only needed for new_param */
+
+                       Assert(IsA(oper, Oper));
+                       tup = get_operator_tuple(oper->opno);
+                       Assert(HeapTupleIsValid(tup));
+                       opform = (Form_pg_operator) GETSTRUCT(tup);
+
+                       /*
+                        * Note: we use make_operand in case runtime type conversion
+                        * function calls must be inserted for this operator!
+                        */
+                       left = make_operand("", lefthand,
+                                                               exprType(lefthand), opform->oprleft);
+                       right = make_operand("", (Node *) prm,
+                                                                prm->paramtype, opform->oprright);
+                       newoper = lappend(newoper,
+                                                         make_opclause(oper,
+                                                                                       (Var *) left,
+                                                                                       (Var *) right));
                        node->setParam = lappendi(node->setParam, prm->paramid);
-                       pfree(var);
                        i++;
                }
+               slink->oper = newoper;
+               slink->lefthand = NIL;
                PlannerInitPlan = lappend(PlannerInitPlan, node);
                if (i > 1)
-                       result = (Node *) ((slink->useor) ? make_orclause(slink->oper) :
-                                                          make_andclause(slink->oper));
+                       result = (Node *) ((slink->useor) ? make_orclause(newoper) :
+                                                          make_andclause(newoper));
                else
-                       result = (Node *) lfirst(slink->oper);
-       }
-       else if (node->parParam == NULL && slink->subLinkType == EXISTS_SUBLINK)
-       {
-               Var                *var = makeVar(0, 0, BOOLOID, -1, PlannerQueryLevel, 0, 0);
-               Param      *prm = makeNode(Param);
-
-               prm->paramkind = PARAM_EXEC;
-               prm->paramid = (AttrNumber) _new_param(var, PlannerQueryLevel);
-               prm->paramtype = var->vartype;
-               node->setParam = lappendi(node->setParam, prm->paramid);
-               pfree(var);
-               PlannerInitPlan = lappend(PlannerInitPlan, node);
-               result = (Node *) prm;
+                       result = (Node *) lfirst(newoper);
        }
        else
-/* make expression of SUBPLAN type */
        {
                Expr       *expr = makeNode(Expr);
-               List       *args = NULL;
+               List       *args = NIL;
+               List       *newoper = NIL;
                int                     i = 0;
 
-               expr->typeOid = BOOLOID;
+               /*
+                * We can't convert subplans of ALL_SUBLINK or ANY_SUBLINK types
+                * to initPlans, even when they are uncorrelated or undirect
+                * correlated, because we need to scan the output of the subplan
+                * for each outer tuple.  However, we have the option to tack a
+                * MATERIAL node onto the top of an uncorrelated/undirect
+                * correlated subplan, which lets us do the work of evaluating the
+                * subplan only once.  We do this if the subplan's top plan node
+                * is anything more complicated than a plain sequential scan, and
+                * we do it even for seqscan if the qual appears selective enough
+                * to eliminate many tuples.
+                */
+               if (node->parParam == NIL)
+               {
+                       bool            use_material;
+
+                       switch (nodeTag(plan))
+                       {
+                               case T_SeqScan:
+                                       if (plan->initPlan || plan->subPlan)
+                                               use_material = true;
+                                       else
+                                       {
+                                               Selectivity qualsel;
+
+                                               qualsel = clauselist_selectivity(subquery,
+                                                                                                                plan->qual,
+                                                                                                                0);
+                                               /* Is 10% selectivity a good threshold?? */
+                                               use_material = qualsel < 0.10;
+                                       }
+                                       break;
+                               case T_Material:
+                               case T_Sort:
+
+                                       /*
+                                        * Don't add another Material node if there's one
+                                        * already, nor if the top node is a Sort, since Sort
+                                        * materializes its output anyway.      (I doubt either
+                                        * case can happen in practice for a subplan, but...)
+                                        */
+                                       use_material = false;
+                                       break;
+                               default:
+                                       use_material = true;
+                                       break;
+                       }
+                       if (use_material)
+                       {
+                               plan = (Plan *) make_material(plan->targetlist, plan);
+                               node->plan = plan;
+                       }
+               }
+
+               /*
+                * Make expression of SUBPLAN type
+                */
+               expr->typeOid = BOOLOID;/* bogus, but we don't really care */
                expr->opType = SUBPLAN_EXPR;
                expr->oper = (Node *) node;
 
                /*
-                * Make expr->args from parParam. Left sides of sublink Oper-s are
-                * handled by optimizer directly... Also, transform right side of
-                * sublink Oper-s into Const.
+                * Make expr->args from parParam.
                 */
                foreach(lst, node->parParam)
                {
                        Var                *var = nth(lfirsti(lst), PlannerParamVar);
 
                        var = (Var *) copyObject(var);
+
+                       /*
+                        * Must fix absolute-level varlevelsup from the
+                        * PlannerParamVar entry.  But since var is at current subplan
+                        * level, this is easy:
+                        */
                        var->varlevelsup = 0;
                        args = lappend(args, var);
                }
+               expr->args = args;
+
+               /*
+                * Convert oper list of Opers into a list of Exprs, using lefthand
+                * arguments and Consts representing inside results.
+                */
                foreach(lst, slink->oper)
                {
-                       List       *rside = lnext(((Expr *) lfirst(lst))->args);
+                       Oper       *oper = (Oper *) lfirst(lst);
+                       Node       *lefthand = nth(i, slink->lefthand);
                        TargetEntry *te = nth(i, plan->targetlist);
-                       Const      *con = makeConst(te->resdom->restype,
-                                                                               0, 0, true, 0, 0, 0);
-
-                       lfirst(rside) = con;
+                       Const      *con;
+                       Operator        tup;
+                       Form_pg_operator opform;
+                       Node       *left,
+                                          *right;
+
+                       /*
+                        * XXX really ought to fill in constlen and constbyval
+                        * correctly, but right now ExecEvalExpr won't look at them...
+                        */
+                       con = makeConst(te->resdom->restype, 0, 0, true, 0, 0, 0);
+
+                       Assert(IsA(oper, Oper));
+                       tup = get_operator_tuple(oper->opno);
+                       Assert(HeapTupleIsValid(tup));
+                       opform = (Form_pg_operator) GETSTRUCT(tup);
+
+                       /*
+                        * Note: we use make_operand in case runtime type conversion
+                        * function calls must be inserted for this operator!
+                        */
+                       left = make_operand("", lefthand,
+                                                               exprType(lefthand), opform->oprleft);
+                       right = make_operand("", (Node *) con,
+                                                                con->consttype, opform->oprright);
+                       newoper = lappend(newoper,
+                                                         make_opclause(oper,
+                                                                                       (Var *) left,
+                                                                                       (Var *) right));
                        i++;
                }
-               expr->args = args;
+               slink->oper = newoper;
+               slink->lefthand = NIL;
                result = (Node *) expr;
        }
 
        return result;
-
 }
 
-static List *
-set_unioni(List *l1, List *l2)
-{
-       if (l1 == NULL)
-               return l2;
-       if (l2 == NULL)
-               return l1;
-
-       return nconc(l1, set_differencei(l2, l1));
-}
+/*
+ * finalize_primnode: build lists of subplans and params appearing
+ * in the given expression tree.  NOTE: items are added to lists passed in,
+ * so caller must initialize lists to NIL before first call!
+ *
+ * Note: the subplan list that is constructed here and assigned to the
+ * plan's subPlan field will be replaced with an up-to-date list in
+ * set_plan_references().  We could almost dispense with building this
+ * subplan list at all; I believe the only place that uses it is the
+ * check in make_subplan to see whether a subselect has any subselects.
+ */
 
-static List *
-_finalize_primnode(void *expr, List **subplan)
+typedef struct finalize_primnode_results
 {
-       List       *result = NULL;
-
-       if (expr == NULL)
-               return NULL;
+       List       *subplans;           /* List of subplans found in expr */
+       List       *paramids;           /* List of PARAM_EXEC paramids found */
+} finalize_primnode_results;
 
-       if (IsA(expr, Param))
-       {
-               if (((Param *) expr)->paramkind == PARAM_EXEC)
-                       return lconsi(((Param *) expr)->paramid, (List *) NULL);
-       }
-       else if (single_node(expr))
-               return NULL;
-       else if (IsA(expr, List))
+static bool
+finalize_primnode(Node *node, finalize_primnode_results *results)
+{
+       if (node == NULL)
+               return false;
+       if (IsA(node, Param))
        {
-               List       *le;
+               if (((Param *) node)->paramkind == PARAM_EXEC)
+               {
+                       int                     paramid = (int) ((Param *) node)->paramid;
 
-               foreach(le, (List *) expr)
-                       result = set_unioni(result,
-                                                               _finalize_primnode(lfirst(le), subplan));
-       }
-       else if (IsA(expr, Iter))
-               return _finalize_primnode(((Iter *) expr)->iterexpr, subplan);
-       else if (or_clause(expr) || and_clause(expr) || is_opclause(expr) ||
-                        not_clause(expr) || is_funcclause(expr))
-               return _finalize_primnode(((Expr *) expr)->args, subplan);
-       else if (IsA(expr, Aggreg))
-               return _finalize_primnode(((Aggreg *) expr)->target, subplan);
-       else if (IsA(expr, ArrayRef))
-       {
-               result = _finalize_primnode(((ArrayRef *) expr)->refupperindexpr, subplan);
-               result = set_unioni(result,
-                                                       _finalize_primnode(((ArrayRef *) expr)->reflowerindexpr, subplan));
-               result = set_unioni(result,
-                         _finalize_primnode(((ArrayRef *) expr)->refexpr, subplan));
-               result = set_unioni(result,
-                _finalize_primnode(((ArrayRef *) expr)->refassgnexpr, subplan));
+                       if (!intMember(paramid, results->paramids))
+                               results->paramids = lconsi(paramid, results->paramids);
+               }
+               return false;                   /* no more to do here */
        }
-       else if (IsA(expr, TargetEntry))
-               return _finalize_primnode(((TargetEntry *) expr)->expr, subplan);
-       else if (is_subplan(expr))
+       if (is_subplan(node))
        {
+               SubPlan    *subplan = (SubPlan *) ((Expr *) node)->oper;
                List       *lst;
 
-               *subplan = lappend(*subplan, ((Expr *) expr)->oper);
-               foreach(lst, ((SubPlan *) ((Expr *) expr)->oper)->plan->extParam)
+               /* Add subplan to subplans list */
+               results->subplans = lappend(results->subplans, subplan);
+               /* Check extParam list for params to add to paramids */
+               foreach(lst, subplan->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 &&
-                               !intMember(lfirsti(lst), result))
-                               result = lappendi(result, lfirsti(lst));
+                               !intMember(paramid, results->paramids))
+                               results->paramids = lconsi(paramid, results->paramids);
                }
+               /* fall through to recurse into subplan args */
        }
-       else
-               elog(ERROR, "_finalize_primnode: can't handle node %d",
-                        nodeTag(expr));
-
-       return result;
+       return expression_tree_walker(node, finalize_primnode,
+                                                                 (void *) results);
 }
 
+/*
+ * Replace correlation vars (uplevel vars) with Params.
+ */
+
+static Node *replace_correlation_vars_mutator(Node *node, void *context);
+
 Node *
 SS_replace_correlation_vars(Node *expr)
 {
+       /* No setup needed for tree walk, so away we go */
+       return replace_correlation_vars_mutator(expr, NULL);
+}
 
-       if (expr == NULL)
+static Node *
+replace_correlation_vars_mutator(Node *node, void *context)
+{
+       if (node == NULL)
                return NULL;
-       if (IsA(expr, List))
-       {
-               List       *le;
-
-               foreach(le, (List *) expr)
-                       lfirst(le) = SS_replace_correlation_vars((Node *) lfirst(le));
-       }
-       else if (IsA(expr, Var))
+       if (IsA(node, Var))
        {
-               if (((Var *) expr)->varlevelsup > 0)
-               {
-                       Assert(((Var *) expr)->varlevelsup < PlannerQueryLevel);
-                       expr = (Node *) _replace_var((Var *) expr);
-               }
+               if (((Var *) node)->varlevelsup > 0)
+                       return (Node *) replace_var((Var *) node);
        }
-       else if (IsA(expr, Iter))
-       {
-               ((Iter *) expr)->iterexpr =
-                       SS_replace_correlation_vars(((Iter *) expr)->iterexpr);
-       }
-       else if (single_node(expr))
-               return expr;
-       else if (or_clause(expr) || and_clause(expr) || is_opclause(expr) ||
-                        not_clause(expr) || is_funcclause(expr))
-               ((Expr *) expr)->args = (List *)
-                       SS_replace_correlation_vars((Node *) ((Expr *) expr)->args);
-       else if (IsA(expr, Aggreg))
-               ((Aggreg *) expr)->target =
-                       SS_replace_correlation_vars((Node *) ((Aggreg *) expr)->target);
-       else if (IsA(expr, ArrayRef))
-       {
-               ((ArrayRef *) expr)->refupperindexpr = (List *)
-                       SS_replace_correlation_vars((Node *) ((ArrayRef *) expr)->refupperindexpr);
-               ((ArrayRef *) expr)->reflowerindexpr = (List *)
-                       SS_replace_correlation_vars((Node *) ((ArrayRef *) expr)->reflowerindexpr);
-               ((ArrayRef *) expr)->refexpr =
-                       SS_replace_correlation_vars((Node *) ((ArrayRef *) expr)->refexpr);
-               ((ArrayRef *) expr)->refassgnexpr =
-                       SS_replace_correlation_vars(((ArrayRef *) expr)->refassgnexpr);
-       }
-       else if (IsA(expr, TargetEntry))
-               ((TargetEntry *) expr)->expr =
-                       SS_replace_correlation_vars((Node *) ((TargetEntry *) expr)->expr);
-       else if (IsA(expr, SubLink))
-       {
-               List       *le;
-
-               foreach(le, ((SubLink *) expr)->oper)   /* left sides only */
-               {
-                       List       *oparg = ((Expr *) lfirst(le))->args;
+       return expression_tree_mutator(node,
+                                                                  replace_correlation_vars_mutator,
+                                                                  context);
+}
 
-                       lfirst(oparg) = (List *)
-                               SS_replace_correlation_vars((Node *) lfirst(oparg));
-               }
-               ((SubLink *) expr)->lefthand = (List *)
-                       SS_replace_correlation_vars((Node *) ((SubLink *) expr)->lefthand);
-       }
-       else
-               elog(NOTICE, "SS_replace_correlation_vars: can't handle node %d",
-                        nodeTag(expr));
+/*
+ * Expand SubLinks to SubPlans in the given expression.
+ */
 
-       return expr;
-}
+static Node *process_sublinks_mutator(Node *node, void *context);
 
 Node *
 SS_process_sublinks(Node *expr)
 {
-       if (expr == NULL)
+       /* No setup needed for tree walk, so away we go */
+       return process_sublinks_mutator(expr, NULL);
+}
+
+static Node *
+process_sublinks_mutator(Node *node, void *context)
+{
+       if (node == NULL)
                return NULL;
-       if (IsA(expr, List))
+       if (IsA(node, SubLink))
        {
-               List       *le;
+               SubLink    *sublink = (SubLink *) node;
 
-               foreach(le, (List *) expr)
-                       lfirst(le) = SS_process_sublinks((Node *) lfirst(le));
-       }
-       else if (or_clause(expr) || and_clause(expr) || is_opclause(expr) ||
-                        not_clause(expr) || is_funcclause(expr))
-               ((Expr *) expr)->args = (List *)
-                       SS_process_sublinks((Node *) ((Expr *) expr)->args);
-       else if (IsA(expr, SubLink))/* got it! */
-       {
-               expr = _make_subplan((SubLink *) expr);
+               /*
+                * First, scan the lefthand-side expressions, if any. This is a
+                * tad klugy since we modify the input SubLink node, but that
+                * should be OK (make_subplan does it too!)
+                */
+               sublink->lefthand = (List *)
+                       process_sublinks_mutator((Node *) sublink->lefthand, context);
+               /* Now build the SubPlan node and make the expr to return */
+               return make_subplan(sublink);
        }
 
-       return expr;
+       /*
+        * Note that we will never see a SubPlan expression in the input
+        * (since this is the very routine that creates 'em to begin with). So
+        * the code in expression_tree_mutator() that might do inappropriate
+        * things with SubPlans or SubLinks will not be exercised.
+        */
+       Assert(!is_subplan(node));
+
+       return expression_tree_mutator(node,
+                                                                  process_sublinks_mutator,
+                                                                  context);
 }
 
 List *
 SS_finalize_plan(Plan *plan)
 {
-       List       *extParam = NULL;
-       List       *locParam = NULL;
-       List       *subPlan = NULL;
-       List       *param_list;
+       List       *extParam = NIL;
+       List       *locParam = NIL;
+       finalize_primnode_results results;
        List       *lst;
 
        if (plan == NULL)
-               return NULL;
+               return NIL;
+
+       results.subplans = NIL;         /* initialize lists to NIL */
+       results.paramids = NIL;
+
+       /*
+        * When we call finalize_primnode, results.paramids lists are
+        * automatically merged together.  But when recursing to self, we have
+        * to do it the hard way.  We want the paramids list to include params
+        * in subplans as well as at this level. (We don't care about finding
+        * subplans of subplans, though.)
+        */
 
-       param_list = _finalize_primnode(plan->targetlist, &subPlan);
-       Assert(subPlan == NULL);
+       /* Find params and subplans in targetlist and qual */
+       finalize_primnode((Node *) plan->targetlist, &results);
+       finalize_primnode((Node *) plan->qual, &results);
 
+       /* Check additional node-type-specific fields */
        switch (nodeTag(plan))
        {
                case T_Result:
-                       param_list = set_unioni(param_list,
-                                                                       _finalize_primnode(((Result *) plan)->resconstantqual, &subPlan));
+                       finalize_primnode(((Result *) plan)->resconstantqual,
+                                                         &results);
                        break;
 
                case T_Append:
                        foreach(lst, ((Append *) plan)->appendplans)
-                               param_list = set_unioni(param_list,
+                               results.paramids = set_unioni(results.paramids,
                                                                 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:
-                       param_list = set_unioni(param_list,
-                       _finalize_primnode(((IndexScan *) plan)->indxqual, &subPlan));
-                       Assert(subPlan == NULL);
+                       finalize_primnode((Node *) ((IndexScan *) plan)->indxqual,
+                                                         &results);
+
+                       /*
+                        * we need not look at indxqualorig, since it will have the
+                        * same param references as indxqual, and we aren't really
+                        * concerned yet about having a complete subplan list.
+                        */
+                       break;
+
+               case T_NestLoop:
+                       finalize_primnode((Node *) ((Join *) plan)->joinqual,
+                                                         &results);
                        break;
 
                case T_MergeJoin:
-                       param_list = set_unioni(param_list,
-                                                                       _finalize_primnode(((MergeJoin *) plan)->mergeclauses, &subPlan));
-                       Assert(subPlan == NULL);
+                       finalize_primnode((Node *) ((Join *) plan)->joinqual,
+                                                         &results);
+                       finalize_primnode((Node *) ((MergeJoin *) plan)->mergeclauses,
+                                                         &results);
                        break;
 
                case T_HashJoin:
-                       param_list = set_unioni(param_list,
-                                                                       _finalize_primnode(((HashJoin *) plan)->hashclauses, &subPlan));
-                       Assert(subPlan == NULL);
+                       finalize_primnode((Node *) ((Join *) plan)->joinqual,
+                                                         &results);
+                       finalize_primnode((Node *) ((HashJoin *) plan)->hashclauses,
+                                                         &results);
                        break;
 
                case T_Hash:
-                       param_list = set_unioni(param_list,
-                                _finalize_primnode(((Hash *) plan)->hashkey, &subPlan));
-                       Assert(subPlan == NULL);
+                       finalize_primnode(((Hash *) plan)->hashkey,
+                                                         &results);
                        break;
 
-               case T_Agg:
-                       param_list = set_unioni(param_list,
-                                        _finalize_primnode(((Agg *) plan)->aggs, &subPlan));
-                       Assert(subPlan == NULL);
+               case T_TidScan:
+                       finalize_primnode((Node *) ((TidScan *) plan)->tideval,
+                                                         &results);
                        break;
 
+               case T_Agg:
                case T_SeqScan:
-               case T_NestLoop:
                case T_Material:
                case T_Sort:
                case T_Unique:
+               case T_SetOp:
+               case T_Limit:
                case T_Group:
                        break;
+
                default:
-                       elog(ERROR, "SS_finalize_plan: node %d unsupported", nodeTag(plan));
-                       return NULL;
+                       elog(ERROR, "SS_finalize_plan: node %d unsupported",
+                                nodeTag(plan));
        }
 
-       param_list = set_unioni(param_list, _finalize_primnode(plan->qual, &subPlan));
-       param_list = set_unioni(param_list, SS_finalize_plan(plan->lefttree));
-       param_list = set_unioni(param_list, SS_finalize_plan(plan->righttree));
+       /* Process left and right subplans, if any */
+       results.paramids = set_unioni(results.paramids,
+                                                                 SS_finalize_plan(plan->lefttree));
+       results.paramids = set_unioni(results.paramids,
+                                                                 SS_finalize_plan(plan->righttree));
+
+       /* Now we have all the paramids and subplans */
 
-       foreach(lst, param_list)
+       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' variable");
+                       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);
                }
        }
 
        plan->extParam = extParam;
        plan->locParam = locParam;
-       plan->subPlan = subPlan;
-
-       return param_list;
-
-}
-
-List      *SS_pull_subplan(void *expr);
-
-List *
-SS_pull_subplan(void *expr)
-{
-       List       *result = NULL;
-
-       if (expr == NULL || single_node(expr))
-               return NULL;
+       plan->subPlan = results.subplans;
 
-       if (IsA(expr, List))
-       {
-               List       *le;
-
-               foreach(le, (List *) expr)
-                       result = nconc(result, SS_pull_subplan(lfirst(le)));
-       }
-       else if (IsA(expr, Iter))
-               return SS_pull_subplan(((Iter *) expr)->iterexpr);
-       else if (or_clause(expr) || and_clause(expr) || is_opclause(expr) ||
-                        not_clause(expr) || is_funcclause(expr))
-               return SS_pull_subplan(((Expr *) expr)->args);
-       else if (IsA(expr, Aggreg))
-               return SS_pull_subplan(((Aggreg *) expr)->target);
-       else if (IsA(expr, ArrayRef))
-       {
-               result = SS_pull_subplan(((ArrayRef *) expr)->refupperindexpr);
-               result = nconc(result,
-                                 SS_pull_subplan(((ArrayRef *) expr)->reflowerindexpr));
-               result = nconc(result,
-                                          SS_pull_subplan(((ArrayRef *) expr)->refexpr));
-               result = nconc(result,
-                                        SS_pull_subplan(((ArrayRef *) expr)->refassgnexpr));
-       }
-       else if (IsA(expr, TargetEntry))
-               return SS_pull_subplan(((TargetEntry *) expr)->expr);
-       else if (is_subplan(expr))
-               return lcons(((Expr *) expr)->oper, NULL);
-       else
-               elog(ERROR, "SS_pull_subplan: can't handle node %d",
-                        nodeTag(expr));
-
-       return result;
+       return results.paramids;
 }