See <xref linkend="fdw-planning"> for additional information.
     </para>
 
+    <para>
+<programlisting>
+void
+GetExistingLocalJoinPath(RelOptInfo *joinrel)
+</programlisting>
+     The function returns copy of a local join path, which can be converted
+     into an alternative local join plan, which may be useful when
+     implementing a <literal>RecheckForeignScan</> method.  The function
+     searches for a parallel-safe, unparameterized path in the
+     <literal>pathlist</> of given <literal>joinrel</>. If it does not find
+     such a path, it returns NULL, in which case a foreign data wrapper may
+     build the local path by itself or may choose not to create access paths
+     for that join.
+    </para>
+
    </sect2>
 
    <sect2 id="fdw-callbacks-update">
      can be executed and the resulting tuple can be stored in the slot.
      This plan need not be efficient since no base table will return more
      than one row; for example, it may implement all joins as nested loops.
+     <literal>GetExistingLocalJoinPath</> may be used to search existing paths
+     for a suitable local join path, which can be used as the alternative
+     local join plan.
     </para>
    </sect2>
 
 
     <para>
 <programlisting>
+UserMapping *
+GetUserMappingById(Oid umid);
+</programlisting>
+
+     This function returns the <structname>UserMapping</structname> object for
+     the given user mapping OID.  The OID of a user mapping for a foreign scan
+     is available in the <structname>RelOptInfo</structname>.
+     If there is no mapping for the OID, this function will throw an error.
+     A <structname>UserMapping</structname> object contains properties of the
+     user mapping (see <filename>foreign/foreign.h</filename> for details).
+    </para>
+
+    <para>
+<programlisting>
 List *
 GetForeignColumnOptions(Oid relid, AttrNumber attnum);
 </programlisting>
 
        return GetForeignServer(serverid);
 }
 
+/*
+ * GetUserMappingById - look up the user mapping by its OID.
+ */
+UserMapping *
+GetUserMappingById(Oid umid)
+{
+       Datum           datum;
+       HeapTuple       tp;
+       bool            isnull;
+       UserMapping *um;
+
+       tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(umid));
+       if (!HeapTupleIsValid(tp))
+               elog(ERROR, "cache lookup failed for user mapping %u", umid);
+
+       um = (UserMapping *) palloc(sizeof(UserMapping));
+       um->umid = umid;
+
+       /* Extract the umuser */
+       datum = SysCacheGetAttr(USERMAPPINGOID,
+                                                       tp,
+                                                       Anum_pg_user_mapping_umuser,
+                                                       &isnull);
+       Assert(!isnull);
+       um->userid = DatumGetObjectId(datum);
+
+       /* Extract the umserver */
+       datum = SysCacheGetAttr(USERMAPPINGOID,
+                                                       tp,
+                                                       Anum_pg_user_mapping_umserver,
+                                                       &isnull);
+       Assert(!isnull);
+       um->serverid = DatumGetObjectId(datum);
+
+       /* Extract the umoptions */
+       datum = SysCacheGetAttr(USERMAPPINGOID,
+                                                       tp,
+                                                       Anum_pg_user_mapping_umoptions,
+                                                       &isnull);
+       if (isnull)
+               um->options = NIL;
+       else
+               um->options = untransformRelOptions(datum);
+
+       ReleaseSysCache(tp);
+
+       return um;
+}
 
 /*
  * GetUserMapping - look up the user mapping.
 
        /* Not found for the specific user -- try PUBLIC */
        tp = SearchSysCache2(USERMAPPINGUSERSERVER,
-                                                        ObjectIdGetDatum(InvalidOid),
-                                                        ObjectIdGetDatum(serverid));
+                                                ObjectIdGetDatum(InvalidOid),
+                                                ObjectIdGetDatum(serverid));
 
        if (!HeapTupleIsValid(tp))
                ereport(ERROR,
                                 errmsg("server \"%s\" does not exist", servername)));
        return oid;
 }
