Move resolution of AlternativeSubPlan choices to the planner.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 27 Sep 2020 16:51:28 +0000 (12:51 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 27 Sep 2020 16:51:28 +0000 (12:51 -0400)
When commit bd3daddaf introduced AlternativeSubPlans, I had some
ambitions towards allowing the choice of subplan to change during
execution.  That has not happened, or even been thought about, in the
ensuing twelve years; so it seems like a failed experiment.  So let's
rip that out and resolve the choice of subplan at the end of planning
(in setrefs.c) rather than during executor startup.  This has a number
of positive benefits:

* Removal of a few hundred lines of executor code, since
AlternativeSubPlans need no longer be supported there.

* Removal of executor-startup overhead (particularly, initialization
of subplans that won't be used).

* Removal of incidental costs of having a larger plan tree, such as
tree-scanning and copying costs in the plancache; not to mention
setrefs.c's own costs of processing the discarded subplans.

* EXPLAIN no longer has to print a weird (and undocumented)
representation of an AlternativeSubPlan choice; it sees only the
subplan actually used.  This should mean less confusion for users.

* Since setrefs.c knows which subexpression of a plan node it's
working on at any instant, it's possible to adjust the estimated
number of executions of the subplan based on that.  For example,
we should usually estimate more executions of a qual expression
than a targetlist expression.  The implementation used here is
pretty simplistic, because we don't want to expend a lot of cycles
on the issue; but it's better than ignoring the point entirely,
as the executor had to.

That last point might possibly result in shifting the choice
between hashed and non-hashed EXISTS subplans in a few cases,
but in general this patch isn't meant to change planner choices.
Since we're doing the resolution so late, it's really impossible
to change any plan choices outside the AlternativeSubPlan itself.

Patch by me; thanks to David Rowley for review.

Discussion: https://postgr.es/m/1992952.1592785225@sss.pgh.pa.us

20 files changed:
src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/backend/executor/nodeSubplan.c
src/backend/jit/llvm/llvmjit_expr.c
src/backend/jit/llvm/llvmjit_types.c
src/backend/nodes/outfuncs.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/utils/adt/ruleutils.c
src/include/executor/execExpr.h
src/include/executor/nodeSubplan.h
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/pathnodes.h
src/include/nodes/primnodes.h
src/test/regress/expected/insert_conflict.out
src/test/regress/expected/subselect.out
src/test/regress/expected/updatable_views.out
src/test/regress/sql/subselect.sql

index 236413f62aaff87a0be6dc19949184e54db79f71..868f8b0858f87d1f47c0a89fa27024f1097cde9c 100644 (file)
@@ -1104,23 +1104,6 @@ ExecInitExprRec(Expr *node, ExprState *state,
                break;
            }
 
-       case T_AlternativeSubPlan:
-           {
-               AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
-               AlternativeSubPlanState *asstate;
-
-               if (!state->parent)
-                   elog(ERROR, "AlternativeSubPlan found with no parent plan");
-
-               asstate = ExecInitAlternativeSubPlan(asplan, state->parent);
-
-               scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN;
-               scratch.d.alternative_subplan.asstate = asstate;
-
-               ExprEvalPushStep(state, &scratch);
-               break;
-           }
-
        case T_FieldSelect:
            {
                FieldSelect *fselect = (FieldSelect *) node;
index b812bbaceef8c0942863e134590fc68d6ab08286..26c2b4963215e2a0e46e9db8b0620877ba14e444 100644 (file)
@@ -431,7 +431,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
        &&CASE_EEOP_GROUPING_FUNC,
        &&CASE_EEOP_WINDOW_FUNC,
        &&CASE_EEOP_SUBPLAN,
-       &&CASE_EEOP_ALTERNATIVE_SUBPLAN,
        &&CASE_EEOP_AGG_STRICT_DESERIALIZE,
        &&CASE_EEOP_AGG_DESERIALIZE,
        &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1536,14 +1535,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
            EEO_NEXT();
        }
 
-       EEO_CASE(EEOP_ALTERNATIVE_SUBPLAN)
-       {
-           /* too complex for an inline implementation */
-           ExecEvalAlternativeSubPlan(state, op, econtext);
-
-           EEO_NEXT();
-       }
-
        /* evaluate a strict aggregate deserialization function */
        EEO_CASE(EEOP_AGG_STRICT_DESERIALIZE)
        {
@@ -3868,20 +3859,6 @@ ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
    *op->resvalue = ExecSubPlan(sstate, econtext, op->resnull);
 }
 
-/*
- * Hand off evaluation of an alternative subplan to nodeSubplan.c
- */
-void
-ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
-{
-   AlternativeSubPlanState *asstate = op->d.alternative_subplan.asstate;
-
-   /* could potentially be nested, so make sure there's enough stack */
-   check_stack_depth();
-
-   *op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull);
-}
-
 /*
  * Evaluate a wholerow Var expression.
  *
index 9a7962518ee69d38fac3c9c67757f0215eb7cda2..9a706df5f061df8aa090f3fa14676a2445b5c7b6 100644 (file)
@@ -1303,83 +1303,3 @@ ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent)
        parent->chgParam = bms_add_member(parent->chgParam, paramid);
    }
 }
-
-
-/*
- * ExecInitAlternativeSubPlan
- *
- * Initialize for execution of one of a set of alternative subplans.
- */
-AlternativeSubPlanState *
-ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent)
-{
-   AlternativeSubPlanState *asstate = makeNode(AlternativeSubPlanState);
-   double      num_calls;
-   SubPlan    *subplan1;
-   SubPlan    *subplan2;
-   Cost        cost1;
-   Cost        cost2;
-   ListCell   *lc;
-
-   asstate->subplan = asplan;
-
-   /*
-    * Initialize subplans.  (Can we get away with only initializing the one
-    * we're going to use?)
-    */
-   foreach(lc, asplan->subplans)
-   {
-       SubPlan    *sp = lfirst_node(SubPlan, lc);
-       SubPlanState *sps = ExecInitSubPlan(sp, parent);
-
-       asstate->subplans = lappend(asstate->subplans, sps);
-       parent->subPlan = lappend(parent->subPlan, sps);
-   }
-
-   /*
-    * Select the one to be used.  For this, we need an estimate of the number
-    * of executions of the subplan.  We use the number of output rows
-    * expected from the parent plan node.  This is a good estimate if we are
-    * in the parent's targetlist, and an underestimate (but probably not by
-    * more than a factor of 2) if we are in the qual.
-    */
-   num_calls = parent->plan->plan_rows;
-
-   /*
-    * The planner saved enough info so that we don't have to work very hard
-    * to estimate the total cost, given the number-of-calls estimate.
-    */
-   Assert(list_length(asplan->subplans) == 2);
-   subplan1 = (SubPlan *) linitial(asplan->subplans);
-   subplan2 = (SubPlan *) lsecond(asplan->subplans);
-
-   cost1 = subplan1->startup_cost + num_calls * subplan1->per_call_cost;
-   cost2 = subplan2->startup_cost + num_calls * subplan2->per_call_cost;
-
-   if (cost1 < cost2)
-       asstate->active = 0;
-   else
-       asstate->active = 1;
-
-   return asstate;
-}
-
-/*
- * ExecAlternativeSubPlan
- *
- * Execute one of a set of alternative subplans.
- *
- * Note: in future we might consider changing to different subplans on the
- * fly, in case the original rowcount estimate turns out to be way off.
- */
-Datum
-ExecAlternativeSubPlan(AlternativeSubPlanState *node,
-                      ExprContext *econtext,
-                      bool *isNull)
-{
-   /* Just pass control to the active subplan */
-   SubPlanState *activesp = list_nth_node(SubPlanState,
-                                          node->subplans, node->active);
-
-   return ExecSubPlan(activesp, econtext, isNull);
-}
index cca5c117a0eeffe676fede4daafc28f9ca81c83e..eb1dea658cb258892739bb8e5c973d7833283d50 100644 (file)
@@ -1918,12 +1918,6 @@ llvm_compile_expr(ExprState *state)
                LLVMBuildBr(b, opblocks[opno + 1]);
                break;
 
