Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * foreign.c
4 : * support for foreign-data wrappers, servers and user mappings.
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * src/backend/foreign/foreign.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
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 "funcapi.h"
24 : #include "miscadmin.h"
25 : #include "optimizer/paths.h"
26 : #include "tcop/tcopprot.h"
27 : #include "utils/builtins.h"
28 : #include "utils/memutils.h"
29 : #include "utils/rel.h"
30 : #include "utils/syscache.h"
31 : #include "utils/tuplestore.h"
32 : #include "utils/varlena.h"
33 :
34 :
35 : /*
36 : * GetForeignDataWrapper - look up the foreign-data wrapper by OID.
37 : */
38 : ForeignDataWrapper *
39 971 : GetForeignDataWrapper(Oid fdwid)
40 : {
41 971 : return GetForeignDataWrapperExtended(fdwid, 0);
42 : }
43 :
44 :
45 : /*
46 : * GetForeignDataWrapperExtended - look up the foreign-data wrapper
47 : * by OID. If flags uses FDW_MISSING_OK, return NULL if the object cannot
48 : * be found instead of raising an error.
49 : */
50 : ForeignDataWrapper *
51 1071 : GetForeignDataWrapperExtended(Oid fdwid, uint16 flags)
52 : {
53 : Form_pg_foreign_data_wrapper fdwform;
54 : ForeignDataWrapper *fdw;
55 : Datum datum;
56 : HeapTuple tp;
57 : bool isnull;
58 :
59 1071 : tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
60 :
61 1071 : if (!HeapTupleIsValid(tp))
62 : {
63 12 : if ((flags & FDW_MISSING_OK) == 0)
64 0 : elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
65 12 : return NULL;
66 : }
67 :
68 1059 : fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
69 :
70 1059 : fdw = palloc_object(ForeignDataWrapper);
71 1059 : fdw->fdwid = fdwid;
72 1059 : fdw->owner = fdwform->fdwowner;
73 1059 : fdw->fdwname = pstrdup(NameStr(fdwform->fdwname));
74 1059 : fdw->fdwhandler = fdwform->fdwhandler;
75 1059 : fdw->fdwvalidator = fdwform->fdwvalidator;
76 1059 : fdw->fdwconnection = fdwform->fdwconnection;
77 :
78 : /* Extract the fdwoptions */
79 1059 : datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
80 : tp,
81 : Anum_pg_foreign_data_wrapper_fdwoptions,
82 : &isnull);
83 1059 : if (isnull)
84 891 : fdw->options = NIL;
85 : else
86 168 : fdw->options = untransformRelOptions(datum);
87 :
88 1059 : ReleaseSysCache(tp);
89 :
90 1059 : return fdw;
91 : }
92 :
93 :
94 : /*
95 : * GetForeignDataWrapperByName - look up the foreign-data wrapper
96 : * definition by name.
97 : */
98 : ForeignDataWrapper *
99 298 : GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
100 : {
101 298 : Oid fdwId = get_foreign_data_wrapper_oid(fdwname, missing_ok);
102 :
103 294 : if (!OidIsValid(fdwId))
104 116 : return NULL;
105 :
106 178 : return GetForeignDataWrapper(fdwId);
107 : }
108 :
109 :
110 : /*
111 : * GetForeignServer - look up the foreign server definition.
112 : */
113 : ForeignServer *
114 2812 : GetForeignServer(Oid serverid)
115 : {
116 2812 : return GetForeignServerExtended(serverid, 0);
117 : }
118 :
119 :
120 : /*
121 : * GetForeignServerExtended - look up the foreign server definition. If
122 : * flags uses FSV_MISSING_OK, return NULL if the object cannot be found
123 : * instead of raising an error.
124 : */
125 : ForeignServer *
126 2957 : GetForeignServerExtended(Oid serverid, uint16 flags)
127 : {
128 : Form_pg_foreign_server serverform;
129 : ForeignServer *server;
130 : HeapTuple tp;
131 : Datum datum;
132 : bool isnull;
133 :
134 2957 : tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
135 :
136 2957 : if (!HeapTupleIsValid(tp))
137 : {
138 13 : if ((flags & FSV_MISSING_OK) == 0)
139 0 : elog(ERROR, "cache lookup failed for foreign server %u", serverid);
140 13 : return NULL;
141 : }
142 :
143 2944 : serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
144 :
145 2944 : server = palloc_object(ForeignServer);
146 2944 : server->serverid = serverid;
147 2944 : server->servername = pstrdup(NameStr(serverform->srvname));
148 2944 : server->owner = serverform->srvowner;
149 2944 : server->fdwid = serverform->srvfdw;
150 :
151 : /* Extract server type */
152 2944 : datum = SysCacheGetAttr(FOREIGNSERVEROID,
153 : tp,
154 : Anum_pg_foreign_server_srvtype,
155 : &isnull);
156 2944 : server->servertype = isnull ? NULL : TextDatumGetCString(datum);
157 :
158 : /* Extract server version */
159 2944 : datum = SysCacheGetAttr(FOREIGNSERVEROID,
160 : tp,
161 : Anum_pg_foreign_server_srvversion,
162 : &isnull);
163 2944 : server->serverversion = isnull ? NULL : TextDatumGetCString(datum);
164 :
165 : /* Extract the srvoptions */
166 2944 : datum = SysCacheGetAttr(FOREIGNSERVEROID,
167 : tp,
168 : Anum_pg_foreign_server_srvoptions,
169 : &isnull);
170 2944 : if (isnull)
171 675 : server->options = NIL;
172 : else
173 2269 : server->options = untransformRelOptions(datum);
174 :
175 2944 : ReleaseSysCache(tp);
176 :
177 2944 : return server;
178 : }
179 :
180 :
181 : /*
182 : * GetForeignServerByName - look up the foreign server definition by name.
183 : */
184 : ForeignServer *
185 644 : GetForeignServerByName(const char *srvname, bool missing_ok)
186 : {
187 644 : Oid serverid = get_foreign_server_oid(srvname, missing_ok);
188 :
189 630 : if (!OidIsValid(serverid))
190 28 : return NULL;
191 :
192 602 : return GetForeignServer(serverid);
193 : }
194 :
195 :
196 : /*
197 : * Retrieve connection string from server's FDW.
198 : *
199 : * NB: leaks into CurrentMemoryContext.
200 : */
201 : char *
202 22 : ForeignServerConnectionString(Oid userid, ForeignServer *server)
203 : {
204 : ForeignDataWrapper *fdw;
205 : Datum connection_datum;
206 :
207 22 : fdw = GetForeignDataWrapper(server->fdwid);
208 :
209 22 : if (!OidIsValid(fdw->fdwconnection))
210 4 : ereport(ERROR,
211 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
212 : errmsg("foreign data wrapper \"%s\" does not support subscription connections",
213 : fdw->fdwname),
214 : errdetail("Foreign data wrapper must be defined with CONNECTION specified.")));
215 :
216 18 : connection_datum = OidFunctionCall3(fdw->fdwconnection,
217 : ObjectIdGetDatum(userid),
218 : ObjectIdGetDatum(server->serverid),
219 : PointerGetDatum(NULL));
220 :
221 18 : return text_to_cstring(DatumGetTextPP(connection_datum));
222 : }
223 :
224 :
225 : /*
226 : * GetUserMapping - look up the user mapping.
227 : *
228 : * If no mapping is found for the supplied user, we also look for
229 : * PUBLIC mappings (userid == InvalidOid).
230 : */
231 : UserMapping *
232 1292 : GetUserMapping(Oid userid, Oid serverid)
233 : {
234 : Datum datum;
235 : HeapTuple tp;
236 : bool isnull;
237 : UserMapping *um;
238 :
239 1292 : tp = SearchSysCache2(USERMAPPINGUSERSERVER,
240 : ObjectIdGetDatum(userid),
241 : ObjectIdGetDatum(serverid));
242 :
243 1292 : if (!HeapTupleIsValid(tp))
244 : {
245 : /* Not found for the specific user -- try PUBLIC */
246 48 : tp = SearchSysCache2(USERMAPPINGUSERSERVER,
247 : ObjectIdGetDatum(InvalidOid),
248 : ObjectIdGetDatum(serverid));
249 : }
250 :
251 1292 : if (!HeapTupleIsValid(tp))
252 : {
253 6 : ForeignServer *server = GetForeignServer(serverid);
254 :
255 6 : ereport(ERROR,
256 : (errcode(ERRCODE_UNDEFINED_OBJECT),
257 : errmsg("user mapping not found for user \"%s\", server \"%s\"",
258 : MappingUserName(userid), server->servername)));
259 : }
260 :
261 1286 : um = palloc_object(UserMapping);
262 1286 : um->umid = ((Form_pg_user_mapping) GETSTRUCT(tp))->oid;
263 1286 : um->userid = userid;
264 1286 : um->serverid = serverid;
265 :
266 : /* Extract the umoptions */
267 1286 : datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
268 : tp,
269 : Anum_pg_user_mapping_umoptions,
270 : &isnull);
271 1286 : if (isnull)
272 1256 : um->options = NIL;
273 : else
274 30 : um->options = untransformRelOptions(datum);
275 :
276 1286 : ReleaseSysCache(tp);
277 :
278 1286 : return um;
279 : }
280 :
281 :
282 : /*
283 : * GetForeignTable - look up the foreign table definition by relation oid.
284 : */
285 : ForeignTable *
286 6555 : GetForeignTable(Oid relid)
287 : {
288 : Form_pg_foreign_table tableform;
289 : ForeignTable *ft;
290 : HeapTuple tp;
291 : Datum datum;
292 : bool isnull;
293 :
294 6555 : tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
295 6555 : if (!HeapTupleIsValid(tp))
296 0 : elog(ERROR, "cache lookup failed for foreign table %u", relid);
297 6555 : tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
298 :
299 6555 : ft = palloc_object(ForeignTable);
300 6555 : ft->relid = relid;
301 6555 : ft->serverid = tableform->ftserver;
302 :
303 : /* Extract the ftoptions */
304 6555 : datum = SysCacheGetAttr(FOREIGNTABLEREL,
305 : tp,
306 : Anum_pg_foreign_table_ftoptions,
307 : &isnull);
308 6555 : if (isnull)
309 0 : ft->options = NIL;
310 : else
311 6555 : ft->options = untransformRelOptions(datum);
312 :
313 6555 : ReleaseSysCache(tp);
314 :
315 6555 : return ft;
316 : }
317 :
318 :
319 : /*
320 : * GetForeignColumnOptions - Get attfdwoptions of given relation/attnum
321 : * as list of DefElem.
322 : */
323 : List *
324 15464 : GetForeignColumnOptions(Oid relid, AttrNumber attnum)
325 : {
326 : List *options;
327 : HeapTuple tp;
328 : Datum datum;
329 : bool isnull;
330 :
331 15464 : tp = SearchSysCache2(ATTNUM,
332 : ObjectIdGetDatum(relid),
333 : Int16GetDatum(attnum));
334 15464 : if (!HeapTupleIsValid(tp))
335 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
336 : attnum, relid);
337 15464 : datum = SysCacheGetAttr(ATTNUM,
338 : tp,
339 : Anum_pg_attribute_attfdwoptions,
340 : &isnull);
341 15464 : if (isnull)
342 11979 : options = NIL;
343 : else
344 3485 : options = untransformRelOptions(datum);
345 :
346 15464 : ReleaseSysCache(tp);
347 :
348 15464 : return options;
349 : }
350 :
351 :
352 : /*
353 : * GetFdwRoutine - call the specified foreign-data wrapper handler routine
354 : * to get its FdwRoutine struct.
355 : */
356 : FdwRoutine *
357 725 : GetFdwRoutine(Oid fdwhandler)
358 : {
359 : Datum datum;
360 : FdwRoutine *routine;
361 :
362 : /* Check if the access to foreign tables is restricted */
363 725 : if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
364 : {
365 : /* there must not be built-in FDW handler */
366 1 : ereport(ERROR,
367 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
368 : errmsg("access to non-system foreign table is restricted")));
369 : }
370 :
371 724 : datum = OidFunctionCall0(fdwhandler);
372 724 : routine = (FdwRoutine *) DatumGetPointer(datum);
373 :
374 724 : if (routine == NULL || !IsA(routine, FdwRoutine))
375 0 : elog(ERROR, "foreign-data wrapper handler function %u did not return an FdwRoutine struct",
376 : fdwhandler);
377 :
378 724 : return routine;
379 : }
380 :
381 :
382 : /*
383 : * GetForeignServerIdByRelId - look up the foreign server
384 : * for the given foreign table, and return its OID.
385 : */
386 : Oid
387 1712 : GetForeignServerIdByRelId(Oid relid)
388 : {
389 : HeapTuple tp;
390 : Form_pg_foreign_table tableform;
391 : Oid serverid;
392 :
393 1712 : tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
394 1712 : if (!HeapTupleIsValid(tp))
395 0 : elog(ERROR, "cache lookup failed for foreign table %u", relid);
396 1712 : tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
397 1712 : serverid = tableform->ftserver;
398 1712 : ReleaseSysCache(tp);
399 :
400 1712 : return serverid;
401 : }
402 :
403 :
404 : /*
405 : * GetFdwRoutineByServerId - look up the handler of the foreign-data wrapper
406 : * for the given foreign server, and retrieve its FdwRoutine struct.
407 : */
408 : FdwRoutine *
409 724 : GetFdwRoutineByServerId(Oid serverid)
410 : {
411 : HeapTuple tp;
412 : Form_pg_foreign_data_wrapper fdwform;
413 : Form_pg_foreign_server serverform;
414 : Oid fdwid;
415 : Oid fdwhandler;
416 :
417 : /* Get foreign-data wrapper OID for the server. */
418 724 : tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
419 724 : if (!HeapTupleIsValid(tp))
420 0 : elog(ERROR, "cache lookup failed for foreign server %u", serverid);
421 724 : serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
422 724 : fdwid = serverform->srvfdw;
423 724 : ReleaseSysCache(tp);
424 :
425 : /* Get handler function OID for the FDW. */
426 724 : tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
427 724 : if (!HeapTupleIsValid(tp))
428 0 : elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
429 724 : fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
430 724 : fdwhandler = fdwform->fdwhandler;
431 :
432 : /* Complain if FDW has been set to NO HANDLER. */
433 724 : if (!OidIsValid(fdwhandler))
434 9 : ereport(ERROR,
435 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
436 : errmsg("foreign-data wrapper \"%s\" has no handler",
437 : NameStr(fdwform->fdwname))));
438 :
439 715 : ReleaseSysCache(tp);
440 :
441 : /* And finally, call the handler function. */
442 715 : return GetFdwRoutine(fdwhandler);
443 : }
444 :
445 :
446 : /*
447 : * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper
448 : * for the given foreign table, and retrieve its FdwRoutine struct.
449 : */
450 : FdwRoutine *
451 395 : GetFdwRoutineByRelId(Oid relid)
452 : {
453 : Oid serverid;
454 :
455 : /* Get server OID for the foreign table. */
456 395 : serverid = GetForeignServerIdByRelId(relid);
457 :
458 : /* Now retrieve server's FdwRoutine struct. */
459 395 : return GetFdwRoutineByServerId(serverid);
460 : }
461 :
462 : /*
463 : * GetFdwRoutineForRelation - look up the handler of the foreign-data wrapper
464 : * for the given foreign table, and retrieve its FdwRoutine struct.
465 : *
466 : * This function is preferred over GetFdwRoutineByRelId because it caches
467 : * the data in the relcache entry, saving a number of catalog lookups.
468 : *
469 : * If makecopy is true then the returned data is freshly palloc'd in the
470 : * caller's memory context. Otherwise, it's a pointer to the relcache data,
471 : * which will be lost in any relcache reset --- so don't rely on it long.
472 : */
473 : FdwRoutine *
474 2647 : GetFdwRoutineForRelation(Relation relation, bool makecopy)
475 : {
476 : FdwRoutine *fdwroutine;
477 : FdwRoutine *cfdwroutine;
478 :
479 2647 : if (relation->rd_fdwroutine == NULL)
480 : {
481 : /* Get the info by consulting the catalogs and the FDW code */
482 192 : fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(relation));
483 :
484 : /* Save the data for later reuse in CacheMemoryContext */
485 183 : cfdwroutine = (FdwRoutine *) MemoryContextAlloc(CacheMemoryContext,
486 : sizeof(FdwRoutine));
487 183 : memcpy(cfdwroutine, fdwroutine, sizeof(FdwRoutine));
488 183 : relation->rd_fdwroutine = cfdwroutine;
489 :
490 : /* Give back the locally palloc'd copy regardless of makecopy */
491 183 : return fdwroutine;
492 : }
493 :
494 : /* We have valid cached data --- does the caller want a copy? */
495 2455 : if (makecopy)
496 : {
497 2252 : fdwroutine = palloc_object(FdwRoutine);
498 2252 : memcpy(fdwroutine, relation->rd_fdwroutine, sizeof(FdwRoutine));
499 2252 : return fdwroutine;
500 : }
501 :
502 : /* Only a short-lived reference is needed, so just hand back cached copy */
503 203 : return relation->rd_fdwroutine;
504 : }
505 :
506 :
507 : /*
508 : * IsImportableForeignTable - filter table names for IMPORT FOREIGN SCHEMA
509 : *
510 : * Returns true if given table name should be imported according to the
511 : * statement's import filter options.
512 : */
513 : bool
514 32 : IsImportableForeignTable(const char *tablename,
515 : ImportForeignSchemaStmt *stmt)
516 : {
517 : ListCell *lc;
518 :
519 32 : switch (stmt->list_type)
520 : {
521 22 : case FDW_IMPORT_SCHEMA_ALL:
522 22 : return true;
523 :
524 5 : case FDW_IMPORT_SCHEMA_LIMIT_TO:
525 7 : foreach(lc, stmt->table_list)
526 : {
527 7 : RangeVar *rv = (RangeVar *) lfirst(lc);
528 :
529 7 : if (strcmp(tablename, rv->relname) == 0)
530 5 : return true;
531 : }
532 0 : return false;
533 :
534 5 : case FDW_IMPORT_SCHEMA_EXCEPT:
535 25 : foreach(lc, stmt->table_list)
536 : {
537 20 : RangeVar *rv = (RangeVar *) lfirst(lc);
538 :
539 20 : if (strcmp(tablename, rv->relname) == 0)
540 0 : return false;
541 : }
542 5 : return true;
543 : }
544 0 : return false; /* shouldn't get here */
545 : }
546 :
547 :
548 : /*
549 : * pg_options_to_table - Convert options array to name/value table
550 : *
551 : * This is useful to provide details for information_schema and pg_dump.
552 : */
553 : Datum
554 607 : pg_options_to_table(PG_FUNCTION_ARGS)
555 : {
556 607 : Datum array = PG_GETARG_DATUM(0);
557 : ListCell *cell;
558 : List *options;
559 : ReturnSetInfo *rsinfo;
560 :
561 607 : options = untransformRelOptions(array);
562 607 : rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
563 :
564 : /* prepare the result set */
565 607 : InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
566 :
567 1708 : foreach(cell, options)
568 : {
569 1101 : DefElem *def = lfirst(cell);
570 : Datum values[2];
571 : bool nulls[2];
572 :
573 1101 : values[0] = CStringGetTextDatum(def->defname);
574 1101 : nulls[0] = false;
575 1101 : if (def->arg)
576 : {
577 1101 : values[1] = CStringGetTextDatum(strVal(def->arg));
578 1101 : nulls[1] = false;
579 : }
580 : else
581 : {
582 0 : values[1] = (Datum) 0;
583 0 : nulls[1] = true;
584 : }
585 1101 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
586 : values, nulls);
587 : }
588 :
589 607 : return (Datum) 0;
590 : }
591 :
592 :
593 : /*
594 : * Describes the valid options for postgresql FDW, server, and user mapping.
595 : */
596 : struct ConnectionOption
597 : {
598 : const char *optname;
599 : Oid optcontext; /* Oid of catalog in which option may appear */
600 : };
601 :
602 : /*
603 : * Copied from fe-connect.c PQconninfoOptions.
604 : *
605 : * The list is small - don't bother with bsearch if it stays so.
606 : */
607 : static const struct ConnectionOption libpq_conninfo_options[] = {
608 : {"authtype", ForeignServerRelationId},
609 : {"service", ForeignServerRelationId},
610 : {"user", UserMappingRelationId},
611 : {"password", UserMappingRelationId},
612 : {"connect_timeout", ForeignServerRelationId},
613 : {"dbname", ForeignServerRelationId},
614 : {"host", ForeignServerRelationId},
615 : {"hostaddr", ForeignServerRelationId},
616 : {"port", ForeignServerRelationId},
617 : {"tty", ForeignServerRelationId},
618 : {"options", ForeignServerRelationId},
619 : {"requiressl", ForeignServerRelationId},
620 : {"sslmode", ForeignServerRelationId},
621 : {"gsslib", ForeignServerRelationId},
622 : {"gssdelegation", ForeignServerRelationId},
623 : {NULL, InvalidOid}
624 : };
625 :
626 :
627 : /*
628 : * Check if the provided option is one of libpq conninfo options.
629 : * context is the Oid of the catalog the option came from, or 0 if we
630 : * don't care.
631 : */
632 : static bool
633 72 : is_conninfo_option(const char *option, Oid context)
634 : {
635 : const struct ConnectionOption *opt;
636 :
637 564 : for (opt = libpq_conninfo_options; opt->optname; opt++)
638 544 : if (context == opt->optcontext && strcmp(opt->optname, option) == 0)
639 52 : return true;
640 20 : return false;
641 : }
642 :
643 :
644 : /*
645 : * Validate the generic option given to SERVER or USER MAPPING.
646 : * Raise an ERROR if the option or its value is considered invalid.
647 : *
648 : * Valid server options are all libpq conninfo options except
649 : * user and password -- these may only appear in USER MAPPING options.
650 : *
651 : * Caution: this function is deprecated, and is now meant only for testing
652 : * purposes, because the list of options it knows about doesn't necessarily
653 : * square with those known to whichever libpq instance you might be using.
654 : * Inquire of libpq itself, instead.
655 : */
656 : Datum
657 92 : postgresql_fdw_validator(PG_FUNCTION_ARGS)
658 : {
659 92 : List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
660 92 : Oid catalog = PG_GETARG_OID(1);
661 :
662 : ListCell *cell;
663 :
664 144 : foreach(cell, options_list)
665 : {
666 72 : DefElem *def = lfirst(cell);
667 :
668 72 : if (!is_conninfo_option(def->defname, catalog))
669 : {
670 : const struct ConnectionOption *opt;
671 : const char *closest_match;
672 : ClosestMatchState match_state;
673 20 : bool has_valid_options = false;
674 :
675 : /*
676 : * Unknown option specified, complain about it. Provide a hint
677 : * with a valid option that looks similar, if there is one.
678 : */
679 20 : initClosestMatch(&match_state, def->defname, 4);
680 320 : for (opt = libpq_conninfo_options; opt->optname; opt++)
681 : {
682 300 : if (catalog == opt->optcontext)
683 : {
684 120 : has_valid_options = true;
685 120 : updateClosestMatch(&match_state, opt->optname);
686 : }
687 : }
688 :
689 20 : closest_match = getClosestMatch(&match_state);
690 20 : ereport(ERROR,
691 : (errcode(ERRCODE_SYNTAX_ERROR),
692 : errmsg("invalid option \"%s\"", def->defname),
693 : has_valid_options ? closest_match ?
694 : errhint("Perhaps you meant the option \"%s\".",
695 : closest_match) : 0 :
696 : errhint("There are no valid options in this context.")));
697 :
698 : PG_RETURN_BOOL(false);
699 : }
700 : }
701 :
702 72 : PG_RETURN_BOOL(true);
703 : }
704 :
705 :
706 : /*
707 : * get_foreign_data_wrapper_oid - given a FDW name, look up the OID
708 : *
709 : * If missing_ok is false, throw an error if name not found. If true, just
710 : * return InvalidOid.
711 : */
712 : Oid
713 512 : get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok)
714 : {
715 : Oid oid;
716 :
717 512 : oid = GetSysCacheOid1(FOREIGNDATAWRAPPERNAME,
718 : Anum_pg_foreign_data_wrapper_oid,
719 : CStringGetDatum(fdwname));
720 512 : if (!OidIsValid(oid) && !missing_ok)
721 16 : ereport(ERROR,
722 : (errcode(ERRCODE_UNDEFINED_OBJECT),
723 : errmsg("foreign-data wrapper \"%s\" does not exist",
724 : fdwname)));
725 496 : return oid;
726 : }
727 :
728 :
729 : /*
730 : * get_foreign_server_oid - given a server name, look up the OID
731 : *
732 : * If missing_ok is false, throw an error if name not found. If true, just
733 : * return InvalidOid.
734 : */
735 : Oid
736 1034 : get_foreign_server_oid(const char *servername, bool missing_ok)
737 : {
738 : Oid oid;
739 :
740 1034 : oid = GetSysCacheOid1(FOREIGNSERVERNAME, Anum_pg_foreign_server_oid,
741 : CStringGetDatum(servername));
742 1034 : if (!OidIsValid(oid) && !missing_ok)
743 26 : ereport(ERROR,
744 : (errcode(ERRCODE_UNDEFINED_OBJECT),
745 : errmsg("server \"%s\" does not exist", servername)));
746 1008 : return oid;
747 : }
748 :
749 : /*
750 : * Get a copy of an existing local path for a given join relation.
751 : *
752 : * This function is usually helpful to obtain an alternate local path for EPQ
753 : * checks.
754 : *
755 : * Right now, this function only supports unparameterized foreign joins, so we
756 : * only search for unparameterized path in the given list of paths. Since we
757 : * are searching for a path which can be used to construct an alternative local
758 : * plan for a foreign join, we look for only MergeJoin, HashJoin or NestLoop
759 : * paths.
760 : *
761 : * If the inner or outer subpath of the chosen path is a ForeignScan, we
762 : * replace it with its outer subpath. For this reason, and also because the
763 : * planner might free the original path later, the path returned by this
764 : * function is a shallow copy of the original. There's no need to copy
765 : * the substructure, so we don't.
766 : *
767 : * Since the plan created using this path will presumably only be used to
768 : * execute EPQ checks, efficiency of the path is not a concern. But since the
769 : * path list in RelOptInfo is anyway sorted by total cost we are likely to
770 : * choose the most efficient path, which is all for the best.
771 : */
772 : Path *
773 78 : GetExistingLocalJoinPath(RelOptInfo *joinrel)
774 : {
775 : ListCell *lc;
776 :
777 : Assert(IS_JOIN_REL(joinrel));
778 :
779 78 : foreach(lc, joinrel->pathlist)
780 : {
781 78 : Path *path = (Path *) lfirst(lc);
782 78 : JoinPath *joinpath = NULL;
783 :
784 : /* Skip parameterized paths. */
785 78 : if (path->param_info != NULL)
786 0 : continue;
787 :
788 78 : switch (path->pathtype)
789 : {
790 23 : case T_HashJoin:
791 : {
792 23 : HashPath *hash_path = makeNode(HashPath);
793 :
794 23 : memcpy(hash_path, path, sizeof(HashPath));
795 23 : joinpath = (JoinPath *) hash_path;
796 : }
797 23 : break;
798 :
799 22 : case T_NestLoop:
800 : {
801 22 : NestPath *nest_path = makeNode(NestPath);
802 :
803 22 : memcpy(nest_path, path, sizeof(NestPath));
804 22 : joinpath = (JoinPath *) nest_path;
805 : }
806 22 : break;
807 :
808 33 : case T_MergeJoin:
809 : {
810 33 : MergePath *merge_path = makeNode(MergePath);
811 :
812 33 : memcpy(merge_path, path, sizeof(MergePath));
813 33 : joinpath = (JoinPath *) merge_path;
814 : }
815 33 : break;
816 :
817 0 : default:
818 :
819 : /*
820 : * Just skip anything else. We don't know if corresponding
821 : * plan would build the output row from whole-row references
822 : * of base relations and execute the EPQ checks.
823 : */
824 0 : break;
825 : }
826 :
827 : /* This path isn't good for us, check next. */
828 78 : if (!joinpath)
829 0 : continue;
830 :
831 : /*
832 : * If either inner or outer path is a ForeignPath corresponding to a
833 : * pushed down join, replace it with the fdw_outerpath, so that we
834 : * maintain path for EPQ checks built entirely of local join
835 : * strategies.
836 : */
837 78 : if (IsA(joinpath->outerjoinpath, ForeignPath))
838 : {
839 : ForeignPath *foreign_path;
840 :
841 78 : foreign_path = (ForeignPath *) joinpath->outerjoinpath;
842 78 : if (IS_JOIN_REL(foreign_path->path.parent))
843 : {
844 20 : joinpath->outerjoinpath = foreign_path->fdw_outerpath;
845 :
846 20 : if (joinpath->path.pathtype == T_MergeJoin)
847 : {
848 10 : MergePath *merge_path = (MergePath *) joinpath;
849 :
850 : /*
851 : * If the new outer path is already well enough ordered
852 : * for the mergejoin, we can skip doing an explicit sort.
853 : */
854 14 : if (merge_path->outersortkeys &&
855 4 : pathkeys_count_contained_in(merge_path->outersortkeys,
856 4 : joinpath->outerjoinpath->pathkeys,
857 : &merge_path->outer_presorted_keys))
858 4 : merge_path->outersortkeys = NIL;
859 : }
860 : }
861 : }
862 :
863 78 : if (IsA(joinpath->innerjoinpath, ForeignPath))
864 : {
865 : ForeignPath *foreign_path;
866 :
867 68 : foreign_path = (ForeignPath *) joinpath->innerjoinpath;
868 68 : if (IS_JOIN_REL(foreign_path->path.parent))
869 : {
870 0 : joinpath->innerjoinpath = foreign_path->fdw_outerpath;
871 :
872 0 : if (joinpath->path.pathtype == T_MergeJoin)
873 : {
874 0 : MergePath *merge_path = (MergePath *) joinpath;
875 :
876 : /*
877 : * If the new inner path is already well enough ordered
878 : * for the mergejoin, we can skip doing an explicit sort.
879 : */
880 0 : if (merge_path->innersortkeys &&
881 0 : pathkeys_contained_in(merge_path->innersortkeys,
882 0 : joinpath->innerjoinpath->pathkeys))
883 0 : merge_path->innersortkeys = NIL;
884 : }
885 : }
886 : }
887 :
888 78 : return (Path *) joinpath;
889 : }
890 0 : return NULL;
891 : }
|