1 /*-------------------------------------------------------------------------
4 * foreign-data wrapper/server creation/manipulation commands
6 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
10 * src/backend/commands/foreigncmds.c
12 *-------------------------------------------------------------------------
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"
47 } import_error_callback_arg
;
49 /* Internal functions */
50 static void import_error_callback(void *arg
);
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
58 * Returns the array in the form of a Datum, or PointerGetDatum(NULL)
59 * if the list is empty.
61 * Note: The array is usually stored to database without further
62 * processing, hence any validation should be done before this
66 optionListToArray(List
*options
)
68 ArrayBuildState
*astate
= NULL
;
71 foreach(cell
, options
)
73 DefElem
*def
= lfirst(cell
);
78 value
= defGetString(def
);
79 len
= VARHDRSZ
+ strlen(def
->defname
) + 1 + strlen(value
);
82 sprintf(VARDATA(t
), "%s=%s", def
->defname
, value
);
84 astate
= accumArrayResult(astate
, PointerGetDatum(t
),
86 CurrentMemoryContext
);
90 return makeArrayResult(astate
, CurrentMemoryContext
);
92 return PointerGetDatum(NULL
);
97 * Transform a list of DefElem into text array format. This is substantially
98 * the same thing as optionListToArray(), except we recognize SET/ADD/DROP
99 * actions for modifying an existing list of options, which is passed in
100 * Datum form as oldOptions. Also, if fdwvalidator isn't InvalidOid
101 * it specifies a validator function to call on the result.
103 * Returns the array in the form of a Datum, or PointerGetDatum(NULL)
104 * if the list is empty.
106 * This is used by CREATE/ALTER of FOREIGN DATA WRAPPER/SERVER/USER MAPPING/
110 transformGenericOptions(Oid catalogId
,
115 List
*resultOptions
= untransformRelOptions(oldOptions
);
119 foreach(optcell
, options
)
121 DefElem
*od
= lfirst(optcell
);
125 * Find the element in resultOptions. We need this for validation in
128 foreach(cell
, resultOptions
)
130 DefElem
*def
= lfirst(cell
);
132 if (strcmp(def
->defname
, od
->defname
) == 0)
137 * It is possible to perform multiple SET/DROP actions on the same
138 * option. The standard permits this, as long as the options to be
139 * added are unique. Note that an unspecified action is taken to be
142 switch (od
->defaction
)
147 (errcode(ERRCODE_UNDEFINED_OBJECT
),
148 errmsg("option \"%s\" not found",
150 resultOptions
= list_delete_cell(resultOptions
, cell
);
156 (errcode(ERRCODE_UNDEFINED_OBJECT
),
157 errmsg("option \"%s\" not found",
166 (errcode(ERRCODE_DUPLICATE_OBJECT
),
167 errmsg("option \"%s\" provided more than once",
169 resultOptions
= lappend(resultOptions
, od
);
173 elog(ERROR
, "unrecognized action %d on option \"%s\"",
174 (int) od
->defaction
, od
->defname
);
179 result
= optionListToArray(resultOptions
);
181 if (OidIsValid(fdwvalidator
))
183 Datum valarg
= result
;
186 * Pass a null options list as an empty array, so that validators
187 * don't have to be declared non-strict to handle the case.
189 if (DatumGetPointer(valarg
) == NULL
)
190 valarg
= PointerGetDatum(construct_empty_array(TEXTOID
));
191 OidFunctionCall2(fdwvalidator
, valarg
, ObjectIdGetDatum(catalogId
));
199 * Internal workhorse for changing a data wrapper's owner.
201 * Allow this only for superusers; also the new owner must be a
205 AlterForeignDataWrapperOwner_internal(Relation rel
, HeapTuple tup
, Oid newOwnerId
)
207 Form_pg_foreign_data_wrapper form
;
208 Datum repl_val
[Natts_pg_foreign_data_wrapper
];
209 bool repl_null
[Natts_pg_foreign_data_wrapper
];
210 bool repl_repl
[Natts_pg_foreign_data_wrapper
];
215 form
= (Form_pg_foreign_data_wrapper
) GETSTRUCT(tup
);
217 /* Must be a superuser to change a FDW owner */
220 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
221 errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
222 NameStr(form
->fdwname
)),
223 errhint("Must be superuser to change owner of a foreign-data wrapper.")));
225 /* New owner must also be a superuser */
226 if (!superuser_arg(newOwnerId
))
228 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
229 errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
230 NameStr(form
->fdwname
)),
231 errhint("The owner of a foreign-data wrapper must be a superuser.")));
233 if (form
->fdwowner
!= newOwnerId
)
235 memset(repl_null
, false, sizeof(repl_null
));
236 memset(repl_repl
, false, sizeof(repl_repl
));
238 repl_repl
[Anum_pg_foreign_data_wrapper_fdwowner
- 1] = true;
239 repl_val
[Anum_pg_foreign_data_wrapper_fdwowner
- 1] = ObjectIdGetDatum(newOwnerId
);
241 aclDatum
= heap_getattr(tup
,
242 Anum_pg_foreign_data_wrapper_fdwacl
,
243 RelationGetDescr(rel
),
245 /* Null ACLs do not require changes */
248 newAcl
= aclnewowner(DatumGetAclP(aclDatum
),
249 form
->fdwowner
, newOwnerId
);
250 repl_repl
[Anum_pg_foreign_data_wrapper_fdwacl
- 1] = true;
251 repl_val
[Anum_pg_foreign_data_wrapper_fdwacl
- 1] = PointerGetDatum(newAcl
);
254 tup
= heap_modify_tuple(tup
, RelationGetDescr(rel
), repl_val
, repl_null
,
257 CatalogTupleUpdate(rel
, &tup
->t_self
, tup
);
259 /* Update owner dependency reference */
260 changeDependencyOnOwner(ForeignDataWrapperRelationId
,
265 InvokeObjectPostAlterHook(ForeignDataWrapperRelationId
,
270 * Change foreign-data wrapper owner -- by name
272 * Note restrictions in the "_internal" function, above.
275 AlterForeignDataWrapperOwner(const char *name
, Oid newOwnerId
)
280 ObjectAddress address
;
281 Form_pg_foreign_data_wrapper form
;
284 rel
= table_open(ForeignDataWrapperRelationId
, RowExclusiveLock
);
286 tup
= SearchSysCacheCopy1(FOREIGNDATAWRAPPERNAME
, CStringGetDatum(name
));
288 if (!HeapTupleIsValid(tup
))
290 (errcode(ERRCODE_UNDEFINED_OBJECT
),
291 errmsg("foreign-data wrapper \"%s\" does not exist", name
)));
293 form
= (Form_pg_foreign_data_wrapper
) GETSTRUCT(tup
);
296 AlterForeignDataWrapperOwner_internal(rel
, tup
, newOwnerId
);
298 ObjectAddressSet(address
, ForeignDataWrapperRelationId
, fdwId
);
302 table_close(rel
, RowExclusiveLock
);
308 * Change foreign-data wrapper owner -- by OID
310 * Note restrictions in the "_internal" function, above.
313 AlterForeignDataWrapperOwner_oid(Oid fwdId
, Oid newOwnerId
)
318 rel
= table_open(ForeignDataWrapperRelationId
, RowExclusiveLock
);
320 tup
= SearchSysCacheCopy1(FOREIGNDATAWRAPPEROID
, ObjectIdGetDatum(fwdId
));
322 if (!HeapTupleIsValid(tup
))
324 (errcode(ERRCODE_UNDEFINED_OBJECT
),
325 errmsg("foreign-data wrapper with OID %u does not exist", fwdId
)));
327 AlterForeignDataWrapperOwner_internal(rel
, tup
, newOwnerId
);
331 table_close(rel
, RowExclusiveLock
);
335 * Internal workhorse for changing a foreign server's owner
338 AlterForeignServerOwner_internal(Relation rel
, HeapTuple tup
, Oid newOwnerId
)
340 Form_pg_foreign_server form
;
341 Datum repl_val
[Natts_pg_foreign_server
];
342 bool repl_null
[Natts_pg_foreign_server
];
343 bool repl_repl
[Natts_pg_foreign_server
];
348 form
= (Form_pg_foreign_server
) GETSTRUCT(tup
);
350 if (form
->srvowner
!= newOwnerId
)
352 /* Superusers can always do it */
361 if (!object_ownercheck(ForeignServerRelationId
, srvId
, GetUserId()))
362 aclcheck_error(ACLCHECK_NOT_OWNER
, OBJECT_FOREIGN_SERVER
,
363 NameStr(form
->srvname
));
365 /* Must be able to become new owner */
366 check_is_member_of_role(GetUserId(), newOwnerId
);
368 /* New owner must have USAGE privilege on foreign-data wrapper */
369 aclresult
= pg_foreign_data_wrapper_aclcheck(form
->srvfdw
, newOwnerId
, ACL_USAGE
);
370 if (aclresult
!= ACLCHECK_OK
)
372 ForeignDataWrapper
*fdw
= GetForeignDataWrapper(form
->srvfdw
);
374 aclcheck_error(aclresult
, OBJECT_FDW
, fdw
->fdwname
);
378 memset(repl_null
, false, sizeof(repl_null
));
379 memset(repl_repl
, false, sizeof(repl_repl
));
381 repl_repl
[Anum_pg_foreign_server_srvowner
- 1] = true;
382 repl_val
[Anum_pg_foreign_server_srvowner
- 1] = ObjectIdGetDatum(newOwnerId
);
384 aclDatum
= heap_getattr(tup
,
385 Anum_pg_foreign_server_srvacl
,
386 RelationGetDescr(rel
),
388 /* Null ACLs do not require changes */
391 newAcl
= aclnewowner(DatumGetAclP(aclDatum
),
392 form
->srvowner
, newOwnerId
);
393 repl_repl
[Anum_pg_foreign_server_srvacl
- 1] = true;
394 repl_val
[Anum_pg_foreign_server_srvacl
- 1] = PointerGetDatum(newAcl
);
397 tup
= heap_modify_tuple(tup
, RelationGetDescr(rel
), repl_val
, repl_null
,
400 CatalogTupleUpdate(rel
, &tup
->t_self
, tup
);
402 /* Update owner dependency reference */
403 changeDependencyOnOwner(ForeignServerRelationId
, form
->oid
,
407 InvokeObjectPostAlterHook(ForeignServerRelationId
,
412 * Change foreign server owner -- by name
415 AlterForeignServerOwner(const char *name
, Oid newOwnerId
)
420 ObjectAddress address
;
421 Form_pg_foreign_server form
;
423 rel
= table_open(ForeignServerRelationId
, RowExclusiveLock
);
425 tup
= SearchSysCacheCopy1(FOREIGNSERVERNAME
, CStringGetDatum(name
));
427 if (!HeapTupleIsValid(tup
))
429 (errcode(ERRCODE_UNDEFINED_OBJECT
),
430 errmsg("server \"%s\" does not exist", name
)));
432 form
= (Form_pg_foreign_server
) GETSTRUCT(tup
);
435 AlterForeignServerOwner_internal(rel
, tup
, newOwnerId
);
437 ObjectAddressSet(address
, ForeignServerRelationId
, servOid
);
441 table_close(rel
, RowExclusiveLock
);
447 * Change foreign server owner -- by OID
450 AlterForeignServerOwner_oid(Oid srvId
, Oid newOwnerId
)
455 rel
= table_open(ForeignServerRelationId
, RowExclusiveLock
);
457 tup
= SearchSysCacheCopy1(FOREIGNSERVEROID
, ObjectIdGetDatum(srvId
));
459 if (!HeapTupleIsValid(tup
))
461 (errcode(ERRCODE_UNDEFINED_OBJECT
),
462 errmsg("foreign server with OID %u does not exist", srvId
)));
464 AlterForeignServerOwner_internal(rel
, tup
, newOwnerId
);
468 table_close(rel
, RowExclusiveLock
);
472 * Convert a handler function name passed from the parser to an Oid.
475 lookup_fdw_handler_func(DefElem
*handler
)
479 if (handler
== NULL
|| handler
->arg
== NULL
)
482 /* handlers have no arguments */
483 handlerOid
= LookupFuncName((List
*) handler
->arg
, 0, NULL
, false);
485 /* check that handler has correct return type */
486 if (get_func_rettype(handlerOid
) != FDW_HANDLEROID
)
488 (errcode(ERRCODE_WRONG_OBJECT_TYPE
),
489 errmsg("function %s must return type %s",
490 NameListToString((List
*) handler
->arg
), "fdw_handler")));
496 * Convert a validator function name passed from the parser to an Oid.
499 lookup_fdw_validator_func(DefElem
*validator
)
503 if (validator
== NULL
|| validator
->arg
== NULL
)
506 /* validators take text[], oid */
507 funcargtypes
[0] = TEXTARRAYOID
;
508 funcargtypes
[1] = OIDOID
;
510 return LookupFuncName((List
*) validator
->arg
, 2, funcargtypes
, false);
511 /* validator's return value is ignored, so we don't check the type */
515 * Process function options of CREATE/ALTER FDW
518 parse_func_options(ParseState
*pstate
, List
*func_options
,
519 bool *handler_given
, Oid
*fdwhandler
,
520 bool *validator_given
, Oid
*fdwvalidator
)
524 *handler_given
= false;
525 *validator_given
= false;
526 /* return InvalidOid if not given */
527 *fdwhandler
= InvalidOid
;
528 *fdwvalidator
= InvalidOid
;
530 foreach(cell
, func_options
)
532 DefElem
*def
= (DefElem
*) lfirst(cell
);
534 if (strcmp(def
->defname
, "handler") == 0)
537 errorConflictingDefElem(def
, pstate
);
538 *handler_given
= true;
539 *fdwhandler
= lookup_fdw_handler_func(def
);
541 else if (strcmp(def
->defname
, "validator") == 0)
543 if (*validator_given
)
544 errorConflictingDefElem(def
, pstate
);
545 *validator_given
= true;
546 *fdwvalidator
= lookup_fdw_validator_func(def
);
549 elog(ERROR
, "option \"%s\" not recognized",
555 * Create a foreign-data wrapper
558 CreateForeignDataWrapper(ParseState
*pstate
, CreateFdwStmt
*stmt
)
561 Datum values
[Natts_pg_foreign_data_wrapper
];
562 bool nulls
[Natts_pg_foreign_data_wrapper
];
566 bool validator_given
;
571 ObjectAddress myself
;
572 ObjectAddress referenced
;
574 rel
= table_open(ForeignDataWrapperRelationId
, RowExclusiveLock
);
576 /* Must be superuser */
579 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
580 errmsg("permission denied to create foreign-data wrapper \"%s\"",
582 errhint("Must be superuser to create a foreign-data wrapper.")));
584 /* For now the owner cannot be specified on create. Use effective user ID. */
585 ownerId
= GetUserId();
588 * Check that there is no other foreign-data wrapper by this name.
590 if (GetForeignDataWrapperByName(stmt
->fdwname
, true) != NULL
)
592 (errcode(ERRCODE_DUPLICATE_OBJECT
),
593 errmsg("foreign-data wrapper \"%s\" already exists",
597 * Insert tuple into pg_foreign_data_wrapper.
599 memset(values
, 0, sizeof(values
));
600 memset(nulls
, false, sizeof(nulls
));
602 fdwId
= GetNewOidWithIndex(rel
, ForeignDataWrapperOidIndexId
,
603 Anum_pg_foreign_data_wrapper_oid
);
604 values
[Anum_pg_foreign_data_wrapper_oid
- 1] = ObjectIdGetDatum(fdwId
);
605 values
[Anum_pg_foreign_data_wrapper_fdwname
- 1] =
606 DirectFunctionCall1(namein
, CStringGetDatum(stmt
->fdwname
));
607 values
[Anum_pg_foreign_data_wrapper_fdwowner
- 1] = ObjectIdGetDatum(ownerId
);
609 /* Lookup handler and validator functions, if given */
610 parse_func_options(pstate
, stmt
->func_options
,
611 &handler_given
, &fdwhandler
,
612 &validator_given
, &fdwvalidator
);
614 values
[Anum_pg_foreign_data_wrapper_fdwhandler
- 1] = ObjectIdGetDatum(fdwhandler
);
615 values
[Anum_pg_foreign_data_wrapper_fdwvalidator
- 1] = ObjectIdGetDatum(fdwvalidator
);
617 nulls
[Anum_pg_foreign_data_wrapper_fdwacl
- 1] = true;
619 fdwoptions
= transformGenericOptions(ForeignDataWrapperRelationId
,
620 PointerGetDatum(NULL
),
624 if (PointerIsValid(DatumGetPointer(fdwoptions
)))
625 values
[Anum_pg_foreign_data_wrapper_fdwoptions
- 1] = fdwoptions
;
627 nulls
[Anum_pg_foreign_data_wrapper_fdwoptions
- 1] = true;
629 tuple
= heap_form_tuple(rel
->rd_att
, values
, nulls
);
631 CatalogTupleInsert(rel
, tuple
);
633 heap_freetuple(tuple
);
635 /* record dependencies */
636 myself
.classId
= ForeignDataWrapperRelationId
;
637 myself
.objectId
= fdwId
;
638 myself
.objectSubId
= 0;
640 if (OidIsValid(fdwhandler
))
642 referenced
.classId
= ProcedureRelationId
;
643 referenced
.objectId
= fdwhandler
;
644 referenced
.objectSubId
= 0;
645 recordDependencyOn(&myself
, &referenced
, DEPENDENCY_NORMAL
);
648 if (OidIsValid(fdwvalidator
))
650 referenced
.classId
= ProcedureRelationId
;
651 referenced
.objectId
= fdwvalidator
;
652 referenced
.objectSubId
= 0;
653 recordDependencyOn(&myself
, &referenced
, DEPENDENCY_NORMAL
);
656 recordDependencyOnOwner(ForeignDataWrapperRelationId
, fdwId
, ownerId
);
658 /* dependency on extension */
659 recordDependencyOnCurrentExtension(&myself
, false);
661 /* Post creation hook for new foreign data wrapper */
662 InvokeObjectPostCreateHook(ForeignDataWrapperRelationId
, fdwId
, 0);
664 table_close(rel
, RowExclusiveLock
);
671 * Alter foreign-data wrapper
674 AlterForeignDataWrapper(ParseState
*pstate
, AlterFdwStmt
*stmt
)
678 Form_pg_foreign_data_wrapper fdwForm
;
679 Datum repl_val
[Natts_pg_foreign_data_wrapper
];
680 bool repl_null
[Natts_pg_foreign_data_wrapper
];
681 bool repl_repl
[Natts_pg_foreign_data_wrapper
];
686 bool validator_given
;
689 ObjectAddress myself
;
691 rel
= table_open(ForeignDataWrapperRelationId
, RowExclusiveLock
);
693 /* Must be superuser */
696 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
697 errmsg("permission denied to alter foreign-data wrapper \"%s\"",
699 errhint("Must be superuser to alter a foreign-data wrapper.")));
701 tp
= SearchSysCacheCopy1(FOREIGNDATAWRAPPERNAME
,
702 CStringGetDatum(stmt
->fdwname
));
704 if (!HeapTupleIsValid(tp
))
706 (errcode(ERRCODE_UNDEFINED_OBJECT
),
707 errmsg("foreign-data wrapper \"%s\" does not exist", stmt
->fdwname
)));
709 fdwForm
= (Form_pg_foreign_data_wrapper
) GETSTRUCT(tp
);
710 fdwId
= fdwForm
->oid
;
712 memset(repl_val
, 0, sizeof(repl_val
));
713 memset(repl_null
, false, sizeof(repl_null
));
714 memset(repl_repl
, false, sizeof(repl_repl
));
716 parse_func_options(pstate
, stmt
->func_options
,
717 &handler_given
, &fdwhandler
,
718 &validator_given
, &fdwvalidator
);
722 repl_val
[Anum_pg_foreign_data_wrapper_fdwhandler
- 1] = ObjectIdGetDatum(fdwhandler
);
723 repl_repl
[Anum_pg_foreign_data_wrapper_fdwhandler
- 1] = true;
726 * It could be that the behavior of accessing foreign table changes
727 * with the new handler. Warn about this.
730 (errmsg("changing the foreign-data wrapper handler can change behavior of existing foreign tables")));
735 repl_val
[Anum_pg_foreign_data_wrapper_fdwvalidator
- 1] = ObjectIdGetDatum(fdwvalidator
);
736 repl_repl
[Anum_pg_foreign_data_wrapper_fdwvalidator
- 1] = true;
739 * It could be that existing options for the FDW or dependent SERVER,
740 * USER MAPPING or FOREIGN TABLE objects are no longer valid according
741 * to the new validator. Warn about this.
743 if (OidIsValid(fdwvalidator
))
745 (errmsg("changing the foreign-data wrapper validator can cause "
746 "the options for dependent objects to become invalid")));
751 * Validator is not changed, but we need it for validating options.
753 fdwvalidator
= fdwForm
->fdwvalidator
;
757 * If options specified, validate and update.
761 /* Extract the current options */
762 datum
= SysCacheGetAttr(FOREIGNDATAWRAPPEROID
,
764 Anum_pg_foreign_data_wrapper_fdwoptions
,
767 datum
= PointerGetDatum(NULL
);
769 /* Transform the options */
770 datum
= transformGenericOptions(ForeignDataWrapperRelationId
,
775 if (PointerIsValid(DatumGetPointer(datum
)))
776 repl_val
[Anum_pg_foreign_data_wrapper_fdwoptions
- 1] = datum
;
778 repl_null
[Anum_pg_foreign_data_wrapper_fdwoptions
- 1] = true;
780 repl_repl
[Anum_pg_foreign_data_wrapper_fdwoptions
- 1] = true;
783 /* Everything looks good - update the tuple */
784 tp
= heap_modify_tuple(tp
, RelationGetDescr(rel
),
785 repl_val
, repl_null
, repl_repl
);
787 CatalogTupleUpdate(rel
, &tp
->t_self
, tp
);
791 ObjectAddressSet(myself
, ForeignDataWrapperRelationId
, fdwId
);
793 /* Update function dependencies if we changed them */
794 if (handler_given
|| validator_given
)
796 ObjectAddress referenced
;
799 * Flush all existing dependency records of this FDW on functions; we
800 * assume there can be none other than the ones we are fixing.
802 deleteDependencyRecordsForClass(ForeignDataWrapperRelationId
,
807 /* And build new ones. */
809 if (OidIsValid(fdwhandler
))
811 referenced
.classId
= ProcedureRelationId
;
812 referenced
.objectId
= fdwhandler
;
813 referenced
.objectSubId
= 0;
814 recordDependencyOn(&myself
, &referenced
, DEPENDENCY_NORMAL
);
817 if (OidIsValid(fdwvalidator
))
819 referenced
.classId
= ProcedureRelationId
;
820 referenced
.objectId
= fdwvalidator
;
821 referenced
.objectSubId
= 0;
822 recordDependencyOn(&myself
, &referenced
, DEPENDENCY_NORMAL
);
826 InvokeObjectPostAlterHook(ForeignDataWrapperRelationId
, fdwId
, 0);
828 table_close(rel
, RowExclusiveLock
);
835 * Create a foreign server
838 CreateForeignServer(CreateForeignServerStmt
*stmt
)
842 Datum values
[Natts_pg_foreign_server
];
843 bool nulls
[Natts_pg_foreign_server
];
848 ObjectAddress myself
;
849 ObjectAddress referenced
;
850 ForeignDataWrapper
*fdw
;
852 rel
= table_open(ForeignServerRelationId
, RowExclusiveLock
);
854 /* For now the owner cannot be specified on create. Use effective user ID. */
855 ownerId
= GetUserId();
858 * Check that there is no other foreign server by this name. If there is
859 * one, do nothing if IF NOT EXISTS was specified.
861 srvId
= get_foreign_server_oid(stmt
->servername
, true);
862 if (OidIsValid(srvId
))
864 if (stmt
->if_not_exists
)
867 * If we are in an extension script, insist that the pre-existing
868 * object be a member of the extension, to avoid security risks.
870 ObjectAddressSet(myself
, ForeignServerRelationId
, srvId
);
871 checkMembershipInCurrentExtension(&myself
);
875 (errcode(ERRCODE_DUPLICATE_OBJECT
),
876 errmsg("server \"%s\" already exists, skipping",
878 table_close(rel
, RowExclusiveLock
);
879 return InvalidObjectAddress
;
883 (errcode(ERRCODE_DUPLICATE_OBJECT
),
884 errmsg("server \"%s\" already exists",
889 * Check that the FDW exists and that we have USAGE on it. Also get the
890 * actual FDW for option validation etc.
892 fdw
= GetForeignDataWrapperByName(stmt
->fdwname
, false);
894 aclresult
= pg_foreign_data_wrapper_aclcheck(fdw
->fdwid
, ownerId
, ACL_USAGE
);
895 if (aclresult
!= ACLCHECK_OK
)
896 aclcheck_error(aclresult
, OBJECT_FDW
, fdw
->fdwname
);
899 * Insert tuple into pg_foreign_server.
901 memset(values
, 0, sizeof(values
));
902 memset(nulls
, false, sizeof(nulls
));
904 srvId
= GetNewOidWithIndex(rel
, ForeignServerOidIndexId
,
905 Anum_pg_foreign_server_oid
);
906 values
[Anum_pg_foreign_server_oid
- 1] = ObjectIdGetDatum(srvId
);
907 values
[Anum_pg_foreign_server_srvname
- 1] =
908 DirectFunctionCall1(namein
, CStringGetDatum(stmt
->servername
));
909 values
[Anum_pg_foreign_server_srvowner
- 1] = ObjectIdGetDatum(ownerId
);
910 values
[Anum_pg_foreign_server_srvfdw
- 1] = ObjectIdGetDatum(fdw
->fdwid
);
912 /* Add server type if supplied */
913 if (stmt
->servertype
)
914 values
[Anum_pg_foreign_server_srvtype
- 1] =
915 CStringGetTextDatum(stmt
->servertype
);
917 nulls
[Anum_pg_foreign_server_srvtype
- 1] = true;
919 /* Add server version if supplied */
921 values
[Anum_pg_foreign_server_srvversion
- 1] =
922 CStringGetTextDatum(stmt
->version
);
924 nulls
[Anum_pg_foreign_server_srvversion
- 1] = true;
926 /* Start with a blank acl */
927 nulls
[Anum_pg_foreign_server_srvacl
- 1] = true;
929 /* Add server options */
930 srvoptions
= transformGenericOptions(ForeignServerRelationId
,
931 PointerGetDatum(NULL
),
935 if (PointerIsValid(DatumGetPointer(srvoptions
)))
936 values
[Anum_pg_foreign_server_srvoptions
- 1] = srvoptions
;
938 nulls
[Anum_pg_foreign_server_srvoptions
- 1] = true;
940 tuple
= heap_form_tuple(rel
->rd_att
, values
, nulls
);
942 CatalogTupleInsert(rel
, tuple
);
944 heap_freetuple(tuple
);
946 /* record dependencies */
947 myself
.classId
= ForeignServerRelationId
;
948 myself
.objectId
= srvId
;
949 myself
.objectSubId
= 0;
951 referenced
.classId
= ForeignDataWrapperRelationId
;
952 referenced
.objectId
= fdw
->fdwid
;
953 referenced
.objectSubId
= 0;
954 recordDependencyOn(&myself
, &referenced
, DEPENDENCY_NORMAL
);
956 recordDependencyOnOwner(ForeignServerRelationId
, srvId
, ownerId
);
958 /* dependency on extension */
959 recordDependencyOnCurrentExtension(&myself
, false);
961 /* Post creation hook for new foreign server */
962 InvokeObjectPostCreateHook(ForeignServerRelationId
, srvId
, 0);
964 table_close(rel
, RowExclusiveLock
);
971 * Alter foreign server
974 AlterForeignServer(AlterForeignServerStmt
*stmt
)
978 Datum repl_val
[Natts_pg_foreign_server
];
979 bool repl_null
[Natts_pg_foreign_server
];
980 bool repl_repl
[Natts_pg_foreign_server
];
982 Form_pg_foreign_server srvForm
;
983 ObjectAddress address
;
985 rel
= table_open(ForeignServerRelationId
, RowExclusiveLock
);
987 tp
= SearchSysCacheCopy1(FOREIGNSERVERNAME
,
988 CStringGetDatum(stmt
->servername
));
990 if (!HeapTupleIsValid(tp
))
992 (errcode(ERRCODE_UNDEFINED_OBJECT
),
993 errmsg("server \"%s\" does not exist", stmt
->servername
)));
995 srvForm
= (Form_pg_foreign_server
) GETSTRUCT(tp
);
996 srvId
= srvForm
->oid
;
999 * Only owner or a superuser can ALTER a SERVER.
1001 if (!object_ownercheck(ForeignServerRelationId
, srvId
, GetUserId()))
1002 aclcheck_error(ACLCHECK_NOT_OWNER
, OBJECT_FOREIGN_SERVER
,
1005 memset(repl_val
, 0, sizeof(repl_val
));
1006 memset(repl_null
, false, sizeof(repl_null
));
1007 memset(repl_repl
, false, sizeof(repl_repl
));
1009 if (stmt
->has_version
)
1012 * Change the server VERSION string.
1015 repl_val
[Anum_pg_foreign_server_srvversion
- 1] =
1016 CStringGetTextDatum(stmt
->version
);
1018 repl_null
[Anum_pg_foreign_server_srvversion
- 1] = true;
1020 repl_repl
[Anum_pg_foreign_server_srvversion
- 1] = true;
1025 ForeignDataWrapper
*fdw
= GetForeignDataWrapper(srvForm
->srvfdw
);
1029 /* Extract the current srvoptions */
1030 datum
= SysCacheGetAttr(FOREIGNSERVEROID
,
1032 Anum_pg_foreign_server_srvoptions
,
1035 datum
= PointerGetDatum(NULL
);
1037 /* Prepare the options array */
1038 datum
= transformGenericOptions(ForeignServerRelationId
,
1043 if (PointerIsValid(DatumGetPointer(datum
)))
1044 repl_val
[Anum_pg_foreign_server_srvoptions
- 1] = datum
;
1046 repl_null
[Anum_pg_foreign_server_srvoptions
- 1] = true;
1048 repl_repl
[Anum_pg_foreign_server_srvoptions
- 1] = true;
1051 /* Everything looks good - update the tuple */
1052 tp
= heap_modify_tuple(tp
, RelationGetDescr(rel
),
1053 repl_val
, repl_null
, repl_repl
);
1055 CatalogTupleUpdate(rel
, &tp
->t_self
, tp
);
1057 InvokeObjectPostAlterHook(ForeignServerRelationId
, srvId
, 0);
1059 ObjectAddressSet(address
, ForeignServerRelationId
, srvId
);
1063 table_close(rel
, RowExclusiveLock
);
1070 * Common routine to check permission for user-mapping-related DDL
1071 * commands. We allow server owners to operate on any mapping, and
1072 * users to operate on their own mapping.
1075 user_mapping_ddl_aclcheck(Oid umuserid
, Oid serverid
, const char *servername
)
1077 Oid curuserid
= GetUserId();
1079 if (!object_ownercheck(ForeignServerRelationId
, serverid
, curuserid
))
1081 if (umuserid
== curuserid
)
1083 AclResult aclresult
;
1085 aclresult
= pg_foreign_server_aclcheck(serverid
, curuserid
, ACL_USAGE
);
1086 if (aclresult
!= ACLCHECK_OK
)
1087 aclcheck_error(aclresult
, OBJECT_FOREIGN_SERVER
, servername
);
1090 aclcheck_error(ACLCHECK_NOT_OWNER
, OBJECT_FOREIGN_SERVER
,
1097 * Create user mapping
1100 CreateUserMapping(CreateUserMappingStmt
*stmt
)
1104 Datum values
[Natts_pg_user_mapping
];
1105 bool nulls
[Natts_pg_user_mapping
];
1109 ObjectAddress myself
;
1110 ObjectAddress referenced
;
1112 ForeignDataWrapper
*fdw
;
1113 RoleSpec
*role
= (RoleSpec
*) stmt
->user
;
1115 rel
= table_open(UserMappingRelationId
, RowExclusiveLock
);
1117 if (role
->roletype
== ROLESPEC_PUBLIC
)
1118 useId
= ACL_ID_PUBLIC
;
1120 useId
= get_rolespec_oid(stmt
->user
, false);
1122 /* Check that the server exists. */
1123 srv
= GetForeignServerByName(stmt
->servername
, false);
1125 user_mapping_ddl_aclcheck(useId
, srv
->serverid
, stmt
->servername
);
1128 * Check that the user mapping is unique within server.
1130 umId
= GetSysCacheOid2(USERMAPPINGUSERSERVER
, Anum_pg_user_mapping_oid
,
1131 ObjectIdGetDatum(useId
),
1132 ObjectIdGetDatum(srv
->serverid
));
1134 if (OidIsValid(umId
))
1136 if (stmt
->if_not_exists
)
1139 * Since user mappings aren't members of extensions (see comments
1140 * below), no need for checkMembershipInCurrentExtension here.
1143 (errcode(ERRCODE_DUPLICATE_OBJECT
),
1144 errmsg("user mapping for \"%s\" already exists for server \"%s\", skipping",
1145 MappingUserName(useId
),
1146 stmt
->servername
)));
1148 table_close(rel
, RowExclusiveLock
);
1149 return InvalidObjectAddress
;
1153 (errcode(ERRCODE_DUPLICATE_OBJECT
),
1154 errmsg("user mapping for \"%s\" already exists for server \"%s\"",
1155 MappingUserName(useId
),
1156 stmt
->servername
)));
1159 fdw
= GetForeignDataWrapper(srv
->fdwid
);
1162 * Insert tuple into pg_user_mapping.
1164 memset(values
, 0, sizeof(values
));
1165 memset(nulls
, false, sizeof(nulls
));
1167 umId
= GetNewOidWithIndex(rel
, UserMappingOidIndexId
,
1168 Anum_pg_user_mapping_oid
);
1169 values
[Anum_pg_user_mapping_oid
- 1] = ObjectIdGetDatum(umId
);
1170 values
[Anum_pg_user_mapping_umuser
- 1] = ObjectIdGetDatum(useId
);
1171 values
[Anum_pg_user_mapping_umserver
- 1] = ObjectIdGetDatum(srv
->serverid
);
1173 /* Add user options */
1174 useoptions
= transformGenericOptions(UserMappingRelationId
,
1175 PointerGetDatum(NULL
),
1179 if (PointerIsValid(DatumGetPointer(useoptions
)))
1180 values
[Anum_pg_user_mapping_umoptions
- 1] = useoptions
;
1182 nulls
[Anum_pg_user_mapping_umoptions
- 1] = true;
1184 tuple
= heap_form_tuple(rel
->rd_att
, values
, nulls
);
1186 CatalogTupleInsert(rel
, tuple
);
1188 heap_freetuple(tuple
);
1190 /* Add dependency on the server */
1191 myself
.classId
= UserMappingRelationId
;
1192 myself
.objectId
= umId
;
1193 myself
.objectSubId
= 0;
1195 referenced
.classId
= ForeignServerRelationId
;
1196 referenced
.objectId
= srv
->serverid
;
1197 referenced
.objectSubId
= 0;
1198 recordDependencyOn(&myself
, &referenced
, DEPENDENCY_NORMAL
);
1200 if (OidIsValid(useId
))
1202 /* Record the mapped user dependency */
1203 recordDependencyOnOwner(UserMappingRelationId
, umId
, useId
);
1207 * Perhaps someday there should be a recordDependencyOnCurrentExtension
1208 * call here; but since roles aren't members of extensions, it seems like
1209 * user mappings shouldn't be either. Note that the grammar and pg_dump
1210 * would need to be extended too if we change this.
1213 /* Post creation hook for new user mapping */
1214 InvokeObjectPostCreateHook(UserMappingRelationId
, umId
, 0);
1216 table_close(rel
, RowExclusiveLock
);
1223 * Alter user mapping
1226 AlterUserMapping(AlterUserMappingStmt
*stmt
)
1230 Datum repl_val
[Natts_pg_user_mapping
];
1231 bool repl_null
[Natts_pg_user_mapping
];
1232 bool repl_repl
[Natts_pg_user_mapping
];
1236 ObjectAddress address
;
1237 RoleSpec
*role
= (RoleSpec
*) stmt
->user
;
1239 rel
= table_open(UserMappingRelationId
, RowExclusiveLock
);
1241 if (role
->roletype
== ROLESPEC_PUBLIC
)
1242 useId
= ACL_ID_PUBLIC
;
1244 useId
= get_rolespec_oid(stmt
->user
, false);
1246 srv
= GetForeignServerByName(stmt
->servername
, false);
1248 umId
= GetSysCacheOid2(USERMAPPINGUSERSERVER
, Anum_pg_user_mapping_oid
,
1249 ObjectIdGetDatum(useId
),
1250 ObjectIdGetDatum(srv
->serverid
));
1251 if (!OidIsValid(umId
))
1253 (errcode(ERRCODE_UNDEFINED_OBJECT
),
1254 errmsg("user mapping for \"%s\" does not exist for server \"%s\"",
1255 MappingUserName(useId
), stmt
->servername
)));
1257 user_mapping_ddl_aclcheck(useId
, srv
->serverid
, stmt
->servername
);
1259 tp
= SearchSysCacheCopy1(USERMAPPINGOID
, ObjectIdGetDatum(umId
));
1261 if (!HeapTupleIsValid(tp
))
1262 elog(ERROR
, "cache lookup failed for user mapping %u", umId
);
1264 memset(repl_val
, 0, sizeof(repl_val
));
1265 memset(repl_null
, false, sizeof(repl_null
));
1266 memset(repl_repl
, false, sizeof(repl_repl
));
1270 ForeignDataWrapper
*fdw
;
1275 * Process the options.
1278 fdw
= GetForeignDataWrapper(srv
->fdwid
);
1280 datum
= SysCacheGetAttr(USERMAPPINGUSERSERVER
,
1282 Anum_pg_user_mapping_umoptions
,
1285 datum
= PointerGetDatum(NULL
);
1287 /* Prepare the options array */
1288 datum
= transformGenericOptions(UserMappingRelationId
,
1293 if (PointerIsValid(DatumGetPointer(datum
)))
1294 repl_val
[Anum_pg_user_mapping_umoptions
- 1] = datum
;
1296 repl_null
[Anum_pg_user_mapping_umoptions
- 1] = true;
1298 repl_repl
[Anum_pg_user_mapping_umoptions
- 1] = true;
1301 /* Everything looks good - update the tuple */
1302 tp
= heap_modify_tuple(tp
, RelationGetDescr(rel
),
1303 repl_val
, repl_null
, repl_repl
);
1305 CatalogTupleUpdate(rel
, &tp
->t_self
, tp
);
1307 InvokeObjectPostAlterHook(UserMappingRelationId
,
1310 ObjectAddressSet(address
, UserMappingRelationId
, umId
);
1314 table_close(rel
, RowExclusiveLock
);
1324 RemoveUserMapping(DropUserMappingStmt
*stmt
)
1326 ObjectAddress object
;
1330 RoleSpec
*role
= (RoleSpec
*) stmt
->user
;
1332 if (role
->roletype
== ROLESPEC_PUBLIC
)
1333 useId
= ACL_ID_PUBLIC
;
1336 useId
= get_rolespec_oid(stmt
->user
, stmt
->missing_ok
);
1337 if (!OidIsValid(useId
))
1340 * IF EXISTS specified, role not found and not public. Notice this
1343 elog(NOTICE
, "role \"%s\" does not exist, skipping",
1349 srv
= GetForeignServerByName(stmt
->servername
, true);
1353 if (!stmt
->missing_ok
)
1355 (errcode(ERRCODE_UNDEFINED_OBJECT
),
1356 errmsg("server \"%s\" does not exist",
1357 stmt
->servername
)));
1358 /* IF EXISTS, just note it */
1360 (errmsg("server \"%s\" does not exist, skipping",
1361 stmt
->servername
)));
1365 umId
= GetSysCacheOid2(USERMAPPINGUSERSERVER
, Anum_pg_user_mapping_oid
,
1366 ObjectIdGetDatum(useId
),
1367 ObjectIdGetDatum(srv
->serverid
));
1369 if (!OidIsValid(umId
))
1371 if (!stmt
->missing_ok
)
1373 (errcode(ERRCODE_UNDEFINED_OBJECT
),
1374 errmsg("user mapping for \"%s\" does not exist for server \"%s\"",
1375 MappingUserName(useId
), stmt
->servername
)));
1377 /* IF EXISTS specified, just note it */
1379 (errmsg("user mapping for \"%s\" does not exist for server \"%s\", skipping",
1380 MappingUserName(useId
), stmt
->servername
)));
1384 user_mapping_ddl_aclcheck(useId
, srv
->serverid
, srv
->servername
);
1389 object
.classId
= UserMappingRelationId
;
1390 object
.objectId
= umId
;
1391 object
.objectSubId
= 0;
1393 performDeletion(&object
, DROP_CASCADE
, 0);
1400 * Create a foreign table
1401 * call after DefineRelation().
1404 CreateForeignTable(CreateForeignTableStmt
*stmt
, Oid relid
)
1408 Datum values
[Natts_pg_foreign_table
];
1409 bool nulls
[Natts_pg_foreign_table
];
1411 AclResult aclresult
;
1412 ObjectAddress myself
;
1413 ObjectAddress referenced
;
1415 ForeignDataWrapper
*fdw
;
1416 ForeignServer
*server
;
1419 * Advance command counter to ensure the pg_attribute tuple is visible;
1420 * the tuple might be updated to add constraints in previous step.
1422 CommandCounterIncrement();
1424 ftrel
= table_open(ForeignTableRelationId
, RowExclusiveLock
);
1427 * For now the owner cannot be specified on create. Use effective user ID.
1429 ownerId
= GetUserId();
1432 * Check that the foreign server exists and that we have USAGE on it. Also
1433 * get the actual FDW for option validation etc.
1435 server
= GetForeignServerByName(stmt
->servername
, false);
1436 aclresult
= pg_foreign_server_aclcheck(server
->serverid
, ownerId
, ACL_USAGE
);
1437 if (aclresult
!= ACLCHECK_OK
)
1438 aclcheck_error(aclresult
, OBJECT_FOREIGN_SERVER
, server
->servername
);
1440 fdw
= GetForeignDataWrapper(server
->fdwid
);
1443 * Insert tuple into pg_foreign_table.
1445 memset(values
, 0, sizeof(values
));
1446 memset(nulls
, false, sizeof(nulls
));
1448 values
[Anum_pg_foreign_table_ftrelid
- 1] = ObjectIdGetDatum(relid
);
1449 values
[Anum_pg_foreign_table_ftserver
- 1] = ObjectIdGetDatum(server
->serverid
);
1450 /* Add table generic options */
1451 ftoptions
= transformGenericOptions(ForeignTableRelationId
,
1452 PointerGetDatum(NULL
),
1456 if (PointerIsValid(DatumGetPointer(ftoptions
)))
1457 values
[Anum_pg_foreign_table_ftoptions
- 1] = ftoptions
;
1459 nulls
[Anum_pg_foreign_table_ftoptions
- 1] = true;
1461 tuple
= heap_form_tuple(ftrel
->rd_att
, values
, nulls
);
1463 CatalogTupleInsert(ftrel
, tuple
);
1465 heap_freetuple(tuple
);
1467 /* Add pg_class dependency on the server */
1468 myself
.classId
= RelationRelationId
;
1469 myself
.objectId
= relid
;
1470 myself
.objectSubId
= 0;
1472 referenced
.classId
= ForeignServerRelationId
;
1473 referenced
.objectId
= server
->serverid
;
1474 referenced
.objectSubId
= 0;
1475 recordDependencyOn(&myself
, &referenced
, DEPENDENCY_NORMAL
);
1477 table_close(ftrel
, RowExclusiveLock
);
1481 * Import a foreign schema
1484 ImportForeignSchema(ImportForeignSchemaStmt
*stmt
)
1486 ForeignServer
*server
;
1487 ForeignDataWrapper
*fdw
;
1488 FdwRoutine
*fdw_routine
;
1489 AclResult aclresult
;
1493 /* Check that the foreign server exists and that we have USAGE on it */
1494 server
= GetForeignServerByName(stmt
->server_name
, false);
1495 aclresult
= pg_foreign_server_aclcheck(server
->serverid
, GetUserId(), ACL_USAGE
);
1496 if (aclresult
!= ACLCHECK_OK
)
1497 aclcheck_error(aclresult
, OBJECT_FOREIGN_SERVER
, server
->servername
);
1499 /* Check that the schema exists and we have CREATE permissions on it */
1500 (void) LookupCreationNamespace(stmt
->local_schema
);
1502 /* Get the FDW and check it supports IMPORT */
1503 fdw
= GetForeignDataWrapper(server
->fdwid
);
1504 if (!OidIsValid(fdw
->fdwhandler
))
1506 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
),
1507 errmsg("foreign-data wrapper \"%s\" has no handler",
1509 fdw_routine
= GetFdwRoutine(fdw
->fdwhandler
);
1510 if (fdw_routine
->ImportForeignSchema
== NULL
)
1512 (errcode(ERRCODE_FDW_NO_SCHEMAS
),
1513 errmsg("foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA",
1516 /* Call FDW to get a list of commands */
1517 cmd_list
= fdw_routine
->ImportForeignSchema(stmt
, server
->serverid
);
1519 /* Parse and execute each command */
1520 foreach(lc
, cmd_list
)
1522 char *cmd
= (char *) lfirst(lc
);
1523 import_error_callback_arg callback_arg
;
1524 ErrorContextCallback sqlerrcontext
;
1525 List
*raw_parsetree_list
;
1529 * Setup error traceback support for ereport(). This is so that any
1530 * error in the generated SQL will be displayed nicely.
1532 callback_arg
.tablename
= NULL
; /* not known yet */
1533 callback_arg
.cmd
= cmd
;
1534 sqlerrcontext
.callback
= import_error_callback
;
1535 sqlerrcontext
.arg
= (void *) &callback_arg
;
1536 sqlerrcontext
.previous
= error_context_stack
;
1537 error_context_stack
= &sqlerrcontext
;
1540 * Parse the SQL string into a list of raw parse trees.
1542 raw_parsetree_list
= pg_parse_query(cmd
);
1545 * Process each parse tree (we allow the FDW to put more than one
1546 * command per string, though this isn't really advised).
1548 foreach(lc2
, raw_parsetree_list
)
1550 RawStmt
*rs
= lfirst_node(RawStmt
, lc2
);
1551 CreateForeignTableStmt
*cstmt
= (CreateForeignTableStmt
*) rs
->stmt
;
1555 * Because we only allow CreateForeignTableStmt, we can skip parse
1556 * analysis, rewrite, and planning steps here.
1558 if (!IsA(cstmt
, CreateForeignTableStmt
))
1560 "foreign-data wrapper \"%s\" returned incorrect statement type %d",
1561 fdw
->fdwname
, (int) nodeTag(cstmt
));
1563 /* Ignore commands for tables excluded by filter options */
1564 if (!IsImportableForeignTable(cstmt
->base
.relation
->relname
, stmt
))
1567 /* Enable reporting of current table's name on error */
1568 callback_arg
.tablename
= cstmt
->base
.relation
->relname
;
1570 /* Ensure creation schema is the one given in IMPORT statement */
1571 cstmt
->base
.relation
->schemaname
= pstrdup(stmt
->local_schema
);
1573 /* No planning needed, just make a wrapper PlannedStmt */
1574 pstmt
= makeNode(PlannedStmt
);
1575 pstmt
->commandType
= CMD_UTILITY
;
1576 pstmt
->canSetTag
= false;
1577 pstmt
->utilityStmt
= (Node
*) cstmt
;
1578 pstmt
->stmt_location
= rs
->stmt_location
;
1579 pstmt
->stmt_len
= rs
->stmt_len
;
1581 /* Execute statement */
1582 ProcessUtility(pstmt
, cmd
, false,
1583 PROCESS_UTILITY_SUBCOMMAND
, NULL
, NULL
,
1584 None_Receiver
, NULL
);
1586 /* Be sure to advance the command counter between subcommands */
1587 CommandCounterIncrement();
1589 callback_arg
.tablename
= NULL
;
1592 error_context_stack
= sqlerrcontext
.previous
;
1597 * error context callback to let us supply the failing SQL statement's text
1600 import_error_callback(void *arg
)
1602 import_error_callback_arg
*callback_arg
= (import_error_callback_arg
*) arg
;
1603 int syntaxerrposition
;
1605 /* If it's a syntax error, convert to internal syntax error report */
1606 syntaxerrposition
= geterrposition();
1607 if (syntaxerrposition
> 0)
1610 internalerrposition(syntaxerrposition
);
1611 internalerrquery(callback_arg
->cmd
);
1614 if (callback_arg
->tablename
)
1615 errcontext("importing foreign table \"%s\"",
1616 callback_arg
->tablename
);