Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  joinpath.c   Sprache: C

 

/*-------------------------------------------------------------------------
 *
 * joinpath.c
 *   Routines to find all possible paths for processing a set of joins
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *   src/backend/optimizer/path/joinpath.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <math.h>

#include "executor/executor.h"
#include "foreign/fdwapi.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "optimizer/restrictinfo.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"

/* Hook for plugins to get control in add_paths_to_joinrel() */
set_join_pathlist_hook_type set_join_pathlist_hook = NULL;

/*
 * Paths parameterized by a parent rel can be considered to be parameterized
 * by any of its children, when we are performing partitionwise joins.  These
 * macros simplify checking for such cases.  Beware multiple eval of args.
 */

#define PATH_PARAM_BY_PARENT(path, rel) \
 ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), \
            (rel)->top_parent_relids))
#define PATH_PARAM_BY_REL_SELF(path, rel)  \
 ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids))

#define PATH_PARAM_BY_REL(path, rel) \
 (PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))

static void try_partial_mergejoin_path(PlannerInfo *root,
            RelOptInfo *joinrel,
            Path *outer_path,
            Path *inner_path,
            List *pathkeys,
            List *mergeclauses,
            List *outersortkeys,
            List *innersortkeys,
            JoinType jointype,
            JoinPathExtraData *extra);
static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
         RelOptInfo *outerrel, RelOptInfo *innerrel,
         JoinType jointype, JoinPathExtraData *extra);
static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
         RelOptInfo *outerrel, RelOptInfo *innerrel,
         JoinType jointype, JoinPathExtraData *extra);
static void consider_parallel_nestloop(PlannerInfo *root,
            RelOptInfo *joinrel,
            RelOptInfo *outerrel,
            RelOptInfo *innerrel,
            JoinType jointype,
            JoinPathExtraData *extra);
static void consider_parallel_mergejoin(PlannerInfo *root,
          RelOptInfo *joinrel,
          RelOptInfo *outerrel,
          RelOptInfo *innerrel,
          JoinType jointype,
          JoinPathExtraData *extra,
          Path *inner_cheapest_total);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
         RelOptInfo *outerrel, RelOptInfo *innerrel,
         JoinType jointype, JoinPathExtraData *extra);
static List *select_mergejoin_clauses(PlannerInfo *root,
           RelOptInfo *joinrel,
           RelOptInfo *outerrel,
           RelOptInfo *innerrel,
           List *restrictlist,
           JoinType jointype,
           bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
          RelOptInfo *joinrel,
          RelOptInfo *innerrel,
          Path *outerpath,
          JoinType jointype,
          JoinPathExtraData *extra,
          bool useallclauses,
          Path *inner_cheapest_total,
          List *merge_pathkeys,
          bool is_partial);


/*
 * add_paths_to_joinrel
 *   Given a join relation and two component rels from which it can be made,
 *   consider all possible paths that use the two component rels as outer
 *   and inner rel respectively.  Add these paths to the join rel's pathlist
 *   if they survive comparison with other paths (and remove any existing
 *   paths that are dominated by these paths).
 *
 * Modifies the pathlist field of the joinrel node to contain the best
 * paths found so far.
 *
 * jointype is not necessarily the same as sjinfo->jointype; it might be
 * "flipped around" if we are considering joining the rels in the opposite
 * direction from what's indicated in sjinfo.
 *
 * Also, this routine and others in this module accept the special JoinTypes
 * JOIN_UNIQUE_OUTER and JOIN_UNIQUE_INNER to indicate that we should
 * unique-ify the outer or inner relation and then apply a regular inner
 * join.  These values are not allowed to propagate outside this module,
 * however.  Path cost estimation code may need to recognize that it's
 * dealing with such a case --- the combination of nominal jointype INNER
 * with sjinfo->jointype == JOIN_SEMI indicates that.
 */

void
add_paths_to_joinrel(PlannerInfo *root,
      RelOptInfo *joinrel,
      RelOptInfo *outerrel,
      RelOptInfo *innerrel,
      JoinType jointype,
      SpecialJoinInfo *sjinfo,
      List *restrictlist)
{
 JoinPathExtraData extra;
 bool  mergejoin_allowed = true;
 ListCell   *lc;
 Relids  joinrelids;

 /*
  * PlannerInfo doesn't contain the SpecialJoinInfos created for joins
  * between child relations, even if there is a SpecialJoinInfo node for
  * the join between the topmost parents. So, while calculating Relids set
  * representing the restriction, consider relids of topmost parent of
  * partitions.
 */

 if (joinrel->reloptkind == RELOPT_OTHER_JOINREL)
  joinrelids = joinrel->top_parent_relids;
 else
  joinrelids = joinrel->relids;

 extra.restrictlist = restrictlist;
 extra.mergeclause_list = NIL;
 extra.sjinfo = sjinfo;
 extra.param_source_rels = NULL;

 /*
  * See if the inner relation is provably unique for this outer rel.
  *
  * We have some special cases: for JOIN_SEMI and JOIN_ANTI, it doesn't
  * matter since the executor can make the equivalent optimization anyway;
  * we need not expend planner cycles on proofs.  For JOIN_UNIQUE_INNER, we
  * must be considering a semijoin whose inner side is not provably unique
  * (else reduce_unique_semijoins would've simplified it), so there's no
  * point in calling innerrel_is_unique.  However, if the LHS covers all of
  * the semijoin's min_lefthand, then it's appropriate to set inner_unique
  * because the path produced by create_unique_path will be unique relative
  * to the LHS.  (If we have an LHS that's only part of the min_lefthand,
  * that is *not* true.)  For JOIN_UNIQUE_OUTER, pass JOIN_INNER to avoid
  * letting that value escape this module.
 */

 switch (jointype)
 {
  case JOIN_SEMI:
  case JOIN_ANTI:

   /*
    * XXX it may be worth proving this to allow a Memoize to be
    * considered for Nested Loop Semi/Anti Joins.
 */

   extra.inner_unique = false; /* well, unproven */
   break;
  case JOIN_UNIQUE_INNER:
   extra.inner_unique = bms_is_subset(sjinfo->min_lefthand,
              outerrel->relids);
   break;
  case JOIN_UNIQUE_OUTER:
   extra.inner_unique = innerrel_is_unique(root,
             joinrel->relids,
             outerrel->relids,
             innerrel,
             JOIN_INNER,
             restrictlist,
             false);
   break;
  default:
   extra.inner_unique = innerrel_is_unique(root,
             joinrel->relids,
             outerrel->relids,
             innerrel,
             jointype,
             restrictlist,
             false);
   break;
 }

 /*
  * 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.
 */

 if (enable_mergejoin || jointype == JOIN_FULL)
  extra.mergeclause_list = select_mergejoin_clauses(root,
                joinrel,
                outerrel,
                innerrel,
                restrictlist,
                jointype,
                &mergejoin_allowed);

 /*
  * If it's SEMI, ANTI, or inner_unique join, compute correction factors
  * for cost estimation.  These will be the same for all paths.
 */

 if (jointype == JOIN_SEMI || jointype == JOIN_ANTI || extra.inner_unique)
  compute_semi_anti_join_factors(root, joinrel, outerrel, innerrel,
            jointype, sjinfo, restrictlist,
            &extra.semifactors);

 /*
  * Decide whether it's sensible to generate parameterized paths for this
  * joinrel, and if so, which relations such paths should require.  There
  * is usually no need to create a parameterized result path unless there
  * is a join order restriction that prevents joining one of our input rels
  * directly to the parameter source rel instead of joining to the other
  * input rel.  (But see allow_star_schema_join().) This restriction
  * reduces the number of parameterized paths we have to deal with at
  * higher join levels, without compromising the quality of the resulting
  * plan.  We express the restriction as a Relids set that must overlap the
  * parameterization of any proposed join path.  Note: param_source_rels
  * should contain only baserels, not OJ relids, so starting from
  * all_baserels not all_query_rels is correct.
 */

 foreach(lc, root->join_info_list)
 {
  SpecialJoinInfo *sjinfo2 = (SpecialJoinInfo *) lfirst(lc);

  /*
   * SJ is relevant to this join if we have some part of its RHS
   * (possibly not all of it), and haven't yet joined to its LHS.  (This
   * test is pretty simplistic, but should be sufficient considering the
   * join has already been proven legal.)  If the SJ is relevant, it
   * presents constraints for joining to anything not in its RHS.
 */

  if (bms_overlap(joinrelids, sjinfo2->min_righthand) &&
   !bms_overlap(joinrelids, sjinfo2->min_lefthand))
   extra.param_source_rels = bms_join(extra.param_source_rels,
              bms_difference(root->all_baserels,
                 sjinfo2->min_righthand));

  /* full joins constrain both sides symmetrically */
  if (sjinfo2->jointype == JOIN_FULL &&
   bms_overlap(joinrelids, sjinfo2->min_lefthand) &&
   !bms_overlap(joinrelids, sjinfo2->min_righthand))
   extra.param_source_rels = bms_join(extra.param_source_rels,
              bms_difference(root->all_baserels,
                 sjinfo2->min_lefthand));
 }

 /*
  * However, when a LATERAL subquery is involved, there will simply not be
  * any paths for the joinrel that aren't parameterized by whatever the
  * subquery is parameterized by, unless its parameterization is resolved
  * within the joinrel.  So we might as well allow additional dependencies
  * on whatever residual lateral dependencies the joinrel will have.
 */

 extra.param_source_rels = bms_add_members(extra.param_source_rels,
             joinrel->lateral_relids);

 /*
  * 1. Consider mergejoin paths where both relations must be explicitly
  * sorted.  Skip this if we can't mergejoin.
 */

 if (mergejoin_allowed)
  sort_inner_and_outer(root, joinrel, outerrel, innerrel,
        jointype, &extra);

 /*
  * 2. Consider paths where the outer relation need not be explicitly
  * sorted. This includes both nestloops and mergejoins where the outer
  * path is already ordered.  Again, skip this if we can't mergejoin.
  * (That's okay because we know that nestloop can't handle
  * right/right-anti/right-semi/full joins at all, so it wouldn't work in
  * the prohibited cases either.)
 */

 if (mergejoin_allowed)
  match_unsorted_outer(root, joinrel, outerrel, innerrel,
        jointype, &extra);

#ifdef NOT_USED

 /*
  * 3. Consider paths where the inner relation need not be explicitly
  * sorted.  This includes mergejoins only (nestloops were already built in
  * match_unsorted_outer).
  *
  * Diked out as redundant 2/13/2000 -- tgl.  There isn't any really
  * significant difference between the inner and outer side of a mergejoin,
  * so match_unsorted_inner creates no paths that aren't equivalent to
  * those made by match_unsorted_outer when add_paths_to_joinrel() is
  * invoked with the two rels given in the other order.
 */

 if (mergejoin_allowed)
  match_unsorted_inner(root, joinrel, outerrel, innerrel,
        jointype, &extra);
#endif

 /*
  * 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.
 */

 if (enable_hashjoin || jointype == JOIN_FULL)
  hash_inner_and_outer(root, joinrel, outerrel, innerrel,
        jointype, &extra);

 /*
  * 5. If inner and outer relations are foreign tables (or joins) belonging
  * 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 &&
  joinrel->fdwroutine->GetForeignJoinPaths)
  joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel,
             outerrel, innerrel,
             jointype, &extra);

 /*
  * 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.
 */

 if (set_join_pathlist_hook)
  set_join_pathlist_hook(root, joinrel, outerrel, innerrel,
          jointype, &extra);
}

