From 6200809f38d9c39c1d3dd096c43461ee696c4384 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Thu, 29 Aug 2024 15:38:58 -0400
Subject: [PATCH v4 1/4] Allow extensions to control join strategy.

At the start of planning, we build a bitmask of allowable join
strategies based on the values of the various enable_* planner GUCs,
indicating which join strategies are allowed. Extensions can override
this mask for an entire subquery 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, including which relation appears on which side of a given join.

There are a number of things that this patch doesn't let you do.
First, it won't help if you want to implement some policy where the
allowable join methods might differ for each individual combination of
input paths (e.g. disable nested loops only when the inner side's
parameterization is not even partially satisfied by the outer side's
paramaeterization). Second, it doesn't allow you to control the
uniquification strategy for a particular joinrel. Third, it doesn't
give you any control over aspects of planning other than join planning.
Future patches may close some of these gaps.
---
 src/backend/optimizer/geqo/geqo_eval.c  |  22 +++--
 src/backend/optimizer/geqo/geqo_main.c  |  10 ++-
 src/backend/optimizer/geqo/geqo_pool.c  |   4 +-
 src/backend/optimizer/path/allpaths.c   |  17 ++--
 src/backend/optimizer/path/costsize.c   |  59 +++++++++-----
 src/backend/optimizer/path/joinpath.c   | 104 +++++++++++++++++-------
 src/backend/optimizer/path/joinrels.c   |  79 ++++++++++--------
 src/backend/optimizer/plan/createplan.c |   1 +
 src/backend/optimizer/plan/planner.c    |  21 +++++
 src/backend/optimizer/util/pathnode.c   |   6 +-
 src/backend/optimizer/util/relnode.c    |  17 ++--
 src/include/nodes/pathnodes.h           |   5 ++
 src/include/optimizer/cost.h            |   4 +-
 src/include/optimizer/geqo.h            |   9 +-
 src/include/optimizer/geqo_pool.h       |   2 +-
 src/include/optimizer/pathnode.h        |   9 +-
 src/include/optimizer/paths.h           |  56 +++++++++++--
 17 files changed, 295 insertions(+), 130 deletions(-)

diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
index d2f7f4e5f3c..6b1d8df3ff6 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 0c5540e2af4..c69b60d2e95 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 0ec97d5a3f1..dbec3c50943 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 172edb643a4..5fe326c8de4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -898,7 +898,7 @@ set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *
 		 bms_membership(root->all_query_rels) != BMS_SINGLETON) &&
 		!(GetTsmRoutine(rte->tablesample->tsmhandler)->repeatable_across_scans))
 	{
-		path = (Path *) create_material_path(rel, path);
+		path = (Path *) create_material_path(rel, path, enable_material);
 	}
 
 	add_path(rel, path);
