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

Generated by: LCOV version 2.0-1