/*
 * We override the param_source_rels heuristic to accept nestloop paths in
 * which the outer rel satisfies some but not all of the inner path's
 * parameterization.  This is necessary to get good plans for star-schema
 * scenarios, in which a parameterized path for a large table may require
 * parameters from multiple small tables that will not get joined directly to
 * each other.  We can handle that by stacking nestloops that have the small
 * tables on the outside; but this breaks the rule the param_source_rels
 * heuristic is based on, namely that parameters should not be passed down
 * across joins unless there's a join-order-constraint-based reason to do so.
 * So we ignore the param_source_rels restriction when this case applies.
 *
 * allow_star_schema_join() returns true if the param_source_rels restriction
 * should be overridden, ie, it's okay to perform this join.
 */

static inline bool
allow_star_schema_join(PlannerInfo *root,
        Relids outerrelids,
        Relids inner_paramrels)
{
 /*
  * It's a star-schema case if the outer rel provides some but not all of
  * the inner rel's parameterization.
 */

 return (bms_overlap(inner_paramrels, outerrelids) &&
   bms_nonempty_difference(inner_paramrels, outerrelids));
}

/*
 * If the parameterization is only partly satisfied by the outer rel,
 * the unsatisfied part can't include any outer-join relids that could
 * null rels of the satisfied part.  That would imply that we're trying
 * to use a clause involving a Var with nonempty varnullingrels at
 * a join level where that value isn't yet computable.
 *
 * In practice, this test never finds a problem because earlier join order
 * restrictions prevent us from attempting a join that would cause a problem.
 * (That's unsurprising, because the code worked before we ever added
 * outer-join relids to expression relids.)  It still seems worth checking
 * as a backstop, but we only do so in assert-enabled builds.
 */

#ifdef USE_ASSERT_CHECKING
static inline bool
have_unsafe_outer_join_ref(PlannerInfo *root,
         Relids outerrelids,
         Relids inner_paramrels)
{
 bool  result = false;
 Relids  unsatisfied = bms_difference(inner_paramrels, outerrelids);
 Relids  satisfied = bms_intersect(inner_paramrels, outerrelids);

 if (bms_overlap(unsatisfied, root->outer_join_rels))
 {
  ListCell   *lc;

  foreach(lc, root->join_info_list)
  {
   SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);

   if (!bms_is_member(sjinfo->ojrelid, unsatisfied))
    continue;  /* not relevant */
   if (bms_overlap(satisfied, sjinfo->min_righthand) ||
    (sjinfo->jointype == JOIN_FULL &&
     bms_overlap(satisfied, sjinfo->min_lefthand)))
   {
    result = true; /* doesn't work */
    break;
   }
  }
 }

 /* Waste no memory when we reject a path here */
 bms_free(unsatisfied);
 bms_free(satisfied);

 return result;
}
#endif       /* USE_ASSERT_CHECKING */

/*
 * paraminfo_get_equal_hashops
 *  Determine if the clauses in param_info and innerrel's lateral vars
 *  can be hashed.
 *  Returns true if hashing is possible, otherwise false.
 *
 * Additionally, on success we collect the outer expressions and the
 * appropriate equality operators for each hashable parameter to innerrel.
 * These are returned in parallel lists in *param_exprs and *operators.
 * We also set *binary_mode to indicate whether strict binary matching is
 * required.
 */

static bool
paraminfo_get_equal_hashops(PlannerInfo *root, ParamPathInfo *param_info,
       RelOptInfo *outerrel, RelOptInfo *innerrel,
       List *ph_lateral_vars, List **param_exprs,
       List **operators, bool *binary_mode)

{
 List    *lateral_vars;
 ListCell   *lc;

 *param_exprs = NIL;
 *operators = NIL;
 *binary_mode = false;

 /* Add join clauses from param_info to the hash key */
 if (param_info != NULL)
 {
  List    *clauses = param_info->ppi_clauses;

  foreach(lc, clauses)
  {
   RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
   OpExpr    *opexpr;
   Node    *expr;
   Oid   hasheqoperator;

   opexpr = (OpExpr *) rinfo->clause;

   /*
    * Bail if the rinfo is not compatible.  We need a join OpExpr
    * with 2 args.
 */

   if (!IsA(opexpr, OpExpr) || list_length(opexpr->args) != 2 ||
    !clause_sides_match_join(rinfo, outerrel->relids,
           innerrel->relids))
   {
    list_free(*operators);
    list_free(*param_exprs);
    return false;
   }

   if (rinfo->outer_is_left)
   {
    expr = (Node *) linitial(opexpr->args);
    hasheqoperator = rinfo->left_hasheqoperator;
   }
   else
   {
    expr = (Node *) lsecond(opexpr->args);
    hasheqoperator = rinfo->right_hasheqoperator;
   }

   /* can't do memoize if we can't hash the outer type */
   if (!OidIsValid(hasheqoperator))
   {
    list_free(*operators);
    list_free(*param_exprs);
    return false;
   }

   /*
    * 'expr' may already exist as a parameter from a previous item in
    * ppi_clauses.  No need to include it again, however we'd better
    * ensure we do switch into binary mode if required.  See below.
 */

   if (!list_member(*param_exprs, expr))
   {
    *operators = lappend_oid(*operators, hasheqoperator);
    *param_exprs = lappend(*param_exprs, expr);
   }

   /*
    * When the join operator is not hashable then it's possible that
    * the operator will be able to distinguish something that the
    * hash equality operator could not. For example with floating
    * point types -0.0 and +0.0 are classed as equal by the hash
    * function and equality function, but some other operator may be
    * able to tell those values apart.  This means that we must put
    * memoize into binary comparison mode so that it does bit-by-bit
    * comparisons rather than a "logical" comparison as it would
    * using the hash equality operator.
 */

   if (!OidIsValid(rinfo->hashjoinoperator))
    *binary_mode = true;
  }
 }

 /* Now add any lateral vars to the cache key too */
 lateral_vars = list_concat(ph_lateral_vars, innerrel->lateral_vars);
 foreach(lc, lateral_vars)
 {
  Node    *expr = (Node *) lfirst(lc);
  TypeCacheEntry *typentry;

  /* Reject if there are any volatile functions in lateral vars */
  if (contain_volatile_functions(expr))
  {
   list_free(*operators);
   list_free(*param_exprs);
   return false;
  }

  typentry = lookup_type_cache(exprType(expr),
          TYPECACHE_HASH_PROC | TYPECACHE_EQ_OPR);

  /* can't use memoize without a valid hash proc and equals operator */
  if (!OidIsValid(typentry->hash_proc) || !OidIsValid(typentry->eq_opr))
  {
   list_free(*operators);
   list_free(*param_exprs);
   return false;
  }

  /*
   * 'expr' may already exist as a parameter from the ppi_clauses.  No
   * need to include it again, however we'd better ensure we do switch
   * into binary mode.
 */

  if (!list_member(*param_exprs, expr))
  {
   *operators = lappend_oid(*operators, typentry->eq_opr);
   *param_exprs = lappend(*param_exprs, expr);
  }

  /*
   * We must go into binary mode as we don't have too much of an idea of
   * how these lateral Vars are being used.  See comment above when we
   * set *binary_mode for the non-lateral Var case. This could be
   * relaxed a bit if we had the RestrictInfos and knew the operators
   * being used, however for cases like Vars that are arguments to
   * functions we must operate in binary mode as we don't have
   * visibility into what the function is doing with the Vars.
 */

  *binary_mode = true;
 }

 /* We're okay to use memoize */
 return true;
}

/*
 * extract_lateral_vars_from_PHVs
 *   Extract lateral references within PlaceHolderVars that are due to be
 *   evaluated at 'innerrelids'.
 */

static List *
extract_lateral_vars_from_PHVs(PlannerInfo *root, Relids innerrelids)
{
 List    *ph_lateral_vars = NIL;
 ListCell   *lc;

 /* Nothing would be found if the query contains no LATERAL RTEs */
 if (!root->hasLateralRTEs)
  return NIL;

 /*
  * No need to consider PHVs that are due to be evaluated at joinrels,
  * since we do not add Memoize nodes on top of joinrel paths.
 */

 if (bms_membership(innerrelids) == BMS_MULTIPLE)
  return NIL;

 foreach(lc, root->placeholder_list)
 {
  PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
  List    *vars;
  ListCell   *cell;

  /* PHV is uninteresting if no lateral refs */
  if (phinfo->ph_lateral == NULL)
   continue;

  /* PHV is uninteresting if not due to be evaluated at innerrelids */
  if (!bms_equal(phinfo->ph_eval_at, innerrelids))
   continue;

  /*
   * If the PHV does not reference any rels in innerrelids, use its
   * contained expression as a cache key rather than extracting the
   * Vars/PHVs from it and using those.  This can be beneficial in cases
   * where the expression results in fewer distinct values to cache
   * tuples for.
 */

  if (!bms_overlap(pull_varnos(root, (Node *) phinfo->ph_var->phexpr),
       innerrelids))
  {
   ph_lateral_vars = lappend(ph_lateral_vars, phinfo->ph_var->phexpr);
   continue;
  }

  /* Fetch Vars and PHVs of lateral references within PlaceHolderVars */
  vars = pull_vars_of_level((Node *) phinfo->ph_var->phexpr, 0);
  foreach(cell, vars)
  {
   Node    *node = (Node *) lfirst(cell);

   if (IsA(node, Var))
   {
    Var     *var = (Var *) node;

    Assert(var->varlevelsup == 0);

    if (bms_is_member(var->varno, phinfo->ph_lateral))
     ph_lateral_vars = lappend(ph_lateral_vars, node);
   }
   else if (IsA(node, PlaceHolderVar))
   {
    PlaceHolderVar *phv = (PlaceHolderVar *) node;

    Assert(phv->phlevelsup == 0);

    if (bms_is_subset(find_placeholder_info(root, phv)->ph_eval_at,
          phinfo->ph_lateral))
     ph_lateral_vars = lappend(ph_lateral_vars, node);
   }
   else
    Assert(false);
  }

  list_free(vars);
 }

 return ph_lateral_vars;
}

