aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/prep/prepjointree.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2021-01-21 15:37:23 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2021-01-21 15:37:23 -0500
commit55dc86eca70b1dc18a79c141b3567efed910329d (patch)
tree24edd3fe9c8d4017595782b980b6bc2191643e91 /src/backend/optimizer/prep/prepjointree.c
parent920f853dc948b98a5dc96580c4ee011a302e33e4 (diff)
downloadpostgresql-55dc86eca70b1dc18a79c141b3567efed910329d.tar.gz
postgresql-55dc86eca70b1dc18a79c141b3567efed910329d.zip
Fix pull_varnos' miscomputation of relids set for a PlaceHolderVar.
Previously, pull_varnos() took the relids of a PlaceHolderVar as being equal to the relids in its contents, but that fails to account for the possibility that we have to postpone evaluation of the PHV due to outer joins. This could result in a malformed plan. The known cases end up triggering the "failed to assign all NestLoopParams to plan nodes" sanity check in createplan.c, but other symptoms may be possible. The right value to use is the join level we actually intend to evaluate the PHV at. We can get that from the ph_eval_at field of the associated PlaceHolderInfo. However, there are some places that call pull_varnos() before the PlaceHolderInfos have been created; in that case, fall back to the conservative assumption that the PHV will be evaluated at its syntactic level. (In principle this might result in missing some legal optimization, but I'm not aware of any cases where it's an issue in practice.) Things are also a bit ticklish for calls occurring during deconstruct_jointree(), but AFAICS the ph_eval_at fields should have reached their final values by the time we need them. The main problem in making this work is that pull_varnos() has no way to get at the PlaceHolderInfos. We can fix that easily, if a bit tediously, in HEAD by passing it the planner "root" pointer. In the back branches that'd cause an unacceptable API/ABI break for extensions, so leave the existing entry points alone and add new ones with the additional parameter. (If an old entry point is called and encounters a PHV, it'll fall back to using the syntactic level, again possibly missing some valid optimization.) Back-patch to v12. The computation is surely also wrong before that, but it appears that we cannot reach a bad plan thanks to join order restrictions imposed on the subquery that the PlaceHolderVar came from. The error only became reachable when commit 4be058fe9 allowed trivial subqueries to be collapsed out completely, eliminating their join order restrictions. Per report from Stephan Springl. Discussion: https://postgr.es/m/171041.1610849523@sss.pgh.pa.us
Diffstat (limited to 'src/backend/optimizer/prep/prepjointree.c')
-rw-r--r--src/backend/optimizer/prep/prepjointree.c40
1 files changed, 25 insertions, 15 deletions
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index f9ef96991df..d961592e015 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -82,7 +82,8 @@ static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root,
int childRToffset);
static void make_setop_translation_list(Query *query, Index newvarno,
AppendRelInfo *appinfo);
-static bool is_simple_subquery(Query *subquery, RangeTblEntry *rte,
+static bool is_simple_subquery(PlannerInfo *root, Query *subquery,
+ RangeTblEntry *rte,
JoinExpr *lowest_outer_join);
static Node *pull_up_simple_values(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte);
@@ -95,7 +96,8 @@ static bool is_simple_union_all(Query *subquery);
static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery,
List *colTypes);
static bool is_safe_append_member(Query *subquery);
-static bool jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted,
+static bool jointree_contains_lateral_outer_refs(PlannerInfo *root,
+ Node *jtnode, bool restricted,
Relids safe_upper_varnos);
static void perform_pullup_replace_vars(PlannerInfo *root,
pullup_replace_vars_context *rvcontext,
@@ -744,7 +746,7 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
* unless is_safe_append_member says so.
*/
if (rte->rtekind == RTE_SUBQUERY &&
- is_simple_subquery(rte->subquery, rte, lowest_outer_join) &&
+ is_simple_subquery(root, rte->subquery, rte, lowest_outer_join) &&
(containing_appendrel == NULL ||
is_safe_append_member(rte->subquery)))
return pull_up_simple_subquery(root, jtnode, rte,
@@ -973,7 +975,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* easier just to keep this "if" looking the same as the one in
* pull_up_subqueries_recurse.
*/
- if (is_simple_subquery(subquery, rte, lowest_outer_join) &&
+ if (is_simple_subquery(root, subquery, rte, lowest_outer_join) &&
(containing_appendrel == NULL || is_safe_append_member(subquery)))
{
/* good to go */
@@ -1398,7 +1400,7 @@ make_setop_translation_list(Query *query, Index newvarno,
* lowest_outer_join is the lowest outer join above the subquery, or NULL.
*/
static bool
-is_simple_subquery(Query *subquery, RangeTblEntry *rte,
+is_simple_subquery(PlannerInfo *root, Query *subquery, RangeTblEntry *rte,
JoinExpr *lowest_outer_join)
{
/*
@@ -1477,7 +1479,8 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
safe_upper_varnos = NULL; /* doesn't matter */
}
- if (jointree_contains_lateral_outer_refs((Node *) subquery->jointree,
+ if (jointree_contains_lateral_outer_refs(root,
+ (Node *) subquery->jointree,
restricted, safe_upper_varnos))
return false;
@@ -1496,7 +1499,9 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
*/
if (lowest_outer_join != NULL)
{
- Relids lvarnos = pull_varnos_of_level((Node *) subquery->targetList, 1);
+ Relids lvarnos = pull_varnos_of_level(root,
+ (Node *) subquery->targetList,
+ 1);
if (!bms_is_subset(lvarnos, safe_upper_varnos))
return false;
@@ -1929,7 +1934,8 @@ is_safe_append_member(Query *subquery)
* in safe_upper_varnos.
*/
static bool
-jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted,
+jointree_contains_lateral_outer_refs(PlannerInfo *root, Node *jtnode,
+ bool restricted,
Relids safe_upper_varnos)
{
if (jtnode == NULL)
@@ -1944,7 +1950,8 @@ jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted,
/* First, recurse to check child joins */
foreach(l, f->fromlist)
{
- if (jointree_contains_lateral_outer_refs(lfirst(l),
+ if (jointree_contains_lateral_outer_refs(root,
+ lfirst(l),
restricted,
safe_upper_varnos))
return true;
@@ -1952,7 +1959,7 @@ jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted,
/* Then check the top-level quals */
if (restricted &&
- !bms_is_subset(pull_varnos_of_level(f->quals, 1),
+ !bms_is_subset(pull_varnos_of_level(root, f->quals, 1),
safe_upper_varnos))
return true;
}
@@ -1971,18 +1978,20 @@ jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted,
}
/* Check the child joins */
- if (jointree_contains_lateral_outer_refs(j->larg,
+ if (jointree_contains_lateral_outer_refs(root,
+ j->larg,
restricted,
safe_upper_varnos))
return true;
- if (jointree_contains_lateral_outer_refs(j->rarg,
+ if (jointree_contains_lateral_outer_refs(root,
+ j->rarg,
restricted,
safe_upper_varnos))
return true;
/* Check the JOIN's qual clauses */
if (restricted &&
- !bms_is_subset(pull_varnos_of_level(j->quals, 1),
+ !bms_is_subset(pull_varnos_of_level(root, j->quals, 1),
safe_upper_varnos))
return true;
}
@@ -2366,7 +2375,8 @@ pullup_replace_vars_callback(Var *var,
* level-zero var must belong to the subquery.
*/
if ((rcon->target_rte->lateral ?
- bms_overlap(pull_varnos((Node *) newnode), rcon->relids) :
+ bms_overlap(pull_varnos(rcon->root, (Node *) newnode),
+ rcon->relids) :
contain_vars_of_level((Node *) newnode, 0)) &&
!contain_nonstrict_functions((Node *) newnode))
{
@@ -2804,7 +2814,7 @@ reduce_outer_joins_pass2(Node *jtnode,
overlap = list_intersection(local_nonnullable_vars,
forced_null_vars);
if (overlap != NIL &&
- bms_overlap(pull_varnos((Node *) overlap),
+ bms_overlap(pull_varnos(root, (Node *) overlap),
right_state->relids))
jointype = JOIN_ANTI;
}