pg_partwise_agg_WIP.patch
application/x-download
Filename: pg_partwise_agg_WIP.patch
Type: application/x-download
Part: 0
Message:
Partition-wise aggregation/grouping
Patch
Format: unified
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/deparse.c | 1 | 0 |
| contrib/postgres_fdw/expected/postgres_fdw.out | 112 | 0 |
| contrib/postgres_fdw/postgres_fdw.c | 13 | 10 |
| contrib/postgres_fdw/postgres_fdw.h | 1 | 0 |
| contrib/postgres_fdw/sql/postgres_fdw.sql | 44 | 0 |
| src/backend/optimizer/path/allpaths.c | 1 | 3 |
| src/backend/optimizer/path/costsize.c | 1 | 0 |
| src/backend/optimizer/plan/createplan.c | 9 | 10 |
| src/backend/optimizer/plan/planner.c | 259 | 6 |
| src/backend/utils/misc/guc.c | 9 | 0 |
| src/include/foreign/fdwapi.h | 2 | 1 |
| src/include/optimizer/cost.h | 1 | 0 |
| src/include/optimizer/paths.h | 2 | 0 |
| src/test/regress/expected/partition_agg.out | 192 | 0 |
| src/test/regress/expected/partition_join.out | 39 | 34 |
| src/test/regress/expected/sysviews.out | 2 | 1 |
| src/test/regress/parallel_schedule | 4 | 0 |
| src/test/regress/sql/partition_agg.sql | 48 | 0 |
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 6ef1e48..8388ea7 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -1064,6 +1064,7 @@ deparseFromExpr(List *quals, deparse_expr_cxt *context)
/* For upper relations, scanrel must be either a joinrel or a baserel */
Assert(context->foreignrel->reloptkind != RELOPT_UPPER_REL ||
IS_JOIN_REL(scanrel) ||
+ scanrel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
scanrel->reloptkind == RELOPT_BASEREL);
/* Construct FROM clause */
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 059c5c3..d968832 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -7181,3 +7181,115 @@ AND ftoptions @> array['fetch_size=60000'];
(1 row)
ROLLBACK;
+-- Partition-wise aggregates with FDW
+CREATE TABLE plt1 (a int, b int, c text) PARTITION BY RANGE(a);
+CREATE TABLE plt1_p1 (a int, b int, c text);
+CREATE TABLE plt1_p2 (a int, b int, c text);
+CREATE TABLE plt1_p3 (a int, b int, c text);
+INSERT INTO plt1_p1 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 10;
+INSERT INTO plt1_p2 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 20 and (i % 30) >= 10;
+INSERT INTO plt1_p3 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 30 and (i % 30) >= 20;
+-- Create foreign partitions
+CREATE FOREIGN TABLE fplt1_p1 PARTITION OF plt1 FOR VALUES FROM (0) TO (10) SERVER loopback OPTIONS (table_name 'plt1_p1');
+CREATE FOREIGN TABLE fplt1_p2 PARTITION OF plt1 FOR VALUES FROM (10) TO (20) SERVER loopback OPTIONS (table_name 'plt1_p2');;
+CREATE FOREIGN TABLE fplt1_p3 PARTITION OF plt1 FOR VALUES FROM (20) TO (30) SERVER loopback OPTIONS (table_name 'plt1_p3');;
+ANALYZE plt1;
+ANALYZE fplt1_p1;
+ANALYZE fplt1_p2;
+ANALYZE fplt1_p3;
+-- When GROUP BY clause matches with PARTITION KEY.
+-- Plan when partition-wise-agg is disabled
+SET enable_partition_wise_agg TO false;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT a, sum(b), min(b), count(*) FROM plt1 GROUP BY a ORDER BY 1;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Sort
+ Output: plt1.a, (sum(plt1.b)), (min(plt1.b)), (count(*))
+ Sort Key: plt1.a
+ -> HashAggregate
+ Output: plt1.a, sum(plt1.b), min(plt1.b), count(*)
+ Group Key: plt1.a
+ -> Append
+ -> Seq Scan on public.plt1
+ Output: plt1.a, plt1.b
+ -> Foreign Scan on public.fplt1_p1
+ Output: fplt1_p1.a, fplt1_p1.b
+ Remote SQL: SELECT a, b FROM public.plt1_p1
+ -> Foreign Scan on public.fplt1_p2
+ Output: fplt1_p2.a, fplt1_p2.b
+ Remote SQL: SELECT a, b FROM public.plt1_p2
+ -> Foreign Scan on public.fplt1_p3
+ Output: fplt1_p3.a, fplt1_p3.b
+ Remote SQL: SELECT a, b FROM public.plt1_p3
+(18 rows)
+
+-- Plan when partition-wise-agg is enabled
+SET enable_partition_wise_agg TO true;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT a, sum(b), min(b), count(*) FROM plt1 GROUP BY a ORDER BY 1;
+NOTICE: partition-wise grouping is possible.
+ QUERY PLAN
+---------------------------------------------------------------------------------------------
+ Sort
+ Output: fplt1_p1.a, (sum(fplt1_p1.b)), (min(fplt1_p1.b)), (count(*))
+ Sort Key: fplt1_p1.a
+ -> Append
+ -> Foreign Scan
+ Output: fplt1_p1.a, (sum(fplt1_p1.b)), (min(fplt1_p1.b)), (count(*))
+ Relations: Aggregate on (public.fplt1_p1 plt1)
+ Remote SQL: SELECT a, sum(b), min(b), count(*) FROM public.plt1_p1 GROUP BY a
+ -> Foreign Scan
+ Output: fplt1_p2.a, (sum(fplt1_p2.b)), (min(fplt1_p2.b)), (count(*))
+ Relations: Aggregate on (public.fplt1_p2 plt1)
+ Remote SQL: SELECT a, sum(b), min(b), count(*) FROM public.plt1_p2 GROUP BY a
+ -> Foreign Scan
+ Output: fplt1_p3.a, (sum(fplt1_p3.b)), (min(fplt1_p3.b)), (count(*))
+ Relations: Aggregate on (public.fplt1_p3 plt1)
+ Remote SQL: SELECT a, sum(b), min(b), count(*) FROM public.plt1_p3 GROUP BY a
+(16 rows)
+
+SELECT a, sum(b), min(b), count(*) FROM plt1 GROUP BY a ORDER BY 1;
+NOTICE: partition-wise grouping is possible.
+ a | sum | min | count
+----+------+-----+-------
+ 0 | 2000 | 0 | 100
+ 1 | 2100 | 1 | 100
+ 2 | 2200 | 2 | 100
+ 3 | 2300 | 3 | 100
+ 4 | 2400 | 4 | 100
+ 5 | 2500 | 5 | 100
+ 6 | 2600 | 6 | 100
+ 7 | 2700 | 7 | 100
+ 8 | 2800 | 8 | 100
+ 9 | 2900 | 9 | 100
+ 10 | 2000 | 0 | 100
+ 11 | 2100 | 1 | 100
+ 12 | 2200 | 2 | 100
+ 13 | 2300 | 3 | 100
+ 14 | 2400 | 4 | 100
+ 15 | 2500 | 5 | 100
+ 16 | 2600 | 6 | 100
+ 17 | 2700 | 7 | 100
+ 18 | 2800 | 8 | 100
+ 19 | 2900 | 9 | 100
+ 20 | 2000 | 0 | 100
+ 21 | 2100 | 1 | 100
+ 22 | 2200 | 2 | 100
+ 23 | 2300 | 3 | 100
+ 24 | 2400 | 4 | 100
+ 25 | 2500 | 5 | 100
+ 26 | 2600 | 6 | 100
+ 27 | 2700 | 7 | 100
+ 28 | 2800 | 8 | 100
+ 29 | 2900 | 9 | 100
+(30 rows)
+
+-- Clean-up
+DROP FOREIGN TABLE fplt1_p3;
+DROP FOREIGN TABLE fplt1_p2;
+DROP FOREIGN TABLE fplt1_p1;
+DROP TABLE plt1_p3;
+DROP TABLE plt1_p2;
+DROP TABLE plt1_p1;
+DROP TABLE plt1;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 22acba8..630c374 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -349,7 +349,8 @@ static bool postgresRecheckForeignScan(ForeignScanState *node,
static void postgresGetForeignUpperPaths(PlannerInfo *root,
UpperRelationKind stage,
RelOptInfo *input_rel,
- RelOptInfo *output_rel);
+ RelOptInfo *output_rel,
+ PathTarget *target);
/*
* Helper functions
@@ -415,7 +416,8 @@ static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
Path *epq_path);
static void add_foreign_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
- RelOptInfo *grouped_rel);
+ RelOptInfo *grouped_rel,
+ PathTarget *target);
/*
@@ -2688,7 +2690,7 @@ estimate_path_cost_size(PlannerInfo *root,
else if (foreignrel->reloptkind == RELOPT_UPPER_REL)
{
PgFdwRelationInfo *ofpinfo;
- PathTarget *ptarget = root->upper_targets[UPPERREL_GROUP_AGG];
+ PathTarget *ptarget = fpinfo->grouped_target;
AggClauseCosts aggcosts;
double input_rows;
int numGroupCols;
@@ -4536,7 +4538,7 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel)
* different from those in the plan's targetlist. Use a copy of path
* target to record the new sortgrouprefs.
*/
- grouping_target = copy_pathtarget(root->upper_targets[UPPERREL_GROUP_AGG]);
+ grouping_target = copy_pathtarget(fpinfo->grouped_target);
/*
* Evaluate grouping targets and check whether they are safe to push down
@@ -4715,7 +4717,8 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel)
*/
static void
postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage,
- RelOptInfo *input_rel, RelOptInfo *output_rel)
+ RelOptInfo *input_rel, RelOptInfo *output_rel,
+ PathTarget *target)
{
PgFdwRelationInfo *fpinfo;
@@ -4735,7 +4738,7 @@ postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage,
fpinfo->pushdown_safe = false;
output_rel->fdw_private = fpinfo;
- add_foreign_grouping_paths(root, input_rel, output_rel);
+ add_foreign_grouping_paths(root, input_rel, output_rel, target);
}
/*
@@ -4747,13 +4750,12 @@ postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage,
*/
static void
add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
- RelOptInfo *grouped_rel)
+ RelOptInfo *grouped_rel, PathTarget *target)
{
Query *parse = root->parse;
PgFdwRelationInfo *ifpinfo = input_rel->fdw_private;
PgFdwRelationInfo *fpinfo = grouped_rel->fdw_private;
ForeignPath *grouppath;
- PathTarget *grouping_target;
double rows;
int width;
Cost startup_cost;
@@ -4764,7 +4766,8 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
!root->hasHavingQual)
return;
- grouping_target = root->upper_targets[UPPERREL_GROUP_AGG];
+ /* Store passed-in target in fpinfo for later use */
+ fpinfo->grouped_target = target;
/* save the input_rel as outerrel in fpinfo */
fpinfo->outerrel = input_rel;
@@ -4795,7 +4798,7 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
/* Create and add foreign path to the grouping relation. */
grouppath = create_foreignscan_path(root,
grouped_rel,
- grouping_target,
+ target,
rows,
startup_cost,
total_cost,
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 57dbb79..99cecb7 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -95,6 +95,7 @@ typedef struct PgFdwRelationInfo
/* Grouping information */
List *grouped_tlist;
+ PathTarget *grouped_target;
/* Subquery information */
bool make_outerrel_subquery; /* do we deparse outerrel as a
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 8f3edc1..f02ec8a 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -1706,3 +1706,47 @@ WHERE ftrelid = 'table30000'::regclass
AND ftoptions @> array['fetch_size=60000'];
ROLLBACK;
+
+
+-- Partition-wise aggregates with FDW
+CREATE TABLE plt1 (a int, b int, c text) PARTITION BY RANGE(a);
+
+CREATE TABLE plt1_p1 (a int, b int, c text);
+CREATE TABLE plt1_p2 (a int, b int, c text);
+CREATE TABLE plt1_p3 (a int, b int, c text);
+
+INSERT INTO plt1_p1 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 10;
+INSERT INTO plt1_p2 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 20 and (i % 30) >= 10;
+INSERT INTO plt1_p3 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 30 and (i % 30) >= 20;
+
+-- Create foreign partitions
+CREATE FOREIGN TABLE fplt1_p1 PARTITION OF plt1 FOR VALUES FROM (0) TO (10) SERVER loopback OPTIONS (table_name 'plt1_p1');
+CREATE FOREIGN TABLE fplt1_p2 PARTITION OF plt1 FOR VALUES FROM (10) TO (20) SERVER loopback OPTIONS (table_name 'plt1_p2');;
+CREATE FOREIGN TABLE fplt1_p3 PARTITION OF plt1 FOR VALUES FROM (20) TO (30) SERVER loopback OPTIONS (table_name 'plt1_p3');;
+
+ANALYZE plt1;
+ANALYZE fplt1_p1;
+ANALYZE fplt1_p2;
+ANALYZE fplt1_p3;
+
+-- When GROUP BY clause matches with PARTITION KEY.
+-- Plan when partition-wise-agg is disabled
+SET enable_partition_wise_agg TO false;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT a, sum(b), min(b), count(*) FROM plt1 GROUP BY a ORDER BY 1;
+
+-- Plan when partition-wise-agg is enabled
+SET enable_partition_wise_agg TO true;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT a, sum(b), min(b), count(*) FROM plt1 GROUP BY a ORDER BY 1;
+SELECT a, sum(b), min(b), count(*) FROM plt1 GROUP BY a ORDER BY 1;
+
+
+-- Clean-up
+DROP FOREIGN TABLE fplt1_p3;
+DROP FOREIGN TABLE fplt1_p2;
+DROP FOREIGN TABLE fplt1_p1;
+DROP TABLE plt1_p3;
+DROP TABLE plt1_p2;
+DROP TABLE plt1_p1;
+DROP TABLE plt1;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8143f80..0ab5c56 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -130,8 +130,6 @@ static void subquery_push_qual(Query *subquery,
static void recurse_push_qual(Node *setOp, Query *topquery,
RangeTblEntry *rte, Index rti, Node *qual);
static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel);
-static void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
- List *live_childrels);
/*
@@ -1338,7 +1336,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
* parameterization or ordering. Similarly it collects partial paths from
* non-dummy children to create partial append paths.
*/
-static void
+void
add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels)
{
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 220c81c..b2edaca 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -128,6 +128,7 @@ bool enable_mergejoin = true;
bool enable_hashjoin = true;
bool enable_gathermerge = true;
bool enable_partition_wise_join = false;
+bool enable_partition_wise_agg = true;
typedef struct
{
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 0aae4ca..929e0a6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1610,6 +1610,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
{
Sort *plan;
Plan *subplan;
+ Relids relids;
/*
* We don't want any excess columns in the sorted tuples, so request a
@@ -1619,7 +1620,12 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
- plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys, NULL);
+ /*
+ * TODO: we need to fix something here. The "other" upper rels are not
+ * marked as "OTHER" rels and may not have relids.
+ */
+ relids = IS_OTHER_REL(best_path->subpath->parent) ? best_path->path.parent->relids : NULL;
+ plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys, relids);
copy_generic_path_info(&plan->plan, (Path *) best_path);
@@ -3393,15 +3399,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
/* Copy foreign server OID; likewise, no need to make FDW do this */
scan_plan->fs_server = rel->serverid;
- /*
- * Likewise, copy the relids that are represented by this foreign scan. An
- * upper rel doesn't have relids set, but it covers all the base relations
- * participating in the underlying scan, so use root's all_baserels.
- */
- if (rel->reloptkind == RELOPT_UPPER_REL)
- scan_plan->fs_relids = root->all_baserels;
- else
- scan_plan->fs_relids = best_path->path.parent->relids;
+ /* Likewise, copy the relids from Path to Plan */
+ scan_plan->fs_relids = best_path->path.parent->relids;
/*
* If this is a foreign join, and to make it valid to push down we had to
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 17fae4f..9f7db45 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -157,6 +157,15 @@ static PathTarget *make_sort_input_target(PlannerInfo *root,
bool *have_postponed_srfs);
static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
List *targets, List *targets_contain_srfs);
+static void try_partition_wise_grouping(PlannerInfo *root,
+ RelOptInfo *input_rel,
+ RelOptInfo *grouped_rel,
+ PathTarget *target,
+ const AggClauseCosts *agg_costs,
+ List *rollup_lists,
+ List *rollup_groupclauses);
+static bool have_grouping_by_partkey(RelOptInfo *input_rel, PathTarget *target,
+ List *groupClause);
/*****************************************************************************
@@ -2067,7 +2076,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
if (final_rel->fdwroutine &&
final_rel->fdwroutine->GetForeignUpperPaths)
final_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_FINAL,
- current_rel, final_rel);
+ current_rel, final_rel,
+ NULL);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
@@ -3283,7 +3293,18 @@ create_grouping_paths(PlannerInfo *root,
ListCell *lc;
/* For now, do all work in the (GROUP_AGG, NULL) upperrel */
- grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
+ if (IS_OTHER_REL(input_rel))
+ {
+
+ /*
+ * TODO: We should mark these rels as "other upper" rels similar to
+ * "other" join and base relations.
+ */
+ grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG,
+ input_rel->relids);
+ }
+ else
+ grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
/*
* If the input relation is not parallel-safe, then the grouped relation
@@ -3303,6 +3324,9 @@ create_grouping_paths(PlannerInfo *root,
grouped_rel->useridiscurrent = input_rel->useridiscurrent;
grouped_rel->fdwroutine = input_rel->fdwroutine;
+ /* Copy input rels's relids to grouped rel */
+ grouped_rel->relids = input_rel->relids;
+
/*
* Check for degenerate grouping.
*/
@@ -3377,6 +3401,34 @@ create_grouping_paths(PlannerInfo *root,
rollup_groupclauses);
/*
+ * Number of groups estimated above is based on parent relation. However
+ * we need to estimate the number of groups for the child. For that we
+ * must know the number of partitions. Find that and devise new estimate
+ * for number of groups.
+ *
+ * FIXME: We might need to do this in get_number_of_groups() itself. But
+ * not sure at this time. Need to revise the logic.
+ */
+ if (IS_OTHER_REL(input_rel))
+ {
+ RelOptInfo *rel;
+
+ /* Find top-most parent rel */
+ if (IS_JOIN_REL(input_rel))
+ rel = find_join_rel(root, input_rel->top_parent_relids);
+ else
+ rel = find_base_rel(root,
+ bms_singleton_member(input_rel->top_parent_relids));
+
+ /*
+ * Divide estimated number of groups by number of children to get
+ * number of groups estimate for child rel.
+ */
+ if (rel->part_scheme->nparts > 0)
+ dNumGroups = clamp_row_est(dNumGroups / rel->part_scheme->nparts);
+ }
+
+ /*
* Determine whether it's possible to perform sort-based implementations
* of grouping. (Note that if groupClause is empty,
* grouping_is_sortable() is trivially true, and all the
@@ -3441,6 +3493,11 @@ create_grouping_paths(PlannerInfo *root,
/* Insufficient support for partial mode. */
try_parallel_aggregation = false;
}
+ else if (IS_OTHER_REL(input_rel))
+ {
+ /* TODO: enable parallel query for partition-wise grouping. */
+ try_parallel_aggregation = false;
+ }
else
{
/* Everything looks good. */
@@ -3855,13 +3912,21 @@ create_grouping_paths(PlannerInfo *root,
errdetail("Some of the datatypes only support hashing, while others only support sorting.")));
/*
+ * If input relation is partitioned check if we can perform partition-wise
+ * grouping and/or aggregation.
+ */
+ try_partition_wise_grouping(root, input_rel, grouped_rel, target,
+ agg_costs, rollup_lists, rollup_groupclauses);
+
+ /*
* If there is an FDW that's responsible for all baserels of the query,
* let it consider adding ForeignPaths.
*/
if (grouped_rel->fdwroutine &&
grouped_rel->fdwroutine->GetForeignUpperPaths)
grouped_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_GROUP_AGG,
- input_rel, grouped_rel);
+ input_rel, grouped_rel,
+ target);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
@@ -3885,6 +3950,191 @@ create_grouping_paths(PlannerInfo *root,
}
/*
+ * If the input relation is partitioned and the partition keys are leading
+ * group by clauses, each partition produces a different set of groups.
+ * Aggregates within each such group can be computed partition-wise. This
+ * might be optimal because of presence of suitable paths with pathkeys or
+ * because the hash tables for most of the partitions fit in the memory.
+ */
+static void
+try_partition_wise_grouping(PlannerInfo *root,
+ RelOptInfo *input_rel,
+ RelOptInfo *grouped_rel,
+ PathTarget *target,
+ const AggClauseCosts *agg_costs,
+ List *rollup_lists,
+ List *rollup_groupclauses)
+{
+ Query *query = root->parse;
+ int nparts;
+ int cnt_parts;
+ PartitionScheme part_scheme = input_rel->part_scheme;
+ RelOptInfo **part_rels;
+ List *live_children = NIL;
+ PathTarget *scanjoin_target;
+ ListCell *lc;
+
+ /* Nothing to do, if user disabled partition-wise aggregation. */
+ if (!enable_partition_wise_agg)
+ return;
+
+ /* Do not handle grouping sets for now. */
+ if (rollup_groupclauses || rollup_lists)
+ return;
+
+ /* Nothing to do, if the input relation is not partitioned. */
+ if (!part_scheme)
+ return;
+
+ Assert(input_rel->part_rels);
+
+ /*
+ * TODO: for now do nothing if partition keys are not leading group by
+ * clauses. In general we may calculate partial aggregates for each
+ * partition and combine them.
+ */
+ if (!have_grouping_by_partkey(input_rel, target, query->groupClause))
+ return;
+
+ /* TODO: should be removed in final version */
+ elog(NOTICE, "partition-wise grouping is possible.");
+
+ nparts = part_scheme->nparts;
+ grouped_rel->part_scheme = input_rel->part_scheme;
+ part_rels = (RelOptInfo **) palloc(nparts * sizeof(RelOptInfo *));
+ grouped_rel->part_rels = part_rels;
+
+ /* Add paths for partition-wise aggregation/grouping. */
+ for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
+ {
+ RelOptInfo *input_child_rel = input_rel->part_rels[cnt_parts];
+ PathTarget *child_target = copy_pathtarget(target);
+ List *appinfos = find_appinfos_by_relids(root,
+ input_child_rel->relids);
+
+ /*
+ * Now that there can be multiple grouping relations, if we have to
+ * manage those in the root, we need separate identifiers for those.
+ * What better identifier than the input relids themselves?
+ */
+ part_rels[cnt_parts] = fetch_upper_rel(root, UPPERREL_GROUP_AGG,
+ input_child_rel->relids);
+
+ /* Ignore empty children. They contribute nothing. */
+ if (IS_DUMMY_REL(input_child_rel))
+ {
+ mark_dummy_rel(part_rels[cnt_parts]);
+ continue;
+ }
+ else
+ live_children = lappend(live_children, part_rels[cnt_parts]);
+
+ /*
+ * Forcibly apply scan/join target to all the Paths for the scan/join
+ * rel.
+ *
+ * In principle we should re-run set_cheapest() here to identify the
+ * cheapest path, but it seems unlikely that adding the same tlist
+ * eval costs to all the paths would change that, so we don't bother.
+ * Instead, just assume that the cheapest-startup and cheapest-total
+ * paths remain so. (There should be no parameterized paths anymore,
+ * so we needn't worry about updating cheapest_parameterized_paths.)
+ */
+ scanjoin_target = copy_pathtarget(input_rel->cheapest_startup_path->pathtarget);
+ scanjoin_target->exprs = (List *) adjust_appendrel_attrs(root, (Node *) scanjoin_target->exprs,
+ appinfos);
+
+ foreach(lc, input_child_rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+ Path *path;
+
+ Assert(subpath->param_info == NULL);
+ path = apply_projection_to_path(root, input_child_rel,
+ subpath, scanjoin_target);
+ /* If we had to add a Result, path is different from subpath */
+ if (path != subpath)
+ {
+ lfirst(lc) = path;
+ if (subpath == input_child_rel->cheapest_startup_path)
+ input_child_rel->cheapest_startup_path = path;
+ if (subpath == input_child_rel->cheapest_total_path)
+ input_child_rel->cheapest_total_path = path;
+ }
+ }
+
+ /*
+ * TODO:
+ * We should somehow make this target available for FDWs, which are
+ * expected to fetch it directly from root->upper_targets. That array
+ * can hold only one target for each kind of upper rel. We will now
+ * have many such upper relations.
+ */
+ child_target->exprs = (List *) adjust_appendrel_attrs(root,
+ (Node *) target->exprs,
+ appinfos);
+
+ create_grouping_paths(root, input_child_rel, child_target, agg_costs,
+ rollup_lists, rollup_groupclauses);
+
+ }
+
+ /*
+ * add_paths_to_append_rel() sets the path target from the given relation.
+ * In this case grouped_rel doesn't have a target set. So temporarily set
+ * it.
+ * TODO: probably we should do something better than this.
+ */
+ grouped_rel->reltarget = target;
+ add_paths_to_append_rel(root, grouped_rel, live_children);
+ grouped_rel->reltarget = NULL;
+}
+
+/*
+ * Returns true if partition keys of the given relation are leading group by
+ * clauses.
+ */
+static bool
+have_grouping_by_partkey(RelOptInfo *input_rel, PathTarget *target,
+ List *groupClause)
+{
+ PartitionScheme part_scheme = input_rel->part_scheme;
+ ListCell *lc;
+ List *tlist = make_tlist_from_pathtarget(target);
+ List *group_exprs = get_sortgrouplist_exprs(groupClause, tlist);
+ int cnt_pk = 0;
+ int num_pks;
+
+ /* Input relation should be partitioned. */
+ Assert(part_scheme);
+
+ num_pks = part_scheme->partnatts;
+
+ foreach(lc, group_exprs)
+ {
+ Expr *group_expr = lfirst(lc);
+ List *pk_exprs;
+
+ /* All partition keys are present in the group clause. */
+ if (cnt_pk >= num_pks)
+ return true;
+
+ pk_exprs = input_rel->partexprs[cnt_pk];
+
+ if (!list_member(pk_exprs, group_expr))
+ return false;
+
+ cnt_pk++;
+ }
+
+ /* All partition keys are present in the group clause. */
+ if (cnt_pk >= num_pks)
+ return true;
+
+ return false;
+}
+
+/*
* create_window_paths
*
* Build a new upperrel containing Paths for window-function evaluation.
@@ -3959,7 +4209,8 @@ create_window_paths(PlannerInfo *root,
if (window_rel->fdwroutine &&
window_rel->fdwroutine->GetForeignUpperPaths)
window_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_WINDOW,
- input_rel, window_rel);
+ input_rel, window_rel,
+ NULL);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
@@ -4263,7 +4514,8 @@ create_distinct_paths(PlannerInfo *root,
if (distinct_rel->fdwroutine &&
distinct_rel->fdwroutine->GetForeignUpperPaths)
distinct_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_DISTINCT,
- input_rel, distinct_rel);
+ input_rel, distinct_rel,
+ NULL);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
@@ -4405,7 +4657,8 @@ create_ordered_paths(PlannerInfo *root,
if (ordered_rel->fdwroutine &&
ordered_rel->fdwroutine->GetForeignUpperPaths)
ordered_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_ORDERED,
- input_rel, ordered_rel);
+ input_rel, ordered_rel,
+ NULL);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 7f423c9..0cce5e4 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -920,6 +920,15 @@ static struct config_bool ConfigureNamesBool[] =
false,
NULL, NULL, NULL
},
+ {
+ {"enable_partition_wise_agg", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables partition-wise aggregation and grouping."),
+ NULL
+ },
+ &enable_partition_wise_agg,
+ true,
+ NULL, NULL, NULL
+ },
{
{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 6ca44f7..10fdf6d 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -62,7 +62,8 @@ typedef void (*GetForeignJoinPaths_function) (PlannerInfo *root,
typedef void (*GetForeignUpperPaths_function) (PlannerInfo *root,
UpperRelationKind stage,
RelOptInfo *input_rel,
- RelOptInfo *output_rel);
+ RelOptInfo *output_rel,
+ PathTarget *target);
typedef void (*AddForeignUpdateTargets_function) (Query *parsetree,
RangeTblEntry *target_rte,
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index e7949d37..7cd8e2c 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -68,6 +68,7 @@ extern bool enable_mergejoin;
extern bool enable_hashjoin;
extern bool enable_gathermerge;
extern bool enable_partition_wise_join;
+extern bool enable_partition_wise_agg;
extern int constraint_exclusion;
extern double clamp_row_est(double nrows);
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index f31b70e..77388cc 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -60,6 +60,8 @@ extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
Path *bitmapqual);
extern void generate_partition_wise_join_paths(PlannerInfo *root,
RelOptInfo *rel);
+extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
+ List *live_childrels);
#ifdef OPTIMIZER_DEBUG
extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
diff --git a/src/test/regress/expected/partition_agg.out b/src/test/regress/expected/partition_agg.out
new file mode 100644
index 0000000..315d396
--- /dev/null
+++ b/src/test/regress/expected/partition_agg.out
@@ -0,0 +1,192 @@
+--
+-- PARTITION_AGG
+-- Test partition-wise aggregation on partitioned tables
+--
+-- Enable partition-wise join, which by default is disabled.
+SET enable_partition_wise_join TO true;
+--
+-- Tests for list partitioned tables.
+--
+CREATE TABLE ptab1 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE ptab1_p1 PARTITION OF ptab1 FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE ptab1_p2 PARTITION OF ptab1 FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE ptab1_p3 PARTITION OF ptab1 FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO ptab1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE ptab1;
+ANALYZE ptab1_p1;
+ANALYZE ptab1_p2;
+ANALYZE ptab1_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uptab1 AS SELECT * FROM ptab1;
+ANALYZE uptab1;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT c, sum(a), avg(b), COUNT(*) FROM ptab1 GROUP BY c ORDER BY 1, 2, 3;
+NOTICE: partition-wise grouping is possible.
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Sort
+ Output: ptab1_p1.c, (sum(ptab1_p1.a)), (avg(ptab1_p1.b)), (count(*))
+ Sort Key: ptab1_p1.c, (sum(ptab1_p1.a)), (avg(ptab1_p1.b))
+ -> Append
+ -> HashAggregate
+ Output: ptab1_p1.c, sum(ptab1_p1.a), avg(ptab1_p1.b), count(*)
+ Group Key: ptab1_p1.c
+ -> Seq Scan on public.ptab1_p1
+ Output: ptab1_p1.c, ptab1_p1.a, ptab1_p1.b
+ -> HashAggregate
+ Output: ptab1_p2.c, sum(ptab1_p2.a), avg(ptab1_p2.b), count(*)
+ Group Key: ptab1_p2.c
+ -> Seq Scan on public.ptab1_p2
+ Output: ptab1_p2.c, ptab1_p2.a, ptab1_p2.b
+ -> HashAggregate
+ Output: ptab1_p3.c, sum(ptab1_p3.a), avg(ptab1_p3.b), count(*)
+ Group Key: ptab1_p3.c
+ -> Seq Scan on public.ptab1_p3
+ Output: ptab1_p3.c, ptab1_p3.a, ptab1_p3.b
+(19 rows)
+
+SELECT c, sum(a), avg(b), COUNT(*) FROM ptab1 GROUP BY c ORDER BY 1, 2, 3;
+NOTICE: partition-wise grouping is possible.
+ c | sum | avg | count
+------+-------+----------------------+-------
+ 0000 | 600 | 24.0000000000000000 | 25
+ 0001 | 1850 | 74.0000000000000000 | 25
+ 0002 | 3100 | 124.0000000000000000 | 25
+ 0003 | 4350 | 174.0000000000000000 | 25
+ 0004 | 5600 | 224.0000000000000000 | 25
+ 0005 | 6850 | 274.0000000000000000 | 25
+ 0006 | 8100 | 324.0000000000000000 | 25
+ 0007 | 9350 | 374.0000000000000000 | 25
+ 0008 | 10600 | 424.0000000000000000 | 25
+ 0009 | 11850 | 474.0000000000000000 | 25
+ 0010 | 13100 | 524.0000000000000000 | 25
+ 0011 | 14350 | 574.0000000000000000 | 25
+(12 rows)
+
+SELECT c, sum(a), avg(b), COUNT(*) FROM uptab1 GROUP BY c ORDER BY 1, 2, 3;
+ c | sum | avg | count
+------+-------+----------------------+-------
+ 0000 | 600 | 24.0000000000000000 | 25
+ 0001 | 1850 | 74.0000000000000000 | 25
+ 0002 | 3100 | 124.0000000000000000 | 25
+ 0003 | 4350 | 174.0000000000000000 | 25
+ 0004 | 5600 | 224.0000000000000000 | 25
+ 0005 | 6850 | 274.0000000000000000 | 25
+ 0006 | 8100 | 324.0000000000000000 | 25
+ 0007 | 9350 | 374.0000000000000000 | 25
+ 0008 | 10600 | 424.0000000000000000 | 25
+ 0009 | 11850 | 474.0000000000000000 | 25
+ 0010 | 13100 | 524.0000000000000000 | 25
+ 0011 | 14350 | 574.0000000000000000 | 25
+(12 rows)
+
+-- JOIN query
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.c, sum(t1.a), avg(t1.b), COUNT(*) FROM ptab1 t1, ptab1 t2 WHERE t1.c = t2.c GROUP BY t1.c ORDER BY 1, 2, 3;
+NOTICE: partition-wise grouping is possible.
+ QUERY PLAN
+------------------------------------------------------------------
+ Sort
+ Output: t1.c, (sum(t1.a)), (avg(t1.b)), (count(*))
+ Sort Key: t1.c, (sum(t1.a)), (avg(t1.b))
+ -> Append
+ -> HashAggregate
+ Output: t1.c, sum(t1.a), avg(t1.b), count(*)
+ Group Key: t1.c
+ -> Hash Join
+ Output: t1.c, t1.a, t1.b
+ Hash Cond: (t1.c = t2.c)
+ -> Seq Scan on public.ptab1_p1 t1
+ Output: t1.c, t1.a, t1.b
+ -> Hash
+ Output: t2.c
+ -> Seq Scan on public.ptab1_p1 t2
+ Output: t2.c
+ -> HashAggregate
+ Output: t1_1.c, sum(t1_1.a), avg(t1_1.b), count(*)
+ Group Key: t1_1.c
+ -> Hash Join
+ Output: t1_1.c, t1_1.a, t1_1.b
+ Hash Cond: (t1_1.c = t2_1.c)
+ -> Seq Scan on public.ptab1_p2 t1_1
+ Output: t1_1.c, t1_1.a, t1_1.b
+ -> Hash
+ Output: t2_1.c
+ -> Seq Scan on public.ptab1_p2 t2_1
+ Output: t2_1.c
+ -> HashAggregate
+ Output: t1_2.c, sum(t1_2.a), avg(t1_2.b), count(*)
+ Group Key: t1_2.c
+ -> Hash Join
+ Output: t1_2.c, t1_2.a, t1_2.b
+ Hash Cond: (t1_2.c = t2_2.c)
+ -> Seq Scan on public.ptab1_p3 t1_2
+ Output: t1_2.c, t1_2.a, t1_2.b
+ -> Hash
+ Output: t2_2.c
+ -> Seq Scan on public.ptab1_p3 t2_2
+ Output: t2_2.c
+(40 rows)
+
+SELECT t1.c, sum(t1.a), avg(t1.b), COUNT(*) FROM ptab1 t1, ptab1 t2 WHERE t1.c = t2.c GROUP BY t1.c ORDER BY 1, 2, 3;
+NOTICE: partition-wise grouping is possible.
+ c | sum | avg | count
+------+--------+----------------------+-------
+ 0000 | 15000 | 24.0000000000000000 | 625
+ 0001 | 46250 | 74.0000000000000000 | 625
+ 0002 | 77500 | 124.0000000000000000 | 625
+ 0003 | 108750 | 174.0000000000000000 | 625
+ 0004 | 140000 | 224.0000000000000000 | 625
+ 0005 | 171250 | 274.0000000000000000 | 625
+ 0006 | 202500 | 324.0000000000000000 | 625
+ 0007 | 233750 | 374.0000000000000000 | 625
+ 0008 | 265000 | 424.0000000000000000 | 625
+ 0009 | 296250 | 474.0000000000000000 | 625
+ 0010 | 327500 | 524.0000000000000000 | 625
+ 0011 | 358750 | 574.0000000000000000 | 625
+(12 rows)
+
+SELECT t1.c, sum(t1.a), avg(t1.b), COUNT(*) FROM uptab1 t1, uptab1 t2 WHERE t1.c = t2.c GROUP BY t1.c ORDER BY 1, 2, 3;
+ c | sum | avg | count
+------+--------+----------------------+-------
+ 0000 | 15000 | 24.0000000000000000 | 625
+ 0001 | 46250 | 74.0000000000000000 | 625
+ 0002 | 77500 | 124.0000000000000000 | 625
+ 0003 | 108750 | 174.0000000000000000 | 625
+ 0004 | 140000 | 224.0000000000000000 | 625
+ 0005 | 171250 | 274.0000000000000000 | 625
+ 0006 | 202500 | 324.0000000000000000 | 625
+ 0007 | 233750 | 374.0000000000000000 | 625
+ 0008 | 265000 | 424.0000000000000000 | 625
+ 0009 | 296250 | 474.0000000000000000 | 625
+ 0010 | 327500 | 524.0000000000000000 | 625
+ 0011 | 358750 | 574.0000000000000000 | 625
+(12 rows)
+
+-- Negative testcase
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT COUNT(*) FROM ptab1 GROUP BY a;
+ QUERY PLAN
+-----------------------------------------
+ HashAggregate
+ Output: count(*), ptab1.a
+ Group Key: ptab1.a
+ -> Append
+ -> Seq Scan on public.ptab1
+ Output: ptab1.a
+ -> Seq Scan on public.ptab1_p1
+ Output: ptab1_p1.a
+ -> Seq Scan on public.ptab1_p2
+ Output: ptab1_p2.a
+ -> Seq Scan on public.ptab1_p3
+ Output: ptab1_p3.a
+(12 rows)
+
+-- Cleanup
+DROP TABLE uptab1;
+DROP TABLE ptab1_p3;
+DROP TABLE ptab1_p2;
+DROP TABLE ptab1_p1;
+DROP TABLE ptab1;
+RESET enable_partition_wise_join;
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 27f09fa..67c2041 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -1399,44 +1399,49 @@ ANALYZE plt1_e;
-- test partition matching with N-way join
EXPLAIN (COSTS OFF)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
- QUERY PLAN
---------------------------------------------------------------------------------------
+NOTICE: partition-wise grouping is possible.
+ QUERY PLAN
+--------------------------------------------------------------------------------
Sort
Sort Key: t1.c, t3.c
- -> HashAggregate
- Group Key: t1.c, t2.c, t3.c
- -> Result
- -> Append
- -> Hash Join
- Hash Cond: (t1.c = t2.c)
- -> Seq Scan on plt1_p1 t1
- -> Hash
- -> Hash Join
- Hash Cond: (t2.c = ltrim(t3.c, 'A'::text))
- -> Seq Scan on plt2_p1 t2
- -> Hash
- -> Seq Scan on plt1_e_p1 t3
- -> Hash Join
- Hash Cond: (t1_1.c = t2_1.c)
- -> Seq Scan on plt1_p2 t1_1
- -> Hash
- -> Hash Join
- Hash Cond: (t2_1.c = ltrim(t3_1.c, 'A'::text))
- -> Seq Scan on plt2_p2 t2_1
- -> Hash
- -> Seq Scan on plt1_e_p2 t3_1
- -> Hash Join
- Hash Cond: (t1_2.c = t2_2.c)
- -> Seq Scan on plt1_p3 t1_2
- -> Hash
- -> Hash Join
- Hash Cond: (t2_2.c = ltrim(t3_2.c, 'A'::text))
- -> Seq Scan on plt2_p3 t2_2
- -> Hash
- -> Seq Scan on plt1_e_p3 t3_2
-(33 rows)
+ -> Append
+ -> HashAggregate
+ Group Key: t1.c, t2.c, t3.c
+ -> Hash Join
+ Hash Cond: (t1.c = t2.c)
+ -> Seq Scan on plt1_p1 t1
+ -> Hash
+ -> Hash Join
+ Hash Cond: (t2.c = ltrim(t3.c, 'A'::text))
+ -> Seq Scan on plt2_p1 t2
+ -> Hash
+ -> Seq Scan on plt1_e_p1 t3
+ -> HashAggregate
+ Group Key: t1_1.c, t2_1.c, t3_1.c
+ -> Hash Join
+ Hash Cond: (t1_1.c = t2_1.c)
+ -> Seq Scan on plt1_p2 t1_1
+ -> Hash
+ -> Hash Join
+ Hash Cond: (t2_1.c = ltrim(t3_1.c, 'A'::text))
+ -> Seq Scan on plt2_p2 t2_1
+ -> Hash
+ -> Seq Scan on plt1_e_p2 t3_1
+ -> HashAggregate
+ Group Key: t1_2.c, t2_2.c, t3_2.c
+ -> Hash Join
+ Hash Cond: (t1_2.c = t2_2.c)
+ -> Seq Scan on plt1_p3 t1_2
+ -> Hash
+ -> Hash Join
+ Hash Cond: (t2_2.c = ltrim(t3_2.c, 'A'::text))
+ -> Seq Scan on plt2_p3 t2_2
+ -> Hash
+ -> Seq Scan on plt1_e_p3 t3_2
+(36 rows)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
+NOTICE: partition-wise grouping is possible.
avg | avg | avg | c | c | c
----------------------+----------------------+-----------------------+------+------+-------
24.0000000000000000 | 24.0000000000000000 | 48.0000000000000000 | 0000 | 0000 | A0000
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index cd1f7f3..f4cd466 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -81,11 +81,12 @@ select name, setting from pg_settings where name like 'enable%';
enable_material | on
enable_mergejoin | on
enable_nestloop | on
+ enable_partition_wise_agg | on
enable_partition_wise_join | off
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(13 rows)
+(14 rows)
-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
-- more-or-less working. We can't test their contents in any great detail
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 966984d..56c07d3 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -104,6 +104,10 @@ test: publication subscription
# Another group of parallel tests
# ----------
test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass partition_join multi_level_partition_join
+# TODO: should be added in parallel tests above, but before that need to make
+# sure we have unique objects to avoid any concurrency issues.
+test: partition_agg
+
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/sql/partition_agg.sql b/src/test/regress/sql/partition_agg.sql
new file mode 100644
index 0000000..df6dd9c
--- /dev/null
+++ b/src/test/regress/sql/partition_agg.sql
@@ -0,0 +1,48 @@
+--
+-- PARTITION_AGG
+-- Test partition-wise aggregation on partitioned tables
+--
+
+-- Enable partition-wise join, which by default is disabled.
+SET enable_partition_wise_join TO true;
+
+--
+-- Tests for list partitioned tables.
+--
+CREATE TABLE ptab1 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE ptab1_p1 PARTITION OF ptab1 FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE ptab1_p2 PARTITION OF ptab1 FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE ptab1_p3 PARTITION OF ptab1 FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO ptab1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE ptab1;
+ANALYZE ptab1_p1;
+ANALYZE ptab1_p2;
+ANALYZE ptab1_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uptab1 AS SELECT * FROM ptab1;
+ANALYZE uptab1;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT c, sum(a), avg(b), COUNT(*) FROM ptab1 GROUP BY c ORDER BY 1, 2, 3;
+SELECT c, sum(a), avg(b), COUNT(*) FROM ptab1 GROUP BY c ORDER BY 1, 2, 3;
+SELECT c, sum(a), avg(b), COUNT(*) FROM uptab1 GROUP BY c ORDER BY 1, 2, 3;
+
+-- JOIN query
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.c, sum(t1.a), avg(t1.b), COUNT(*) FROM ptab1 t1, ptab1 t2 WHERE t1.c = t2.c GROUP BY t1.c ORDER BY 1, 2, 3;
+SELECT t1.c, sum(t1.a), avg(t1.b), COUNT(*) FROM ptab1 t1, ptab1 t2 WHERE t1.c = t2.c GROUP BY t1.c ORDER BY 1, 2, 3;
+SELECT t1.c, sum(t1.a), avg(t1.b), COUNT(*) FROM uptab1 t1, uptab1 t2 WHERE t1.c = t2.c GROUP BY t1.c ORDER BY 1, 2, 3;
+
+
+-- Negative testcase
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT COUNT(*) FROM ptab1 GROUP BY a;
+
+-- Cleanup
+DROP TABLE uptab1;
+DROP TABLE ptab1_p3;
+DROP TABLE ptab1_p2;
+DROP TABLE ptab1_p1;
+DROP TABLE ptab1;
+RESET enable_partition_wise_join;