+
+/*
+ * Get a copy of an existing local path for a given join relation.
+ *
+ * This function is usually helpful to obtain an alternate local path for EPQ
+ * checks.
+ *
+ * Right now, this function only supports unparameterized foreign joins, so we
+ * only search for unparameterized path in the given list of paths. Since we
+ * are searching for a path which can be used to construct an alternative local
+ * plan for a foreign join, we look for only MergeJoin, HashJoin or NestLoop
+ * paths.
+ *
+ * If the inner or outer subpath of the chosen path is a ForeignScan, we
+ * replace it with its outer subpath.  For this reason, and also because the
+ * planner might free the original path later, the path returned by this
+ * function is a shallow copy of the original.  There's no need to copy
+ * the substructure, so we don't.
+ *
+ * Since the plan created using this path will presumably only be used to
+ * execute EPQ checks, efficiency of the path is not a concern. But since the
+ * list passed is expected to be from RelOptInfo, it's anyway sorted by total
+ * cost and hence we are likely to choose the most efficient path, which is
+ * all for the best.
+ */
+extern Path *
+GetExistingLocalJoinPath(RelOptInfo *joinrel)
+{
+       ListCell   *lc;
+
+       Assert(joinrel->reloptkind == RELOPT_JOINREL);
+
+       foreach(lc, joinrel->pathlist)
+       {
+               Path       *path = (Path *) lfirst(lc);
+               JoinPath   *joinpath = NULL;
+
+               /* Skip parameterised or non-parallel-safe paths. */
+               if (path->param_info != NULL || !path->parallel_safe)
+                       continue;
+
+               switch (path->pathtype)
+               {
+                       case T_HashJoin:
+                               {
+                                       HashPath   *hash_path = makeNode(HashPath);
+
+                                       memcpy(hash_path, path, sizeof(HashPath));
+                                       joinpath = (JoinPath *) hash_path;
+                               }
+                               break;
+
+                       case T_NestLoop:
+                               {
+                                       NestPath   *nest_path = makeNode(NestPath);
+
+                                       memcpy(nest_path, path, sizeof(NestPath));
+                                       joinpath = (JoinPath *) nest_path;
+                               }
+                               break;
+
+                       case T_MergeJoin:
+                               {
+                                       MergePath  *merge_path = makeNode(MergePath);
+
+                                       memcpy(merge_path, path, sizeof(MergePath));
+                                       joinpath = (JoinPath *) merge_path;
+                               }
+                               break;
+
+                       default:
+
+                               /*
+                                * Just skip anything else. We don't know if corresponding
+                                * plan would build the output row from whole-row references
+                                * of base relations and execute the EPQ checks.
+                                */
+                               break;
+               }
+
+               /* This path isn't good for us, check next. */
+               if (!joinpath)
+                       continue;
+
+               /*
+                * If either inner or outer path is a ForeignPath corresponding to a
+                * pushed down join, replace it with the fdw_outerpath, so that we
+                * maintain path for EPQ checks built entirely of local join
+                * strategies.
+                */
+               if (IsA(joinpath->outerjoinpath, ForeignPath))
+               {
+                       ForeignPath *foreign_path;
+
+                       foreign_path = (ForeignPath *) joinpath->outerjoinpath;
+                       if (foreign_path->path.parent->reloptkind == RELOPT_JOINREL)
+                               joinpath->outerjoinpath = foreign_path->fdw_outerpath;
+               }
+
+               if (IsA(joinpath->innerjoinpath, ForeignPath))
+               {
+                       ForeignPath *foreign_path;
+
+                       foreign_path = (ForeignPath *) joinpath->innerjoinpath;
+                       if (foreign_path->path.parent->reloptkind == RELOPT_JOINREL)
+                               joinpath->innerjoinpath = foreign_path->fdw_outerpath;
+               }
+
+               return (Path *) joinpath;
+       }
+       return NULL;
+}