/*
 * get_memoize_path
 *  If possible, make and return a Memoize path atop of 'inner_path'.
 *  Otherwise return NULL.
 *
 * Note that currently we do not add Memoize nodes on top of join relation
 * paths.  This is because the ParamPathInfos for join relation paths do not
 * maintain ppi_clauses, as the set of relevant clauses varies depending on how
 * the join is formed.  In addition, joinrels do not maintain lateral_vars.  So
 * we do not have a way to extract cache keys from joinrels.
 */

static Path *
get_memoize_path(PlannerInfo *root, RelOptInfo *innerrel,
     RelOptInfo *outerrel, Path *inner_path,
     Path *outer_path, JoinType jointype,
     JoinPathExtraData *extra)
{
 List    *param_exprs;
 List    *hash_operators;
 ListCell   *lc;
 bool  binary_mode;
 List    *ph_lateral_vars;

 /* Obviously not if it's disabled */
 if (!enable_memoize)
  return NULL;

 /*
  * We can safely not bother with all this unless we expect to perform more
  * than one inner scan.  The first scan is always going to be a cache
  * miss.  This would likely fail later anyway based on costs, so this is
  * really just to save some wasted effort.
 */

 if (outer_path->parent->rows < 2)
  return NULL;

 /*
  * Extract lateral Vars/PHVs within PlaceHolderVars that are due to be
  * evaluated at innerrel.  These lateral Vars/PHVs could be used as
  * memoize cache keys.
 */

 ph_lateral_vars = extract_lateral_vars_from_PHVs(root, innerrel->relids);

 /*
  * We can only have a memoize node when there's some kind of cache key,
  * either parameterized path clauses or lateral Vars.  No cache key sounds
  * more like something a Materialize node might be more useful for.
 */

 if ((inner_path->param_info == NULL ||
   inner_path->param_info->ppi_clauses == NIL) &&
  innerrel->lateral_vars == NIL &&
  ph_lateral_vars == NIL)
  return NULL;

 /*
  * Currently we don't do this for SEMI and ANTI joins unless they're
  * marked as inner_unique.  This is because nested loop SEMI/ANTI joins
  * don't scan the inner node to completion, which will mean memoize cannot
  * mark the cache entry as complete.
  *
  * XXX Currently we don't attempt to mark SEMI/ANTI joins as inner_unique
  * = true.  Should we?  See add_paths_to_joinrel()
 */

 if (!extra->inner_unique && (jointype == JOIN_SEMI ||
         jointype == JOIN_ANTI))
  return NULL;

 /*
  * Memoize normally marks cache entries as complete when it runs out of
  * tuples to read from its subplan.  However, with unique joins, Nested
  * Loop will skip to the next outer tuple after finding the first matching
  * inner tuple.  This means that we may not read the inner side of the
  * join to completion which leaves no opportunity to mark the cache entry
  * as complete.  To work around that, when the join is unique we
  * automatically mark cache entries as complete after fetching the first
  * tuple.  This works when the entire join condition is parameterized.
  * Otherwise, when the parameterization is only a subset of the join
  * condition, we can't be sure which part of it causes the join to be
  * unique.  This means there are no guarantees that only 1 tuple will be
  * read.  We cannot mark the cache entry as complete after reading the
  * first tuple without that guarantee.  This means the scope of Memoize
  * node's usefulness is limited to only outer rows that have no join
  * partner as this is the only case where Nested Loop would exhaust the
  * inner scan of a unique join.  Since the scope is limited to that, we
  * just don't bother making a memoize path in this case.
  *
  * Lateral vars needn't be considered here as they're not considered when
  * determining if the join is unique.
 */

 if (extra->inner_unique)
 {
  Bitmapset  *ppi_serials;

  if (inner_path->param_info == NULL)
   return NULL;

  ppi_serials = inner_path->param_info->ppi_serials;

  foreach_node(RestrictInfo, rinfo, extra->restrictlist)
  {
   if (!bms_is_member(rinfo->rinfo_serial, ppi_serials))
    return NULL;
  }
 }

 /*
  * We can't use a memoize node if there are volatile functions in the
  * inner rel's target list or restrict list.  A cache hit could reduce the
  * number of calls to these functions.
 */

 if (contain_volatile_functions((Node *) innerrel->reltarget))
  return NULL;

 foreach(lc, innerrel->baserestrictinfo)
 {
  RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);

  if (contain_volatile_functions((Node *) rinfo))
   return NULL;
 }

 /*
  * Also check the parameterized path restrictinfos for volatile functions.
  * Indexed functions must be immutable so shouldn't have any volatile
  * functions, however, with a lateral join the inner scan may not be an
  * index scan.
 */

 if (inner_path->param_info != NULL)
 {
  foreach(lc, inner_path->param_info->ppi_clauses)
  {
   RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);

   if (contain_volatile_functions((Node *) rinfo))
    return NULL;
  }
 }

 /* Check if we have hash ops for each parameter to the path */
 if (paraminfo_get_equal_hashops(root,
         inner_path->param_info,
         outerrel->top_parent ?
         outerrel->top_parent : outerrel,
         innerrel,
         ph_lateral_vars,
         ¶m_exprs,
         &hash_operators,
         &binary_mode))
 {
  return (Path *) create_memoize_path(root,
           innerrel,
           inner_path,
           param_exprs,
           hash_operators,
           extra->inner_unique,
           binary_mode,
           outer_path->rows);
 }

 return NULL;
}

/*
 * try_nestloop_path
 *   Consider a nestloop join path; if it appears useful, push it into
 *   the joinrel's pathlist via add_path().
 */

static void
try_nestloop_path(PlannerInfo *root,
      RelOptInfo *joinrel,
      Path *outer_path,
      Path *inner_path,
      List *pathkeys,
      JoinType jointype,
      JoinPathExtraData *extra)
{
 Relids  required_outer;
 JoinCostWorkspace workspace;
 RelOptInfo *innerrel = inner_path->parent;
 RelOptInfo *outerrel = outer_path->parent;
 Relids  innerrelids;
 Relids  outerrelids;
 Relids  inner_paramrels = PATH_REQ_OUTER(inner_path);
 Relids  outer_paramrels = PATH_REQ_OUTER(outer_path);

 /*
  * If we are forming an outer join at this join, it's nonsensical to use
  * an input path that uses the outer join as part of its parameterization.
  * (This can happen despite our join order restrictions, since those apply
  * to what is in an input relation not what its parameters are.)
 */

 if (extra->sjinfo->ojrelid != 0 &&
  (bms_is_member(extra->sjinfo->ojrelid, inner_paramrels) ||
   bms_is_member(extra->sjinfo->ojrelid, outer_paramrels)))
  return;

 /*
  * Any parameterization of the input paths refers to topmost parents of
  * the relevant relations, because reparameterize_path_by_child() hasn't
  * been called yet.  So we must consider topmost parents of the relations
  * being joined, too, while determining parameterization of the result and
  * checking for disallowed parameterization cases.
 */

 if (innerrel->top_parent_relids)
  innerrelids = innerrel->top_parent_relids;
 else
  innerrelids = innerrel->relids;

 if (outerrel->top_parent_relids)
  outerrelids = outerrel->top_parent_relids;
 else
  outerrelids = outerrel->relids;

 /*
  * Check to see if proposed path is still parameterized, and reject if the
  * parameterization wouldn't be sensible --- unless allow_star_schema_join
  * says to allow it anyway.
 */

 required_outer = calc_nestloop_required_outer(outerrelids, outer_paramrels,
              innerrelids, inner_paramrels);
 if (required_outer &&
  !bms_overlap(required_outer, extra->param_source_rels) &&
  !allow_star_schema_join(root, outerrelids, inner_paramrels))
 {
  /* Waste no memory when we reject a path here */
  bms_free(required_outer);
  return;
 }

 /* If we got past that, we shouldn't have any unsafe outer-join refs */
 Assert(!have_unsafe_outer_join_ref(root, outerrelids, inner_paramrels));

 /*
  * If the inner path is parameterized, it is parameterized by the topmost
  * parent of the outer rel, not the outer rel itself.  We will need to
  * translate the parameterization, if this path is chosen, during
  * create_plan().  Here we just check whether we will be able to perform
  * the translation, and if not avoid creating a nestloop path.
 */

 if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent) &&
  !path_is_reparameterizable_by_child(inner_path, outer_path->parent))
 {
  bms_free(required_outer);
  return;
 }

 /*
  * Do a precheck to quickly eliminate obviously-inferior paths.  We
  * calculate a cheap lower bound on the path's cost and then use
  * add_path_precheck() to see if the path is clearly going to be dominated
  * by some existing path for the joinrel.  If not, do the full pushup with
  * creating a fully valid path structure and submitting it to add_path().
  * The latter two steps are expensive enough to make this two-phase
  * methodology worthwhile.
 */

 initial_cost_nestloop(root, &workspace, jointype,
        outer_path, inner_path, extra);

 if (add_path_precheck(joinrel, workspace.disabled_nodes,
        workspace.startup_cost, workspace.total_cost,
        pathkeys, required_outer))
 {
  add_path(joinrel, (Path *)
     create_nestloop_path(root,
           joinrel,
           jointype,
           &workspace,
           extra,
           outer_path,
           inner_path,
           extra->restrictlist,
           pathkeys,
           required_outer));
 }
 else
 {
  /* Waste no memory when we reject a path here */
  bms_free(required_outer);
 }
}

/*
 * try_partial_nestloop_path
 *   Consider a partial nestloop join path; if it appears useful, push it into
 *   the joinrel's partial_pathlist via add_partial_path().
 */