@@ -3371,6 +3371,7 @@ make_rel_from_joinlist(PlannerInfo *root, List *joinlist)
 	}
 	else
 	{
+
 		/*
 		 * Consider the different orders in which we could join the rels,
 		 * using a plugin, GEQO, or the regular join search code.
@@ -3381,11 +3382,14 @@ 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,
+										root->glob->default_jsa_mask);
 		else if (enable_geqo && levels_needed >= geqo_threshold)
-			return geqo(root, levels_needed, initial_rels);
+			return geqo(root, levels_needed, initial_rels,
+						root->glob->default_jsa_mask);
 		else
-			return standard_join_search(root, levels_needed, initial_rels);
+			return standard_join_search(root, levels_needed, initial_rels,
+										root->glob->default_jsa_mask);
 	}
 }
 
@@ -3419,7 +3423,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;
@@ -3454,7 +3459,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 2bb6db1df77..5888ecac65d 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2481,7 +2481,7 @@ cost_merge_append(Path *path, PlannerInfo *root,
  */
 void
 cost_material(Path *path,
-			  int input_disabled_nodes,
+			  bool enabled, int input_disabled_nodes,
 			  Cost input_startup_cost, Cost input_total_cost,
 			  double tuples, int width)
 {
@@ -2519,7 +2519,7 @@ cost_material(Path *path,
 		run_cost += seq_page_cost * npages;
 	}
 
-	path->disabled_nodes = input_disabled_nodes + (enable_material ? 0 : 1);
+	path->disabled_nodes = input_disabled_nodes + (enabled ? 0 : 1);
 	path->startup_cost = startup_cost;
 	path->total_cost = startup_cost + run_cost;
 }
@@ -3266,7 +3266,7 @@ cost_group(Path *path, PlannerInfo *root,
  */
 void
 initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace,
-					  JoinType jointype,
+					  JoinType jointype, unsigned nestloop_subtype,
 					  Path *outer_path, Path *inner_path,
 					  JoinPathExtraData *extra)
 {
@@ -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 & nestloop_subtype) == 0 ? 1 : 0;
 	disabled_nodes += inner_path->disabled_nodes;
 	disabled_nodes += outer_path->disabled_nodes;
 
@@ -3678,7 +3678,12 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace,
 	Assert(outerstartsel <= outerendsel);
 	Assert(innerstartsel <= innerendsel);
 
-	disabled_nodes = enable_mergejoin ? 0 : 1;
+	/*
+	 * Assume for now that this node is not itself disabled. We'll sort out
+	 * whether that's really the case in final_cost_mergejoin(); here, we'll
+	 * just account for any disabled child nodes.
+	 */
+	disabled_nodes = 0;
 
 	/* cost of source data */
 
@@ -3859,9 +3864,6 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 				rescannedtuples;
 	double		rescanratio;
 
-	/* Set the number of disabled nodes. */
-	path->jpath.path.disabled_nodes = workspace->disabled_nodes;
-
 	/* Protect some assumptions below that rowcounts aren't zero */
 	if (inner_path_rows <= 0)
 		inner_path_rows = 1;
@@ -3988,16 +3990,20 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 		path->materialize_inner = false;
 
 	/*
-	 * Prefer materializing if it looks cheaper, unless the user has asked to
-	 * suppress materialization.
+	 * If merge joins with materialization are enabled, then choose
+	 * materialization if either (a) it looks cheaper or (b) merge joins
+	 * without materialization are disabled.
 	 */
-	else if (enable_material && mat_inner_cost < bare_inner_cost)
+	else if ((extra->jsa_mask & JSA_MERGEJOIN_MATERIALIZE) != 0 &&
+			 (mat_inner_cost < bare_inner_cost ||
+			  (extra->jsa_mask & JSA_MERGEJOIN_PLAIN) == 0))
 		path->materialize_inner = true;
 
 	/*
-	 * Even if materializing doesn't look cheaper, we *must* do it if the
-	 * inner path is to be used directly (without sorting) and it doesn't
-	 * support mark/restore.
+	 * Regardless of what plan shapes are enabled and what the costs seem
+	 * to be, we *must* materialize it if the inner path is to be used directly
+	 * (without sorting) and it doesn't support mark/restore. Planner failure
+	 * is not an option!
 	 *
 	 * Since the inner side must be ordered, and only Sorts and IndexScans can
 	 * create order to begin with, and they both support mark/restore, you
@@ -4005,10 +4011,6 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * merge joins can *preserve* the order of their inputs, so they can be
 	 * selected as the input of a mergejoin, and they don't support
 	 * mark/restore at present.
-	 *
-	 * We don't test the value of enable_material here, because
-	 * materialization is required for correctness in this case, and turning
-	 * it off does not entitle us to deliver an invalid plan.
 	 */
 	else if (innersortkeys == NIL &&
 			 !ExecSupportsMarkRestore(inner_path))
@@ -4025,7 +4027,8 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * rather than necessary for correctness, we skip it if enable_material is
 	 * off.
 	 */
-	else if (enable_material && innersortkeys != NIL &&
+	else if ((extra->jsa_mask & JSA_MERGEJOIN_MATERIALIZE) != 0 &&
+			 innersortkeys != NIL &&
 			 relation_byte_size(inner_path_rows,
 								inner_path->pathtarget->width) >
 			 (work_mem * 1024L))
@@ -4033,11 +4036,25 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	else
 		path->materialize_inner = false;
 
-	/* Charge the right incremental cost for the chosen case */
+	/* Get the number of disabled nodes, not yet including this one. */
+	path->jpath.path.disabled_nodes = workspace->disabled_nodes;
+
+	/*
+	 * Charge the right incremental cost for the chosen case, and increment
+	 * disabled_nodes if appropriate.
+	 */
 	if (path->materialize_inner)
+	{
 		run_cost += mat_inner_cost;
+		if ((extra->jsa_mask & JSA_MERGEJOIN_MATERIALIZE) == 0)
+			++path->jpath.path.disabled_nodes;
+	}
 	else
+	{
 		run_cost += bare_inner_cost;
+		if ((extra->jsa_mask & JSA_MERGEJOIN_PLAIN) == 0)
+			++path->jpath.path.disabled_nodes;
+	}
 
 	/* CPU costs */
 
@@ -4177,7 +4194,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 a244300463c..90f3829d04d 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -28,8 +28,9 @@
 #include "utils/lsyscache.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
@@ -129,7 +130,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;
@@ -152,6 +154,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,13 +206,39 @@ add_paths_to_joinrel(PlannerInfo *root,
 			break;
 	}
 
+	/*
+	 * 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_ANY) != 0 || jointype == JOIN_FULL)
 		extra.mergeclause_list = select_mergejoin_clauses(root,
 														  joinrel,
 														  outerrel,
@@ -317,10 +346,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);
 
@@ -329,7 +358,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,
@@ -338,8 +367,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,
@@ -829,6 +863,7 @@ try_nestloop_path(PlannerInfo *root,
 				  Path *inner_path,
 				  List *pathkeys,
 				  JoinType jointype,
+				  unsigned nestloop_subtype,
 				  JoinPathExtraData *extra)
 {
 	Relids		required_outer;
@@ -913,7 +948,7 @@ try_nestloop_path(PlannerInfo *root,
 	 * The latter two steps are expensive enough to make this two-phase
 	 * methodology worthwhile.
 	 */
-	initial_cost_nestloop(root, &workspace, jointype,
+	initial_cost_nestloop(root, &workspace, jointype, nestloop_subtype,
 						  outer_path, inner_path, extra);
 
 	if (add_path_precheck(joinrel, workspace.disabled_nodes,
@@ -951,6 +986,7 @@ try_partial_nestloop_path(PlannerInfo *root,
 						  Path *inner_path,
 						  List *pathkeys,
 						  JoinType jointype,
+						  unsigned nestloop_subtype,
 						  JoinPathExtraData *extra)
 {
 	JoinCostWorkspace workspace;
@@ -998,7 +1034,7 @@ try_partial_nestloop_path(PlannerInfo *root,
 	 * Before creating a path, get a quick lower bound on what it is likely to
 	 * cost.  Bail out right away if it looks terrible.
 	 */
-	initial_cost_nestloop(root, &workspace, jointype,
+	initial_cost_nestloop(root, &workspace, jointype, nestloop_subtype,
 						  outer_path, inner_path, extra);
 	if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes,
 								   workspace.total_cost, pathkeys))
@@ -1893,20 +1929,21 @@ 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);
+				create_material_path(innerrel, inner_cheapest_total, true);
 	}
 
 	foreach(lc1, outerrel->pathlist)
@@ -1954,6 +1991,7 @@ match_unsorted_outer(PlannerInfo *root,
 							  inner_cheapest_total,
 							  merge_pathkeys,
 							  jointype,
+							  JSA_NESTLOOP_PLAIN,
 							  extra);
 		}
 		else if (nestjoinOK)
@@ -1977,6 +2015,7 @@ match_unsorted_outer(PlannerInfo *root,
 								  innerpath,
 								  merge_pathkeys,
 								  jointype,
+								  JSA_NESTLOOP_PLAIN,
 								  extra);
 
 				/*
@@ -1993,6 +2032,7 @@ match_unsorted_outer(PlannerInfo *root,
 									  mpath,
 									  merge_pathkeys,
 									  jointype,
+									  JSA_NESTLOOP_MEMOIZE,
 									  extra);
 			}
 
@@ -2004,6 +2044,7 @@ match_unsorted_outer(PlannerInfo *root,
 								  matpath,
 								  merge_pathkeys,
 								  jointype,
+								  JSA_NESTLOOP_MATERIALIZE,
 								  extra);
 		}
 
@@ -2135,20 +2176,18 @@ 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);
-	}
+			create_material_path(innerrel, inner_cheapest_total, true);
 
 	foreach(lc1, outerrel->partial_pathlist)
 	{
@@ -2193,7 +2232,8 @@ consider_parallel_nestloop(PlannerInfo *root,
 			}
 
 			try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
-									  pathkeys, jointype, extra);
+									  pathkeys, jointype, JSA_NESTLOOP_PLAIN,
+									  extra);
 
 			/*
 			 * Try generating a memoize path and see if that makes the nested
@@ -2204,13 +2244,15 @@ consider_parallel_nestloop(PlannerInfo *root,
 									 extra);
 			if (mpath != NULL)
 				try_partial_nestloop_path(root, joinrel, outerpath, mpath,
-										  pathkeys, jointype, extra);
+										  pathkeys, jointype,
+										  JSA_NESTLOOP_MEMOIZE, extra);
 		}
 
 		/* Also consider materialized form of the cheapest inner path */
 		if (matpath != NULL)
 			try_partial_nestloop_path(root, joinrel, outerpath, matpath,
-									  pathkeys, jointype, extra);
+									  pathkeys, jointype,
+									  JSA_NESTLOOP_MATERIALIZE, extra);
 	}
 }
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 7db5e30eef8..dad5c555365 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/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 0d195a07ffc..1669e4b8b03 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6623,6 +6623,7 @@ materialize_finished_plan(Plan *subplan)
 
 	/* Set cost data */
 	cost_material(&matpath,
+				  enable_material,
 				  subplan->disabled_nodes,
 				  subplan->startup_cost,
 				  subplan->total_cost,
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 0f423e96847..7c1000879ec 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -419,6 +419,27 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 		tuple_fraction = 0.0;
 	}
 
