LCOV - code coverage report
Current view: top level - contrib/postgres_fdw - option.c (source / functions) Hit Total Coverage
Test: PostgreSQL 14devel Lines: 92 96 95.8 %
Date: 2020-11-27 11:06:40 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * option.c
       4             :  *        FDW option handling for postgres_fdw
       5             :  *
       6             :  * Portions Copyright (c) 2012-2020, 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 "postgres_fdw.h"
      22             : #include "utils/builtins.h"
      23             : #include "utils/varlena.h"
      24             : 
      25             : /*
      26             :  * Describes the valid options for objects that this wrapper uses.
      27             :  */
      28             : typedef struct PgFdwOption
      29             : {
      30             :     const char *keyword;
      31             :     Oid         optcontext;     /* OID of catalog in which option may appear */
      32             :     bool        is_libpq_opt;   /* true if it's used in libpq */
      33             : } PgFdwOption;
      34             : 
      35             : /*
      36             :  * Valid options for postgres_fdw.
      37             :  * Allocated and filled in InitPgFdwOptions.
      38             :  */
      39             : static PgFdwOption *postgres_fdw_options;
      40             : 
      41             : /*
      42             :  * Valid options for libpq.
      43             :  * Allocated and filled in InitPgFdwOptions.
      44             :  */
      45             : static PQconninfoOption *libpq_options;
      46             : 
      47             : /*
      48             :  * Helper functions
      49             :  */
      50             : static void InitPgFdwOptions(void);
      51             : static bool is_valid_option(const char *keyword, Oid context);
      52             : static bool is_libpq_option(const char *keyword);
      53             : 
      54             : #include "miscadmin.h"
      55             : 
      56             : /*
      57             :  * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
      58             :  * USER MAPPING or FOREIGN TABLE that uses postgres_fdw.
      59             :  *
      60             :  * Raise an ERROR if the option or its value is considered invalid.
      61             :  */
      62           4 : PG_FUNCTION_INFO_V1(postgres_fdw_validator);
      63             : 
      64             : Datum
      65         302 : postgres_fdw_validator(PG_FUNCTION_ARGS)
      66             : {
      67         302 :     List       *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
      68         302 :     Oid         catalog = PG_GETARG_OID(1);
      69             :     ListCell   *cell;
      70             : 
      71             :     /* Build our options lists if we didn't yet. */
      72         302 :     InitPgFdwOptions();
      73             : 
      74             :     /*
      75             :      * Check that only options supported by postgres_fdw, and allowed for the
      76             :      * current object type, are given.
      77             :      */
      78         904 :     foreach(cell, options_list)
      79             :     {
      80         614 :         DefElem    *def = (DefElem *) lfirst(cell);
      81             : 
      82         614 :         if (!is_valid_option(def->defname, catalog))
      83             :         {
      84             :             /*
      85             :              * Unknown option specified, complain about it. Provide a hint
      86             :              * with list of valid options for the object.
      87             :              */
      88             :             PgFdwOption *opt;
      89             :             StringInfoData buf;
      90             : 
      91           4 :             initStringInfo(&buf);
      92         188 :             for (opt = postgres_fdw_options; opt->keyword; opt++)
      93             :             {
      94         184 :                 if (catalog == opt->optcontext)
      95          80 :                     appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
      96             :                                      opt->keyword);
      97             :             }
      98             : 
      99           4 :             ereport(ERROR,
     100             :                     (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
     101             :                      errmsg("invalid option \"%s\"", def->defname),
     102             :                      errhint("Valid options in this context are: %s",
     103             :                              buf.data)));
     104             :         }
     105             : 
     106             :         /*
     107             :          * Validate option value, when we can do so without any context.
     108             :          */
     109         610 :         if (strcmp(def->defname, "use_remote_estimate") == 0 ||
     110         590 :             strcmp(def->defname, "updatable") == 0)
     111             :         {
     112             :             /* these accept only boolean values */
     113          28 :             (void) defGetBoolean(def);
     114             :         }
     115         582 :         else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
     116         574 :                  strcmp(def->defname, "fdw_tuple_cost") == 0)
     117          16 :         {
     118             :             /* these must have a non-negative numeric value */
     119             :             double      val;
     120             :             char       *endp;
     121             : 
     122          16 :             val = strtod(defGetString(def), &endp);
     123          16 :             if (*endp || val < 0)
     124           0 :                 ereport(ERROR,
     125             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     126             :                          errmsg("%s requires a non-negative numeric value",
     127             :                                 def->defname)));
     128             :         }
     129         566 :         else if (strcmp(def->defname, "extensions") == 0)
     130             :         {
     131             :             /* check list syntax, warn about uninstalled extensions */
     132          22 :             (void) ExtractExtensionList(defGetString(def), true);
     133             :         }
     134         544 :         else if (strcmp(def->defname, "fetch_size") == 0)
     135             :         {
     136             :             int         fetch_size;
     137             : 
     138           8 :             fetch_size = strtol(defGetString(def), NULL, 10);
     139           8 :             if (fetch_size <= 0)
     140           0 :                 ereport(ERROR,
     141             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     142             :                          errmsg("%s requires a non-negative integer value",
     143             :                                 def->defname)));
     144             :         }
     145         536 :         else if (strcmp(def->defname, "password_required") == 0)
     146             :         {
     147          10 :             bool        pw_required = defGetBoolean(def);
     148             : 
     149             :             /*
     150             :              * Only the superuser may set this option on a user mapping, or
     151             :              * alter a user mapping on which this option is set. We allow a
     152             :              * user to clear this option if it's set - in fact, we don't have
     153             :              * a choice since we can't see the old mapping when validating an
     154             :              * alter.
     155             :              */
     156          10 :             if (!superuser() && !pw_required)
     157           2 :                 ereport(ERROR,
     158             :                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     159             :                          errmsg("password_required=false is superuser-only"),
     160             :                          errhint("User mappings with the password_required option set to false may only be created or modified by the superuser")));
     161             :         }
     162         526 :         else if (strcmp(def->defname, "sslcert") == 0 ||
     163         514 :                  strcmp(def->defname, "sslkey") == 0)
     164             :         {
     165             :             /* similarly for sslcert / sslkey on user mapping */
     166          24 :             if (catalog == UserMappingRelationId && !superuser())
     167           4 :                 ereport(ERROR,
     168             :                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     169             :                          errmsg("sslcert and sslkey are superuser-only"),
     170             :                          errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser")));
     171             :         }
     172             :     }
     173             : 
     174         290 :     PG_RETURN_VOID();
     175             : }
     176             : 
     177             : /*
     178             :  * Initialize option lists.
     179             :  */
     180             : static void
     181         414 : InitPgFdwOptions(void)
     182             : {
     183             :     int         num_libpq_opts;
     184             :     PQconninfoOption *lopt;
     185             :     PgFdwOption *popt;
     186             : 
     187             :     /* non-libpq FDW-specific FDW options */
     188             :     static const PgFdwOption non_libpq_options[] = {
     189             :         {"schema_name", ForeignTableRelationId, false},
     190             :         {"table_name", ForeignTableRelationId, false},
     191             :         {"column_name", AttributeRelationId, false},
     192             :         /* use_remote_estimate is available on both server and table */
     193             :         {"use_remote_estimate", ForeignServerRelationId, false},
     194             :         {"use_remote_estimate", ForeignTableRelationId, false},
     195             :         /* cost factors */
     196             :         {"fdw_startup_cost", ForeignServerRelationId, false},
     197             :         {"fdw_tuple_cost", ForeignServerRelationId, false},
     198             :         /* shippable extensions */
     199             :         {"extensions", ForeignServerRelationId, false},
     200             :         /* updatable is available on both server and table */
     201             :         {"updatable", ForeignServerRelationId, false},
     202             :         {"updatable", ForeignTableRelationId, false},
     203             :         /* fetch_size is available on both server and table */
     204             :         {"fetch_size", ForeignServerRelationId, false},
     205             :         {"fetch_size", ForeignTableRelationId, false},
     206             :         {"password_required", UserMappingRelationId, false},
     207             : 
     208             :         /*
     209             :          * sslcert and sslkey are in fact libpq options, but we repeat them
     210             :          * here to allow them to appear in both foreign server context (when
     211             :          * we generate libpq options) and user mapping context (from here).
     212             :          */
     213             :         {"sslcert", UserMappingRelationId, true},
     214             :         {"sslkey", UserMappingRelationId, true},
     215             : 
     216             :         {NULL, InvalidOid, false}
     217             :     };
     218             : 
     219             :     /* Prevent redundant initialization. */
     220         414 :     if (postgres_fdw_options)
     221         408 :         return;
     222             : 
     223             :     /*
     224             :      * Get list of valid libpq options.
     225             :      *
     226             :      * To avoid unnecessary work, we get the list once and use it throughout
     227             :      * the lifetime of this backend process.  We don't need to care about
     228             :      * memory context issues, because PQconndefaults allocates with malloc.
     229             :      */
     230           6 :     libpq_options = PQconndefaults();
     231           6 :     if (!libpq_options)         /* assume reason for failure is OOM */
     232           0 :         ereport(ERROR,
     233             :                 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
     234             :                  errmsg("out of memory"),
     235             :                  errdetail("Could not get libpq's default connection options.")));
     236             : 
     237             :     /* Count how many libpq options are available. */
     238           6 :     num_libpq_opts = 0;
     239         222 :     for (lopt = libpq_options; lopt->keyword; lopt++)
     240         216 :         num_libpq_opts++;
     241             : 
     242             :     /*
     243             :      * Construct an array which consists of all valid options for
     244             :      * postgres_fdw, by appending FDW-specific options to libpq options.
     245             :      *
     246             :      * We use plain malloc here to allocate postgres_fdw_options because it
     247             :      * lives as long as the backend process does.  Besides, keeping
     248             :      * libpq_options in memory allows us to avoid copying every keyword
     249             :      * string.
     250             :      */
     251           6 :     postgres_fdw_options = (PgFdwOption *)
     252           6 :         malloc(sizeof(PgFdwOption) * num_libpq_opts +
     253             :                sizeof(non_libpq_options));
     254           6 :     if (postgres_fdw_options == NULL)
     255           0 :         ereport(ERROR,
     256             :                 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
     257             :                  errmsg("out of memory")));
     258             : 
     259           6 :     popt = postgres_fdw_options;
     260         222 :     for (lopt = libpq_options; lopt->keyword; lopt++)
     261             :     {
     262             :         /* Hide debug options, as well as settings we override internally. */
     263         216 :         if (strchr(lopt->dispchar, 'D') ||
     264         198 :             strcmp(lopt->keyword, "fallback_application_name") == 0 ||
     265         192 :             strcmp(lopt->keyword, "client_encoding") == 0)
     266          30 :             continue;
     267             : 
     268             :         /* We don't have to copy keyword string, as described above. */
     269         186 :         popt->keyword = lopt->keyword;
     270             : 
     271             :         /*
     272             :          * "user" and any secret options are allowed only on user mappings.
     273             :          * Everything else is a server option.
     274             :          */
     275         186 :         if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
     276          18 :             popt->optcontext = UserMappingRelationId;
     277             :         else
     278         168 :             popt->optcontext = ForeignServerRelationId;
     279         186 :         popt->is_libpq_opt = true;
     280             : 
     281         186 :         popt++;
     282             :     }
     283             : 
     284             :     /* Append FDW-specific options and dummy terminator. */
     285           6 :     memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
     286             : }
     287             : 
     288             : /*
     289             :  * Check whether the given option is one of the valid postgres_fdw options.
     290             :  * context is the Oid of the catalog holding the object the option is for.
     291             :  */
     292             : static bool
     293         614 : is_valid_option(const char *keyword, Oid context)
     294             : {
     295             :     PgFdwOption *opt;
     296             : 
     297             :     Assert(postgres_fdw_options);   /* must be initialized already */
     298             : 
     299       15896 :     for (opt = postgres_fdw_options; opt->keyword; opt++)
     300             :     {
     301       15892 :         if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
     302         610 :             return true;
     303             :     }
     304             : 
     305           4 :     return false;
     306             : }
     307             : 
     308             : /*
     309             :  * Check whether the given option is one of the valid libpq options.
     310             :  */
     311             : static bool
     312         152 : is_libpq_option(const char *keyword)
     313             : {
     314             :     PgFdwOption *opt;
     315             : 
     316             :     Assert(postgres_fdw_options);   /* must be initialized already */
     317             : 
     318        2338 :     for (opt = postgres_fdw_options; opt->keyword; opt++)
     319             :     {
     320        2310 :         if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
     321         124 :             return true;
     322             :     }
     323             : 
     324          28 :     return false;
     325             : }
     326             : 
     327             : /*
     328             :  * Generate key-value arrays which include only libpq options from the
     329             :  * given list (which can contain any kind of options).  Caller must have
     330             :  * allocated large-enough arrays.  Returns number of options found.
     331             :  */
     332             : int
     333         112 : ExtractConnectionOptions(List *defelems, const char **keywords,
     334             :                          const char **values)
     335             : {
     336             :     ListCell   *lc;
     337             :     int         i;
     338             : 
     339             :     /* Build our options lists if we didn't yet. */
     340         112 :     InitPgFdwOptions();
     341             : 
     342         112 :     i = 0;
     343         264 :     foreach(lc, defelems)
     344             :     {
     345         152 :         DefElem    *d = (DefElem *) lfirst(lc);
     346             : 
     347         152 :         if (is_libpq_option(d->defname))
     348             :         {
     349         124 :             keywords[i] = d->defname;
     350         124 :             values[i] = defGetString(d);
     351         124 :             i++;
     352             :         }
     353             :     }
     354         112 :     return i;
     355             : }
     356             : 
     357             : /*
     358             :  * Parse a comma-separated string and return a List of the OIDs of the
     359             :  * extensions named in the string.  If any names in the list cannot be
     360             :  * found, report a warning if warnOnMissing is true, else just silently
     361             :  * ignore them.
     362             :  */
     363             : List *
     364        1574 : ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
     365             : {
     366        1574 :     List       *extensionOids = NIL;
     367             :     List       *extlist;
     368             :     ListCell   *lc;
     369             : 
     370             :     /* SplitIdentifierString scribbles on its input, so pstrdup first */
     371        1574 :     if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
     372             :     {
     373             :         /* syntax error in name list */
     374           2 :         ereport(ERROR,
     375             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     376             :                  errmsg("parameter \"%s\" must be a list of extension names",
     377             :                         "extensions")));
     378             :     }
     379             : 
     380        3146 :     foreach(lc, extlist)
     381             :     {
     382        1574 :         const char *extension_name = (const char *) lfirst(lc);
     383        1574 :         Oid         extension_oid = get_extension_oid(extension_name, true);
     384             : 
     385        1574 :         if (OidIsValid(extension_oid))
     386             :         {
     387        1570 :             extensionOids = lappend_oid(extensionOids, extension_oid);
     388             :         }
     389           4 :         else if (warnOnMissing)
     390             :         {
     391           4 :             ereport(WARNING,
     392             :                     (errcode(ERRCODE_UNDEFINED_OBJECT),
     393             :                      errmsg("extension \"%s\" is not installed",
     394             :                             extension_name)));
     395             :         }
     396             :     }
     397             : 
     398        1572 :     list_free(extlist);
     399        1572 :     return extensionOids;
     400             : }

Generated by: LCOV version 1.13