Age Owner Branch data TLA 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 *
6346 peter_e@gmx.net 39 :CBC 980 : GetForeignDataWrapper(Oid fdwid)
40 : : {
2699 michael@paquier.xyz 41 : 980 : 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 *
36 nathan@postgresql.or 51 :GNC 1080 : 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 : :
5924 rhaas@postgresql.org 59 :CBC 1080 : tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
60 : :
6346 peter_e@gmx.net 61 [ + + ]: 1080 : if (!HeapTupleIsValid(tp))
62 : : {
2699 michael@paquier.xyz 63 [ - + ]: 12 : if ((flags & FDW_MISSING_OK) == 0)
2699 michael@paquier.xyz 64 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
2699 michael@paquier.xyz 65 :CBC 12 : return NULL;
66 : : }
67 : :
6346 peter_e@gmx.net 68 : 1068 : fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
69 : :
146 michael@paquier.xyz 70 :GNC 1068 : fdw = palloc_object(ForeignDataWrapper);
6346 peter_e@gmx.net 71 :CBC 1068 : fdw->fdwid = fdwid;
72 : 1068 : fdw->owner = fdwform->fdwowner;
73 : 1068 : fdw->fdwname = pstrdup(NameStr(fdwform->fdwname));
5554 tgl@sss.pgh.pa.us 74 : 1068 : fdw->fdwhandler = fdwform->fdwhandler;
6279 peter_e@gmx.net 75 : 1068 : fdw->fdwvalidator = fdwform->fdwvalidator;
60 jdavis@postgresql.or 76 :GNC 1068 : fdw->fdwconnection = fdwform->fdwconnection;
77 : :
78 : : /* Extract the fdwoptions */
6346 peter_e@gmx.net 79 :CBC 1068 : datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
80 : : tp,
81 : : Anum_pg_foreign_data_wrapper_fdwoptions,
82 : : &isnull);
5553 tgl@sss.pgh.pa.us 83 [ + + ]: 1068 : if (isnull)
84 : 900 : fdw->options = NIL;
85 : : else
86 : 168 : fdw->options = untransformRelOptions(datum);
87 : :
6346 peter_e@gmx.net 88 : 1068 : ReleaseSysCache(tp);
89 : :
90 : 1068 : return fdw;
91 : : }
92 : :
93 : :
94 : : /*
95 : : * GetForeignDataWrapperByName - look up the foreign-data wrapper
96 : : * definition by name.
97 : : */
98 : : ForeignDataWrapper *
99 : 302 : GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
100 : : {
5513 rhaas@postgresql.org 101 : 302 : Oid fdwId = get_foreign_data_wrapper_oid(fdwname, missing_ok);
102 : :
5553 tgl@sss.pgh.pa.us 103 [ + + ]: 298 : if (!OidIsValid(fdwId))
6346 peter_e@gmx.net 104 : 118 : return NULL;
105 : :
106 : 180 : return GetForeignDataWrapper(fdwId);
107 : : }
108 : :
109 : :
110 : : /*
111 : : * GetForeignServer - look up the foreign server definition.
112 : : */
113 : : ForeignServer *
114 : 2874 : GetForeignServer(Oid serverid)
115 : : {
2699 michael@paquier.xyz 116 : 2874 : 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 *
36 nathan@postgresql.or 126 :GNC 3019 : 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 : :
5924 rhaas@postgresql.org 134 :CBC 3019 : tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
135 : :
6346 peter_e@gmx.net 136 [ + + ]: 3019 : if (!HeapTupleIsValid(tp))
137 : : {
2699 michael@paquier.xyz 138 [ - + ]: 13 : if ((flags & FSV_MISSING_OK) == 0)
2699 michael@paquier.xyz 139 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for foreign server %u", serverid);
2699 michael@paquier.xyz 140 :CBC 13 : return NULL;
141 : : }
142 : :
6346 peter_e@gmx.net 143 : 3006 : serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
144 : :
146 michael@paquier.xyz 145 :GNC 3006 : server = palloc_object(ForeignServer);
6346 peter_e@gmx.net 146 :CBC 3006 : server->serverid = serverid;
147 : 3006 : server->servername = pstrdup(NameStr(serverform->srvname));
148 : 3006 : server->owner = serverform->srvowner;
149 : 3006 : server->fdwid = serverform->srvfdw;
150 : :
151 : : /* Extract server type */
152 : 3006 : datum = SysCacheGetAttr(FOREIGNSERVEROID,
153 : : tp,
154 : : Anum_pg_foreign_server_srvtype,
155 : : &isnull);
3709 tgl@sss.pgh.pa.us 156 [ + + ]: 3006 : server->servertype = isnull ? NULL : TextDatumGetCString(datum);
157 : :
158 : : /* Extract server version */
6346 peter_e@gmx.net 159 : 3006 : datum = SysCacheGetAttr(FOREIGNSERVEROID,
160 : : tp,
161 : : Anum_pg_foreign_server_srvversion,
162 : : &isnull);
3709 tgl@sss.pgh.pa.us 163 [ + + ]: 3006 : server->serverversion = isnull ? NULL : TextDatumGetCString(datum);
164 : :
165 : : /* Extract the srvoptions */
6346 peter_e@gmx.net 166 : 3006 : datum = SysCacheGetAttr(FOREIGNSERVEROID,
167 : : tp,
168 : : Anum_pg_foreign_server_srvoptions,
169 : : &isnull);
5553 tgl@sss.pgh.pa.us 170 [ + + ]: 3006 : if (isnull)
171 : 673 : server->options = NIL;
172 : : else
173 : 2333 : server->options = untransformRelOptions(datum);
174 : :
6346 peter_e@gmx.net 175 : 3006 : ReleaseSysCache(tp);
176 : :
177 : 3006 : return server;
178 : : }
179 : :
180 : :
181 : : /*
182 : : * GetForeignServerByName - look up the foreign server definition by name.
183 : : */
184 : : ForeignServer *
185 : 652 : GetForeignServerByName(const char *srvname, bool missing_ok)
186 : : {
5513 rhaas@postgresql.org 187 : 652 : Oid serverid = get_foreign_server_oid(srvname, missing_ok);
188 : :
5553 tgl@sss.pgh.pa.us 189 [ + + ]: 638 : if (!OidIsValid(serverid))
6346 peter_e@gmx.net 190 : 33 : return NULL;
191 : :
192 : 605 : return GetForeignServer(serverid);
193 : : }
194 : :
195 : :
196 : : /*
197 : : * Retrieve connection string from server's FDW.
198 : : *
199 : : * NB: leaks into CurrentMemoryContext.
200 : : */
201 : : char *
42 jdavis@postgresql.or 202 :GNC 21 : ForeignServerConnectionString(Oid userid, ForeignServer *server)
203 : : {
204 : : ForeignDataWrapper *fdw;
205 : : Datum connection_datum;
206 : :
207 : 21 : fdw = GetForeignDataWrapper(server->fdwid);
208 : :
209 [ + + ]: 21 : 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 : 17 : connection_datum = OidFunctionCall3(fdw->fdwconnection,
217 : : ObjectIdGetDatum(userid),
218 : : ObjectIdGetDatum(server->serverid),
219 : : PointerGetDatum(NULL));
220 : :
221 : 17 : 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 *
6346 peter_e@gmx.net 232 :CBC 1315 : GetUserMapping(Oid userid, Oid serverid)
233 : : {
234 : : Datum datum;
235 : : HeapTuple tp;
236 : : bool isnull;
237 : : UserMapping *um;
238 : :
3574 tgl@sss.pgh.pa.us 239 : 1315 : tp = SearchSysCache2(USERMAPPINGUSERSERVER,
240 : : ObjectIdGetDatum(userid),
241 : : ObjectIdGetDatum(serverid));
242 : :
243 [ + + ]: 1315 : if (!HeapTupleIsValid(tp))
244 : : {
245 : : /* Not found for the specific user -- try PUBLIC */
246 : 47 : tp = SearchSysCache2(USERMAPPINGUSERSERVER,
247 : : ObjectIdGetDatum(InvalidOid),
248 : : ObjectIdGetDatum(serverid));
249 : : }
250 : :
251 [ + + ]: 1315 : if (!HeapTupleIsValid(tp))
252 : : {
887 peter@eisentraut.org 253 : 6 : ForeignServer *server = GetForeignServer(serverid);
254 : :
3574 tgl@sss.pgh.pa.us 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 : :
146 michael@paquier.xyz 261 :GNC 1309 : um = palloc_object(UserMapping);
2723 andres@anarazel.de 262 :CBC 1309 : um->umid = ((Form_pg_user_mapping) GETSTRUCT(tp))->oid;
5553 tgl@sss.pgh.pa.us 263 : 1309 : um->userid = userid;
264 : 1309 : um->serverid = serverid;
265 : :
266 : : /* Extract the umoptions */
6346 peter_e@gmx.net 267 : 1309 : datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
268 : : tp,
269 : : Anum_pg_user_mapping_umoptions,
270 : : &isnull);
5553 tgl@sss.pgh.pa.us 271 [ + + ]: 1309 : if (isnull)
272 : 1275 : um->options = NIL;
273 : : else
274 : 34 : um->options = untransformRelOptions(datum);
275 : :
6346 peter_e@gmx.net 276 : 1309 : ReleaseSysCache(tp);
277 : :
278 : 1309 : return um;
279 : : }
280 : :
281 : :
282 : : /*
283 : : * GetForeignTable - look up the foreign table definition by relation oid.
284 : : */
285 : : ForeignTable *
5553 tgl@sss.pgh.pa.us 286 : 6643 : GetForeignTable(Oid relid)
287 : : {
288 : : Form_pg_foreign_table tableform;
289 : : ForeignTable *ft;
290 : : HeapTuple tp;
291 : : Datum datum;
292 : : bool isnull;
293 : :
294 : 6643 : tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
295 [ - + ]: 6643 : if (!HeapTupleIsValid(tp))
5553 tgl@sss.pgh.pa.us 296 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for foreign table %u", relid);
5553 tgl@sss.pgh.pa.us 297 :CBC 6643 : tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
298 : :
146 michael@paquier.xyz 299 :GNC 6643 : ft = palloc_object(ForeignTable);
5553 tgl@sss.pgh.pa.us 300 :CBC 6643 : ft->relid = relid;
301 : 6643 : ft->serverid = tableform->ftserver;
302 : :
303 : : /* Extract the ftoptions */
304 : 6643 : datum = SysCacheGetAttr(FOREIGNTABLEREL,
305 : : tp,
306 : : Anum_pg_foreign_table_ftoptions,
307 : : &isnull);
308 [ - + ]: 6643 : if (isnull)
5553 tgl@sss.pgh.pa.us 309 :UBC 0 : ft->options = NIL;
310 : : else
5553 tgl@sss.pgh.pa.us 311 :CBC 6643 : ft->options = untransformRelOptions(datum);
312 : :
313 : 6643 : ReleaseSysCache(tp);
314 : :
315 : 6643 : return ft;
316 : : }
317 : :
318 : :
319 : : /*
320 : : * GetForeignColumnOptions - Get attfdwoptions of given relation/attnum
321 : : * as list of DefElem.
322 : : */
323 : : List *
5172 324 : 15498 : GetForeignColumnOptions(Oid relid, AttrNumber attnum)
325 : : {
326 : : List *options;
327 : : HeapTuple tp;
328 : : Datum datum;
329 : : bool isnull;
330 : :
331 : 15498 : tp = SearchSysCache2(ATTNUM,
332 : : ObjectIdGetDatum(relid),
333 : : Int16GetDatum(attnum));
334 [ - + ]: 15498 : if (!HeapTupleIsValid(tp))
5172 tgl@sss.pgh.pa.us 335 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
336 : : attnum, relid);
5172 tgl@sss.pgh.pa.us 337 :CBC 15498 : datum = SysCacheGetAttr(ATTNUM,
338 : : tp,
339 : : Anum_pg_attribute_attfdwoptions,
340 : : &isnull);
341 [ + + ]: 15498 : if (isnull)
342 : 12004 : options = NIL;
343 : : else
344 : 3494 : options = untransformRelOptions(datum);
345 : :
346 : 15498 : ReleaseSysCache(tp);
347 : :
348 : 15498 : 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 *
5553 357 : 735 : GetFdwRoutine(Oid fdwhandler)
358 : : {
359 : : Datum datum;
360 : : FdwRoutine *routine;
361 : :
362 : : /* Check if the access to foreign tables is restricted */
638 msawada@postgresql.o 363 [ + + ]: 735 : 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 : :
5553 tgl@sss.pgh.pa.us 371 : 734 : datum = OidFunctionCall0(fdwhandler);
372 : 734 : routine = (FdwRoutine *) DatumGetPointer(datum);
373 : :
374 [ + - - + ]: 734 : if (routine == NULL || !IsA(routine, FdwRoutine))
5553 tgl@sss.pgh.pa.us 375 [ # # ]:UBC 0 : elog(ERROR, "foreign-data wrapper handler function %u did not return an FdwRoutine struct",
376 : : fdwhandler);
377 : :
5553 tgl@sss.pgh.pa.us 378 :CBC 734 : 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
4013 387 : 1727 : GetForeignServerIdByRelId(Oid relid)
388 : : {
389 : : HeapTuple tp;
390 : : Form_pg_foreign_table tableform;
391 : : Oid serverid;
392 : :
5553 393 : 1727 : tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
394 [ - + ]: 1727 : if (!HeapTupleIsValid(tp))
5553 tgl@sss.pgh.pa.us 395 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for foreign table %u", relid);
5553 tgl@sss.pgh.pa.us 396 :CBC 1727 : tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
397 : 1727 : serverid = tableform->ftserver;
398 : 1727 : ReleaseSysCache(tp);
399 : :
4013 400 : 1727 : 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 : 734 : 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. */
5553 418 : 734 : tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
419 [ - + ]: 734 : if (!HeapTupleIsValid(tp))
5553 tgl@sss.pgh.pa.us 420 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for foreign server %u", serverid);
5553 tgl@sss.pgh.pa.us 421 :CBC 734 : serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
422 : 734 : fdwid = serverform->srvfdw;
423 : 734 : ReleaseSysCache(tp);
424 : :
425 : : /* Get handler function OID for the FDW. */
426 : 734 : tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
427 [ - + ]: 734 : if (!HeapTupleIsValid(tp))
5553 tgl@sss.pgh.pa.us 428 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
5553 tgl@sss.pgh.pa.us 429 :CBC 734 : fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
430 : 734 : fdwhandler = fdwform->fdwhandler;
431 : :
432 : : /* Complain if FDW has been set to NO HANDLER. */
433 [ + + ]: 734 : 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 : 725 : ReleaseSysCache(tp);
440 : :
441 : : /* And finally, call the handler function. */
4013 442 : 725 : 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 *
4022 rhaas@postgresql.org 451 : 405 : GetFdwRoutineByRelId(Oid relid)
452 : : {
453 : : Oid serverid;
454 : :
455 : : /* Get server OID for the foreign table. */
4013 tgl@sss.pgh.pa.us 456 : 405 : serverid = GetForeignServerIdByRelId(relid);
457 : :
458 : : /* Now retrieve server's FdwRoutine struct. */
459 : 405 : 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 *
4808 474 : 2668 : GetFdwRoutineForRelation(Relation relation, bool makecopy)
475 : : {
476 : : FdwRoutine *fdwroutine;
477 : : FdwRoutine *cfdwroutine;
478 : :
479 [ + + ]: 2668 : if (relation->rd_fdwroutine == NULL)
480 : : {
481 : : /* Get the info by consulting the catalogs and the FDW code */
482 : 202 : fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(relation));
483 : :
484 : : /* Save the data for later reuse in CacheMemoryContext */
485 : 193 : cfdwroutine = (FdwRoutine *) MemoryContextAlloc(CacheMemoryContext,
486 : : sizeof(FdwRoutine));
487 : 193 : memcpy(cfdwroutine, fdwroutine, sizeof(FdwRoutine));
488 : 193 : relation->rd_fdwroutine = cfdwroutine;
489 : :
490 : : /* Give back the locally palloc'd copy regardless of makecopy */
491 : 193 : return fdwroutine;
492 : : }
493 : :
494 : : /* We have valid cached data --- does the caller want a copy? */
495 [ + + ]: 2466 : if (makecopy)
496 : : {
146 michael@paquier.xyz 497 :GNC 2257 : fdwroutine = palloc_object(FdwRoutine);
4808 tgl@sss.pgh.pa.us 498 :CBC 2257 : memcpy(fdwroutine, relation->rd_fdwroutine, sizeof(FdwRoutine));
499 : 2257 : return fdwroutine;
500 : : }
501 : :
502 : : /* Only a short-lived reference is needed, so just hand back cached copy */
503 : 209 : 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
4317 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 : : }
4317 tgl@sss.pgh.pa.us 532 :UBC 0 : return false;
533 : :
4317 tgl@sss.pgh.pa.us 534 :CBC 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)
4317 tgl@sss.pgh.pa.us 540 :UBC 0 : return false;
541 : : }
4317 tgl@sss.pgh.pa.us 542 :CBC 5 : return true;
543 : : }
4317 tgl@sss.pgh.pa.us 544 :UBC 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
1531 michael@paquier.xyz 554 :CBC 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 */
1295 565 : 607 : InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
566 : :
6172 bruce@momjian.us 567 [ + - + + : 1708 : foreach(cell, options)
+ + ]
568 : : {
569 : 1101 : DefElem *def = lfirst(cell);
570 : : Datum values[2];
571 : : bool nulls[2];
572 : :
6346 peter_e@gmx.net 573 : 1101 : values[0] = CStringGetTextDatum(def->defname);
5348 tgl@sss.pgh.pa.us 574 : 1101 : nulls[0] = false;
575 [ + - ]: 1101 : if (def->arg)
576 : : {
1979 peter@eisentraut.org 577 : 1101 : values[1] = CStringGetTextDatum(strVal(def->arg));
5348 tgl@sss.pgh.pa.us 578 : 1101 : nulls[1] = false;
579 : : }
580 : : else
581 : : {
5348 tgl@sss.pgh.pa.us 582 :UBC 0 : values[1] = (Datum) 0;
583 : 0 : nulls[1] = true;
584 : : }
1520 michael@paquier.xyz 585 :CBC 1101 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
586 : : values, nulls);
587 : : }
588 : :
6346 peter_e@gmx.net 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
6279 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++)
5977 heikki.linnakangas@i 638 [ + + + + ]: 544 : if (context == opt->optcontext && strcmp(opt->optname, option) == 0)
6279 peter_e@gmx.net 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 : : {
6172 bruce@momjian.us 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 : : {
6279 peter_e@gmx.net 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;
1327 peter@eisentraut.org 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);
6279 peter_e@gmx.net 680 [ + + ]: 320 : for (opt = libpq_conninfo_options; opt->optname; opt++)
681 : : {
5977 heikki.linnakangas@i 682 [ + + ]: 300 : if (catalog == opt->optcontext)
683 : : {
1327 peter@eisentraut.org 684 : 120 : has_valid_options = true;
685 : 120 : updateClosestMatch(&match_state, opt->optname);
686 : : }
687 : : }
688 : :
689 : 20 : closest_match = getClosestMatch(&match_state);
6279 peter_e@gmx.net 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
5513 rhaas@postgresql.org 713 : 512 : get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok)
714 : : {
715 : : Oid oid;
716 : :
2723 andres@anarazel.de 717 : 512 : oid = GetSysCacheOid1(FOREIGNDATAWRAPPERNAME,
718 : : Anum_pg_foreign_data_wrapper_oid,
719 : : CStringGetDatum(fdwname));
5513 rhaas@postgresql.org 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 : 1045 : get_foreign_server_oid(const char *servername, bool missing_ok)
737 : : {
738 : : Oid oid;
739 : :
2723 andres@anarazel.de 740 : 1045 : oid = GetSysCacheOid1(FOREIGNSERVERNAME, Anum_pg_foreign_server_oid,
741 : : CStringGetDatum(servername));
5513 rhaas@postgresql.org 742 [ + + + + ]: 1045 : if (!OidIsValid(oid) && !missing_ok)
743 [ + - ]: 26 : ereport(ERROR,
744 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
745 : : errmsg("server \"%s\" does not exist", servername)));
746 : 1019 : 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 *
3743 773 : 78 : GetExistingLocalJoinPath(RelOptInfo *joinrel)
774 : : {
775 : : ListCell *lc;
776 : :
3319 777 [ - + - - ]: 78 : Assert(IS_JOIN_REL(joinrel));
778 : :
3743 779 [ + - + - : 78 : foreach(lc, joinrel->pathlist)
+ - ]
780 : : {
781 : 78 : Path *path = (Path *) lfirst(lc);
782 : 78 : JoinPath *joinpath = NULL;
783 : :
784 : : /* Skip parameterized paths. */
3742 785 [ - + ]: 78 : if (path->param_info != NULL)
3743 rhaas@postgresql.org 786 :UBC 0 : continue;
787 : :
3743 rhaas@postgresql.org 788 [ + + + - ]:CBC 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 : :
3743 rhaas@postgresql.org 817 :UBC 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. */
3743 rhaas@postgresql.org 828 [ - + ]:CBC 78 : if (!joinpath)
3743 rhaas@postgresql.org 829 :UBC 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 : : */
3743 rhaas@postgresql.org 837 [ + - ]:CBC 78 : if (IsA(joinpath->outerjoinpath, ForeignPath))
838 : : {
839 : : ForeignPath *foreign_path;
840 : :
841 : 78 : foreign_path = (ForeignPath *) joinpath->outerjoinpath;
3319 842 [ + + - + ]: 78 : if (IS_JOIN_REL(foreign_path->path.parent))
843 : : {
3743 844 : 20 : joinpath->outerjoinpath = foreign_path->fdw_outerpath;
845 : :
362 rguo@postgresql.org 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 : :
3743 rhaas@postgresql.org 863 [ + + ]: 78 : if (IsA(joinpath->innerjoinpath, ForeignPath))
864 : : {
865 : : ForeignPath *foreign_path;
866 : :
867 : 68 : foreign_path = (ForeignPath *) joinpath->innerjoinpath;
3319 868 [ + - - + ]: 68 : if (IS_JOIN_REL(foreign_path->path.parent))
869 : : {
3743 rhaas@postgresql.org 870 :UBC 0 : joinpath->innerjoinpath = foreign_path->fdw_outerpath;
871 : :
362 rguo@postgresql.org 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 : :
3743 rhaas@postgresql.org 888 :CBC 78 : return (Path *) joinpath;
889 : : }
3743 rhaas@postgresql.org 890 :UBC 0 : return NULL;
891 : : }
|