1 /*-------------------------------------------------------------------------
4 * FDW option handling for postgres_fdw
6 * Portions Copyright (c) 2012-2019, PostgreSQL Global Development Group
9 * contrib/postgres_fdw/option.c
11 *-------------------------------------------------------------------------
15 #include "postgres_fdw.h"
17 #include "access/reloptions.h"
18 #include "catalog/pg_foreign_server.h"
19 #include "catalog/pg_foreign_table.h"
20 #include "catalog/pg_user_mapping.h"
21 #include "commands/defrem.h"
22 #include "commands/extension.h"
23 #include "utils/builtins.h"
24 #include "utils/varlena.h"
28 * Describes the valid options for objects that this wrapper uses.
30 typedef struct PgFdwOption
33 Oid optcontext
; /* OID of catalog in which option may appear */
34 bool is_libpq_opt
; /* true if it's used in libpq */
38 * Valid options for postgres_fdw.
39 * Allocated and filled in InitPgFdwOptions.
41 static PgFdwOption
*postgres_fdw_options
;
44 * Valid options for libpq.
45 * Allocated and filled in InitPgFdwOptions.
47 static PQconninfoOption
*libpq_options
;
52 static void InitPgFdwOptions(void);
53 static bool is_valid_option(const char *keyword
, Oid context
);
54 static bool is_libpq_option(const char *keyword
);
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.
63 PG_FUNCTION_INFO_V1(postgres_fdw_validator
);
66 postgres_fdw_validator(PG_FUNCTION_ARGS
)
68 List
*options_list
= untransformRelOptions(PG_GETARG_DATUM(0));
69 Oid catalog
= PG_GETARG_OID(1);
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
)
81 DefElem
*def
= (DefElem
*) lfirst(cell
);
83 if (!is_valid_option(def
->defname
, catalog
))
86 * Unknown option specified, complain about it. Provide a hint
87 * with list of valid options for the object.
93 for (opt
= postgres_fdw_options
; opt
->keyword
; opt
++)
95 if (catalog
== opt
->optcontext
)
96 appendStringInfo(&buf
, "%s%s", (buf
.len
> 0) ? ", " : "",
101 (errcode(ERRCODE_FDW_INVALID_OPTION_NAME
),
102 errmsg("invalid option \"%s\"", def
->defname
),
103 errhint("Valid options in this context are: %s",
108 * Validate option value, when we can do so without any context.
110 if (strcmp(def
->defname
, "use_remote_estimate") == 0 ||
111 strcmp(def
->defname
, "updatable") == 0)
113 /* these accept only boolean values */
114 (void) defGetBoolean(def
);
116 else if (strcmp(def
->defname
, "fdw_startup_cost") == 0 ||
117 strcmp(def
->defname
, "fdw_tuple_cost") == 0)
120 * These must have a floating point value greater than or equal to
126 val
= strtod(defGetString(def
), &endp
);
127 if (*endp
|| val
< 0)
129 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
130 errmsg("\"%s\" must be a floating point value greater than or equal to zero",
133 else if (strcmp(def
->defname
, "extensions") == 0)
135 /* check list syntax, warn about uninstalled extensions */
136 (void) ExtractExtensionList(defGetString(def
), true);
138 else if (strcmp(def
->defname
, "fetch_size") == 0)
142 fetch_size
= strtol(defGetString(def
), NULL
, 10);
145 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
146 errmsg("\"%s\" must be an integer value greater than zero",
155 * Initialize option lists.
158 InitPgFdwOptions(void)
161 PQconninfoOption
*lopt
;
164 /* non-libpq FDW-specific FDW options */
165 static const PgFdwOption non_libpq_options
[] = {
166 {"schema_name", ForeignTableRelationId
, false},
167 {"table_name", ForeignTableRelationId
, false},
168 {"column_name", AttributeRelationId
, false},
169 /* use_remote_estimate is available on both server and table */
170 {"use_remote_estimate", ForeignServerRelationId
, false},
171 {"use_remote_estimate", ForeignTableRelationId
, false},
173 {"fdw_startup_cost", ForeignServerRelationId
, false},
174 {"fdw_tuple_cost", ForeignServerRelationId
, false},
175 /* shippable extensions */
176 {"extensions", ForeignServerRelationId
, false},
177 /* updatable is available on both server and table */
178 {"updatable", ForeignServerRelationId
, false},
179 {"updatable", ForeignTableRelationId
, false},
180 /* fetch_size is available on both server and table */
181 {"fetch_size", ForeignServerRelationId
, false},
182 {"fetch_size", ForeignTableRelationId
, false},
183 {NULL
, InvalidOid
, false}
186 /* Prevent redundant initialization. */
187 if (postgres_fdw_options
)
191 * Get list of valid libpq options.
193 * To avoid unnecessary work, we get the list once and use it throughout
194 * the lifetime of this backend process. We don't need to care about
195 * memory context issues, because PQconndefaults allocates with malloc.
197 libpq_options
= PQconndefaults();
198 if (!libpq_options
) /* assume reason for failure is OOM */
200 (errcode(ERRCODE_FDW_OUT_OF_MEMORY
),
201 errmsg("out of memory"),
202 errdetail("Could not get libpq's default connection options.")));
204 /* Count how many libpq options are available. */
206 for (lopt
= libpq_options
; lopt
->keyword
; lopt
++)
210 * Construct an array which consists of all valid options for
211 * postgres_fdw, by appending FDW-specific options to libpq options.
213 * We use plain malloc here to allocate postgres_fdw_options because it
214 * lives as long as the backend process does. Besides, keeping
215 * libpq_options in memory allows us to avoid copying every keyword
218 postgres_fdw_options
= (PgFdwOption
*)
219 malloc(sizeof(PgFdwOption
) * num_libpq_opts
+
220 sizeof(non_libpq_options
));
221 if (postgres_fdw_options
== NULL
)
223 (errcode(ERRCODE_FDW_OUT_OF_MEMORY
),
224 errmsg("out of memory")));
226 popt
= postgres_fdw_options
;
227 for (lopt
= libpq_options
; lopt
->keyword
; lopt
++)
229 /* Hide debug options, as well as settings we override internally. */
230 if (strchr(lopt
->dispchar
, 'D') ||
231 strcmp(lopt
->keyword
, "fallback_application_name") == 0 ||
232 strcmp(lopt
->keyword
, "client_encoding") == 0)
235 /* We don't have to copy keyword string, as described above. */
236 popt
->keyword
= lopt
->keyword
;
239 * "user" and any secret options are allowed only on user mappings.
240 * Everything else is a server option.
242 if (strcmp(lopt
->keyword
, "user") == 0 || strchr(lopt
->dispchar
, '*'))
243 popt
->optcontext
= UserMappingRelationId
;
245 popt
->optcontext
= ForeignServerRelationId
;
246 popt
->is_libpq_opt
= true;
251 /* Append FDW-specific options and dummy terminator. */
252 memcpy(popt
, non_libpq_options
, sizeof(non_libpq_options
));
256 * Check whether the given option is one of the valid postgres_fdw options.
257 * context is the Oid of the catalog holding the object the option is for.
260 is_valid_option(const char *keyword
, Oid context
)
264 Assert(postgres_fdw_options
); /* must be initialized already */
266 for (opt
= postgres_fdw_options
; opt
->keyword
; opt
++)
268 if (context
== opt
->optcontext
&& strcmp(opt
->keyword
, keyword
) == 0)
276 * Check whether the given option is one of the valid libpq options.
279 is_libpq_option(const char *keyword
)
283 Assert(postgres_fdw_options
); /* must be initialized already */
285 for (opt
= postgres_fdw_options
; opt
->keyword
; opt
++)
287 if (opt
->is_libpq_opt
&& strcmp(opt
->keyword
, keyword
) == 0)
295 * Generate key-value arrays which include only libpq options from the
296 * given list (which can contain any kind of options). Caller must have
297 * allocated large-enough arrays. Returns number of options found.
300 ExtractConnectionOptions(List
*defelems
, const char **keywords
,
306 /* Build our options lists if we didn't yet. */
310 foreach(lc
, defelems
)
312 DefElem
*d
= (DefElem
*) lfirst(lc
);
314 if (is_libpq_option(d
->defname
))
316 keywords
[i
] = d
->defname
;
317 values
[i
] = defGetString(d
);
325 * Parse a comma-separated string and return a List of the OIDs of the
326 * extensions named in the string. If any names in the list cannot be
327 * found, report a warning if warnOnMissing is true, else just silently
331 ExtractExtensionList(const char *extensionsString
, bool warnOnMissing
)
333 List
*extensionOids
= NIL
;
337 /* SplitIdentifierString scribbles on its input, so pstrdup first */
338 if (!SplitIdentifierString(pstrdup(extensionsString
), ',', &extlist
))
340 /* syntax error in name list */
342 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
343 errmsg("parameter \"%s\" must be a list of extension names",
349 const char *extension_name
= (const char *) lfirst(lc
);
350 Oid extension_oid
= get_extension_oid(extension_name
, true);
352 if (OidIsValid(extension_oid
))
354 extensionOids
= lappend_oid(extensionOids
, extension_oid
);
356 else if (warnOnMissing
)
359 (errcode(ERRCODE_UNDEFINED_OBJECT
),
360 errmsg("extension \"%s\" is not installed",
366 return extensionOids
;