-           case EEOP_ALTERNATIVE_SUBPLAN:
-               build_EvalXFunc(b, mod, "ExecEvalAlternativeSubPlan",
-                               v_state, op, v_econtext);
-               LLVMBuildBr(b, opblocks[opno + 1]);
-               break;
-
            case EEOP_AGG_STRICT_DESERIALIZE:
            case EEOP_AGG_DESERIALIZE:
                {
index 0a93d5f6658cf1429137454a32f9c766105db51e..1ed3cafa2f23bf81ca804e8c14815eb7d5cd7c4a 100644 (file)
@@ -102,7 +102,6 @@ void       *referenced_functions[] =
    ExecAggTransReparent,
    ExecEvalAggOrderedTransDatum,
    ExecEvalAggOrderedTransTuple,
-   ExecEvalAlternativeSubPlan,
    ExecEvalArrayCoerce,
    ExecEvalArrayExpr,
    ExecEvalConstraintCheck,
index e2f177515dac3ea6220c8a18e1f7089ca6705d38..f0386480ab890c6ed24dcd3b442a3cc50c9ca1f7 100644 (file)
@@ -2254,6 +2254,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
    WRITE_BOOL_FIELD(hasLateralRTEs);
    WRITE_BOOL_FIELD(hasHavingQual);
    WRITE_BOOL_FIELD(hasPseudoConstantQuals);
+   WRITE_BOOL_FIELD(hasAlternativeSubPlans);
    WRITE_BOOL_FIELD(hasRecursion);
    WRITE_INT_FIELD(wt_param_id);
    WRITE_BITMAPSET_FIELD(curOuterRels);
index 8007e205ed77c072f5bde9fe821a94e3b7d84fc5..3e2b4965c4a8ef5dd53171b8fa6ef13fea1799c4 100644 (file)
@@ -629,6 +629,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
    root->minmax_aggs = NIL;
    root->qual_security_level = 0;
    root->inhTargetKind = INHKIND_NONE;
+   root->hasPseudoConstantQuals = false;
+   root->hasAlternativeSubPlans = false;
    root->hasRecursion = hasRecursion;
    if (hasRecursion)
        root->wt_param_id = assign_special_exec_param(root);
@@ -759,9 +761,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
     */
    root->hasHavingQual = (parse->havingQual != NULL);
 
-   /* Clear this flag; might get set in distribute_qual_to_rels */
-   root->hasPseudoConstantQuals = false;
-
    /*
     * Do expression preprocessing on targetlist and quals, as well as other
     * random expressions in the querytree.  Note that we do not need to
index baefe0e946173a5c57b0db932ab7add3c7cbd4da..dd8e2e966ddd4373671b304b8074bafe31231733 100644 (file)
@@ -49,6 +49,7 @@ typedef struct
 {
    PlannerInfo *root;
    int         rtoffset;
+   double      num_exec;
 } fix_scan_expr_context;
 
 typedef struct
@@ -58,6 +59,7 @@ typedef struct
    indexed_tlist *inner_itlist;
    Index       acceptable_rel;
    int         rtoffset;
+   double      num_exec;
 } fix_join_expr_context;
 
 typedef struct
@@ -66,8 +68,28 @@ typedef struct
    indexed_tlist *subplan_itlist;
    Index       newvarno;
    int         rtoffset;
+   double      num_exec;
 } fix_upper_expr_context;
 
+/*
+ * Selecting the best alternative in an AlternativeSubPlan expression requires
+ * estimating how many times that expression will be evaluated.  For an
+ * expression in a plan node's targetlist, the plan's estimated number of
+ * output rows is clearly what to use, but for an expression in a qual it's
+ * far less clear.  Since AlternativeSubPlans aren't heavily used, we don't
+ * want to expend a lot of cycles making such estimates.  What we use is twice
+ * the number of output rows.  That's not entirely unfounded: we know that
+ * clause_selectivity() would fall back to a default selectivity estimate
+ * of 0.5 for any SubPlan, so if the qual containing the SubPlan is the last
+ * to be applied (which it likely would be, thanks to order_qual_clauses()),
+ * this matches what we could have estimated in a far more laborious fashion.
+ * Obviously there are many other scenarios, but it's probably not worth the
+ * trouble to try to improve on this estimate, especially not when we don't
+ * have a better estimate for the selectivity of the SubPlan qual itself.
+ */
+#define NUM_EXEC_TLIST(parentplan)  ((parentplan)->plan_rows)
+#define NUM_EXEC_QUAL(parentplan)   ((parentplan)->plan_rows * 2.0)
+
 /*
  * Check if a Const node is a regclass value.  We accept plain OID too,
  * since a regclass Const will get folded to that type if it's an argument
@@ -79,8 +101,8 @@ typedef struct
    (((con)->consttype == REGCLASSOID || (con)->consttype == OIDOID) && \
     !(con)->constisnull)
 
-#define fix_scan_list(root, lst, rtoffset) \
-   ((List *) fix_scan_expr(root, (Node *) (lst), rtoffset))
+#define fix_scan_list(root, lst, rtoffset, num_exec) \
+   ((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))
 
 static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
 static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
@@ -109,7 +131,8 @@ static Plan *set_mergeappend_references(PlannerInfo *root,
                                        int rtoffset);
 static void set_hash_references(PlannerInfo *root, Plan *plan, int rtoffset);
 static Relids offset_relid_set(Relids relids, int rtoffset);
-static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
+static Node *fix_scan_expr(PlannerInfo *root, Node *node,
+                          int rtoffset, double num_exec);
 static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
 static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
 static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
@@ -133,14 +156,15 @@ static List *fix_join_expr(PlannerInfo *root,
                           List *clauses,
                           indexed_tlist *outer_itlist,
                           indexed_tlist *inner_itlist,
-                          Index acceptable_rel, int rtoffset);
+                          Index acceptable_rel,
+                          int rtoffset, double num_exec);
 static Node *fix_join_expr_mutator(Node *node,
                                   fix_join_expr_context *context);
 static Node *fix_upper_expr(PlannerInfo *root,
                            Node *node,
                            indexed_tlist *subplan_itlist,
                            Index newvarno,
-                           int rtoffset);
+                           int rtoffset, double num_exec);
 static Node *fix_upper_expr_mutator(Node *node,
                                    fix_upper_expr_context *context);
 static List *set_returning_clause_references(PlannerInfo *root,
@@ -177,17 +201,20 @@ static List *set_returning_clause_references(PlannerInfo *root,
  * 5. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
  * now that we have finished planning all MULTIEXPR subplans.
  *
- * 6. We compute regproc OIDs for operators (ie, we look up the function
+ * 6. AlternativeSubPlan expressions are replaced by just one of their
+ * alternatives, using an estimate of how many times they'll be executed.
+ *
+ * 7. We compute regproc OIDs for operators (ie, we look up the function
  * that implements each op).
  *
- * 7. We create lists of specific objects that the plan depends on.
+ * 8. We create lists of specific objects that the plan depends on.
  * This will be used by plancache.c to drive invalidation of cached plans.
  * Relation dependencies are represented by OIDs, and everything else by
  * PlanInvalItems (this distinction is motivated by the shared-inval APIs).
  * Currently, relations, user-defined functions, and domains are the only
  * types of objects that are explicitly tracked this way.
  *
- * 8. We assign every plan node in the tree a unique ID.
+ * 9. We assign every plan node in the tree a unique ID.
  *
  * We also perform one final optimization step, which is to delete
  * SubqueryScan, Append, and MergeAppend plan nodes that aren't doing
@@ -490,9 +517,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scanrelid += rtoffset;
                splan->plan.targetlist =
-                   fix_scan_list(root, splan->plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->plan.qual =
-                   fix_scan_list(root, splan->plan.qual, rtoffset);
+                   fix_scan_list(root, splan->plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
            }
            break;
        case T_SampleScan:
@@ -501,11 +530,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
                splan->tablesample = (TableSampleClause *)
-                   fix_scan_expr(root, (Node *) splan->tablesample, rtoffset);
+                   fix_scan_expr(root, (Node *) splan->tablesample,
+                                 rtoffset, 1);
            }
            break;
        case T_IndexScan:
@@ -514,17 +546,23 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
                splan->indexqual =
-                   fix_scan_list(root, splan->indexqual, rtoffset);
+                   fix_scan_list(root, splan->indexqual,
+                                 rtoffset, 1);
                splan->indexqualorig =
-                   fix_scan_list(root, splan->indexqualorig, rtoffset);
+                   fix_scan_list(root, splan->indexqualorig,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
                splan->indexorderby =
-                   fix_scan_list(root, splan->indexorderby, rtoffset);
+                   fix_scan_list(root, splan->indexorderby,
+                                 rtoffset, 1);
                splan->indexorderbyorig =
-                   fix_scan_list(root, splan->indexorderbyorig, rtoffset);
+                   fix_scan_list(root, splan->indexorderbyorig,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
            }
            break;
        case T_IndexOnlyScan:
@@ -543,9 +581,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
                Assert(splan->scan.plan.targetlist == NIL);
                Assert(splan->scan.plan.qual == NIL);
                splan->indexqual =
-                   fix_scan_list(root, splan->indexqual, rtoffset);
+                   fix_scan_list(root, splan->indexqual, rtoffset, 1);
                splan->indexqualorig =
-                   fix_scan_list(root, splan->indexqualorig, rtoffset);
+                   fix_scan_list(root, splan->indexqualorig,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
            }
            break;
        case T_BitmapHeapScan:
@@ -554,11 +593,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
                splan->bitmapqualorig =
-                   fix_scan_list(root, splan->bitmapqualorig, rtoffset);
+                   fix_scan_list(root, splan->bitmapqualorig,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
            }
            break;
        case T_TidScan:
@@ -567,11 +609,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
                splan->tidquals =
-                   fix_scan_list(root, splan->tidquals, rtoffset);
+                   fix_scan_list(root, splan->tidquals,
+                                 rtoffset, 1);
            }
            break;
        case T_SubqueryScan:
@@ -585,11 +630,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
                splan->functions =
-                   fix_scan_list(root, splan->functions, rtoffset);
+                   fix_scan_list(root, splan->functions, rtoffset, 1);
            }
            break;
        case T_TableFuncScan:
@@ -598,11 +645,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
                splan->tablefunc = (TableFunc *)
-                   fix_scan_expr(root, (Node *) splan->tablefunc, rtoffset);
+                   fix_scan_expr(root, (Node *) splan->tablefunc,
+                                 rtoffset, 1);
            }
            break;
        case T_ValuesScan:
@@ -611,11 +661,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
                splan->values_lists =
-                   fix_scan_list(root, splan->values_lists, rtoffset);
+                   fix_scan_list(root, splan->values_lists,
+                                 rtoffset, 1);
            }
            break;
        case T_CteScan:
@@ -624,9 +677,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
            }
            break;
        case T_NamedTuplestoreScan:
@@ -635,9 +690,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
            }
            break;
        case T_WorkTableScan:
@@ -646,9 +703,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 
                splan->scan.scanrelid += rtoffset;
                splan->scan.plan.targetlist =
-                   fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.targetlist,
+                                 rtoffset, NUM_EXEC_TLIST(plan));
                splan->scan.plan.qual =
-                   fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                   fix_scan_list(root, splan->scan.plan.qual,
+                                 rtoffset, NUM_EXEC_QUAL(plan));
            }
            break;
        case T_ForeignScan:
@@ -732,9 +791,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
                Assert(splan->plan.qual == NIL);
 
                splan->limitOffset =
-                   fix_scan_expr(root, splan->limitOffset, rtoffset);
+                   fix_scan_expr(root, splan->limitOffset, rtoffset, 1);
                splan->limitCount =
-                   fix_scan_expr(root, splan->limitCount, rtoffset);
+                   fix_scan_expr(root, splan->limitCount, rtoffset, 1);
            }
            break;
        case T_Agg:
@@ -775,9 +834,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
                 * variable refs, so fix_scan_expr works for them.
                 */
                wplan->startOffset =
-                   fix_scan_expr(root, wplan->startOffset, rtoffset);
+                   fix_scan_expr(root, wplan->startOffset, rtoffset, 1);
                wplan->endOffset =
-                   fix_scan_expr(root, wplan->endOffset, rtoffset);
+                   fix_scan_expr(root, wplan->endOffset, rtoffset, 1);
            }
            break;
        case T_Result:
