mirror of
https://github.com/postgres/postgres.git
synced 2025-07-07 00:36:50 +03:00
Avoid crash in partitionwise join planning under GEQO.
While trying to plan a partitionwise join, we may be faced with cases where one or both input partitions for a particular segment of the join have been pruned away. In HEAD and v11, this is problematic because earlier processing didn't bother to make a pruned RelOptInfo fully valid. With an upcoming patch to make partition pruning more efficient, this'll be even more problematic because said RelOptInfo won't exist at all. The existing code attempts to deal with this by retroactively making the RelOptInfo fully valid, but that causes crashes under GEQO because join planning is done in a short-lived memory context. In v11 we could probably have fixed this by switching to the planner's main context while fixing up the RelOptInfo, but that idea doesn't scale well to the upcoming patch. It would be better not to mess with the base-relation data structures during join planning, anyway --- that's just a recipe for order-of-operations bugs. In many cases, though, we don't actually need the child RelOptInfo, because if the input is certainly empty then the join segment's result is certainly empty, so we can skip making a join plan altogether. (The existing code ultimately arrives at the same conclusion, but only after doing a lot more work.) This approach works except when the pruned-away partition is on the nullable side of a LEFT, ANTI, or FULL join, and the other side isn't pruned. But in those cases the existing code leaves a lot to be desired anyway --- the correct output is just the result of the unpruned side of the join, but we were emitting a useless outer join against a dummy Result. Pending somebody writing code to handle that more nicely, let's just abandon the partitionwise-join optimization in such cases. When the modified code skips making a join plan, it doesn't make a join RelOptInfo either; this requires some upper-level code to cope with nulls in part_rels[] arrays. We would have had to have that anyway after the upcoming patch. Back-patch to v11 since the crash is demonstrable there. Discussion: https://postgr.es/m/8305.1553884377@sss.pgh.pa.us
This commit is contained in:
@ -1166,11 +1166,11 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
|
||||
* for partitioned child rels.
|
||||
*
|
||||
* Note: here we abuse the consider_partitionwise_join flag by setting
|
||||
* it *even* for child rels that are not partitioned. In that case,
|
||||
* we set it to tell try_partitionwise_join() that it doesn't need to
|
||||
* generate their targetlists and EC entries as they have already been
|
||||
* generated here, as opposed to the dummy child rels for which the
|
||||
* flag is left set to false so that it will generate them.
|
||||
* it for child rels that are not themselves partitioned. We do so to
|
||||
* tell try_partitionwise_join() that the child rel is sufficiently
|
||||
* valid to be used as a per-partition input, even if it later gets
|
||||
* proven to be dummy. (It's not usable until we've set up the
|
||||
* reltarget and EC entries, which we just did.)
|
||||
*/
|
||||
if (rel->consider_partitionwise_join)
|
||||
childrel->consider_partitionwise_join = true;
|
||||
@ -3551,7 +3551,9 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel)
|
||||
{
|
||||
RelOptInfo *child_rel = part_rels[cnt_parts];
|
||||
|
||||
Assert(child_rel != NULL);
|
||||
/* If it's been pruned entirely, it's certainly dummy. */
|
||||
if (child_rel == NULL)
|
||||
continue;
|
||||
|
||||
/* Add partitionwise join paths for partitioned child-joins. */
|
||||
generate_partitionwise_join_paths(root, child_rel);
|
||||
|
@ -43,8 +43,6 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1,
|
||||
RelOptInfo *rel2, RelOptInfo *joinrel,
|
||||
SpecialJoinInfo *parent_sjinfo,
|
||||
List *parent_restrictlist);
|
||||
static void update_child_rel_info(PlannerInfo *root,
|
||||
RelOptInfo *rel, RelOptInfo *childrel);
|
||||
static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel,
|
||||
bool strict_op);
|
||||
|
||||
@ -1401,6 +1399,10 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
|
||||
{
|
||||
RelOptInfo *child_rel1 = rel1->part_rels[cnt_parts];
|
||||
RelOptInfo *child_rel2 = rel2->part_rels[cnt_parts];
|
||||
bool rel1_empty = (child_rel1 == NULL ||
|
||||
IS_DUMMY_REL(child_rel1));
|
||||
bool rel2_empty = (child_rel2 == NULL ||
|
||||
IS_DUMMY_REL(child_rel2));
|
||||
SpecialJoinInfo *child_sjinfo;
|
||||
List *child_restrictlist;
|
||||
RelOptInfo *child_joinrel;
|
||||
@ -1409,24 +1411,69 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
|
||||
int nappinfos;
|
||||
|
||||
/*
|
||||
* If a child table has consider_partitionwise_join=false, it means
|
||||
* Check for cases where we can prove that this segment of the join
|
||||
* returns no rows, due to one or both inputs being empty (including
|
||||
* inputs that have been pruned away entirely). If so just ignore it.
|
||||
* These rules are equivalent to populate_joinrel_with_paths's rules
|
||||
* for dummy input relations.
|
||||
*/
|
||||
switch (parent_sjinfo->jointype)
|
||||
{
|
||||
case JOIN_INNER:
|
||||
case JOIN_SEMI:
|
||||
if (rel1_empty || rel2_empty)
|
||||
continue; /* ignore this join segment */
|
||||
break;
|
||||
case JOIN_LEFT:
|
||||
case JOIN_ANTI:
|
||||
if (rel1_empty)
|
||||
continue; /* ignore this join segment */
|
||||
break;
|
||||
case JOIN_FULL:
|
||||
if (rel1_empty && rel2_empty)
|
||||
continue; /* ignore this join segment */
|
||||
break;
|
||||
default:
|
||||
/* other values not expected here */
|
||||
elog(ERROR, "unrecognized join type: %d",
|
||||
(int) parent_sjinfo->jointype);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a child has been pruned entirely then we can't generate paths
|
||||
* for it, so we have to reject partitionwise joining unless we were
|
||||
* able to eliminate this partition above.
|
||||
*/
|
||||
if (child_rel1 == NULL || child_rel2 == NULL)
|
||||
{
|
||||
/*
|
||||
* Mark the joinrel as unpartitioned so that later functions treat
|
||||
* it correctly.
|
||||
*/
|
||||
joinrel->nparts = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a leaf relation has consider_partitionwise_join=false, it means
|
||||
* that it's a dummy relation for which we skipped setting up tlist
|
||||
* expressions and adding EC members in set_append_rel_size(), so do
|
||||
* that now for use later.
|
||||
* expressions and adding EC members in set_append_rel_size(), so
|
||||
* again we have to fail here.
|
||||
*/
|
||||
if (rel1_is_simple && !child_rel1->consider_partitionwise_join)
|
||||
{
|
||||
Assert(child_rel1->reloptkind == RELOPT_OTHER_MEMBER_REL);
|
||||
Assert(IS_DUMMY_REL(child_rel1));
|
||||
update_child_rel_info(root, rel1, child_rel1);
|
||||
child_rel1->consider_partitionwise_join = true;
|
||||
joinrel->nparts = 0;
|
||||
return;
|
||||
}
|
||||
if (rel2_is_simple && !child_rel2->consider_partitionwise_join)
|
||||
{
|
||||
Assert(child_rel2->reloptkind == RELOPT_OTHER_MEMBER_REL);
|
||||
Assert(IS_DUMMY_REL(child_rel2));
|
||||
update_child_rel_info(root, rel2, child_rel2);
|
||||
child_rel2->consider_partitionwise_join = true;
|
||||
joinrel->nparts = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* We should never try to join two overlapping sets of rels. */
|
||||
@ -1470,28 +1517,6 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up tlist expressions for the childrel, and add EC members referencing
|
||||
* the childrel.
|
||||
*/
|
||||
static void
|
||||
update_child_rel_info(PlannerInfo *root,
|
||||
RelOptInfo *rel, RelOptInfo *childrel)
|
||||
{
|
||||
AppendRelInfo *appinfo = root->append_rel_array[childrel->relid];
|
||||
|
||||
/* Make child tlist expressions */
|
||||
childrel->reltarget->exprs = (List *)
|
||||
adjust_appendrel_attrs(root,
|
||||
(Node *) rel->reltarget->exprs,
|
||||
1, &appinfo);
|
||||
|
||||
/* Make child entries in the EquivalenceClass as well */
|
||||
if (rel->has_eclass_joins || has_useful_pathkeys(root, rel))
|
||||
add_child_rel_equivalences(root, appinfo, rel, childrel);
|
||||
childrel->has_eclass_joins = rel->has_eclass_joins;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if there exists an equi-join condition for each pair of
|
||||
* partition keys from given relations being joined.
|
||||
|
@ -6963,6 +6963,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
|
||||
List *child_scanjoin_targets = NIL;
|
||||
ListCell *lc;
|
||||
|
||||
/* Pruned or dummy children can be ignored. */
|
||||
if (child_rel == NULL || IS_DUMMY_REL(child_rel))
|
||||
continue;
|
||||
|
||||
/* Translate scan/join targets for this child. */
|
||||
appinfos = find_appinfos_by_relids(root, child_rel->relids,
|
||||
&nappinfos);
|
||||
@ -7063,8 +7067,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root,
|
||||
RelOptInfo *child_grouped_rel;
|
||||
RelOptInfo *child_partially_grouped_rel;
|
||||
|
||||
/* Input child rel must have a path */
|
||||
Assert(child_input_rel->pathlist != NIL);
|
||||
/* Pruned or dummy children can be ignored. */
|
||||
if (child_input_rel == NULL || IS_DUMMY_REL(child_input_rel))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Copy the given "extra" structure as is and then override the
|
||||
@ -7106,14 +7111,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root,
|
||||
extra->target_parallel_safe,
|
||||
child_extra.havingQual);
|
||||
|
||||
/* Ignore empty children. They contribute nothing. */
|
||||
if (IS_DUMMY_REL(child_input_rel))
|
||||
{
|
||||
mark_dummy_rel(child_grouped_rel);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Create grouping paths for this child relation. */
|
||||
create_ordinary_grouping_paths(root, child_input_rel,
|
||||
child_grouped_rel,
|
||||
|
Reference in New Issue
Block a user