LCOV - code coverage report
Current view: top level - contrib/postgres_fdw - option.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 95.3 % 169 161
Test Date: 2026-02-17 17:20: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           14 : PG_FUNCTION_INFO_V1(postgres_fdw_validator);
      64              : 
      65              : Datum
      66          310 : postgres_fdw_validator(PG_FUNCTION_ARGS)
      67              : {
      68          310 :     List       *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
      69          310 :     Oid         catalog = PG_GETARG_OID(1);
      70              :     ListCell   *cell;
      71              : 
      72              :     /* Build our options lists if we didn't yet. */
      73          310 :     InitPgFdwOptions();
      74              : 
      75              :     /*
      76              :      * Check that only options supported by postgres_fdw, and allowed for the
      77              :      * current object type, are given.
      78              :      */
      79         1017 :     foreach(cell, options_list)
      80              :     {
      81          723 :         DefElem    *def = (DefElem *) lfirst(cell);
      82              : 
      83          723 :         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          497 :             for (opt = postgres_fdw_options; opt->keyword; opt++)
      96              :             {
      97          490 :                 if (catalog == opt->optcontext)
      98              :                 {
      99          180 :                     has_valid_options = true;
     100          180 :                     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          716 :         if (strcmp(def->defname, "use_remote_estimate") == 0 ||
     118          677 :             strcmp(def->defname, "updatable") == 0 ||
     119          671 :             strcmp(def->defname, "truncatable") == 0 ||
     120          666 :             strcmp(def->defname, "async_capable") == 0 ||
     121          662 :             strcmp(def->defname, "parallel_commit") == 0 ||
     122          658 :             strcmp(def->defname, "parallel_abort") == 0 ||
     123          654 :             strcmp(def->defname, "keep_connections") == 0)
     124              :         {
     125              :             /* these accept only boolean values */
     126           80 :             (void) defGetBoolean(def);
     127              :         }
     128          636 :         else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
     129          627 :                  strcmp(def->defname, "fdw_tuple_cost") == 0)
     130           15 :         {
     131              :             /*
     132              :              * These must have a floating point value greater than or equal to
     133              :              * zero.
     134              :              */
     135              :             char       *value;
     136              :             double      real_val;
     137              :             bool        is_parsed;
     138              : 
     139           17 :             value = defGetString(def);
     140           17 :             is_parsed = parse_real(value, &real_val, 0, NULL);
     141              : 
     142           17 :             if (!is_parsed)
     143            2 :                 ereport(ERROR,
     144              :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     145              :                          errmsg("invalid value for floating point option \"%s\": %s",
     146              :                                 def->defname, value)));
     147              : 
     148           15 :             if (real_val < 0)
     149            0 :                 ereport(ERROR,
     150              :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     151              :                          errmsg("\"%s\" must be a floating point value greater than or equal to zero",
     152              :                                 def->defname)));
     153              :         }
     154          619 :         else if (strcmp(def->defname, "extensions") == 0)
     155              :         {
     156              :             /* check list syntax, warn about uninstalled extensions */
     157           33 :             (void) ExtractExtensionList(defGetString(def), true);
     158              :         }
     159          586 :         else if (strcmp(def->defname, "fetch_size") == 0 ||
     160          581 :                  strcmp(def->defname, "batch_size") == 0)
     161           24 :         {
     162              :             char       *value;
     163              :             int         int_val;
     164              :             bool        is_parsed;
     165              : 
     166           26 :             value = defGetString(def);
     167           26 :             is_parsed = parse_int(value, &int_val, 0, NULL);
     168              : 
     169           26 :             if (!is_parsed)
     170            2 :                 ereport(ERROR,
     171              :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     172              :                          errmsg("invalid value for integer option \"%s\": %s",
     173              :                                 def->defname, value)));
     174              : 
     175           24 :             if (int_val <= 0)
     176            0 :                 ereport(ERROR,
     177              :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     178              :                          errmsg("\"%s\" must be an integer value greater than zero",
     179              :                                 def->defname)));
     180              :         }
     181          560 :         else if (strcmp(def->defname, "password_required") == 0)
     182              :         {
     183            6 :             bool        pw_required = defGetBoolean(def);
     184              : 
     185              :             /*
     186              :              * Only the superuser may set this option on a user mapping, or
     187              :              * alter a user mapping on which this option is set. We allow a
     188              :              * user to clear this option if it's set - in fact, we don't have
     189              :              * a choice since we can't see the old mapping when validating an
     190              :              * alter.
     191              :              */
     192            6 :             if (!superuser() && !pw_required)
     193            1 :                 ereport(ERROR,
     194              :                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     195              :                          errmsg("password_required=false is superuser-only"),
     196              :                          errhint("User mappings with the password_required option set to false may only be created or modified by the superuser.")));
     197              :         }
     198          554 :         else if (strcmp(def->defname, "sslcert") == 0 ||
     199          544 :                  strcmp(def->defname, "sslkey") == 0)
     200              :         {
     201              :             /* similarly for sslcert / sslkey on user mapping */
     202           20 :             if (catalog == UserMappingRelationId && !superuser())
     203            2 :                 ereport(ERROR,
     204              :                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     205              :                          errmsg("sslcert and sslkey are superuser-only"),
     206              :                          errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser.")));
     207              :         }
     208          534 :         else if (strcmp(def->defname, "analyze_sampling") == 0)
     209              :         {
     210              :             char       *value;
     211              : 
     212            7 :             value = defGetString(def);
     213              : 
     214              :             /* we recognize off/auto/random/system/bernoulli */
     215            7 :             if (strcmp(value, "off") != 0 &&
     216            5 :                 strcmp(value, "auto") != 0 &&
     217            4 :                 strcmp(value, "random") != 0 &&
     218            3 :                 strcmp(value, "system") != 0 &&
     219            2 :                 strcmp(value, "bernoulli") != 0)
     220            1 :                 ereport(ERROR,
     221              :                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     222              :                          errmsg("invalid value for string option \"%s\": %s",
     223              :                                 def->defname, value)));
     224              :         }
     225              :     }
     226              : 
     227          294 :     PG_RETURN_VOID();
     228              : }
     229              : 
     230              : /*
     231              :  * Initialize option lists.
     232              :  */
     233              : static void
     234          474 : InitPgFdwOptions(void)
     235              : {
     236              :     int         num_libpq_opts;
     237              :     PQconninfoOption *libpq_options;
     238              :     PQconninfoOption *lopt;
     239              :     PgFdwOption *popt;
     240              : 
     241              :     /* non-libpq FDW-specific FDW options */
     242              :     static const PgFdwOption non_libpq_options[] = {
     243              :         {"schema_name", ForeignTableRelationId, false},
     244              :         {"table_name", ForeignTableRelationId, false},
     245              :         {"column_name", AttributeRelationId, false},
     246              :         /* use_remote_estimate is available on both server and table */
     247              :         {"use_remote_estimate", ForeignServerRelationId, false},
     248              :         {"use_remote_estimate", ForeignTableRelationId, false},
     249              :         /* cost factors */
     250              :         {"fdw_startup_cost", ForeignServerRelationId, false},
     251              :         {"fdw_tuple_cost", ForeignServerRelationId, false},
     252              :         /* shippable extensions */
     253              :         {"extensions", ForeignServerRelationId, false},
     254              :         /* updatable is available on both server and table */
     255              :         {"updatable", ForeignServerRelationId, false},
     256              :         {"updatable", ForeignTableRelationId, false},
     257              :         /* truncatable is available on both server and table */
     258              :         {"truncatable", ForeignServerRelationId, false},
     259              :         {"truncatable", ForeignTableRelationId, false},
     260              :         /* fetch_size is available on both server and table */
     261              :         {"fetch_size", ForeignServerRelationId, false},
     262              :         {"fetch_size", ForeignTableRelationId, false},
     263              :         /* batch_size is available on both server and table */
     264              :         {"batch_size", ForeignServerRelationId, false},
     265              :         {"batch_size", ForeignTableRelationId, false},
     266              :         /* async_capable is available on both server and table */
     267              :         {"async_capable", ForeignServerRelationId, false},
     268              :         {"async_capable", ForeignTableRelationId, false},
     269              :         {"parallel_commit", ForeignServerRelationId, false},
     270              :         {"parallel_abort", ForeignServerRelationId, false},
     271              :         {"keep_connections", ForeignServerRelationId, false},
     272              :         {"password_required", UserMappingRelationId, false},
     273              : 
     274              :         /* sampling is available on both server and table */
     275              :         {"analyze_sampling", ForeignServerRelationId, false},
     276              :         {"analyze_sampling", ForeignTableRelationId, false},
     277              : 
     278              :         {"use_scram_passthrough", ForeignServerRelationId, false},
     279              :         {"use_scram_passthrough", UserMappingRelationId, false},
     280              : 
     281              :         /*
     282              :          * sslcert and sslkey are in fact libpq options, but we repeat them
     283              :          * here to allow them to appear in both foreign server context (when
     284              :          * we generate libpq options) and user mapping context (from here).
     285              :          */
     286              :         {"sslcert", UserMappingRelationId, true},
     287              :         {"sslkey", UserMappingRelationId, true},
     288              : 
     289              :         /*
     290              :          * gssdelegation is also a libpq option but should be allowed in a
     291              :          * user mapping context too
     292              :          */
     293              :         {"gssdelegation", UserMappingRelationId, true},
     294              : 
     295              :         {NULL, InvalidOid, false}
     296              :     };
     297              : 
     298              :     /* Prevent redundant initialization. */
     299          474 :     if (postgres_fdw_options)
     300          457 :         return;
     301              : 
     302              :     /*
     303              :      * Get list of valid libpq options.
     304              :      *
     305              :      * To avoid unnecessary work, we get the list once and use it throughout
     306              :      * the lifetime of this backend process.  Hence, we'll allocate it in
     307              :      * TopMemoryContext.
     308              :      */
     309           17 :     libpq_options = PQconndefaults();
     310           17 :     if (!libpq_options)         /* assume reason for failure is OOM */
     311            0 :         ereport(ERROR,
     312              :                 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
     313              :                  errmsg("out of memory"),
     314              :                  errdetail("Could not get libpq's default connection options.")));
     315              : 
     316              :     /* Count how many libpq options are available. */
     317           17 :     num_libpq_opts = 0;
     318          884 :     for (lopt = libpq_options; lopt->keyword; lopt++)
     319          867 :         num_libpq_opts++;
     320              : 
     321              :     /*
     322              :      * Construct an array which consists of all valid options for
     323              :      * postgres_fdw, by appending FDW-specific options to libpq options.
     324              :      */
     325           17 :     postgres_fdw_options = (PgFdwOption *)
     326           17 :         MemoryContextAlloc(TopMemoryContext,
     327           17 :                            sizeof(PgFdwOption) * num_libpq_opts +
     328              :                            sizeof(non_libpq_options));
     329              : 
     330           17 :     popt = postgres_fdw_options;
     331          884 :     for (lopt = libpq_options; lopt->keyword; lopt++)
     332              :     {
     333              :         /* Hide debug options, as well as settings we override internally. */
     334          867 :         if (strchr(lopt->dispchar, 'D') ||
     335          799 :             strcmp(lopt->keyword, "fallback_application_name") == 0 ||
     336          782 :             strcmp(lopt->keyword, "client_encoding") == 0)
     337          102 :             continue;
     338              : 
     339              :         /*
     340              :          * Disallow OAuth options for now, since the builtin flow communicates
     341              :          * on stderr by default and can't cache tokens yet.
     342              :          */
     343          765 :         if (strncmp(lopt->keyword, "oauth_", strlen("oauth_")) == 0)
     344           68 :             continue;
     345              : 
     346         1394 :         popt->keyword = MemoryContextStrdup(TopMemoryContext,
     347          697 :                                             lopt->keyword);
     348              : 
     349              :         /*
     350              :          * "user" and any secret options are allowed only on user mappings.
     351              :          * Everything else is a server option.
     352              :          */
     353          697 :         if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
     354           51 :             popt->optcontext = UserMappingRelationId;
     355              :         else
     356          646 :             popt->optcontext = ForeignServerRelationId;
     357          697 :         popt->is_libpq_opt = true;
     358              : 
     359          697 :         popt++;
     360              :     }
     361              : 
     362              :     /* Done with libpq's output structure. */
     363           17 :     PQconninfoFree(libpq_options);
     364              : 
     365              :     /* Append FDW-specific options and dummy terminator. */
     366           17 :     memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
     367              : }
     368              : 
     369              : /*
     370              :  * Check whether the given option is one of the valid postgres_fdw options.
     371              :  * context is the Oid of the catalog holding the object the option is for.
     372              :  */
     373              : static bool
     374          723 : is_valid_option(const char *keyword, Oid context)
     375              : {
     376              :     PgFdwOption *opt;
     377              : 
     378              :     Assert(postgres_fdw_options);   /* must be initialized already */
     379              : 
     380        23829 :     for (opt = postgres_fdw_options; opt->keyword; opt++)
     381              :     {
     382        23822 :         if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
     383          716 :             return true;
     384              :     }
     385              : 
     386            7 :     return false;
     387              : }
     388              : 
     389              : /*
     390              :  * Check whether the given option is one of the valid libpq options.
     391              :  */
     392              : static bool
     393          303 : is_libpq_option(const char *keyword)
     394              : {
     395              :     PgFdwOption *opt;
     396              : 
     397              :     Assert(postgres_fdw_options);   /* must be initialized already */
     398              : 
     399         9304 :     for (opt = postgres_fdw_options; opt->keyword; opt++)
     400              :     {
     401         9200 :         if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
     402          199 :             return true;
     403              :     }
     404              : 
     405          104 :     return false;
     406              : }
     407              : 
     408              : /*
     409              :  * Generate key-value arrays which include only libpq options from the
     410              :  * given list (which can contain any kind of options).  Caller must have
     411              :  * allocated large-enough arrays.  Returns number of options found.
     412              :  */
     413              : int
     414          164 : ExtractConnectionOptions(List *defelems, const char **keywords,
     415              :                          const char **values)
     416              : {
     417              :     ListCell   *lc;
     418              :     int         i;
     419              : 
     420              :     /* Build our options lists if we didn't yet. */
     421          164 :     InitPgFdwOptions();
     422              : 
     423          164 :     i = 0;
     424          467 :     foreach(lc, defelems)
     425              :     {
     426          303 :         DefElem    *d = (DefElem *) lfirst(lc);
     427              : 
     428          303 :         if (is_libpq_option(d->defname))
     429              :         {
     430          199 :             keywords[i] = d->defname;
     431          199 :             values[i] = defGetString(d);
     432          199 :             i++;
     433              :         }
     434              :     }
     435          164 :     return i;
     436              : }
     437              : 
     438              : /*
     439              :  * Parse a comma-separated string and return a List of the OIDs of the
     440              :  * extensions named in the string.  If any names in the list cannot be
     441              :  * found, report a warning if warnOnMissing is true, else just silently
     442              :  * ignore them.
     443              :  */
     444              : List *
     445          961 : ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
     446              : {
     447          961 :     List       *extensionOids = NIL;
     448              :     List       *extlist;
     449              :     ListCell   *lc;
     450              : 
     451              :     /* SplitIdentifierString scribbles on its input, so pstrdup first */
     452          961 :     if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
     453              :     {
     454              :         /* syntax error in name list */
     455            1 :         ereport(ERROR,
     456              :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     457              :                  errmsg("parameter \"%s\" must be a list of extension names",
     458              :                         "extensions")));
     459              :     }
     460              : 
     461         1921 :     foreach(lc, extlist)
     462              :     {
     463          961 :         const char *extension_name = (const char *) lfirst(lc);
     464          961 :         Oid         extension_oid = get_extension_oid(extension_name, true);
     465              : 
     466          961 :         if (OidIsValid(extension_oid))
     467              :         {
     468          959 :             extensionOids = lappend_oid(extensionOids, extension_oid);
     469              :         }
     470            2 :         else if (warnOnMissing)
     471              :         {
     472            2 :             ereport(WARNING,
     473              :                     (errcode(ERRCODE_UNDEFINED_OBJECT),
     474              :                      errmsg("extension \"%s\" is not installed",
     475              :                             extension_name)));
     476              :         }
     477              :     }
     478              : 
     479          960 :     list_free(extlist);
     480          960 :     return extensionOids;
     481              : }
     482              : 
     483              : /*
     484              :  * Replace escape sequences beginning with % character in the given
     485              :  * application_name with status information, and return it.
     486              :  *
     487              :  * This function always returns a palloc'd string, so the caller is
     488              :  * responsible for pfreeing it.
     489              :  */
     490              : char *
     491           23 : process_pgfdw_appname(const char *appname)
     492              : {
     493              :     const char *p;
     494              :     StringInfoData buf;
     495              : 
     496           23 :     initStringInfo(&buf);
     497              : 
     498          331 :     for (p = appname; *p != '\0'; p++)
     499              :     {
     500          308 :         if (*p != '%')
     501              :         {
     502              :             /* literal char, just copy */
     503          299 :             appendStringInfoChar(&buf, *p);
     504          299 :             continue;
     505              :         }
     506              : 
     507              :         /* must be a '%', so skip to the next char */
     508            9 :         p++;
     509            9 :         if (*p == '\0')
     510            0 :             break;              /* format error - ignore it */
     511            9 :         else if (*p == '%')
     512              :         {
     513              :             /* string contains %% */
     514            1 :             appendStringInfoChar(&buf, '%');
     515            1 :             continue;
     516              :         }
     517              : 
     518              :         /* process the option */
     519            8 :         switch (*p)
     520              :         {
     521            1 :             case 'a':
     522            1 :                 appendStringInfoString(&buf, application_name);
     523            1 :                 break;
     524            2 :             case 'c':
     525            2 :                 appendStringInfo(&buf, "%" PRIx64 ".%x", MyStartTime, MyProcPid);
     526            2 :                 break;
     527            2 :             case 'C':
     528            2 :                 appendStringInfoString(&buf, cluster_name);
     529            2 :                 break;
     530            1 :             case 'd':
     531            1 :                 if (MyProcPort)
     532              :                 {
     533            1 :                     const char *dbname = MyProcPort->database_name;
     534              : 
     535            1 :                     if (dbname)
     536            1 :                         appendStringInfoString(&buf, dbname);
     537              :                     else
     538            0 :                         appendStringInfoString(&buf, "[unknown]");
     539              :                 }
     540            1 :                 break;
     541            1 :             case 'p':
     542            1 :                 appendStringInfo(&buf, "%d", MyProcPid);
     543            1 :                 break;
     544            1 :             case 'u':
     545            1 :                 if (MyProcPort)
     546              :                 {
     547            1 :                     const char *username = MyProcPort->user_name;
     548              : 
     549            1 :                     if (username)
     550            1 :                         appendStringInfoString(&buf, username);
     551              :                     else
     552            0 :                         appendStringInfoString(&buf, "[unknown]");
     553              :                 }
     554            1 :                 break;
     555            0 :             default:
     556              :                 /* format error - ignore it */
     557            0 :                 break;
     558              :         }
     559              :     }
     560              : 
     561           23 :     return buf.data;
     562              : }
     563              : 
     564              : /*
     565              :  * Module load callback
     566              :  */
     567              : void
     568           17 : _PG_init(void)
     569              : {
     570              :     /*
     571              :      * Unlike application_name GUC, don't set GUC_IS_NAME flag nor check_hook
     572              :      * to allow postgres_fdw.application_name to be any string more than
     573              :      * NAMEDATALEN characters and to include non-ASCII characters. Instead,
     574              :      * remote server truncates application_name of remote connection to less
     575              :      * than NAMEDATALEN and replaces any non-ASCII characters in it with a '?'
     576              :      * character.
     577              :      */
     578           17 :     DefineCustomStringVariable("postgres_fdw.application_name",
     579              :                                "Sets the application name to be used on the remote server.",
     580              :                                NULL,
     581              :                                &pgfdw_application_name,
     582              :                                NULL,
     583              :                                PGC_USERSET,
     584              :                                0,
     585              :                                NULL,
     586              :                                NULL,
     587              :                                NULL);
     588              : 
     589           17 :     MarkGUCPrefixReserved("postgres_fdw");
     590           17 : }
        

Generated by: LCOV version 2.0-1