/src/postgres/src/backend/commands/foreigncmds.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * foreigncmds.c |
4 | | * foreign-data wrapper/server creation/manipulation commands |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, 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 | | optionListToArray(List *options) |
67 | 0 | { |
68 | 0 | ArrayBuildState *astate = NULL; |
69 | 0 | ListCell *cell; |
70 | |
|
71 | 0 | foreach(cell, options) |
72 | 0 | { |
73 | 0 | DefElem *def = lfirst(cell); |
74 | 0 | const char *name; |
75 | 0 | const char *value; |
76 | 0 | Size len; |
77 | 0 | text *t; |
78 | |
|
79 | 0 | name = def->defname; |
80 | 0 | value = defGetString(def); |
81 | | |
82 | | /* Insist that name not contain "=", else "a=b=c" is ambiguous */ |
83 | 0 | if (strchr(name, '=') != NULL) |
84 | 0 | ereport(ERROR, |
85 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
86 | 0 | errmsg("invalid option name \"%s\": must not contain \"=\"", |
87 | 0 | name))); |
88 | | |
89 | 0 | len = VARHDRSZ + strlen(name) + 1 + strlen(value); |
90 | | /* +1 leaves room for sprintf's trailing null */ |
91 | 0 | t = palloc(len + 1); |
92 | 0 | SET_VARSIZE(t, len); |
93 | 0 | sprintf(VARDATA(t), "%s=%s", name, value); |
94 | |
|
95 | 0 | astate = accumArrayResult(astate, PointerGetDatum(t), |
96 | 0 | false, TEXTOID, |
97 | 0 | CurrentMemoryContext); |
98 | 0 | } |
99 | | |
100 | 0 | if (astate) |
101 | 0 | return makeArrayResult(astate, CurrentMemoryContext); |
102 | | |
103 | 0 | return PointerGetDatum(NULL); |
104 | 0 | } |
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 | | transformGenericOptions(Oid catalogId, |
122 | | Datum oldOptions, |
123 | | List *options, |
124 | | Oid fdwvalidator) |
125 | 0 | { |
126 | 0 | List *resultOptions = untransformRelOptions(oldOptions); |
127 | 0 | ListCell *optcell; |
128 | 0 | Datum result; |
129 | |
|
130 | 0 | foreach(optcell, options) |
131 | 0 | { |
132 | 0 | DefElem *od = lfirst(optcell); |
133 | 0 | ListCell *cell; |
134 | | |
135 | | /* |
136 | | * Find the element in resultOptions. We need this for validation in |
137 | | * all cases. |
138 | | */ |
139 | 0 | foreach(cell, resultOptions) |
140 | 0 | { |
141 | 0 | DefElem *def = lfirst(cell); |
142 | |
|
143 | 0 | if (strcmp(def->defname, od->defname) == 0) |
144 | 0 | break; |
145 | 0 | } |
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 | 0 | switch (od->defaction) |
154 | 0 | { |
155 | 0 | case DEFELEM_DROP: |
156 | 0 | if (!cell) |
157 | 0 | ereport(ERROR, |
158 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
159 | 0 | errmsg("option \"%s\" not found", |
160 | 0 | od->defname))); |
161 | 0 | resultOptions = list_delete_cell(resultOptions, cell); |
162 | 0 | break; |
163 | | |
164 | 0 | case DEFELEM_SET: |
165 | 0 | if (!cell) |
166 | 0 | ereport(ERROR, |
167 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
168 | 0 | errmsg("option \"%s\" not found", |
169 | 0 | od->defname))); |
170 | 0 | lfirst(cell) = od; |
171 | 0 | break; |
172 | | |
173 | 0 | case DEFELEM_ADD: |
174 | 0 | case DEFELEM_UNSPEC: |
175 | 0 | if (cell) |
176 | 0 | ereport(ERROR, |
177 | 0 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
178 | 0 | errmsg("option \"%s\" provided more than once", |
179 | 0 | od->defname))); |
180 | 0 | resultOptions = lappend(resultOptions, od); |
181 | 0 | break; |
182 | | |
183 | 0 | default: |
184 | 0 | elog(ERROR, "unrecognized action %d on option \"%s\"", |
185 | 0 | (int) od->defaction, od->defname); |
186 | 0 | break; |
187 | 0 | } |
188 | 0 | } |
189 | | |
190 | 0 | result = optionListToArray(resultOptions); |
191 | |
|
192 | 0 | if (OidIsValid(fdwvalidator)) |
193 | 0 | { |
194 | 0 | 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 | 0 | if (DatumGetPointer(valarg) == NULL) |
201 | 0 | valarg = PointerGetDatum(construct_empty_array(TEXTOID)); |
202 | 0 | OidFunctionCall2(fdwvalidator, valarg, ObjectIdGetDatum(catalogId)); |
203 | 0 | } |
204 | |
|
205 | 0 | return result; |
206 | 0 | } |
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 | | AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) |
217 | 0 | { |
218 | 0 | Form_pg_foreign_data_wrapper form; |
219 | 0 | Datum repl_val[Natts_pg_foreign_data_wrapper]; |
220 | 0 | bool repl_null[Natts_pg_foreign_data_wrapper]; |
221 | 0 | bool repl_repl[Natts_pg_foreign_data_wrapper]; |
222 | 0 | Acl *newAcl; |
223 | 0 | Datum aclDatum; |
224 | 0 | bool isNull; |
225 | |
|
226 | 0 | form = (Form_pg_foreign_data_wrapper) GETSTRUCT(tup); |
227 | | |
228 | | /* Must be a superuser to change a FDW owner */ |
229 | 0 | if (!superuser()) |
230 | 0 | ereport(ERROR, |
231 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
232 | 0 | errmsg("permission denied to change owner of foreign-data wrapper \"%s\"", |
233 | 0 | NameStr(form->fdwname)), |
234 | 0 | errhint("Must be superuser to change owner of a foreign-data wrapper."))); |
235 | | |
236 | | /* New owner must also be a superuser */ |
237 | 0 | if (!superuser_arg(newOwnerId)) |
238 | 0 | ereport(ERROR, |
239 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
240 | 0 | errmsg("permission denied to change owner of foreign-data wrapper \"%s\"", |
241 | 0 | NameStr(form->fdwname)), |
242 | 0 | errhint("The owner of a foreign-data wrapper must be a superuser."))); |
243 | | |
244 | 0 | if (form->fdwowner != newOwnerId) |
245 | 0 | { |
246 | 0 | memset(repl_null, false, sizeof(repl_null)); |
247 | 0 | memset(repl_repl, false, sizeof(repl_repl)); |
248 | |
|
249 | 0 | repl_repl[Anum_pg_foreign_data_wrapper_fdwowner - 1] = true; |
250 | 0 | repl_val[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(newOwnerId); |
251 | |
|
252 | 0 | aclDatum = heap_getattr(tup, |
253 | 0 | Anum_pg_foreign_data_wrapper_fdwacl, |
254 | 0 | RelationGetDescr(rel), |
255 | 0 | &isNull); |
256 | | /* Null ACLs do not require changes */ |
257 | 0 | if (!isNull) |
258 | 0 | { |
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 | 0 | tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, |
266 | 0 | repl_repl); |
267 | |
|
268 | 0 | CatalogTupleUpdate(rel, &tup->t_self, tup); |
269 | | |
270 | | /* Update owner dependency reference */ |
271 | 0 | changeDependencyOnOwner(ForeignDataWrapperRelationId, |
272 | 0 | form->oid, |
273 | 0 | newOwnerId); |
274 | 0 | } |
275 | |
|
276 | 0 | InvokeObjectPostAlterHook(ForeignDataWrapperRelationId, |
277 | 0 | form->oid, 0); |
278 | 0 | } |
279 | | |
280 | | /* |
281 | | * Change foreign-data wrapper owner -- by name |
282 | | * |
283 | | * Note restrictions in the "_internal" function, above. |
284 | | */ |
285 | | ObjectAddress |
286 | | AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId) |
287 | 0 | { |
288 | 0 | Oid fdwId; |
289 | 0 | HeapTuple tup; |
290 | 0 | Relation rel; |
291 | 0 | ObjectAddress address; |
292 | 0 | Form_pg_foreign_data_wrapper form; |
293 | | |
294 | |
|
295 | 0 | rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock); |
296 | |
|
297 | 0 | tup = SearchSysCacheCopy1(FOREIGNDATAWRAPPERNAME, CStringGetDatum(name)); |
298 | |
|
299 | 0 | if (!HeapTupleIsValid(tup)) |
300 | 0 | ereport(ERROR, |
301 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
302 | 0 | errmsg("foreign-data wrapper \"%s\" does not exist", name))); |
303 | | |
304 | 0 | form = (Form_pg_foreign_data_wrapper) GETSTRUCT(tup); |
305 | 0 | fdwId = form->oid; |
306 | |
|
307 | 0 | AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId); |
308 | |
|
309 | 0 | ObjectAddressSet(address, ForeignDataWrapperRelationId, fdwId); |
310 | |
|
311 | 0 | heap_freetuple(tup); |
312 | |
|
313 | 0 | table_close(rel, RowExclusiveLock); |
314 | |
|
315 | 0 | return address; |
316 | 0 | } |
317 | | |
318 | | /* |
319 | | * Change foreign-data wrapper owner -- by OID |
320 | | * |
321 | | * Note restrictions in the "_internal" function, above. |
322 | | */ |
323 | | void |
324 | | AlterForeignDataWrapperOwner_oid(Oid fwdId, Oid newOwnerId) |
325 | 0 | { |
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 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
336 | 0 | 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 | | AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) |
350 | 0 | { |
351 | 0 | Form_pg_foreign_server form; |
352 | 0 | Datum repl_val[Natts_pg_foreign_server]; |
353 | 0 | bool repl_null[Natts_pg_foreign_server]; |
354 | 0 | bool repl_repl[Natts_pg_foreign_server]; |
355 | 0 | Acl *newAcl; |
356 | 0 | Datum aclDatum; |
357 | 0 | bool isNull; |
358 | |
|
359 | 0 | form = (Form_pg_foreign_server) GETSTRUCT(tup); |
360 | |
|
361 | 0 | if (form->srvowner != newOwnerId) |
362 | 0 | { |
363 | | /* Superusers can always do it */ |
364 | 0 | if (!superuser()) |
365 | 0 | { |
366 | 0 | Oid srvId; |
367 | 0 | AclResult aclresult; |
368 | |
|
369 | 0 | srvId = form->oid; |
370 | | |
371 | | /* Must be owner */ |
372 | 0 | if (!object_ownercheck(ForeignServerRelationId, srvId, GetUserId())) |
373 | 0 | aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FOREIGN_SERVER, |
374 | 0 | NameStr(form->srvname)); |
375 | | |
376 | | /* Must be able to become new owner */ |
377 | 0 | check_can_set_role(GetUserId(), newOwnerId); |
378 | | |
379 | | /* New owner must have USAGE privilege on foreign-data wrapper */ |
380 | 0 | aclresult = object_aclcheck(ForeignDataWrapperRelationId, form->srvfdw, newOwnerId, ACL_USAGE); |
381 | 0 | if (aclresult != ACLCHECK_OK) |
382 | 0 | { |
383 | 0 | ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw); |
384 | |
|
385 | 0 | aclcheck_error(aclresult, OBJECT_FDW, fdw->fdwname); |
386 | 0 | } |
387 | 0 | } |
388 | |
|
389 | 0 | memset(repl_null, false, sizeof(repl_null)); |
390 | 0 | memset(repl_repl, false, sizeof(repl_repl)); |
391 | |
|
392 | 0 | repl_repl[Anum_pg_foreign_server_srvowner - 1] = true; |
393 | 0 | repl_val[Anum_pg_foreign_server_srvowner - 1] = ObjectIdGetDatum(newOwnerId); |
394 | |
|
395 | 0 | aclDatum = heap_getattr(tup, |
396 | 0 | Anum_pg_foreign_server_srvacl, |
397 | 0 | RelationGetDescr(rel), |
398 | 0 | &isNull); |
399 | | /* Null ACLs do not require changes */ |
400 | 0 | if (!isNull) |
401 | 0 | { |
402 | 0 | newAcl = aclnewowner(DatumGetAclP(aclDatum), |
403 | 0 | form->srvowner, newOwnerId); |
404 | 0 | repl_repl[Anum_pg_foreign_server_srvacl - 1] = true; |
405 | 0 | repl_val[Anum_pg_foreign_server_srvacl - 1] = PointerGetDatum(newAcl); |
406 | 0 | } |
407 | |
|
408 | 0 | tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, |
409 | 0 | repl_repl); |
410 | |
|
411 | 0 | CatalogTupleUpdate(rel, &tup->t_self, tup); |
412 | | |
413 | | /* Update owner dependency reference */ |
414 | 0 | changeDependencyOnOwner(ForeignServerRelationId, form->oid, |
415 | 0 | newOwnerId); |
416 | 0 | } |
417 | |
|
418 | 0 | InvokeObjectPostAlterHook(ForeignServerRelationId, |
419 | 0 | form->oid, 0); |
420 | 0 | } |
421 | | |
422 | | /* |
423 | | * Change foreign server owner -- by name |
424 | | */ |
425 | | ObjectAddress |
426 | | AlterForeignServerOwner(const char *name, Oid newOwnerId) |
427 | 0 | { |
428 | 0 | Oid servOid; |
429 | 0 | HeapTuple tup; |
430 | 0 | Relation rel; |
431 | 0 | ObjectAddress address; |
432 | 0 | Form_pg_foreign_server form; |
433 | |
|
434 | 0 | rel = table_open(ForeignServerRelationId, RowExclusiveLock); |
435 | |
|
436 | 0 | tup = SearchSysCacheCopy1(FOREIGNSERVERNAME, CStringGetDatum(name)); |
437 | |
|
438 | 0 | if (!HeapTupleIsValid(tup)) |
439 | 0 | ereport(ERROR, |
440 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
441 | 0 | errmsg("server \"%s\" does not exist", name))); |
442 | | |
443 | 0 | form = (Form_pg_foreign_server) GETSTRUCT(tup); |
444 | 0 | servOid = form->oid; |
445 | |
|
446 | 0 | AlterForeignServerOwner_internal(rel, tup, newOwnerId); |
447 | |
|
448 | 0 | ObjectAddressSet(address, ForeignServerRelationId, servOid); |
449 | |
|
450 | 0 | heap_freetuple(tup); |
451 | |
|
452 | 0 | table_close(rel, RowExclusiveLock); |
453 | |
|
454 | 0 | return address; |
455 | 0 | } |
456 | | |
457 | | /* |
458 | | * Change foreign server owner -- by OID |
459 | | */ |
460 | | void |
461 | | AlterForeignServerOwner_oid(Oid srvId, Oid newOwnerId) |
462 | 0 | { |
463 | 0 | HeapTuple tup; |
464 | 0 | Relation rel; |
465 | |
|
466 | 0 | rel = table_open(ForeignServerRelationId, RowExclusiveLock); |
467 | |
|
468 | 0 | tup = SearchSysCacheCopy1(FOREIGNSERVEROID, ObjectIdGetDatum(srvId)); |
469 | |
|
470 | 0 | if (!HeapTupleIsValid(tup)) |
471 | 0 | ereport(ERROR, |
472 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
473 | 0 | errmsg("foreign server with OID %u does not exist", srvId))); |
474 | | |
475 | 0 | AlterForeignServerOwner_internal(rel, tup, newOwnerId); |
476 | |
|
477 | 0 | heap_freetuple(tup); |
478 | |
|
479 | 0 | table_close(rel, RowExclusiveLock); |
480 | 0 | } |
481 | | |
482 | | /* |
483 | | * Convert a handler function name passed from the parser to an Oid. |
484 | | */ |
485 | | static Oid |
486 | | lookup_fdw_handler_func(DefElem *handler) |
487 | 0 | { |
488 | 0 | Oid handlerOid; |
489 | |
|
490 | 0 | if (handler == NULL || handler->arg == NULL) |
491 | 0 | return InvalidOid; |
492 | | |
493 | | /* handlers have no arguments */ |
494 | 0 | handlerOid = LookupFuncName((List *) handler->arg, 0, NULL, false); |
495 | | |
496 | | /* check that handler has correct return type */ |
497 | 0 | if (get_func_rettype(handlerOid) != FDW_HANDLEROID) |
498 | 0 | ereport(ERROR, |
499 | 0 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
500 | 0 | errmsg("function %s must return type %s", |
501 | 0 | NameListToString((List *) handler->arg), "fdw_handler"))); |
502 | | |
503 | 0 | return handlerOid; |
504 | 0 | } |
505 | | |
506 | | /* |
507 | | * Convert a validator function name passed from the parser to an Oid. |
508 | | */ |
509 | | static Oid |
510 | | lookup_fdw_validator_func(DefElem *validator) |
511 | 0 | { |
512 | 0 | Oid funcargtypes[2]; |
513 | |
|
514 | 0 | if (validator == NULL || validator->arg == NULL) |
515 | 0 | return InvalidOid; |
516 | | |
517 | | /* validators take text[], oid */ |
518 | 0 | funcargtypes[0] = TEXTARRAYOID; |
519 | 0 | funcargtypes[1] = OIDOID; |
520 | |
|
521 | 0 | return LookupFuncName((List *) validator->arg, 2, funcargtypes, false); |
522 | | /* validator's return value is ignored, so we don't check the type */ |
523 | 0 | } |
524 | | |
525 | | /* |
526 | | * Process function options of CREATE/ALTER FDW |
527 | | */ |
528 | | static void |
529 | | parse_func_options(ParseState *pstate, List *func_options, |
530 | | bool *handler_given, Oid *fdwhandler, |
531 | | bool *validator_given, Oid *fdwvalidator) |
532 | 0 | { |
533 | 0 | ListCell *cell; |
534 | |
|
535 | 0 | *handler_given = false; |
536 | 0 | *validator_given = false; |
537 | | /* return InvalidOid if not given */ |
538 | 0 | *fdwhandler = InvalidOid; |
539 | 0 | *fdwvalidator = InvalidOid; |
540 | |
|
541 | 0 | foreach(cell, func_options) |
542 | 0 | { |
543 | 0 | DefElem *def = (DefElem *) lfirst(cell); |
544 | |
|
545 | 0 | if (strcmp(def->defname, "handler") == 0) |
546 | 0 | { |
547 | 0 | if (*handler_given) |
548 | 0 | errorConflictingDefElem(def, pstate); |
549 | 0 | *handler_given = true; |
550 | 0 | *fdwhandler = lookup_fdw_handler_func(def); |
551 | 0 | } |
552 | 0 | else if (strcmp(def->defname, "validator") == 0) |
553 | 0 | { |
554 | 0 | if (*validator_given) |
555 | 0 | errorConflictingDefElem(def, pstate); |
556 | 0 | *validator_given = true; |
557 | 0 | *fdwvalidator = lookup_fdw_validator_func(def); |
558 | 0 | } |
559 | 0 | else |
560 | 0 | elog(ERROR, "option \"%s\" not recognized", |
561 | 0 | def->defname); |
562 | 0 | } |
563 | 0 | } |
564 | | |
565 | | /* |
566 | | * Create a foreign-data wrapper |
567 | | */ |
568 | | ObjectAddress |
569 | | CreateForeignDataWrapper(ParseState *pstate, CreateFdwStmt *stmt) |
570 | 0 | { |
571 | 0 | Relation rel; |
572 | 0 | Datum values[Natts_pg_foreign_data_wrapper]; |
573 | 0 | bool nulls[Natts_pg_foreign_data_wrapper]; |
574 | 0 | HeapTuple tuple; |
575 | 0 | Oid fdwId; |
576 | 0 | bool handler_given; |
577 | 0 | bool validator_given; |
578 | 0 | Oid fdwhandler; |
579 | 0 | Oid fdwvalidator; |
580 | 0 | Datum fdwoptions; |
581 | 0 | Oid ownerId; |
582 | 0 | ObjectAddress myself; |
583 | 0 | ObjectAddress referenced; |
584 | |
|
585 | 0 | rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock); |
586 | | |
587 | | /* Must be superuser */ |
588 | 0 | if (!superuser()) |
589 | 0 | ereport(ERROR, |
590 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
591 | 0 | errmsg("permission denied to create foreign-data wrapper \"%s\"", |
592 | 0 | stmt->fdwname), |
593 | 0 | 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 | 0 | ownerId = GetUserId(); |
597 | | |
598 | | /* |
599 | | * Check that there is no other foreign-data wrapper by this name. |
600 | | */ |
601 | 0 | if (GetForeignDataWrapperByName(stmt->fdwname, true) != NULL) |
602 | 0 | ereport(ERROR, |
603 | 0 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
604 | 0 | errmsg("foreign-data wrapper \"%s\" already exists", |
605 | 0 | stmt->fdwname))); |
606 | | |
607 | | /* |
608 | | * Insert tuple into pg_foreign_data_wrapper. |
609 | | */ |
610 | 0 | memset(values, 0, sizeof(values)); |
611 | 0 | memset(nulls, false, sizeof(nulls)); |
612 | |
|
613 | 0 | fdwId = GetNewOidWithIndex(rel, ForeignDataWrapperOidIndexId, |
614 | 0 | Anum_pg_foreign_data_wrapper_oid); |
615 | 0 | values[Anum_pg_foreign_data_wrapper_oid - 1] = ObjectIdGetDatum(fdwId); |
616 | 0 | values[Anum_pg_foreign_data_wrapper_fdwname - 1] = |
617 | 0 | DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname)); |
618 | 0 | values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId); |
619 | | |
620 | | /* Lookup handler and validator functions, if given */ |
621 | 0 | parse_func_options(pstate, stmt->func_options, |
622 | 0 | &handler_given, &fdwhandler, |
623 | 0 | &validator_given, &fdwvalidator); |
624 | |
|
625 | 0 | values[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler); |
626 | 0 | values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator); |
627 | |
|
628 | 0 | nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true; |
629 | |
|
630 | 0 | fdwoptions = transformGenericOptions(ForeignDataWrapperRelationId, |
631 | 0 | PointerGetDatum(NULL), |
632 | 0 | stmt->options, |
633 | 0 | fdwvalidator); |
634 | |
|
635 | 0 | if (DatumGetPointer(fdwoptions) != NULL) |
636 | 0 | values[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = fdwoptions; |
637 | 0 | else |
638 | 0 | nulls[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true; |
639 | |
|
640 | 0 | tuple = heap_form_tuple(rel->rd_att, values, nulls); |
641 | |
|
642 | 0 | CatalogTupleInsert(rel, tuple); |
643 | |
|
644 | 0 | heap_freetuple(tuple); |
645 | | |
646 | | /* record dependencies */ |
647 | 0 | myself.classId = ForeignDataWrapperRelationId; |
648 | 0 | myself.objectId = fdwId; |
649 | 0 | myself.objectSubId = 0; |
650 | |
|
651 | 0 | if (OidIsValid(fdwhandler)) |
652 | 0 | { |
653 | 0 | referenced.classId = ProcedureRelationId; |
654 | 0 | referenced.objectId = fdwhandler; |
655 | 0 | referenced.objectSubId = 0; |
656 | 0 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
657 | 0 | } |
658 | |
|
659 | 0 | if (OidIsValid(fdwvalidator)) |
660 | 0 | { |
661 | 0 | referenced.classId = ProcedureRelationId; |
662 | 0 | referenced.objectId = fdwvalidator; |
663 | 0 | referenced.objectSubId = 0; |
664 | 0 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
665 | 0 | } |
666 | |
|
667 | 0 | recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId); |
668 | | |
669 | | /* dependency on extension */ |
670 | 0 | recordDependencyOnCurrentExtension(&myself, false); |
671 | | |
672 | | /* Post creation hook for new foreign data wrapper */ |
673 | 0 | InvokeObjectPostCreateHook(ForeignDataWrapperRelationId, fdwId, 0); |
674 | |
|
675 | 0 | table_close(rel, RowExclusiveLock); |
676 | |
|
677 | 0 | return myself; |
678 | 0 | } |
679 | | |
680 | | |
681 | | /* |
682 | | * Alter foreign-data wrapper |
683 | | */ |
684 | | ObjectAddress |
685 | | AlterForeignDataWrapper(ParseState *pstate, AlterFdwStmt *stmt) |
686 | 0 | { |
687 | 0 | Relation rel; |
688 | 0 | HeapTuple tp; |
689 | 0 | Form_pg_foreign_data_wrapper fdwForm; |
690 | 0 | Datum repl_val[Natts_pg_foreign_data_wrapper]; |
691 | 0 | bool repl_null[Natts_pg_foreign_data_wrapper]; |
692 | 0 | bool repl_repl[Natts_pg_foreign_data_wrapper]; |
693 | 0 | Oid fdwId; |
694 | 0 | bool isnull; |
695 | 0 | Datum datum; |
696 | 0 | bool handler_given; |
697 | 0 | bool validator_given; |
698 | 0 | Oid fdwhandler; |
699 | 0 | Oid fdwvalidator; |
700 | 0 | ObjectAddress myself; |
701 | |
|
702 | 0 | rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock); |
703 | | |
704 | | /* Must be superuser */ |
705 | 0 | if (!superuser()) |
706 | 0 | ereport(ERROR, |
707 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
708 | 0 | errmsg("permission denied to alter foreign-data wrapper \"%s\"", |
709 | 0 | stmt->fdwname), |
710 | 0 | errhint("Must be superuser to alter a foreign-data wrapper."))); |
711 | | |
712 | 0 | tp = SearchSysCacheCopy1(FOREIGNDATAWRAPPERNAME, |
713 | 0 | CStringGetDatum(stmt->fdwname)); |
714 | |
|
715 | 0 | if (!HeapTupleIsValid(tp)) |
716 | 0 | ereport(ERROR, |
717 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
718 | 0 | errmsg("foreign-data wrapper \"%s\" does not exist", stmt->fdwname))); |
719 | | |
720 | 0 | fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp); |
721 | 0 | fdwId = fdwForm->oid; |
722 | |
|
723 | 0 | memset(repl_val, 0, sizeof(repl_val)); |
724 | 0 | memset(repl_null, false, sizeof(repl_null)); |
725 | 0 | memset(repl_repl, false, sizeof(repl_repl)); |
726 | |
|
727 | 0 | parse_func_options(pstate, stmt->func_options, |
728 | 0 | &handler_given, &fdwhandler, |
729 | 0 | &validator_given, &fdwvalidator); |
730 | |
|
731 | 0 | if (handler_given) |
732 | 0 | { |
733 | 0 | repl_val[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler); |
734 | 0 | 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 | 0 | ereport(WARNING, |
741 | 0 | (errmsg("changing the foreign-data wrapper handler can change behavior of existing foreign tables"))); |
742 | 0 | } |
743 | | |
744 | 0 | if (validator_given) |
745 | 0 | { |
746 | 0 | repl_val[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator); |
747 | 0 | 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 | 0 | if (OidIsValid(fdwvalidator)) |
755 | 0 | ereport(WARNING, |
756 | 0 | (errmsg("changing the foreign-data wrapper validator can cause " |
757 | 0 | "the options for dependent objects to become invalid"))); |
758 | 0 | } |
759 | 0 | else |
760 | 0 | { |
761 | | /* |
762 | | * Validator is not changed, but we need it for validating options. |
763 | | */ |
764 | 0 | fdwvalidator = fdwForm->fdwvalidator; |
765 | 0 | } |
766 | | |
767 | | /* |
768 | | * If options specified, validate and update. |
769 | | */ |
770 | 0 | if (stmt->options) |
771 | 0 | { |
772 | | /* Extract the current options */ |
773 | 0 | datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, |
774 | 0 | tp, |
775 | 0 | Anum_pg_foreign_data_wrapper_fdwoptions, |
776 | 0 | &isnull); |
777 | 0 | if (isnull) |
778 | 0 | datum = PointerGetDatum(NULL); |
779 | | |
780 | | /* Transform the options */ |
781 | 0 | datum = transformGenericOptions(ForeignDataWrapperRelationId, |
782 | 0 | datum, |
783 | 0 | stmt->options, |
784 | 0 | fdwvalidator); |
785 | |
|
786 | 0 | if (DatumGetPointer(datum) != NULL) |
787 | 0 | repl_val[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = datum; |
788 | 0 | else |
789 | 0 | repl_null[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true; |
790 | |
|
791 | 0 | repl_repl[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true; |
792 | 0 | } |
793 | | |
794 | | /* Everything looks good - update the tuple */ |
795 | 0 | tp = heap_modify_tuple(tp, RelationGetDescr(rel), |
796 | 0 | repl_val, repl_null, repl_repl); |
797 | |
|
798 | 0 | CatalogTupleUpdate(rel, &tp->t_self, tp); |
799 | |
|
800 | 0 | heap_freetuple(tp); |
801 | |
|
802 | 0 | ObjectAddressSet(myself, ForeignDataWrapperRelationId, fdwId); |
803 | | |
804 | | /* Update function dependencies if we changed them */ |
805 | 0 | if (handler_given || validator_given) |
806 | 0 | { |
807 | 0 | 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 | 0 | deleteDependencyRecordsForClass(ForeignDataWrapperRelationId, |
814 | 0 | fdwId, |
815 | 0 | ProcedureRelationId, |
816 | 0 | DEPENDENCY_NORMAL); |
817 | | |
818 | | /* And build new ones. */ |
819 | |
|
820 | 0 | if (OidIsValid(fdwhandler)) |
821 | 0 | { |
822 | 0 | referenced.classId = ProcedureRelationId; |
823 | 0 | referenced.objectId = fdwhandler; |
824 | 0 | referenced.objectSubId = 0; |
825 | 0 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
826 | 0 | } |
827 | |
|
828 | 0 | if (OidIsValid(fdwvalidator)) |
829 | 0 | { |
830 | 0 | referenced.classId = ProcedureRelationId; |
831 | 0 | referenced.objectId = fdwvalidator; |
832 | 0 | referenced.objectSubId = 0; |
833 | 0 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
834 | 0 | } |
835 | 0 | } |
836 | |
|
837 | 0 | InvokeObjectPostAlterHook(ForeignDataWrapperRelationId, fdwId, 0); |
838 | |
|
839 | 0 | table_close(rel, RowExclusiveLock); |
840 | |
|
841 | 0 | return myself; |
842 | 0 | } |
843 | | |
844 | | |
845 | | /* |
846 | | * Create a foreign server |
847 | | */ |
848 | | ObjectAddress |
849 | | CreateForeignServer(CreateForeignServerStmt *stmt) |
850 | 0 | { |
851 | 0 | Relation rel; |
852 | 0 | Datum srvoptions; |
853 | 0 | Datum values[Natts_pg_foreign_server]; |
854 | 0 | bool nulls[Natts_pg_foreign_server]; |
855 | 0 | HeapTuple tuple; |
856 | 0 | Oid srvId; |
857 | 0 | Oid ownerId; |
858 | 0 | AclResult aclresult; |
859 | 0 | ObjectAddress myself; |
860 | 0 | ObjectAddress referenced; |
861 | 0 | ForeignDataWrapper *fdw; |
862 | |
|
863 | 0 | rel = table_open(ForeignServerRelationId, RowExclusiveLock); |
864 | | |
865 | | /* For now the owner cannot be specified on create. Use effective user ID. */ |
866 | 0 | 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 | 0 | srvId = get_foreign_server_oid(stmt->servername, true); |
873 | 0 | if (OidIsValid(srvId)) |
874 | 0 | { |
875 | 0 | if (stmt->if_not_exists) |
876 | 0 | { |
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 | 0 | ObjectAddressSet(myself, ForeignServerRelationId, srvId); |
882 | 0 | checkMembershipInCurrentExtension(&myself); |
883 | | |
884 | | /* OK to skip */ |
885 | 0 | ereport(NOTICE, |
886 | 0 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
887 | 0 | errmsg("server \"%s\" already exists, skipping", |
888 | 0 | stmt->servername))); |
889 | 0 | table_close(rel, RowExclusiveLock); |
890 | 0 | return InvalidObjectAddress; |
891 | 0 | } |
892 | 0 | else |
893 | 0 | ereport(ERROR, |
894 | 0 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
895 | 0 | errmsg("server \"%s\" already exists", |
896 | 0 | 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 | 0 | fdw = GetForeignDataWrapperByName(stmt->fdwname, false); |
904 | |
|
905 | 0 | aclresult = object_aclcheck(ForeignDataWrapperRelationId, fdw->fdwid, ownerId, ACL_USAGE); |
906 | 0 | if (aclresult != ACLCHECK_OK) |
907 | 0 | aclcheck_error(aclresult, OBJECT_FDW, fdw->fdwname); |
908 | | |
909 | | /* |
910 | | * Insert tuple into pg_foreign_server. |
911 | | */ |
912 | 0 | memset(values, 0, sizeof(values)); |
913 | 0 | memset(nulls, false, sizeof(nulls)); |
914 | |
|
915 | 0 | srvId = GetNewOidWithIndex(rel, ForeignServerOidIndexId, |
916 | 0 | Anum_pg_foreign_server_oid); |
917 | 0 | values[Anum_pg_foreign_server_oid - 1] = ObjectIdGetDatum(srvId); |
918 | 0 | values[Anum_pg_foreign_server_srvname - 1] = |
919 | 0 | DirectFunctionCall1(namein, CStringGetDatum(stmt->servername)); |
920 | 0 | values[Anum_pg_foreign_server_srvowner - 1] = ObjectIdGetDatum(ownerId); |
921 | 0 | values[Anum_pg_foreign_server_srvfdw - 1] = ObjectIdGetDatum(fdw->fdwid); |
922 | | |
923 | | /* Add server type if supplied */ |
924 | 0 | if (stmt->servertype) |
925 | 0 | values[Anum_pg_foreign_server_srvtype - 1] = |
926 | 0 | CStringGetTextDatum(stmt->servertype); |
927 | 0 | else |
928 | 0 | nulls[Anum_pg_foreign_server_srvtype - 1] = true; |
929 | | |
930 | | /* Add server version if supplied */ |
931 | 0 | if (stmt->version) |
932 | 0 | values[Anum_pg_foreign_server_srvversion - 1] = |
933 | 0 | CStringGetTextDatum(stmt->version); |
934 | 0 | else |
935 | 0 | nulls[Anum_pg_foreign_server_srvversion - 1] = true; |
936 | | |
937 | | /* Start with a blank acl */ |
938 | 0 | nulls[Anum_pg_foreign_server_srvacl - 1] = true; |
939 | | |
940 | | /* Add server options */ |
941 | 0 | srvoptions = transformGenericOptions(ForeignServerRelationId, |
942 | 0 | PointerGetDatum(NULL), |
943 | 0 | stmt->options, |
944 | 0 | fdw->fdwvalidator); |
945 | |
|
946 | 0 | if (DatumGetPointer(srvoptions) != NULL) |
947 | 0 | values[Anum_pg_foreign_server_srvoptions - 1] = srvoptions; |
948 | 0 | else |
949 | 0 | nulls[Anum_pg_foreign_server_srvoptions - 1] = true; |
950 | |
|
951 | 0 | tuple = heap_form_tuple(rel->rd_att, values, nulls); |
952 | |
|
953 | 0 | CatalogTupleInsert(rel, tuple); |
954 | |
|
955 | 0 | heap_freetuple(tuple); |
956 | | |
957 | | /* record dependencies */ |
958 | 0 | myself.classId = ForeignServerRelationId; |
959 | 0 | myself.objectId = srvId; |
960 | 0 | myself.objectSubId = 0; |
961 | |
|
962 | 0 | referenced.classId = ForeignDataWrapperRelationId; |
963 | 0 | referenced.objectId = fdw->fdwid; |
964 | 0 | referenced.objectSubId = 0; |
965 | 0 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
966 | |
|
967 | 0 | recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId); |
968 | | |
969 | | /* dependency on extension */ |
970 | 0 | recordDependencyOnCurrentExtension(&myself, false); |
971 | | |
972 | | /* Post creation hook for new foreign server */ |
973 | 0 | InvokeObjectPostCreateHook(ForeignServerRelationId, srvId, 0); |
974 | |
|
975 | 0 | table_close(rel, RowExclusiveLock); |
976 | |
|
977 | 0 | return myself; |
978 | 0 | } |
979 | | |
980 | | |
981 | | /* |
982 | | * Alter foreign server |
983 | | */ |
984 | | ObjectAddress |
985 | | AlterForeignServer(AlterForeignServerStmt *stmt) |
986 | 0 | { |
987 | 0 | Relation rel; |
988 | 0 | HeapTuple tp; |
989 | 0 | Datum repl_val[Natts_pg_foreign_server]; |
990 | 0 | bool repl_null[Natts_pg_foreign_server]; |
991 | 0 | bool repl_repl[Natts_pg_foreign_server]; |
992 | 0 | Oid srvId; |
993 | 0 | Form_pg_foreign_server srvForm; |
994 | 0 | ObjectAddress address; |
995 | |
|
996 | 0 | rel = table_open(ForeignServerRelationId, RowExclusiveLock); |
997 | |
|
998 | 0 | tp = SearchSysCacheCopy1(FOREIGNSERVERNAME, |
999 | 0 | CStringGetDatum(stmt->servername)); |
1000 | |
|
1001 | 0 | if (!HeapTupleIsValid(tp)) |
1002 | 0 | ereport(ERROR, |
1003 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
1004 | 0 | errmsg("server \"%s\" does not exist", stmt->servername))); |
1005 | | |
1006 | 0 | srvForm = (Form_pg_foreign_server) GETSTRUCT(tp); |
1007 | 0 | srvId = srvForm->oid; |
1008 | | |
1009 | | /* |
1010 | | * Only owner or a superuser can ALTER a SERVER. |
1011 | | */ |
1012 | 0 | if (!object_ownercheck(ForeignServerRelationId, srvId, GetUserId())) |
1013 | 0 | aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FOREIGN_SERVER, |
1014 | 0 | stmt->servername); |
1015 | |
|
1016 | 0 | memset(repl_val, 0, sizeof(repl_val)); |
1017 | 0 | memset(repl_null, false, sizeof(repl_null)); |
1018 | 0 | memset(repl_repl, false, sizeof(repl_repl)); |
1019 | |
|
1020 | 0 | if (stmt->has_version) |
1021 | 0 | { |
1022 | | /* |
1023 | | * Change the server VERSION string. |
1024 | | */ |
1025 | 0 | if (stmt->version) |
1026 | 0 | repl_val[Anum_pg_foreign_server_srvversion - 1] = |
1027 | 0 | CStringGetTextDatum(stmt->version); |
1028 | 0 | else |
1029 | 0 | repl_null[Anum_pg_foreign_server_srvversion - 1] = true; |
1030 | |
|
1031 | 0 | repl_repl[Anum_pg_foreign_server_srvversion - 1] = true; |
1032 | 0 | } |
1033 | |
|
1034 | 0 | if (stmt->options) |
1035 | 0 | { |
1036 | 0 | ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw); |
1037 | 0 | Datum datum; |
1038 | 0 | bool isnull; |
1039 | | |
1040 | | /* Extract the current srvoptions */ |
1041 | 0 | datum = SysCacheGetAttr(FOREIGNSERVEROID, |
1042 | 0 | tp, |
1043 | 0 | Anum_pg_foreign_server_srvoptions, |
1044 | 0 | &isnull); |
1045 | 0 | if (isnull) |
1046 | 0 | datum = PointerGetDatum(NULL); |
1047 | | |
1048 | | /* Prepare the options array */ |
1049 | 0 | datum = transformGenericOptions(ForeignServerRelationId, |
1050 | 0 | datum, |
1051 | 0 | stmt->options, |
1052 | 0 | fdw->fdwvalidator); |
1053 | |
|
1054 | 0 | if (DatumGetPointer(datum) != NULL) |
1055 | 0 | repl_val[Anum_pg_foreign_server_srvoptions - 1] = datum; |
1056 | 0 | else |
1057 | 0 | repl_null[Anum_pg_foreign_server_srvoptions - 1] = true; |
1058 | |
|
1059 | 0 | repl_repl[Anum_pg_foreign_server_srvoptions - 1] = true; |
1060 | 0 | } |
1061 | | |
1062 | | /* Everything looks good - update the tuple */ |
1063 | 0 | tp = heap_modify_tuple(tp, RelationGetDescr(rel), |
1064 | 0 | repl_val, repl_null, repl_repl); |
1065 | |
|
1066 | 0 | CatalogTupleUpdate(rel, &tp->t_self, tp); |
1067 | |
|
1068 | 0 | InvokeObjectPostAlterHook(ForeignServerRelationId, srvId, 0); |
1069 | |
|
1070 | 0 | ObjectAddressSet(address, ForeignServerRelationId, srvId); |
1071 | |
|
1072 | 0 | heap_freetuple(tp); |
1073 | |
|
1074 | 0 | table_close(rel, RowExclusiveLock); |
1075 | |
|
1076 | 0 | return address; |
1077 | 0 | } |
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 | | user_mapping_ddl_aclcheck(Oid umuserid, Oid serverid, const char *servername) |
1087 | 0 | { |
1088 | 0 | Oid curuserid = GetUserId(); |
1089 | |
|
1090 | 0 | if (!object_ownercheck(ForeignServerRelationId, serverid, curuserid)) |
1091 | 0 | { |
1092 | 0 | if (umuserid == curuserid) |
1093 | 0 | { |
1094 | 0 | AclResult aclresult; |
1095 | |
|
1096 | 0 | aclresult = object_aclcheck(ForeignServerRelationId, serverid, curuserid, ACL_USAGE); |
1097 | 0 | if (aclresult != ACLCHECK_OK) |
1098 | 0 | aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, servername); |
1099 | 0 | } |
1100 | 0 | else |
1101 | 0 | aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FOREIGN_SERVER, |
1102 | 0 | servername); |
1103 | 0 | } |
1104 | 0 | } |
1105 | | |
1106 | | |
1107 | | /* |
1108 | | * Create user mapping |
1109 | | */ |
1110 | | ObjectAddress |
1111 | | CreateUserMapping(CreateUserMappingStmt *stmt) |
1112 | 0 | { |
1113 | 0 | Relation rel; |
1114 | 0 | Datum useoptions; |
1115 | 0 | Datum values[Natts_pg_user_mapping]; |
1116 | 0 | bool nulls[Natts_pg_user_mapping]; |
1117 | 0 | HeapTuple tuple; |
1118 | 0 | Oid useId; |
1119 | 0 | Oid umId; |
1120 | 0 | ObjectAddress myself; |
1121 | 0 | ObjectAddress referenced; |
1122 | 0 | ForeignServer *srv; |
1123 | 0 | ForeignDataWrapper *fdw; |
1124 | 0 | RoleSpec *role = (RoleSpec *) stmt->user; |
1125 | |
|
1126 | 0 | rel = table_open(UserMappingRelationId, RowExclusiveLock); |
1127 | |
|
1128 | 0 | if (role->roletype == ROLESPEC_PUBLIC) |
1129 | 0 | useId = ACL_ID_PUBLIC; |
1130 | 0 | else |
1131 | 0 | useId = get_rolespec_oid(stmt->user, false); |
1132 | | |
1133 | | /* Check that the server exists. */ |
1134 | 0 | srv = GetForeignServerByName(stmt->servername, false); |
1135 | |
|
1136 | 0 | user_mapping_ddl_aclcheck(useId, srv->serverid, stmt->servername); |
1137 | | |
1138 | | /* |
1139 | | * Check that the user mapping is unique within server. |
1140 | | */ |
1141 | 0 | umId = GetSysCacheOid2(USERMAPPINGUSERSERVER, Anum_pg_user_mapping_oid, |
1142 | 0 | ObjectIdGetDatum(useId), |
1143 | 0 | ObjectIdGetDatum(srv->serverid)); |
1144 | |
|
1145 | 0 | if (OidIsValid(umId)) |
1146 | 0 | { |
1147 | 0 | if (stmt->if_not_exists) |
1148 | 0 | { |
1149 | | /* |
1150 | | * Since user mappings aren't members of extensions (see comments |
1151 | | * below), no need for checkMembershipInCurrentExtension here. |
1152 | | */ |
1153 | 0 | ereport(NOTICE, |
1154 | 0 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
1155 | 0 | errmsg("user mapping for \"%s\" already exists for server \"%s\", skipping", |
1156 | 0 | MappingUserName(useId), |
1157 | 0 | stmt->servername))); |
1158 | | |
1159 | 0 | table_close(rel, RowExclusiveLock); |
1160 | 0 | return InvalidObjectAddress; |
1161 | 0 | } |
1162 | 0 | else |
1163 | 0 | ereport(ERROR, |
1164 | 0 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
1165 | 0 | errmsg("user mapping for \"%s\" already exists for server \"%s\"", |
1166 | 0 | MappingUserName(useId), |
1167 | 0 | stmt->servername))); |
1168 | 0 | } |
1169 | | |
1170 | 0 | fdw = GetForeignDataWrapper(srv->fdwid); |
1171 | | |
1172 | | /* |
1173 | | * Insert tuple into pg_user_mapping. |
1174 | | */ |
1175 | 0 | memset(values, 0, sizeof(values)); |
1176 | 0 | memset(nulls, false, sizeof(nulls)); |
1177 | |
|
1178 | 0 | umId = GetNewOidWithIndex(rel, UserMappingOidIndexId, |
1179 | 0 | Anum_pg_user_mapping_oid); |
1180 | 0 | values[Anum_pg_user_mapping_oid - 1] = ObjectIdGetDatum(umId); |
1181 | 0 | values[Anum_pg_user_mapping_umuser - 1] = ObjectIdGetDatum(useId); |
1182 | 0 | values[Anum_pg_user_mapping_umserver - 1] = ObjectIdGetDatum(srv->serverid); |
1183 | | |
1184 | | /* Add user options */ |
1185 | 0 | useoptions = transformGenericOptions(UserMappingRelationId, |
1186 | 0 | PointerGetDatum(NULL), |
1187 | 0 | stmt->options, |
1188 | 0 | fdw->fdwvalidator); |
1189 | |
|
1190 | 0 | if (DatumGetPointer(useoptions) != NULL) |
1191 | 0 | values[Anum_pg_user_mapping_umoptions - 1] = useoptions; |
1192 | 0 | else |
1193 | 0 | nulls[Anum_pg_user_mapping_umoptions - 1] = true; |
1194 | |
|
1195 | 0 | tuple = heap_form_tuple(rel->rd_att, values, nulls); |
1196 | |
|
1197 | 0 | CatalogTupleInsert(rel, tuple); |
1198 | |
|
1199 | 0 | heap_freetuple(tuple); |
1200 | | |
1201 | | /* Add dependency on the server */ |
1202 | 0 | myself.classId = UserMappingRelationId; |
1203 | 0 | myself.objectId = umId; |
1204 | 0 | myself.objectSubId = 0; |
1205 | |
|
1206 | 0 | referenced.classId = ForeignServerRelationId; |
1207 | 0 | referenced.objectId = srv->serverid; |
1208 | 0 | referenced.objectSubId = 0; |
1209 | 0 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
1210 | |
|
1211 | 0 | if (OidIsValid(useId)) |
1212 | 0 | { |
1213 | | /* Record the mapped user dependency */ |
1214 | 0 | recordDependencyOnOwner(UserMappingRelationId, umId, useId); |
1215 | 0 | } |
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 | 0 | InvokeObjectPostCreateHook(UserMappingRelationId, umId, 0); |
1226 | |
|
1227 | 0 | table_close(rel, RowExclusiveLock); |
1228 | |
|
1229 | 0 | return myself; |
1230 | 0 | } |
1231 | | |
1232 | | |
1233 | | /* |
1234 | | * Alter user mapping |
1235 | | */ |
1236 | | ObjectAddress |
1237 | | AlterUserMapping(AlterUserMappingStmt *stmt) |
1238 | 0 | { |
1239 | 0 | Relation rel; |
1240 | 0 | HeapTuple tp; |
1241 | 0 | Datum repl_val[Natts_pg_user_mapping]; |
1242 | 0 | bool repl_null[Natts_pg_user_mapping]; |
1243 | 0 | bool repl_repl[Natts_pg_user_mapping]; |
1244 | 0 | Oid useId; |
1245 | 0 | Oid umId; |
1246 | 0 | ForeignServer *srv; |
1247 | 0 | ObjectAddress address; |
1248 | 0 | RoleSpec *role = (RoleSpec *) stmt->user; |
1249 | |
|
1250 | 0 | rel = table_open(UserMappingRelationId, RowExclusiveLock); |
1251 | |
|
1252 | 0 | if (role->roletype == ROLESPEC_PUBLIC) |
1253 | 0 | useId = ACL_ID_PUBLIC; |
1254 | 0 | else |
1255 | 0 | useId = get_rolespec_oid(stmt->user, false); |
1256 | |
|
1257 | 0 | srv = GetForeignServerByName(stmt->servername, false); |
1258 | |
|
1259 | 0 | umId = GetSysCacheOid2(USERMAPPINGUSERSERVER, Anum_pg_user_mapping_oid, |
1260 | 0 | ObjectIdGetDatum(useId), |
1261 | 0 | ObjectIdGetDatum(srv->serverid)); |
1262 | 0 | if (!OidIsValid(umId)) |
1263 | 0 | ereport(ERROR, |
1264 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
1265 | 0 | errmsg("user mapping for \"%s\" does not exist for server \"%s\"", |
1266 | 0 | MappingUserName(useId), stmt->servername))); |
1267 | | |
1268 | 0 | user_mapping_ddl_aclcheck(useId, srv->serverid, stmt->servername); |
1269 | |
|
1270 | 0 | tp = SearchSysCacheCopy1(USERMAPPINGOID, ObjectIdGetDatum(umId)); |
1271 | |
|
1272 | 0 | if (!HeapTupleIsValid(tp)) |
1273 | 0 | elog(ERROR, "cache lookup failed for user mapping %u", umId); |
1274 | | |
1275 | 0 | memset(repl_val, 0, sizeof(repl_val)); |
1276 | 0 | memset(repl_null, false, sizeof(repl_null)); |
1277 | 0 | memset(repl_repl, false, sizeof(repl_repl)); |
1278 | |
|
1279 | 0 | if (stmt->options) |
1280 | 0 | { |
1281 | 0 | ForeignDataWrapper *fdw; |
1282 | 0 | Datum datum; |
1283 | 0 | bool isnull; |
1284 | | |
1285 | | /* |
1286 | | * Process the options. |
1287 | | */ |
1288 | |
|
1289 | 0 | fdw = GetForeignDataWrapper(srv->fdwid); |
1290 | |
|
1291 | 0 | datum = SysCacheGetAttr(USERMAPPINGUSERSERVER, |
1292 | 0 | tp, |
1293 | 0 | Anum_pg_user_mapping_umoptions, |
1294 | 0 | &isnull); |
1295 | 0 | if (isnull) |
1296 | 0 | datum = PointerGetDatum(NULL); |
1297 | | |
1298 | | /* Prepare the options array */ |
1299 | 0 | datum = transformGenericOptions(UserMappingRelationId, |
1300 | 0 | datum, |
1301 | 0 | stmt->options, |
1302 | 0 | fdw->fdwvalidator); |
1303 | |
|
1304 | 0 | if (DatumGetPointer(datum) != NULL) |
1305 | 0 | repl_val[Anum_pg_user_mapping_umoptions - 1] = datum; |
1306 | 0 | else |
1307 | 0 | repl_null[Anum_pg_user_mapping_umoptions - 1] = true; |
1308 | |
|
1309 | 0 | repl_repl[Anum_pg_user_mapping_umoptions - 1] = true; |
1310 | 0 | } |
1311 | | |
1312 | | /* Everything looks good - update the tuple */ |
1313 | 0 | tp = heap_modify_tuple(tp, RelationGetDescr(rel), |
1314 | 0 | repl_val, repl_null, repl_repl); |
1315 | |
|
1316 | 0 | CatalogTupleUpdate(rel, &tp->t_self, tp); |
1317 | |
|
1318 | 0 | InvokeObjectPostAlterHook(UserMappingRelationId, |
1319 | 0 | umId, 0); |
1320 | |
|
1321 | 0 | ObjectAddressSet(address, UserMappingRelationId, umId); |
1322 | |
|
1323 | 0 | heap_freetuple(tp); |
1324 | |
|
1325 | 0 | table_close(rel, RowExclusiveLock); |
1326 | |
|
1327 | 0 | return address; |
1328 | 0 | } |
1329 | | |
1330 | | |
1331 | | /* |
1332 | | * Drop user mapping |
1333 | | */ |
1334 | | Oid |
1335 | | RemoveUserMapping(DropUserMappingStmt *stmt) |
1336 | | { |
1337 | | ObjectAddress object; |
1338 | | Oid useId; |
1339 | | Oid umId; |
1340 | | ForeignServer *srv; |
1341 | | RoleSpec *role = (RoleSpec *) stmt->user; |
1342 | | |
1343 | | if (role->roletype == ROLESPEC_PUBLIC) |
1344 | | useId = ACL_ID_PUBLIC; |
1345 | | else |
1346 | | { |
1347 | | useId = get_rolespec_oid(stmt->user, stmt->missing_ok); |
1348 | | if (!OidIsValid(useId)) |
1349 | | { |
1350 | | /* |
1351 | | * IF EXISTS specified, role not found and not public. Notice this |
1352 | | * and leave. |
1353 | | */ |
1354 | | elog(NOTICE, "role \"%s\" does not exist, skipping", |
1355 | | role->rolename); |
1356 | | return InvalidOid; |
1357 | | } |
1358 | | } |
1359 | | |
1360 | | srv = GetForeignServerByName(stmt->servername, true); |
1361 | | |
1362 | | if (!srv) |
1363 | | { |
1364 | | if (!stmt->missing_ok) |
1365 | | ereport(ERROR, |
1366 | | (errcode(ERRCODE_UNDEFINED_OBJECT), |
1367 | | errmsg("server \"%s\" does not exist", |
1368 | | stmt->servername))); |
1369 | | /* IF EXISTS, just note it */ |
1370 | | ereport(NOTICE, |
1371 | | (errmsg("server \"%s\" does not exist, skipping", |
1372 | | stmt->servername))); |
1373 | | return InvalidOid; |
1374 | | } |
1375 | | |
1376 | | umId = GetSysCacheOid2(USERMAPPINGUSERSERVER, Anum_pg_user_mapping_oid, |
1377 | | ObjectIdGetDatum(useId), |
1378 | | ObjectIdGetDatum(srv->serverid)); |
1379 | | |
1380 | | if (!OidIsValid(umId)) |
1381 | | { |
1382 | | if (!stmt->missing_ok) |
1383 | | 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 | | ereport(NOTICE, |
1390 | | (errmsg("user mapping for \"%s\" does not exist for server \"%s\", skipping", |
1391 | | MappingUserName(useId), stmt->servername))); |
1392 | | return InvalidOid; |
1393 | | } |
1394 | | |
1395 | | user_mapping_ddl_aclcheck(useId, srv->serverid, srv->servername); |
1396 | | |
1397 | | /* |
1398 | | * Do the deletion |
1399 | | */ |
1400 | | object.classId = UserMappingRelationId; |
1401 | | object.objectId = umId; |
1402 | | object.objectSubId = 0; |
1403 | | |
1404 | | performDeletion(&object, DROP_CASCADE, 0); |
1405 | | |
1406 | | return umId; |
1407 | | } |
1408 | | |
1409 | | |
1410 | | /* |
1411 | | * Create a foreign table |
1412 | | * call after DefineRelation(). |
1413 | | */ |
1414 | | void |
1415 | | CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid) |
1416 | 0 | { |
1417 | 0 | Relation ftrel; |
1418 | 0 | Datum ftoptions; |
1419 | 0 | Datum values[Natts_pg_foreign_table]; |
1420 | 0 | bool nulls[Natts_pg_foreign_table]; |
1421 | 0 | HeapTuple tuple; |
1422 | 0 | AclResult aclresult; |
1423 | 0 | ObjectAddress myself; |
1424 | 0 | ObjectAddress referenced; |
1425 | 0 | Oid ownerId; |
1426 | 0 | ForeignDataWrapper *fdw; |
1427 | 0 | 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 | 0 | CommandCounterIncrement(); |
1434 | |
|
1435 | 0 | ftrel = table_open(ForeignTableRelationId, RowExclusiveLock); |
1436 | | |
1437 | | /* |
1438 | | * For now the owner cannot be specified on create. Use effective user ID. |
1439 | | */ |
1440 | 0 | 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 | 0 | server = GetForeignServerByName(stmt->servername, false); |
1447 | 0 | aclresult = object_aclcheck(ForeignServerRelationId, server->serverid, ownerId, ACL_USAGE); |
1448 | 0 | if (aclresult != ACLCHECK_OK) |
1449 | 0 | aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, server->servername); |
1450 | |
|
1451 | 0 | fdw = GetForeignDataWrapper(server->fdwid); |
1452 | | |
1453 | | /* |
1454 | | * Insert tuple into pg_foreign_table. |
1455 | | */ |
1456 | 0 | memset(values, 0, sizeof(values)); |
1457 | 0 | memset(nulls, false, sizeof(nulls)); |
1458 | |
|
1459 | 0 | values[Anum_pg_foreign_table_ftrelid - 1] = ObjectIdGetDatum(relid); |
1460 | 0 | values[Anum_pg_foreign_table_ftserver - 1] = ObjectIdGetDatum(server->serverid); |
1461 | | /* Add table generic options */ |
1462 | 0 | ftoptions = transformGenericOptions(ForeignTableRelationId, |
1463 | 0 | PointerGetDatum(NULL), |
1464 | 0 | stmt->options, |
1465 | 0 | fdw->fdwvalidator); |
1466 | |
|
1467 | 0 | if (DatumGetPointer(ftoptions) != NULL) |
1468 | 0 | values[Anum_pg_foreign_table_ftoptions - 1] = ftoptions; |
1469 | 0 | else |
1470 | 0 | nulls[Anum_pg_foreign_table_ftoptions - 1] = true; |
1471 | |
|
1472 | 0 | tuple = heap_form_tuple(ftrel->rd_att, values, nulls); |
1473 | |
|
1474 | 0 | CatalogTupleInsert(ftrel, tuple); |
1475 | |
|
1476 | 0 | heap_freetuple(tuple); |
1477 | | |
1478 | | /* Add pg_class dependency on the server */ |
1479 | 0 | myself.classId = RelationRelationId; |
1480 | 0 | myself.objectId = relid; |
1481 | 0 | myself.objectSubId = 0; |
1482 | |
|
1483 | 0 | referenced.classId = ForeignServerRelationId; |
1484 | 0 | referenced.objectId = server->serverid; |
1485 | 0 | referenced.objectSubId = 0; |
1486 | 0 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
1487 | |
|
1488 | 0 | table_close(ftrel, RowExclusiveLock); |
1489 | 0 | } |
1490 | | |
1491 | | /* |
1492 | | * Import a foreign schema |
1493 | | */ |
1494 | | void |
1495 | | ImportForeignSchema(ImportForeignSchemaStmt *stmt) |
1496 | 0 | { |
1497 | 0 | ForeignServer *server; |
1498 | 0 | ForeignDataWrapper *fdw; |
1499 | 0 | FdwRoutine *fdw_routine; |
1500 | 0 | AclResult aclresult; |
1501 | 0 | List *cmd_list; |
1502 | 0 | ListCell *lc; |
1503 | | |
1504 | | /* Check that the foreign server exists and that we have USAGE on it */ |
1505 | 0 | server = GetForeignServerByName(stmt->server_name, false); |
1506 | 0 | aclresult = object_aclcheck(ForeignServerRelationId, server->serverid, GetUserId(), ACL_USAGE); |
1507 | 0 | 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 | 0 | (void) LookupCreationNamespace(stmt->local_schema); |
1512 | | |
1513 | | /* Get the FDW and check it supports IMPORT */ |
1514 | 0 | fdw = GetForeignDataWrapper(server->fdwid); |
1515 | 0 | if (!OidIsValid(fdw->fdwhandler)) |
1516 | 0 | ereport(ERROR, |
1517 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
1518 | 0 | errmsg("foreign-data wrapper \"%s\" has no handler", |
1519 | 0 | fdw->fdwname))); |
1520 | 0 | fdw_routine = GetFdwRoutine(fdw->fdwhandler); |
1521 | 0 | if (fdw_routine->ImportForeignSchema == NULL) |
1522 | 0 | ereport(ERROR, |
1523 | 0 | (errcode(ERRCODE_FDW_NO_SCHEMAS), |
1524 | 0 | errmsg("foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA", |
1525 | 0 | 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 | 0 | { |
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 | 0 | { |
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 | 0 | "foreign-data wrapper \"%s\" returned incorrect statement type %d", |
1572 | 0 | 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 | 0 | 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 | | import_error_callback(void *arg) |
1613 | 0 | { |
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 | 0 | { |
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 | } |