Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a104a01

Browse files
committed
Add some additional core functions to support join pushdown for FDWs.
GetExistingLocalJoinPath() is useful for handling EvalPlanQual rechecks properly, and GetUserMappingById() is needed to make sure you're using the right credentials. Shigeru Hanada, Etsuro Fujita, Ashutosh Bapat, Robert Haas
1 parent c1772ad commit a104a01

File tree

4 files changed

+196
-2
lines changed

4 files changed

+196
-2
lines changed

doc/src/sgml/fdwhandler.sgml

+32
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,21 @@ GetForeignJoinPaths (PlannerInfo *root,
341341
See <xref linkend="fdw-planning"> for additional information.
342342
</para>
343343

344+
<para>
345+
<programlisting>
346+
void
347+
GetExistingLocalJoinPath(RelOptInfo *joinrel)
348+
</programlisting>
349+
The function returns copy of a local join path, which can be converted
350+
into an alternative local join plan, which may be useful when
351+
implementing a <literal>RecheckForeignScan</> method. The function
352+
searches for a parallel-safe, unparameterized path in the
353+
<literal>pathlist</> of given <literal>joinrel</>. If it does not find
354+
such a path, it returns NULL, in which case a foreign data wrapper may
355+
build the local path by itself or may choose not to create access paths
356+
for that join.
357+
</para>
358+
344359
</sect2>
345360

346361
<sect2 id="fdw-callbacks-update">
@@ -794,6 +809,9 @@ RecheckForeignScan (ForeignScanState *node, TupleTableSlot *slot);
794809
can be executed and the resulting tuple can be stored in the slot.
795810
This plan need not be efficient since no base table will return more
796811
than one row; for example, it may implement all joins as nested loops.
812+
<literal>GetExistingLocalJoinPath</> may be used to search existing paths
813+
for a suitable local join path, which can be used as the alternative
814+
local join plan.
797815
</para>
798816
</sect2>
799817

@@ -1069,6 +1087,20 @@ GetForeignTable(Oid relid);
10691087

10701088
<para>
10711089
<programlisting>
1090+
UserMapping *
1091+
GetUserMappingById(Oid umid);
1092+
</programlisting>
1093+
1094+
This function returns the <structname>UserMapping</structname> object for
1095+
the given user mapping OID. The OID of a user mapping for a foreign scan
1096+
is available in the <structname>RelOptInfo</structname>.
1097+
If there is no mapping for the OID, this function will throw an error.
1098+
A <structname>UserMapping</structname> object contains properties of the
1099+
user mapping (see <filename>foreign/foreign.h</filename> for details).
1100+
</para>
1101+
1102+
<para>
1103+
<programlisting>
10721104
List *
10731105
GetForeignColumnOptions(Oid relid, AttrNumber attnum);
10741106
</programlisting>

src/backend/foreign/foreign.c

+162-2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,54 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
160160
return GetForeignServer(serverid);
161161
}
162162

163+
/*
164+
* GetUserMappingById - look up the user mapping by its OID.
165+
*/
166+
UserMapping *
167+
GetUserMappingById(Oid umid)
168+
{
169+
Datum datum;
170+
HeapTuple tp;
171+
bool isnull;
172+
UserMapping *um;
173+
174+
tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(umid));
175+
if (!HeapTupleIsValid(tp))
176+
elog(ERROR, "cache lookup failed for user mapping %u", umid);
177+
178+
um = (UserMapping *) palloc(sizeof(UserMapping));
179+
um->umid = umid;
180+
181+
/* Extract the umuser */
182+
datum = SysCacheGetAttr(USERMAPPINGOID,
183+
tp,
184+
Anum_pg_user_mapping_umuser,
185+
&isnull);
186+
Assert(!isnull);
187+
um->userid = DatumGetObjectId(datum);
188+
189+
/* Extract the umserver */
190+
datum = SysCacheGetAttr(USERMAPPINGOID,
191+
tp,
192+
Anum_pg_user_mapping_umserver,
193+
&isnull);
194+
Assert(!isnull);
195+
um->serverid = DatumGetObjectId(datum);
196+
197+
/* Extract the umoptions */
198+
datum = SysCacheGetAttr(USERMAPPINGOID,
199+
tp,
200+
Anum_pg_user_mapping_umoptions,
201+
&isnull);
202+
if (isnull)
203+
um->options = NIL;
204+
else
205+
um->options = untransformRelOptions(datum);
206+
207+
ReleaseSysCache(tp);
208+
209+
return um;
210+
}
163211

164212
/*
165213
* GetUserMapping - look up the user mapping.
@@ -240,8 +288,8 @@ find_user_mapping(Oid userid, Oid serverid)
240288

241289
/* Not found for the specific user -- try PUBLIC */
242290
tp = SearchSysCache2(USERMAPPINGUSERSERVER,
243-
ObjectIdGetDatum(InvalidOid),
244-
ObjectIdGetDatum(serverid));
291+
ObjectIdGetDatum(InvalidOid),
292+
ObjectIdGetDatum(serverid));
245293

