LCOV - code coverage report
Current view: top level - src/backend/commands - foreigncmds.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 512 541 94.6 %
Date: 2024-04-13 10:11:43 Functions: 21 22 95.5 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14