@@ -793,13 +852,15 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
                else
                {
                    splan->plan.targetlist =
-                       fix_scan_list(root, splan->plan.targetlist, rtoffset);
+                       fix_scan_list(root, splan->plan.targetlist,
+                                     rtoffset, NUM_EXEC_TLIST(plan));
                    splan->plan.qual =
-                       fix_scan_list(root, splan->plan.qual, rtoffset);
+                       fix_scan_list(root, splan->plan.qual,
+                                     rtoffset, NUM_EXEC_QUAL(plan));
                }
                /* resconstantqual can't contain any subplan variable refs */
                splan->resconstantqual =
-                   fix_scan_expr(root, splan->resconstantqual, rtoffset);
+                   fix_scan_expr(root, splan->resconstantqual, rtoffset, 1);
            }
            break;
        case T_ProjectSet:
@@ -813,7 +874,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
                Assert(splan->plan.qual == NIL);
 
                splan->withCheckOptionLists =
-                   fix_scan_list(root, splan->withCheckOptionLists, rtoffset);
+                   fix_scan_list(root, splan->withCheckOptionLists,
+                                 rtoffset, 1);
 
                if (splan->returningLists)
                {
@@ -874,18 +936,18 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
                        fix_join_expr(root, splan->onConflictSet,
                                      NULL, itlist,
                                      linitial_int(splan->resultRelations),
-                                     rtoffset);
+                                     rtoffset, NUM_EXEC_QUAL(plan));
 
                    splan->onConflictWhere = (Node *)
                        fix_join_expr(root, (List *) splan->onConflictWhere,
                                      NULL, itlist,
                                      linitial_int(splan->resultRelations),
-                                     rtoffset);
+                                     rtoffset, NUM_EXEC_QUAL(plan));
 
                    pfree(itlist);
 
                    splan->exclRelTlist =
