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