Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * foreigncmds.c
4 : : * foreign-data wrapper/server creation/manipulation commands
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : *
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/commands/foreigncmds.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : : #include "postgres.h"
15 : :
16 : : #include "access/htup_details.h"
17 : : #include "access/reloptions.h"
18 : : #include "access/table.h"
19 : : #include "access/xact.h"
20 : : #include "catalog/catalog.h"
21 : : #include "catalog/dependency.h"
22 : : #include "catalog/indexing.h"
23 : : #include "catalog/objectaccess.h"
24 : : #include "catalog/pg_foreign_data_wrapper.h"
25 : : #include "catalog/pg_foreign_server.h"
26 : : #include "catalog/pg_foreign_table.h"
27 : : #include "catalog/pg_proc.h"
28 : : #include "catalog/pg_type.h"
29 : : #include "catalog/pg_user_mapping.h"
30 : : #include "commands/defrem.h"
31 : : #include "foreign/fdwapi.h"
32 : : #include "foreign/foreign.h"
33 : : #include "miscadmin.h"
34 : : #include "parser/parse_func.h"
35 : : #include "tcop/utility.h"
36 : : #include "utils/acl.h"
37 : : #include "utils/builtins.h"
38 : : #include "utils/lsyscache.h"
39 : : #include "utils/rel.h"
40 : : #include "utils/syscache.h"
41 : :
42 : :
43 : : typedef struct
44 : : {
45 : : char *tablename;
46 : : char *cmd;
47 : : } import_error_callback_arg;
48 : :
49 : : /* Internal functions */
50 : : static void import_error_callback(void *arg);
51 : :
52 : :
53 : : /*
54 : : * Convert a DefElem list to the text array format that is used in
55 : : * pg_foreign_data_wrapper, pg_foreign_server, pg_user_mapping, and
56 : : * pg_foreign_table.
57 : : *
58 : : * Returns the array in the form of a Datum, or PointerGetDatum(NULL)
59 : : * if the list is empty.
60 : : *
61 : : * Note: The array is usually stored to database without further
62 : : * processing, hence any validation should be done before this
63 : : * conversion.
64 : : */
65 : : static Datum
66 : 108 : optionListToArray(List *options)
67 : : {
68 : 108 : ArrayBuildState *astate = NULL;
69 : 108 : ListCell *cell;
70 : :
71 [ + + + + : 194 : foreach(cell, options)
+ + ]
72 : : {
73 : 86 : DefElem *def = lfirst(cell);
74 : 86 : const char *name;
75 : 86 : const char *value;
76 : 86 : Size len;
77 : 86 : text *t;
78 : :
79 : 86 : name = def->defname;
80 : 86 : value = defGetString(def);
81 : :
82 : : /* Insist that name not contain "=", else "a=b=c" is ambiguous */
83 [ + - ]: 86 : if (strchr(name, '=') != NULL)
84 [ # # # # ]: 0 : ereport(ERROR,
85 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
86 : : errmsg("invalid option name \"%s\": must not contain \"=\"",
87 : : name)));
88 : :
89 : 86 : len = VARHDRSZ + strlen(name) + 1 + strlen(value);
90 : : /* +1 leaves room for sprintf's trailing null */
91 : 86 : t = palloc(len + 1);
92 : 86 : SET_VARSIZE(t, len);
93 : 86 : sprintf(VARDATA(t), "%s=%s", name, value);
94 : :
95 : 172 : astate = accumArrayResult(astate, PointerGetDatum(t),
96 : : false, TEXTOID,
97 : 86 : CurrentMemoryContext);
98 : 86 : }
99 : :
100 [ + + ]: 108 : if (astate)
101 : 44 : return makeArrayResult(astate, CurrentMemoryContext);
102 : :
103 : 64 : return PointerGetDatum(NULL);
104 : 108 : }
105 : :
106 : :
107 : : /*
108 : : * Transform a list of DefElem into text array format. This is substantially
109 : : * the same thing as optionListToArray(), except we recognize SET/ADD/DROP
110 : : * actions for modifying an existing list of options, which is passed in
111 : : * Datum form as oldOptions. Also, if fdwvalidator isn't InvalidOid
112 : : * it specifies a validator function to call on the result.
113 : : *
114 : : * Returns the array in the form of a Datum, or PointerGetDatum(NULL)
115 : : * if the list is empty.
116 : : *
117 : : * This is used by CREATE/ALTER of FOREIGN DATA WRAPPER/SERVER/USER MAPPING/
118 : : * FOREIGN TABLE.
119 : : */
120 : : Datum
121 : 112 : transformGenericOptions(Oid catalogId,
122 : : Datum oldOptions,
123 : : List *options,
124 : : Oid fdwvalidator)
125 : : {
126 : 112 : List *resultOptions = untransformRelOptions(oldOptions);
127 : 112 : ListCell *optcell;
128 : 112 : Datum result;
129 : :
130 [ + + + + : 197 : foreach(optcell, options)
+ + ]
131 : : {
132 : 89 : DefElem *od = lfirst(optcell);
133 : 89 : ListCell *cell;
134 : :
135 : : /*
136 : : * Find the element in resultOptions. We need this for validation in
137 : : * all cases.
138 : : */
139 [ + + + + : 164 : foreach(cell, resultOptions)
+ + ]
140 : : {
141 : 75 : DefElem *def = lfirst(cell);
142 : :
143 [ + + ]: 75 : if (strcmp(def->defname, od->defname) == 0)
144 : 16 : break;
145 [ + + ]: 75 : }
146 : :
147 : : /*
148 : : * It is possible to perform multiple SET/DROP actions on the same
149 : : * option. The standard permits this, as long as the options to be
150 : : * added are unique. Note that an unspecified action is taken to be
151 : : * ADD.
152 : : */
153 [ + + + - ]: 89 : switch (od->defaction)
154 : : {
155 : : case DEFELEM_DROP:
156 [ + + ]: 10 : if (!cell)
157 [ + - + - ]: 1 : ereport(ERROR,
158 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
159 : : errmsg("option \"%s\" not found",
160 : : od->defname)));
161 : 9 : resultOptions = list_delete_cell(resultOptions, cell);
162 : 9 : break;
163 : :
164 : : case DEFELEM_SET:
165 [ + + ]: 6 : if (!cell)
166 [ + - + - ]: 1 : ereport(ERROR,
167 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
168 : : errmsg("option \"%s\" not found",
169 : : od->defname)));
170 : 5 : lfirst(cell) = od;
171 : 5 : break;
172 : :
173 : : case DEFELEM_ADD:
174 : : case DEFELEM_UNSPEC:
175 [ + + ]: 73 : if (cell)
176 [ + - + - ]: 2 : ereport(ERROR,
177 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
178 : : errmsg("option \"%s\" provided more than once",
179 : : od->defname)));
180 : 71 : resultOptions = lappend(resultOptions, od);
181 : 71 : break;
182 : :
183 : : default:
184 [ # # # # ]: 0 : elog(ERROR, "unrecognized action %d on option \"%s\"",
185 : : (int) od->defaction, od->defname);
186 : 0 : break;
187 : : }
188 : 85 : }
189 : :
190 : 108 : result = optionListToArray(resultOptions);
191 : :
192 [ + + ]: 108 : if (OidIsValid(fdwvalidator))
193 : : {
194 : 23 : Datum valarg = result;
195 : :
196 : : /*
197 : : * Pass a null options list as an empty array, so that validators
198 : : * don't have to be declared non-strict to handle the case.
199 : : */
200 [ + + ]: 23 : if (DatumGetPointer(valarg) == NULL)
201 : 12 : valarg = PointerGetDatum(construct_empty_array(TEXTOID));
202 : 23 : OidFunctionCall2(fdwvalidator, valarg, ObjectIdGetDatum(catalogId));
203 : 23 : }
204 : :
205 : 216 : return result;
206 : 108 : }
207 : :
208 : :
209 : : /*
210 : : * Internal workhorse for changing a data wrapper's owner.
211 : : *
212 : : * Allow this only for superusers; also the new owner must be a
213 : : * superuser.
214 : : */
215 : : static void
216 : 3 : AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
217 : : {
218 : 3 : Form_pg_foreign_data_wrapper form;
219 : 3 : Datum repl_val[Natts_pg_foreign_data_wrapper];
220 : 3 : bool repl_null[Natts_pg_foreign_data_wrapper];
221 : 3 : bool repl_repl[Natts_pg_foreign_data_wrapper];
222 : 3 : Acl *newAcl;
223 : 3 : Datum aclDatum;
224 : 3 : bool isNull;
225 : :
226 : 3 : form = (Form_pg_foreign_data_wrapper) GETSTRUCT(tup);
227 : :
228 : : /* Must be a superuser to change a FDW owner */
229 [ + + ]: 3 : if (!superuser())
230 [ + - + - ]: 1 : ereport(ERROR,
231 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
232 : : errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
233 : : NameStr(form->fdwname)),
234 : : errhint("Must be superuser to change owner of a foreign-data wrapper.")));
235 : :
236 : : /* New owner must also be a superuser */
237 [ + + ]: 2 : if (!superuser_arg(newOwnerId))
238 [ + - + - ]: 1 : ereport(ERROR,
239 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
240 : : errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
241 : : NameStr(form->fdwname)),
242 : : errhint("The owner of a foreign-data wrapper must be a superuser.")));
243 : :
244 [ - + ]: 1 : if (form->fdwowner != newOwnerId)
245 : : {
246 : 1 : memset(repl_null, false, sizeof(repl_null));
247 : 1 : memset(repl_repl, false, sizeof(repl_repl));
248 : :
249 : 1 : repl_repl[Anum_pg_foreign_data_wrapper_fdwowner - 1] = true;
250 : 1 : repl_val[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(newOwnerId);
251 : :
252 : 2 : aclDatum = heap_getattr(tup,
253 : : Anum_pg_foreign_data_wrapper_fdwacl,
254 : 1 : RelationGetDescr(rel),
255 : : &isNull);
256 : : /* Null ACLs do not require changes */
257 [ + - ]: 1 : if (!isNull)
258 : : {
259 : 0 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
260 : 0 : form->fdwowner, newOwnerId);
261 : 0 : repl_repl[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
262 : 0 : repl_val[Anum_pg_foreign_data_wrapper_fdwacl - 1] = PointerGetDatum(newAcl);
263 : 0 : }
264 : :
265 : 2 : tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
266 : 1 : repl_repl);
267 : :
268 : 1 : CatalogTupleUpdate(rel, &tup->t_self, tup);
269 : :
270 : : /* Update owner dependency reference */
271 : 1 : changeDependencyOnOwner(ForeignDataWrapperRelationId,
272 : 1 : form->oid,
273 : 1 : newOwnerId);
274 : 1 : }
275 : :
276 [ + - ]: 1 : InvokeObjectPostAlterHook(ForeignDataWrapperRelationId,
277 : : form->oid, 0);
278 : 1 : }
279 : :
280 : : /*
281 : : * Change foreign-data wrapper owner -- by name
282 : : *
283 : : * Note restrictions in the "_internal" function, above.
284 : : */
285 : : ObjectAddress
286 : 1 : AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId)
287 : : {
288 : 1 : Oid fdwId;
289 : 1 : HeapTuple tup;
290 : 1 : Relation rel;
291 : : ObjectAddress address;
292 : 1 : Form_pg_foreign_data_wrapper form;
293 : :
294 : :
295 : 1 : rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock);
296 : :
297 : 1 : tup = SearchSysCacheCopy1(FOREIGNDATAWRAPPERNAME, CStringGetDatum(name));
298 : :
299 [ + - ]: 1 : if (!HeapTupleIsValid(tup))
300 [ # # # # ]: 0 : ereport(ERROR,
301 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
302 : : errmsg("foreign-data wrapper \"%s\" does not exist", name)));
303 : :
304 : 1 : form = (Form_pg_foreign_data_wrapper) GETSTRUCT(tup);
305 : 1 : fdwId = form->oid;
306 : :
307 : 1 : AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId);
308 : :
309 : 1 : ObjectAddressSet(address, ForeignDataWrapperRelationId, fdwId);
310 : :
311 : 1 : heap_freetuple(tup);
312 : :
313 : 1 : table_close(rel, RowExclusiveLock);
314 : :
315 : : return address;
316 : 1 : }
317 : :
318 : : /*
319 : : * Change foreign-data wrapper owner -- by OID
320 : : *
321 : : * Note restrictions in the "_internal" function, above.
322 : : */
323 : : void
324 : 0 : AlterForeignDataWrapperOwner_oid(Oid fwdId, Oid newOwnerId)
325 : : {
326 : 0 : HeapTuple tup;
327 : 0 : Relation rel;
328 : :
329 : 0 : rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock);
330 : :
331 : 0 : tup = SearchSysCacheCopy1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fwdId));
332 : :
333 [ # # ]: 0 : if (!HeapTupleIsValid(tup))
334 [ # # # # ]: 0 : ereport(ERROR,
335 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
336 : : errmsg("foreign-data wrapper with OID %u does not exist", fwdId)));
337 : :
338 : 0 : AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId);
339 : :
340 : 0 : heap_freetuple(tup);
341 : :
342 : 0 : table_close(rel, RowExclusiveLock);
343 : 0 : }
344 : :
345 : : /*
346 : : * Internal workhorse for changing a foreign server's owner
347 : : */
348 : : static void
349 : 11 : AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
350 : : {
351 : 11 : Form_pg_foreign_server form;
352 : 11 : Datum repl_val[Natts_pg_foreign_server];
353 : 11 : bool repl_null[Natts_pg_foreign_server];
354 : 11 : bool repl_repl[Natts_pg_foreign_server];
355 : 11 : Acl *newAcl;
356 : 11 : Datum aclDatum;
357 : 11 : bool isNull;
358 : :
359 : 11 : form = (Form_pg_foreign_server) GETSTRUCT(tup);
360 : :
361 [ + + ]: 11 : if (form->srvowner != newOwnerId)
362 : : {
363 : : /* Superusers can always do it */
364 [ + + ]: 10 : if (!superuser())
365 : : {
366 : 3 : Oid srvId;
367 : 3 : AclResult aclresult;
368 : :
369 : 3 : srvId = form->oid;
370 : :
371 : : /* Must be owner */
372 [ + + ]: 3 : if (!object_ownercheck(ForeignServerRelationId, srvId, GetUserId()))
373 : 2 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FOREIGN_SERVER,
374 : 2 : NameStr(form->srvname));
375 : :
376 : : /* Must be able to become new owner */
377 : 3 : check_can_set_role(GetUserId(), newOwnerId);
378 : :
379 : : /* New owner must have USAGE privilege on foreign-data wrapper */
380 : 3 : aclresult = object_aclcheck(ForeignDataWrapperRelationId, form->srvfdw, newOwnerId, ACL_USAGE);
381 [ - + ]: 3 : if (aclresult != ACLCHECK_OK)
382 : : {
383 : 1 : ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw);
384 : :
385 : 1 : aclcheck_error(aclresult, OBJECT_FDW, fdw->fdwname);
386 : 1 : }
387 : 1 : }
388 : :
389 : 8 : memset(repl_null, false, sizeof(repl_null));
390 : 8 : memset(repl_repl, false, sizeof(repl_repl));
391 : :
392 : 8 : repl_repl[Anum_pg_foreign_server_srvowner - 1] = true;
393 : 8 : repl_val[Anum_pg_foreign_server_srvowner - 1] = ObjectIdGetDatum(newOwnerId);
394 : :
395 : 16 : aclDatum = heap_getattr(tup,
396 : : Anum_pg_foreign_server_srvacl,
397 : 8 : RelationGetDescr(rel),
398 : : &isNull);
399 : : /* Null ACLs do not require changes */
400 [ + + ]: 8 : if (!isNull)
401 : : {
402 : 6 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
403 : 3 : form->srvowner, newOwnerId);
404 : 3 : repl_repl[Anum_pg_foreign_server_srvacl - 1] = true;
405 : 3 : repl_val[Anum_pg_foreign_server_srvacl - 1] = PointerGetDatum(newAcl);
406 : 3 : }
407 : :
408 : 16 : tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
409 : 8 : repl_repl);
410 : :
411 : 8 : CatalogTupleUpdate(rel, &tup->t_self, tup);
412 : :
413 : : /* Update owner dependency reference */
414 : 16 : changeDependencyOnOwner(ForeignServerRelationId, form->oid,
415 : 8 : newOwnerId);
416 : 8 : }
417 : :
418 [ + - ]: 9 : InvokeObjectPostAlterHook(ForeignServerRelationId,
419 : : form->oid, 0);
420 : 9 : }
421 : :
422 : : /*
423 : : * Change foreign server owner -- by name
424 : : */
425 : : ObjectAddress
426 : 7 : AlterForeignServerOwner(const char *name, Oid newOwnerId)
427 : : {
428 : 7 : Oid servOid;
429 : 7 : HeapTuple tup;
430 : 7 : Relation rel;
431 : : ObjectAddress address;
432 : 7 : Form_pg_foreign_server form;
433 : :
434 : 7 : rel = table_open(ForeignServerRelationId, RowExclusiveLock);
435 : :
436 : 7 : tup = SearchSysCacheCopy1(FOREIGNSERVERNAME, CStringGetDatum(name));
437 : :
438 [ + - ]: 7 : if (!HeapTupleIsValid(tup))
439 [ # # # # ]: 0 : ereport(ERROR,
440 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
441 : : errmsg("server \"%s\" does not exist", name)));
442 : :
443 : 7 : form = (Form_pg_foreign_server) GETSTRUCT(tup);
444 : 7 : servOid = form->oid;
445 : :
446 : 7 : AlterForeignServerOwner_internal(rel, tup, newOwnerId);
447 : :
448 : 7 : ObjectAddressSet(address, ForeignServerRelationId, servOid);
449 : :
450 : 7 : heap_freetuple(tup);
451 : :
452 : 7 : table_close(rel, RowExclusiveLock);
453 : :
454 : : return address;
455 : 7 : }
456 : :
457 : : /*
458 : : * Change foreign server owner -- by OID
459 : : */
460 : : void
461 : 2 : AlterForeignServerOwner_oid(Oid srvId, Oid newOwnerId)
462 : : {
463 : 2 : HeapTuple tup;
464 : 2 : Relation rel;
465 : :
466 : 2 : rel = table_open(ForeignServerRelationId, RowExclusiveLock);
467 : :
468 : 2 : tup = SearchSysCacheCopy1(FOREIGNSERVEROID, ObjectIdGetDatum(srvId));
469 : :
470 [ + - ]: 2 : if (!HeapTupleIsValid(tup))
471 [ # # # # ]: 0 : ereport(ERROR,
472 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
473 : : errmsg("foreign server with OID %u does not exist", srvId)));
474 : :
475 : 2 : AlterForeignServerOwner_internal(rel, tup, newOwnerId);
476 : :
477 : 2 : heap_freetuple(tup);
478 : :
479 : 2 : table_close(rel, RowExclusiveLock);
480 : 2 : }
481 : :
482 : : /*
483 : : * Convert a handler function name passed from the parser to an Oid.
484 : : */
485 : : static Oid
486 : 6 : lookup_fdw_handler_func(DefElem *handler)
487 : : {
488 : 6 : Oid handlerOid;
489 : :
490 [ + - - + ]: 6 : if (handler == NULL || handler->arg == NULL)
491 : 0 : return InvalidOid;
492 : :
493 : : /* handlers have no arguments */
494 : 6 : handlerOid = LookupFuncName((List *) handler->arg, 0, NULL, false);
495 : :
496 : : /* check that handler has correct return type */
497 [ + + ]: 6 : if (get_func_rettype(handlerOid) != FDW_HANDLEROID)
498 [ + - + - ]: 2 : ereport(ERROR,
499 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
500 : : errmsg("function %s must return type %s",
501 : : NameListToString((List *) handler->arg), "fdw_handler")));
502 : :
503 : 4 : return handlerOid;
504 : 4 : }
505 : :
506 : : /*
507 : : * Convert a validator function name passed from the parser to an Oid.
508 : : */
509 : : static Oid
510 : 7 : lookup_fdw_validator_func(DefElem *validator)
511 : : {
512 : 7 : Oid funcargtypes[2];
513 : :
514 [ + - + + ]: 7 : if (validator == NULL || validator->arg == NULL)
515 : 1 : return InvalidOid;
516 : :
517 : : /* validators take text[], oid */
518 : 6 : funcargtypes[0] = TEXTARRAYOID;
519 : 6 : funcargtypes[1] = OIDOID;
520 : :
521 : 6 : return LookupFuncName((List *) validator->arg, 2, funcargtypes, false);
522 : : /* validator's return value is ignored, so we don't check the type */
523 : 7 : }
524 : :
525 : : /*
526 : : * Process function options of CREATE/ALTER FDW
527 : : */
528 : : static void
529 : 33 : parse_func_options(ParseState *pstate, List *func_options,
530 : : bool *handler_given, Oid *fdwhandler,
531 : : bool *validator_given, Oid *fdwvalidator)
532 : : {
533 : 33 : ListCell *cell;
534 : :
535 : 33 : *handler_given = false;
536 : 33 : *validator_given = false;
537 : : /* return InvalidOid if not given */
538 : 33 : *fdwhandler = InvalidOid;
539 : 33 : *fdwvalidator = InvalidOid;
540 : :
541 [ + + + + : 44 : foreach(cell, func_options)
+ + ]
542 : : {
543 : 13 : DefElem *def = (DefElem *) lfirst(cell);
544 : :
545 [ + + ]: 13 : if (strcmp(def->defname, "handler") == 0)
546 : : {
547 [ + + ]: 8 : if (*handler_given)
548 : 2 : errorConflictingDefElem(def, pstate);
549 : 6 : *handler_given = true;
550 : 6 : *fdwhandler = lookup_fdw_handler_func(def);
551 : 6 : }
552 [ + - ]: 5 : else if (strcmp(def->defname, "validator") == 0)
553 : : {
554 [ - + ]: 5 : if (*validator_given)
555 : 0 : errorConflictingDefElem(def, pstate);
556 : 5 : *validator_given = true;
557 : 5 : *fdwvalidator = lookup_fdw_validator_func(def);
558 : 5 : }
559 : : else
560 [ # # # # ]: 0 : elog(ERROR, "option \"%s\" not recognized",
561 : : def->defname);
562 : 11 : }
563 : 31 : }
564 : :
565 : : /*
566 : : * Create a foreign-data wrapper
567 : : */
568 : : ObjectAddress
569 : 22 : CreateForeignDataWrapper(ParseState *pstate, CreateFdwStmt *stmt)
570 : : {
571 : 22 : Relation rel;
572 : 22 : Datum values[Natts_pg_foreign_data_wrapper];
573 : 22 : bool nulls[Natts_pg_foreign_data_wrapper];
574 : 22 : HeapTuple tuple;
575 : 22 : Oid fdwId;
576 : 22 : bool handler_given;
577 : 22 : bool validator_given;
578 : 22 : Oid fdwhandler;
579 : 22 : Oid fdwvalidator;
580 : 22 : Datum fdwoptions;
581 : 22 : Oid ownerId;
582 : : ObjectAddress myself;
583 : 22 : ObjectAddress referenced;
584 : :
585 : 22 : rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock);
586 : :
587 : : /* Must be superuser */
588 [ + + ]: 22 : if (!superuser())
589 [ + - + - ]: 3 : ereport(ERROR,
590 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
591 : : errmsg("permission denied to create foreign-data wrapper \"%s\"",
592 : : stmt->fdwname),
593 : : errhint("Must be superuser to create a foreign-data wrapper.")));
594 : :
595 : : /* For now the owner cannot be specified on create. Use effective user ID. */
596 : 19 : ownerId = GetUserId();
597 : :
598 : : /*
599 : : * Check that there is no other foreign-data wrapper by this name.
600 : : */
601 [ + + ]: 19 : if (GetForeignDataWrapperByName(stmt->fdwname, true) != NULL)
602 [ + - + - ]: 1 : ereport(ERROR,
603 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
604 : : errmsg("foreign-data wrapper \"%s\" already exists",
605 : : stmt->fdwname)));
606 : :
607 : : /*
608 : : * Insert tuple into pg_foreign_data_wrapper.
609 : : */
610 : 18 : memset(values, 0, sizeof(values));
611 : 18 : memset(nulls, false, sizeof(nulls));
612 : :
613 : 18 : fdwId = GetNewOidWithIndex(rel, ForeignDataWrapperOidIndexId,
614 : : Anum_pg_foreign_data_wrapper_oid);
615 : 18 : values[Anum_pg_foreign_data_wrapper_oid - 1] = ObjectIdGetDatum(fdwId);
616 : 18 : values[Anum_pg_foreign_data_wrapper_fdwname - 1] =
617 : 18 : DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname));
618 : 18 : values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId);
619 : :
620 : : /* Lookup handler and validator functions, if given */
621 : 18 : parse_func_options(pstate, stmt->func_options,
622 : : &handler_given, &fdwhandler,
623 : : &validator_given, &fdwvalidator);
624 : :
625 : 18 : values[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler);
626 : 18 : values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);
627 : :
628 : 18 : nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
629 : :
630 : 18 : fdwoptions = transformGenericOptions(ForeignDataWrapperRelationId,
631 : 18 : PointerGetDatum(NULL),
632 : 18 : stmt->options,
633 : 18 : fdwvalidator);
634 : :
635 [ + + ]: 18 : if (DatumGetPointer(fdwoptions) != NULL)
636 : 3 : values[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = fdwoptions;
637 : : else
638 : 15 : nulls[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true;
639 : :
640 : 18 : tuple = heap_form_tuple(rel->rd_att, values, nulls);
641 : :
642 : 18 : CatalogTupleInsert(rel, tuple);
643 : :
644 : 18 : heap_freetuple(tuple);
645 : :
646 : : /* record dependencies */
647 : 18 : myself.classId = ForeignDataWrapperRelationId;
648 : 18 : myself.objectId = fdwId;
649 : 18 : myself.objectSubId = 0;
650 : :
651 [ + + ]: 18 : if (OidIsValid(fdwhandler))
652 : : {
653 : 1 : referenced.classId = ProcedureRelationId;
654 : 1 : referenced.objectId = fdwhandler;
655 : 1 : referenced.objectSubId = 0;
656 : 1 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
657 : 1 : }
658 : :
659 [ + + ]: 18 : if (OidIsValid(fdwvalidator))
660 : : {
661 : 3 : referenced.classId = ProcedureRelationId;
662 : 3 : referenced.objectId = fdwvalidator;
663 : 3 : referenced.objectSubId = 0;
664 : 3 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
665 : 3 : }
666 : :
667 : 18 : recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId);
668 : :
669 : : /* dependency on extension */
670 : 18 : recordDependencyOnCurrentExtension(&myself, false);
671 : :
672 : : /* Post creation hook for new foreign data wrapper */
673 [ + - ]: 18 : InvokeObjectPostCreateHook(ForeignDataWrapperRelationId, fdwId, 0);
674 : :
675 : 18 : table_close(rel, RowExclusiveLock);
676 : :
677 : : return myself;
678 : 18 : }
679 : :
680 : :
681 : : /*
682 : : * Alter foreign-data wrapper
683 : : */
684 : : ObjectAddress
685 : 16 : AlterForeignDataWrapper(ParseState *pstate, AlterFdwStmt *stmt)
686 : : {
687 : 16 : Relation rel;
688 : 16 : HeapTuple tp;
689 : 16 : Form_pg_foreign_data_wrapper fdwForm;
690 : 16 : Datum repl_val[Natts_pg_foreign_data_wrapper];
691 : 16 : bool repl_null[Natts_pg_foreign_data_wrapper];
692 : 16 : bool repl_repl[Natts_pg_foreign_data_wrapper];
693 : 16 : Oid fdwId;
694 : 16 : bool isnull;
695 : 16 : Datum datum;
696 : 16 : bool handler_given;
697 : 16 : bool validator_given;
698 : 16 : Oid fdwhandler;
699 : 16 : Oid fdwvalidator;
700 : : ObjectAddress myself;
701 : :
702 : 16 : rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock);
703 : :
704 : : /* Must be superuser */
705 [ + + ]: 16 : if (!superuser())
706 [ + - + - ]: 4 : ereport(ERROR,
707 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
708 : : errmsg("permission denied to alter foreign-data wrapper \"%s\"",
709 : : stmt->fdwname),
710 : : errhint("Must be superuser to alter a foreign-data wrapper.")));
711 : :
712 : 12 : tp = SearchSysCacheCopy1(FOREIGNDATAWRAPPERNAME,
713 : : CStringGetDatum(stmt->fdwname));
714 : :
715 [ + - ]: 12 : if (!HeapTupleIsValid(tp))
716 [ # # # # ]: 0 : ereport(ERROR,
717 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
718 : : errmsg("foreign-data wrapper \"%s\" does not exist", stmt->fdwname)));
719 : :
720 : 12 : fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
721 : 12 : fdwId = fdwForm->oid;
722 : :
723 : 12 : memset(repl_val, 0, sizeof(repl_val));
724 : 12 : memset(repl_null, false, sizeof(repl_null));
725 : 12 : memset(repl_repl, false, sizeof(repl_repl));
726 : :
727 : 12 : parse_func_options(pstate, stmt->func_options,
728 : : &handler_given, &fdwhandler,
729 : : &validator_given, &fdwvalidator);
730 : :
731 [ + + ]: 12 : if (handler_given)
732 : : {
733 : 1 : repl_val[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler);
734 : 1 : repl_repl[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = true;
735 : :
736 : : /*
737 : : * It could be that the behavior of accessing foreign table changes
738 : : * with the new handler. Warn about this.
739 : : */
740 [ - + + - ]: 1 : ereport(WARNING,
741 : : (errmsg("changing the foreign-data wrapper handler can change behavior of existing foreign tables")));
742 : 1 : }
743 : :
744 [ + + ]: 12 : if (validator_given)
745 : : {
746 : 2 : repl_val[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);
747 : 2 : repl_repl[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = true;
748 : :
749 : : /*
750 : : * It could be that existing options for the FDW or dependent SERVER,
751 : : * USER MAPPING or FOREIGN TABLE objects are no longer valid according
752 : : * to the new validator. Warn about this.
753 : : */
754 [ + + ]: 2 : if (OidIsValid(fdwvalidator))
755 [ - + + - ]: 1 : ereport(WARNING,
756 : : (errmsg("changing the foreign-data wrapper validator can cause "
757 : : "the options for dependent objects to become invalid")));
758 : 2 : }
759 : : else
760 : : {
761 : : /*
762 : : * Validator is not changed, but we need it for validating options.
763 : : */
764 : 10 : fdwvalidator = fdwForm->fdwvalidator;
765 : : }
766 : :
767 : : /*
768 : : * If options specified, validate and update.
769 : : */
770 [ + + ]: 12 : if (stmt->options)
771 : : {
772 : : /* Extract the current options */
773 : 5 : datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
774 : 5 : tp,
775 : : Anum_pg_foreign_data_wrapper_fdwoptions,
776 : : &isnull);
777 [ + + ]: 5 : if (isnull)
778 : 2 : datum = PointerGetDatum(NULL);
779 : :
780 : : /* Transform the options */
781 : 5 : datum = transformGenericOptions(ForeignDataWrapperRelationId,
782 : 5 : datum,
783 : 5 : stmt->options,
784 : 5 : fdwvalidator);
785 : :
786 [ + - ]: 5 : if (DatumGetPointer(datum) != NULL)
787 : 5 : repl_val[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = datum;
788 : : else
789 : 0 : repl_null[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true;
790 : :
791 : 5 : repl_repl[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true;
792 : 5 : }
793 : :
794 : : /* Everything looks good - update the tuple */
795 : 24 : tp = heap_modify_tuple(tp, RelationGetDescr(rel),
796 : 12 : repl_val, repl_null, repl_repl);
797 : :
798 : 12 : CatalogTupleUpdate(rel, &tp->t_self, tp);
799 : :
800 : 12 : heap_freetuple(tp);
801 : :
802 : 12 : ObjectAddressSet(myself, ForeignDataWrapperRelationId, fdwId);
803 : :
804 : : /* Update function dependencies if we changed them */
805 [ + + + + ]: 12 : if (handler_given || validator_given)
806 : : {
807 : 7 : ObjectAddress referenced;
808 : :
809 : : /*
810 : : * Flush all existing dependency records of this FDW on functions; we
811 : : * assume there can be none other than the ones we are fixing.
812 : : */
813 : 7 : deleteDependencyRecordsForClass(ForeignDataWrapperRelationId,
814 : 7 : fdwId,
815 : : ProcedureRelationId,
816 : : DEPENDENCY_NORMAL);
817 : :
818 : : /* And build new ones. */
819 : :
820 [ + + ]: 7 : if (OidIsValid(fdwhandler))
821 : : {
822 : 1 : referenced.classId = ProcedureRelationId;
823 : 1 : referenced.objectId = fdwhandler;
824 : 1 : referenced.objectSubId = 0;
825 : 1 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
826 : 1 : }
827 : :
828 [ + + ]: 3 : if (OidIsValid(fdwvalidator))
829 : : {
830 : 1 : referenced.classId = ProcedureRelationId;
831 : 1 : referenced.objectId = fdwvalidator;
832 : 1 : referenced.objectSubId = 0;
833 : 1 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
834 : 1 : }
835 : 3 : }
836 : :
837 [ + - ]: 8 : InvokeObjectPostAlterHook(ForeignDataWrapperRelationId, fdwId, 0);
838 : :
839 : 8 : table_close(rel, RowExclusiveLock);
840 : :
841 : : return myself;
842 : 8 : }
843 : :
844 : :
845 : : /*
846 : : * Create a foreign server
847 : : */
848 : : ObjectAddress
849 : 27 : CreateForeignServer(CreateForeignServerStmt *stmt)
850 : : {
851 : 27 : Relation rel;
852 : 27 : Datum srvoptions;
853 : 27 : Datum values[Natts_pg_foreign_server];
854 : 27 : bool nulls[Natts_pg_foreign_server];
855 : 27 : HeapTuple tuple;
856 : 27 : Oid srvId;
857 : 27 : Oid ownerId;
858 : 27 : AclResult aclresult;
859 : 27 : ObjectAddress myself;
860 : 27 : ObjectAddress referenced;
861 : 27 : ForeignDataWrapper *fdw;
862 : :
863 : 27 : rel = table_open(ForeignServerRelationId, RowExclusiveLock);
864 : :
865 : : /* For now the owner cannot be specified on create. Use effective user ID. */
866 : 27 : ownerId = GetUserId();
867 : :
868 : : /*
869 : : * Check that there is no other foreign server by this name. If there is
870 : : * one, do nothing if IF NOT EXISTS was specified.
871 : : */
872 : 27 : srvId = get_foreign_server_oid(stmt->servername, true);
873 [ + + ]: 27 : if (OidIsValid(srvId))
874 : : {
875 [ + + ]: 2 : if (stmt->if_not_exists)
876 : : {
877 : : /*
878 : : * If we are in an extension script, insist that the pre-existing
879 : : * object be a member of the extension, to avoid security risks.
880 : : */
881 : 1 : ObjectAddressSet(myself, ForeignServerRelationId, srvId);
882 : 1 : checkMembershipInCurrentExtension(&myself);
883 : :
884 : : /* OK to skip */
885 [ - + + - ]: 1 : ereport(NOTICE,
886 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
887 : : errmsg("server \"%s\" already exists, skipping",
888 : : stmt->servername)));
889 : 1 : table_close(rel, RowExclusiveLock);
890 : 1 : return InvalidObjectAddress;
891 : : }
892 : : else
893 [ + - + - ]: 1 : ereport(ERROR,
894 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
895 : : errmsg("server \"%s\" already exists",
896 : : stmt->servername)));
897 : 0 : }
898 : :
899 : : /*
900 : : * Check that the FDW exists and that we have USAGE on it. Also get the
901 : : * actual FDW for option validation etc.
902 : : */
903 : 25 : fdw = GetForeignDataWrapperByName(stmt->fdwname, false);
904 : :
905 : 25 : aclresult = object_aclcheck(ForeignDataWrapperRelationId, fdw->fdwid, ownerId, ACL_USAGE);
906 [ + + ]: 25 : if (aclresult != ACLCHECK_OK)
907 : 4 : aclcheck_error(aclresult, OBJECT_FDW, fdw->fdwname);
908 : :
909 : : /*
910 : : * Insert tuple into pg_foreign_server.
911 : : */
912 : 25 : memset(values, 0, sizeof(values));
913 : 25 : memset(nulls, false, sizeof(nulls));
914 : :
915 : 25 : srvId = GetNewOidWithIndex(rel, ForeignServerOidIndexId,
916 : : Anum_pg_foreign_server_oid);
917 : 25 : values[Anum_pg_foreign_server_oid - 1] = ObjectIdGetDatum(srvId);
918 : 25 : values[Anum_pg_foreign_server_srvname - 1] =
919 : 25 : DirectFunctionCall1(namein, CStringGetDatum(stmt->servername));
920 : 25 : values[Anum_pg_foreign_server_srvowner - 1] = ObjectIdGetDatum(ownerId);
921 : 25 : values[Anum_pg_foreign_server_srvfdw - 1] = ObjectIdGetDatum(fdw->fdwid);
922 : :
923 : : /* Add server type if supplied */
924 [ + + ]: 25 : if (stmt->servertype)
925 : 2 : values[Anum_pg_foreign_server_srvtype - 1] =
926 : 2 : CStringGetTextDatum(stmt->servertype);
927 : : else
928 : 23 : nulls[Anum_pg_foreign_server_srvtype - 1] = true;
929 : :
930 : : /* Add server version if supplied */
931 [ + + ]: 25 : if (stmt->version)
932 : 2 : values[Anum_pg_foreign_server_srvversion - 1] =
933 : 2 : CStringGetTextDatum(stmt->version);
934 : : else
935 : 23 : nulls[Anum_pg_foreign_server_srvversion - 1] = true;
936 : :
937 : : /* Start with a blank acl */
938 : 25 : nulls[Anum_pg_foreign_server_srvacl - 1] = true;
939 : :
940 : : /* Add server options */
941 : 25 : srvoptions = transformGenericOptions(ForeignServerRelationId,
942 : 25 : PointerGetDatum(NULL),
943 : 25 : stmt->options,
944 : 25 : fdw->fdwvalidator);
945 : :
946 [ + + ]: 25 : if (DatumGetPointer(srvoptions) != NULL)
947 : 5 : values[Anum_pg_foreign_server_srvoptions - 1] = srvoptions;
948 : : else
949 : 20 : nulls[Anum_pg_foreign_server_srvoptions - 1] = true;
950 : :
951 : 25 : tuple = heap_form_tuple(rel->rd_att, values, nulls);
952 : :
953 : 25 : CatalogTupleInsert(rel, tuple);
954 : :
955 : 25 : heap_freetuple(tuple);
956 : :
957 : : /* record dependencies */
958 : 25 : myself.classId = ForeignServerRelationId;
959 : 25 : myself.objectId = srvId;
960 : 25 : myself.objectSubId = 0;
961 : :
962 : 25 : referenced.classId = ForeignDataWrapperRelationId;
963 : 25 : referenced.objectId = fdw->fdwid;
964 : 25 : referenced.objectSubId = 0;
965 : 25 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
966 : :
967 : 25 : recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId);
968 : :
969 : : /* dependency on extension */
970 : 25 : recordDependencyOnCurrentExtension(&myself, false);
971 : :
972 : : /* Post creation hook for new foreign server */
973 [ + - ]: 25 : InvokeObjectPostCreateHook(ForeignServerRelationId, srvId, 0);
974 : :
975 : 25 : table_close(rel, RowExclusiveLock);
976 : :
977 : 25 : return myself;
978 : 26 : }
979 : :
980 : :
981 : : /*
982 : : * Alter foreign server
983 : : */
984 : : ObjectAddress
985 : 8 : AlterForeignServer(AlterForeignServerStmt *stmt)
986 : : {
987 : 8 : Relation rel;
988 : 8 : HeapTuple tp;
989 : 8 : Datum repl_val[Natts_pg_foreign_server];
990 : 8 : bool repl_null[Natts_pg_foreign_server];
991 : 8 : bool repl_repl[Natts_pg_foreign_server];
992 : 8 : Oid srvId;
993 : 8 : Form_pg_foreign_server srvForm;
994 : : ObjectAddress address;
995 : :
996 : 8 : rel = table_open(ForeignServerRelationId, RowExclusiveLock);
997 : :
998 : 8 : tp = SearchSysCacheCopy1(FOREIGNSERVERNAME,
999 : : CStringGetDatum(stmt->servername));
1000 : :
1001 [ + + ]: 8 : if (!HeapTupleIsValid(tp))
1002 [ + - + - ]: 1 : ereport(ERROR,
1003 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
1004 : : errmsg("server \"%s\" does not exist", stmt->servername)));
1005 : :
1006 : 7 : srvForm = (Form_pg_foreign_server) GETSTRUCT(tp);
1007 : 7 : srvId = srvForm->oid;
1008 : :
1009 : : /*
1010 : : * Only owner or a superuser can ALTER a SERVER.
1011 : : */
1012 [ + + ]: 7 : if (!object_ownercheck(ForeignServerRelationId, srvId, GetUserId()))
1013 : 4 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FOREIGN_SERVER,
1014 : 4 : stmt->servername);
1015 : :
1016 : 7 : memset(repl_val, 0, sizeof(repl_val));
1017 : 7 : memset(repl_null, false, sizeof(repl_null));
1018 : 7 : memset(repl_repl, false, sizeof(repl_repl));
1019 : :
1020 [ + + ]: 7 : if (stmt->has_version)
1021 : : {
1022 : : /*
1023 : : * Change the server VERSION string.
1024 : : */
1025 [ + - ]: 4 : if (stmt->version)
1026 : 4 : repl_val[Anum_pg_foreign_server_srvversion - 1] =
1027 : 4 : CStringGetTextDatum(stmt->version);
1028 : : else
1029 : 0 : repl_null[Anum_pg_foreign_server_srvversion - 1] = true;
1030 : :
1031 : 4 : repl_repl[Anum_pg_foreign_server_srvversion - 1] = true;
1032 : 4 : }
1033 : :
1034 [ + + ]: 7 : if (stmt->options)
1035 : : {
1036 : 4 : ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
1037 : 4 : Datum datum;
1038 : 4 : bool isnull;
1039 : :
1040 : : /* Extract the current srvoptions */
1041 : 4 : datum = SysCacheGetAttr(FOREIGNSERVEROID,
1042 : 4 : tp,
1043 : : Anum_pg_foreign_server_srvoptions,
1044 : : &isnull);
1045 [ + + ]: 4 : if (isnull)
1046 : 2 : datum = PointerGetDatum(NULL);
1047 : :
1048 : : /* Prepare the options array */
1049 : 4 : datum = transformGenericOptions(ForeignServerRelationId,
1050 : 4 : datum,
1051 : 4 : stmt->options,
1052 : 4 : fdw->fdwvalidator);
1053 : :
1054 [ + + ]: 4 : if (DatumGetPointer(datum) != NULL)
1055 : 3 : repl_val[Anum_pg_foreign_server_srvoptions - 1] = datum;
1056 : : else
1057 : 1 : repl_null[Anum_pg_foreign_server_srvoptions - 1] = true;
1058 : :
1059 : 4 : repl_repl[Anum_pg_foreign_server_srvoptions - 1] = true;
1060 : 4 : }
1061 : :
1062 : : /* Everything looks good - update the tuple */
1063 : 14 : tp = heap_modify_tuple(tp, RelationGetDescr(rel),
1064 : 7 : repl_val, repl_null, repl_repl);
1065 : :
1066 : 7 : CatalogTupleUpdate(rel, &tp->t_self, tp);
1067 : :
1068 [ + - ]: 7 : InvokeObjectPostAlterHook(ForeignServerRelationId, srvId, 0);
1069 : :
1070 : 7 : ObjectAddressSet(address, ForeignServerRelationId, srvId);
1071 : :
1072 : 7 : heap_freetuple(tp);
1073 : :
1074 : 7 : table_close(rel, RowExclusiveLock);
1075 : :
1076 : : return address;
1077 : 7 : }
1078 : :
1079 : :
1080 : : /*
1081 : : * Common routine to check permission for user-mapping-related DDL
1082 : : * commands. We allow server owners to operate on any mapping, and
1083 : : * users to operate on their own mapping.
1084 : : */
1085 : : static void
1086 : 39 : user_mapping_ddl_aclcheck(Oid umuserid, Oid serverid, const char *servername)
1087 : : {
1088 : 39 : Oid curuserid = GetUserId();
1089 : :
1090 [ + + ]: 39 : if (!object_ownercheck(ForeignServerRelationId, serverid, curuserid))
1091 : : {
1092 [ + + ]: 10 : if (umuserid == curuserid)
1093 : : {
1094 : 1 : AclResult aclresult;
1095 : :
1096 : 1 : aclresult = object_aclcheck(ForeignServerRelationId, serverid, curuserid, ACL_USAGE);
1097 [ - + ]: 1 : if (aclresult != ACLCHECK_OK)
1098 : 1 : aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, servername);
1099 : 1 : }
1100 : : else
1101 : 9 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FOREIGN_SERVER,
1102 : 9 : servername);
1103 : 10 : }
1104 : 39 : }
1105 : :
1106 : :
1107 : : /*
1108 : : * Create user mapping
1109 : : */
1110 : : ObjectAddress
1111 : 27 : CreateUserMapping(CreateUserMappingStmt *stmt)
1112 : : {
1113 : 27 : Relation rel;
1114 : 27 : Datum useoptions;
1115 : 27 : Datum values[Natts_pg_user_mapping];
1116 : 27 : bool nulls[Natts_pg_user_mapping];
1117 : 27 : HeapTuple tuple;
1118 : 27 : Oid useId;
1119 : 27 : Oid umId;
1120 : 27 : ObjectAddress myself;
1121 : 27 : ObjectAddress referenced;
1122 : 27 : ForeignServer *srv;
1123 : 27 : ForeignDataWrapper *fdw;
1124 : 27 : RoleSpec *role = (RoleSpec *) stmt->user;
1125 : :
1126 : 27 : rel = table_open(UserMappingRelationId, RowExclusiveLock);
1127 : :
1128 [ + + ]: 27 : if (role->roletype == ROLESPEC_PUBLIC)
1129 : 8 : useId = ACL_ID_PUBLIC;
1130 : : else
1131 : 19 : useId = get_rolespec_oid(stmt->user, false);
1132 : :
1133 : : /* Check that the server exists. */
1134 : 27 : srv = GetForeignServerByName(stmt->servername, false);
1135 : :
1136 : 27 : user_mapping_ddl_aclcheck(useId, srv->serverid, stmt->servername);
1137 : :
1138 : : /*
1139 : : * Check that the user mapping is unique within server.
1140 : : */
1141 : 27 : umId = GetSysCacheOid2(USERMAPPINGUSERSERVER, Anum_pg_user_mapping_oid,
1142 : : ObjectIdGetDatum(useId),
1143 : : ObjectIdGetDatum(srv->serverid));
1144 : :
1145 [ + + ]: 27 : if (OidIsValid(umId))
1146 : : {
1147 [ + + ]: 10 : if (stmt->if_not_exists)
1148 : : {
1149 : : /*
1150 : : * Since user mappings aren't members of extensions (see comments
1151 : : * below), no need for checkMembershipInCurrentExtension here.
1152 : : */
1153 [ - + + + : 8 : ereport(NOTICE,
+ - ]
1154 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
1155 : : errmsg("user mapping for \"%s\" already exists for server \"%s\", skipping",
1156 : : MappingUserName(useId),
1157 : : stmt->servername)));
1158 : :
1159 : 8 : table_close(rel, RowExclusiveLock);
1160 : 8 : return InvalidObjectAddress;
1161 : : }
1162 : : else
1163 [ + - + - : 2 : ereport(ERROR,
+ - ]
1164 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
1165 : : errmsg("user mapping for \"%s\" already exists for server \"%s\"",
1166 : : MappingUserName(useId),
1167 : : stmt->servername)));
1168 : 0 : }
1169 : :
1170 : 17 : fdw = GetForeignDataWrapper(srv->fdwid);
1171 : :
1172 : : /*
1173 : : * Insert tuple into pg_user_mapping.
1174 : : */
1175 : 17 : memset(values, 0, sizeof(values));
1176 : 17 : memset(nulls, false, sizeof(nulls));
1177 : :
1178 : 17 : umId = GetNewOidWithIndex(rel, UserMappingOidIndexId,
1179 : : Anum_pg_user_mapping_oid);
1180 : 17 : values[Anum_pg_user_mapping_oid - 1] = ObjectIdGetDatum(umId);
1181 : 17 : values[Anum_pg_user_mapping_umuser - 1] = ObjectIdGetDatum(useId);
1182 : 17 : values[Anum_pg_user_mapping_umserver - 1] = ObjectIdGetDatum(srv->serverid);
1183 : :
1184 : : /* Add user options */
1185 : 17 : useoptions = transformGenericOptions(UserMappingRelationId,
1186 : 17 : PointerGetDatum(NULL),
1187 : 17 : stmt->options,
1188 : 17 : fdw->fdwvalidator);
1189 : :
1190 [ + + ]: 17 : if (DatumGetPointer(useoptions) != NULL)
1191 : 6 : values[Anum_pg_user_mapping_umoptions - 1] = useoptions;
1192 : : else
1193 : 11 : nulls[Anum_pg_user_mapping_umoptions - 1] = true;
1194 : :
1195 : 17 : tuple = heap_form_tuple(rel->rd_att, values, nulls);
1196 : :
1197 : 17 : CatalogTupleInsert(rel, tuple);
1198 : :
1199 : 17 : heap_freetuple(tuple);
1200 : :
1201 : : /* Add dependency on the server */
1202 : 17 : myself.classId = UserMappingRelationId;
1203 : 17 : myself.objectId = umId;
1204 : 17 : myself.objectSubId = 0;
1205 : :
1206 : 17 : referenced.classId = ForeignServerRelationId;
1207 : 17 : referenced.objectId = srv->serverid;
1208 : 17 : referenced.objectSubId = 0;
1209 : 17 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
1210 : :
1211 [ + + ]: 17 : if (OidIsValid(useId))
1212 : : {
1213 : : /* Record the mapped user dependency */
1214 : 12 : recordDependencyOnOwner(UserMappingRelationId, umId, useId);
1215 : 12 : }
1216 : :
1217 : : /*
1218 : : * Perhaps someday there should be a recordDependencyOnCurrentExtension
1219 : : * call here; but since roles aren't members of extensions, it seems like
1220 : : * user mappings shouldn't be either. Note that the grammar and pg_dump
1221 : : * would need to be extended too if we change this.
1222 : : */
1223 : :
1224 : : /* Post creation hook for new user mapping */
1225 [ + - ]: 17 : InvokeObjectPostCreateHook(UserMappingRelationId, umId, 0);
1226 : :
1227 : 17 : table_close(rel, RowExclusiveLock);
1228 : :
1229 : 17 : return myself;
1230 : 25 : }
1231 : :
1232 : :
1233 : : /*
1234 : : * Alter user mapping
1235 : : */
1236 : : ObjectAddress
1237 : 11 : AlterUserMapping(AlterUserMappingStmt *stmt)
1238 : : {
1239 : 11 : Relation rel;
1240 : 11 : HeapTuple tp;
1241 : 11 : Datum repl_val[Natts_pg_user_mapping];
1242 : 11 : bool repl_null[Natts_pg_user_mapping];
1243 : 11 : bool repl_repl[Natts_pg_user_mapping];
1244 : 11 : Oid useId;
1245 : 11 : Oid umId;
1246 : 11 : ForeignServer *srv;
1247 : : ObjectAddress address;
1248 : 11 : RoleSpec *role = (RoleSpec *) stmt->user;
1249 : :
1250 : 11 : rel = table_open(UserMappingRelationId, RowExclusiveLock);
1251 : :
1252 [ + + ]: 11 : if (role->roletype == ROLESPEC_PUBLIC)
1253 : 3 : useId = ACL_ID_PUBLIC;
1254 : : else
1255 : 8 : useId = get_rolespec_oid(stmt->user, false);
1256 : :
1257 : 11 : srv = GetForeignServerByName(stmt->servername, false);
1258 : :
1259 : 11 : umId = GetSysCacheOid2(USERMAPPINGUSERSERVER, Anum_pg_user_mapping_oid,
1260 : : ObjectIdGetDatum(useId),
1261 : : ObjectIdGetDatum(srv->serverid));
1262 [ + + ]: 11 : if (!OidIsValid(umId))
1263 [ + - + - : 1 : ereport(ERROR,
- + ]
1264 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
1265 : : errmsg("user mapping for \"%s\" does not exist for server \"%s\"",
1266 : : MappingUserName(useId), stmt->servername)));
1267 : :
1268 : 10 : user_mapping_ddl_aclcheck(useId, srv->serverid, stmt->servername);
1269 : :
1270 : 10 : tp = SearchSysCacheCopy1(USERMAPPINGOID, ObjectIdGetDatum(umId));
1271 : :
1272 [ + - ]: 10 : if (!HeapTupleIsValid(tp))
1273 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for user mapping %u", umId);
1274 : :
1275 : 10 : memset(repl_val, 0, sizeof(repl_val));
1276 : 10 : memset(repl_null, false, sizeof(repl_null));
1277 : 10 : memset(repl_repl, false, sizeof(repl_repl));
1278 : :
1279 [ + + ]: 10 : if (stmt->options)
1280 : : {
1281 : 4 : ForeignDataWrapper *fdw;
1282 : 4 : Datum datum;
1283 : 4 : bool isnull;
1284 : :
1285 : : /*
1286 : : * Process the options.
1287 : : */
1288 : :
1289 : 4 : fdw = GetForeignDataWrapper(srv->fdwid);
1290 : :
1291 : 4 : datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
1292 : 4 : tp,
1293 : : Anum_pg_user_mapping_umoptions,
1294 : : &isnull);
1295 [ + + ]: 4 : if (isnull)
1296 : 2 : datum = PointerGetDatum(NULL);
1297 : :
1298 : : /* Prepare the options array */
1299 : 4 : datum = transformGenericOptions(UserMappingRelationId,
1300 : 4 : datum,
1301 : 4 : stmt->options,
1302 : 4 : fdw->fdwvalidator);
1303 : :
1304 [ + + ]: 4 : if (DatumGetPointer(datum) != NULL)
1305 : 3 : repl_val[Anum_pg_user_mapping_umoptions - 1] = datum;
1306 : : else
1307 : 1 : repl_null[Anum_pg_user_mapping_umoptions - 1] = true;
1308 : :
1309 : 4 : repl_repl[Anum_pg_user_mapping_umoptions - 1] = true;
1310 : 4 : }
1311 : :
1312 : : /* Everything looks good - update the tuple */
1313 : 20 : tp = heap_modify_tuple(tp, RelationGetDescr(rel),
1314 : 10 : repl_val, repl_null, repl_repl);
1315 : :
1316 : 10 : CatalogTupleUpdate(rel, &tp->t_self, tp);
1317 : :
1318 [ + - ]: 10 : InvokeObjectPostAlterHook(UserMappingRelationId,
1319 : : umId, 0);
1320 : :
1321 : 10 : ObjectAddressSet(address, UserMappingRelationId, umId);
1322 : :
1323 : 10 : heap_freetuple(tp);
1324 : :
1325 : 10 : table_close(rel, RowExclusiveLock);
1326 : :
1327 : : return address;
1328 : 10 : }
1329 : :
1330 : :
1331 : : /*
1332 : : * Drop user mapping
1333 : : */
1334 : : Oid
1335 : 11 : RemoveUserMapping(DropUserMappingStmt *stmt)
1336 : : {
1337 : 11 : ObjectAddress object;
1338 : 11 : Oid useId;
1339 : 11 : Oid umId;
1340 : 11 : ForeignServer *srv;
1341 : 11 : RoleSpec *role = (RoleSpec *) stmt->user;
1342 : :
1343 [ + + ]: 11 : if (role->roletype == ROLESPEC_PUBLIC)
1344 : 4 : useId = ACL_ID_PUBLIC;
1345 : : else
1346 : : {
1347 : 7 : useId = get_rolespec_oid(stmt->user, stmt->missing_ok);
1348 [ + + ]: 7 : if (!OidIsValid(useId))
1349 : : {
1350 : : /*
1351 : : * IF EXISTS specified, role not found and not public. Notice this
1352 : : * and leave.
1353 : : */
1354 [ - + + - ]: 1 : elog(NOTICE, "role \"%s\" does not exist, skipping",
1355 : : role->rolename);
1356 : 1 : return InvalidOid;
1357 : : }
1358 : : }
1359 : :
1360 : 10 : srv = GetForeignServerByName(stmt->servername, true);
1361 : :
1362 [ + + ]: 10 : if (!srv)
1363 : : {
1364 [ + + ]: 2 : if (!stmt->missing_ok)
1365 [ + - + - ]: 1 : ereport(ERROR,
1366 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
1367 : : errmsg("server \"%s\" does not exist",
1368 : : stmt->servername)));
1369 : : /* IF EXISTS, just note it */
1370 [ - + + - ]: 1 : ereport(NOTICE,
1371 : : (errmsg("server \"%s\" does not exist, skipping",
1372 : : stmt->servername)));
1373 : 1 : return InvalidOid;
1374 : : }
1375 : :
1376 : 8 : umId = GetSysCacheOid2(USERMAPPINGUSERSERVER, Anum_pg_user_mapping_oid,
1377 : : ObjectIdGetDatum(useId),
1378 : : ObjectIdGetDatum(srv->serverid));
1379 : :
1380 [ + + ]: 8 : if (!OidIsValid(umId))
1381 : : {
1382 [ + + ]: 2 : if (!stmt->missing_ok)
1383 [ + - + - : 1 : ereport(ERROR,
- + ]
1384 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
1385 : : errmsg("user mapping for \"%s\" does not exist for server \"%s\"",
1386 : : MappingUserName(useId), stmt->servername)));
1387 : :
1388 : : /* IF EXISTS specified, just note it */
1389 [ - + + - : 1 : ereport(NOTICE,
- + ]
1390 : : (errmsg("user mapping for \"%s\" does not exist for server \"%s\", skipping",
1391 : : MappingUserName(useId), stmt->servername)));
1392 : 1 : return InvalidOid;
1393 : : }
1394 : :
1395 : 6 : user_mapping_ddl_aclcheck(useId, srv->serverid, srv->servername);
1396 : :
1397 : : /*
1398 : : * Do the deletion
1399 : : */
1400 : 6 : object.classId = UserMappingRelationId;
1401 : 6 : object.objectId = umId;
1402 : 6 : object.objectSubId = 0;
1403 : :
1404 : 6 : performDeletion(&object, DROP_CASCADE, 0);
1405 : :
1406 : 6 : return umId;
1407 : 9 : }
1408 : :
1409 : :
1410 : : /*
1411 : : * Create a foreign table
1412 : : * call after DefineRelation().
1413 : : */
1414 : : void
1415 : 23 : CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
1416 : : {
1417 : 23 : Relation ftrel;
1418 : 23 : Datum ftoptions;
1419 : 23 : Datum values[Natts_pg_foreign_table];
1420 : 23 : bool nulls[Natts_pg_foreign_table];
1421 : 23 : HeapTuple tuple;
1422 : 23 : AclResult aclresult;
1423 : 23 : ObjectAddress myself;
1424 : 23 : ObjectAddress referenced;
1425 : 23 : Oid ownerId;
1426 : 23 : ForeignDataWrapper *fdw;
1427 : 23 : ForeignServer *server;
1428 : :
1429 : : /*
1430 : : * Advance command counter to ensure the pg_attribute tuple is visible;
1431 : : * the tuple might be updated to add constraints in previous step.
1432 : : */
1433 : 23 : CommandCounterIncrement();
1434 : :
1435 : 23 : ftrel = table_open(ForeignTableRelationId, RowExclusiveLock);
1436 : :
1437 : : /*
1438 : : * For now the owner cannot be specified on create. Use effective user ID.
1439 : : */
1440 : 23 : ownerId = GetUserId();
1441 : :
1442 : : /*
1443 : : * Check that the foreign server exists and that we have USAGE on it. Also
1444 : : * get the actual FDW for option validation etc.
1445 : : */
1446 : 23 : server = GetForeignServerByName(stmt->servername, false);
1447 : 23 : aclresult = object_aclcheck(ForeignServerRelationId, server->serverid, ownerId, ACL_USAGE);
1448 [ + - ]: 23 : if (aclresult != ACLCHECK_OK)
1449 : 0 : aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, server->servername);
1450 : :
1451 : 23 : fdw = GetForeignDataWrapper(server->fdwid);
1452 : :
1453 : : /*
1454 : : * Insert tuple into pg_foreign_table.
1455 : : */
1456 : 23 : memset(values, 0, sizeof(values));
1457 : 23 : memset(nulls, false, sizeof(nulls));
1458 : :
1459 : 23 : values[Anum_pg_foreign_table_ftrelid - 1] = ObjectIdGetDatum(relid);
1460 : 23 : values[Anum_pg_foreign_table_ftserver - 1] = ObjectIdGetDatum(server->serverid);
1461 : : /* Add table generic options */
1462 : 23 : ftoptions = transformGenericOptions(ForeignTableRelationId,
1463 : 23 : PointerGetDatum(NULL),
1464 : 23 : stmt->options,
1465 : 23 : fdw->fdwvalidator);
1466 : :
1467 [ + + ]: 23 : if (DatumGetPointer(ftoptions) != NULL)
1468 : 7 : values[Anum_pg_foreign_table_ftoptions - 1] = ftoptions;
1469 : : else
1470 : 16 : nulls[Anum_pg_foreign_table_ftoptions - 1] = true;
1471 : :
1472 : 23 : tuple = heap_form_tuple(ftrel->rd_att, values, nulls);
1473 : :
1474 : 23 : CatalogTupleInsert(ftrel, tuple);
1475 : :
1476 : 23 : heap_freetuple(tuple);
1477 : :
1478 : : /* Add pg_class dependency on the server */
1479 : 23 : myself.classId = RelationRelationId;
1480 : 23 : myself.objectId = relid;
1481 : 23 : myself.objectSubId = 0;
1482 : :
1483 : 23 : referenced.classId = ForeignServerRelationId;
1484 : 23 : referenced.objectId = server->serverid;
1485 : 23 : referenced.objectSubId = 0;
1486 : 23 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
1487 : :
1488 : 23 : table_close(ftrel, RowExclusiveLock);
1489 : 23 : }
1490 : :
1491 : : /*
1492 : : * Import a foreign schema
1493 : : */
1494 : : void
1495 : 4 : ImportForeignSchema(ImportForeignSchemaStmt *stmt)
1496 : : {
1497 : 4 : ForeignServer *server;
1498 : 4 : ForeignDataWrapper *fdw;
1499 : 4 : FdwRoutine *fdw_routine;
1500 : 4 : AclResult aclresult;
1501 : 4 : List *cmd_list;
1502 : 4 : ListCell *lc;
1503 : :
1504 : : /* Check that the foreign server exists and that we have USAGE on it */
1505 : 4 : server = GetForeignServerByName(stmt->server_name, false);
1506 : 4 : aclresult = object_aclcheck(ForeignServerRelationId, server->serverid, GetUserId(), ACL_USAGE);
1507 [ - + ]: 4 : if (aclresult != ACLCHECK_OK)
1508 : 0 : aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, server->servername);
1509 : :
1510 : : /* Check that the schema exists and we have CREATE permissions on it */
1511 : 4 : (void) LookupCreationNamespace(stmt->local_schema);
1512 : :
1513 : : /* Get the FDW and check it supports IMPORT */
1514 : 4 : fdw = GetForeignDataWrapper(server->fdwid);
1515 [ - + ]: 4 : if (!OidIsValid(fdw->fdwhandler))
1516 [ + - + - ]: 4 : ereport(ERROR,
1517 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1518 : : errmsg("foreign-data wrapper \"%s\" has no handler",
1519 : : fdw->fdwname)));
1520 : 0 : fdw_routine = GetFdwRoutine(fdw->fdwhandler);
1521 [ # # ]: 0 : if (fdw_routine->ImportForeignSchema == NULL)
1522 [ # # # # ]: 0 : ereport(ERROR,
1523 : : (errcode(ERRCODE_FDW_NO_SCHEMAS),
1524 : : errmsg("foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA",
1525 : : fdw->fdwname)));
1526 : :
1527 : : /* Call FDW to get a list of commands */
1528 : 0 : cmd_list = fdw_routine->ImportForeignSchema(stmt, server->serverid);
1529 : :
1530 : : /* Parse and execute each command */
1531 [ # # # # : 0 : foreach(lc, cmd_list)
# # ]
1532 : : {
1533 : 0 : char *cmd = (char *) lfirst(lc);
1534 : 0 : import_error_callback_arg callback_arg;
1535 : 0 : ErrorContextCallback sqlerrcontext;
1536 : 0 : List *raw_parsetree_list;
1537 : 0 : ListCell *lc2;
1538 : :
1539 : : /*
1540 : : * Setup error traceback support for ereport(). This is so that any
1541 : : * error in the generated SQL will be displayed nicely.
1542 : : */
1543 : 0 : callback_arg.tablename = NULL; /* not known yet */
1544 : 0 : callback_arg.cmd = cmd;
1545 : 0 : sqlerrcontext.callback = import_error_callback;
1546 : 0 : sqlerrcontext.arg = &callback_arg;
1547 : 0 : sqlerrcontext.previous = error_context_stack;
1548 : 0 : error_context_stack = &sqlerrcontext;
1549 : :
1550 : : /*
1551 : : * Parse the SQL string into a list of raw parse trees.
1552 : : */
1553 : 0 : raw_parsetree_list = pg_parse_query(cmd);
1554 : :
1555 : : /*
1556 : : * Process each parse tree (we allow the FDW to put more than one
1557 : : * command per string, though this isn't really advised).
1558 : : */
1559 [ # # # # : 0 : foreach(lc2, raw_parsetree_list)
# # ]
1560 : : {
1561 : 0 : RawStmt *rs = lfirst_node(RawStmt, lc2);
1562 : 0 : CreateForeignTableStmt *cstmt = (CreateForeignTableStmt *) rs->stmt;
1563 : 0 : PlannedStmt *pstmt;
1564 : :
1565 : : /*
1566 : : * Because we only allow CreateForeignTableStmt, we can skip parse
1567 : : * analysis, rewrite, and planning steps here.
1568 : : */
1569 [ # # ]: 0 : if (!IsA(cstmt, CreateForeignTableStmt))
1570 [ # # # # ]: 0 : elog(ERROR,
1571 : : "foreign-data wrapper \"%s\" returned incorrect statement type %d",
1572 : : fdw->fdwname, (int) nodeTag(cstmt));
1573 : :
1574 : : /* Ignore commands for tables excluded by filter options */
1575 [ # # ]: 0 : if (!IsImportableForeignTable(cstmt->base.relation->relname, stmt))
1576 : 0 : continue;
1577 : :
1578 : : /* Enable reporting of current table's name on error */
1579 : 0 : callback_arg.tablename = cstmt->base.relation->relname;
1580 : :
1581 : : /* Ensure creation schema is the one given in IMPORT statement */
1582 : 0 : cstmt->base.relation->schemaname = pstrdup(stmt->local_schema);
1583 : :
1584 : : /* No planning needed, just make a wrapper PlannedStmt */
1585 : 0 : pstmt = makeNode(PlannedStmt);
1586 : 0 : pstmt->commandType = CMD_UTILITY;
1587 : 0 : pstmt->canSetTag = false;
1588 : 0 : pstmt->utilityStmt = (Node *) cstmt;
1589 : 0 : pstmt->stmt_location = rs->stmt_location;
1590 : 0 : pstmt->stmt_len = rs->stmt_len;
1591 : 0 : pstmt->planOrigin = PLAN_STMT_INTERNAL;
1592 : :
1593 : : /* Execute statement */
1594 : 0 : ProcessUtility(pstmt, cmd, false,
1595 : : PROCESS_UTILITY_SUBCOMMAND, NULL, NULL,
1596 : 0 : None_Receiver, NULL);
1597 : :
1598 : : /* Be sure to advance the command counter between subcommands */
1599 : 0 : CommandCounterIncrement();
1600 : :
1601 : 0 : callback_arg.tablename = NULL;
1602 [ # # # ]: 0 : }
1603 : :
1604 : 0 : error_context_stack = sqlerrcontext.previous;
1605 : 0 : }
1606 : 0 : }
1607 : :
1608 : : /*
1609 : : * error context callback to let us supply the failing SQL statement's text
1610 : : */
1611 : : static void
1612 : 0 : import_error_callback(void *arg)
1613 : : {
1614 : 0 : import_error_callback_arg *callback_arg = (import_error_callback_arg *) arg;
1615 : 0 : int syntaxerrposition;
1616 : :
1617 : : /* If it's a syntax error, convert to internal syntax error report */
1618 : 0 : syntaxerrposition = geterrposition();
1619 [ # # ]: 0 : if (syntaxerrposition > 0)
1620 : : {
1621 : 0 : errposition(0);
1622 : 0 : internalerrposition(syntaxerrposition);
1623 : 0 : internalerrquery(callback_arg->cmd);
1624 : 0 : }
1625 : :
1626 [ # # ]: 0 : if (callback_arg->tablename)
1627 : 0 : errcontext("importing foreign table \"%s\"",
1628 : 0 : callback_arg->tablename);
1629 : 0 : }
|