1 /*-------------------------------------------------------------------------
2 *
3 * option.c
4 * FDW option handling for postgres_fdw
5 *
6 * Portions Copyright (c) 2012-2016, 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
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 */
34 } PgFdwOption;
35
36 /*
37 * Valid options for postgres_fdw.
38 * Allocated and filled in InitPgFdwOptions.
39 */
40 static PgFdwOption *postgres_fdw_options;
41
42 /*
43 * Valid options for libpq.
44 * Allocated and filled in InitPgFdwOptions.
45 */
46 static PQconninfoOption *libpq_options;
47
48 /*
49 * Helper functions
50 */
51 static void InitPgFdwOptions(void);
52 static bool is_valid_option(const char *keyword, Oid context);
53 static bool is_libpq_option(const char *keyword);
54
55
56 /*
57 * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
58 * USER MAPPING or FOREIGN TABLE that uses postgres_fdw.
59 *
60 * Raise an ERROR if the option or its value is considered invalid.
61 */
62 PG_FUNCTION_INFO_V1(postgres_fdw_validator);
63
64 Datum
postgres_fdw_validator(PG_FUNCTION_ARGS)65 postgres_fdw_validator(PG_FUNCTION_ARGS)
66 {
67 List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
68 Oid catalog = PG_GETARG_OID(1);
69 ListCell *cell;
70
71 /* Build our options lists if we didn't yet. */
72 InitPgFdwOptions();
73
74 /*
75 * Check that only options supported by postgres_fdw, and allowed for the
76 * current object type, are given.
77 */
78 foreach(cell, options_list)
79 {
80 DefElem *def = (DefElem *) lfirst(cell);
81
82 if (!is_valid_option(def->defname, catalog))
83 {
84 /*
85 * Unknown option specified, complain about it. Provide a hint
86 * with list of valid options for the object.
87 */
88 PgFdwOption *opt;
89 StringInfoData buf;
90
91 initStringInfo(&buf);
92 for (opt = postgres_fdw_options; opt->keyword; opt++)
93 {
94 if (catalog == opt->optcontext)
95 appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
96 opt->keyword);
97 }
98
99 ereport(ERROR,
100 (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
101 errmsg("invalid option \"%s\"", def->defname),
102 errhint("Valid options in this context are: %s",
103 buf.data)));
104 }
105
106 /*
107 * Validate option value, when we can do so without any context.
108 */
109 if (strcmp(def->defname, "use_remote_estimate") == 0 ||
110 strcmp(def->defname, "updatable") == 0)
111 {
112 /* these accept only boolean values */
113 (void) defGetBoolean(def);
114 }
115 else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
116 strcmp(def->defname, "fdw_tuple_cost") == 0)
117 {
118 /*
119 * These must have a floating point value greater than or equal to
120 * zero.
121 */
122 double val;
123 char *endp;
124
125 val = strtod(defGetString(def), &endp);
126 if (*endp || val < 0)
127 ereport(ERROR,
128 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
129 errmsg("\"%s\" must be a floating point value greater than or equal to zero",
130 def->defname)));
131 }
132 else if (strcmp(def->defname, "extensions") == 0)
133 {
134 /* check list syntax, warn about uninstalled extensions */
135 (void) ExtractExtensionList(defGetString(def), true);
136 }
137 else if (strcmp(def->defname, "fetch_size") == 0)
138 {
139 int fetch_size;
140
141 fetch_size = strtol(defGetString(def), NULL, 10);
142 if (fetch_size <= 0)
143 ereport(ERROR,
144 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
145 errmsg("\"%s\" must be an integer value greater than zero",
146 def->defname)));
147 }
148 }
149
150 PG_RETURN_VOID();
151 }
152
153 /*
154 * Initialize option lists.
155 */
156 static void
InitPgFdwOptions(void)157 InitPgFdwOptions(void)
158 {
159 int num_libpq_opts;
160 PQconninfoOption *lopt;
161 PgFdwOption *popt;
162
163 /* non-libpq FDW-specific FDW options */
164 static const PgFdwOption non_libpq_options[] = {
165 {"schema_name", ForeignTableRelationId, false},
166 {"table_name", ForeignTableRelationId, false},
167 {"column_name", AttributeRelationId, false},
168 /* use_remote_estimate is available on both server and table */
169 {"use_remote_estimate", ForeignServerRelationId, false},
170 {"use_remote_estimate", ForeignTableRelationId, false},
171 /* cost factors */
172 {"fdw_startup_cost", ForeignServerRelationId, false},
173 {"fdw_tuple_cost", ForeignServerRelationId, false},
174 /* shippable extensions */
175 {"extensions", ForeignServerRelationId, false},
176 /* updatable is available on both server and table */
177 {"updatable", ForeignServerRelationId, false},
178 {"updatable", ForeignTableRelationId, false},
179 /* fetch_size is available on both server and table */
180 {"fetch_size", ForeignServerRelationId, false},
181 {"fetch_size", ForeignTableRelationId, false},
182 {NULL, InvalidOid, false}
183 };
184
185 /* Prevent redundant initialization. */
186 if (postgres_fdw_options)
187 return;
188
189 /*
190 * Get list of valid libpq options.
191 *
192 * To avoid unnecessary work, we get the list once and use it throughout
193 * the lifetime of this backend process. We don't need to care about
194 * memory context issues, because PQconndefaults allocates with malloc.
195 */
196 libpq_options = PQconndefaults();
197 if (!libpq_options) /* assume reason for failure is OOM */
198 ereport(ERROR,
199 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
200 errmsg("out of memory"),
201 errdetail("could not get libpq's default connection options")));
202
203 /* Count how many libpq options are available. */
204 num_libpq_opts = 0;
205 for (lopt = libpq_options; lopt->keyword; lopt++)
206 num_libpq_opts++;
207
208 /*
209 * Construct an array which consists of all valid options for
210 * postgres_fdw, by appending FDW-specific options to libpq options.
211 *
212 * We use plain malloc here to allocate postgres_fdw_options because it
213 * lives as long as the backend process does. Besides, keeping
214 * libpq_options in memory allows us to avoid copying every keyword
215 * string.
216 */
217 postgres_fdw_options = (PgFdwOption *)
218 malloc(sizeof(PgFdwOption) * num_libpq_opts +
219 sizeof(non_libpq_options));
220 if (postgres_fdw_options == NULL)
221 ereport(ERROR,
222 (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
223 errmsg("out of memory")));
224
225 popt = postgres_fdw_options;
226 for (lopt = libpq_options; lopt->keyword; lopt++)
227 {
228 /* Hide debug options, as well as settings we override internally. */
229 if (strchr(lopt->dispchar, 'D') ||
230 strcmp(lopt->keyword, "fallback_application_name") == 0 ||
231 strcmp(lopt->keyword, "client_encoding") == 0)
232 continue;
233
234 /* We don't have to copy keyword string, as described above. */
235 popt->keyword = lopt->keyword;
236
237 /*
238 * "user" and any secret options are allowed only on user mappings.
239 * Everything else is a server option.
240 */
241 if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
242 popt->optcontext = UserMappingRelationId;
243 else
244 popt->optcontext = ForeignServerRelationId;
245 popt->is_libpq_opt = true;
246
247 popt++;
248 }
249
250 /* Append FDW-specific options and dummy terminator. */
251 memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
252 }
253
254 /*
255 * Check whether the given option is one of the valid postgres_fdw options.
256 * context is the Oid of the catalog holding the object the option is for.
257 */
258 static bool
is_valid_option(const char * keyword,Oid context)259 is_valid_option(const char *keyword, Oid context)
260 {
261 PgFdwOption *opt;
262
263 Assert(postgres_fdw_options); /* must be initialized already */
264
265 for (opt = postgres_fdw_options; opt->keyword; opt++)
266 {
267 if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
268 return true;
269 }
270
271 return false;
272 }
273
274 /*
275 * Check whether the given option is one of the valid libpq options.
276 */
277 static bool
is_libpq_option(const char * keyword)278 is_libpq_option(const char *keyword)
279 {
280 PgFdwOption *opt;
281
282 Assert(postgres_fdw_options); /* must be initialized already */
283
284 for (opt = postgres_fdw_options; opt->keyword; opt++)
285 {
286 if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
287 return true;
288 }
289
290 return false;
291 }
292
293 /*
294 * Generate key-value arrays which include only libpq options from the
295 * given list (which can contain any kind of options). Caller must have
296 * allocated large-enough arrays. Returns number of options found.
297 */
298 int
ExtractConnectionOptions(List * defelems,const char ** keywords,const char ** values)299 ExtractConnectionOptions(List *defelems, const char **keywords,
300 const char **values)
301 {
302 ListCell *lc;
303 int i;
304
305 /* Build our options lists if we didn't yet. */
306 InitPgFdwOptions();
307
308 i = 0;
309 foreach(lc, defelems)
310 {
311 DefElem *d = (DefElem *) lfirst(lc);
312
313 if (is_libpq_option(d->defname))
314 {
315 keywords[i] = d->defname;
316 values[i] = defGetString(d);
317 i++;
318 }
319 }
320 return i;
321 }
322
323 /*
324 * Parse a comma-separated string and return a List of the OIDs of the
325 * extensions named in the string. If any names in the list cannot be
326 * found, report a warning if warnOnMissing is true, else just silently
327 * ignore them.
328 */
329 List *
ExtractExtensionList(const char * extensionsString,bool warnOnMissing)330 ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
331 {
332 List *extensionOids = NIL;
333 List *extlist;
334 ListCell *lc;
335
336 /* SplitIdentifierString scribbles on its input, so pstrdup first */
337 if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
338 {
339 /* syntax error in name list */
340 ereport(ERROR,
341 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
342 errmsg("parameter \"%s\" must be a list of extension names",
343 "extensions")));
344 }
345
346 foreach(lc, extlist)
347 {
348 const char *extension_name = (const char *) lfirst(lc);
349 Oid extension_oid = get_extension_oid(extension_name, true);
350
351 if (OidIsValid(extension_oid))
352 {
353 extensionOids = lappend_oid(extensionOids, extension_oid);
354 }
355 else if (warnOnMissing)
356 {
357 ereport(WARNING,
358 (errcode(ERRCODE_UNDEFINED_OBJECT),
359 errmsg("extension \"%s\" is not installed",
360 extension_name)));
361 }
362 }
363
364 list_free(extlist);
365 return extensionOids;
366 }
367