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