diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c new file mode 100644 index b5cab0c..d0e07bd *** a/src/backend/optimizer/geqo/geqo_eval.c --- b/src/backend/optimizer/geqo/geqo_eval.c *************** typedef struct *** 40,46 **** } Clump; static List *merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, ! bool force); static bool desirable_join(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel); --- 40,46 ---- } Clump; static List *merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, ! int num_gene, bool force); static bool desirable_join(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel); *************** gimme_tree(PlannerInfo *root, Gene *tour *** 196,202 **** cur_clump->size = 1; /* Merge it into the clumps list, using only desirable joins */ ! clumps = merge_clump(root, clumps, cur_clump, false); } if (list_length(clumps) > 1) --- 196,202 ---- cur_clump->size = 1; /* Merge it into the clumps list, using only desirable joins */ ! clumps = merge_clump(root, clumps, cur_clump, num_gene, false); } if (list_length(clumps) > 1) *************** gimme_tree(PlannerInfo *root, Gene *tour *** 210,216 **** { Clump *clump = (Clump *) lfirst(lc); ! fclumps = merge_clump(root, fclumps, clump, true); } clumps = fclumps; } --- 210,216 ---- { Clump *clump = (Clump *) lfirst(lc); ! fclumps = merge_clump(root, fclumps, clump, num_gene, true); } clumps = fclumps; } *************** gimme_tree(PlannerInfo *root, Gene *tour *** 235,241 **** * "desirable" joins. */ static List * ! merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, bool force) { ListCell *prev; ListCell *lc; --- 235,242 ---- * "desirable" joins. */ static List * ! merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, int num_gene, ! bool force) { ListCell *prev; ListCell *lc; *************** merge_clump(PlannerInfo *root, List *clu *** 264,271 **** /* Keep searching if join order is not valid */ if (joinrel) { ! /* Create GatherPaths for any useful partial paths for rel */ ! generate_gather_paths(root, joinrel); /* Find and save the cheapest paths for this joinrel */ set_cheapest(joinrel); --- 265,278 ---- /* Keep searching if join order is not valid */ if (joinrel) { ! /* ! * Create GatherPaths for any useful partial paths for rel ! * other than top-level rel. The gather path for top-level ! * rel is generated once path target is available. See ! * grouping_planner. ! */ ! if (old_clump->size + new_clump->size < num_gene) ! generate_gather_paths(root, joinrel, NULL); /* Find and save the cheapest paths for this joinrel */ set_cheapest(joinrel); *************** merge_clump(PlannerInfo *root, List *clu *** 283,289 **** * others. When no further merge is possible, we'll reinsert * it into the list. */ ! return merge_clump(root, clumps, old_clump, force); } } prev = lc; --- 290,296 ---- * others. When no further merge is possible, we'll reinsert * it into the list. */ ! return merge_clump(root, clumps, old_clump, num_gene, force); } } prev = lc; diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c new file mode 100644 index 5b746a9..74512da *** a/src/backend/optimizer/path/allpaths.c --- b/src/backend/optimizer/path/allpaths.c *************** set_rel_pathlist(PlannerInfo *root, RelO *** 480,493 **** } /* ! * If this is a baserel, consider gathering any partial paths we may have ! * created for it. (If we tried to gather inheritance children, we could ! * end up with a very large number of gather nodes, each trying to grab ! * its own pool of workers, so don't do this for otherrels. Instead, ! * we'll consider gathering partial paths for the parent appendrel.) */ ! if (rel->reloptkind == RELOPT_BASEREL) ! generate_gather_paths(root, rel); /* * Allow a plugin to editorialize on the set of Paths for this base --- 480,497 ---- } /* ! * If this is a baserel and not the top-level rel, consider gathering any ! * partial paths we may have created for it. (If we tried to gather ! * inheritance children, we could end up with a very large number of ! * gather nodes, each trying to grab its own pool of workers, so don't do ! * this for otherrels. Instead, we'll consider gathering partial paths ! * for the parent appendrel.). The gather path for top-level rel is ! * generated once path target is available. See grouping_planner. */ ! if (rel->reloptkind == RELOPT_BASEREL && ! root->simple_rel_array_size > 2 && ! !root->append_rel_list) ! generate_gather_paths(root, rel, NULL); /* * Allow a plugin to editorialize on the set of Paths for this base *************** set_worktable_pathlist(PlannerInfo *root *** 2228,2238 **** * Gather Merge on top of a partial path. * * This must not be called until after we're done creating all partial paths ! * for the specified relation. (Otherwise, add_partial_path might delete a ! * path that some GatherPath or GatherMergePath has a reference to.) */ void ! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel) { Path *cheapest_partial_path; Path *simple_gather_path; --- 2232,2243 ---- * Gather Merge on top of a partial path. * * This must not be called until after we're done creating all partial paths ! * for the specified relation (Otherwise, add_partial_path might delete a ! * path that some GatherPath or GatherMergePath has a reference to.) and path ! * target for top level scan/join node is available. */ void ! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, PathTarget *target) { Path *cheapest_partial_path; Path *simple_gather_path; *************** generate_gather_paths(PlannerInfo *root, *** 2251,2256 **** --- 2256,2266 ---- simple_gather_path = (Path *) create_gather_path(root, rel, cheapest_partial_path, rel->reltarget, NULL, NULL); + + /* Add projection step if needed */ + if (target && simple_gather_path->pathtarget != target) + simple_gather_path = apply_projection_to_path(root, rel, simple_gather_path, target); + add_path(rel, simple_gather_path); /* *************** generate_gather_paths(PlannerInfo *root, *** 2260,2273 **** foreach(lc, rel->partial_pathlist) { Path *subpath = (Path *) lfirst(lc); ! GatherMergePath *path; if (subpath->pathkeys == NIL) continue; ! path = create_gather_merge_path(root, rel, subpath, rel->reltarget, ! subpath->pathkeys, NULL, NULL); ! add_path(rel, &path->path); } } --- 2270,2287 ---- foreach(lc, rel->partial_pathlist) { Path *subpath = (Path *) lfirst(lc); ! Path *path; if (subpath->pathkeys == NIL) continue; ! path = (Path *) create_gather_merge_path(root, rel, subpath, rel->reltarget, ! subpath->pathkeys, NULL, NULL); ! /* Add projection step if needed */ ! if (target && path->pathtarget != target) ! path = apply_projection_to_path(root, rel, path, target); ! ! add_path(rel, path); } } *************** standard_join_search(PlannerInfo *root, *** 2432,2439 **** { rel = (RelOptInfo *) lfirst(lc); ! /* Create GatherPaths for any useful partial paths for rel */ ! generate_gather_paths(root, rel); /* Find and save the cheapest paths for this rel */ set_cheapest(rel); --- 2446,2458 ---- { rel = (RelOptInfo *) lfirst(lc); ! /* ! * Create GatherPaths for any useful partial paths for rel other ! * than top-level rel. The gather path for top-level rel is ! * generated once path target is available. See grouping_planner. ! */ ! if (lev < levels_needed) ! generate_gather_paths(root, rel, NULL); /* Find and save the cheapest paths for this rel */ set_cheapest(rel); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c new file mode 100644 index 7f146d6..659f27d *** a/src/backend/optimizer/plan/planner.c --- b/src/backend/optimizer/plan/planner.c *************** grouping_planner(PlannerInfo *root, bool *** 1860,1865 **** --- 1860,1877 ---- } /* + * Consider ways to implement parallel paths. We always skip + * generating parallel path for top level scan/join nodes till the + * pathtarget is computed. This is to ensure that we can account for + * the fact that most of the target evaluation work will be performed + * in workers. + */ + generate_gather_paths(root, current_rel, scanjoin_target); + + /* Set or update cheapest_total_path and related fields */ + set_cheapest(current_rel); + + /* * Upper planning steps which make use of the top scan/join rel's * partial pathlist will expect partial paths for that rel to produce * the same output as complete paths ... and we just changed the diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c new file mode 100644 index 26567cb..41f98b3 *** a/src/backend/optimizer/util/pathnode.c --- b/src/backend/optimizer/util/pathnode.c *************** apply_projection_to_path(PlannerInfo *ro *** 2408,2423 **** * projection-capable, so as to avoid modifying the subpath in place. * It seems unlikely at present that there could be any other * references to the subpath, but better safe than sorry. - * - * Note that we don't change the GatherPath's cost estimates; it might - * be appropriate to do so, to reflect the fact that the bulk of the - * target evaluation will happen in workers. */ gpath->subpath = (Path *) create_projection_path(root, gpath->subpath->parent, gpath->subpath, target); } else if (path->parallel_safe && !is_parallel_safe(root, (Node *) target->exprs)) --- 2408,2434 ---- * projection-capable, so as to avoid modifying the subpath in place. * It seems unlikely at present that there could be any other * references to the subpath, but better safe than sorry. */ gpath->subpath = (Path *) create_projection_path(root, gpath->subpath->parent, gpath->subpath, target); + + /* + * Adjust the cost of GatherPath to reflect the fact that the bulk of + * the target evaluation will happen in workers. + */ + if (((ProjectionPath *) gpath->subpath)->dummypp) + { + path->total_cost -= (target->cost.per_tuple - oldcost.per_tuple) * path->rows; + path->total_cost += (target->cost.per_tuple - oldcost.per_tuple) * gpath->subpath->rows; + } + else + { + path->total_cost -= (target->cost.per_tuple - oldcost.per_tuple) * path->rows; + path->total_cost += (cpu_tuple_cost + target->cost.per_tuple) * gpath->subpath->rows; + } } else if (path->parallel_safe && !is_parallel_safe(root, (Node *) target->exprs)) diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h new file mode 100644 index 4e06b2e..61694a0 *** a/src/include/optimizer/paths.h --- b/src/include/optimizer/paths.h *************** extern void set_dummy_rel_pathlist(RelOp *** 53,59 **** extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels); ! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel); extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages, double index_pages); extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel, --- 53,60 ---- extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels); ! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, ! PathTarget *target); extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages, double index_pages); extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel, diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out new file mode 100644 index 2ae600f..232a455 *** a/src/test/regress/expected/select_parallel.out --- b/src/test/regress/expected/select_parallel.out *************** explain (costs off) *** 101,106 **** --- 101,123 ---- -> Parallel Index Only Scan using tenk1_unique1 on tenk1 (5 rows) + -- test that parallel plan gets selected when target list contains costly + -- function + create or replace function costly_func(var1 integer) returns integer + as $$ + begin + return var1 + 10; + end; + $$ language plpgsql PARALLEL SAFE Cost 100000; + explain (costs off) select ten, costly_func(ten) from tenk1; + QUERY PLAN + ---------------------------------- + Gather + Workers Planned: 4 + -> Parallel Seq Scan on tenk1 + (3 rows) + + drop function costly_func(var1 integer); -- test parallel plans for queries containing un-correlated subplans. alter table tenk2 set (parallel_workers = 0); explain (costs off) diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql new file mode 100644 index 89fe80a..2b8072d *** a/src/test/regress/sql/select_parallel.sql --- b/src/test/regress/sql/select_parallel.sql *************** explain (costs off) *** 39,44 **** --- 39,55 ---- select sum(parallel_restricted(unique1)) from tenk1 group by(parallel_restricted(unique1)); + -- test that parallel plan gets selected when target list contains costly + -- function + create or replace function costly_func(var1 integer) returns integer + as $$ + begin + return var1 + 10; + end; + $$ language plpgsql PARALLEL SAFE Cost 100000; + explain (costs off) select ten, costly_func(ten) from tenk1; + drop function costly_func(var1 integer); + -- test parallel plans for queries containing un-correlated subplans. alter table tenk2 set (parallel_workers = 0); explain (costs off)