-                       fix_scan_list(root, splan->exclRelTlist, rtoffset);
+                       fix_scan_list(root, splan->exclRelTlist, rtoffset, 1);
                }
 
                splan->nominalRelation += rtoffset;
@@ -1026,19 +1088,24 @@ set_indexonlyscan_references(PlannerInfo *root,
                       (Node *) plan->scan.plan.targetlist,
                       index_itlist,
                       INDEX_VAR,
-                      rtoffset);
+                      rtoffset,
+                      NUM_EXEC_TLIST((Plan *) plan));
    plan->scan.plan.qual = (List *)
        fix_upper_expr(root,
                       (Node *) plan->scan.plan.qual,
                       index_itlist,
                       INDEX_VAR,
-                      rtoffset);
+                      rtoffset,
+                      NUM_EXEC_QUAL((Plan *) plan));
    /* indexqual is already transformed to reference index columns */
-   plan->indexqual = fix_scan_list(root, plan->indexqual, rtoffset);
+   plan->indexqual = fix_scan_list(root, plan->indexqual,
+                                   rtoffset, 1);
    /* indexorderby is already transformed to reference index columns */
-   plan->indexorderby = fix_scan_list(root, plan->indexorderby, rtoffset);
+   plan->indexorderby = fix_scan_list(root, plan->indexorderby,
+                                      rtoffset, 1);
    /* indextlist must NOT be transformed to reference index columns */
-   plan->indextlist = fix_scan_list(root, plan->indextlist, rtoffset);
+   plan->indextlist = fix_scan_list(root, plan->indextlist,
+                                    rtoffset, NUM_EXEC_TLIST((Plan *) plan));
 
    pfree(index_itlist);
 
@@ -1084,9 +1151,11 @@ set_subqueryscan_references(PlannerInfo *root,
         */
        plan->scan.scanrelid += rtoffset;
        plan->scan.plan.targetlist =
-           fix_scan_list(root, plan->scan.plan.targetlist, rtoffset);
+           fix_scan_list(root, plan->scan.plan.targetlist,
+                         rtoffset, NUM_EXEC_TLIST((Plan *) plan));
        plan->scan.plan.qual =
-           fix_scan_list(root, plan->scan.plan.qual, rtoffset);
+           fix_scan_list(root, plan->scan.plan.qual,
+                         rtoffset, NUM_EXEC_QUAL((Plan *) plan));
 
        result = (Plan *) plan;
    }
@@ -1202,29 +1271,34 @@ set_foreignscan_references(PlannerInfo *root,
                           (Node *) fscan->scan.plan.targetlist,
                           itlist,
                           INDEX_VAR,
-                          rtoffset);
+                          rtoffset,
+                          NUM_EXEC_TLIST((Plan *) fscan));
        fscan->scan.plan.qual = (List *)
            fix_upper_expr(root,
                           (Node *) fscan->scan.plan.qual,
                           itlist,
                           INDEX_VAR,
-                          rtoffset);
+                          rtoffset,
+                          NUM_EXEC_QUAL((Plan *) fscan));
        fscan->fdw_exprs = (List *)
            fix_upper_expr(root,
                           (Node *) fscan->fdw_exprs,
                           itlist,
                           INDEX_VAR,
-                          rtoffset);
+                          rtoffset,
+                          NUM_EXEC_QUAL((Plan *) fscan));
        fscan->fdw_recheck_quals = (List *)
            fix_upper_expr(root,
                           (Node *) fscan->fdw_recheck_quals,
                           itlist,
                           INDEX_VAR,
-                          rtoffset);
+                          rtoffset,
+                          NUM_EXEC_QUAL((Plan *) fscan));
        pfree(itlist);
        /* fdw_scan_tlist itself just needs fix_scan_list() adjustments */
        fscan->fdw_scan_tlist =
-           fix_scan_list(root, fscan->fdw_scan_tlist, rtoffset);
+           fix_scan_list(root, fscan->fdw_scan_tlist,
+                         rtoffset, NUM_EXEC_TLIST((Plan *) fscan));
    }
    else
    {
@@ -1233,13 +1307,17 @@ set_foreignscan_references(PlannerInfo *root,
         * way
         */
        fscan->scan.plan.targetlist =
-           fix_scan_list(root, fscan->scan.plan.targetlist, rtoffset);
+           fix_scan_list(root, fscan->scan.plan.targetlist,
+                         rtoffset, NUM_EXEC_TLIST((Plan *) fscan));
        fscan->scan.plan.qual =
-           fix_scan_list(root, fscan->scan.plan.qual, rtoffset);
+           fix_scan_list(root, fscan->scan.plan.qual,
+                         rtoffset, NUM_EXEC_QUAL((Plan *) fscan));
        fscan->fdw_exprs =
-           fix_scan_list(root, fscan->fdw_exprs, rtoffset);
+           fix_scan_list(root, fscan->fdw_exprs,
+                         rtoffset, NUM_EXEC_QUAL((Plan *) fscan));
        fscan->fdw_recheck_quals =
-           fix_scan_list(root, fscan->fdw_recheck_quals, rtoffset);
+           fix_scan_list(root, fscan->fdw_recheck_quals,
+                         rtoffset, NUM_EXEC_QUAL((Plan *) fscan));
    }
 
    fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset);
@@ -1270,33 +1348,40 @@ set_customscan_references(PlannerInfo *root,
                           (Node *) cscan->scan.plan.targetlist,
                           itlist,
                           INDEX_VAR,
-                          rtoffset);
+                          rtoffset,
+                          NUM_EXEC_TLIST((Plan *) cscan));
        cscan->scan.plan.qual = (List *)
            fix_upper_expr(root,
                           (Node *) cscan->scan.plan.qual,
                           itlist,
                           INDEX_VAR,
-                          rtoffset);
+                          rtoffset,
+                          NUM_EXEC_QUAL((Plan *) cscan));
        cscan->custom_exprs = (List *)
            fix_upper_expr(root,
                           (Node *) cscan->custom_exprs,
                           itlist,
                           INDEX_VAR,
-                          rtoffset);
+                          rtoffset,
+                          NUM_EXEC_QUAL((Plan *) cscan));
        pfree(itlist);
        /* custom_scan_tlist itself just needs fix_scan_list() adjustments */
        cscan->custom_scan_tlist =