static void
try_partial_nestloop_path(PlannerInfo *root,
        RelOptInfo *joinrel,
        Path *outer_path,
        Path *inner_path,
        List *pathkeys,
        JoinType jointype,
        JoinPathExtraData *extra)
{
 JoinCostWorkspace workspace;

 /*
  * If the inner path is parameterized, the parameterization must be fully
  * satisfied by the proposed outer path.  Parameterized partial paths are
  * not supported.  The caller should already have verified that no lateral
  * rels are required here.
 */

 Assert(bms_is_empty(joinrel->lateral_relids));
 Assert(bms_is_empty(PATH_REQ_OUTER(outer_path)));
 if (inner_path->param_info != NULL)
 {
  Relids  inner_paramrels = inner_path->param_info->ppi_req_outer;
  RelOptInfo *outerrel = outer_path->parent;
  Relids  outerrelids;

  /*
   * The inner and outer paths are parameterized, if at all, by the top
   * level parents, not the child relations, so we must use those relids
   * for our parameterization tests.
 */

  if (outerrel->top_parent_relids)
   outerrelids = outerrel->top_parent_relids;
  else
   outerrelids = outerrel->relids;

  if (!bms_is_subset(inner_paramrels, outerrelids))
   return;
 }

 /*
  * If the inner path is parameterized, it is parameterized by the topmost
  * parent of the outer rel, not the outer rel itself.  We will need to
  * translate the parameterization, if this path is chosen, during
  * create_plan().  Here we just check whether we will be able to perform
  * the translation, and if not avoid creating a nestloop path.
 */

 if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent) &&
  !path_is_reparameterizable_by_child(inner_path, outer_path->parent))
  return;

 /*
  * 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,
        outer_path, inner_path, extra);
 if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes,
           workspace.total_cost, pathkeys))
  return;

 /* Might be good enough to be worth trying, so let's try it. */
 add_partial_path(joinrel, (Path *)
      create_nestloop_path(root,
            joinrel,
            jointype,
            &workspace,
            extra,
            outer_path,
            inner_path,
            extra->restrictlist,
            pathkeys,
            NULL));
}

/*
 * try_mergejoin_path
 *   Consider a merge join path; if it appears useful, push it into
 *   the joinrel's pathlist via add_path().
 */

static void
try_mergejoin_path(PlannerInfo *root,
       RelOptInfo *joinrel,
       Path *outer_path,
       Path *inner_path,
       List *pathkeys,
       List *mergeclauses,
       List *outersortkeys,
       List *innersortkeys,
       JoinType jointype,
       JoinPathExtraData *extra,
       bool is_partial)
{
 Relids  required_outer;
 int   outer_presorted_keys = 0;
 JoinCostWorkspace workspace;

 if (is_partial)
 {
  try_partial_mergejoin_path(root,
           joinrel,
           outer_path,
           inner_path,
           pathkeys,
           mergeclauses,
           outersortkeys,
           innersortkeys,
           jointype,
           extra);
  return;
 }

 /*
  * If we are forming an outer join at this join, it's nonsensical to use
  * an input path that uses the outer join as part of its parameterization.
  * (This can happen despite our join order restrictions, since those apply
  * to what is in an input relation not what its parameters are.)
 */

 if (extra->sjinfo->ojrelid != 0 &&
  (bms_is_member(extra->sjinfo->ojrelid, PATH_REQ_OUTER(inner_path)) ||
   bms_is_member(extra->sjinfo->ojrelid, PATH_REQ_OUTER(outer_path))))
  return;

 /*
  * Check to see if proposed path is still parameterized, and reject if the
  * parameterization wouldn't be sensible.
 */

 required_outer = calc_non_nestloop_required_outer(outer_path,
               inner_path);
 if (required_outer &&
  !bms_overlap(required_outer, extra->param_source_rels))
 {
  /* Waste no memory when we reject a path here */
  bms_free(required_outer);
  return;
 }

 /*
  * If the given paths are already well enough ordered, we can skip doing
  * an explicit sort.
  *
  * We need to determine the number of presorted keys of the outer path to
  * decide whether explicit incremental sort can be applied when
  * outersortkeys is not NIL.  We do not need to do the same for the inner
  * path though, as incremental sort currently does not support
  * mark/restore.
 */

 if (outersortkeys &&
  pathkeys_count_contained_in(outersortkeys, outer_path->pathkeys,
         &outer_presorted_keys))
  outersortkeys = NIL;
 if (innersortkeys &&
  pathkeys_contained_in(innersortkeys, inner_path->pathkeys))
  innersortkeys = NIL;

 /*
  * See comments in try_nestloop_path().
 */

 initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
         outer_path, inner_path,
         outersortkeys, innersortkeys,
         outer_presorted_keys,
         extra);

 if (add_path_precheck(joinrel, workspace.disabled_nodes,
        workspace.startup_cost, workspace.total_cost,
        pathkeys, required_outer))
 {
  add_path(joinrel, (Path *)
     create_mergejoin_path(root,
            joinrel,
            jointype,
            &workspace,
            extra,
            outer_path,
            inner_path,
            extra->restrictlist,
            pathkeys,
            required_outer,
            mergeclauses,
            outersortkeys,
            innersortkeys,
            outer_presorted_keys));
 }
 else
 {
  /* Waste no memory when we reject a path here */
  bms_free(required_outer);
 }
}

/*
 * try_partial_mergejoin_path
 *   Consider a partial merge join path; if it appears useful, push it into
 *   the joinrel's pathlist via add_partial_path().
 */

static void
try_partial_mergejoin_path(PlannerInfo *root,
         RelOptInfo *joinrel,
         Path *outer_path,
         Path *inner_path,
         List *pathkeys,
         List *mergeclauses,
         List *outersortkeys,
         List *innersortkeys,
         JoinType jointype,
         JoinPathExtraData *extra)
{
 int   outer_presorted_keys = 0;
 JoinCostWorkspace workspace;

 /*
  * See comments in try_partial_hashjoin_path().
 */

 Assert(bms_is_empty(joinrel->lateral_relids));
 Assert(bms_is_empty(PATH_REQ_OUTER(outer_path)));
 if (!bms_is_empty(PATH_REQ_OUTER(inner_path)))
  return;

 /*
  * If the given paths are already well enough ordered, we can skip doing
  * an explicit sort.
  *
  * We need to determine the number of presorted keys of the outer path to
  * decide whether explicit incremental sort can be applied when
  * outersortkeys is not NIL.  We do not need to do the same for the inner
  * path though, as incremental sort currently does not support
  * mark/restore.
 */

 if (outersortkeys &&
  pathkeys_count_contained_in(outersortkeys, outer_path->pathkeys,
         &outer_presorted_keys))
  outersortkeys = NIL;
 if (innersortkeys &&
  pathkeys_contained_in(innersortkeys, inner_path->pathkeys))
  innersortkeys = NIL;

 /*
  * See comments in try_partial_nestloop_path().
 */

 initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
         outer_path, inner_path,
         outersortkeys, innersortkeys,
         outer_presorted_keys,
         extra);

 if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes,
           workspace.total_cost, pathkeys))
  return;

 /* Might be good enough to be worth trying, so let's try it. */
 add_partial_path(joinrel, (Path *)
      create_mergejoin_path(root,
             joinrel,
             jointype,
             &workspace,
             extra,
             outer_path,
             inner_path,
             extra->restrictlist,
             pathkeys,
             NULL,
             mergeclauses,
             outersortkeys,
             innersortkeys,
             outer_presorted_keys));
}

/*
 * try_hashjoin_path
 *   Consider a hash join path; if it appears useful, push it into
 *   the joinrel's pathlist via add_path().
 */

static void
try_hashjoin_path(PlannerInfo *root,
      RelOptInfo *joinrel,
      Path *outer_path,
      Path *inner_path,
      List *hashclauses,
      JoinType jointype,
      JoinPathExtraData *extra)
{
 Relids  required_outer;
 JoinCostWorkspace workspace;

 /*
  * If we are forming an outer join at this join, it's nonsensical to use
  * an input path that uses the outer join as part of its parameterization.
  * (This can happen despite our join order restrictions, since those apply
  * to what is in an input relation not what its parameters are.)
 */

 if (extra->sjinfo->ojrelid != 0 &&
  (bms_is_member(extra->sjinfo->ojrelid, PATH_REQ_OUTER(inner_path)) ||
   bms_is_member(extra->sjinfo->ojrelid, PATH_REQ_OUTER(outer_path))))
  return;

 /*
  * Check to see if proposed path is still parameterized, and reject if the
  * parameterization wouldn't be sensible.
 */

 required_outer = calc_non_nestloop_required_outer(outer_path,
               inner_path);
 if (required_outer &&
  !bms_overlap(required_outer, extra->param_source_rels))
 {
  /* Waste no memory when we reject a path here */
  bms_free(required_outer);
  return;
 }

 /*
  * See comments in try_nestloop_path().  Also note that hashjoin paths
  * never have any output pathkeys, per comments in create_hashjoin_path.
 */

 initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
        outer_path, inner_path, extra, false);

 if (add_path_precheck(joinrel, workspace.disabled_nodes,
        workspace.startup_cost, workspace.total_cost,
        NIL, required_outer))
 {
  add_path(joinrel, (Path *)
     create_hashjoin_path(root,
           joinrel,
           jointype,
           &workspace,
           extra,
           outer_path,
           inner_path,
           false, /* parallel_hash */
           extra->restrictlist,
           required_outer,
           hashclauses));
 }
 else
 {
  /* Waste no memory when we reject a path here */
  bms_free(required_outer);
 }
}

/*
 * try_partial_hashjoin_path
 *   Consider a partial hashjoin join path; if it appears useful, push it into
 *   the joinrel's partial_pathlist via add_partial_path().
 *   The outer side is partial.  If parallel_hash is true, then the inner path
 *   must be partial and will be run in parallel to create one or more shared
 *   hash tables; otherwise the inner path must be complete and a copy of it
 *   is run in every process to create separate identical private hash tables.
 */

