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