diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index a6c6de78f1..cc2b957956 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -2075,9 +2075,10 @@ show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es) return; /* Set up deparsing context */ - context = set_deparse_context_planstate(es->deparse_cxt, - (Node *) planstate, - ancestors); + context = set_deparse_context_plan(es->deparse_cxt, + (Node *) planstate, + planstate->plan, + ancestors); useprefix = list_length(es->rtable) > 1; /* Deparse each result column (we now include resjunk ones) */ @@ -2106,9 +2107,10 @@ show_expression(Node *node, const char *qlabel, char *exprstr; /* Set up deparsing context */ - context = set_deparse_context_planstate(es->deparse_cxt, - (Node *) planstate, - ancestors); + context = set_deparse_context_plan(es->deparse_cxt, + (Node *) planstate, + planstate->plan, + ancestors); /* Deparse the expression */ exprstr = deparse_expression(node, context, useprefix, false); @@ -2232,9 +2234,10 @@ show_grouping_sets(PlanState *planstate, Agg *agg, ListCell *lc; /* Set up deparsing context */ - context = set_deparse_context_planstate(es->deparse_cxt, - (Node *) planstate, - ancestors); + context = set_deparse_context_plan(es->deparse_cxt, + (Node *) planstate, + planstate->plan, + ancestors); useprefix = (list_length(es->rtable) > 1 || es->verbose); ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es); @@ -2371,9 +2374,10 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel, initStringInfo(&sortkeybuf); /* Set up deparsing context */ - context = set_deparse_context_planstate(es->deparse_cxt, - (Node *) planstate, - ancestors); + context = set_deparse_context_plan(es->deparse_cxt, + (Node *) planstate, + planstate->plan, + ancestors); useprefix = (list_length(es->rtable) > 1 || es->verbose); for (keyno = 0; keyno < nkeys; keyno++) @@ -2479,9 +2483,10 @@ show_tablesample(TableSampleClause *tsc, PlanState *planstate, ListCell *lc; /* Set up deparsing context */ - context = set_deparse_context_planstate(es->deparse_cxt, - (Node *) planstate, - ancestors); + context = set_deparse_context_plan(es->deparse_cxt, + (Node *) planstate, + planstate->plan, + ancestors); useprefix = list_length(es->rtable) > 1; /* Get the tablesample method name */ diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index f3be2429db..26a4409fef 100644 --- a/src/backend/executor/nodeAppend.c +++ b/src/backend/executor/nodeAppend.c @@ -78,7 +78,6 @@ struct ParallelAppendState }; #define INVALID_SUBPLAN_INDEX -1 -#define NO_MATCHING_SUBPLANS -2 static TupleTableSlot *ExecAppend(PlanState *pstate); static bool choose_next_subplan_locally(AppendState *node); @@ -142,23 +141,6 @@ ExecInitAppend(Append *node, EState *estate, int eflags) validsubplans = ExecFindInitialMatchingSubPlans(prunestate, list_length(node->appendplans)); - /* - * The case where no subplans survive pruning must be handled - * specially. The problem here is that code in explain.c requires - * an Append to have at least one subplan in order for it to - * properly determine the Vars in that subplan's targetlist. We - * sidestep this issue by just initializing the first subplan and - * setting as_whichplan to NO_MATCHING_SUBPLANS to indicate that - * we don't really need to scan any subnodes. - */ - if (bms_is_empty(validsubplans)) - { - appendstate->as_whichplan = NO_MATCHING_SUBPLANS; - - /* Mark the first as valid so that it's initialized below */ - validsubplans = bms_make_singleton(0); - } - nplans = bms_num_members(validsubplans); } else @@ -170,14 +152,12 @@ ExecInitAppend(Append *node, EState *estate, int eflags) } /* - * If no runtime pruning is required, we can fill as_valid_subplans - * immediately, preventing later calls to ExecFindMatchingSubPlans. + * If there's at least one valid plan and no run-time pruning is + * required, we can fill as_valid_subplans immediately, preventing + * later calls to ExecFindMatchingSubPlans. */ - if (!prunestate->do_exec_prune) - { - Assert(nplans > 0); + if (nplans > 0 && !prunestate->do_exec_prune) appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1); - } } else { @@ -202,8 +182,11 @@ ExecInitAppend(Append *node, EState *estate, int eflags) appendstate->ps.resultopsset = true; appendstate->ps.resultopsfixed = false; - appendplanstates = (PlanState **) palloc(nplans * - sizeof(PlanState *)); + if (nplans > 0) + appendplanstates = (PlanState **) palloc(nplans * + sizeof(PlanState *)); + else + appendplanstates = NULL; /* * call ExecInitNode on each of the valid plans to be executed and save @@ -260,6 +243,10 @@ ExecAppend(PlanState *pstate) if (node->as_whichplan < 0) { + /* Nothing to do if there are no subplans */ + if (node->as_nplans == 0) + return ExecClearTuple(node->ps.ps_ResultTupleSlot); + /* * If no subplan has been chosen, we must choose one before * proceeding. @@ -267,10 +254,6 @@ ExecAppend(PlanState *pstate) if (node->as_whichplan == INVALID_SUBPLAN_INDEX && !node->choose_next_subplan(node)) return ExecClearTuple(node->ps.ps_ResultTupleSlot); - - /* Nothing to do if there are no matching subplans */ - else if (node->as_whichplan == NO_MATCHING_SUBPLANS) - return ExecClearTuple(node->ps.ps_ResultTupleSlot); } for (;;) @@ -465,7 +448,7 @@ choose_next_subplan_locally(AppendState *node) int nextplan; /* We should never be called when there are no subplans */ - Assert(whichplan != NO_MATCHING_SUBPLANS); + Assert(node->as_nplans > 0); /* * If first call then have the bms member function choose the first valid @@ -516,7 +499,7 @@ choose_next_subplan_for_leader(AppendState *node) Assert(ScanDirectionIsForward(node->ps.state->es_direction)); /* We should never be called when there are no subplans */ - Assert(node->as_whichplan != NO_MATCHING_SUBPLANS); + Assert(node->as_nplans > 0); LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE); @@ -597,7 +580,7 @@ choose_next_subplan_for_worker(AppendState *node) Assert(ScanDirectionIsForward(node->ps.state->es_direction)); /* We should never be called when there are no subplans */ - Assert(node->as_whichplan != NO_MATCHING_SUBPLANS); + Assert(node->as_nplans > 0); LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE); diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 7ba53ba185..c08c4f38ff 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -81,7 +81,6 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) mergestate->ps.plan = (Plan *) node; mergestate->ps.state = estate; mergestate->ps.ExecProcNode = ExecMergeAppend; - mergestate->ms_noopscan = false; /* If run-time partition pruning is enabled, then set that up now */ if (node->part_prune_info != NULL) @@ -102,23 +101,6 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) validsubplans = ExecFindInitialMatchingSubPlans(prunestate, list_length(node->mergeplans)); - /* - * The case where no subplans survive pruning must be handled - * specially. The problem here is that code in explain.c requires - * a MergeAppend to have at least one subplan in order for it to - * properly determine the Vars in that subplan's targetlist. We - * sidestep this issue by just initializing the first subplan and - * setting ms_noopscan to true to indicate that we don't really - * need to scan any subnodes. - */ - if (bms_is_empty(validsubplans)) - { - mergestate->ms_noopscan = true; - - /* Mark the first as valid so that it's initialized below */ - validsubplans = bms_make_singleton(0); - } - nplans = bms_num_members(validsubplans); } else @@ -133,11 +115,8 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) * If no runtime pruning is required, we can fill ms_valid_subplans * immediately, preventing later calls to ExecFindMatchingSubPlans. */ - if (!prunestate->do_exec_prune) - { - Assert(nplans > 0); + if (nplans > 0 && !prunestate->do_exec_prune) mergestate->ms_valid_subplans = bms_add_range(NULL, 0, nplans - 1); - } } else { @@ -153,7 +132,11 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) mergestate->ms_prune_state = NULL; } - mergeplanstates = (PlanState **) palloc(nplans * sizeof(PlanState *)); + if (nplans > 0) + mergeplanstates = (PlanState **) palloc(nplans * sizeof(PlanState *)); + else + mergeplanstates = NULL; + mergestate->mergeplans = mergeplanstates; mergestate->ms_nplans = nplans; @@ -244,7 +227,7 @@ ExecMergeAppend(PlanState *pstate) if (!node->ms_initialized) { /* Nothing to do if all subplans were pruned */ - if (node->ms_noopscan) + if (node->ms_nplans == 0) return ExecClearTuple(node->ps.ps_ResultTupleSlot); /* diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 0c7a533e69..712ff3a9a6 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -146,7 +146,13 @@ typedef struct * state nodes, as well as their plan nodes' targetlists, and the index tlist * if the current plan node might contain INDEX_VAR Vars. (These fields could * be derived on-the-fly from the current PlanState, but it seems notationally - * clearer to set them up as separate fields.) + * clearer to set them up as separate fields.). Additionally we store the Plan + * nodes corisponding to each PlanState node. It might appear like we could + * just obtain these from looking at the PlanStates->plan field, but in some + * cases a PlanState can be NULL when a Plan node was not initialized during + * executor startup. This can happen for things such as run-time partition + * pruning. Storing these allows us to still resolve Vars for Plan nodes that + * were never initialized. */ typedef struct { @@ -158,6 +164,10 @@ typedef struct bool unique_using; /* Are we making USING names globally unique */ List *using_names; /* List of assigned names for USING columns */ /* Remaining fields are used only when deparsing a Plan tree: */ + Plan *plan; /* immediate parent of current expression */ + Plan *outer_plan; /* outer subplan, or NULL if none */ + Plan *inner_plan; /* inner subplan, or NULL if none */ + List *ancestor_plans; /* ancestors of plan */ PlanState *planstate; /* immediate parent of current expression */ List *ancestors; /* ancestors of planstate */ PlanState *outer_planstate; /* outer subplan state, or NULL if none */ @@ -358,12 +368,14 @@ static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte, static void flatten_join_using_qual(Node *qual, List **leftvars, List **rightvars); static char *get_rtable_name(int rtindex, deparse_context *context); -static void set_deparse_planstate(deparse_namespace *dpns, PlanState *ps); +static void set_deparse_plan(deparse_namespace *dpns, PlanState *ps, + Plan *plan); static void push_child_plan(deparse_namespace *dpns, PlanState *ps, - deparse_namespace *save_dpns); + Plan *plan, deparse_namespace *save_dpns); static void pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns); static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, + ListCell *ancestor_plan_cell, deparse_namespace *save_dpns); static void pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns); @@ -410,7 +422,8 @@ static void resolve_special_varno(Node *node, deparse_context *context, void *private, void (*callback) (Node *, deparse_context *, void *)); static Node *find_param_referent(Param *param, deparse_context *context, - deparse_namespace **dpns_p, ListCell **ancestor_cell_p); + deparse_namespace **dpns_p, ListCell **ancestor_cell_p, + ListCell **ancestor_plan_cell_p); static void get_parameter(Param *param, deparse_context *context); static const char *get_simple_binary_op_name(OpExpr *expr); static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags); @@ -3285,7 +3298,7 @@ deparse_context_for(const char *aliasname, Oid relid) * this is rather expensive if the rangetable is large, and it'll be the same * for every expression in the Plan tree; so we do it just once and re-use * the result of this function for each expression. (Note that the result - * is not usable until set_deparse_context_planstate() is applied to it.) + * is not usable until set_deparse_context_plan() is applied to it.) * * In addition to the plan's rangetable list, pass the per-RTE alias names * assigned by a previous call to select_rtable_names_for_explain. @@ -3314,7 +3327,7 @@ deparse_context_for_plan_rtable(List *rtable, List *rtable_names) } /* - * set_deparse_context_planstate - Specify Plan node containing expression + * set_deparse_context_plan - Specify Plan node containing expression * * When deparsing an expression in a Plan tree, we might have to resolve * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must @@ -3342,19 +3355,32 @@ deparse_context_for_plan_rtable(List *rtable, List *rtable_names) * The result is the same List passed in; this is a notational convenience. */ List * -set_deparse_context_planstate(List *dpcontext, - Node *planstate, List *ancestors) +set_deparse_context_plan(List *dpcontext, + Node *planstate, Plan *plan, List *ancestors) { deparse_namespace *dpns; + ListCell *lc; /* Should always have one-entry namespace list for Plan deparsing */ Assert(list_length(dpcontext) == 1); dpns = (deparse_namespace *) linitial(dpcontext); /* Set our attention on the specific plan node passed in */ - set_deparse_planstate(dpns, (PlanState *) planstate); + set_deparse_plan(dpns, (PlanState *) planstate, plan); dpns->ancestors = ancestors; + if (dpns->ancestor_plans != NIL) + { + list_free(dpns->ancestor_plans); + dpns->ancestor_plans = NIL; + } + + foreach(lc, ancestors) + { + PlanState *ps = lfirst(lc); + dpns->ancestor_plans = lappend(dpns->ancestor_plans, ps->plan); + } + return dpcontext; } @@ -4620,19 +4646,20 @@ get_rtable_name(int rtindex, deparse_context *context) } /* - * set_deparse_planstate: set up deparse_namespace to parse subexpressions + * set_deparse_plan: set up deparse_namespace to parse subexpressions * of a given PlanState node * - * This sets the planstate, outer_planstate, inner_planstate, outer_tlist, - * inner_tlist, and index_tlist fields. Caller is responsible for adjusting - * the ancestors list if necessary. Note that the rtable and ctes fields do - * not need to change when shifting attention to different plan nodes in a - * single plan tree. + * This sets the planstate, outer_planstate, inner_planstate, plan, + * outer_plan, inner_plan outer_tlist, inner_tlist, and index_tlist fields. + * Caller is responsible for adjusting the ancestors and ancestor_plans lists + * if necessary. Note that the rtable and ctes fields do not need to change + * when shifting attention to different plan nodes in a single plan tree. */ static void -set_deparse_planstate(deparse_namespace *dpns, PlanState *ps) +set_deparse_plan(deparse_namespace *dpns, PlanState *ps, Plan *plan) { dpns->planstate = ps; + dpns->plan = plan; /* * We special-case Append and MergeAppend to pretend that the first child @@ -4642,17 +4669,60 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps) * first child plan is the OUTER referent; this is to support RETURNING * lists containing references to non-target relations. */ - if (IsA(ps, AppendState)) - dpns->outer_planstate = ((AppendState *) ps)->appendplans[0]; - else if (IsA(ps, MergeAppendState)) - dpns->outer_planstate = ((MergeAppendState *) ps)->mergeplans[0]; - else if (IsA(ps, ModifyTableState)) - dpns->outer_planstate = ((ModifyTableState *) ps)->mt_plans[0]; + if (IsA(plan, Append)) + { + AppendState *astate = (AppendState *) ps; + + /* + * It's possible that run-time partition pruning pruned every subplan + * which means we'll have no subnodes in the AppendState. If this + * happens we'll use the first of the planned subnodes and set the + * outer_planstate to NULL. + */ + if (astate->as_nplans > 0) + { + dpns->outer_planstate = astate->appendplans[0]; + dpns->outer_plan = dpns->outer_planstate->plan; + } + else + { + dpns->outer_planstate = NULL; + dpns->outer_plan = (Plan *) linitial(((Append *) plan)->appendplans); + } + } + else if (IsA(plan, MergeAppend)) + { + MergeAppendState *mastate = (MergeAppendState *) ps; + + /* + * Like the Append case above, use the first subplan from the plan if + * run-time pruning pruned all of the subnodes. + */ + if (mastate->ms_nplans > 0) + { + dpns->outer_planstate = mastate->mergeplans[0]; + dpns->outer_plan = dpns->outer_planstate->plan; + } + else + { + dpns->outer_planstate = NULL; + dpns->outer_plan = (Plan *) linitial(((MergeAppend *) plan)->mergeplans); + } + } + else if (IsA(plan, ModifyTable)) + { + ModifyTableState *mtstate = (ModifyTableState *) ps; + dpns->outer_planstate = mtstate->mt_plans[0]; + dpns->outer_plan = dpns->outer_planstate->plan; + } else - dpns->outer_planstate = outerPlanState(ps); + { + dpns->outer_plan = outerPlan(plan); + dpns->outer_planstate = ps != NULL ? outerPlanState(ps) : NULL; + } - if (dpns->outer_planstate) - dpns->outer_tlist = dpns->outer_planstate->plan->targetlist; + if (dpns->outer_plan) + dpns->outer_tlist = dpns->outer_plan->targetlist; else dpns->outer_tlist = NIL; @@ -4665,29 +4735,41 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps) * to reuse OUTER, it's used for RETURNING in some modify table cases, * although not INSERT .. CONFLICT). */ - if (IsA(ps, SubqueryScanState)) - dpns->inner_planstate = ((SubqueryScanState *) ps)->subplan; - else if (IsA(ps, CteScanState)) - dpns->inner_planstate = ((CteScanState *) ps)->cteplanstate; - else if (IsA(ps, ModifyTableState)) + if (IsA(plan, SubqueryScan)) + { + dpns->inner_planstate = ((SubqueryScanState *)ps)->subplan; + dpns->inner_plan = dpns->inner_planstate->plan; + } + else if (IsA(plan, CteScan)) + { + dpns->inner_planstate = ((CteScanState *)ps)->cteplanstate; + dpns->inner_plan = dpns->inner_planstate->plan; + } + else if (IsA(plan, ModifyTable)) + { + dpns->inner_plan = plan; dpns->inner_planstate = ps; + } else - dpns->inner_planstate = innerPlanState(ps); + { + dpns->inner_plan = innerPlan(plan); + dpns->inner_planstate = ps != NULL ? innerPlanState(ps) : NULL; + } - if (IsA(ps, ModifyTableState)) - dpns->inner_tlist = ((ModifyTableState *) ps)->mt_excludedtlist; - else if (dpns->inner_planstate) - dpns->inner_tlist = dpns->inner_planstate->plan->targetlist; + if (IsA(plan, ModifyTable)) + dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist; + else if (dpns->inner_plan) + dpns->inner_tlist = dpns->inner_plan->targetlist; else dpns->inner_tlist = NIL; /* Set up referent for INDEX_VAR Vars, if needed */ - if (IsA(ps->plan, IndexOnlyScan)) - dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist; - else if (IsA(ps->plan, ForeignScan)) - dpns->index_tlist = ((ForeignScan *) ps->plan)->fdw_scan_tlist; - else if (IsA(ps->plan, CustomScan)) - dpns->index_tlist = ((CustomScan *) ps->plan)->custom_scan_tlist; + if (IsA(plan, IndexOnlyScan)) + dpns->index_tlist = ((IndexOnlyScan *) plan)->indextlist; + else if (IsA(plan, ForeignScan)) + dpns->index_tlist = ((ForeignScan *) plan)->fdw_scan_tlist; + else if (IsA(plan, CustomScan)) + dpns->index_tlist = ((CustomScan *) plan)->custom_scan_tlist; else dpns->index_tlist = NIL; } @@ -4705,17 +4787,18 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps) * previous state for pop_child_plan. */ static void -push_child_plan(deparse_namespace *dpns, PlanState *ps, +push_child_plan(deparse_namespace *dpns, PlanState *ps, Plan *plan, deparse_namespace *save_dpns) { /* Save state for restoration later */ *save_dpns = *dpns; - /* Link current plan node into ancestors list */ + /* Link current plan node into ancestors and ancestor_plans lists */ dpns->ancestors = lcons(dpns->planstate, dpns->ancestors); + dpns->ancestor_plans = lcons(dpns->plan, dpns->ancestor_plans); /* Set attention on selected child */ - set_deparse_planstate(dpns, ps); + set_deparse_plan(dpns, ps, plan); } /* @@ -4725,15 +4808,22 @@ static void pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns) { List *ancestors; + List *ancestor_plans; - /* Get rid of ancestors list cell added by push_child_plan */ + /* Get rid of ancestors list cells added by push_child_plan */ ancestors = list_delete_first(dpns->ancestors); + ancestor_plans = list_delete_first(dpns->ancestor_plans); /* Restore fields changed by push_child_plan */ *dpns = *save_dpns; - /* Make sure dpns->ancestors is right (may be unnecessary) */ + /* + * Make sure dpns->ancestors and dpns->ancestor_plans is right (may be + * unnecessary) + */ dpns->ancestors = ancestors; + dpns->ancestor_plans = ancestor_plans; + } /* @@ -4753,10 +4843,12 @@ pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns) */ static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, - deparse_namespace *save_dpns) + ListCell *ancestor_plan_cell, deparse_namespace *save_dpns) { - PlanState *ps = (PlanState *) lfirst(ancestor_cell); + PlanState *planstate = (PlanState *) lfirst(ancestor_cell); + Plan *plan = (Plan *) lfirst(ancestor_plan_cell); List *ancestors; + List *ancestor_plans; /* Save state for restoration later */ *save_dpns = *dpns; @@ -4767,8 +4859,14 @@ push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, ancestors = lappend(ancestors, lfirst(ancestor_cell)); dpns->ancestors = ancestors; + /* and same for the ancestor_plans list */ + ancestor_plans = NIL; + while ((ancestor_plan_cell = lnext(ancestor_plan_cell)) != NULL) + ancestor_plans = lappend(ancestor_plans, lfirst(ancestor_plan_cell)); + dpns->ancestor_plans = ancestor_plans; + /* Set attention on selected ancestor */ - set_deparse_planstate(dpns, ps); + set_deparse_plan(dpns, planstate, plan); } /* @@ -6702,7 +6800,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) */ if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) && attnum > list_length(rte->eref->colnames) && - dpns->inner_planstate) + dpns->inner_plan) { TargetEntry *tle; deparse_namespace save_dpns; @@ -6713,7 +6811,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) var->varattno, rte->eref->aliasname); Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->inner_planstate, &save_dpns); + push_child_plan(dpns, dpns->inner_planstate, dpns->inner_plan, &save_dpns); /* * Force parentheses because our caller probably assumed a Var is a @@ -6863,7 +6961,7 @@ resolve_special_varno(Node *node, deparse_context *context, void *private, if (!tle) elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno); - push_child_plan(dpns, dpns->outer_planstate, &save_dpns); + push_child_plan(dpns, dpns->outer_planstate, dpns->outer_plan, &save_dpns); resolve_special_varno((Node *) tle->expr, context, private, callback); pop_child_plan(dpns, &save_dpns); return; @@ -6877,7 +6975,7 @@ resolve_special_varno(Node *node, deparse_context *context, void *private, if (!tle) elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno); - push_child_plan(dpns, dpns->inner_planstate, &save_dpns); + push_child_plan(dpns, dpns->inner_planstate, dpns->inner_plan, &save_dpns); resolve_special_varno((Node *) tle->expr, context, private, callback); pop_child_plan(dpns, &save_dpns); return; @@ -6947,15 +7045,18 @@ get_name_for_var_field(Var *var, int fieldno, { Param *param = (Param *) var; ListCell *ancestor_cell; + ListCell *ancestor_plan_cell; - expr = find_param_referent(param, context, &dpns, &ancestor_cell); + expr = find_param_referent(param, context, &dpns, &ancestor_cell, + &ancestor_plan_cell); if (expr) { /* Found a match, so recurse to decipher the field name */ deparse_namespace save_dpns; const char *result; - push_ancestor_plan(dpns, ancestor_cell, &save_dpns); + push_ancestor_plan(dpns, ancestor_cell, ancestor_plan_cell, + &save_dpns); result = get_name_for_var_field((Var *) expr, fieldno, 0, context); pop_ancestor_plan(dpns, &save_dpns); @@ -7005,7 +7106,8 @@ get_name_for_var_field(Var *var, int fieldno, elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno); Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->outer_planstate, &save_dpns); + push_child_plan(dpns, dpns->outer_planstate, dpns->outer_plan, + &save_dpns); result = get_name_for_var_field((Var *) tle->expr, fieldno, levelsup, context); @@ -7024,7 +7126,8 @@ get_name_for_var_field(Var *var, int fieldno, elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno); Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->inner_planstate, &save_dpns); + push_child_plan(dpns, dpns->inner_planstate, dpns->inner_plan, + &save_dpns); result = get_name_for_var_field((Var *) tle->expr, fieldno, levelsup, context); @@ -7134,7 +7237,7 @@ get_name_for_var_field(Var *var, int fieldno, deparse_namespace save_dpns; const char *result; - if (!dpns->inner_planstate) + if (!dpns->inner_plan) elog(ERROR, "failed to find plan for subquery %s", rte->eref->aliasname); tle = get_tle_by_resno(dpns->inner_tlist, attnum); @@ -7142,7 +7245,8 @@ get_name_for_var_field(Var *var, int fieldno, elog(ERROR, "bogus varattno for subquery var: %d", attnum); Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->inner_planstate, &save_dpns); + push_child_plan(dpns, dpns->inner_planstate, + dpns->inner_plan, &save_dpns); result = get_name_for_var_field((Var *) tle->expr, fieldno, levelsup, context); @@ -7252,7 +7356,7 @@ get_name_for_var_field(Var *var, int fieldno, deparse_namespace save_dpns; const char *result; - if (!dpns->inner_planstate) + if (!dpns->inner_plan) elog(ERROR, "failed to find plan for CTE %s", rte->eref->aliasname); tle = get_tle_by_resno(dpns->inner_tlist, attnum); @@ -7260,7 +7364,8 @@ get_name_for_var_field(Var *var, int fieldno, elog(ERROR, "bogus varattno for subquery var: %d", attnum); Assert(netlevelsup == 0); - push_child_plan(dpns, dpns->inner_planstate, &save_dpns); + push_child_plan(dpns, dpns->inner_planstate, + dpns->inner_plan, &save_dpns); result = get_name_for_var_field((Var *) tle->expr, fieldno, levelsup, context); @@ -7286,13 +7391,14 @@ get_name_for_var_field(Var *var, int fieldno, * Try to find the referenced expression for a PARAM_EXEC Param that might * reference a parameter supplied by an upper NestLoop or SubPlan plan node. * - * If successful, return the expression and set *dpns_p and *ancestor_cell_p - * appropriately for calling push_ancestor_plan(). If no referent can be - * found, return NULL. + * If successful, return the expression and set *dpns_p, *ancestor_cell_p + * and ancestor_plan_cell_p appropriately for calling push_ancestor_plan(). + * If no referent can be found, return NULL. */ static Node * find_param_referent(Param *param, deparse_context *context, - deparse_namespace **dpns_p, ListCell **ancestor_cell_p) + deparse_namespace **dpns_p, ListCell **ancestor_cell_p, + ListCell **ancestor_plan_cell_p) { /* Initialize output parameters to prevent compiler warnings */ *dpns_p = NULL; @@ -7309,12 +7415,13 @@ find_param_referent(Param *param, deparse_context *context, PlanState *child_ps; bool in_same_plan_level; ListCell *lc; + ListCell *lcp; dpns = (deparse_namespace *) linitial(context->namespaces); child_ps = dpns->planstate; in_same_plan_level = true; - foreach(lc, dpns->ancestors) + forboth(lc, dpns->ancestors, lcp, dpns->ancestor_plans) { PlanState *ps = (PlanState *) lfirst(lc); ListCell *lc2; @@ -7339,6 +7446,7 @@ find_param_referent(Param *param, deparse_context *context, /* Found a match, so return it */ *dpns_p = dpns; *ancestor_cell_p = lc; + *ancestor_plan_cell_p = lcp; return (Node *) nlp->paramval; } } @@ -7368,6 +7476,7 @@ find_param_referent(Param *param, deparse_context *context, /* Found a match, so return it */ *dpns_p = dpns; *ancestor_cell_p = lc; + *ancestor_plan_cell_p = lcp; return arg; } } @@ -7416,6 +7525,7 @@ get_parameter(Param *param, deparse_context *context) Node *expr; deparse_namespace *dpns; ListCell *ancestor_cell; + ListCell *ancestor_plan_cell; /* * If it's a PARAM_EXEC parameter, try to locate the expression from which @@ -7423,7 +7533,8 @@ get_parameter(Param *param, deparse_context *context) * an error, since the Param might well be a subplan output rather than an * input. */ - expr = find_param_referent(param, context, &dpns, &ancestor_cell); + expr = find_param_referent(param, context, &dpns, &ancestor_cell, + &ancestor_plan_cell); if (expr) { /* Found a match, so print it */ @@ -7432,7 +7543,8 @@ get_parameter(Param *param, deparse_context *context) bool need_paren; /* Switch attention to the ancestor plan node */ - push_ancestor_plan(dpns, ancestor_cell, &save_dpns); + push_ancestor_plan(dpns, ancestor_cell, ancestor_plan_cell, + &save_dpns); /* * Force prefixing of Vars, since they won't belong to the relation diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index ff3328752e..67ce8e343b 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1185,8 +1185,6 @@ struct AppendState * slots current output tuple of each subplan * heap heap of active tuples * initialized true if we have fetched first tuple from each subplan - * noopscan true if partition pruning proved that none of the - * mergeplans can contain a record to satisfy this query. * prune_state details required to allow partitions to be * eliminated from the scan, or NULL if not possible. * valid_subplans for runtime pruning, valid mergeplans indexes to @@ -1203,7 +1201,6 @@ typedef struct MergeAppendState TupleTableSlot **ms_slots; /* array of length ms_nplans */ struct binaryheap *ms_heap; /* binary heap of slot indices */ bool ms_initialized; /* are subplans started? */ - bool ms_noopscan; struct PartitionPruneState *ms_prune_state; Bitmapset *ms_valid_subplans; } MergeAppendState; diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index 7c49e9d0a8..f4b846bf6d 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -29,8 +29,9 @@ extern char *deparse_expression(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit); extern List *deparse_context_for(const char *aliasname, Oid relid); extern List *deparse_context_for_plan_rtable(List *rtable, List *rtable_names); -extern List *set_deparse_context_planstate(List *dpcontext, - Node *planstate, List *ancestors); +extern List *set_deparse_context_plan(List *dpcontext, + Node *planstatee, Plan *plan, + List *ancestors); extern List *select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used); extern char *generate_collation_name(Oid collid); diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 0789b316eb..144e62b548 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2058,20 +2058,17 @@ select explain_parallel_append('execute ab_q5 (2, 3, 3)'); (19 rows) -- Try some params whose values do not belong to any partition. --- We'll still get a single subplan in this case, but it should not be scanned. select explain_parallel_append('execute ab_q5 (33, 44, 55)'); - explain_parallel_append -------------------------------------------------------------------------------- + explain_parallel_append +----------------------------------------------------------- Finalize Aggregate (actual rows=1 loops=1) -> Gather (actual rows=3 loops=1) Workers Planned: 2 Workers Launched: 2 -> Partial Aggregate (actual rows=1 loops=3) -> Parallel Append (actual rows=0 loops=N) - Subplans Removed: 8 - -> Parallel Seq Scan on ab_a1_b1 (never executed) - Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) -(9 rows) + Subplans Removed: 9 +(7 rows) -- Test Parallel Append with PARAM_EXEC Params select explain_parallel_append('select count(*) from ab where (a = (select 1) or a = (select 3)) and b = 2'); @@ -2961,16 +2958,13 @@ explain (analyze, costs off, summary off, timing off) execute q1 (2,2); Filter: (b = ANY (ARRAY[$1, $2])) (4 rows) --- Try with no matching partitions. One subplan should remain in this case, --- but it shouldn't be executed. +-- Try with no matching partitions. explain (analyze, costs off, summary off, timing off) execute q1 (0,0); - QUERY PLAN ----------------------------------------------- + QUERY PLAN +-------------------------------- Append (actual rows=0 loops=1) - Subplans Removed: 1 - -> Seq Scan on listp_1_1 (never executed) - Filter: (b = ANY (ARRAY[$1, $2])) -(4 rows) + Subplans Removed: 2 +(2 rows) deallocate q1; -- Test more complex cases where a not-equal condition further eliminates partitions. @@ -3011,15 +3005,12 @@ explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,0); (4 rows) -- Both partitions allowed by IN clause, then both excluded again by <> clauses. --- One subplan will remain in this case, but it should not be executed. explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1); - QUERY PLAN -------------------------------------------------------------------------- + QUERY PLAN +-------------------------------- Append (actual rows=0 loops=1) - Subplans Removed: 1 - -> Seq Scan on listp_1_1 (never executed) - Filter: ((b = ANY (ARRAY[$1, $2])) AND ($3 <> b) AND ($4 <> b)) -(4 rows) + Subplans Removed: 2 +(2 rows) -- Ensure Params that evaluate to NULL properly prune away all partitions explain (analyze, costs off, summary off, timing off) @@ -3168,14 +3159,12 @@ execute mt_q1(25); -- Ensure MergeAppend behaves correctly when no subplans match explain (analyze, costs off, summary off, timing off) execute mt_q1(35); - QUERY PLAN ------------------------------------------------------------------------- + QUERY PLAN +-------------------------------------- Merge Append (actual rows=0 loops=1) Sort Key: ma_test_p1.b - Subplans Removed: 2 - -> Index Scan using ma_test_p1_b_idx on ma_test_p1 (never executed) - Filter: ((a >= $1) AND ((a % 10) = 5)) -(5 rows) + Subplans Removed: 3 +(3 rows) execute mt_q1(35); a diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index c30e58eef7..f2912d9afe 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -485,7 +485,6 @@ select explain_parallel_append('execute ab_q5 (1, 1, 1)'); select explain_parallel_append('execute ab_q5 (2, 3, 3)'); -- Try some params whose values do not belong to any partition. --- We'll still get a single subplan in this case, but it should not be scanned. select explain_parallel_append('execute ab_q5 (33, 44, 55)'); -- Test Parallel Append with PARAM_EXEC Params @@ -726,8 +725,7 @@ explain (analyze, costs off, summary off, timing off) execute q1 (1,1); explain (analyze, costs off, summary off, timing off) execute q1 (2,2); --- Try with no matching partitions. One subplan should remain in this case, --- but it shouldn't be executed. +-- Try with no matching partitions. explain (analyze, costs off, summary off, timing off) execute q1 (0,0); deallocate q1; @@ -745,7 +743,6 @@ execute q1 (1,2,3,4); explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,0); -- Both partitions allowed by IN clause, then both excluded again by <> clauses. --- One subplan will remain in this case, but it should not be executed. explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1); -- Ensure Params that evaluate to NULL properly prune away all partitions