diff options
author | Robert Haas | 2024-08-29 19:38:58 +0000 |
---|---|---|
committer | Robert Haas | 2024-08-30 18:08:23 +0000 |
commit | 58d495a03f78d157f2d13a355d21a8bccf2c9de8 (patch) | |
tree | 21db03ab1597516fd5f499a4d9ebc48ba8a637d4 | |
parent | 6654bb92047b37cee053cedd6fa1829841b2ad8e (diff) |
Allow extensions to control join strategy.
At the start of join planning, we build a bitmask called jsa_mask based on
the values of the various enable_* planner GUCs, indicating which join
strategies are allowed. Extensions can override this mask on a plan-wide
basis using join_search_hook, or, generaly more usefully, they can change
the mask for each call to add_paths_to_joinrel using a new hook called
join_path_setup_hook. This is sufficient to allow an extension to force
the use of particular join strategies either in general or for specific
joins, and it is also sufficient to allow an extension to force the join
order.
If you want to control some aspect of planner behavior that is not
join-related, this patch won't help, but the same concepts could be
applied to scans, appendrels, and aggregation.
Notes:
- Materialization and memoization are optional sub-strategies when
performing a nested loop. You might want to avoid these strategies, allow
them, or force them. I don't know how this should be controlled.
- join_path_setup_hook will see JOIN_UNIQUE_INNER or JOIN_UNIQUE_OUTER as the
jointype and can reject if it wants. Conversely, you could allow that
case and reject the JOIN_SEMI/JOIN_RIGHT_SEMI case. However, if you wanted
to force the uniquification to use a sort or a hash rather than the
other, more would be needed (though it looks like you could kludge things
by modifying the SpecialJoinInfo).
- To fully control merge joins produced by sort_outer_and_inner(),
you would need control over which merge clauses are selected.
In match_unsorted_outer(), it's just a function of the input paths.
- You could potentially want to disallow certain strategies at greater
granularity than is possible here - e.g. unparameterized paths aren't
disabled, but making a nested loop out of them is.
- There are going to be residual references to various planner GUCs in
the code path, such as enable_parallel_hash and enable_sort. That seems
fine.
-rw-r--r-- | src/backend/optimizer/geqo/geqo_eval.c | 22 | ||||
-rw-r--r-- | src/backend/optimizer/geqo/geqo_main.c | 10 | ||||
-rw-r--r-- | src/backend/optimizer/geqo/geqo_pool.c | 4 | ||||
-rw-r--r-- | src/backend/optimizer/path/allpaths.c | 28 | ||||
-rw-r--r-- | src/backend/optimizer/path/costsize.c | 6 | ||||
-rw-r--r-- | src/backend/optimizer/path/joinpath.c | 94 | ||||
-rw-r--r-- | src/backend/optimizer/path/joinrels.c | 79 | ||||
-rw-r--r-- | src/backend/optimizer/util/relnode.c | 17 | ||||
-rw-r--r-- | src/include/nodes/pathnodes.h | 2 | ||||
-rw-r--r-- | src/include/optimizer/geqo.h | 9 | ||||
-rw-r--r-- | src/include/optimizer/geqo_pool.h | 2 | ||||
-rw-r--r-- | src/include/optimizer/pathnode.h | 6 | ||||
-rw-r--r-- | src/include/optimizer/paths.h | 56 |
13 files changed, 239 insertions, 96 deletions
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c index d2f7f4e5f3..6b1d8df3ff 100644 --- a/src/backend/optimizer/geqo/geqo_eval.c +++ b/src/backend/optimizer/geqo/geqo_eval.c @@ -40,7 +40,7 @@ typedef struct } Clump; static List *merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, - int num_gene, bool force); + int num_gene, bool force, unsigned jsa_mask); static bool desirable_join(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel); @@ -54,7 +54,7 @@ static bool desirable_join(PlannerInfo *root, * returns DBL_MAX. */ Cost -geqo_eval(PlannerInfo *root, Gene *tour, int num_gene) +geqo_eval(PlannerInfo *root, Gene *tour, int num_gene, unsigned jsa_mask) { MemoryContext mycontext; MemoryContext oldcxt; @@ -99,7 +99,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene) root->join_rel_hash = NULL; /* construct the best path for the given combination of relations */ - joinrel = gimme_tree(root, tour, num_gene); + joinrel = gimme_tree(root, tour, num_gene, jsa_mask); /* * compute fitness, if we found a valid join @@ -160,7 +160,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene) * since there's no provision for un-clumping, this must lead to failure.) */ RelOptInfo * -gimme_tree(PlannerInfo *root, Gene *tour, int num_gene) +gimme_tree(PlannerInfo *root, Gene *tour, int num_gene, unsigned jsa_mask) { GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private; List *clumps; @@ -196,7 +196,8 @@ gimme_tree(PlannerInfo *root, Gene *tour, int num_gene) cur_clump->size = 1; /* Merge it into the clumps list, using only desirable joins */ - clumps = merge_clump(root, clumps, cur_clump, num_gene, false); + clumps = merge_clump(root, clumps, cur_clump, num_gene, false, + jsa_mask); } if (list_length(clumps) > 1) @@ -210,7 +211,8 @@ gimme_tree(PlannerInfo *root, Gene *tour, int num_gene) { Clump *clump = (Clump *) lfirst(lc); - fclumps = merge_clump(root, fclumps, clump, num_gene, true); + fclumps = merge_clump(root, fclumps, clump, num_gene, true, + jsa_mask); } clumps = fclumps; } @@ -236,7 +238,7 @@ gimme_tree(PlannerInfo *root, Gene *tour, int num_gene) */ static List * merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, int num_gene, - bool force) + bool force, unsigned jsa_mask) { ListCell *lc; int pos; @@ -259,7 +261,8 @@ merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, int num_gene, */ joinrel = make_join_rel(root, old_clump->joinrel, - new_clump->joinrel); + new_clump->joinrel, + jsa_mask); /* Keep searching if join order is not valid */ if (joinrel) @@ -292,7 +295,8 @@ merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, int num_gene, * others. When no further merge is possible, we'll reinsert * it into the list. */ - return merge_clump(root, clumps, old_clump, num_gene, force); + return merge_clump(root, clumps, old_clump, num_gene, force, + jsa_mask); } } } diff --git a/src/backend/optimizer/geqo/geqo_main.c b/src/backend/optimizer/geqo/geqo_main.c index 0c5540e2af..c69b60d2e9 100644 --- a/src/backend/optimizer/geqo/geqo_main.c +++ b/src/backend/optimizer/geqo/geqo_main.c @@ -69,7 +69,8 @@ static int gimme_number_generations(int pool_size); */ RelOptInfo * -geqo(PlannerInfo *root, int number_of_rels, List *initial_rels) +geqo(PlannerInfo *root, int number_of_rels, List *initial_rels, + unsigned jsa_mask) { GeqoPrivateData private; int generation; @@ -116,7 +117,7 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels) pool = alloc_pool(root, pool_size, number_of_rels); /* random initialization of the pool */ - random_init_pool(root, pool); + random_init_pool(root, pool, jsa_mask); /* sort the pool according to cheapest path as fitness */ sort_pool(root, pool); /* we have to do it only one time, since all @@ -218,7 +219,8 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels) /* EVALUATE FITNESS */ - kid->worth = geqo_eval(root, kid->string, pool->string_length); + kid->worth = geqo_eval(root, kid->string, pool->string_length, + jsa_mask); /* push the kid into the wilderness of life according to its worth */ spread_chromo(root, kid, pool); @@ -269,7 +271,7 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels) */ best_tour = (Gene *) pool->data[0].string; - best_rel = gimme_tree(root, best_tour, pool->string_length); + best_rel = gimme_tree(root, best_tour, pool->string_length, jsa_mask); if (best_rel == NULL) elog(ERROR, "geqo failed to make a valid plan"); diff --git a/src/backend/optimizer/geqo/geqo_pool.c b/src/backend/optimizer/geqo/geqo_pool.c index 0ec97d5a3f..dbec3c5094 100644 --- a/src/backend/optimizer/geqo/geqo_pool.c +++ b/src/backend/optimizer/geqo/geqo_pool.c @@ -88,7 +88,7 @@ free_pool(PlannerInfo *root, Pool *pool) * initialize genetic pool */ void -random_init_pool(PlannerInfo *root, Pool *pool) +random_init_pool(PlannerInfo *root, Pool *pool, unsigned jsa_mask) { Chromosome *chromo = (Chromosome *) pool->data; int i; @@ -107,7 +107,7 @@ random_init_pool(PlannerInfo *root, Pool *pool) { init_tour(root, chromo[i].string, pool->string_length); pool->data[i].worth = geqo_eval(root, chromo[i].string, - pool->string_length); + pool->string_length, jsa_mask); if (pool->data[i].worth < DBL_MAX) i++; else diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 057b4b79eb..8f18a6f00a 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -3367,6 +3367,21 @@ make_rel_from_joinlist(PlannerInfo *root, List *joinlist) } else { + unsigned jsa_mask; + + /* Compute the initial join strategy advice mask. */ + jsa_mask = JSA_FOREIGN; + if (enable_hashjoin) + jsa_mask |= JSA_HASHJOIN; + if (enable_material) + jsa_mask |= JSA_NESTLOOP_MATERIALIZE; + if (enable_mergejoin) + jsa_mask |= JSA_MERGEJOIN; + if (enable_nestloop) + jsa_mask |= JSA_NESTLOOP; + if (enable_partitionwise_join) + jsa_mask |= JSA_PARTITIONWISE; + /* * Consider the different orders in which we could join the rels, * using a plugin, GEQO, or the regular join search code. @@ -3377,11 +3392,13 @@ make_rel_from_joinlist(PlannerInfo *root, List *joinlist) root->initial_rels = initial_rels; if (join_search_hook) - return (*join_search_hook) (root, levels_needed, initial_rels); + return (*join_search_hook) (root, levels_needed, initial_rels, + jsa_mask); else if (enable_geqo && levels_needed >= geqo_threshold) - return geqo(root, levels_needed, initial_rels); + return geqo(root, levels_needed, initial_rels, jsa_mask); else - return standard_join_search(root, levels_needed, initial_rels); + return standard_join_search(root, levels_needed, initial_rels, + jsa_mask); } } @@ -3415,7 +3432,8 @@ make_rel_from_joinlist(PlannerInfo *root, List *joinlist) * original states of those data structures. See geqo_eval() for an example. */ RelOptInfo * -standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels) +standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels, + unsigned jsa_mask) { int lev; RelOptInfo *rel; @@ -3450,7 +3468,7 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels) * level, and build paths for making each one from every available * pair of lower-level relations. */ - join_search_one_level(root, lev); + join_search_one_level(root, lev, jsa_mask); /* * Run generate_partitionwise_join_paths() and diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index e1523d15df..19717a5324 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -3280,7 +3280,7 @@ initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace, Cost inner_rescan_run_cost; /* Count up disabled nodes. */ - disabled_nodes = enable_nestloop ? 0 : 1; + disabled_nodes = (extra->jsa_mask & JSA_NESTLOOP) == 0 ? 1 : 0; disabled_nodes += inner_path->disabled_nodes; disabled_nodes += outer_path->disabled_nodes; @@ -3676,7 +3676,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, Assert(outerstartsel <= outerendsel); Assert(innerstartsel <= innerendsel); - disabled_nodes = enable_mergejoin ? 0 : 1; + disabled_nodes = (extra->jsa_mask & JSA_MERGEJOIN) == 0 ? 1 : 0; /* cost of source data */ @@ -4132,7 +4132,7 @@ initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace, size_t space_allowed; /* unused */ /* Count up disabled nodes. */ - disabled_nodes = enable_hashjoin ? 0 : 1; + disabled_nodes = (extra->jsa_mask & JSA_HASHJOIN) == 0 ? 1 : 0; disabled_nodes += inner_path->disabled_nodes; disabled_nodes += outer_path->disabled_nodes; diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index b0e8c94dfc..4fa6c3d918 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -27,8 +27,9 @@ #include "optimizer/planmain.h" #include "utils/typcache.h" -/* Hook for plugins to get control in add_paths_to_joinrel() */ +/* Hooks for plugins to get control in add_paths_to_joinrel() */ set_join_pathlist_hook_type set_join_pathlist_hook = NULL; +join_path_setup_hook_type join_path_setup_hook = NULL; /* * Paths parameterized by a parent rel can be considered to be parameterized @@ -128,7 +129,8 @@ add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *innerrel, JoinType jointype, SpecialJoinInfo *sjinfo, - List *restrictlist) + List *restrictlist, + unsigned jsa_mask) { JoinPathExtraData extra; bool mergejoin_allowed = true; @@ -151,6 +153,7 @@ add_paths_to_joinrel(PlannerInfo *root, extra.mergeclause_list = NIL; extra.sjinfo = sjinfo; extra.param_source_rels = NULL; + extra.jsa_mask = jsa_mask; /* * See if the inner relation is provably unique for this outer rel. @@ -203,12 +206,38 @@ add_paths_to_joinrel(PlannerInfo *root, } /* + * Give extensions a chance to take control. In particular, an extension + * might want to modify extra.jsa_mask so as to provide join strategy + * advice. An extension can also override jsa_mask on a query-wide basis + * by using join_search_hook, but extensions that want to provide + * different advice when joining different rels, or even different advice + * for the same joinrel based on the choice of innerrel and outerrel, need + * to use this hook. + * + * A very simple way for an extension to use this hook is to set + * extra.jsa_mask = 0, if it simply doesn't want any of the paths + * generated by this call to add_paths_to_joinrel() to be selected. An + * extension could use this technique to constrain the join order, since + * it could thereby arrange to reject all paths from join orders that it + * does not like. An extension can also selectively clear bits from + * extra.jsa_mask to rule out specific techniques for specific joins, or + * even replace the mask entirely. + * + * NB: Below this point, this function should be careful to reference only + * extra.jsa_mask, and not jsa_mask directly, to avoid disregarding any + * changes made by the hook we're about to call. + */ + if (join_path_setup_hook) + join_path_setup_hook(root, joinrel, outerrel, innerrel, + jointype, &extra); + + /* * Find potential mergejoin clauses. We can skip this if we are not * interested in doing a mergejoin. However, mergejoin may be our only - * way of implementing a full outer join, so override enable_mergejoin if - * it's a full join. + * way of implementing a full outer join, so in that case we don't care + * whether mergejoins are disabled. */ - if (enable_mergejoin || jointype == JOIN_FULL) + if ((extra.jsa_mask & JSA_MERGEJOIN) != 0 || jointype == JOIN_FULL) extra.mergeclause_list = select_mergejoin_clauses(root, joinrel, outerrel, @@ -316,10 +345,10 @@ add_paths_to_joinrel(PlannerInfo *root, /* * 4. Consider paths where both outer and inner relations must be hashed - * before being joined. As above, disregard enable_hashjoin for full - * joins, because there may be no other alternative. + * before being joined. As above, when it's a full join, we must try this + * even when the path type is disabled, because it may be our only option. */ - if (enable_hashjoin || jointype == JOIN_FULL) + if ((extra.jsa_mask & JSA_HASHJOIN) != 0 || jointype == JOIN_FULL) hash_inner_and_outer(root, joinrel, outerrel, innerrel, jointype, &extra); @@ -328,7 +357,7 @@ add_paths_to_joinrel(PlannerInfo *root, * to the same server and assigned to the same user to check access * permissions as, give the FDW a chance to push down joins. */ - if (joinrel->fdwroutine && + if ((extra.jsa_mask & JSA_FOREIGN) != 0 && joinrel->fdwroutine && joinrel->fdwroutine->GetForeignJoinPaths) joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel, outerrel, innerrel, @@ -337,8 +366,13 @@ add_paths_to_joinrel(PlannerInfo *root, /* * 6. Finally, give extensions a chance to manipulate the path list. They * could add new paths (such as CustomPaths) by calling add_path(), or - * add_partial_path() if parallel aware. They could also delete or modify - * paths added by the core code. + * add_partial_path() if parallel aware. + * + * In theory, extensions could also use this hook to delete or modify + * paths added by the core code, but in practice this is difficult to make + * work, since it's too late to get back any paths that have already been + * discarded by add_path() or add_partial_path(). If you're trying to + * suppress paths, consider using join_path_setup_hook instead. */ if (set_join_pathlist_hook) set_join_pathlist_hook(root, joinrel, outerrel, innerrel, @@ -1892,20 +1926,30 @@ match_unsorted_outer(PlannerInfo *root, if (inner_cheapest_total == NULL) return; inner_cheapest_total = (Path *) - create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo); + create_unique_path(root, innerrel, inner_cheapest_total, + extra->sjinfo); Assert(inner_cheapest_total); } else if (nestjoinOK) { /* - * Consider materializing the cheapest inner path, unless - * enable_material is off or the path in question materializes its - * output anyway. + * Consider materializing the cheapest inner path, unless that is + * disabled or the path in question materializes its output anyway. */ - if (enable_material && inner_cheapest_total != NULL && + if ((extra->jsa_mask & JSA_NESTLOOP_MATERIALIZE) != 0 && + inner_cheapest_total != NULL && !ExecMaterializesOutput(inner_cheapest_total->pathtype)) + { matpath = (Path *) create_material_path(innerrel, inner_cheapest_total); + + /* + * Ignore create_material_path()'s idea of whether the path is + * disabled in general; JSA_NESTLOOP_MATERIALIZE means it is + * acceptable here. + */ + matpath->disabled_nodes = inner_cheapest_total->disabled_nodes; + } } foreach(lc1, outerrel->pathlist) @@ -2134,19 +2178,27 @@ consider_parallel_nestloop(PlannerInfo *root, /* * Consider materializing the cheapest inner path, unless: 1) we're doing * JOIN_UNIQUE_INNER, because in this case we have to unique-ify the - * cheapest inner path, 2) enable_material is off, 3) the cheapest inner - * path is not parallel-safe, 4) the cheapest inner path is parameterized - * by the outer rel, or 5) the cheapest inner path materializes its output - * anyway. + * cheapest inner path, 2) materialization is disabled here, 3) the + * cheapest inner path is not parallel-safe, 4) the cheapest inner path is + * parameterized by the outer rel, or 5) the cheapest inner path + * materializes its output anyway. */ if (save_jointype != JOIN_UNIQUE_INNER && - enable_material && inner_cheapest_total->parallel_safe && + (extra->jsa_mask & JSA_NESTLOOP_MATERIALIZE) != 0 && + inner_cheapest_total->parallel_safe && !PATH_PARAM_BY_REL(inner_cheapest_total, outerrel) && !ExecMaterializesOutput(inner_cheapest_total->pathtype)) { matpath = (Path *) create_material_path(innerrel, inner_cheapest_total); Assert(matpath->parallel_safe); + + /* + * Ignore create_material_path()'s idea of whether the path is + * disabled in general; JSA_NESTLOOP_MATERIALIZE means it is + * acceptable here. + */ + matpath->disabled_nodes = inner_cheapest_total->disabled_nodes; } foreach(lc1, outerrel->partial_pathlist) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 7db5e30eef..dad5c55536 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -26,10 +26,12 @@ static void make_rels_by_clause_joins(PlannerInfo *root, RelOptInfo *old_rel, List *other_rels, - int first_rel_idx); + int first_rel_idx, + unsigned jsa_mask); static void make_rels_by_clauseless_joins(PlannerInfo *root, RelOptInfo *old_rel, - List *other_rels); + List *other_rels, + unsigned jsa_mask); static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel); static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel); static bool restriction_is_constant_false(List *restrictlist, @@ -37,11 +39,13 @@ static bool restriction_is_constant_false(List *restrictlist, bool only_pushed_down); static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, - SpecialJoinInfo *sjinfo, List *restrictlist); + SpecialJoinInfo *sjinfo, + List *restrictlist, unsigned jsa_mask); static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo, - List *parent_restrictlist); + List *parent_restrictlist, + unsigned jsa_mask); static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, Relids left_relids, Relids right_relids); @@ -69,7 +73,7 @@ static void get_matching_part_pairs(PlannerInfo *root, RelOptInfo *joinrel, * The result is returned in root->join_rel_level[level]. */ void -join_search_one_level(PlannerInfo *root, int level) +join_search_one_level(PlannerInfo *root, int level, unsigned jsa_mask) { List **joinrels = root->join_rel_level; ListCell *r; @@ -114,7 +118,8 @@ join_search_one_level(PlannerInfo *root, int level) else first_rel = 0; - make_rels_by_clause_joins(root, old_rel, joinrels[1], first_rel); + make_rels_by_clause_joins(root, old_rel, joinrels[1], first_rel, + jsa_mask); } else { @@ -132,7 +137,8 @@ join_search_one_level(PlannerInfo *root, int level) */ make_rels_by_clauseless_joins(root, old_rel, - joinrels[1]); + joinrels[1], + jsa_mask); } } @@ -189,7 +195,7 @@ join_search_one_level(PlannerInfo *root, int level) if (have_relevant_joinclause(root, old_rel, new_rel) || have_join_order_restriction(root, old_rel, new_rel)) { - (void) make_join_rel(root, old_rel, new_rel); + (void) make_join_rel(root, old_rel, new_rel, jsa_mask); } } } @@ -227,7 +233,8 @@ join_search_one_level(PlannerInfo *root, int level) make_rels_by_clauseless_joins(root, old_rel, - joinrels[1]); + joinrels[1], + jsa_mask); } /*---------- @@ -279,7 +286,8 @@ static void make_rels_by_clause_joins(PlannerInfo *root, RelOptInfo *old_rel, List *other_rels, - int first_rel_idx) + int first_rel_idx, + unsigned jsa_mask) { ListCell *l; @@ -291,7 +299,7 @@ make_rels_by_clause_joins(PlannerInfo *root, (have_relevant_joinclause(root, old_rel, other_rel) || have_join_order_restriction(root, old_rel, other_rel))) { - (void) make_join_rel(root, old_rel, other_rel); + (void) make_join_rel(root, old_rel, other_rel, jsa_mask); } } } @@ -312,7 +320,8 @@ make_rels_by_clause_joins(PlannerInfo *root, static void make_rels_by_clauseless_joins(PlannerInfo *root, RelOptInfo *old_rel, - List *other_rels) + List *other_rels, + unsigned jsa_mask) { ListCell *l; @@ -322,7 +331,7 @@ make_rels_by_clauseless_joins(PlannerInfo *root, if (!bms_overlap(other_rel->relids, old_rel->relids)) { - (void) make_join_rel(root, old_rel, other_rel); + (void) make_join_rel(root, old_rel, other_rel, jsa_mask); } } } @@ -701,7 +710,8 @@ init_dummy_sjinfo(SpecialJoinInfo *sjinfo, Relids left_relids, * turned into joins. */ RelOptInfo * -make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) +make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, + unsigned jsa_mask) { Relids joinrelids; SpecialJoinInfo *sjinfo; @@ -759,7 +769,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) */ joinrel = build_join_rel(root, joinrelids, rel1, rel2, sjinfo, pushed_down_joins, - &restrictlist); + &restrictlist, jsa_mask); /* * If we've already proven this join is empty, we needn't consider any @@ -773,7 +783,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) /* Add paths to the join relation. */ populate_joinrel_with_paths(root, rel1, rel2, joinrel, sjinfo, - restrictlist); + restrictlist, jsa_mask); bms_free(joinrelids); @@ -892,7 +902,8 @@ add_outer_joins_to_relids(PlannerInfo *root, Relids input_relids, static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, - SpecialJoinInfo *sjinfo, List *restrictlist) + SpecialJoinInfo *sjinfo, List *restrictlist, + unsigned jsa_mask) { /* * Consider paths using each rel as both outer and inner. Depending on @@ -923,10 +934,10 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, } add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_INNER, sjinfo, - restrictlist); + restrictlist, jsa_mask); add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_INNER, sjinfo, - restrictlist); + restrictlist, jsa_mask); break; case JOIN_LEFT: if (is_dummy_rel(rel1) || @@ -940,10 +951,10 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, mark_dummy_rel(rel2); add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_LEFT, sjinfo, - restrictlist); + restrictlist, jsa_mask); add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_RIGHT, sjinfo, - restrictlist); + restrictlist, jsa_mask); break; case JOIN_FULL: if ((is_dummy_rel(rel1) && is_dummy_rel(rel2)) || @@ -954,10 +965,10 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, } add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_FULL, sjinfo, - restrictlist); + restrictlist, jsa_mask); add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_FULL, sjinfo, - restrictlist); + restrictlist, jsa_mask); /* * If there are join quals that aren't mergeable or hashable, we @@ -990,10 +1001,10 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, } add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_SEMI, sjinfo, - restrictlist); + restrictlist, jsa_mask); add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_RIGHT_SEMI, sjinfo, - restrictlist); + restrictlist, jsa_mask); } /* @@ -1016,10 +1027,10 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, } add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_UNIQUE_INNER, sjinfo, - restrictlist); + restrictlist, jsa_mask); add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_UNIQUE_OUTER, sjinfo, - restrictlist); + restrictlist, jsa_mask); } break; case JOIN_ANTI: @@ -1034,10 +1045,10 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, mark_dummy_rel(rel2); add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_ANTI, sjinfo, - restrictlist); + restrictlist, jsa_mask); add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_RIGHT_ANTI, sjinfo, - restrictlist); + restrictlist, jsa_mask); break; default: /* other values not expected here */ @@ -1046,7 +1057,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, } /* Apply partitionwise join technique, if possible. */ - try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist); + try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist, + jsa_mask); } @@ -1480,7 +1492,7 @@ restriction_is_constant_false(List *restrictlist, static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo, - List *parent_restrictlist) + List *parent_restrictlist, unsigned jsa_mask) { bool rel1_is_simple = IS_SIMPLE_REL(rel1); bool rel2_is_simple = IS_SIMPLE_REL(rel2); @@ -1662,7 +1674,8 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, { child_joinrel = build_child_join_rel(root, child_rel1, child_rel2, joinrel, child_restrictlist, - child_sjinfo, nappinfos, appinfos); + child_sjinfo, nappinfos, + appinfos, jsa_mask); joinrel->part_rels[cnt_parts] = child_joinrel; joinrel->live_parts = bms_add_member(joinrel->live_parts, cnt_parts); joinrel->all_partrels = bms_add_members(joinrel->all_partrels, @@ -1677,7 +1690,7 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, /* And make paths for the child join */ populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, - child_restrictlist); + child_restrictlist, jsa_mask); /* * When there are thousands of partitions involved, this loop will diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index d7266e4cdb..9e328c5ac7 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -69,7 +69,8 @@ static void build_joinrel_partition_info(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo, - List *restrictlist); + List *restrictlist, + unsigned jsa_mask); static bool have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *rel1, RelOptInfo *rel2, JoinType jointype, List *restrictlist); @@ -668,7 +669,8 @@ build_join_rel(PlannerInfo *root, RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo, List *pushed_down_joins, - List **restrictlist_ptr) + List **restrictlist_ptr, + unsigned jsa_mask) { RelOptInfo *joinrel; List *restrictlist; @@ -817,7 +819,7 @@ build_join_rel(PlannerInfo *root, /* Store the partition information. */ build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo, - restrictlist); + restrictlist, jsa_mask); /* * Set estimates of the joinrel's size. @@ -882,7 +884,8 @@ RelOptInfo * build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel, RelOptInfo *parent_joinrel, List *restrictlist, SpecialJoinInfo *sjinfo, - int nappinfos, AppendRelInfo **appinfos) + int nappinfos, AppendRelInfo **appinfos, + unsigned jsa_mask) { RelOptInfo *joinrel = makeNode(RelOptInfo); @@ -981,7 +984,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, /* Is the join between partitions itself partitioned? */ build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo, - restrictlist); + restrictlist, jsa_mask); /* Child joinrel is parallel safe if parent is parallel safe. */ joinrel->consider_parallel = parent_joinrel->consider_parallel; @@ -2005,12 +2008,12 @@ static void build_joinrel_partition_info(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo, - List *restrictlist) + List *restrictlist, unsigned jsa_mask) { PartitionScheme part_scheme; /* Nothing to do if partitionwise join technique is disabled. */ - if (!enable_partitionwise_join) + if ((jsa_mask & JSA_PARTITIONWISE) == 0) { Assert(!IS_PARTITIONED_REL(joinrel)); return; diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 540d021592..a839ab1a4a 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -3225,6 +3225,7 @@ typedef struct SemiAntiJoinFactors * sjinfo is extra info about special joins for selectivity estimation * semifactors is as shown above (only valid for SEMI/ANTI/inner_unique joins) * param_source_rels are OK targets for parameterization of result paths + * jsa_mask is a bitmask of JSA_* constants to direct the join strategy */ typedef struct JoinPathExtraData { @@ -3234,6 +3235,7 @@ typedef struct JoinPathExtraData SpecialJoinInfo *sjinfo; SemiAntiJoinFactors semifactors; Relids param_source_rels; + unsigned jsa_mask; } JoinPathExtraData; /* diff --git a/src/include/optimizer/geqo.h b/src/include/optimizer/geqo.h index c52906d091..a9d14b07aa 100644 --- a/src/include/optimizer/geqo.h +++ b/src/include/optimizer/geqo.h @@ -81,10 +81,13 @@ typedef struct /* routines in geqo_main.c */ extern RelOptInfo *geqo(PlannerInfo *root, - int number_of_rels, List *initial_rels); + int number_of_rels, List *initial_rels, + unsigned jsa_mask); /* routines in geqo_eval.c */ -extern Cost geqo_eval(PlannerInfo *root, Gene *tour, int num_gene); -extern RelOptInfo *gimme_tree(PlannerInfo *root, Gene *tour, int num_gene); +extern Cost geqo_eval(PlannerInfo *root, Gene *tour, int num_gene, + unsigned jsa_mask); +extern RelOptInfo *gimme_tree(PlannerInfo *root, Gene *tour, int num_gene, + unsigned jsa_mask); #endif /* GEQO_H */ diff --git a/src/include/optimizer/geqo_pool.h b/src/include/optimizer/geqo_pool.h index b5e8055472..bd1ed15290 100644 --- a/src/include/optimizer/geqo_pool.h +++ b/src/include/optimizer/geqo_pool.h @@ -29,7 +29,7 @@ extern Pool *alloc_pool(PlannerInfo *root, int pool_size, int string_length); extern void free_pool(PlannerInfo *root, Pool *pool); -extern void random_init_pool(PlannerInfo *root, Pool *pool); +extern void random_init_pool(PlannerInfo *root, Pool *pool, unsigned jsa_mask); extern Chromosome *alloc_chromo(PlannerInfo *root, int string_length); extern void free_chromo(PlannerInfo *root, Chromosome *chromo); diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 1035e6560c..266c551cac 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -324,7 +324,8 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root, RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo, List *pushed_down_joins, - List **restrictlist_ptr); + List **restrictlist_ptr, + unsigned jsa_mask); extern Relids min_join_parameterization(PlannerInfo *root, Relids joinrelids, RelOptInfo *outer_rel, @@ -351,6 +352,7 @@ extern RelOptInfo *build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel, RelOptInfo *parent_joinrel, List *restrictlist, SpecialJoinInfo *sjinfo, - int nappinfos, AppendRelInfo **appinfos); + int nappinfos, AppendRelInfo **appinfos, + unsigned jsa_mask); #endif /* PATHNODE_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 970499c469..4177d7a45a 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -16,10 +16,44 @@ #include "nodes/pathnodes.h" +/* + * Join strategy advice. + * + * Paths that don't match the join strategy advice will be either be disabled + * or will not be generated in the first place. It's only permissible to skip + * generating a path if doing so can't result in planner failure. The initial + * mask is computed on the basis of the various enable_* GUCs, and can be + * overriden by hooks. + * + * JSA_FOREIGN means that it's OK for a FDW to produce join paths. + * + * JSA_MERGEJOIN means that a mergejoin is OK. + * + * JSA_NESTLOOP means that a nested loop is OK in general. + * + * JSA_NESTLOOP_MATERIALIZE and JSA_NESTLOOP_NO_MATERIALIZE mean that it's + * OK for a nested loop to materialize or not materialize the inner side. + * JSA_NESTLOOP_MEMOIZE and JSA_NESTLOOP_NO_MEMOIZE mean that it's OK for + * a nested loop to memoize or not memoize the inner side. + * + * JSA_HASHJOIN means that a hash join is OK. + * + * JSA_PARTITIONWISE means that a partitionwise join is OK. + */ +#define JSA_FOREIGN 0x0001 +#define JSA_MERGEJOIN 0x0002 +#define JSA_NESTLOOP 0x0004 +#define JSA_NESTLOOP_MATERIALIZE 0x0008 +#define JSA_NESTLOOP_NO_MATERIALIZE 0x0010 +#define JSA_NESTLOOP_MEMOIZE 0x0020 +#define JSA_NESTLOOP_NO_MEMOIZE 0x0040 +#define JSA_HASHJOIN 0x0080 +#define JSA_PARTITIONWISE 0x0100 /* * allpaths.c */ + extern PGDLLIMPORT bool enable_geqo; extern PGDLLIMPORT int geqo_threshold; extern PGDLLIMPORT int min_parallel_table_scan_size; @@ -33,7 +67,14 @@ typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root, RangeTblEntry *rte); extern PGDLLIMPORT set_rel_pathlist_hook_type set_rel_pathlist_hook; -/* Hook for plugins to get control in add_paths_to_joinrel() */ +/* Hooks for plugins to get control in add_paths_to_joinrel() */ +typedef void (*join_path_setup_hook_type) (PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + JoinPathExtraData *extra); +extern PGDLLIMPORT join_path_setup_hook_type join_path_setup_hook; typedef void (*set_join_pathlist_hook_type) (PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, @@ -45,13 +86,14 @@ extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook; /* Hook for plugins to replace standard_join_search() */ typedef RelOptInfo *(*join_search_hook_type) (PlannerInfo *root, int levels_needed, - List *initial_rels); + List *initial_rels, + unsigned jsa_mask); extern PGDLLIMPORT join_search_hook_type join_search_hook; extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist); extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed, - List *initial_rels); + List *initial_rels, unsigned jsa_mask); extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows); @@ -92,15 +134,17 @@ extern bool create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel); extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, SpecialJoinInfo *sjinfo, - List *restrictlist); + List *restrictlist, unsigned jsa_mask); /* * joinrels.c * routines to determine which relations to join */ -extern void join_search_one_level(PlannerInfo *root, int level); +extern void join_search_one_level(PlannerInfo *root, int level, + unsigned jsa_mask); extern RelOptInfo *make_join_rel(PlannerInfo *root, - RelOptInfo *rel1, RelOptInfo *rel2); + RelOptInfo *rel1, RelOptInfo *rel2, + unsigned jsa_mask); extern Relids add_outer_joins_to_relids(PlannerInfo *root, Relids input_relids, SpecialJoinInfo *sjinfo, List **pushed_down_joins); |