-           fix_scan_list(root, cscan->custom_scan_tlist, rtoffset);
+           fix_scan_list(root, cscan->custom_scan_tlist,
+                         rtoffset, NUM_EXEC_TLIST((Plan *) cscan));
    }
    else
    {
        /* Adjust tlist, qual, custom_exprs in the standard way */
        cscan->scan.plan.targetlist =
-           fix_scan_list(root, cscan->scan.plan.targetlist, rtoffset);
+           fix_scan_list(root, cscan->scan.plan.targetlist,
+                         rtoffset, NUM_EXEC_TLIST((Plan *) cscan));
        cscan->scan.plan.qual =
-           fix_scan_list(root, cscan->scan.plan.qual, rtoffset);
+           fix_scan_list(root, cscan->scan.plan.qual,
+                         rtoffset, NUM_EXEC_QUAL((Plan *) cscan));
        cscan->custom_exprs =
-           fix_scan_list(root, cscan->custom_exprs, rtoffset);
+           fix_scan_list(root, cscan->custom_exprs,
+                         rtoffset, NUM_EXEC_QUAL((Plan *) cscan));
    }
 
    /* Adjust child plan-nodes recursively, if needed */
@@ -1458,7 +1543,8 @@ set_hash_references(PlannerInfo *root, Plan *plan, int rtoffset)
                       (Node *) hplan->hashkeys,
                       outer_itlist,
                       OUTER_VAR,
-                      rtoffset);
+                      rtoffset,
+                      NUM_EXEC_QUAL(plan));
 
    /* Hash doesn't project */
    set_dummy_tlist_references(plan, rtoffset);
@@ -1623,6 +1709,69 @@ fix_param_node(PlannerInfo *root, Param *p)
    return (Node *) copyObject(p);
 }
 
+/*
+ * fix_alternative_subplan
+ *     Do set_plan_references processing on an AlternativeSubPlan
+ *
+ * Choose one of the alternative implementations and return just that one,
+ * discarding the rest of the AlternativeSubPlan structure.
+ * Note: caller must still recurse into the result!
+ *
+ * We don't make any attempt to fix up cost estimates in the parent plan
+ * node or higher-level nodes.  However, we do remove the rejected subplan(s)
+ * from root->glob->subplans, to minimize cycles expended on them later.
+ */
+static Node *
+fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
+                       double num_exec)
+{
+   SubPlan    *bestplan = NULL;
+   Cost        bestcost = 0;
+   ListCell   *lc;
+
+   /*
+    * Compute the estimated cost of each subplan assuming num_exec
+    * executions, and keep the cheapest one.  Replace discarded subplans with
+    * NULL pointers in the global subplans list.  In event of exact equality
+    * of estimates, we prefer the later plan; this is a bit arbitrary, but in
+    * current usage it biases us to break ties against fast-start subplans.
+    */
+   Assert(asplan->subplans != NIL);
+
+   foreach(lc, asplan->subplans)
+   {
+       SubPlan    *curplan = (SubPlan *) lfirst(lc);
+       Cost        curcost;
+
+       curcost = curplan->startup_cost + num_exec * curplan->per_call_cost;
+       if (bestplan == NULL)
+       {
+           bestplan = curplan;
+           bestcost = curcost;
+       }
+       else if (curcost <= bestcost)
+       {
+           /* drop old bestplan */
+           ListCell   *lc2 = list_nth_cell(root->glob->subplans,
+                                           bestplan->plan_id - 1);
+
+           lfirst(lc2) = NULL;
+           bestplan = curplan;
+           bestcost = curcost;
+       }
+       else
+       {
+           /* drop curplan */
+           ListCell   *lc2 = list_nth_cell(root->glob->subplans,
+                                           curplan->plan_id - 1);
+
+           lfirst(lc2) = NULL;
+       }
+   }
+
+   return (Node *) bestplan;
+}
+
 /*
  * fix_scan_expr
  *     Do set_plan_references processing on a scan-level expression
@@ -1630,21 +1779,24 @@ fix_param_node(PlannerInfo *root, Param *p)
  * This consists of incrementing all Vars' varnos by rtoffset,
  * replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars,
  * replacing Aggref nodes that should be replaced by initplan output Params,
+ * choosing the best implementation for AlternativeSubPlans,
  * looking up operator opcode info for OpExpr and related nodes,
  * and adding OIDs from regclass Const nodes into root->glob->relationOids.
  */
 static Node *
-fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
+fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
 {
    fix_scan_expr_context context;
 
    context.root = root;
    context.rtoffset = rtoffset;
+   context.num_exec = num_exec;
 
    if (rtoffset != 0 ||
        root->multiexpr_params != NIL ||
        root->glob->lastPHId != 0 ||
-       root->minmax_aggs != NIL)
+       root->minmax_aggs != NIL ||
+       root->hasAlternativeSubPlans)
    {
        return fix_scan_expr_mutator(node, &context);
    }
@@ -1655,7 +1807,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
         * are no MULTIEXPR subqueries then we don't need to replace
         * PARAM_MULTIEXPR Params, and if there are no placeholders anywhere
         * we won't need to remove them, and if there are no minmax Aggrefs we
-        * won't need to replace them.  Then it's OK to just scribble on the
+        * won't need to replace them, and if there are no AlternativeSubPlans
+        * we won't need to remove them.  Then it's OK to just scribble on the
         * input node tree instead of copying (since the only change, filling
         * in any unset opfuncid fields, is harmless).  This saves just enough
         * cycles to be noticeable on trivial queries.
@@ -1729,6 +1882,11 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
 
        return fix_scan_expr_mutator((Node *) phv->phexpr, context);
    }
+   if (IsA(node, AlternativeSubPlan))
+       return fix_scan_expr_mutator(fix_alternative_subplan(context->root,
+                                                            (AlternativeSubPlan *) node,
+                                                            context->num_exec),
+                                    context);
    fix_expr_common(context->root, node);
    return expression_tree_mutator(node, fix_scan_expr_mutator,
                                   (void *) context);
@@ -1740,6 +1898,7 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
    if (node == NULL)
        return false;
    Assert(!IsA(node, PlaceHolderVar));
+   Assert(!IsA(node, AlternativeSubPlan));
    fix_expr_common(context->root, node);
    return expression_tree_walker(node, fix_scan_expr_walker,
                                  (void *) context);
@@ -1776,7 +1935,8 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
                                   outer_itlist,
                                   inner_itlist,
                                   (Index) 0,
-                                  rtoffset);
+                                  rtoffset,
+                                  NUM_EXEC_QUAL((Plan *) join));
 
    /* Now do join-type-specific stuff */
    if (IsA(join, NestLoop))
@@ -1792,7 +1952,8 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
                                                   (Node *) nlp->paramval,
                                                   outer_itlist,
                                                   OUTER_VAR,
-                                                  rtoffset);
+                                                  rtoffset,
+                                                  NUM_EXEC_TLIST(outer_plan));
            /* Check we replaced any PlaceHolderVar with simple Var */
            if (!(IsA(nlp->paramval, Var) &&
                  nlp->paramval->varno == OUTER_VAR))
