Fix calculation of relid sets for partitionwise child joins.

Applying add_outer_joins_to_relids() to a child join doesn't actually
work, even if we've built a SpecialJoinInfo specialized to the child,
because that function will also compare the join's relids to elements
of the main join_info_list, which only deal in regular relids not
child relids.  This mistake escaped detection by the existing
partitionwise join tests because they didn't test any cases where
add_outer_joins_to_relids() needs to add additional OJ relids (that
is, any cases where join reordering per identity 3 is possible).

Instead, let's apply adjust_child_relids() to the relids of the parent
join.  This requires minor code reordering to collect the relevant
AppendRelInfo structures first, but that's work we'd do shortly anyway.

Report and fix by Richard Guo; cosmetic changes by me

Discussion: https://postgr.es/m/CAMbWs49NCNbyubZWgci3o=_OTY=snCfAPtMnM-32f3mm-K-Ckw@mail.gmail.com
This commit is contained in:
Tom Lane 2023-07-21 12:00:14 -04:00
parent 7c7412cae3
commit 3c90dcd039
4 changed files with 75 additions and 12 deletions

View File

@ -1640,13 +1640,15 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
child_rel1->relids, child_rel1->relids,
child_rel2->relids); child_rel2->relids);
/* Build correct join relids for child join */
child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids);
child_joinrelids = add_outer_joins_to_relids(root, child_joinrelids,
child_sjinfo, NULL);
/* Find the AppendRelInfo structures */ /* Find the AppendRelInfo structures */
appinfos = find_appinfos_by_relids(root, child_joinrelids, &nappinfos); appinfos = find_appinfos_by_relids(root,
bms_union(child_rel1->relids,
child_rel2->relids),
&nappinfos);
/* Build correct join relids for child join */
child_joinrelids = adjust_child_relids(joinrel->relids,
nappinfos, appinfos);
/* /*
* Construct restrictions applicable to the child join from those * Construct restrictions applicable to the child join from those

View File

@ -871,10 +871,19 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
/* The parent joinrel should have consider_partitionwise_join set. */ /* The parent joinrel should have consider_partitionwise_join set. */
Assert(parent_joinrel->consider_partitionwise_join); Assert(parent_joinrel->consider_partitionwise_join);
/*
* Find the AppendRelInfo structures for the child baserels. We'll need
* these for computing the child join's relid set, and later for mapping
* Vars to the child rel.
*/
appinfos = find_appinfos_by_relids(root,
bms_union(outer_rel->relids,
inner_rel->relids),
&nappinfos);
joinrel->reloptkind = RELOPT_OTHER_JOINREL; joinrel->reloptkind = RELOPT_OTHER_JOINREL;
joinrel->relids = bms_union(outer_rel->relids, inner_rel->relids); joinrel->relids = adjust_child_relids(parent_joinrel->relids,
joinrel->relids = add_outer_joins_to_relids(root, joinrel->relids, sjinfo, nappinfos, appinfos);
NULL);
joinrel->rows = 0; joinrel->rows = 0;
/* cheap startup cost is interesting iff not all tuples to be retrieved */ /* cheap startup cost is interesting iff not all tuples to be retrieved */
joinrel->consider_startup = (root->tuple_fraction > 0); joinrel->consider_startup = (root->tuple_fraction > 0);
@ -935,9 +944,6 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
/* Compute information relevant to foreign relations. */ /* Compute information relevant to foreign relations. */
set_foreign_rel_properties(joinrel, outer_rel, inner_rel); set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
/* Compute information needed for mapping Vars to the child rel */
appinfos = find_appinfos_by_relids(root, joinrel->relids, &nappinfos);
/* Set up reltarget struct */ /* Set up reltarget struct */
build_child_join_reltarget(root, parent_joinrel, joinrel, build_child_join_reltarget(root, parent_joinrel, joinrel,
nappinfos, appinfos); nappinfos, appinfos);

View File

@ -62,6 +62,52 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
450 | 0450 | 450 | 0450 450 | 0450 | 450 | 0450
(4 rows) (4 rows)
-- left outer join, 3-way
EXPLAIN (COSTS OFF)
SELECT COUNT(*) FROM prt1 t1
LEFT JOIN prt1 t2 ON t1.a = t2.a
LEFT JOIN prt1 t3 ON t2.a = t3.a;
QUERY PLAN
--------------------------------------------------------
Aggregate
-> Append
-> Hash Left Join
Hash Cond: (t2_1.a = t3_1.a)
-> Hash Left Join
Hash Cond: (t1_1.a = t2_1.a)
-> Seq Scan on prt1_p1 t1_1
-> Hash
-> Seq Scan on prt1_p1 t2_1
-> Hash
-> Seq Scan on prt1_p1 t3_1
-> Hash Left Join
Hash Cond: (t2_2.a = t3_2.a)
-> Hash Left Join
Hash Cond: (t1_2.a = t2_2.a)
-> Seq Scan on prt1_p2 t1_2
-> Hash
-> Seq Scan on prt1_p2 t2_2
-> Hash
-> Seq Scan on prt1_p2 t3_2
-> Hash Left Join
Hash Cond: (t2_3.a = t3_3.a)
-> Hash Left Join
Hash Cond: (t1_3.a = t2_3.a)
-> Seq Scan on prt1_p3 t1_3
-> Hash
-> Seq Scan on prt1_p3 t2_3
-> Hash
-> Seq Scan on prt1_p3 t3_3
(29 rows)
SELECT COUNT(*) FROM prt1 t1
LEFT JOIN prt1 t2 ON t1.a = t2.a
LEFT JOIN prt1 t3 ON t2.a = t3.a;
count
-------
300
(1 row)
-- left outer join, with whole-row reference; partitionwise join does not apply -- left outer join, with whole-row reference; partitionwise join does not apply
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b;

View File

@ -34,6 +34,15 @@ EXPLAIN (COSTS OFF)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
-- left outer join, 3-way
EXPLAIN (COSTS OFF)
SELECT COUNT(*) FROM prt1 t1
LEFT JOIN prt1 t2 ON t1.a = t2.a
LEFT JOIN prt1 t3 ON t2.a = t3.a;
SELECT COUNT(*) FROM prt1 t1
LEFT JOIN prt1 t2 ON t1.a = t2.a
LEFT JOIN prt1 t3 ON t2.a = t3.a;
-- left outer join, with whole-row reference; partitionwise join does not apply -- left outer join, with whole-row reference; partitionwise join does not apply
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b;