Update copyright for 2022
[pgsql.git] / src / backend / foreign / foreign.c
blob294e22c78c12cbd30604ce34fed0c7e5fc1582fb
1 /*-------------------------------------------------------------------------
3 * foreign.c
4 * support for foreign-data wrappers, servers and user mappings.
6 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
8 * IDENTIFICATION
9 * src/backend/foreign/foreign.c
11 *-------------------------------------------------------------------------
13 #include "postgres.h"
15 #include "access/htup_details.h"
16 #include "access/reloptions.h"
17 #include "catalog/pg_foreign_data_wrapper.h"
18 #include "catalog/pg_foreign_server.h"
19 #include "catalog/pg_foreign_table.h"
20 #include "catalog/pg_user_mapping.h"
21 #include "foreign/fdwapi.h"
22 #include "foreign/foreign.h"
23 #include "lib/stringinfo.h"
24 #include "miscadmin.h"
25 #include "utils/builtins.h"
26 #include "utils/memutils.h"
27 #include "utils/rel.h"
28 #include "utils/syscache.h"
32 * GetForeignDataWrapper - look up the foreign-data wrapper by OID.
34 ForeignDataWrapper *
35 GetForeignDataWrapper(Oid fdwid)
37 return GetForeignDataWrapperExtended(fdwid, 0);
42 * GetForeignDataWrapperExtended - look up the foreign-data wrapper
43 * by OID. If flags uses FDW_MISSING_OK, return NULL if the object cannot
44 * be found instead of raising an error.
46 ForeignDataWrapper *
47 GetForeignDataWrapperExtended(Oid fdwid, bits16 flags)
49 Form_pg_foreign_data_wrapper fdwform;
50 ForeignDataWrapper *fdw;
51 Datum datum;
52 HeapTuple tp;
53 bool isnull;
55 tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
57 if (!HeapTupleIsValid(tp))
59 if ((flags & FDW_MISSING_OK) == 0)
60 elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
61 return NULL;
64 fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
66 fdw = (ForeignDataWrapper *) palloc(sizeof(ForeignDataWrapper));
67 fdw->fdwid = fdwid;
68 fdw->owner = fdwform->fdwowner;
69 fdw->fdwname = pstrdup(NameStr(fdwform->fdwname));
70 fdw->fdwhandler = fdwform->fdwhandler;
71 fdw->fdwvalidator = fdwform->fdwvalidator;
73 /* Extract the fdwoptions */
74 datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
75 tp,
76 Anum_pg_foreign_data_wrapper_fdwoptions,
77 &isnull);
78 if (isnull)
79 fdw->options = NIL;
80 else
81 fdw->options = untransformRelOptions(datum);
83 ReleaseSysCache(tp);
85 return fdw;
90 * GetForeignDataWrapperByName - look up the foreign-data wrapper
91 * definition by name.
93 ForeignDataWrapper *
94 GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
96 Oid fdwId = get_foreign_data_wrapper_oid(fdwname, missing_ok);
98 if (!OidIsValid(fdwId))
99 return NULL;
101 return GetForeignDataWrapper(fdwId);
106 * GetForeignServer - look up the foreign server definition.
108 ForeignServer *
109 GetForeignServer(Oid serverid)
111 return GetForeignServerExtended(serverid, 0);
116 * GetForeignServerExtended - look up the foreign server definition. If
117 * flags uses FSV_MISSING_OK, return NULL if the object cannot be found
118 * instead of raising an error.
120 ForeignServer *
121 GetForeignServerExtended(Oid serverid, bits16 flags)
123 Form_pg_foreign_server serverform;
124 ForeignServer *server;
125 HeapTuple tp;
126 Datum datum;
127 bool isnull;
129 tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
131 if (!HeapTupleIsValid(tp))
133 if ((flags & FSV_MISSING_OK) == 0)
134 elog(ERROR, "cache lookup failed for foreign server %u", serverid);
135 return NULL;
138 serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
140 server = (ForeignServer *) palloc(sizeof(ForeignServer));
141 server->serverid = serverid;
142 server->servername = pstrdup(NameStr(serverform->srvname));
143 server->owner = serverform->srvowner;
144 server->fdwid = serverform->srvfdw;
146 /* Extract server type */
147 datum = SysCacheGetAttr(FOREIGNSERVEROID,
149 Anum_pg_foreign_server_srvtype,
150 &isnull);
151 server->servertype = isnull ? NULL : TextDatumGetCString(datum);
153 /* Extract server version */
154 datum = SysCacheGetAttr(FOREIGNSERVEROID,
156 Anum_pg_foreign_server_srvversion,
157 &isnull);
158 server->serverversion = isnull ? NULL : TextDatumGetCString(datum);
160 /* Extract the srvoptions */
161 datum = SysCacheGetAttr(FOREIGNSERVEROID,
163 Anum_pg_foreign_server_srvoptions,
164 &isnull);
165 if (isnull)
166 server->options = NIL;
167 else
168 server->options = untransformRelOptions(datum);
170 ReleaseSysCache(tp);
172 return server;
177 * GetForeignServerByName - look up the foreign server definition by name.
179 ForeignServer *
180 GetForeignServerByName(const char *srvname, bool missing_ok)
182 Oid serverid = get_foreign_server_oid(srvname, missing_ok);
184 if (!OidIsValid(serverid))
185 return NULL;
187 return GetForeignServer(serverid);
192 * GetUserMapping - look up the user mapping.
194 * If no mapping is found for the supplied user, we also look for
195 * PUBLIC mappings (userid == InvalidOid).
197 UserMapping *
198 GetUserMapping(Oid userid, Oid serverid)
200 Datum datum;
201 HeapTuple tp;
202 bool isnull;
203 UserMapping *um;
205 tp = SearchSysCache2(USERMAPPINGUSERSERVER,
206 ObjectIdGetDatum(userid),
207 ObjectIdGetDatum(serverid));
209 if (!HeapTupleIsValid(tp))
211 /* Not found for the specific user -- try PUBLIC */
212 tp = SearchSysCache2(USERMAPPINGUSERSERVER,
213 ObjectIdGetDatum(InvalidOid),
214 ObjectIdGetDatum(serverid));
217 if (!HeapTupleIsValid(tp))
218 ereport(ERROR,
219 (errcode(ERRCODE_UNDEFINED_OBJECT),
220 errmsg("user mapping not found for \"%s\"",
221 MappingUserName(userid))));
223 um = (UserMapping *) palloc(sizeof(UserMapping));
224 um->umid = ((Form_pg_user_mapping) GETSTRUCT(tp))->oid;
225 um->userid = userid;
226 um->serverid = serverid;
228 /* Extract the umoptions */
229 datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
231 Anum_pg_user_mapping_umoptions,
232 &isnull);
233 if (isnull)
234 um->options = NIL;
235 else
236 um->options = untransformRelOptions(datum);
238 ReleaseSysCache(tp);
240 return um;
245 * GetForeignTable - look up the foreign table definition by relation oid.
247 ForeignTable *
248 GetForeignTable(Oid relid)
250 Form_pg_foreign_table tableform;
251 ForeignTable *ft;
252 HeapTuple tp;
253 Datum datum;
254 bool isnull;
256 tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
257 if (!HeapTupleIsValid(tp))
258 elog(ERROR, "cache lookup failed for foreign table %u", relid);
259 tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
261 ft = (ForeignTable *) palloc(sizeof(ForeignTable));
262 ft->relid = relid;
263 ft->serverid = tableform->ftserver;
265 /* Extract the ftoptions */
266 datum = SysCacheGetAttr(FOREIGNTABLEREL,
268 Anum_pg_foreign_table_ftoptions,
269 &isnull);
270 if (isnull)
271 ft->options = NIL;
272 else
273 ft->options = untransformRelOptions(datum);
275 ReleaseSysCache(tp);
277 return ft;
282 * GetForeignColumnOptions - Get attfdwoptions of given relation/attnum
283 * as list of DefElem.
285 List *
286 GetForeignColumnOptions(Oid relid, AttrNumber attnum)
288 List *options;
289 HeapTuple tp;
290 Datum datum;
291 bool isnull;
293 tp = SearchSysCache2(ATTNUM,
294 ObjectIdGetDatum(relid),
295 Int16GetDatum(attnum));
296 if (!HeapTupleIsValid(tp))
297 elog(ERROR, "cache lookup failed for attribute %d of relation %u",
298 attnum, relid);
299 datum = SysCacheGetAttr(ATTNUM,
301 Anum_pg_attribute_attfdwoptions,
302 &isnull);
303 if (isnull)
304 options = NIL;
305 else
306 options = untransformRelOptions(datum);
308 ReleaseSysCache(tp);
310 return options;
315 * GetFdwRoutine - call the specified foreign-data wrapper handler routine
316 * to get its FdwRoutine struct.
318 FdwRoutine *
319 GetFdwRoutine(Oid fdwhandler)
321 Datum datum;
322 FdwRoutine *routine;
324 datum = OidFunctionCall0(fdwhandler);
325 routine = (FdwRoutine *) DatumGetPointer(datum);
327 if (routine == NULL || !IsA(routine, FdwRoutine))
328 elog(ERROR, "foreign-data wrapper handler function %u did not return an FdwRoutine struct",
329 fdwhandler);
331 return routine;
336 * GetForeignServerIdByRelId - look up the foreign server
337 * for the given foreign table, and return its OID.
340 GetForeignServerIdByRelId(Oid relid)
342 HeapTuple tp;
343 Form_pg_foreign_table tableform;
344 Oid serverid;
346 tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
347 if (!HeapTupleIsValid(tp))
348 elog(ERROR, "cache lookup failed for foreign table %u", relid);
349 tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
350 serverid = tableform->ftserver;
351 ReleaseSysCache(tp);
353 return serverid;
358 * GetFdwRoutineByServerId - look up the handler of the foreign-data wrapper
359 * for the given foreign server, and retrieve its FdwRoutine struct.
361 FdwRoutine *
362 GetFdwRoutineByServerId(Oid serverid)
364 HeapTuple tp;
365 Form_pg_foreign_data_wrapper fdwform;
366 Form_pg_foreign_server serverform;
367 Oid fdwid;
368 Oid fdwhandler;
370 /* Get foreign-data wrapper OID for the server. */
371 tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
372 if (!HeapTupleIsValid(tp))
373 elog(ERROR, "cache lookup failed for foreign server %u", serverid);
374 serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
375 fdwid = serverform->srvfdw;
376 ReleaseSysCache(tp);
378 /* Get handler function OID for the FDW. */
379 tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
380 if (!HeapTupleIsValid(tp))
381 elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
382 fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
383 fdwhandler = fdwform->fdwhandler;
385 /* Complain if FDW has been set to NO HANDLER. */
386 if (!OidIsValid(fdwhandler))
387 ereport(ERROR,
388 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
389 errmsg("foreign-data wrapper \"%s\" has no handler",
390 NameStr(fdwform->fdwname))));
392 ReleaseSysCache(tp);
394 /* And finally, call the handler function. */
395 return GetFdwRoutine(fdwhandler);
400 * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper
401 * for the given foreign table, and retrieve its FdwRoutine struct.
403 FdwRoutine *
404 GetFdwRoutineByRelId(Oid relid)
406 Oid serverid;
408 /* Get server OID for the foreign table. */
409 serverid = GetForeignServerIdByRelId(relid);
411 /* Now retrieve server's FdwRoutine struct. */
412 return GetFdwRoutineByServerId(serverid);
416 * GetFdwRoutineForRelation - look up the handler of the foreign-data wrapper
417 * for the given foreign table, and retrieve its FdwRoutine struct.
419 * This function is preferred over GetFdwRoutineByRelId because it caches
420 * the data in the relcache entry, saving a number of catalog lookups.
422 * If makecopy is true then the returned data is freshly palloc'd in the
423 * caller's memory context. Otherwise, it's a pointer to the relcache data,
424 * which will be lost in any relcache reset --- so don't rely on it long.
426 FdwRoutine *
427 GetFdwRoutineForRelation(Relation relation, bool makecopy)
429 FdwRoutine *fdwroutine;
430 FdwRoutine *cfdwroutine;
432 if (relation->rd_fdwroutine == NULL)
434 /* Get the info by consulting the catalogs and the FDW code */
435 fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(relation));
437 /* Save the data for later reuse in CacheMemoryContext */
438 cfdwroutine = (FdwRoutine *) MemoryContextAlloc(CacheMemoryContext,
439 sizeof(FdwRoutine));
440 memcpy(cfdwroutine, fdwroutine, sizeof(FdwRoutine));
441 relation->rd_fdwroutine = cfdwroutine;
443 /* Give back the locally palloc'd copy regardless of makecopy */
444 return fdwroutine;
447 /* We have valid cached data --- does the caller want a copy? */
448 if (makecopy)
450 fdwroutine = (FdwRoutine *) palloc(sizeof(FdwRoutine));
451 memcpy(fdwroutine, relation->rd_fdwroutine, sizeof(FdwRoutine));
452 return fdwroutine;
455 /* Only a short-lived reference is needed, so just hand back cached copy */
456 return relation->rd_fdwroutine;
461 * IsImportableForeignTable - filter table names for IMPORT FOREIGN SCHEMA
463 * Returns true if given table name should be imported according to the
464 * statement's import filter options.
466 bool
467 IsImportableForeignTable(const char *tablename,
468 ImportForeignSchemaStmt *stmt)
470 ListCell *lc;
472 switch (stmt->list_type)
474 case FDW_IMPORT_SCHEMA_ALL:
475 return true;
477 case FDW_IMPORT_SCHEMA_LIMIT_TO:
478 foreach(lc, stmt->table_list)
480 RangeVar *rv = (RangeVar *) lfirst(lc);
482 if (strcmp(tablename, rv->relname) == 0)
483 return true;
485 return false;
487 case FDW_IMPORT_SCHEMA_EXCEPT:
488 foreach(lc, stmt->table_list)
490 RangeVar *rv = (RangeVar *) lfirst(lc);
492 if (strcmp(tablename, rv->relname) == 0)
493 return false;
495 return true;
497 return false; /* shouldn't get here */
502 * deflist_to_tuplestore - Helper function to convert DefElem list to
503 * tuplestore usable in SRF.
505 static void
506 deflist_to_tuplestore(ReturnSetInfo *rsinfo, List *options)
508 ListCell *cell;
509 TupleDesc tupdesc;
510 Tuplestorestate *tupstore;
511 Datum values[2];
512 bool nulls[2];
513 MemoryContext per_query_ctx;
514 MemoryContext oldcontext;
516 /* check to see if caller supports us returning a tuplestore */
517 if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
518 ereport(ERROR,
519 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
520 errmsg("set-valued function called in context that cannot accept a set")));
521 if (!(rsinfo->allowedModes & SFRM_Materialize) ||
522 rsinfo->expectedDesc == NULL)
523 ereport(ERROR,
524 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
525 errmsg("materialize mode required, but it is not allowed in this context")));
527 per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
528 oldcontext = MemoryContextSwitchTo(per_query_ctx);
531 * Now prepare the result set.
533 tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
534 tupstore = tuplestore_begin_heap(true, false, work_mem);
535 rsinfo->returnMode = SFRM_Materialize;
536 rsinfo->setResult = tupstore;
537 rsinfo->setDesc = tupdesc;
539 foreach(cell, options)
541 DefElem *def = lfirst(cell);
543 values[0] = CStringGetTextDatum(def->defname);
544 nulls[0] = false;
545 if (def->arg)
547 values[1] = CStringGetTextDatum(strVal(def->arg));
548 nulls[1] = false;
550 else
552 values[1] = (Datum) 0;
553 nulls[1] = true;
555 tuplestore_putvalues(tupstore, tupdesc, values, nulls);
558 /* clean up and return the tuplestore */
559 tuplestore_donestoring(tupstore);
561 MemoryContextSwitchTo(oldcontext);
566 * Convert options array to name/value table. Useful for information
567 * schema and pg_dump.
569 Datum
570 pg_options_to_table(PG_FUNCTION_ARGS)
572 Datum array = PG_GETARG_DATUM(0);
574 deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo,
575 untransformRelOptions(array));
577 return (Datum) 0;
582 * Describes the valid options for postgresql FDW, server, and user mapping.
584 struct ConnectionOption
586 const char *optname;
587 Oid optcontext; /* Oid of catalog in which option may appear */
591 * Copied from fe-connect.c PQconninfoOptions.
593 * The list is small - don't bother with bsearch if it stays so.
595 static const struct ConnectionOption libpq_conninfo_options[] = {
596 {"authtype", ForeignServerRelationId},
597 {"service", ForeignServerRelationId},
598 {"user", UserMappingRelationId},
599 {"password", UserMappingRelationId},
600 {"connect_timeout", ForeignServerRelationId},
601 {"dbname", ForeignServerRelationId},
602 {"host", ForeignServerRelationId},
603 {"hostaddr", ForeignServerRelationId},
604 {"port", ForeignServerRelationId},
605 {"tty", ForeignServerRelationId},
606 {"options", ForeignServerRelationId},
607 {"requiressl", ForeignServerRelationId},
608 {"sslmode", ForeignServerRelationId},
609 {"gsslib", ForeignServerRelationId},
610 {NULL, InvalidOid}
615 * Check if the provided option is one of libpq conninfo options.
616 * context is the Oid of the catalog the option came from, or 0 if we
617 * don't care.
619 static bool
620 is_conninfo_option(const char *option, Oid context)
622 const struct ConnectionOption *opt;
624 for (opt = libpq_conninfo_options; opt->optname; opt++)
625 if (context == opt->optcontext && strcmp(opt->optname, option) == 0)
626 return true;
627 return false;
632 * Validate the generic option given to SERVER or USER MAPPING.
633 * Raise an ERROR if the option or its value is considered invalid.
635 * Valid server options are all libpq conninfo options except
636 * user and password -- these may only appear in USER MAPPING options.
638 * Caution: this function is deprecated, and is now meant only for testing
639 * purposes, because the list of options it knows about doesn't necessarily
640 * square with those known to whichever libpq instance you might be using.
641 * Inquire of libpq itself, instead.
643 Datum
644 postgresql_fdw_validator(PG_FUNCTION_ARGS)
646 List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
647 Oid catalog = PG_GETARG_OID(1);
649 ListCell *cell;
651 foreach(cell, options_list)
653 DefElem *def = lfirst(cell);
655 if (!is_conninfo_option(def->defname, catalog))
657 const struct ConnectionOption *opt;
658 StringInfoData buf;
661 * Unknown option specified, complain about it. Provide a hint
662 * with list of valid options for the object.
664 initStringInfo(&buf);
665 for (opt = libpq_conninfo_options; opt->optname; opt++)
666 if (catalog == opt->optcontext)
667 appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
668 opt->optname);
670 ereport(ERROR,
671 (errcode(ERRCODE_SYNTAX_ERROR),
672 errmsg("invalid option \"%s\"", def->defname),
673 buf.len > 0
674 ? errhint("Valid options in this context are: %s",
675 buf.data)
676 : errhint("There are no valid options in this context.")));
678 PG_RETURN_BOOL(false);
682 PG_RETURN_BOOL(true);
687 * get_foreign_data_wrapper_oid - given a FDW name, look up the OID
689 * If missing_ok is false, throw an error if name not found. If true, just
690 * return InvalidOid.
693 get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok)
695 Oid oid;
697 oid = GetSysCacheOid1(FOREIGNDATAWRAPPERNAME,
698 Anum_pg_foreign_data_wrapper_oid,
699 CStringGetDatum(fdwname));
700 if (!OidIsValid(oid) && !missing_ok)
701 ereport(ERROR,
702 (errcode(ERRCODE_UNDEFINED_OBJECT),
703 errmsg("foreign-data wrapper \"%s\" does not exist",
704 fdwname)));
705 return oid;
710 * get_foreign_server_oid - given a server name, look up the OID
712 * If missing_ok is false, throw an error if name not found. If true, just
713 * return InvalidOid.
716 get_foreign_server_oid(const char *servername, bool missing_ok)
718 Oid oid;
720 oid = GetSysCacheOid1(FOREIGNSERVERNAME, Anum_pg_foreign_server_oid,
721 CStringGetDatum(servername));
722 if (!OidIsValid(oid) && !missing_ok)
723 ereport(ERROR,
724 (errcode(ERRCODE_UNDEFINED_OBJECT),
725 errmsg("server \"%s\" does not exist", servername)));
726 return oid;
730 * Get a copy of an existing local path for a given join relation.
732 * This function is usually helpful to obtain an alternate local path for EPQ
733 * checks.
735 * Right now, this function only supports unparameterized foreign joins, so we
736 * only search for unparameterized path in the given list of paths. Since we
737 * are searching for a path which can be used to construct an alternative local
738 * plan for a foreign join, we look for only MergeJoin, HashJoin or NestLoop
739 * paths.
741 * If the inner or outer subpath of the chosen path is a ForeignScan, we
742 * replace it with its outer subpath. For this reason, and also because the
743 * planner might free the original path later, the path returned by this
744 * function is a shallow copy of the original. There's no need to copy
745 * the substructure, so we don't.
747 * Since the plan created using this path will presumably only be used to
748 * execute EPQ checks, efficiency of the path is not a concern. But since the
749 * path list in RelOptInfo is anyway sorted by total cost we are likely to
750 * choose the most efficient path, which is all for the best.
752 Path *
753 GetExistingLocalJoinPath(RelOptInfo *joinrel)
755 ListCell *lc;
757 Assert(IS_JOIN_REL(joinrel));
759 foreach(lc, joinrel->pathlist)
761 Path *path = (Path *) lfirst(lc);
762 JoinPath *joinpath = NULL;
764 /* Skip parameterized paths. */
765 if (path->param_info != NULL)
766 continue;
768 switch (path->pathtype)
770 case T_HashJoin:
772 HashPath *hash_path = makeNode(HashPath);
774 memcpy(hash_path, path, sizeof(HashPath));
775 joinpath = (JoinPath *) hash_path;
777 break;
779 case T_NestLoop:
781 NestPath *nest_path = makeNode(NestPath);
783 memcpy(nest_path, path, sizeof(NestPath));
784 joinpath = (JoinPath *) nest_path;
786 break;
788 case T_MergeJoin:
790 MergePath *merge_path = makeNode(MergePath);
792 memcpy(merge_path, path, sizeof(MergePath));
793 joinpath = (JoinPath *) merge_path;
795 break;
797 default:
800 * Just skip anything else. We don't know if corresponding
801 * plan would build the output row from whole-row references
802 * of base relations and execute the EPQ checks.
804 break;
807 /* This path isn't good for us, check next. */
808 if (!joinpath)
809 continue;
812 * If either inner or outer path is a ForeignPath corresponding to a
813 * pushed down join, replace it with the fdw_outerpath, so that we
814 * maintain path for EPQ checks built entirely of local join
815 * strategies.
817 if (IsA(joinpath->outerjoinpath, ForeignPath))
819 ForeignPath *foreign_path;
821 foreign_path = (ForeignPath *) joinpath->outerjoinpath;
822 if (IS_JOIN_REL(foreign_path->path.parent))
823 joinpath->outerjoinpath = foreign_path->fdw_outerpath;
826 if (IsA(joinpath->innerjoinpath, ForeignPath))
828 ForeignPath *foreign_path;
830 foreign_path = (ForeignPath *) joinpath->innerjoinpath;
831 if (IS_JOIN_REL(foreign_path->path.parent))
832 joinpath->innerjoinpath = foreign_path->fdw_outerpath;
835 return (Path *) joinpath;
837 return NULL;