@@ -1808,7 +1969,8 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
                                         outer_itlist,
                                         inner_itlist,
                                         (Index) 0,
-                                        rtoffset);
+                                        rtoffset,
+                                        NUM_EXEC_QUAL((Plan *) join));
    }
    else if (IsA(join, HashJoin))
    {
@@ -1819,7 +1981,8 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
                                        outer_itlist,
                                        inner_itlist,
                                        (Index) 0,
-                                       rtoffset);
+                                       rtoffset,
+                                       NUM_EXEC_QUAL((Plan *) join));
 
        /*
         * HashJoin's hashkeys are used to look for matching tuples from its
@@ -1829,7 +1992,8 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
                                               (Node *) hj->hashkeys,
                                               outer_itlist,
                                               OUTER_VAR,
-                                              rtoffset);
+                                              rtoffset,
+                                              NUM_EXEC_QUAL((Plan *) join));
    }
 
    /*
@@ -1867,13 +2031,15 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
                                          outer_itlist,
                                          inner_itlist,
                                          (Index) 0,
-                                         rtoffset);
+                                         rtoffset,
+                                         NUM_EXEC_TLIST((Plan *) join));
    join->plan.qual = fix_join_expr(root,
                                    join->plan.qual,
                                    outer_itlist,
                                    inner_itlist,
                                    (Index) 0,
-                                   rtoffset);
+                                   rtoffset,
+                                   NUM_EXEC_QUAL((Plan *) join));
 
    pfree(outer_itlist);
    pfree(inner_itlist);
@@ -1926,14 +2092,16 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
                                         (Node *) tle->expr,
                                         subplan_itlist,
                                         OUTER_VAR,
-                                        rtoffset);
+                                        rtoffset,
+                                        NUM_EXEC_TLIST(plan));
        }
        else
            newexpr = fix_upper_expr(root,
                                     (Node *) tle->expr,
                                     subplan_itlist,
                                     OUTER_VAR,
-                                    rtoffset);
+                                    rtoffset,
+                                    NUM_EXEC_TLIST(plan));
        tle = flatCopyTargetEntry(tle);
        tle->expr = (Expr *) newexpr;
        output_targetlist = lappend(output_targetlist, tle);
@@ -1945,7 +2113,8 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
                       (Node *) plan->qual,
                       subplan_itlist,
                       OUTER_VAR,
-                      rtoffset);
+                      rtoffset,
+                      NUM_EXEC_QUAL(plan));
 
    pfree(subplan_itlist);
 }
@@ -2389,6 +2558,7 @@ search_indexed_tlist_for_sortgroupref(Expr *node,
  * 'acceptable_rel' is either zero or the rangetable index of a relation
  *     whose Vars may appear in the clause without provoking an error
  * 'rtoffset': how much to increment varnos by
+ * 'num_exec': estimated number of executions of expression
  *
  * Returns the new expression tree.  The original clause structure is
  * not modified.
@@ -2399,7 +2569,8 @@ fix_join_expr(PlannerInfo *root,
              indexed_tlist *outer_itlist,
              indexed_tlist *inner_itlist,
              Index acceptable_rel,
-             int rtoffset)
+             int rtoffset,
+             double num_exec)
 {
    fix_join_expr_context context;
 
@@ -2408,6 +2579,7 @@ fix_join_expr(PlannerInfo *root,
    context.inner_itlist = inner_itlist;
    context.acceptable_rel = acceptable_rel;
    context.rtoffset = rtoffset;
+   context.num_exec = num_exec;
    return (List *) fix_join_expr_mutator((Node *) clauses, &context);
 }
 
@@ -2502,6 +2674,11 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
    /* Special cases (apply only AFTER failing to match to lower tlist) */
    if (IsA(node, Param))
        return fix_param_node(context->root, (Param *) node);
+   if (IsA(node, AlternativeSubPlan))
+       return fix_join_expr_mutator(fix_alternative_subplan(context->root,
+                                                            (AlternativeSubPlan *) node,
+                                                            context->num_exec),
+                                    context);
    fix_expr_common(context->root, node);
    return expression_tree_mutator(node,
                                   fix_join_expr_mutator,
@@ -2533,6 +2710,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
  * 'subplan_itlist': indexed target list for subplan (or index)
  * 'newvarno': varno to use for Vars referencing tlist elements
  * 'rtoffset': how much to increment varnos by
+ * 'num_exec': estimated number of executions of expression
  *
  * The resulting tree is a copy of the original in which all Var nodes have
  * varno = newvarno, varattno = resno of corresponding targetlist element.
@@ -2543,7 +2721,8 @@ fix_upper_expr(PlannerInfo *root,
               Node *node,
               indexed_tlist *subplan_itlist,
               Index newvarno,
-              int rtoffset)
+              int rtoffset,
+              double num_exec)
 {
    fix_upper_expr_context context;
 
@@ -2551,6 +2730,7 @@ fix_upper_expr(PlannerInfo *root,
    context.subplan_itlist = subplan_itlist;
    context.newvarno = newvarno;
    context.rtoffset = rtoffset;
+   context.num_exec = num_exec;
    return fix_upper_expr_mutator(node, &context);
 }
 
@@ -2623,6 +2803,11 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
        }
        /* If no match, just fall through to process it normally */
    }
+   if (IsA(node, AlternativeSubPlan))
+       return fix_upper_expr_mutator(fix_alternative_subplan(context->root,
+                                                             (AlternativeSubPlan *) node,
+                                                             context->num_exec),
+                                     context);
    fix_expr_common(context->root, node);
    return expression_tree_mutator(node,
                                   fix_upper_expr_mutator,
@@ -2687,7 +2872,8 @@ set_returning_clause_references(PlannerInfo *root,
                          itlist,
                          NULL,
                          resultRelation,
-                         rtoffset);
+                         rtoffset,
+                         NUM_EXEC_TLIST(topplan));
 
    pfree(itlist);
 
