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