+	/* Compute the initial join strategy advice mask. */
+	glob->default_jsa_mask = JSA_FOREIGN;
+	if (enable_hashjoin)
+		glob->default_jsa_mask |= JSA_HASHJOIN;
+	if (enable_mergejoin)
+	{
+		glob->default_jsa_mask |= JSA_MERGEJOIN_PLAIN;
+		if (enable_material)
+			glob->default_jsa_mask |= JSA_MERGEJOIN_MATERIALIZE;
+	}
+	if (enable_nestloop)
+	{
+		glob->default_jsa_mask |= JSA_NESTLOOP_PLAIN;
+		if (enable_material)
+			glob->default_jsa_mask |= JSA_NESTLOOP_MATERIALIZE;
+		if (enable_memoize)
+			glob->default_jsa_mask |= JSA_NESTLOOP_MEMOIZE;
+	}
+	if (enable_partitionwise_join)
+		glob->default_jsa_mask |= JSA_PARTITIONWISE;
+
 	/* primary planning entry point (may recurse for subqueries) */
 	root = subquery_planner(glob, parse, NULL, false, tuple_fraction, NULL);
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index fc97bf6ee26..a704d0cd7bb 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1631,7 +1631,7 @@ create_group_result_path(PlannerInfo *root, RelOptInfo *rel,
  *	  pathnode.
  */
 MaterialPath *
-create_material_path(RelOptInfo *rel, Path *subpath)
+create_material_path(RelOptInfo *rel, Path *subpath, bool enabled)
 {
 	MaterialPath *pathnode = makeNode(MaterialPath);
 
@@ -1650,6 +1650,7 @@ create_material_path(RelOptInfo *rel, Path *subpath)
 	pathnode->subpath = subpath;
 
 	cost_material(&pathnode->path,
+				  enabled,
 				  subpath->disabled_nodes,
 				  subpath->startup_cost,
 				  subpath->total_cost,
@@ -4158,7 +4159,8 @@ reparameterize_path(PlannerInfo *root, Path *path,
 											loop_count);
 				if (spath == NULL)
 					return NULL;
-				return (Path *) create_material_path(rel, spath);
+				return (Path *) create_material_path(rel, spath,
+													 enable_material);
 			}
 		case T_Memoize:
 			{
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index d7266e4cdba..9e328c5ac7c 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 07e2415398e..ca328d6edad 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -161,6 +161,9 @@ typedef struct PlannerGlobal
 	/* worst PROPARALLEL hazard level */
 	char		maxParallelHazard;
 
+	/* default join strategy advice, except where overrriden by hooks */
+	uint32		default_jsa_mask;
+
 	/* partition descriptors */
 	PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
 } PlannerGlobal;
@@ -3231,6 +3234,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
 {
@@ -3240,6 +3244,7 @@ typedef struct JoinPathExtraData
 	SpecialJoinInfo *sjinfo;
 	SemiAntiJoinFactors semifactors;
 	Relids		param_source_rels;
+	unsigned	jsa_mask;
 } JoinPathExtraData;
 
 /*
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 854a782944a..071a8749cfa 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -125,7 +125,7 @@ extern void cost_merge_append(Path *path, PlannerInfo *root,
 							  Cost input_startup_cost, Cost input_total_cost,
 							  double tuples);
 extern void cost_material(Path *path,
-						  int input_disabled_nodes,
+						  bool enabled, int input_disabled_nodes,
 						  Cost input_startup_cost, Cost input_total_cost,
 						  double tuples, int width);
 extern void cost_agg(Path *path, PlannerInfo *root,
@@ -148,7 +148,7 @@ extern void cost_group(Path *path, PlannerInfo *root,
 					   double input_tuples);
 extern void initial_cost_nestloop(PlannerInfo *root,
 								  JoinCostWorkspace *workspace,
-								  JoinType jointype,
+								  JoinType jointype, unsigned nestloop_subtype,
 								  Path *outer_path, Path *inner_path,
 								  JoinPathExtraData *extra);
 extern void final_cost_nestloop(PlannerInfo *root, NestPath *path,
diff --git a/src/include/optimizer/geqo.h b/src/include/optimizer/geqo.h
index c52906d0916..a9d14b07aa1 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 b5e80554724..bd1ed152907 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 1035e6560c1..ed2e3a1c8b8 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -82,7 +82,8 @@ extern GroupResultPath *create_group_result_path(PlannerInfo *root,
 												 RelOptInfo *rel,
 												 PathTarget *target,
 												 List *havingqual);
-extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
+extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath,
+										  bool enabled);
 extern MemoizePath *create_memoize_path(PlannerInfo *root,
 										RelOptInfo *rel,
 										Path *subpath,
@@ -324,7 +325,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 +353,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 54869d44013..f9f346f86a1 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.
+ *
+ * We have five main join strategies: a foreign join (when supported by the
+ * relevant FDW), a merge join, a nested loop, a hash join, and a partitionwise
+ * join. Merge joins are further subdivided based on whether the inner side
+ * is materialized, and nested loops are further subdivided based on whether
+ * the inner side is materialized, memoized, or neither. "Plain" means a
+ * strategy where neither materialization nor memoization is used.
+ *
+ * If you don't care whether materialization or memoization is used, set all
+ * the bits for the relevant major join strategy. If you do care, just set the
+ * subset of bits that correspond to the cases you want to allow.
+ */
+#define JSA_FOREIGN						0x0001
+#define JSA_MERGEJOIN_PLAIN				0x0002
+#define JSA_MERGEJOIN_MATERIALIZE		0x0004
+#define JSA_NESTLOOP_PLAIN				0x0008
+#define JSA_NESTLOOP_MATERIALIZE		0x0010
+#define JSA_NESTLOOP_MEMOIZE			0x0020
+#define JSA_HASHJOIN					0x0040
+#define JSA_PARTITIONWISE				0x0080
+
+#define JSA_MERGEJOIN_ANY	\
+	(JSA_MERGEJOIN_PLAIN | JSA_MERGEJOIN_MATERIALIZE)
+#define JSA_NESTLOOP_ANY \
+	(JSA_NESTLOOP_PLAIN | JSA_NESTLOOP_MATERIALIZE | JSA_NESTLOOP_MEMOIZE)
 
 /*
  * 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);
-- 
2.39.3 (Apple Git-145)

