LCOV - code coverage report
Current view: top level - contrib/postgres_fdw - option.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 157 166 94.6 %
Date: 2025-01-18 04:15:08 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * option.c
       4             :  *        FDW and GUC option handling for postgres_fdw
       5             :  *
       6             :  * Portions Copyright (c) 2012-2025, PostgreSQL Global Development Group
       7             :  *
       8             :  * IDENTIFICATION
       9             :  *        contrib/postgres_fdw/option.c
      10             :  *
      11             :  *-------------------------------------------------------------------------
      12             :  */
      13             : #include "postgres.h"
      14             : 
      15             : #include "access/reloptions.h"
      16             : #include "catalog/pg_foreign_server.h"
      17             : #include "catalog/pg_foreign_table.h"
      18             : #include "catalog/pg_user_mapping.h"
      19             : #include "commands/defrem.h"
      20             : #include "commands/extension.h"
      21             : #include "libpq/libpq-be.h"
      22             : #include "postgres_fdw.h"
      23             : #include "utils/guc.h"
      24             : #include "utils/varlena.h"
      25             : 
      26             : /*
      27             :  * Describes the valid options for objects that this wrapper uses.
      28             :  */
      29             : typedef struct PgFdwOption
      30             : {
      31             :     const char *keyword;
      32             :     Oid         optcontext;     /* OID of catalog in which option may appear */
      33             :     bool        is_libpq_opt;   /* true if it's used in libpq */
      34             : } PgFdwOption;
      35             : 
      36             : /*
      37             :  * Valid options for postgres_fdw.
      38             :  * Allocated and filled in InitPgFdwOptions.
      39             :  */
      40             : static PgFdwOption *postgres_fdw_options;
      41             : 
      42             : /*
      43             :  * Valid options for libpq.
      44             :  * Allocated and filled in InitPgFdwOptions.
      45             :  */
      46             : static PQconninfoOption *libpq_options;
      47             : 
      48             : /*
      49             :  * GUC parameters
      50             :  */
      51             : char       *pgfdw_application_name = NULL;
      52             : 
      53             : /*
      54             :  * Helper functions
      55             :  */
      56             : static void InitPgFdwOptions(void);
      57             : static bool is_valid_option(const char *keyword, Oid context);
      58             : static bool is_libpq_option(const char *keyword);
      59             : 
      60             : #include "miscadmin.h"
      61             : 
      62             : /*
      63             :  * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
      64             :  * USER MAPPING or FOREIGN TABLE that uses postgres_fdw.
      65             :  *
      66             :  * Raise an ERROR if the option or its value is considered invalid.
      67             :  */
      68          22 : PG_FUNCTION_INFO_V1(postgres_fdw_validator);
      69             : 
      70             : Datum
      71         564 : postgres_fdw_validator(PG_FUNCTION_ARGS)
      72             : {
      73         564 :     List       *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
      74         564 :     Oid         catalog = PG_GETARG_OID(1);
      75             :     ListCell   *cell;
      76             : 
      77             :     /* Build our options lists if we didn't yet. */
      78         564 :     InitPgFdwOptions();
      79             : 
      80             :     /*
      81             :      * Check that only options supported by postgres_fdw, and allowed for the
      82             :      * current object type, are given.
      83             :      */
      84        1806 :     foreach(cell, options_list)
      85             :     {
      86        1266 :         DefElem    *def = (DefElem *) lfirst(cell);
      87             : 
      88        1266 :         if (!is_valid_option(def->defname, catalog))
      89             :         {
      90             :             /*
      91             :              * Unknown option specified, complain about it. Provide a hint
      92             :              * with a valid option that looks similar, if there is one.
      93             :              */
      94             :             PgFdwOption *opt;
      95             :             const char *closest_match;
      96             :             ClosestMatchState match_state;
      97           6 :             bool        has_valid_options = false;
      98             : 
      99           6 :             initClosestMatch(&match_state, def->defname, 4);
     100         408 :             for (opt = postgres_fdw_options; opt->keyword; opt++)
     101             :             {
     102         402 :                 if (catalog == opt->optcontext)
     103             :                 {
     104         114 :                     has_valid_options = true;
     105         114 :                     updateClosestMatch(&match_state, opt->keyword);
     106             :                 }
     107             :             }
     108             : 
     109           6 :             closest_match = getClosestMatch(&match_state);
     110           6 :             ereport(ERROR,
     111             :                     (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
     112             :                      errmsg("invalid option \"%s\"", def->defname),
     113             :                      has_valid_options ? closest_match ?
     114             :                      errhint("Perhaps you meant the option \"%s\".",
     115             :                              closest_match) : 0 :
     116             :                      errhint("There are no valid options in this context.")));
     117             :         }
     118             : 
     119             :         /*
     120             :          * Validate option value, when we can do so without any context.
     121             :          */
     122        1260 :         if (strcmp(def->defname, "use_remote_estimate") == 0 ||
     123        1194 :             strcmp(def->defname, "updatable") == 0 ||
     124        1186 :             strcmp(def->defname, "truncatable") == 0 ||
     125        1176 :             strcmp(def->defname, "async_capable") == 0 ||
     126        1168 :             strcmp(def->defname, "parallel_commit") == 0 ||
     127        1160 :             strcmp(def->defname, "parallel_abort") == 0 ||
     128        1152 :             strcmp(def->defname, "keep_connections") == 0)
     129             :         {
     130             :             /* these accept only boolean values */
     131         144 :             (void) defGetBoolean(def);
     132             :         }
     133        1116 :         else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
     134        1102 :                  strcmp(def->defname, "fdw_tuple_cost") == 0)
     135          22 :         {
     136             :             /*
     137             :              * These must have a floating point value greater than or equal to
     138             :              * zero.
     139             :              */
     140             :             char       *value;
     141             :             double      real_val;
     142             :             bool        is_parsed;
     143             : 
     144          26 :             value = defGetString(def);
     145          26 :             is_parsed = parse_real(value, &real_val, 0, NULL);
     146             : 
     147          26 :             if (!is_parsed)
     148           4 :                 ereport(ERROR,
     149             :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     150             :                          errmsg("invalid value for floating point option \"%s\": %s",
     151             :                                 def->defname, value)));
     152             : 
     153          22 :             if (real_val < 0)
     154           0 :                 ereport(ERROR,
     155             :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     156             :                          errmsg("\"%s\" must be a floating point value greater than or equal to zero",
     157             :                                 def->defname)));
     158             :         }
     159        1090 :         else if (strcmp(def->defname, "extensions") == 0)
     160             :         {
     161             :             /* check list syntax, warn about uninstalled extensions */
     162          62 :             (void) ExtractExtensionList(defGetString(def), true);
     163             :         }
     164        1028 :         else if (strcmp(def->defname, "fetch_size") == 0 ||
     165        1018 :                  strcmp(def->defname, "batch_size") == 0)
     166          46 :         {
     167             :             char       *value;
     168             :             int         int_val;
     169             :             bool        is_parsed;
     170             : 
     171          50 :             value = defGetString(def);
     172          50 :             is_parsed = parse_int(value, &int_val, 0, NULL);
     173             : 
     174          50 :             if (!is_parsed)
     175           4 :                 ereport(ERROR,
     176             :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     177             :                          errmsg("invalid value for integer option \"%s\": %s",
     178             :                                 def->defname, value)));
     179             : 
     180          46 :             if (int_val <= 0)
     181           0 :                 ereport(ERROR,
     182             :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     183             :                          errmsg("\"%s\" must be an integer value greater than zero",
     184             :                                 def->defname)));
     185             :         }
     186         978 :         else if (strcmp(def->defname, "password_required") == 0)
     187             :         {
     188          12 :             bool        pw_required = defGetBoolean(def);
     189             : 
     190             :             /*
     191             :              * Only the superuser may set this option on a user mapping, or
     192             :              * alter a user mapping on which this option is set. We allow a
     193             :              * user to clear this option if it's set - in fact, we don't have
     194             :              * a choice since we can't see the old mapping when validating an
     195             :              * alter.
     196             :              */
     197          12 :             if (!superuser() && !pw_required)
     198           2 :                 ereport(ERROR,
     199             :                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     200             :                          errmsg("password_required=false is superuser-only"),
     201             :                          errhint("User mappings with the password_required option set to false may only be created or modified by the superuser.")));
     202             :         }
     203         966 :         else if (strcmp(def->defname, "sslcert") == 0 ||
     204         954 :                  strcmp(def->defname, "sslkey") == 0)
     205             :         {
     206             :             /* similarly for sslcert / sslkey on user mapping */
     207          24 :             if (catalog == UserMappingRelationId && !superuser())
     208           4 :                 ereport(ERROR,
     209             :                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     210             :                          errmsg("sslcert and sslkey are superuser-only"),
     211             :                          errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser.")));
     212             :         }
     213         942 :         else if (strcmp(def->defname, "analyze_sampling") == 0)
     214             :         {
     215             :             char       *value;
     216             : 
     217          14 :             value = defGetString(def);
     218             : 
     219             :             /* we recognize off/auto/random/system/bernoulli */
     220          14 :             if (strcmp(value, "off") != 0 &&
     221          10 :                 strcmp(value, "auto") != 0 &&
     222           8 :                 strcmp(value, "random") != 0 &&
     223           6 :                 strcmp(value, "system") != 0 &&
     224           4 :                 strcmp(value, "bernoulli") != 0)
     225           2 :                 ereport(ERROR,
     226             :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     227             :                          errmsg("invalid value for string option \"%s\": %s",
     228             :                                 def->defname, value)));
     229             :         }
     230             :     }
     231             : 
     232         540 :     PG_RETURN_VOID();
     233             : }
     234             : 
     235             : /*
     236             :  * Initialize option lists.
     237             :  */
     238             : static void
     239         848 : InitPgFdwOptions(void)
     240             : {
     241             :     int         num_libpq_opts;
     242             :     PQconninfoOption *lopt;
     243             :     PgFdwOption *popt;
     244             : 
     245             :     /* non-libpq FDW-specific FDW options */
     246             :     static const PgFdwOption non_libpq_options[] = {
     247             :         {"schema_name", ForeignTableRelationId, false},
     248             :         {"table_name", ForeignTableRelationId, false},
     249             :         {"column_name", AttributeRelationId, false},
     250             :         /* use_remote_estimate is available on both server and table */
     251             :         {"use_remote_estimate", ForeignServerRelationId, false},
     252             :         {"use_remote_estimate", ForeignTableRelationId, false},
     253             :         /* cost factors */
     254             :         {"fdw_startup_cost", ForeignServerRelationId, false},
     255             :         {"fdw_tuple_cost", ForeignServerRelationId, false},
     256             :         /* shippable extensions */
     257             :         {"extensions", ForeignServerRelationId, false},
     258             :         /* updatable is available on both server and table */
     259             :         {"updatable", ForeignServerRelationId, false},
     260             :         {"updatable", ForeignTableRelationId, false},
     261             :         /* truncatable is available on both server and table */
     262             :         {"truncatable", ForeignServerRelationId, false},
     263             :         {"truncatable", ForeignTableRelationId, false},
     264             :         /* fetch_size is available on both server and table */
     265             :         {"fetch_size", ForeignServerRelationId, false},
     266             :         {"fetch_size", ForeignTableRelationId, false},
     267             :         /* batch_size is available on both server and table */
     268             :         {"batch_size", ForeignServerRelationId, false},
     269             :         {"batch_size", ForeignTableRelationId, false},
     270             :         /* async_capable is available on both server and table */
     271             :         {"async_capable", ForeignServerRelationId, false},
     272             :         {"async_capable", ForeignTableRelationId, false},
     273             :         {"parallel_commit", ForeignServerRelationId, false},
     274             :         {"parallel_abort", ForeignServerRelationId, false},
     275             :         {"keep_connections", ForeignServerRelationId, false},
     276             :         {"password_required", UserMappingRelationId, false},
     277             : 
     278             :         /* sampling is available on both server and table */
     279             :         {"analyze_sampling", ForeignServerRelationId, false},
     280             :         {"analyze_sampling", ForeignTableRelationId, false},
     281             : 
     282             :         {"use_scram_passthrough", ForeignServerRelationId, false},
     283             :         {"use_scram_passthrough", UserMappingRelationId, false},
     284             : 
     285             :         /*
     286             :          * sslcert and sslkey are in fact libpq options, but we repeat them
     287             :          * here to allow them to appear in both foreign server context (when
     288             :          * we generate libpq options) and user mapping context (from here).
     289             :          */
     290             :         {"sslcert", UserMappingRelationId, true},
     291             :         {"sslkey", UserMappingRelationId, true},
     292             : 
     293             :         /*
     294             :          * gssdelegation is also a libpq option but should be allowed in a
     295             :          * user mapping context too
     296             :          */
     297             :         {"gssdelegation", UserMappingRelationId, true},
     298             : 
     299             :         {NULL, InvalidOid, false}
     300             :     };
     301             : 
     302             :     /* Prevent redundant initialization. */
     303         848 :     if (postgres_fdw_options)
     304         820 :         return;
     305             : 
     306             :     /*
     307             :      * Get list of valid libpq options.
     308             :      *
     309             :      * To avoid unnecessary work, we get the list once and use it throughout
     310             :      * the lifetime of this backend process.  We don't need to care about
     311             :      * memory context issues, because PQconndefaults allocates with malloc.
     312             :      */
     313          28 :     libpq_options = PQconndefaults();
     314          28 :     if (!libpq_options)         /* assume reason for failure is OOM */
     315           0 :         ereport(ERROR,
     316             :                 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
     317             :                  errmsg("out of memory"),
     318             :                  errdetail("Could not get libpq's default connection options.")));
     319             : 
     320             :     /* Count how many libpq options are available. */
     321          28 :     num_libpq_opts = 0;
     322        1232 :     for (lopt = libpq_options; lopt->keyword; lopt++)
     323        1204 :         num_libpq_opts++;
     324             : 
     325             :     /*
     326             :      * Construct an array which consists of all valid options for
     327             :      * postgres_fdw, by appending FDW-specific options to libpq options.
     328             :      *
     329             :      * We use plain malloc here to allocate postgres_fdw_options because it
     330             :      * lives as long as the backend process does.  Besides, keeping
     331             :      * libpq_options in memory allows us to avoid copying every keyword
     332             :      * string.
     333             :      */
     334          28 :     postgres_fdw_options = (PgFdwOption *)
     335          28 :         malloc(sizeof(PgFdwOption) * num_libpq_opts +
     336             :                sizeof(non_libpq_options));
     337          28 :     if (postgres_fdw_options == NULL)
     338           0 :         ereport(ERROR,
     339             :                 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
     340             :                  errmsg("out of memory")));
     341             : 
     342          28 :     popt = postgres_fdw_options;
     343        1232 :     for (lopt = libpq_options; lopt->keyword; lopt++)
     344             :     {
     345             :         /* Hide debug options, as well as settings we override internally. */
     346        1204 :         if (strchr(lopt->dispchar, 'D') ||
     347        1120 :             strcmp(lopt->keyword, "fallback_application_name") == 0 ||
     348        1092 :             strcmp(lopt->keyword, "client_encoding") == 0)
     349         140 :             continue;
     350             : 
     351             :         /* We don't have to copy keyword string, as described above. */
     352        1064 :         popt->keyword = lopt->keyword;
     353             : 
     354             :         /*
     355             :          * "user" and any secret options are allowed only on user mappings.
     356             :          * Everything else is a server option.
     357             :          */
     358        1064 :         if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
     359          84 :             popt->optcontext = UserMappingRelationId;
     360             :         else
     361         980 :             popt->optcontext = ForeignServerRelationId;
     362        1064 :         popt->is_libpq_opt = true;
     363             : 
     364        1064 :         popt++;
     365             :     }
     366             : 
     367             :     /* Append FDW-specific options and dummy terminator. */
     368          28 :     memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
     369             : }
     370             : 
     371             : /*
     372             :  * Check whether the given option is one of the valid postgres_fdw options.
     373             :  * context is the Oid of the catalog holding the object the option is for.
     374             :  */
     375             : static bool
     376        1266 : is_valid_option(const char *keyword, Oid context)
     377             : {
     378             :     PgFdwOption *opt;
     379             : 
     380             :     Assert(postgres_fdw_options);   /* must be initialized already */
     381             : 
     382       39374 :     for (opt = postgres_fdw_options; opt->keyword; opt++)
     383             :     {
     384       39368 :         if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
     385        1260 :             return true;
     386             :     }
     387             : 
     388           6 :     return false;
     389             : }
     390             : 
     391             : /*
     392             :  * Check whether the given option is one of the valid libpq options.
     393             :  */
     394             : static bool
     395         508 : is_libpq_option(const char *keyword)
     396             : {
     397             :     PgFdwOption *opt;
     398             : 
     399             :     Assert(postgres_fdw_options);   /* must be initialized already */
     400             : 
     401       14100 :     for (opt = postgres_fdw_options; opt->keyword; opt++)
     402             :     {
     403       13936 :         if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
     404         344 :             return true;
     405             :     }
     406             : 
     407         164 :     return false;
     408             : }
     409             : 
     410             : /*
     411             :  * Generate key-value arrays which include only libpq options from the
     412             :  * given list (which can contain any kind of options).  Caller must have
     413             :  * allocated large-enough arrays.  Returns number of options found.
     414             :  */
     415             : int
     416         284 : ExtractConnectionOptions(List *defelems, const char **keywords,
     417             :                          const char **values)
     418             : {
     419             :     ListCell   *lc;
     420             :     int         i;
     421             : 
     422             :     /* Build our options lists if we didn't yet. */
     423         284 :     InitPgFdwOptions();
     424             : 
     425         284 :     i = 0;
     426         792 :     foreach(lc, defelems)
     427             :     {
     428         508 :         DefElem    *d = (DefElem *) lfirst(lc);
     429             : 
     430         508 :         if (is_libpq_option(d->defname))
     431             :         {
     432         344 :             keywords[i] = d->defname;
     433         344 :             values[i] = defGetString(d);
     434         344 :             i++;
     435             :         }
     436             :     }
     437         284 :     return i;
     438             : }
     439             : 
     440             : /*
     441             :  * Parse a comma-separated string and return a List of the OIDs of the
     442             :  * extensions named in the string.  If any names in the list cannot be
     443             :  * found, report a warning if warnOnMissing is true, else just silently
     444             :  * ignore them.
     445             :  */
     446             : List *
     447        1872 : ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
     448             : {
     449        1872 :     List       *extensionOids = NIL;
     450             :     List       *extlist;
     451             :     ListCell   *lc;
     452             : 
     453             :     /* SplitIdentifierString scribbles on its input, so pstrdup first */
     454        1872 :     if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
     455             :     {
     456             :         /* syntax error in name list */
     457           2 :         ereport(ERROR,
     458             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     459             :                  errmsg("parameter \"%s\" must be a list of extension names",
     460             :                         "extensions")));
     461             :     }
     462             : 
     463        3742 :     foreach(lc, extlist)
     464             :     {
     465        1872 :         const char *extension_name = (const char *) lfirst(lc);
     466        1872 :         Oid         extension_oid = get_extension_oid(extension_name, true);
     467             : 
     468        1872 :         if (OidIsValid(extension_oid))
     469             :         {
     470        1868 :             extensionOids = lappend_oid(extensionOids, extension_oid);
     471             :         }
     472           4 :         else if (warnOnMissing)
     473             :         {
     474           4 :             ereport(WARNING,
     475             :                     (errcode(ERRCODE_UNDEFINED_OBJECT),
     476             :                      errmsg("extension \"%s\" is not installed",
     477             :                             extension_name)));
     478             :         }
     479             :     }
     480             : 
     481        1870 :     list_free(extlist);
     482        1870 :     return extensionOids;
     483             : }
     484             : 
     485             : /*
     486             :  * Replace escape sequences beginning with % character in the given
     487             :  * application_name with status information, and return it.
     488             :  *
     489             :  * This function always returns a palloc'd string, so the caller is
     490             :  * responsible for pfreeing it.
     491             :  */
     492             : char *
     493          36 : process_pgfdw_appname(const char *appname)
     494             : {
     495             :     const char *p;
     496             :     StringInfoData buf;
     497             : 
     498          36 :     initStringInfo(&buf);
     499             : 
     500         502 :     for (p = appname; *p != '\0'; p++)
     501             :     {
     502         466 :         if (*p != '%')
     503             :         {
     504             :             /* literal char, just copy */
     505         448 :             appendStringInfoChar(&buf, *p);
     506         448 :             continue;
     507             :         }
     508             : 
     509             :         /* must be a '%', so skip to the next char */
     510          18 :         p++;
     511          18 :         if (*p == '\0')
     512           0 :             break;              /* format error - ignore it */
     513          18 :         else if (*p == '%')
     514             :         {
     515             :             /* string contains %% */
     516           2 :             appendStringInfoChar(&buf, '%');
     517           2 :             continue;
     518             :         }
     519             : 
     520             :         /* process the option */
     521          16 :         switch (*p)
     522             :         {
     523           2 :             case 'a':
     524           2 :                 appendStringInfoString(&buf, application_name);
     525           2 :                 break;
     526           4 :             case 'c':
     527           4 :                 appendStringInfo(&buf, INT64_HEX_FORMAT ".%x", MyStartTime, MyProcPid);
     528           4 :                 break;
     529           4 :             case 'C':
     530           4 :                 appendStringInfoString(&buf, cluster_name);
     531           4 :                 break;
     532           2 :             case 'd':
     533           2 :                 if (MyProcPort)
     534             :                 {
     535           2 :                     const char *dbname = MyProcPort->database_name;
     536             : 
     537           2 :                     if (dbname)
     538           2 :                         appendStringInfoString(&buf, dbname);
     539             :                     else
     540           0 :                         appendStringInfoString(&buf, "[unknown]");
     541             :                 }
     542           2 :                 break;
     543           2 :             case 'p':
     544           2 :                 appendStringInfo(&buf, "%d", MyProcPid);
     545           2 :                 break;
     546           2 :             case 'u':
     547           2 :                 if (MyProcPort)
     548             :                 {
     549           2 :                     const char *username = MyProcPort->user_name;
     550             : 
     551           2 :                     if (username)
     552           2 :                         appendStringInfoString(&buf, username);
     553             :                     else
     554           0 :                         appendStringInfoString(&buf, "[unknown]");
     555             :                 }
     556           2 :                 break;
     557           0 :             default:
     558             :                 /* format error - ignore it */
     559           0 :                 break;
     560             :         }
     561             :     }
     562             : 
     563          36 :     return buf.data;
     564             : }
     565             : 
     566             : /*
     567             :  * Module load callback
     568             :  */
     569             : void
     570          28 : _PG_init(void)
     571             : {
     572             :     /*
     573             :      * Unlike application_name GUC, don't set GUC_IS_NAME flag nor check_hook
     574             :      * to allow postgres_fdw.application_name to be any string more than
     575             :      * NAMEDATALEN characters and to include non-ASCII characters. Instead,
     576             :      * remote server truncates application_name of remote connection to less
     577             :      * than NAMEDATALEN and replaces any non-ASCII characters in it with a '?'
     578             :      * character.
     579             :      */
     580          28 :     DefineCustomStringVariable("postgres_fdw.application_name",
     581             :                                "Sets the application name to be used on the remote server.",
     582             :                                NULL,
     583             :                                &pgfdw_application_name,
     584             :                                NULL,
     585             :                                PGC_USERSET,
     586             :                                0,
     587             :                                NULL,
     588             :                                NULL,
     589             :                                NULL);
     590             : 
     591          28 :     MarkGUCPrefixReserved("postgres_fdw");
     592          28 : }

Generated by: LCOV version 1.14