static void
try_partial_hashjoin_path(PlannerInfo *root,
        RelOptInfo *joinrel,
        Path *outer_path,
        Path *inner_path,
        List *hashclauses,
        JoinType jointype,
        JoinPathExtraData *extra,
        bool parallel_hash)
{
 JoinCostWorkspace workspace;

 /*
  * If the inner path is parameterized, we can't use a partial hashjoin.
  * Parameterized partial paths are not supported.  The caller should
  * already have verified that no lateral rels are required here.
 */

 Assert(bms_is_empty(joinrel->lateral_relids));
 Assert(bms_is_empty(PATH_REQ_OUTER(outer_path)));
 if (!bms_is_empty(PATH_REQ_OUTER(inner_path)))
  return;

 /*
  * 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_hashjoin(root, &workspace, jointype, hashclauses,
        outer_path, inner_path, extra, parallel_hash);
 if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes,
           workspace.total_cost, NIL))
  return;

 /* Might be good enough to be worth trying, so let's try it. */
 add_partial_path(joinrel, (Path *)
      create_hashjoin_path(root,
            joinrel,
            jointype,
            &workspace,
            extra,
            outer_path,
            inner_path,
            parallel_hash,
            extra->restrictlist,
            NULL,
            hashclauses));
}

/*
 * sort_inner_and_outer
 *   Create mergejoin join paths by explicitly sorting both the outer and
 *   inner join relations on each available merge ordering.
 *
 * 'joinrel' is the join relation
 * 'outerrel' is the outer join relation
 * 'innerrel' is the inner join relation
 * 'jointype' is the type of join to do
 * 'extra' contains additional input values
 */

static void
sort_inner_and_outer(PlannerInfo *root,
      RelOptInfo *joinrel,
      RelOptInfo *outerrel,
      RelOptInfo *innerrel,
      JoinType jointype,
      JoinPathExtraData *extra)
{
 JoinType save_jointype = jointype;
 Path    *outer_path;
 Path    *inner_path;
 Path    *cheapest_partial_outer = NULL;
 Path    *cheapest_safe_inner = NULL;
 List    *all_pathkeys;
 ListCell   *l;

 /* Nothing to do if there are no available mergejoin clauses */
 if (extra->mergeclause_list == NIL)
  return;

 /*
  * We only consider the cheapest-total-cost input paths, since we are
  * assuming here that a sort is required.  We will consider
  * cheapest-startup-cost input paths later, and only if they don't need a
  * sort.
  *
  * This function intentionally does not consider parameterized input
  * paths, except when the cheapest-total is parameterized.  If we did so,
  * we'd have a combinatorial explosion of mergejoin paths of dubious
  * value.  This interacts with decisions elsewhere that also discriminate
  * against mergejoins with parameterized inputs; see comments in
  * src/backend/optimizer/README.
 */

 outer_path = outerrel->cheapest_total_path;
 inner_path = innerrel->cheapest_total_path;

 /*
  * If either cheapest-total path is parameterized by the other rel, we
  * can't use a mergejoin.  (There's no use looking for alternative input
  * paths, since these should already be the least-parameterized available
  * paths.)
 */

 if (PATH_PARAM_BY_REL(outer_path, innerrel) ||
  PATH_PARAM_BY_REL(inner_path, outerrel))
  return;

 /*
  * If unique-ification is requested, do it and then handle as a plain
  * inner join.
 */

 if (jointype == JOIN_UNIQUE_OUTER)
 {
  outer_path = (Path *) create_unique_path(root, outerrel,
             outer_path, extra->sjinfo);
  Assert(outer_path);
  jointype = JOIN_INNER;
 }
 else if (jointype == JOIN_UNIQUE_INNER)
 {
  inner_path = (Path *) create_unique_path(root, innerrel,
             inner_path, extra->sjinfo);
  Assert(inner_path);
  jointype = JOIN_INNER;
 }

 /*
  * If the joinrel is parallel-safe, we may be able to consider a partial
  * merge join.  However, we can't handle JOIN_UNIQUE_OUTER, because the
  * outer path will be partial, and therefore we won't be able to properly
  * guarantee uniqueness.  Similarly, we can't handle JOIN_FULL, JOIN_RIGHT
  * and JOIN_RIGHT_ANTI, because they can produce false null extended rows.
  * Also, the resulting path must not be parameterized.
 */

 if (joinrel->consider_parallel &&
  save_jointype != JOIN_UNIQUE_OUTER &&
  save_jointype != JOIN_FULL &&
  save_jointype != JOIN_RIGHT &&
  save_jointype != JOIN_RIGHT_ANTI &&
  outerrel->partial_pathlist != NIL &&
  bms_is_empty(joinrel->lateral_relids))
 {
  cheapest_partial_outer = (Path *) linitial(outerrel->partial_pathlist);

  if (inner_path->parallel_safe)
   cheapest_safe_inner = inner_path;
  else if (save_jointype != JOIN_UNIQUE_INNER)
   cheapest_safe_inner =
    get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
 }

 /*
  * Each possible ordering of the available mergejoin clauses will generate
  * a differently-sorted result path at essentially the same cost.  We have
  * no basis for choosing one over another at this level of joining, but
  * some sort orders may be more useful than others for higher-level
  * mergejoins, so it's worth considering multiple orderings.
  *
  * Actually, it's not quite true that every mergeclause ordering will
  * generate a different path order, because some of the clauses may be
  * partially redundant (refer to the same EquivalenceClasses).  Therefore,
  * what we do is convert the mergeclause list to a list of canonical
  * pathkeys, and then consider different orderings of the pathkeys.
  *
  * Generating a path for *every* permutation of the pathkeys doesn't seem
  * like a winning strategy; the cost in planning time is too high. For
  * now, we generate one path for each pathkey, listing that pathkey first
  * and the rest in random order.  This should allow at least a one-clause
  * mergejoin without re-sorting against any other possible mergejoin
  * partner path.  But if we've not guessed the right ordering of secondary
  * keys, we may end up evaluating clauses as qpquals when they could have
  * been done as mergeclauses.  (In practice, it's rare that there's more
  * than two or three mergeclauses, so expending a huge amount of thought
  * on that is probably not worth it.)
  *
  * The pathkey order returned by select_outer_pathkeys_for_merge() has
  * some heuristics behind it (see that function), so be sure to try it
  * exactly as-is as well as making variants.
 */

 all_pathkeys = select_outer_pathkeys_for_merge(root,
               extra->mergeclause_list,
               joinrel);

 foreach(l, all_pathkeys)
 {
  PathKey    *front_pathkey = (PathKey *) lfirst(l);
  List    *cur_mergeclauses;
  List    *outerkeys;
  List    *innerkeys;
  List    *merge_pathkeys;

  /* Make a pathkey list with this guy first */
  if (l != list_head(all_pathkeys))
   outerkeys = lcons(front_pathkey,
         list_delete_nth_cell(list_copy(all_pathkeys),
               foreach_current_index(l)));
  else
   outerkeys = all_pathkeys; /* no work at first one... */

  /* Sort the mergeclauses into the corresponding ordering */
  cur_mergeclauses =
   find_mergeclauses_for_outer_pathkeys(root,
             outerkeys,
             extra->mergeclause_list);

  /* Should have used them all... */
  Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));

  /* Build sort pathkeys for the inner side */
  innerkeys = make_inner_pathkeys_for_merge(root,
              cur_mergeclauses,
              outerkeys);

  /* Build pathkeys representing output sort order */
  merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
            outerkeys);

  /*
   * And now we can make the path.
   *
   * Note: it's possible that the cheapest paths will already be sorted
   * properly.  try_mergejoin_path will detect that case and suppress an
   * explicit sort step, so we needn't do so here.
 */

  try_mergejoin_path(root,
         joinrel,
         outer_path,
         inner_path,
         merge_pathkeys,
         cur_mergeclauses,
         outerkeys,
         innerkeys,
         jointype,
         extra,
         false);

  /*
   * If we have partial outer and parallel safe inner path then try
   * partial mergejoin path.
 */

  if (cheapest_partial_outer && cheapest_safe_inner)
   try_partial_mergejoin_path(root,
            joinrel,
            cheapest_partial_outer,
            cheapest_safe_inner,
            merge_pathkeys,
            cur_mergeclauses,
            outerkeys,
            innerkeys,
            jointype,
            extra);
 }
}

/*
 * generate_mergejoin_paths
 * Creates possible mergejoin paths for input outerpath.
 *
 * We generate mergejoins if mergejoin clauses are available.  We have
 * two ways to generate the inner path for a mergejoin: sort the cheapest
 * inner path, or use an inner path that is already suitably ordered for the
 * merge.  If we have several mergeclauses, it could be that there is no inner
 * path (or only a very expensive one) for the full list of mergeclauses, but
 * better paths exist if we truncate the mergeclause list (thereby discarding
 * some sort key requirements).  So, we consider truncations of the
 * mergeclause list as well as the full list.  (Ideally we'd consider all
 * subsets of the mergeclause list, but that seems way too expensive.)
 */

