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