246294
if (!HeapTupleIsValid(tp))
247295
ereport(ERROR,
@@ -732,3 +780,115 @@ get_foreign_server_oid(const char *servername, bool missing_ok)
732780
errmsg("server \"%s\" does not exist", servername)));
733781
return oid;
734782
}
783+
784+
/*
785+
* Get a copy of an existing local path for a given join relation.
786+
*
787+
* This function is usually helpful to obtain an alternate local path for EPQ
788+
* checks.
789+
*
790+
* Right now, this function only supports unparameterized foreign joins, so we
791+
* only search for unparameterized path in the given list of paths. Since we
792+
* are searching for a path which can be used to construct an alternative local
793+
* plan for a foreign join, we look for only MergeJoin, HashJoin or NestLoop
794+
* paths.
795+
*
796+
* If the inner or outer subpath of the chosen path is a ForeignScan, we
797+
* replace it with its outer subpath. For this reason, and also because the
798+
* planner might free the original path later, the path returned by this
799+
* function is a shallow copy of the original. There's no need to copy
800+
* the substructure, so we don't.
801+
*
802+
* Since the plan created using this path will presumably only be used to
803+
* execute EPQ checks, efficiency of the path is not a concern. But since the
804+
* list passed is expected to be from RelOptInfo, it's anyway sorted by total
805+
* cost and hence we are likely to choose the most efficient path, which is
806+
* all for the best.
807+
*/
808+
extern Path *
809+
GetExistingLocalJoinPath(RelOptInfo *joinrel)
810+
{
811+
ListCell *lc;
812+
813+
Assert(joinrel->reloptkind == RELOPT_JOINREL);
814+
815+
foreach(lc, joinrel->pathlist)
816+
{
817+
Path *path = (Path *) lfirst(lc);
818+
JoinPath *joinpath = NULL;
819+
820+
/* Skip parameterised or non-parallel-safe paths. */
821+
if (path->param_info != NULL || !path->parallel_safe)
822+
continue;
823+
824+
switch (path->pathtype)
825+
{
826+
case T_HashJoin:
827+
{
828+
HashPath *hash_path = makeNode(HashPath);
829+
830+
memcpy(hash_path, path, sizeof(HashPath));
831+
joinpath = (JoinPath *) hash_path;
832+
}
833+
break;
834+
835+
case T_NestLoop:
836+
{
837+
NestPath *nest_path = makeNode(NestPath);
838+
839+
memcpy(nest_path, path, sizeof(NestPath));
840+
joinpath = (JoinPath *) nest_path;
841+
}
842+
break;
843+
844+
case T_MergeJoin:
845+
{
846+
MergePath *merge_path = makeNode(MergePath);
847+
848+
memcpy(merge_path, path, sizeof(MergePath));
849+
joinpath = (JoinPath *) merge_path;
850+
}
851+
break;
852+
853+
default:
854+
855+
/*
856+
* Just skip anything else. We don't know if corresponding
857+
* plan would build the output row from whole-row references
858+
* of base relations and execute the EPQ checks.
859+
*/
860+
break;
861+
}
862+
863+
/* This path isn't good for us, check next. */
864+
if (!joinpath)
865+
continue;
866+
867+
/*
868+
* If either inner or outer path is a ForeignPath corresponding to a
869+
* pushed down join, replace it with the fdw_outerpath, so that we
870+
* maintain path for EPQ checks built entirely of local join
871+
* strategies.
872+
*/
873+
if (IsA(joinpath->outerjoinpath, ForeignPath))
874+
{
875+
ForeignPath *foreign_path;
876+
877+
foreign_path = (ForeignPath *) joinpath->outerjoinpath;
878+
if (foreign_path->path.parent->reloptkind == RELOPT_JOINREL)
879+
joinpath->outerjoinpath = foreign_path->fdw_outerpath;
880+
}
881+
882+
if (IsA(joinpath->innerjoinpath, ForeignPath))
883+
{
884+
ForeignPath *foreign_path;
885+
886+
foreign_path = (ForeignPath *) joinpath->innerjoinpath;
887+
if (foreign_path->path.parent->reloptkind == RELOPT_JOINREL)
888+
joinpath->innerjoinpath = foreign_path->fdw_outerpath;
889+
}
890+
891+
return (Path *) joinpath;
892+
}
893+
return NULL;
894+
}

src/include/foreign/fdwapi.h

+1
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,6 @@ extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
202202
extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy);
203203
extern bool IsImportableForeignTable(const char *tablename,
204204
ImportForeignSchemaStmt *stmt);
205+
extern Path *GetExistingLocalJoinPath(RelOptInfo *joinrel);
205206

206207
#endif /* FDWAPI_H */

src/include/foreign/foreign.h

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ extern ForeignServer *GetForeignServer(Oid serverid);
7373
extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
7474
extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
7575
extern Oid GetUserMappingId(Oid userid, Oid serverid);
76+
extern UserMapping *GetUserMappingById(Oid umid);
7677
extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
7778
extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
7879
bool missing_ok);

0 commit comments

Comments
 (0)