static void
generate_mergejoin_paths(PlannerInfo *root,
       RelOptInfo *joinrel,
       RelOptInfo *innerrel,
       Path *outerpath,
       JoinType jointype,
       JoinPathExtraData *extra,
       bool useallclauses,
       Path *inner_cheapest_total,
       List *merge_pathkeys,
       bool is_partial)
{
 List    *mergeclauses;
 List    *innersortkeys;
 List    *trialsortkeys;
 Path    *cheapest_startup_inner;
 Path    *cheapest_total_inner;
 JoinType save_jointype = jointype;
 int   num_sortkeys;
 int   sortkeycnt;

 if (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER)
  jointype = JOIN_INNER;

 /* Look for useful mergeclauses (if any) */
 mergeclauses =
  find_mergeclauses_for_outer_pathkeys(root,
            outerpath->pathkeys,
            extra->mergeclause_list);

 /*
  * Done with this outer path if no chance for a mergejoin.
  *
  * Special corner case: for "x FULL JOIN y ON true", there will be no join
  * clauses at all.  Ordinarily we'd generate a clauseless nestloop path,
  * but since mergejoin is our only join type that supports FULL JOIN
  * without any join clauses, it's necessary to generate a clauseless
  * mergejoin path instead.
 */

 if (mergeclauses == NIL)
 {
  if (jointype == JOIN_FULL)
    /* okay to try for mergejoin */ ;
  else
   return;
 }
 if (useallclauses &&
  list_length(mergeclauses) != list_length(extra->mergeclause_list))
  return;

 /* Compute the required ordering of the inner path */
 innersortkeys = make_inner_pathkeys_for_merge(root,
              mergeclauses,
              outerpath->pathkeys);

 /*
  * Generate a mergejoin on the basis of sorting the cheapest inner. Since
  * a sort will be needed, only cheapest total cost matters. (But
  * try_mergejoin_path will do the right thing if inner_cheapest_total is
  * already correctly sorted.)
 */

 try_mergejoin_path(root,
        joinrel,
        outerpath,
        inner_cheapest_total,
        merge_pathkeys,
        mergeclauses,
        NIL,
        innersortkeys,
        jointype,
        extra,
        is_partial);

 /* Can't do anything else if inner path needs to be unique'd */
 if (save_jointype == JOIN_UNIQUE_INNER)
  return;

 /*
  * Look for presorted inner paths that satisfy the innersortkey list ---
  * or any truncation thereof, if we are allowed to build a mergejoin using
  * a subset of the merge clauses.  Here, we consider both cheap startup
  * cost and cheap total cost.
  *
  * Currently we do not consider parameterized inner paths here. This
  * interacts with decisions elsewhere that also discriminate against
  * mergejoins with parameterized inputs; see comments in
  * src/backend/optimizer/README.
  *
  * As we shorten the sortkey list, we should consider only paths that are
  * strictly cheaper than (in particular, not the same as) any path found
  * in an earlier iteration.  Otherwise we'd be intentionally using fewer
  * merge keys than a given path allows (treating the rest as plain
  * joinquals), which is unlikely to be a good idea.  Also, eliminating
  * paths here on the basis of compare_path_costs is a lot cheaper than
  * building the mergejoin path only to throw it away.
  *
  * If inner_cheapest_total is well enough sorted to have not required a
  * sort in the path made above, we shouldn't make a duplicate path with
  * it, either.  We handle that case with the same logic that handles the
  * previous consideration, by initializing the variables that track
  * cheapest-so-far properly.  Note that we do NOT reject
  * inner_cheapest_total if we find it matches some shorter set of
  * pathkeys.  That case corresponds to using fewer mergekeys to avoid
  * sorting inner_cheapest_total, whereas we did sort it above, so the
  * plans being considered are different.
 */

 if (pathkeys_contained_in(innersortkeys,
         inner_cheapest_total->pathkeys))
 {
  /* inner_cheapest_total didn't require a sort */
  cheapest_startup_inner = inner_cheapest_total;
  cheapest_total_inner = inner_cheapest_total;
 }
 else
 {
  /* it did require a sort, at least for the full set of keys */
  cheapest_startup_inner = NULL;
  cheapest_total_inner = NULL;
 }
 num_sortkeys = list_length(innersortkeys);
 if (num_sortkeys > 1 && !useallclauses)
  trialsortkeys = list_copy(innersortkeys); /* need modifiable copy */
 else
  trialsortkeys = innersortkeys; /* won't really truncate */

 for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
 {
  Path    *innerpath;
  List    *newclauses = NIL;

  /*
   * Look for an inner path ordered well enough for the first
   * 'sortkeycnt' innersortkeys.  NB: trialsortkeys list is modified
   * destructively, which is why we made a copy...
 */

  trialsortkeys = list_truncate(trialsortkeys, sortkeycnt);
  innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist,
               trialsortkeys,
               NULL,
               TOTAL_COST,
               is_partial);
  if (innerpath != NULL &&
   (cheapest_total_inner == NULL ||
    compare_path_costs(innerpath, cheapest_total_inner,
        TOTAL_COST) < 0))
  {
   /* Found a cheap (or even-cheaper) sorted path */
   /* Select the right mergeclauses, if we didn't already */
   if (sortkeycnt < num_sortkeys)
   {
    newclauses =
     trim_mergeclauses_for_inner_pathkeys(root,
               mergeclauses,
               trialsortkeys);
    Assert(newclauses != NIL);
   }
   else
    newclauses = mergeclauses;
   try_mergejoin_path(root,
          joinrel,
          outerpath,
          innerpath,
          merge_pathkeys,
          newclauses,
          NIL,
          NIL,
          jointype,
          extra,
          is_partial);
   cheapest_total_inner = innerpath;
  }
  /* Same on the basis of cheapest startup cost ... */
  innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist,
               trialsortkeys,
               NULL,
               STARTUP_COST,
               is_partial);
  if (innerpath != NULL &&
   (cheapest_startup_inner == NULL ||
    compare_path_costs(innerpath, cheapest_startup_inner,
        STARTUP_COST) < 0))
  {
   /* Found a cheap (or even-cheaper) sorted path */
   if (innerpath != cheapest_total_inner)
   {
    /*
     * Avoid rebuilding clause list if we already made one; saves
     * memory in big join trees...
 */

    if (newclauses == NIL)
    {
     if (sortkeycnt < num_sortkeys)
     {
      newclauses =
       trim_mergeclauses_for_inner_pathkeys(root,
                 mergeclauses,
                 trialsortkeys);
      Assert(newclauses != NIL);
     }
     else
      newclauses = mergeclauses;
    }
    try_mergejoin_path(root,
           joinrel,
           outerpath,
           innerpath,
           merge_pathkeys,
           newclauses,
           NIL,
           NIL,
           jointype,
           extra,
           is_partial);
   }
   cheapest_startup_inner = innerpath;
  }

  /*
   * Don't consider truncated sortkeys if we need all clauses.
 */

  if (useallclauses)
   break;
 }
}

/*
 * match_unsorted_outer
 *   Creates possible join paths for processing a single join relation
 *   'joinrel' by employing either iterative substitution or
 *   mergejoining on each of its possible outer paths (considering
 *   only outer paths that are already ordered well enough for merging).
 *
 * We always generate a nestloop path for each available outer path.
 * In fact we may generate as many as five: one on the cheapest-total-cost
 * inner path, one on the same with materialization, one on the
 * cheapest-startup-cost inner path (if different), one on the
 * cheapest-total inner-indexscan path (if any), and one on the
 * cheapest-startup inner-indexscan path (if different).
 *
 * We also consider mergejoins if mergejoin clauses are available.  See
 * detailed comments in generate_mergejoin_paths.
 *
 * 'joinrel' is the join relation
 * 'outerrel' is the outer join relation
 * 'innerrel' is the inner join relation
 * 'jointype' is the type of join to do
 * 'extra' contains additional input values
 */

static void
match_unsorted_outer(PlannerInfo *root,
      RelOptInfo *joinrel,
      RelOptInfo *outerrel,
      RelOptInfo *innerrel,
      JoinType jointype,
      JoinPathExtraData *extra)
{
 JoinType save_jointype = jointype;
 bool  nestjoinOK;
 bool  useallclauses;
 Path    *inner_cheapest_total = innerrel->cheapest_total_path;
 Path    *matpath = NULL;
 ListCell   *lc1;

 /*
  * For now we do not support RIGHT_SEMI join in mergejoin or nestloop
  * join.
 */

 if (jointype == JOIN_RIGHT_SEMI)
  return;

 /*
  * Nestloop only supports inner, left, semi, and anti joins.  Also, if we
  * are doing a right, right-anti or full mergejoin, we must use *all* the
  * mergeclauses as join clauses, else we will not have a valid plan.
  * (Although these two flags are currently inverses, keep them separate
  * for clarity and possible future changes.)
 */

 switch (jointype)
 {
  case JOIN_INNER:
  case JOIN_LEFT:
  case JOIN_SEMI:
  case JOIN_ANTI:
   nestjoinOK = true;
   useallclauses = false;
   break;
  case JOIN_RIGHT:
  case JOIN_RIGHT_ANTI:
  case JOIN_FULL:
   nestjoinOK = false;
   useallclauses = true;
   break;
  case JOIN_UNIQUE_OUTER:
  case JOIN_UNIQUE_INNER:
   jointype = JOIN_INNER;
   nestjoinOK = true;
   useallclauses = false;
   break;
  default:
   elog(ERROR, "unrecognized join type: %d",
     (int) jointype);
   nestjoinOK = false; /* keep compiler quiet */
   useallclauses = false;
   break;
 }

 /*
  * If inner_cheapest_total is parameterized by the outer rel, ignore it;
  * we will consider it below as a member of cheapest_parameterized_paths,
  * but the other possibilities considered in this routine aren't usable.
 */

 if (PATH_PARAM_BY_REL(inner_cheapest_total, outerrel))
  inner_cheapest_total = NULL;

 /*
  * If we need to unique-ify the inner path, we will consider only the
  * cheapest-total inner.
 */

 if (save_jointype == JOIN_UNIQUE_INNER)
 {
  /* No way to do this with an inner path parameterized by outer rel */
  if (inner_cheapest_total == NULL)
   return;
  inner_cheapest_total = (Path *)
   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.
 */

  if (enable_material && inner_cheapest_total != NULL &&
   !ExecMaterializesOutput(inner_cheapest_total->pathtype))
   matpath = (Path *)
    create_material_path(innerrel, inner_cheapest_total);
 }

 foreach(lc1, outerrel->pathlist)
 {
  Path    *outerpath = (Path *) lfirst(lc1);
  List    *merge_pathkeys;

  /*
   * We cannot use an outer path that is parameterized by the inner rel.
 */

  if (PATH_PARAM_BY_REL(outerpath, innerrel))
   continue;

  /*
   * If we need to unique-ify the outer path, it's pointless to consider
   * any but the cheapest outer.  (XXX we don't consider parameterized
   * outers, nor inners, for unique-ified cases.  Should we?)
 */

  if (save_jointype == JOIN_UNIQUE_OUTER)
  {
   if (outerpath != outerrel->cheapest_total_path)
    continue;
   outerpath = (Path *) create_unique_path(root, outerrel,
             outerpath, extra->sjinfo);
   Assert(outerpath);
  }

  /*
   * The result will have this sort order (even if it is implemented as
   * a nestloop, and even if some of the mergeclauses are implemented by
   * qpquals rather than as true mergeclauses):
 */

  merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
            outerpath->pathkeys);

  if (save_jointype == JOIN_UNIQUE_INNER)
  {
   /*
    * Consider nestloop join, but only with the unique-ified cheapest
    * inner path
 */

   try_nestloop_path(root,
         joinrel,
         outerpath,
         inner_cheapest_total,
         merge_pathkeys,
         jointype,
         extra);
  }
  else if (nestjoinOK)
  {
   /*
    * Consider nestloop joins using this outer path and various
    * available paths for the inner relation.  We consider the
    * cheapest-total paths for each available parameterization of the
    * inner relation, including the unparameterized case.
 */

   ListCell   *lc2;

   foreach(lc2, innerrel->cheapest_parameterized_paths)
   {
    Path    *innerpath = (Path *) lfirst(lc2);
    Path    *mpath;

    try_nestloop_path(root,
          joinrel,
          outerpath,
          innerpath,
          merge_pathkeys,
          jointype,
          extra);

    /*
     * Try generating a memoize path and see if that makes the
     * nested loop any cheaper.
 */

    mpath = get_memoize_path(root, innerrel, outerrel,
           innerpath, outerpath, jointype,
           extra);
    if (mpath != NULL)
     try_nestloop_path(root,
           joinrel,
           outerpath,
           mpath,
           merge_pathkeys,
           jointype,
           extra);
   }

   /* Also consider materialized form of the cheapest inner path */
   if (matpath != NULL)
    try_nestloop_path(root,
          joinrel,
          outerpath,
          matpath,
          merge_pathkeys,
          jointype,
          extra);
  }

  /* Can't do anything else if outer path needs to be unique'd */
  if (save_jointype == JOIN_UNIQUE_OUTER)
   continue;

  /* Can't do anything else if inner rel is parameterized by outer */
  if (inner_cheapest_total == NULL)
   continue;

  /* Generate merge join paths */
  generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
         save_jointype, extra, useallclauses,
         inner_cheapest_total, merge_pathkeys,
         false);
 }

 /*
  * Consider partial nestloop and mergejoin plan if outerrel has any
  * partial path and the joinrel is parallel-safe.  However, we can't
  * handle JOIN_UNIQUE_OUTER, because the outer path will be partial, and
  * therefore we won't be able to properly guarantee uniqueness.  Nor can
  * we handle joins needing lateral rels, since partial paths must not be
  * parameterized. Similarly, we can't handle JOIN_FULL, JOIN_RIGHT and
  * JOIN_RIGHT_ANTI, because they can produce false null extended rows.
 */

 if (joinrel->consider_parallel &&
  save_jointype != JOIN_UNIQUE_OUTER &&
  save_jointype != JOIN_FULL &&
  save_jointype != JOIN_RIGHT &&
  save_jointype != JOIN_RIGHT_ANTI &&
  outerrel->partial_pathlist != NIL &&
  bms_is_empty(joinrel->lateral_relids))
 {
  if (nestjoinOK)
   consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
            save_jointype, extra);

  /*
   * If inner_cheapest_total is NULL or non parallel-safe then find the
   * cheapest total parallel safe path.  If doing JOIN_UNIQUE_INNER, we
   * can't use any alternative inner path.
 */

  if (inner_cheapest_total == NULL ||
   !inner_cheapest_total->parallel_safe)
  {
   if (save_jointype == JOIN_UNIQUE_INNER)
    return;

   inner_cheapest_total = get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
  }

  if (inner_cheapest_total)
   consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
          save_jointype, extra,
          inner_cheapest_total);
 }
}