index 6eb794669fe35d6661e4c3abcb99f1293a9ab8d7..fcce81926b7d6948a3bd12ae592a946abc4ff7bb 100644 (file)
@@ -81,6 +81,7 @@ static Node *convert_testexpr(PlannerInfo *root,
 static Node *convert_testexpr_mutator(Node *node,
                                      convert_testexpr_context *context);
 static bool subplan_is_hashable(Plan *plan);
+static bool subpath_is_hashable(Path *path);
 static bool testexpr_is_hashable(Node *testexpr, List *param_ids);
 static bool test_opexpr_is_hashable(OpExpr *testexpr, List *param_ids);
 static bool hash_ok_operator(OpExpr *expr);
@@ -247,7 +248,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
     * likely to be better (it depends on the expected number of executions of
     * the EXISTS qual, and we are much too early in planning the outer query
     * to be able to guess that).  So we generate both plans, if possible, and
-    * leave it to the executor to decide which to use.
+    * leave it to setrefs.c to decide which to use.
     */
    if (simple_exists && IsA(result, SubPlan))
    {
@@ -273,20 +274,20 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
            plan_params = root->plan_params;
            root->plan_params = NIL;
 
-           /* Select best Path and turn it into a Plan */
+           /* Select best Path */
            final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
            best_path = final_rel->cheapest_total_path;
 
-           plan = create_plan(subroot, best_path);
-
            /* Now we can check if it'll fit in hash_mem */
-           /* XXX can we check this at the Path stage? */
-           if (subplan_is_hashable(plan))
+           if (subpath_is_hashable(best_path))
            {
                SubPlan    *hashplan;
                AlternativeSubPlan *asplan;
 
-               /* OK, convert to SubPlan format. */
+               /* OK, finish planning the ANY subquery */
+               plan = create_plan(subroot, best_path);
+
+               /* ... and convert to SubPlan format */
                hashplan = castNode(SubPlan,
                                    build_subplan(root, plan, subroot,
                                                  plan_params,
@@ -298,10 +299,11 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
                Assert(hashplan->parParam == NIL);
                Assert(hashplan->useHashTable);
 
-               /* Leave it to the executor to decide which plan to use */
+               /* Leave it to setrefs.c to decide which plan to use */
                asplan = makeNode(AlternativeSubPlan);
                asplan->subplans = list_make2(result, hashplan);
                result = (Node *) asplan;
+               root->hasAlternativeSubPlans = true;
            }
        }
    }
@@ -714,6 +716,9 @@ convert_testexpr_mutator(Node *node,
 
 /*
  * subplan_is_hashable: can we implement an ANY subplan by hashing?
+ *
+ * This is not responsible for checking whether the combining testexpr
+ * is suitable for hashing.  We only look at the subquery itself.
  */
 static bool
 subplan_is_hashable(Plan *plan)
@@ -735,6 +740,31 @@ subplan_is_hashable(Plan *plan)
    return true;
 }
 
+/*
+ * subpath_is_hashable: can we implement an ANY subplan by hashing?
+ *
+ * Identical to subplan_is_hashable, but work from a Path for the subplan.
+ */
+static bool
+subpath_is_hashable(Path *path)
+{
+   double      subquery_size;
+   int         hash_mem = get_hash_mem();
+
+   /*
+    * The estimated size of the subquery result must fit in hash_mem. (Note:
+    * we use heap tuple overhead here even though the tuples will actually be
+    * stored as MinimalTuples; this provides some fudge factor for hashtable
+    * overhead.)
+    */
+   subquery_size = path->rows *
+       (MAXALIGN(path->pathtarget->width) + MAXALIGN(SizeofHeapTupleHeader));
+   if (subquery_size > hash_mem * 1024L)
+       return false;
+
+   return true;
+}
+
 /*
  * testexpr_is_hashable: is an ANY SubLink's test expression hashable?
  *
index 15877e37a609144baca74355875c8e8242c8112b..03cf241996377bd29862d9c994a826141746445f 100644 (file)
@@ -8192,7 +8192,12 @@ get_rule_expr(Node *node, deparse_context *context,
                AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
                ListCell   *lc;
 
-               /* As above, this can only happen during EXPLAIN */
+               /*
+                * This case cannot be reached in normal usage, since no
+                * AlternativeSubPlan can appear either in parsetrees or
+                * finished plan trees.  We keep it just in case somebody
+                * wants to use this code to print planner data structures.
+                */
                appendStringInfoString(buf, "(alternatives: ");
                foreach(lc, asplan->subplans)
                {
index dbe8649a5763f23ef9bbc1493fbe1a5775c39dd2..b792de1bc95e87690eb2c7c13a640420c30f5869 100644 (file)
@@ -218,7 +218,6 @@ typedef enum ExprEvalOp
    EEOP_GROUPING_FUNC,
    EEOP_WINDOW_FUNC,
    EEOP_SUBPLAN,
-   EEOP_ALTERNATIVE_SUBPLAN,
 
    /* aggregation related nodes */
    EEOP_AGG_STRICT_DESERIALIZE,
@@ -589,13 +588,6 @@ typedef struct ExprEvalStep
            SubPlanState *sstate;
        }           subplan;
 
-       /* for EEOP_ALTERNATIVE_SUBPLAN */
-       struct
-       {
-           /* out-of-line state, created by nodeSubplan.c */
-           AlternativeSubPlanState *asstate;
-       }           alternative_subplan;
-
        /* for EEOP_AGG_*DESERIALIZE */
        struct
        {
@@ -734,8 +726,6 @@ extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op,
                            ExprContext *econtext);
-extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op,
-                                      ExprContext *econtext);
 extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
                                ExprContext *econtext);
 extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
index 83e90b3d07b943663d4599cb9c802ee6bf6575a2..b629af1f5fb921d7657afd38d61cccccab78e2b0 100644 (file)
 
 extern SubPlanState *ExecInitSubPlan(SubPlan *subplan, PlanState *parent);
 
-extern AlternativeSubPlanState *ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent);
-
 extern Datum ExecSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull);
 
-extern Datum ExecAlternativeSubPlan(AlternativeSubPlanState *node, ExprContext *econtext, bool *isNull);
-
 extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
 
 extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
index a5ab1aed14d5af6b475f75b7256f24e92e345014..ef448d67c77d9ad6a681c984173efc455db34aea 100644 (file)
@@ -877,18 +877,6 @@ typedef struct SubPlanState
    ExprState  *cur_eq_comp;    /* equality comparator for LHS vs. table */
 } SubPlanState;
 
-/* ----------------
- *     AlternativeSubPlanState node
- * ----------------
- */
-typedef struct AlternativeSubPlanState
-{
-   NodeTag     type;
-   AlternativeSubPlan *subplan;    /* expression plan node */
-   List       *subplans;       /* SubPlanStates of alternative subplans */
-   int         active;         /* list index of the one we're using */
-} AlternativeSubPlanState;
-
 /*
  * DomainConstraintState - one item to check during CoerceToDomain
  *
index 381d84b4e4f859e91d9b61de543a3344f06c769c..7ddd8c011bfc329ae67a71f01bc4d71bc5b81935 100644 (file)
@@ -213,7 +213,6 @@ typedef enum NodeTag
    T_WindowFuncExprState,
    T_SetExprState,
    T_SubPlanState,
-   T_AlternativeSubPlanState,
    T_DomainConstraintState,
 
    /*
index 485d1b06c910f2f64fcf599d7e37d02ff51e59a0..dbe86e7af6571e91273cb0613d872b8e860a0dd3 100644 (file)
@@ -347,6 +347,7 @@ struct PlannerInfo
    bool        hasHavingQual;  /* true if havingQual was non-null */
    bool        hasPseudoConstantQuals; /* true if any RestrictInfo has
                                         * pseudoconstant = true */
+   bool        hasAlternativeSubPlans; /* true if we've made any of those */
    bool        hasRecursion;   /* true if planning a recursive WITH item */
 
    /* These fields are used only when hasRecursion is true: */
