LCOV - code coverage report
Current view: top level - contrib/postgres_fdw - option.c (source / functions) Hit Total Coverage
Test: PostgreSQL 14devel Lines: 98 103 95.1 %
Date: 2021-05-13 08:06:46 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-2021, 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         392 : postgres_fdw_validator(PG_FUNCTION_ARGS)
      66             : {
      67         392 :     List       *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
      68         392 :     Oid         catalog = PG_GETARG_OID(1);
      69             :     ListCell   *cell;
      70             : 
      71             :     /* Build our options lists if we didn't yet. */
      72         392 :     InitPgFdwOptions();
      73             : 
      74             :     /*
      75             :      * Check that only options supported by postgres_fdw, and allowed for the
      76             :      * current object type, are given.
      77             :      */
      78        1176 :     foreach(cell, options_list)
      79             :     {
      80         796 :         DefElem    *def = (DefElem *) lfirst(cell);
      81             : 
      82         796 :         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         224 :             for (opt = postgres_fdw_options; opt->keyword; opt++)
      93             :             {
      94         220 :                 if (catalog == opt->optcontext)
      95          92 :                     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         792 :         if (strcmp(def->defname, "use_remote_estimate") == 0 ||
     110         756 :             strcmp(def->defname, "updatable") == 0 ||
     111         748 :             strcmp(def->defname, "truncatable") == 0 ||
     112         738 :             strcmp(def->defname, "async_capable") == 0 ||
     113         734 :             strcmp(def->defname, "keep_connections") == 0)
     114             :         {
     115             :             /* these accept only boolean values */
     116          68 :             (void) defGetBoolean(def);
     117             :         }
     118         724 :         else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
     119         716 :                  strcmp(def->defname, "fdw_tuple_cost") == 0)
     120          16 :         {
     121             :             /* these must have a non-negative numeric value */
     122             :             double      val;
     123             :             char       *endp;
     124             : 
     125          16 :             val = strtod(defGetString(def), &endp);
     126          16 :             if (*endp || val < 0)
     127           0 :                 ereport(ERROR,
     128             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     129             :                          errmsg("%s requires a non-negative numeric value",
     130             :                                 def->defname)));
     131             :         }
     132         708 :         else if (strcmp(def->defname, "extensions") == 0)
     133             :         {
     134             :             /* check list syntax, warn about uninstalled extensions */
     135          32 :             (void) ExtractExtensionList(defGetString(def), true);
     136             :         }
     137         676 :         else if (strcmp(def->defname, "fetch_size") == 0)
     138             :         {
     139             :             int         fetch_size;
     140             : 
     141           8 :             fetch_size = strtol(defGetString(def), NULL, 10);
     142           8 :             if (fetch_size <= 0)
     143           0 :                 ereport(ERROR,
     144             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     145             :                          errmsg("%s requires a non-negative integer value",
     146             :                                 def->defname)));
     147             :         }
     148         668 :         else if (strcmp(def->defname, "batch_size") == 0)
     149             :         {
     150             :             int         batch_size;
     151             : 
     152          18 :             batch_size = strtol(defGetString(def), NULL, 10);
     153          18 :             if (batch_size <= 0)
     154           0 :                 ereport(ERROR,
     155             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     156             :                          errmsg("%s requires a non-negative integer value",
     157             :                                 def->defname)));
     158             :         }
     159         650 :         else if (strcmp(def->defname, "password_required") == 0)
     160             :         {
     161          10 :             bool        pw_required = defGetBoolean(def);
     162             : 
     163             :             /*
     164             :              * Only the superuser may set this option on a user mapping, or
     165             :              * alter a user mapping on which this option is set. We allow a
     166             :              * user to clear this option if it's set - in fact, we don't have
     167             :              * a choice since we can't see the old mapping when validating an
     168             :              * alter.
     169             :              */
     170          10 :             if (!superuser() && !pw_required)
     171           2 :                 ereport(ERROR,
     172             :                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     173             :                          errmsg("password_required=false is superuser-only"),
     174             :                          errhint("User mappings with the password_required option set to false may only be created or modified by the superuser")));
     175             :         }
     176         640 :         else if (strcmp(def->defname, "sslcert") == 0 ||
     177         628 :                  strcmp(def->defname, "sslkey") == 0)
     178             :         {
     179             :             /* similarly for sslcert / sslkey on user mapping */
     180          24 :             if (catalog == UserMappingRelationId && !superuser())
     181           4 :                 ereport(ERROR,
     182             :                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     183             :                          errmsg("sslcert and sslkey are superuser-only"),
     184             :                          errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser")));
     185             :         }
     186             :     }
     187             : 
     188         380 :     PG_RETURN_VOID();
     189             : }
     190             : 
     191             : /*
     192             :  * Initialize option lists.
     193             :  */
     194             : static void
     195         552 : InitPgFdwOptions(void)
     196             : {
     197             :     int         num_libpq_opts;
     198             :     PQconninfoOption *lopt;
     199             :     PgFdwOption *popt;
     200             : 
     201             :     /* non-libpq FDW-specific FDW options */
     202             :     static const PgFdwOption non_libpq_options[] = {
     203             :         {"schema_name", ForeignTableRelationId, false},
     204             :         {"table_name", ForeignTableRelationId, false},
     205             :         {"column_name", AttributeRelationId, false},
     206             :         /* use_remote_estimate is available on both server and table */
     207             :         {"use_remote_estimate", ForeignServerRelationId, false},
     208             :         {"use_remote_estimate", ForeignTableRelationId, false},
     209             :         /* cost factors */
     210             :         {"fdw_startup_cost", ForeignServerRelationId, false},
     211             :         {"fdw_tuple_cost", ForeignServerRelationId, false},
     212             :         /* shippable extensions */
     213             :         {"extensions", ForeignServerRelationId, false},
     214             :         /* updatable is available on both server and table */
     215             :         {"updatable", ForeignServerRelationId, false},
     216             :         {"updatable", ForeignTableRelationId, false},
     217             :         /* truncatable is available on both server and table */
     218             :         {"truncatable", ForeignServerRelationId, false},
     219             :         {"truncatable", ForeignTableRelationId, false},
     220             :         /* fetch_size is available on both server and table */
     221             :         {"fetch_size", ForeignServerRelationId, false},
     222             :         {"fetch_size", ForeignTableRelationId, false},
     223             :         /* batch_size is available on both server and table */
     224             :         {"batch_size", ForeignServerRelationId, false},
     225             :         {"batch_size", ForeignTableRelationId, false},
     226             :         /* async_capable is available on both server and table */
     227             :         {"async_capable", ForeignServerRelationId, false},
     228             :         {"async_capable", ForeignTableRelationId, false},
     229             :         {"keep_connections", ForeignServerRelationId, false},
     230             :         {"password_required", UserMappingRelationId, false},
     231             : 
     232             :         /*
     233             :          * sslcert and sslkey are in fact libpq options, but we repeat them
     234             :          * here to allow them to appear in both foreign server context (when
     235             :          * we generate libpq options) and user mapping context (from here).
     236             :          */
     237             :         {"sslcert", UserMappingRelationId, true},
     238             :         {"sslkey", UserMappingRelationId, true},
     239             : 
     240             :         {NULL, InvalidOid, false}
     241             :     };
     242             : 
     243             :     /* Prevent redundant initialization. */
     244         552 :     if (postgres_fdw_options)
     245         546 :         return;
     246             : 
     247             :     /*
     248             :      * Get list of valid libpq options.
     249             :      *
     250             :      * To avoid unnecessary work, we get the list once and use it throughout
     251             :      * the lifetime of this backend process.  We don't need to care about
     252             :      * memory context issues, because PQconndefaults allocates with malloc.
     253             :      */
     254           6 :     libpq_options = PQconndefaults();
     255           6 :     if (!libpq_options)         /* assume reason for failure is OOM */
     256           0 :         ereport(ERROR,
     257             :                 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
     258             :                  errmsg("out of memory"),
     259             :                  errdetail("Could not get libpq's default connection options.")));
     260             : 
     261             :     /* Count how many libpq options are available. */
     262           6 :     num_libpq_opts = 0;
     263         222 :     for (lopt = libpq_options; lopt->keyword; lopt++)
     264         216 :         num_libpq_opts++;
     265             : 
     266             :     /*
     267             :      * Construct an array which consists of all valid options for
     268             :      * postgres_fdw, by appending FDW-specific options to libpq options.
     269             :      *
     270             :      * We use plain malloc here to allocate postgres_fdw_options because it
     271             :      * lives as long as the backend process does.  Besides, keeping
     272             :      * libpq_options in memory allows us to avoid copying every keyword
     273             :      * string.
     274             :      */
     275           6 :     postgres_fdw_options = (PgFdwOption *)
     276           6 :         malloc(sizeof(PgFdwOption) * num_libpq_opts +
     277             :                sizeof(non_libpq_options));
     278           6 :     if (postgres_fdw_options == NULL)
     279           0 :         ereport(ERROR,
     280             :                 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
     281             :                  errmsg("out of memory")));
     282             : 
     283           6 :     popt = postgres_fdw_options;
     284         222 :     for (lopt = libpq_options; lopt->keyword; lopt++)
     285             :     {
     286             :         /* Hide debug options, as well as settings we override internally. */
     287         216 :         if (strchr(lopt->dispchar, 'D') ||
     288         210 :             strcmp(lopt->keyword, "fallback_application_name") == 0 ||
     289         204 :             strcmp(lopt->keyword, "client_encoding") == 0)
     290          18 :             continue;
     291             : 
     292             :         /* We don't have to copy keyword string, as described above. */
     293         198 :         popt->keyword = lopt->keyword;
     294             : 
     295             :         /*
     296             :          * "user" and any secret options are allowed only on user mappings.
     297             :          * Everything else is a server option.
     298             :          */
     299         198 :         if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
     300          18 :             popt->optcontext = UserMappingRelationId;
     301             :         else
     302         180 :             popt->optcontext = ForeignServerRelationId;
     303         198 :         popt->is_libpq_opt = true;
     304             : 
     305         198 :         popt++;
     306             :     }
     307             : 
     308             :     /* Append FDW-specific options and dummy terminator. */
     309           6 :     memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
     310             : }
     311             : 
     312             : /*
     313             :  * Check whether the given option is one of the valid postgres_fdw options.
     314             :  * context is the Oid of the catalog holding the object the option is for.
     315             :  */
     316             : static bool
     317         796 : is_valid_option(const char *keyword, Oid context)
     318             : {
     319             :     PgFdwOption *opt;
     320             : 
     321             :     Assert(postgres_fdw_options);   /* must be initialized already */
     322             : 
     323       22428 :     for (opt = postgres_fdw_options; opt->keyword; opt++)
     324             :     {
     325       22424 :         if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
     326         792 :             return true;
     327             :     }
     328             : 
     329           4 :     return false;
     330             : }
     331             : 
     332             : /*
     333             :  * Check whether the given option is one of the valid libpq options.
     334             :  */
     335             : static bool
     336         254 : is_libpq_option(const char *keyword)
     337             : {
     338             :     PgFdwOption *opt;
     339             : 
     340             :     Assert(postgres_fdw_options);   /* must be initialized already */
     341             : 
     342        5406 :     for (opt = postgres_fdw_options; opt->keyword; opt++)
     343             :     {
     344        5338 :         if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
     345         186 :             return true;
     346             :     }
     347             : 
     348          68 :     return false;
     349             : }
     350             : 
     351             : /*
     352             :  * Generate key-value arrays which include only libpq options from the
     353             :  * given list (which can contain any kind of options).  Caller must have
     354             :  * allocated large-enough arrays.  Returns number of options found.
     355             :  */
     356             : int
     357         160 : ExtractConnectionOptions(List *defelems, const char **keywords,
     358             :                          const char **values)
     359             : {
     360             :     ListCell   *lc;
     361             :     int         i;
     362             : 
     363             :     /* Build our options lists if we didn't yet. */
     364         160 :     InitPgFdwOptions();
     365             : 
     366         160 :     i = 0;
     367         414 :     foreach(lc, defelems)
     368             :     {
     369         254 :         DefElem    *d = (DefElem *) lfirst(lc);
     370             : 
     371         254 :         if (is_libpq_option(d->defname))
     372             :         {
     373         186 :             keywords[i] = d->defname;
     374         186 :             values[i] = defGetString(d);
     375         186 :             i++;
     376             :         }
     377             :     }
     378         160 :     return i;
     379             : }
     380             : 
     381             : /*
     382             :  * Parse a comma-separated string and return a List of the OIDs of the
     383             :  * extensions named in the string.  If any names in the list cannot be
     384             :  * found, report a warning if warnOnMissing is true, else just silently
     385             :  * ignore them.
     386             :  */
     387             : List *
     388        1512 : ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
     389             : {
     390        1512 :     List       *extensionOids = NIL;
     391             :     List       *extlist;
     392             :     ListCell   *lc;
     393             : 
     394             :     /* SplitIdentifierString scribbles on its input, so pstrdup first */
     395        1512 :     if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
     396             :     {
     397             :         /* syntax error in name list */
     398           2 :         ereport(ERROR,
     399             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     400             :                  errmsg("parameter \"%s\" must be a list of extension names",
     401             :                         "extensions")));
     402             :     }
     403             : 
     404        3022 :     foreach(lc, extlist)
     405             :     {
     406        1512 :         const char *extension_name = (const char *) lfirst(lc);
     407        1512 :         Oid         extension_oid = get_extension_oid(extension_name, true);
     408             : 
     409        1512 :         if (OidIsValid(extension_oid))
     410             :         {
     411        1508 :             extensionOids = lappend_oid(extensionOids, extension_oid);
     412             :         }
     413           4 :         else if (warnOnMissing)
     414             :         {
     415           4 :             ereport(WARNING,
     416             :                     (errcode(ERRCODE_UNDEFINED_OBJECT),
     417             :                      errmsg("extension \"%s\" is not installed",
     418             :                             extension_name)));
     419             :         }
     420             :     }
     421             : 
     422        1510 :     list_free(extlist);
     423        1510 :     return extensionOids;
     424             : }

Generated by: LCOV version 1.13