1 /*-------------------------------------------------------------------------
2  *
3  * option.c
4  *		  FDW option handling for postgres_fdw
5  *
6  * Portions Copyright (c) 2012-2017, 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