index d73be2ad46cc955c478da576daedd7a9857f3835..fd65ee8f9c59f1d0e6e72dac4a891b7c0dd31fc3 100644 (file)
@@ -736,6 +736,9 @@ typedef struct SubPlan
 /*
  * AlternativeSubPlan - expression node for a choice among SubPlans
  *
+ * This is used only transiently during planning: by the time the plan
+ * reaches the executor, all AlternativeSubPlan nodes have been removed.
+ *
  * The subplans are given as a List so that the node definition need not
  * change if there's ever more than two alternatives.  For the moment,
  * though, there are always exactly two; and the first one is the fast-start
index 1338b2b23e1739a7671a1586d634b00803a1d19d..ff157ceb1c19fb18e5b1ab238a5ea3341154c711 100644 (file)
@@ -50,14 +50,12 @@ explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on con
  Insert on insertconflicttest
    Conflict Resolution: UPDATE
    Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key
-   Conflict Filter: (alternatives: SubPlan 1 or hashed SubPlan 2)
+   Conflict Filter: (SubPlan 1)
    ->  Result
    SubPlan 1
      ->  Index Only Scan using both_index_expr_key on insertconflicttest ii
            Index Cond: (key = excluded.key)
-   SubPlan 2
-     ->  Seq Scan on insertconflicttest ii_1
-(10 rows)
+(8 rows)
 
 -- Neither collation nor operator class specifications are required --
 -- supplying them merely *limits* matches to indexes with matching opclasses
index b81923f2e7410c260d3ee1451895ee6f35b7017f..9d56cdacf37dbff50800f26e8ad00e084a868f59 100644 (file)
@@ -874,6 +874,53 @@ select * from int8_tbl where q1 in (select c1 from inner_text);
 (2 rows)
 
 rollback;  -- to get rid of the bogus operator
+--
+-- Test resolution of hashed vs non-hashed implementation of EXISTS subplan
+--
+explain (costs off)
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Seq Scan on tenk1 t
+         Filter: ((hashed SubPlan 2) OR (ten < 0))
+         SubPlan 2
+           ->  Index Only Scan using tenk1_unique1 on tenk1 k
+(5 rows)
+
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);
+ count 
+-------
+ 10000
+(1 row)
+
+explain (costs off)
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0)
+  and thousand = 1;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on tenk1 t
+         Recheck Cond: (thousand = 1)
+         Filter: ((SubPlan 1) OR (ten < 0))
+         ->  Bitmap Index Scan on tenk1_thous_tenthous
+               Index Cond: (thousand = 1)
+         SubPlan 1
+           ->  Index Only Scan using tenk1_unique1 on tenk1 k
+                 Index Cond: (unique1 = t.unique2)
+(9 rows)
+
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0)
+  and thousand = 1;
+ count 
+-------
+    10
+(1 row)
+
 --
 -- Test case for planner bug with nested EXISTS handling
 --
index 5de53f2782aaf45fa56b200d8b2353afd004c5a6..caed1c19ec76c09aee03f5a49fd207bd9834c5db 100644 (file)
@@ -1869,9 +1869,7 @@ EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
    SubPlan 1
      ->  Index Only Scan using ref_tbl_pkey on ref_tbl r
            Index Cond: (a = b.a)
-   SubPlan 2
-     ->  Seq Scan on ref_tbl r_1
-(7 rows)
+(5 rows)
 
 EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
                         QUERY PLAN                         
@@ -1885,9 +1883,7 @@ EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
    SubPlan 1
      ->  Index Only Scan using ref_tbl_pkey on ref_tbl r_1
            Index Cond: (a = b.a)
-   SubPlan 2
-     ->  Seq Scan on ref_tbl r_2
-(11 rows)
+(9 rows)
 
 DROP TABLE base_tbl, ref_tbl CASCADE;
 NOTICE:  drop cascades to view rw_view1
@@ -2301,8 +2297,8 @@ SELECT * FROM v1 WHERE a=8;
 
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
-                                                        QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
  Update on public.t1
    Update on public.t1
    Update on public.t11 t1_1
@@ -2311,32 +2307,26 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
    ->  Index Scan using t1_a_idx on public.t1
          Output: 100, t1.b, t1.c, t1.ctid
          Index Cond: ((t1.a > 5) AND (t1.a < 7))
-         Filter: ((t1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
+         Filter: ((t1.a <> 6) AND (SubPlan 1) AND snoop(t1.a) AND leakproof(t1.a))
          SubPlan 1
            ->  Append
                  ->  Seq Scan on public.t12 t12_1
                        Filter: (t12_1.a = t1.a)
                  ->  Seq Scan on public.t111 t12_2
                        Filter: (t12_2.a = t1.a)
-         SubPlan 2
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_4
-                       Output: t12_4.a
-                 ->  Seq Scan on public.t111 t12_5
-                       Output: t12_5.a
    ->  Index Scan using t11_a_idx on public.t11 t1_1
          Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
          Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
-         Filter: ((t1_1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+         Filter: ((t1_1.a <> 6) AND (SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
    ->  Index Scan using t12_a_idx on public.t12 t1_2
          Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
          Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
-         Filter: ((t1_2.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+         Filter: ((t1_2.a <> 6) AND (SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
    ->  Index Scan using t111_a_idx on public.t111 t1_3
          Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
          Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
-         Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(33 rows)
+         Filter: ((t1_3.a <> 6) AND (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+(27 rows)
 
 UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
 SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
@@ -2351,8 +2341,8 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
 
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
-                                               QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
  Update on public.t1
    Update on public.t1
    Update on public.t11 t1_1
@@ -2361,32 +2351,26 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
    ->  Index Scan using t1_a_idx on public.t1
          Output: (t1.a + 1), t1.b, t1.c, t1.ctid
          Index Cond: ((t1.a > 5) AND (t1.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
+         Filter: ((SubPlan 1) AND snoop(t1.a) AND leakproof(t1.a))
          SubPlan 1
            ->  Append
                  ->  Seq Scan on public.t12 t12_1
                        Filter: (t12_1.a = t1.a)
                  ->  Seq Scan on public.t111 t12_2
                        Filter: (t12_2.a = t1.a)
-         SubPlan 2
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_4
-                       Output: t12_4.a
-                 ->  Seq Scan on public.t111 t12_5
-                       Output: t12_5.a
    ->  Index Scan using t11_a_idx on public.t11 t1_1
          Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
          Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+         Filter: ((SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
    ->  Index Scan using t12_a_idx on public.t12 t1_2
          Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
          Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+         Filter: ((SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
    ->  Index Scan using t111_a_idx on public.t111 t1_3
          Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
          Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(33 rows)
+         Filter: ((SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+(27 rows)
 
 UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
 NOTICE:  snooped value: 8
index cce8ebdb3d9fc9d73c0b4cd94dd36292c8e54252..a25cb6fc5c5362877db4f44d0d200cea9be3f05b 100644 (file)
@@ -509,6 +509,23 @@ select * from int8_tbl where q1 in (select c1 from inner_text);
 
 rollback;  -- to get rid of the bogus operator
 
+--
+-- Test resolution of hashed vs non-hashed implementation of EXISTS subplan
+--
+explain (costs off)
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);
+
+explain (costs off)
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0)
+  and thousand = 1;
+select count(*) from tenk1 t
+where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0)
+  and thousand = 1;
+
 --
 -- Test case for planner bug with nested EXISTS handling
 --