/*
 * consider_parallel_mergejoin
 *   Try to build partial paths for a joinrel by joining a partial path
 *   for the outer relation to a complete path for the inner relation.
 *
 * 'joinrel' is the join relation
 * 'outerrel' is the outer join relation
 * 'innerrel' is the inner join relation
 * 'jointype' is the type of join to do
 * 'extra' contains additional input values
 * 'inner_cheapest_total' cheapest total path for innerrel
 */

static void
consider_parallel_mergejoin(PlannerInfo *root,
       RelOptInfo *joinrel,
       RelOptInfo *outerrel,
       RelOptInfo *innerrel,
       JoinType jointype,
       JoinPathExtraData *extra,
       Path *inner_cheapest_total)
{
 ListCell   *lc1;

 /* generate merge join path for each partial outer path */
 foreach(lc1, outerrel->partial_pathlist)
 {
  Path    *outerpath = (Path *) lfirst(lc1);
  List    *merge_pathkeys;

  /*
   * Figure out what useful ordering any paths we create will have.
 */

  merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
            outerpath->pathkeys);

  generate_mergejoin_paths(root, joinrel, innerrel, outerpath, jointype,
         extra, false, inner_cheapest_total,
         merge_pathkeys, true);
 }
}

/*
 * consider_parallel_nestloop
 *   Try to build partial paths for a joinrel by joining a partial path for the
 *   outer relation to a complete path for the inner relation.
 *
 * 'joinrel' is the join relation
 * 'outerrel' is the outer join relation
 * 'innerrel' is the inner join relation
 * 'jointype' is the type of join to do
 * 'extra' contains additional input values
 */

static void
consider_parallel_nestloop(PlannerInfo *root,
         RelOptInfo *joinrel,
         RelOptInfo *outerrel,
         RelOptInfo *innerrel,
         JoinType jointype,
         JoinPathExtraData *extra)
{
 JoinType save_jointype = jointype;
 Path    *inner_cheapest_total = innerrel->cheapest_total_path;
 Path    *matpath = NULL;
 ListCell   *lc1;

 if (jointype == JOIN_UNIQUE_INNER)
  jointype = JOIN_INNER;

 /*
  * 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.
 */

 if (save_jointype != JOIN_UNIQUE_INNER &&
  enable_material && 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);
 }

 foreach(lc1, outerrel->partial_pathlist)
 {
  Path    *outerpath = (Path *) lfirst(lc1);
  List    *pathkeys;
  ListCell   *lc2;

  /* Figure out what useful ordering any paths we create will have. */
  pathkeys = build_join_pathkeys(root, joinrel, jointype,
            outerpath->pathkeys);

  /*
   * Try the cheapest parameterized paths; only those which will produce
   * an unparameterized path when joined to this outerrel will survive
   * try_partial_nestloop_path.  The cheapest unparameterized path is
   * also in this list.
 */

  foreach(lc2, innerrel->cheapest_parameterized_paths)
  {
   Path    *innerpath = (Path *) lfirst(lc2);
   Path    *mpath;

   /* Can't join to an inner path that is not parallel-safe */
   if (!innerpath->parallel_safe)
    continue;

   /*
    * If we're doing JOIN_UNIQUE_INNER, we can only use the inner's
    * cheapest_total_path, and we have to unique-ify it.  (We might
    * be able to relax this to allow other safe, unparameterized
    * inner paths, but right now create_unique_path is not on board
    * with that.)
 */

   if (save_jointype == JOIN_UNIQUE_INNER)
   {
    if (innerpath != innerrel->cheapest_total_path)
     continue;
    innerpath = (Path *) create_unique_path(root, innerrel,
              innerpath,
              extra->sjinfo);
    Assert(innerpath);
   }

   try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
           pathkeys, jointype, extra);

   /*
    * Try generating a memoize path and see if that makes the nested
    * loop any cheaper.
 */

   mpath = get_memoize_path(root, innerrel, outerrel,
          innerpath, outerpath, jointype,
          extra);
   if (mpath != NULL)
    try_partial_nestloop_path(root, joinrel, outerpath, mpath,
            pathkeys, jointype, extra);
  }

  /* Also consider materialized form of the cheapest inner path */
  if (matpath != NULL)
   try_partial_nestloop_path(root, joinrel, outerpath, matpath,
           pathkeys, jointype, extra);
 }
}

/*
 * hash_inner_and_outer
 *   Create hashjoin join paths by explicitly hashing both the outer and
 *   inner keys of each available hash clause.
 *
 * 'joinrel' is the join relation
 * 'outerrel' is the outer join relation
 * 'innerrel' is the inner join relation
 * 'jointype' is the type of join to do
 * 'extra' contains additional input values
 */

