1 /*-------------------------------------------------------------------------
2 *
3 * option.c
4 * FDW option handling for postgres_fdw
5 *
6 * Portions Copyright (c) 2012-2019, PostgreSQL Global Development Group
7 *
8 * IDENTIFICATION
9 * contrib/postgres_fdw/option.c
10 *
11 *-------------------------------------------------------------------------
12 */
13 #include "postgres.h"
14
15 #include "postgres_fdw.h"
16
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"
25
26
27 /*
28 * Describes the valid options for objects that this wrapper uses.
29 */
30 typedef struct PgFdwOption
31 {
32 const char *keyword;
33 Oid optcontext; /* OID of catalog in which option may appear */
34 bool is_libpq_opt; /* true if it's used in libpq */
35 } PgFdwOption;
36
37 /*
38 * Valid options for postgres_fdw.
39 * Allocated and filled in InitPgFdwOptions.
40 */
41 static PgFdwOption *postgres_fdw_options;
42
43 /*
44 * Valid options for libpq.
45 * Allocated and filled in InitPgFdwOptions.
46 */
47 static PQconninfoOption *libpq_options;
48
49 /*
50 * Helper functions
51 */
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);
55
56
57 /*
58 * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
59 * USER MAPPING or FOREIGN TABLE that uses postgres_fdw.
60 *
61 * Raise an ERROR if the option or its value is considered invalid.
62 */
63 PG_FUNCTION_INFO_V1(postgres_fdw_validator);
64
65 Datum
postgres_fdw_validator(PG_FUNCTION_ARGS)66 postgres_fdw_validator(PG_FUNCTION_ARGS)
67 {
68 List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
69 Oid catalog = PG_GETARG_OID(1);
70 ListCell *cell;
71
72 /* Build our options lists if we didn't yet. */
73 InitPgFdwOptions();
74
75 /*
76 * Check that only options supported by postgres_fdw, and allowed for the
77 * current object type, are given.
78 */
79 foreach(cell, options_list)
80 {
81 DefElem *def = (DefElem *) lfirst(cell);
82
83 if (!is_valid_option(def->defname, catalog))
84 {
85 /*
86 * Unknown option specified, complain about it. Provide a hint
87 * with list of valid options for the object.
88 */
89 PgFdwOption *opt;
90 StringInfoData buf;
91
92 initStringInfo(&buf);
93 for (opt = postgres_fdw_options; opt->keyword; opt++)
94 {
95 if (catalog == opt->optcontext)
96 appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
97 opt->keyword);
98 }
99
100 ereport(ERROR,
101 (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
102 errmsg("invalid option \"%s\"", def->defname),
103 errhint("Valid options in this context are: %s",
104 buf.data)));
105 }
106
107 /*
108 * Validate option value, when we can do so without any context.
109 */
110 if (strcmp(def->defname, "use_remote_estimate") == 0 ||
111 strcmp(def->defname, "updatable") == 0)
112 {
113 /* these accept only boolean values */
114 (void) defGetBoolean(def);
115 }
116 else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
117 strcmp(def->defname, "fdw_tuple_cost") == 0)
118 {
119 /*
120 * These must have a floating point value greater than or equal to
121 * zero.
122 */
123 double val;
124 char *endp;
125
126 val = strtod(defGetString(def), &endp);
127 if (*endp || val < 0)
128 ereport(ERROR,
129 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
130 errmsg("\"%s\" must be a floating point value greater than or equal to zero",
131 def->defname)));
132 }
133 else if (strcmp(def->defname, "extensions") == 0)
134 {
135 /* check list syntax, warn about uninstalled extensions */
136 (void) ExtractExtensionList(defGetString(def), true);
137 }
138 else if (strcmp(def->defname, "fetch_size") == 0)
139 {
140 int fetch_size;
141
142 fetch_size = strtol(defGetString(def), NULL, 10);
143 if (fetch_size <= 0)
144 ereport(ERROR,
145 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
146 errmsg("\"%s\" must be an integer value greater than zero",
147 def->defname)));
148 }
149 }
150
151 PG_RETURN_VOID();
152 }
153
154 /*
155 * Initialize option lists.
156 */
157 static void
InitPgFdwOptions(void)158 InitPgFdwOptions(void)
159 {
160 int num_libpq_opts;
161 PQconninfoOption *lopt;
162 PgFdwOption *popt;
163
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},
172 /* cost factors */
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}
184 };
185
186 /* Prevent redundant initialization. */
187 if (postgres_fdw_options)
188 return;
189
190 /*
191 * Get list of valid libpq options.
192 *
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.
196 */
197 libpq_options = PQconndefaults();
198 if (!libpq_options) /* assume reason for failure is OOM */
199 ereport(ERROR,
200 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
201 errmsg("out of memory"),
202 errdetail("Could not get libpq's default connection options.")));
203
204 /* Count how many libpq options are available. */
205 num_libpq_opts = 0;
206 for (lopt = libpq_options; lopt->keyword; lopt++)
207 num_libpq_opts++;
208
209 /*
210 * Construct an array which consists of all valid options for
211 * postgres_fdw, by appending FDW-specific options to libpq options.
212 *
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
216 * string.
217 */
218 postgres_fdw_options = (PgFdwOption *)
219 malloc(sizeof(PgFdwOption) * num_libpq_opts +
220 sizeof(non_libpq_options));
221 if (postgres_fdw_options == NULL)
222 ereport(ERROR,
223 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
224 errmsg("out of memory")));
225
226 popt = postgres_fdw_options;
227 for (lopt = libpq_options; lopt->keyword; lopt++)
228 {
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)
233 continue;
234
235 /* We don't have to copy keyword string, as described above. */
236 popt->keyword = lopt->keyword;
237
238 /*
239 * "user" and any secret options are allowed only on user mappings.
240 * Everything else is a server option.
241 */
242 if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
243 popt->optcontext = UserMappingRelationId;
244 else
245 popt->optcontext = ForeignServerRelationId;
246 popt->is_libpq_opt = true;
247
248 popt++;
249 }
250
251 /* Append FDW-specific options and dummy terminator. */
252 memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
253 }
254
255 /*
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.
258 */
259 static bool
is_valid_option(const char * keyword,Oid context)260 is_valid_option(const char *keyword, Oid context)
261 {
262 PgFdwOption *opt;
263
264 Assert(postgres_fdw_options); /* must be initialized already */
265
266 for (opt = postgres_fdw_options; opt->keyword; opt++)
267 {
268 if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
269 return true;
270 }
271
272 return false;
273 }
274
275 /*
276 * Check whether the given option is one of the valid libpq options.
277 */
278 static bool
is_libpq_option(const char * keyword)279 is_libpq_option(const char *keyword)
280 {
281 PgFdwOption *opt;
282
283 Assert(postgres_fdw_options); /* must be initialized already */
284
285 for (opt = postgres_fdw_options; opt->keyword; opt++)
286 {
287 if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
288 return true;
289 }
290
291 return false;
292 }
293
294 /*
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.
298 */
299 int
ExtractConnectionOptions(List * defelems,const char ** keywords,const char ** values)300 ExtractConnectionOptions(List *defelems, const char **keywords,
301 const char **values)
302 {
303 ListCell *lc;
304 int i;
305
306 /* Build our options lists if we didn't yet. */
307 InitPgFdwOptions();
308
309 i = 0;
310 foreach(lc, defelems)
311 {
312 DefElem *d = (DefElem *) lfirst(lc);
313
314 if (is_libpq_option(d->defname))
315 {
316 keywords[i] = d->defname;
317 values[i] = defGetString(d);
318 i++;
319 }
320 }
321 return i;
322 }
323
324 /*
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
328 * ignore them.
329 */
330 List *
ExtractExtensionList(const char * extensionsString,bool warnOnMissing)331 ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
332 {
333 List *extensionOids = NIL;
334 List *extlist;
335 ListCell *lc;
336
337 /* SplitIdentifierString scribbles on its input, so pstrdup first */
338 if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
339 {
340 /* syntax error in name list */
341 ereport(ERROR,
342 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
343 errmsg("parameter \"%s\" must be a list of extension names",
344 "extensions")));
345 }
346
347 foreach(lc, extlist)
348 {
349 const char *extension_name = (const char *) lfirst(lc);
350 Oid extension_oid = get_extension_oid(extension_name, true);
351
352 if (OidIsValid(extension_oid))
353 {
354 extensionOids = lappend_oid(extensionOids, extension_oid);
355 }
356 else if (warnOnMissing)
357 {
358 ereport(WARNING,
359 (errcode(ERRCODE_UNDEFINED_OBJECT),
360 errmsg("extension \"%s\" is not installed",
361 extension_name)));
362 }
363 }
364
365 list_free(extlist);
366 return extensionOids;
367 }
368