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