1/*-------------------------------------------------------------------------
4 * FDW and GUC option handling for postgres_fdw
6 * Portions Copyright (c) 2012-2025, PostgreSQL Global Development Group
9 * contrib/postgres_fdw/option.c
11 *-------------------------------------------------------------------------
28 * Describes the valid options for objects that this wrapper uses.
38 * Valid options for postgres_fdw.
39 * Allocated and filled in InitPgFdwOptions.
58 * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
59 * USER MAPPING or FOREIGN TABLE that uses postgres_fdw.
61 * Raise an ERROR if the option or its value is considered invalid.
72 /* Build our options lists if we didn't yet. */
76 * Check that only options supported by postgres_fdw, and allowed for the
77 * current object type, are given.
79 foreach(cell, options_list)
86 * Unknown option specified, complain about it. Provide a hint
87 * with a valid option that looks similar, if there is one.
90 const char *closest_match;
92 bool has_valid_options =
false;
99 has_valid_options =
true;
106 (
errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
108 has_valid_options ? closest_match ?
109 errhint(
"Perhaps you meant the option \"%s\".",
111 errhint(
"There are no valid options in this context.")));
115 * Validate option value, when we can do so without any context.
117 if (strcmp(def->
defname,
"use_remote_estimate") == 0 ||
118 strcmp(def->
defname,
"updatable") == 0 ||
119 strcmp(def->
defname,
"truncatable") == 0 ||
120 strcmp(def->
defname,
"async_capable") == 0 ||
121 strcmp(def->
defname,
"parallel_commit") == 0 ||
122 strcmp(def->
defname,
"parallel_abort") == 0 ||
123 strcmp(def->
defname,
"keep_connections") == 0)
125 /* these accept only boolean values */
128 else if (strcmp(def->
defname,
"fdw_startup_cost") == 0 ||
129 strcmp(def->
defname,
"fdw_tuple_cost") == 0)
132 * These must have a floating point value greater than or equal to
144 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
145 errmsg(
"invalid value for floating point option \"%s\": %s",
150 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
151 errmsg(
"\"%s\" must be a floating point value greater than or equal to zero",
154 else if (strcmp(def->
defname,
"extensions") == 0)
156 /* check list syntax, warn about uninstalled extensions */
159 else if (strcmp(def->
defname,
"fetch_size") == 0 ||
160 strcmp(def->
defname,
"batch_size") == 0)
171 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
172 errmsg(
"invalid value for integer option \"%s\": %s",
177 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
178 errmsg(
"\"%s\" must be an integer value greater than zero",
181 else if (strcmp(def->
defname,
"password_required") == 0)
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
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.")));
198 else if (strcmp(def->
defname,
"sslcert") == 0 ||
199 strcmp(def->
defname,
"sslkey") == 0)
201 /* similarly for sslcert / sslkey on user mapping */
202 if (catalog == UserMappingRelationId && !
superuser())
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.")));
208 else if (strcmp(def->
defname,
"analyze_sampling") == 0)
214 /* we recognize off/auto/random/system/bernoulli */
215 if (strcmp(
value,
"off") != 0 &&
216 strcmp(
value,
"auto") != 0 &&
217 strcmp(
value,
"random") != 0 &&
218 strcmp(
value,
"system") != 0 &&
219 strcmp(
value,
"bernoulli") != 0)
221 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
222 errmsg(
"invalid value for string option \"%s\": %s",
231 * Initialize option lists.
241 /* non-libpq FDW-specific FDW 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},
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},
274 /* sampling is available on both server and table */
275 {
"analyze_sampling", ForeignServerRelationId,
false},
276 {
"analyze_sampling", ForeignTableRelationId,
false},
278 {
"use_scram_passthrough", ForeignServerRelationId,
false},
279 {
"use_scram_passthrough", UserMappingRelationId,
false},
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).
286 {
"sslcert", UserMappingRelationId,
true},
287 {
"sslkey", UserMappingRelationId,
true},
290 * gssdelegation is also a libpq option but should be allowed in a
291 * user mapping context too
293 {
"gssdelegation", UserMappingRelationId,
true},
298 /* Prevent redundant initialization. */
303 * Get list of valid libpq options.
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
310 if (!libpq_options)
/* assume reason for failure is OOM */
312 (
errcode(ERRCODE_FDW_OUT_OF_MEMORY),
314 errdetail(
"Could not get libpq's default connection options.")));
316 /* Count how many libpq options are available. */
318 for (lopt = libpq_options; lopt->
keyword; lopt++)
322 * Construct an array which consists of all valid options for
323 * postgres_fdw, by appending FDW-specific options to libpq options.
328 sizeof(non_libpq_options));
331 for (lopt = libpq_options; lopt->
keyword; lopt++)
333 /* Hide debug options, as well as settings we override internally. */
335 strcmp(lopt->
keyword,
"fallback_application_name") == 0 ||
336 strcmp(lopt->
keyword,
"client_encoding") == 0)
340 * Disallow OAuth options for now, since the builtin flow communicates
341 * on stderr by default and can't cache tokens yet.
343 if (strncmp(lopt->
keyword,
"oauth_", strlen(
"oauth_")) == 0)
350 * "user" and any secret options are allowed only on user mappings.
351 * Everything else is a server option.
362 /* Done with libpq's output structure. */
365 /* Append FDW-specific options and dummy terminator. */
366 memcpy(popt, non_libpq_options,
sizeof(non_libpq_options));
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.
390 * Check whether the given option is one of the valid libpq options.
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.
420 /* Build our options lists if we didn't yet. */
424 foreach(lc, defelems)
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
451 /* SplitIdentifierString scribbles on its input, so pstrdup first */
454 /* syntax error in name list */
456 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
457 errmsg(
"parameter \"%s\" must be a list of extension names",
463 const char *extension_name = (
const char *)
lfirst(lc);
468 extensionOids =
lappend_oid(extensionOids, extension_oid);
470 else if (warnOnMissing)
473 (
errcode(ERRCODE_UNDEFINED_OBJECT),
474 errmsg(
"extension \"%s\" is not installed",
480 return extensionOids;
484 * Replace escape sequences beginning with % character in the given
485 * application_name with status information, and return it.
487 * This function always returns a palloc'd string, so the caller is
488 * responsible for pfreeing it.
498 for (p = appname; *p !=
'0円'; p++)
502 /* literal char, just copy */
507 /* must be a '%', so skip to the next char */
510 break;
/* format error - ignore it */
513 /* string contains %% */
518 /* process the option */
556 /* format error - ignore it */
565 * Module load callback
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 '?'
579 "Sets the application name to be used on the remote server.",
static Datum values[MAXATTR]
#define OidIsValid(objectId)
static bool is_valid_option(const char *keyword, Oid context)
int ExtractConnectionOptions(List *defelems, const char **keywords, const char **values)
static bool is_libpq_option(const char *keyword)
Datum postgres_fdw_validator(PG_FUNCTION_ARGS)
List * ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
static PgFdwOption * postgres_fdw_options
struct PgFdwOption PgFdwOption
PG_FUNCTION_INFO_V1(postgres_fdw_validator)
char * pgfdw_application_name
char * process_pgfdw_appname(const char *appname)
static void InitPgFdwOptions(void)
char * defGetString(DefElem *def)
bool defGetBoolean(DefElem *def)
int errdetail(const char *fmt,...)
int errhint(const char *fmt,...)
int errcode(int sqlerrcode)
int errmsg(const char *fmt,...)
#define ereport(elevel,...)
Oid get_extension_oid(const char *extname, bool missing_ok)
void PQconninfoFree(PQconninfoOption *connOptions)
PQconninfoOption * PQconndefaults(void)
#define PG_GETARG_DATUM(n)
bool parse_int(const char *value, int *result, int flags, const char **hintmsg)
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)
bool parse_real(const char *value, double *result, int flags, const char **hintmsg)
void MarkGUCPrefixReserved(const char *className)
Assert(PointerIsAligned(start, uint64))
struct parser_state match_state[5]
static const JsonPathKeyword keywords[]
List * lappend_oid(List *list, Oid datum)
void list_free(List *list)
char * MemoryContextStrdup(MemoryContext context, const char *string)
void * MemoryContextAlloc(MemoryContext context, Size size)
char * pstrdup(const char *in)
MemoryContext TopMemoryContext
List * untransformRelOptions(Datum options)
void appendStringInfo(StringInfo str, const char *fmt,...)
void appendStringInfoString(StringInfo str, const char *s)
void appendStringInfoChar(StringInfo str, char ch)
void initStringInfo(StringInfo str)
const char * getClosestMatch(ClosestMatchState *state)
bool SplitIdentifierString(char *rawstring, char separator, List **namelist)
void initClosestMatch(ClosestMatchState *state, const char *source, int max_d)
void updateClosestMatch(ClosestMatchState *state, const char *candidate)