Commit a87c7291 authored by Tom Lane's avatar Tom Lane

Fix EquivalenceClass processing for nested append relations.

The original coding of EquivalenceClasses didn't foresee that appendrel
child relations might themselves be appendrels; but this is possible for
example when a UNION ALL subquery scans a table with inheritance children.
The oversight led to failure to optimize ordering-related issues very well
for the grandchild tables.  After some false starts involving explicitly
flattening the appendrel representation, we found that this could be fixed
easily by removing a few implicit assumptions about appendrel parent rels
not being children themselves.

Kyotaro Horiguchi and Tom Lane, reviewed by Noah Misch
parent b777be0d
...@@ -1021,10 +1021,15 @@ get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel, ...@@ -1021,10 +1021,15 @@ get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel,
* accumulate_append_subpath * accumulate_append_subpath
* Add a subpath to the list being built for an Append or MergeAppend * Add a subpath to the list being built for an Append or MergeAppend
* *
* It's possible that the child is itself an Append path, in which case * It's possible that the child is itself an Append or MergeAppend path, in
* we can "cut out the middleman" and just add its child paths to our * which case we can "cut out the middleman" and just add its child paths to
* own list. (We don't try to do this earlier because we need to * our own list. (We don't try to do this earlier because we need to apply
* apply both levels of transformation to the quals.) * both levels of transformation to the quals.)
*
* Note that if we omit a child MergeAppend in this way, we are effectively
* omitting a sort step, which seems fine: if the parent is to be an Append,
* its result would be unsorted anyway, while if the parent is to be a
* MergeAppend, there's no point in a separate sort on a child.
*/ */
static List * static List *
accumulate_append_subpath(List *subpaths, Path *path) accumulate_append_subpath(List *subpaths, Path *path)
...@@ -1036,6 +1041,13 @@ accumulate_append_subpath(List *subpaths, Path *path) ...@@ -1036,6 +1041,13 @@ accumulate_append_subpath(List *subpaths, Path *path)
/* list_copy is important here to avoid sharing list substructure */ /* list_copy is important here to avoid sharing list substructure */
return list_concat(subpaths, list_copy(apath->subpaths)); return list_concat(subpaths, list_copy(apath->subpaths));
} }
else if (IsA(path, MergeAppendPath))
{
MergeAppendPath *mpath = (MergeAppendPath *) path;
/* list_copy is important here to avoid sharing list substructure */
return list_concat(subpaths, list_copy(mpath->subpaths));
}
else else
return lappend(subpaths, path); return lappend(subpaths, path);
} }
......
...@@ -1937,16 +1937,20 @@ add_child_rel_equivalences(PlannerInfo *root, ...@@ -1937,16 +1937,20 @@ add_child_rel_equivalences(PlannerInfo *root,
if (cur_ec->ec_has_volatile) if (cur_ec->ec_has_volatile)
continue; continue;
/* No point in searching if parent rel not mentioned in eclass */ /*
if (!bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) * No point in searching if parent rel not mentioned in eclass; but
* we can't tell that for sure if parent rel is itself a child.
*/
if (parent_rel->reloptkind == RELOPT_BASEREL &&
!bms_is_subset(parent_rel->relids, cur_ec->ec_relids))
continue; continue;
foreach(lc2, cur_ec->ec_members) foreach(lc2, cur_ec->ec_members)
{ {
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
if (cur_em->em_is_const || cur_em->em_is_child) if (cur_em->em_is_const)
continue; /* ignore consts and children here */ continue; /* ignore consts here */
/* Does it reference parent_rel? */ /* Does it reference parent_rel? */
if (bms_overlap(cur_em->em_relids, parent_rel->relids)) if (bms_overlap(cur_em->em_relids, parent_rel->relids))
......
...@@ -751,7 +751,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) ...@@ -751,7 +751,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
/* Compute sort column info, and adjust MergeAppend's tlist as needed */ /* Compute sort column info, and adjust MergeAppend's tlist as needed */
(void) prepare_sort_from_pathkeys(root, plan, pathkeys, (void) prepare_sort_from_pathkeys(root, plan, pathkeys,
NULL, best_path->path.parent->relids,
NULL, NULL,
true, true,
&node->numCols, &node->numCols,
......
...@@ -502,9 +502,56 @@ explain (costs off) ...@@ -502,9 +502,56 @@ explain (costs off)
Index Cond: (ab = 'ab'::text) Index Cond: (ab = 'ab'::text)
(7 rows) (7 rows)
--
-- Test that ORDER BY for UNION ALL can be pushed down to inheritance
-- children.
--
CREATE TEMP TABLE t1c (b text, a text);
ALTER TABLE t1c INHERIT t1;
CREATE TEMP TABLE t2c (primary key (ab)) INHERITS (t2);
INSERT INTO t1c VALUES ('v', 'w'), ('c', 'd'), ('m', 'n'), ('e', 'f');
INSERT INTO t2c VALUES ('vw'), ('cd'), ('mn'), ('ef');
CREATE INDEX t1c_ab_idx on t1c ((a || b));
set enable_seqscan = on;
set enable_indexonlyscan = off;
explain (costs off)
SELECT * FROM
(SELECT a || b AS ab FROM t1
UNION ALL
SELECT ab FROM t2) t
ORDER BY 1 LIMIT 8;
QUERY PLAN
------------------------------------------------
Limit
-> Merge Append
Sort Key: ((t1.a || t1.b))
-> Index Scan using t1_ab_idx on t1
-> Index Scan using t1c_ab_idx on t1c
-> Index Scan using t2_pkey on t2
-> Index Scan using t2c_pkey on t2c
(7 rows)
SELECT * FROM
(SELECT a || b AS ab FROM t1
UNION ALL
SELECT ab FROM t2) t
ORDER BY 1 LIMIT 8;
ab
----
ab
ab
cd
dc
ef
fe
mn
nm
(8 rows)
reset enable_seqscan; reset enable_seqscan;
reset enable_indexscan; reset enable_indexscan;
reset enable_bitmapscan; reset enable_bitmapscan;
reset enable_indexonlyscan;
-- Test constraint exclusion of UNION ALL subqueries -- Test constraint exclusion of UNION ALL subqueries
explain (costs off) explain (costs off)
SELECT * FROM SELECT * FROM
......
...@@ -198,9 +198,38 @@ explain (costs off) ...@@ -198,9 +198,38 @@ explain (costs off)
SELECT * FROM t2) t SELECT * FROM t2) t
WHERE ab = 'ab'; WHERE ab = 'ab';
--
-- Test that ORDER BY for UNION ALL can be pushed down to inheritance
-- children.
--
CREATE TEMP TABLE t1c (b text, a text);
ALTER TABLE t1c INHERIT t1;
CREATE TEMP TABLE t2c (primary key (ab)) INHERITS (t2);
INSERT INTO t1c VALUES ('v', 'w'), ('c', 'd'), ('m', 'n'), ('e', 'f');
INSERT INTO t2c VALUES ('vw'), ('cd'), ('mn'), ('ef');
CREATE INDEX t1c_ab_idx on t1c ((a || b));
set enable_seqscan = on;
set enable_indexonlyscan = off;
explain (costs off)
SELECT * FROM
(SELECT a || b AS ab FROM t1
UNION ALL
SELECT ab FROM t2) t
ORDER BY 1 LIMIT 8;
SELECT * FROM
(SELECT a || b AS ab FROM t1
UNION ALL
SELECT ab FROM t2) t
ORDER BY 1 LIMIT 8;
reset enable_seqscan; reset enable_seqscan;
reset enable_indexscan; reset enable_indexscan;
reset enable_bitmapscan; reset enable_bitmapscan;
reset enable_indexonlyscan;
-- Test constraint exclusion of UNION ALL subqueries -- Test constraint exclusion of UNION ALL subqueries
explain (costs off) explain (costs off)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment