At update of non-LP_NORMAL TID, fail instead of corrupting page header.
[pgsql.git] / contrib / postgres_fdw / option.c
blobd0766f007d2f9ab2f565ae59202b3b98321fc2ed
1 /*-------------------------------------------------------------------------
3 * option.c
4 * FDW and GUC option handling for postgres_fdw
6 * Portions Copyright (c) 2012-2025, PostgreSQL Global Development Group
8 * IDENTIFICATION
9 * contrib/postgres_fdw/option.c
11 *-------------------------------------------------------------------------
13 #include "postgres.h"
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/varlena.h"
27 * Describes the valid options for objects that this wrapper uses.
29 typedef struct PgFdwOption
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 */
34 } PgFdwOption;
37 * Valid options for postgres_fdw.
38 * Allocated and filled in InitPgFdwOptions.
40 static PgFdwOption *postgres_fdw_options;
43 * Valid options for libpq.
44 * Allocated and filled in InitPgFdwOptions.
46 static PQconninfoOption *libpq_options;
49 * GUC parameters
51 char *pgfdw_application_name = NULL;
54 * Helper functions
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);
60 #include "miscadmin.h"
63 * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
64 * USER MAPPING or FOREIGN TABLE that uses postgres_fdw.
66 * Raise an ERROR if the option or its value is considered invalid.
68 PG_FUNCTION_INFO_V1(postgres_fdw_validator);
70 Datum
71 postgres_fdw_validator(PG_FUNCTION_ARGS)
73 List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
74 Oid catalog = PG_GETARG_OID(1);
75 ListCell *cell;
77 /* Build our options lists if we didn't yet. */
78 InitPgFdwOptions();
81 * Check that only options supported by postgres_fdw, and allowed for the
82 * current object type, are given.
84 foreach(cell, options_list)
86 DefElem *def = (DefElem *) lfirst(cell);
88 if (!is_valid_option(def->defname, catalog))
91 * Unknown option specified, complain about it. Provide a hint
92 * with a valid option that looks similar, if there is one.
94 PgFdwOption *opt;
95 const char *closest_match;
96 ClosestMatchState match_state;
97 bool has_valid_options = false;
99 initClosestMatch(&match_state, def->defname, 4);
100 for (opt = postgres_fdw_options; opt->keyword; opt++)
102 if (catalog == opt->optcontext)
104 has_valid_options = true;
105 updateClosestMatch(&match_state, opt->keyword);
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.")));
120 * Validate option value, when we can do so without any context.
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)
130 /* these accept only boolean values */
131 (void) defGetBoolean(def);
133 else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
134 strcmp(def->defname, "fdw_tuple_cost") == 0)
137 * These must have a floating point value greater than or equal to
138 * zero.
140 char *value;
141 double real_val;
142 bool is_parsed;
144 value = defGetString(def);
145 is_parsed = parse_real(value, &real_val, 0, NULL);
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)));
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)));
159 else if (strcmp(def->defname, "extensions") == 0)
161 /* check list syntax, warn about uninstalled extensions */
162 (void) ExtractExtensionList(defGetString(def), true);
164 else if (strcmp(def->defname, "fetch_size") == 0 ||
165 strcmp(def->defname, "batch_size") == 0)
167 char *value;
168 int int_val;
169 bool is_parsed;
171 value = defGetString(def);
172 is_parsed = parse_int(value, &int_val, 0, NULL);
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)));
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)));
186 else if (strcmp(def->defname, "password_required") == 0)
188 bool pw_required = defGetBoolean(def);
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.
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.")));
203 else if (strcmp(def->defname, "sslcert") == 0 ||
204 strcmp(def->defname, "sslkey") == 0)
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.")));
213 else if (strcmp(def->defname, "analyze_sampling") == 0)
215 char *value;
217 value = defGetString(def);
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)));
232 PG_RETURN_VOID();
236 * Initialize option lists.
238 static void
239 InitPgFdwOptions(void)
241 int num_libpq_opts;
242 PQconninfoOption *lopt;
243 PgFdwOption *popt;
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},
278 /* sampling is available on both server and table */
279 {"analyze_sampling", ForeignServerRelationId, false},
280 {"analyze_sampling", ForeignTableRelationId, false},
282 {"use_scram_passthrough", ForeignServerRelationId, false},
283 {"use_scram_passthrough", UserMappingRelationId, false},
286 * sslcert and sslkey are in fact libpq options, but we repeat them
287 * here to allow them to appear in both foreign server context (when
288 * we generate libpq options) and user mapping context (from here).
290 {"sslcert", UserMappingRelationId, true},
291 {"sslkey", UserMappingRelationId, true},
294 * gssdelegation is also a libpq option but should be allowed in a
295 * user mapping context too
297 {"gssdelegation", UserMappingRelationId, true},
299 {NULL, InvalidOid, false}
302 /* Prevent redundant initialization. */
303 if (postgres_fdw_options)
304 return;
307 * Get list of valid libpq options.
309 * To avoid unnecessary work, we get the list once and use it throughout
310 * the lifetime of this backend process. We don't need to care about
311 * memory context issues, because PQconndefaults allocates with malloc.
313 libpq_options = PQconndefaults();
314 if (!libpq_options) /* assume reason for failure is OOM */
315 ereport(ERROR,
316 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
317 errmsg("out of memory"),
318 errdetail("Could not get libpq's default connection options.")));
320 /* Count how many libpq options are available. */
321 num_libpq_opts = 0;
322 for (lopt = libpq_options; lopt->keyword; lopt++)
323 num_libpq_opts++;
326 * Construct an array which consists of all valid options for
327 * postgres_fdw, by appending FDW-specific options to libpq options.
329 * We use plain malloc here to allocate postgres_fdw_options because it
330 * lives as long as the backend process does. Besides, keeping
331 * libpq_options in memory allows us to avoid copying every keyword
332 * string.
334 postgres_fdw_options = (PgFdwOption *)
335 malloc(sizeof(PgFdwOption) * num_libpq_opts +
336 sizeof(non_libpq_options));
337 if (postgres_fdw_options == NULL)
338 ereport(ERROR,
339 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
340 errmsg("out of memory")));
342 popt = postgres_fdw_options;
343 for (lopt = libpq_options; lopt->keyword; lopt++)
345 /* Hide debug options, as well as settings we override internally. */
346 if (strchr(lopt->dispchar, 'D') ||
347 strcmp(lopt->keyword, "fallback_application_name") == 0 ||
348 strcmp(lopt->keyword, "client_encoding") == 0)
349 continue;
351 /* We don't have to copy keyword string, as described above. */
352 popt->keyword = lopt->keyword;
355 * "user" and any secret options are allowed only on user mappings.
356 * Everything else is a server option.
358 if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
359 popt->optcontext = UserMappingRelationId;
360 else
361 popt->optcontext = ForeignServerRelationId;
362 popt->is_libpq_opt = true;
364 popt++;
367 /* Append FDW-specific options and dummy terminator. */
368 memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
372 * Check whether the given option is one of the valid postgres_fdw options.
373 * context is the Oid of the catalog holding the object the option is for.
375 static bool
376 is_valid_option(const char *keyword, Oid context)
378 PgFdwOption *opt;
380 Assert(postgres_fdw_options); /* must be initialized already */
382 for (opt = postgres_fdw_options; opt->keyword; opt++)
384 if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
385 return true;
388 return false;
392 * Check whether the given option is one of the valid libpq options.
394 static bool
395 is_libpq_option(const char *keyword)
397 PgFdwOption *opt;
399 Assert(postgres_fdw_options); /* must be initialized already */
401 for (opt = postgres_fdw_options; opt->keyword; opt++)
403 if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
404 return true;
407 return false;
411 * Generate key-value arrays which include only libpq options from the
412 * given list (which can contain any kind of options). Caller must have
413 * allocated large-enough arrays. Returns number of options found.
416 ExtractConnectionOptions(List *defelems, const char **keywords,
417 const char **values)
419 ListCell *lc;
420 int i;
422 /* Build our options lists if we didn't yet. */
423 InitPgFdwOptions();
425 i = 0;
426 foreach(lc, defelems)
428 DefElem *d = (DefElem *) lfirst(lc);
430 if (is_libpq_option(d->defname))
432 keywords[i] = d->defname;
433 values[i] = defGetString(d);
434 i++;
437 return i;
441 * Parse a comma-separated string and return a List of the OIDs of the
442 * extensions named in the string. If any names in the list cannot be
443 * found, report a warning if warnOnMissing is true, else just silently
444 * ignore them.
446 List *
447 ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
449 List *extensionOids = NIL;
450 List *extlist;
451 ListCell *lc;
453 /* SplitIdentifierString scribbles on its input, so pstrdup first */
454 if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
456 /* syntax error in name list */
457 ereport(ERROR,
458 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
459 errmsg("parameter \"%s\" must be a list of extension names",
460 "extensions")));
463 foreach(lc, extlist)
465 const char *extension_name = (const char *) lfirst(lc);
466 Oid extension_oid = get_extension_oid(extension_name, true);
468 if (OidIsValid(extension_oid))
470 extensionOids = lappend_oid(extensionOids, extension_oid);
472 else if (warnOnMissing)
474 ereport(WARNING,
475 (errcode(ERRCODE_UNDEFINED_OBJECT),
476 errmsg("extension \"%s\" is not installed",
477 extension_name)));
481 list_free(extlist);
482 return extensionOids;
486 * Replace escape sequences beginning with % character in the given
487 * application_name with status information, and return it.
489 * This function always returns a palloc'd string, so the caller is
490 * responsible for pfreeing it.
492 char *
493 process_pgfdw_appname(const char *appname)
495 const char *p;
496 StringInfoData buf;
498 initStringInfo(&buf);
500 for (p = appname; *p != '\0'; p++)
502 if (*p != '%')
504 /* literal char, just copy */
505 appendStringInfoChar(&buf, *p);
506 continue;
509 /* must be a '%', so skip to the next char */
510 p++;
511 if (*p == '\0')
512 break; /* format error - ignore it */
513 else if (*p == '%')
515 /* string contains %% */
516 appendStringInfoChar(&buf, '%');
517 continue;
520 /* process the option */
521 switch (*p)
523 case 'a':
524 appendStringInfoString(&buf, application_name);
525 break;
526 case 'c':
527 appendStringInfo(&buf, INT64_HEX_FORMAT ".%x", MyStartTime, MyProcPid);
528 break;
529 case 'C':
530 appendStringInfoString(&buf, cluster_name);
531 break;
532 case 'd':
533 if (MyProcPort)
535 const char *dbname = MyProcPort->database_name;
537 if (dbname)
538 appendStringInfoString(&buf, dbname);
539 else
540 appendStringInfoString(&buf, "[unknown]");
542 break;
543 case 'p':
544 appendStringInfo(&buf, "%d", MyProcPid);
545 break;
546 case 'u':
547 if (MyProcPort)
549 const char *username = MyProcPort->user_name;
551 if (username)
552 appendStringInfoString(&buf, username);
553 else
554 appendStringInfoString(&buf, "[unknown]");
556 break;
557 default:
558 /* format error - ignore it */
559 break;
563 return buf.data;
567 * Module load callback
569 void
570 _PG_init(void)
573 * Unlike application_name GUC, don't set GUC_IS_NAME flag nor check_hook
574 * to allow postgres_fdw.application_name to be any string more than
575 * NAMEDATALEN characters and to include non-ASCII characters. Instead,
576 * remote server truncates application_name of remote connection to less
577 * than NAMEDATALEN and replaces any non-ASCII characters in it with a '?'
578 * character.
580 DefineCustomStringVariable("postgres_fdw.application_name",
581 "Sets the application name to be used on the remote server.",
582 NULL,
583 &pgfdw_application_name,
584 NULL,
585 PGC_USERSET,
587 NULL,
588 NULL,
589 NULL);
591 MarkGUCPrefixReserved("postgres_fdw");