Allow extensions to control join strategy.
authorRobert Haas <[email protected]>
Thu, 29 Aug 2024 19:38:58 +0000 (15:38 -0400)
committerRobert Haas <[email protected]>
Fri, 30 Aug 2024 18:08:23 +0000 (14:08 -0400)
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  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.

13 files changed:
src/backend/optimizer/geqo/geqo_eval.c
src/backend/optimizer/geqo/geqo_main.c
src/backend/optimizer/geqo/geqo_pool.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/path/joinpath.c
src/backend/optimizer/path/joinrels.c
src/backend/optimizer/util/relnode.c
src/include/nodes/pathnodes.h
src/include/optimizer/geqo.h
src/include/optimizer/geqo_pool.h
src/include/optimizer/pathnode.h
src/include/optimizer/paths.h

index d2f7f4e5f3c0fb44d84a6ae8faf19341f747b20f..6b1d8df3ff6e9ff29846fce3dcd2fb64a8a0fc8a 100644 (file)
@@ -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);
                        }
                }
        }
index 0c5540e2af4456431ec7a5801f78174cb0e85285..c69b60d2e95ea3c0fd358a4ef7a663e67efc1569 100644 (file)
@@ -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");
index 0ec97d5a3f14c3628ea62a094514edc90dd0b56f..dbec3c509434c4df7b60bfedea72fbe6c057e909 100644 (file)
@@ -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
index 057b4b79ebb8dd702035cef7eb6de4605bf9fc87..8f18a6f00a949173fab7faefea994c87cd4f591b 100644 (file)
@@ -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
index e1523d15df1c7450ae6f6eb4b468a8eac698832e..19717a53241fe5850a0af3bfce94d6863844699c 100644 (file)
@@ -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;
 
index b0e8c94dfc37ddfd77f0acb0329ec3246b42ad46..4fa6c3d9188e5ce727e3928d832f3bfbeb616763 100644 (file)
@@ -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.
@@ -202,13 +205,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) != 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)
index 7db5e30eef8c2a93504c530b7a3d039c2de80ce1..dad5c555365a2bb609af4bdfa541d6e60668e83b 100644 (file)
 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
index d7266e4cdba01764b71af5b782e421bca4519724..9e328c5ac7c17d6ee84a380d8a655d89f21468b1 100644 (file)
@@ -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;
index 540d021592e68c71fc0c0ce13f96bf562f8570ce..a839ab1a4a4c6e699570d6211f03dd8e894cbfe2 100644 (file)
@@ -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;
 
 /*
index c52906d0916e17d4faea005e94aedcacf9d5928e..a9d14b07aa1fe0dd9bbc00c0b731a046e1f0cc83 100644 (file)
@@ -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 */
index b5e8055472457f4cc7e6c430d18d562ed35ac276..bd1ed15290773ab3a85f672bc898192fc7810ba4 100644 (file)
@@ -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);
 
index 1035e6560c1fe7a8b792adc7e04780fb88e691c5..266c551cac9edb5d115cb69b31a2a57e97e25b57 100644 (file)
@@ -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 */
index 970499c469102f32e6c4fffc6214fe091a0647b2..4177d7a45adc12df8833fca482beb91bfaaa8f56 100644 (file)
 
 #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);