diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2023-01-30 13:16:20 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2023-01-30 13:16:20 -0500 |
commit | 2489d76c4906f4461a364ca8ad7e0751ead8aa0d (patch) | |
tree | 145ebc28d5ea8f5a5ba340b9e353a11de786adae /src/backend/optimizer/plan/setrefs.c | |
parent | ec7e053a98f39a9e3c7e6d35f0d2e83933882399 (diff) | |
download | postgresql-2489d76c4906f4461a364ca8ad7e0751ead8aa0d.tar.gz postgresql-2489d76c4906f4461a364ca8ad7e0751ead8aa0d.zip |
Make Vars be outer-join-aware.
Traditionally we used the same Var struct to represent the value
of a table column everywhere in parse and plan trees. This choice
predates our support for SQL outer joins, and it's really a pretty
bad idea with outer joins, because the Var's value can depend on
where it is in the tree: it might go to NULL above an outer join.
So expression nodes that are equal() per equalfuncs.c might not
represent the same value, which is a huge correctness hazard for
the planner.
To improve this, decorate Var nodes with a bitmapset showing
which outer joins (identified by RTE indexes) may have nulled
them at the point in the parse tree where the Var appears.
This allows us to trust that equal() Vars represent the same value.
A certain amount of klugery is still needed to cope with cases
where we re-order two outer joins, but it's possible to make it
work without sacrificing that core principle. PlaceHolderVars
receive similar decoration for the same reason.
In the planner, we include these outer join bitmapsets into the relids
that an expression is considered to depend on, and in consequence also
add outer-join relids to the relids of join RelOptInfos. This allows
us to correctly perceive whether an expression can be calculated above
or below a particular outer join.
This change affects FDWs that want to plan foreign joins. They *must*
follow suit when labeling foreign joins in order to match with the
core planner, but for many purposes (if postgres_fdw is any guide)
they'd prefer to consider only base relations within the join.
To support both requirements, redefine ForeignScan.fs_relids as
base+OJ relids, and add a new field fs_base_relids that's set up by
the core planner.
Large though it is, this commit just does the minimum necessary to
install the new mechanisms and get check-world passing again.
Follow-up patches will perform some cleanup. (The README additions
and comments mention some stuff that will appear in the follow-up.)
Patch by me; thanks to Richard Guo for review.
Discussion: https://postgr.es/m/830269.1656693747@sss.pgh.pa.us
Diffstat (limited to 'src/backend/optimizer/plan/setrefs.c')
-rw-r--r-- | src/backend/optimizer/plan/setrefs.c | 234 |
1 files changed, 183 insertions, 51 deletions
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 85ba9d1ca1e..4348bfef601 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -30,11 +30,21 @@ #include "utils/syscache.h" +typedef enum +{ + NRM_EQUAL, /* expect exact match of nullingrels */ + NRM_SUBSET, /* actual Var may have a subset of input */ + NRM_SUPERSET /* actual Var may have a superset of input */ +} NullingRelsMatch; + typedef struct { int varno; /* RT index of Var */ AttrNumber varattno; /* attr number of Var */ AttrNumber resno; /* TLE position of Var */ +#ifdef USE_ASSERT_CHECKING + Bitmapset *varnullingrels; /* Var's varnullingrels */ +#endif } tlist_vinfo; typedef struct @@ -60,6 +70,7 @@ typedef struct indexed_tlist *inner_itlist; Index acceptable_rel; int rtoffset; + NullingRelsMatch nrm_match; double num_exec; } fix_join_expr_context; @@ -69,6 +80,7 @@ typedef struct indexed_tlist *subplan_itlist; int newvarno; int rtoffset; + NullingRelsMatch nrm_match; double num_exec; } fix_upper_expr_context; @@ -159,7 +171,12 @@ static indexed_tlist *build_tlist_index(List *tlist); static Var *search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist, int newvarno, - int rtoffset); + int rtoffset, + NullingRelsMatch nrm_match); +static Var *search_indexed_tlist_for_phv(PlaceHolderVar *phv, + indexed_tlist *itlist, + int newvarno, + NullingRelsMatch nrm_match); static Var *search_indexed_tlist_for_non_var(Expr *node, indexed_tlist *itlist, int newvarno); @@ -172,14 +189,18 @@ static List *fix_join_expr(PlannerInfo *root, indexed_tlist *outer_itlist, indexed_tlist *inner_itlist, Index acceptable_rel, - int rtoffset, double num_exec); + int rtoffset, + NullingRelsMatch nrm_match, + 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, int newvarno, - int rtoffset, double num_exec); + int rtoffset, + NullingRelsMatch nrm_match, + double num_exec); static Node *fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context); static List *set_returning_clause_references(PlannerInfo *root, @@ -1118,13 +1139,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) fix_join_expr(root, splan->onConflictSet, NULL, itlist, linitial_int(splan->resultRelations), - rtoffset, NUM_EXEC_QUAL(plan)); + rtoffset, NRM_EQUAL, NUM_EXEC_QUAL(plan)); splan->onConflictWhere = (Node *) fix_join_expr(root, (List *) splan->onConflictWhere, NULL, itlist, linitial_int(splan->resultRelations), - rtoffset, NUM_EXEC_QUAL(plan)); + rtoffset, NRM_EQUAL, NUM_EXEC_QUAL(plan)); pfree(itlist); @@ -1181,6 +1202,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) NULL, itlist, resultrel, rtoffset, + NRM_EQUAL, NUM_EXEC_TLIST(plan)); /* Fix quals too. */ @@ -1189,6 +1211,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) NULL, itlist, resultrel, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL(plan)); } } @@ -1334,6 +1357,7 @@ set_indexonlyscan_references(PlannerInfo *root, index_itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_TLIST((Plan *) plan)); plan->scan.plan.qual = (List *) fix_upper_expr(root, @@ -1341,6 +1365,7 @@ set_indexonlyscan_references(PlannerInfo *root, index_itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) plan)); plan->recheckqual = (List *) fix_upper_expr(root, @@ -1348,6 +1373,7 @@ set_indexonlyscan_references(PlannerInfo *root, index_itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) plan)); /* indexqual is already transformed to reference index columns */ plan->indexqual = fix_scan_list(root, plan->indexqual, @@ -1554,6 +1580,7 @@ set_foreignscan_references(PlannerInfo *root, itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_TLIST((Plan *) fscan)); fscan->scan.plan.qual = (List *) fix_upper_expr(root, @@ -1561,6 +1588,7 @@ set_foreignscan_references(PlannerInfo *root, itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) fscan)); fscan->fdw_exprs = (List *) fix_upper_expr(root, @@ -1568,6 +1596,7 @@ set_foreignscan_references(PlannerInfo *root, itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) fscan)); fscan->fdw_recheck_quals = (List *) fix_upper_expr(root, @@ -1575,6 +1604,7 @@ set_foreignscan_references(PlannerInfo *root, itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) fscan)); pfree(itlist); /* fdw_scan_tlist itself just needs fix_scan_list() adjustments */ @@ -1603,6 +1633,7 @@ set_foreignscan_references(PlannerInfo *root, } fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset); + fscan->fs_base_relids = offset_relid_set(fscan->fs_base_relids, rtoffset); /* Adjust resultRelation if it's valid */ if (fscan->resultRelation > 0) @@ -1635,6 +1666,7 @@ set_customscan_references(PlannerInfo *root, itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_TLIST((Plan *) cscan)); cscan->scan.plan.qual = (List *) fix_upper_expr(root, @@ -1642,6 +1674,7 @@ set_customscan_references(PlannerInfo *root, itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) cscan)); cscan->custom_exprs = (List *) fix_upper_expr(root, @@ -1649,6 +1682,7 @@ set_customscan_references(PlannerInfo *root, itlist, INDEX_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) cscan)); pfree(itlist); /* custom_scan_tlist itself just needs fix_scan_list() adjustments */ @@ -1835,6 +1869,7 @@ set_hash_references(PlannerInfo *root, Plan *plan, int rtoffset) outer_itlist, OUTER_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL(plan)); /* Hash doesn't project */ @@ -2170,6 +2205,7 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context) /* At scan level, we should always just evaluate the contained expr */ PlaceHolderVar *phv = (PlaceHolderVar *) node; + Assert(phv->phnullingrels == NULL); return fix_scan_expr_mutator((Node *) phv->phexpr, context); } if (IsA(node, AlternativeSubPlan)) @@ -2227,6 +2263,7 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) inner_itlist, (Index) 0, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) join)); /* Now do join-type-specific stuff */ @@ -2239,11 +2276,21 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) { NestLoopParam *nlp = (NestLoopParam *) lfirst(lc); + /* + * Because we don't reparameterize parameterized paths to match + * the outer-join level at which they are used, Vars seen in the + * NestLoopParam expression may have nullingrels that are just a + * subset of those in the Vars actually available from the outer + * side. Not checking this exactly is a bit grotty, but the work + * needed to make things match up perfectly seems well out of + * proportion to the value. + */ nlp->paramval = (Var *) fix_upper_expr(root, (Node *) nlp->paramval, outer_itlist, OUTER_VAR, rtoffset, + NRM_SUBSET, NUM_EXEC_TLIST(outer_plan)); /* Check we replaced any PlaceHolderVar with simple Var */ if (!(IsA(nlp->paramval, Var) && @@ -2261,6 +2308,7 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) inner_itlist, (Index) 0, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) join)); } else if (IsA(join, HashJoin)) @@ -2273,6 +2321,7 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) inner_itlist, (Index) 0, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) join)); /* @@ -2284,45 +2333,27 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) outer_itlist, OUTER_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL((Plan *) join)); } /* * Now we need to fix up the targetlist and qpqual, which are logically - * above the join. This means they should not re-use any input expression - * that was computed in the nullable side of an outer join. Vars and - * PlaceHolderVars are fine, so we can implement this restriction just by - * clearing has_non_vars in the indexed_tlist structs. - * - * XXX This is a grotty workaround for the fact that we don't clearly - * distinguish between a Var appearing below an outer join and the "same" - * Var appearing above it. If we did, we'd not need to hack the matching - * rules this way. + * above the join. This means that, if it's not an inner join, any Vars + * and PHVs appearing here should have nullingrels that include the + * effects of the outer join, ie they will have nullingrels equal to the + * input Vars' nullingrels plus the bit added by the outer join. We don't + * currently have enough info available here to identify what that should + * be, so we just tell fix_join_expr to accept superset nullingrels + * matches instead of exact ones. */ - switch (join->jointype) - { - case JOIN_LEFT: - case JOIN_SEMI: - case JOIN_ANTI: - inner_itlist->has_non_vars = false; - break; - case JOIN_RIGHT: - outer_itlist->has_non_vars = false; - break; - case JOIN_FULL: - outer_itlist->has_non_vars = false; - inner_itlist->has_non_vars = false; - break; - default: - break; - } - join->plan.targetlist = fix_join_expr(root, join->plan.targetlist, outer_itlist, inner_itlist, (Index) 0, rtoffset, + (join->jointype == JOIN_INNER ? NRM_EQUAL : NRM_SUPERSET), NUM_EXEC_TLIST((Plan *) join)); join->plan.qual = fix_join_expr(root, join->plan.qual, @@ -2330,6 +2361,7 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) inner_itlist, (Index) 0, rtoffset, + (join->jointype == JOIN_INNER ? NRM_EQUAL : NRM_SUPERSET), NUM_EXEC_QUAL((Plan *) join)); pfree(outer_itlist); @@ -2384,6 +2416,7 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset) subplan_itlist, OUTER_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_TLIST(plan)); } else @@ -2392,6 +2425,7 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset) subplan_itlist, OUTER_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_TLIST(plan)); tle = flatCopyTargetEntry(tle); tle->expr = (Expr *) newexpr; @@ -2405,6 +2439,7 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset) subplan_itlist, OUTER_VAR, rtoffset, + NRM_EQUAL, NUM_EXEC_QUAL(plan)); pfree(subplan_itlist); @@ -2605,7 +2640,7 @@ set_dummy_tlist_references(Plan *plan, int rtoffset) * tlist_member() searches. * * The result of this function is an indexed_tlist struct to pass to - * search_indexed_tlist_for_var() or search_indexed_tlist_for_non_var(). + * search_indexed_tlist_for_var() and siblings. * When done, the indexed_tlist may be freed with a single pfree(). */ static indexed_tlist * @@ -2637,6 +2672,9 @@ build_tlist_index(List *tlist) vinfo->varno = var->varno; vinfo->varattno = var->varattno; vinfo->resno = tle->resno; +#ifdef USE_ASSERT_CHECKING + vinfo->varnullingrels = var->varnullingrels; +#endif vinfo++; } else if (tle->expr && IsA(tle->expr, PlaceHolderVar)) @@ -2689,6 +2727,9 @@ build_tlist_index_other_vars(List *tlist, int ignore_rel) vinfo->varno = var->varno; vinfo->varattno = var->varattno; vinfo->resno = tle->resno; +#ifdef USE_ASSERT_CHECKING + vinfo->varnullingrels = var->varnullingrels; +#endif vinfo++; } } @@ -2708,10 +2749,17 @@ build_tlist_index_other_vars(List *tlist, int ignore_rel) * modified varno/varattno (to wit, newvarno and the resno of the TLE entry). * Also ensure that varnosyn is incremented by rtoffset. * If no match, return NULL. + * + * In debugging builds, we cross-check the varnullingrels of the subplan + * output Var based on nrm_match. Most call sites should pass NRM_EQUAL + * indicating we expect an exact match. However, there are places where + * we haven't cleaned things up completely, and we have to settle for + * allowing subset or superset matches. */ static Var * search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist, - int newvarno, int rtoffset) + int newvarno, int rtoffset, + NullingRelsMatch nrm_match) { int varno = var->varno; AttrNumber varattno = var->varattno; @@ -2727,6 +2775,27 @@ search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist, /* Found a match */ Var *newvar = copyVar(var); + /* + * Assert that we kept all the nullingrels machinations straight. + * + * XXX we skip the check for system columns and whole-row Vars. + * That's because such Vars might be row identity Vars, which are + * generated without any varnullingrels. It'd be hard to do + * otherwise, since they're normally made very early in planning, + * when we haven't looked at the jointree yet and don't know which + * joins might null such Vars. Doesn't seem worth the expense to + * make them fully valid. (While it's slightly annoying that we + * thereby lose checking for user-written references to such + * columns, it seems unlikely that a bug in nullingrels logic + * would affect only system columns.) + */ + Assert(varattno <= 0 || + (nrm_match == NRM_SUBSET ? + bms_is_subset(var->varnullingrels, vinfo->varnullingrels) : + nrm_match == NRM_SUPERSET ? + bms_is_subset(vinfo->varnullingrels, var->varnullingrels) : + bms_equal(vinfo->varnullingrels, var->varnullingrels))); + newvar->varno = newvarno; newvar->varattno = vinfo->resno; if (newvar->varnosyn > 0) @@ -2739,15 +2808,63 @@ search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist, } /* - * search_indexed_tlist_for_non_var --- find a non-Var in an indexed tlist + * search_indexed_tlist_for_phv --- find a PlaceHolderVar in an indexed tlist * * If a match is found, return a Var constructed to reference the tlist item. * If no match, return NULL. * - * NOTE: it is a waste of time to call this unless itlist->has_ph_vars or - * itlist->has_non_vars. Furthermore, set_join_references() relies on being - * able to prevent matching of non-Vars by clearing itlist->has_non_vars, - * so there's a correctness reason not to call it unless that's set. + * Cross-check phnullingrels as in search_indexed_tlist_for_var. + * + * NOTE: it is a waste of time to call this unless itlist->has_ph_vars. + */ +static Var * +search_indexed_tlist_for_phv(PlaceHolderVar *phv, + indexed_tlist *itlist, int newvarno, + NullingRelsMatch nrm_match) +{ + ListCell *lc; + + foreach(lc, itlist->tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + if (tle->expr && IsA(tle->expr, PlaceHolderVar)) + { + PlaceHolderVar *subphv = (PlaceHolderVar *) tle->expr; + Var *newvar; + + /* + * Analogously to search_indexed_tlist_for_var, we match on phid + * only. We don't use equal(), partially for speed but mostly + * because phnullingrels might not be exactly equal. + */ + if (phv->phid != subphv->phid) + continue; + + /* Assert that we kept all the nullingrels machinations straight */ + Assert(nrm_match == NRM_SUBSET ? + bms_is_subset(phv->phnullingrels, subphv->phnullingrels) : + nrm_match == NRM_SUPERSET ? + bms_is_subset(subphv->phnullingrels, phv->phnullingrels) : + bms_equal(subphv->phnullingrels, phv->phnullingrels)); + + /* Found a matching subplan output expression */ + newvar = makeVarFromTargetEntry(newvarno, tle); + newvar->varnosyn = 0; /* wasn't ever a plain Var */ + newvar->varattnosyn = 0; + return newvar; + } + } + return NULL; /* no match */ +} + +/* + * search_indexed_tlist_for_non_var --- find a non-Var/PHV in an indexed tlist + * + * If a match is found, return a Var constructed to reference the tlist item. + * If no match, return NULL. + * + * NOTE: it is a waste of time to call this unless itlist->has_non_vars. */ static Var * search_indexed_tlist_for_non_var(Expr *node, @@ -2854,6 +2971,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 + * 'nrm_match': as for search_indexed_tlist_for_var() * 'num_exec': estimated number of executions of expression * * Returns the new expression tree. The original clause structure is @@ -2866,6 +2984,7 @@ fix_join_expr(PlannerInfo *root, indexed_tlist *inner_itlist, Index acceptable_rel, int rtoffset, + NullingRelsMatch nrm_match, double num_exec) { fix_join_expr_context context; @@ -2875,6 +2994,7 @@ fix_join_expr(PlannerInfo *root, context.inner_itlist = inner_itlist; context.acceptable_rel = acceptable_rel; context.rtoffset = rtoffset; + context.nrm_match = nrm_match; context.num_exec = num_exec; return (List *) fix_join_expr_mutator((Node *) clauses, &context); } @@ -2896,7 +3016,8 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) newvar = search_indexed_tlist_for_var(var, context->outer_itlist, OUTER_VAR, - context->rtoffset); + context->rtoffset, + context->nrm_match); if (newvar) return (Node *) newvar; } @@ -2907,7 +3028,8 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) newvar = search_indexed_tlist_for_var(var, context->inner_itlist, INNER_VAR, - context->rtoffset); + context->rtoffset, + context->nrm_match); if (newvar) return (Node *) newvar; } @@ -2932,22 +3054,25 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) /* See if the PlaceHolderVar has bubbled up from a lower plan node */ if (context->outer_itlist && context->outer_itlist->has_ph_vars) { - newvar = search_indexed_tlist_for_non_var((Expr *) phv, - context->outer_itlist, - OUTER_VAR); + newvar = search_indexed_tlist_for_phv(phv, + context->outer_itlist, + OUTER_VAR, + context->nrm_match); if (newvar) return (Node *) newvar; } if (context->inner_itlist && context->inner_itlist->has_ph_vars) { - newvar = search_indexed_tlist_for_non_var((Expr *) phv, - context->inner_itlist, - INNER_VAR); + newvar = search_indexed_tlist_for_phv(phv, + context->inner_itlist, + INNER_VAR, + context->nrm_match); if (newvar) return (Node *) newvar; } /* If not supplied by input plans, evaluate the contained expr */ + /* XXX can we assert something about phnullingrels? */ return fix_join_expr_mutator((Node *) phv->phexpr, context); } /* Try matching more complex expressions too, if tlists have any */ @@ -3006,6 +3131,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 + * 'nrm_match': as for search_indexed_tlist_for_var() * 'num_exec': estimated number of executions of expression * * The resulting tree is a copy of the original in which all Var nodes have @@ -3018,6 +3144,7 @@ fix_upper_expr(PlannerInfo *root, indexed_tlist *subplan_itlist, int newvarno, int rtoffset, + NullingRelsMatch nrm_match, double num_exec) { fix_upper_expr_context context; @@ -3026,6 +3153,7 @@ fix_upper_expr(PlannerInfo *root, context.subplan_itlist = subplan_itlist; context.newvarno = newvarno; context.rtoffset = rtoffset; + context.nrm_match = nrm_match; context.num_exec = num_exec; return fix_upper_expr_mutator(node, &context); } @@ -3044,7 +3172,8 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context) newvar = search_indexed_tlist_for_var(var, context->subplan_itlist, context->newvarno, - context->rtoffset); + context->rtoffset, + context->nrm_match); if (!newvar) elog(ERROR, "variable not found in subplan target list"); return (Node *) newvar; @@ -3056,13 +3185,15 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context) /* See if the PlaceHolderVar has bubbled up from a lower plan node */ if (context->subplan_itlist->has_ph_vars) { - newvar = search_indexed_tlist_for_non_var((Expr *) phv, - context->subplan_itlist, - context->newvarno); + newvar = search_indexed_tlist_for_phv(phv, + context->subplan_itlist, + context->newvarno, + context->nrm_match); if (newvar) return (Node *) newvar; } /* If not supplied by input plan, evaluate the contained expr */ + /* XXX can we assert something about phnullingrels? */ return fix_upper_expr_mutator((Node *) phv->phexpr, context); } /* Try matching more complex expressions too, if tlist has any */ @@ -3169,6 +3300,7 @@ set_returning_clause_references(PlannerInfo *root, NULL, resultRelation, rtoffset, + NRM_EQUAL, NUM_EXEC_TLIST(topplan)); pfree(itlist); |