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