static void
hash_inner_and_outer(PlannerInfo *root,
      RelOptInfo *joinrel,
      RelOptInfo *outerrel,
      RelOptInfo *innerrel,
      JoinType jointype,
      JoinPathExtraData *extra)
{
 JoinType save_jointype = jointype;
 bool  isouterjoin = IS_OUTER_JOIN(jointype);
 List    *hashclauses;
 ListCell   *l;

 /*
  * We need to build only one hashclauses list for any given pair of outer
  * and inner relations; all of the hashable clauses will be used as keys.
  *
  * Scan the join's restrictinfo list to find hashjoinable clauses that are
  * usable with this pair of sub-relations.
 */

 hashclauses = NIL;
 foreach(l, extra->restrictlist)
 {
  RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);

  /*
   * If processing an outer join, only use its own join clauses for
   * hashing.  For inner joins we need not be so picky.
 */

  if (isouterjoin && RINFO_IS_PUSHED_DOWN(restrictinfo, joinrel->relids))
   continue;

  if (!restrictinfo->can_join ||
   restrictinfo->hashjoinoperator == InvalidOid)
   continue;   /* not hashjoinable */

  /*
   * Check if clause has the form "outer op inner" or "inner op outer".
 */

  if (!clause_sides_match_join(restrictinfo, outerrel->relids,
          innerrel->relids))
   continue;   /* no good for these input relations */

  /*
   * If clause has the form "inner op outer", check if its operator has
   * valid commutator.  This is necessary because hashclauses in this
   * form will get commuted in createplan.c to put the outer var on the
   * left (see get_switched_clauses).  This probably shouldn't ever
   * fail, since hashable operators ought to have commutators, but be
   * paranoid.
   *
   * The clause being hashjoinable indicates that it's an OpExpr.
 */

  if (!restrictinfo->outer_is_left &&
   !OidIsValid(get_commutator(castNode(OpExpr, restrictinfo->clause)->opno)))
   continue;

  hashclauses = lappend(hashclauses, restrictinfo);
 }

 /* If we found any usable hashclauses, make paths */
 if (hashclauses)
 {
  /*
   * We consider both the cheapest-total-cost and cheapest-startup-cost
   * outer paths.  There's no need to consider any but the
   * cheapest-total-cost inner path, however.
 */

  Path    *cheapest_startup_outer = outerrel->cheapest_startup_path;
  Path    *cheapest_total_outer = outerrel->cheapest_total_path;
  Path    *cheapest_total_inner = innerrel->cheapest_total_path;

  /*
   * If either cheapest-total path is parameterized by the other rel, we
   * can't use a hashjoin.  (There's no use looking for alternative
   * input paths, since these should already be the least-parameterized
   * available paths.)
 */

  if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
   PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
   return;

  /* Unique-ify if need be; we ignore parameterized possibilities */
  if (jointype == JOIN_UNIQUE_OUTER)
  {
   cheapest_total_outer = (Path *)
    create_unique_path(root, outerrel,
           cheapest_total_outer, extra->sjinfo);
   Assert(cheapest_total_outer);
   jointype = JOIN_INNER;
   try_hashjoin_path(root,
         joinrel,
         cheapest_total_outer,
         cheapest_total_inner,
         hashclauses,
         jointype,
         extra);
   /* no possibility of cheap startup here */
  }
  else if (jointype == JOIN_UNIQUE_INNER)
  {
   cheapest_total_inner = (Path *)
    create_unique_path(root, innerrel,
           cheapest_total_inner, extra->sjinfo);
   Assert(cheapest_total_inner);
   jointype = JOIN_INNER;
   try_hashjoin_path(root,
         joinrel,
         cheapest_total_outer,
         cheapest_total_inner,
         hashclauses,
         jointype,
         extra);
   if (cheapest_startup_outer != NULL &&
    cheapest_startup_outer != cheapest_total_outer)
    try_hashjoin_path(root,
          joinrel,
          cheapest_startup_outer,
          cheapest_total_inner,
          hashclauses,
          jointype,
          extra);
  }
  else
  {
   /*
    * For other jointypes, we consider the cheapest startup outer
    * together with the cheapest total inner, and then consider
    * pairings of cheapest-total paths including parameterized ones.
    * There is no use in generating parameterized paths on the basis
    * of possibly cheap startup cost, so this is sufficient.
 */

   ListCell   *lc1;
   ListCell   *lc2;

   if (cheapest_startup_outer != NULL)
    try_hashjoin_path(root,
          joinrel,
          cheapest_startup_outer,
          cheapest_total_inner,
          hashclauses,
          jointype,
          extra);

   foreach(lc1, outerrel->cheapest_parameterized_paths)
   {
    Path    *outerpath = (Path *) lfirst(lc1);

    /*
     * We cannot use an outer path that is parameterized by the
     * inner rel.
 */

    if (PATH_PARAM_BY_REL(outerpath, innerrel))
     continue;

    foreach(lc2, innerrel->cheapest_parameterized_paths)
    {
     Path    *innerpath = (Path *) lfirst(lc2);

     /*
      * We cannot use an inner path that is parameterized by
      * the outer rel, either.
 */

     if (PATH_PARAM_BY_REL(innerpath, outerrel))
      continue;

     if (outerpath == cheapest_startup_outer &&
      innerpath == cheapest_total_inner)
      continue; /* already tried it */

     try_hashjoin_path(root,
           joinrel,
           outerpath,
           innerpath,
           hashclauses,
           jointype,
           extra);
    }
   }
  }

  /*
   * If the joinrel is parallel-safe, we may be able to consider a
   * partial hash join.
   *
   * However, we can't handle JOIN_UNIQUE_OUTER, because the outer path
   * will be partial, and therefore we won't be able to properly
   * guarantee uniqueness.
   *
   * Similarly, we can't handle JOIN_RIGHT_SEMI, because the hash table
   * is either a shared hash table or a private hash table per backend.
   * In the shared case, there is no concurrency protection for the
   * match flags, so multiple workers could inspect and set the flags
   * concurrently, potentially producing incorrect results.  In the
   * private case, each worker has its own copy of the hash table, so no
   * single process has all the match flags.
   *
   * Also, the resulting path must not be parameterized.
 */

  if (joinrel->consider_parallel &&
   save_jointype != JOIN_UNIQUE_OUTER &&
   save_jointype != JOIN_RIGHT_SEMI &&
   outerrel->partial_pathlist != NIL &&
   bms_is_empty(joinrel->lateral_relids))
  {
   Path    *cheapest_partial_outer;
   Path    *cheapest_partial_inner = NULL;
   Path    *cheapest_safe_inner = NULL;

   cheapest_partial_outer =
    (Path *) linitial(outerrel->partial_pathlist);

   /*
    * Can we use a partial inner plan too, so that we can build a
    * shared hash table in parallel?  We can't handle
    * JOIN_UNIQUE_INNER because we can't guarantee uniqueness.
 */

   if (innerrel->partial_pathlist != NIL &&
    save_jointype != JOIN_UNIQUE_INNER &&
    enable_parallel_hash)
   {
    cheapest_partial_inner =
     (Path *) linitial(innerrel->partial_pathlist);
    try_partial_hashjoin_path(root, joinrel,
            cheapest_partial_outer,
            cheapest_partial_inner,
            hashclauses, jointype, extra,
            true /* parallel_hash */ );
   }

   /*
    * Normally, given that the joinrel is parallel-safe, the cheapest
    * total inner path will also be parallel-safe, but if not, we'll
    * have to search for the cheapest safe, unparameterized inner
    * path.  If doing JOIN_UNIQUE_INNER, we can't use any alternative
    * inner path.  If full, right, or right-anti join, we can't use
    * parallelism (building the hash table in each backend) because
    * no one process has all the match bits.
 */

   if (save_jointype == JOIN_FULL ||
    save_jointype == JOIN_RIGHT ||
    save_jointype == JOIN_RIGHT_ANTI)
    cheapest_safe_inner = NULL;
   else if (cheapest_total_inner->parallel_safe)
    cheapest_safe_inner = cheapest_total_inner;
   else if (save_jointype != JOIN_UNIQUE_INNER)
    cheapest_safe_inner =
     get_cheapest_parallel_safe_total_inner(innerrel->pathlist);

   if (cheapest_safe_inner != NULL)
    try_partial_hashjoin_path(root, joinrel,
            cheapest_partial_outer,
            cheapest_safe_inner,
            hashclauses, jointype, extra,
            false /* parallel_hash */ );
  }
 }
}

/*
 * select_mergejoin_clauses
 *   Select mergejoin clauses that are usable for a particular join.
 *   Returns a list of RestrictInfo nodes for those clauses.
 *
 * *mergejoin_allowed is normally set to true, but it is set to false if
 * this is a right-semi join, or this is a right/right-anti/full join and
 * there are nonmergejoinable join clauses.  The executor's mergejoin
 * machinery cannot handle such cases, so we have to avoid generating a
 * mergejoin plan.  (Note that this flag does NOT consider whether there are
 * actually any mergejoinable clauses.  This is correct because in some
 * cases we need to build a clauseless mergejoin.  Simply returning NIL is
 * therefore not enough to distinguish safe from unsafe cases.)
 *
 * We also mark each selected RestrictInfo to show which side is currently
 * being considered as outer.  These are transient markings that are only
 * good for the duration of the current add_paths_to_joinrel() call!
 *
 * We examine each restrictinfo clause known for the join to see
 * if it is mergejoinable and involves vars from the two sub-relations
 * currently of interest.
 */

static List *
select_mergejoin_clauses(PlannerInfo *root,
       RelOptInfo *joinrel,
       RelOptInfo *outerrel,
       RelOptInfo *innerrel,
       List *restrictlist,
       JoinType jointype,
       bool *mergejoin_allowed)
{
 List    *result_list = NIL;
 bool  isouterjoin = IS_OUTER_JOIN(jointype);
 bool  have_nonmergeable_joinclause = false;
 ListCell   *l;

 /*
  * For now we do not support RIGHT_SEMI join in mergejoin: the benefit of
  * swapping inputs tends to be small here.
 */

 if (jointype == JOIN_RIGHT_SEMI)
 {
  *mergejoin_allowed = false;
  return NIL;
 }

 foreach(l, restrictlist)
 {
  RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);

  /*
   * If processing an outer join, only use its own join clauses in the
   * merge.  For inner joins we can use pushed-down clauses too. (Note:
   * we don't set have_nonmergeable_joinclause here because pushed-down
   * clauses will become otherquals not joinquals.)
 */

  if (isouterjoin && RINFO_IS_PUSHED_DOWN(restrictinfo, joinrel->relids))
   continue;

  /* Check that clause is a mergeable operator clause */
  if (!restrictinfo->can_join ||
   restrictinfo->mergeopfamilies == NIL)
  {
   /*
    * The executor can handle extra joinquals that are constants, but
    * not anything else, when doing right/right-anti/full merge join.
    * (The reason to support constants is so we can do FULL JOIN ON
    * FALSE.)
 */

   if (!restrictinfo->clause || !IsA(restrictinfo->clause, Const))
    have_nonmergeable_joinclause = true;
   continue;   /* not mergejoinable */
  }

  /*
   * Check if clause has the form "outer op inner" or "inner op outer".
 */

  if (!clause_sides_match_join(restrictinfo, outerrel->relids,
          innerrel->relids))
  {
   have_nonmergeable_joinclause = true;
   continue;   /* no good for these input relations */
  }

  /*
   * If clause has the form "inner op outer", check if its operator has
   * valid commutator.  This is necessary because mergejoin clauses in
   * this form will get commuted in createplan.c to put the outer var on
   * the left (see get_switched_clauses).  This probably shouldn't ever
   * fail, since mergejoinable operators ought to have commutators, but
   * be paranoid.
   *
   * The clause being mergejoinable indicates that it's an OpExpr.
 */

  if (!restrictinfo->outer_is_left &&
   !OidIsValid(get_commutator(castNode(OpExpr, restrictinfo->clause)->opno)))
  {
   have_nonmergeable_joinclause = true;
   continue;
  }

  /*
   * Insist that each side have a non-redundant eclass.  This
   * restriction is needed because various bits of the planner expect
   * that each clause in a merge be associable with some pathkey in a
   * canonical pathkey list, but redundant eclasses can't appear in
   * canonical sort orderings.  (XXX it might be worth relaxing this,
   * but not enough time to address it for 8.3.)
 */

  update_mergeclause_eclasses(root, restrictinfo);

  if (EC_MUST_BE_REDUNDANT(restrictinfo->left_ec) ||
   EC_MUST_BE_REDUNDANT(restrictinfo->right_ec))
  {
   have_nonmergeable_joinclause = true;
   continue;   /* can't handle redundant eclasses */
  }

  result_list = lappend(result_list, restrictinfo);
 }

 /*
  * Report whether mergejoin is allowed (see comment at top of function).
 */

 switch (jointype)
 {
  case JOIN_RIGHT:
  case JOIN_RIGHT_ANTI:
  case JOIN_FULL:
   *mergejoin_allowed = !have_nonmergeable_joinclause;
   break;
  default:
   *mergejoin_allowed = true;
   break;
 }

 return result_list;
}

Messung V0.5 in Prozent
C=93 H=100 G=96

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.75Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-01) ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Bemerkung:

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Anfrage:

Dauer der Verarbeitung:

Sekunden

sprechenden Kalenders






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1126864
#Domains=2039723