1 /*-------------------------------------------------------------------------
2  *
3  * option.c
4  * 		FDW option handling for mysql_fdw
5  *
6  * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group
7  * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation.
8  *
9  * IDENTIFICATION
10  * 		option.c
11  *
12  *-------------------------------------------------------------------------
13  */
14 #include "postgres.h"
15 
16 #include "access/reloptions.h"
17 #include "catalog/pg_foreign_server.h"
18 #include "catalog/pg_foreign_table.h"
19 #include "catalog/pg_user_mapping.h"
20 #include "catalog/pg_type.h"
21 #include "commands/defrem.h"
22 #include "miscadmin.h"
23 #include "mysql_fdw.h"
24 #include "utils/lsyscache.h"
25 
26 /*
27  * Describes the valid options for objects that use this wrapper.
28  */
29 struct MySQLFdwOption
30 {
31 	const char *optname;
32 	Oid			optcontext;		/* Oid of catalog in which option may appear */
33 };
34 
35 /*
36  * Valid options for mysql_fdw.
37  */
38 static struct MySQLFdwOption valid_options[] =
39 {
40 	/* Connection options */
41 	{"host", ForeignServerRelationId},
42 	{"port", ForeignServerRelationId},
43 	{"init_command", ForeignServerRelationId},
44 	{"username", UserMappingRelationId},
45 	{"password", UserMappingRelationId},
46 	{"dbname", ForeignTableRelationId},
47 	{"table_name", ForeignTableRelationId},
48 	{"secure_auth", ForeignServerRelationId},
49 	{"max_blob_size", ForeignTableRelationId},
50 	{"use_remote_estimate", ForeignServerRelationId},
51 	{"ssl_key", ForeignServerRelationId},
52 	{"ssl_cert", ForeignServerRelationId},
53 	{"ssl_ca", ForeignServerRelationId},
54 	{"ssl_capath", ForeignServerRelationId},
55 	{"ssl_cipher", ForeignServerRelationId},
56 
57 	/* Sentinel */
58 	{NULL, InvalidOid}
59 };
60 
61 extern Datum mysql_fdw_validator(PG_FUNCTION_ARGS);
62 
63 PG_FUNCTION_INFO_V1(mysql_fdw_validator);
64 
65 /*
66  * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
67  * USER MAPPING or FOREIGN TABLE that uses file_fdw.
68  *
69  * Raise an ERROR if the option or its value is considered invalid.
70  */
71 Datum
mysql_fdw_validator(PG_FUNCTION_ARGS)72 mysql_fdw_validator(PG_FUNCTION_ARGS)
73 {
74 	List	   *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
75 	Oid			catalog = PG_GETARG_OID(1);
76 	ListCell   *cell;
77 
78 	/*
79 	 * Check that only options supported by mysql_fdw, and allowed for the
80 	 * current object type, are given.
81 	 */
82 	foreach(cell, options_list)
83 	{
84 		DefElem    *def = (DefElem *) lfirst(cell);
85 
86 		if (!mysql_is_valid_option(def->defname, catalog))
87 		{
88 			struct MySQLFdwOption *opt;
89 			StringInfoData buf;
90 
91 			/*
92 			 * Unknown option specified, complain about it. Provide a hint
93 			 * with list of valid options for the object.
94 			 */
95 			initStringInfo(&buf);
96 			for (opt = valid_options; opt->optname; opt++)
97 			{
98 				if (catalog == opt->optcontext)
99 					appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
100 									 opt->optname);
101 			}
102 
103 			ereport(ERROR,
104 					(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
105 					 errmsg("invalid option \"%s\"", def->defname),
106 					 errhint("Valid options in this context are: %s",
107 							 buf.len ? buf.data : "<none>")));
108 		}
109 	}
110 
111 	PG_RETURN_VOID();
112 }
113 
114 /*
115  * Check if the provided option is one of the valid options.
116  * context is the Oid of the catalog holding the object the option is for.
117  */
118 bool
mysql_is_valid_option(const char * option,Oid context)119 mysql_is_valid_option(const char *option, Oid context)
120 {
121 	struct MySQLFdwOption *opt;
122 
123 	for (opt = valid_options; opt->optname; opt++)
124 	{
125 		if (context == opt->optcontext && strcmp(opt->optname, option) == 0)
126 			return true;
127 	}
128 
129 	return false;
130 }
131 
132 /*
133  * Fetch the options for a mysql_fdw foreign table.
134  */
135 mysql_opt *
mysql_get_options(Oid foreignoid,bool is_foreigntable)136 mysql_get_options(Oid foreignoid, bool is_foreigntable)
137 {
138 	ForeignTable *f_table;
139 	ForeignServer *f_server;
140 	UserMapping *f_mapping;
141 	List	   *options;
142 	ListCell   *lc;
143 	mysql_opt  *opt;
144 
145 	opt = (mysql_opt *) palloc0(sizeof(mysql_opt));
146 
147 	/*
148 	 * Extract options from FDW objects.
149 	 */
150 	if (is_foreigntable)
151 	{
152 		f_table = GetForeignTable(foreignoid);
153 		f_server = GetForeignServer(f_table->serverid);
154 	}
155 	else
156 	{
157 		f_table = NULL;
158 		f_server = GetForeignServer(foreignoid);
159 	}
160 
161 	f_mapping = GetUserMapping(GetUserId(), f_server->serverid);
162 
163 	options = NIL;
164 	if (f_table)
165 		options = list_concat(options, f_table->options);
166 
167 	options = list_concat(options, f_server->options);
168 	options = list_concat(options, f_mapping->options);
169 
170 	/* Default secure authentication is true */
171 	opt->svr_sa = true;
172 
173 	opt->use_remote_estimate = false;
174 
175 	/* Loop through the options */
176 	foreach(lc, options)
177 	{
178 		DefElem    *def = (DefElem *) lfirst(lc);
179 
180 		if (strcmp(def->defname, "host") == 0)
181 			opt->svr_address = defGetString(def);
182 
183 		if (strcmp(def->defname, "port") == 0)
184 			opt->svr_port = atoi(defGetString(def));
185 
186 		if (strcmp(def->defname, "username") == 0)
187 			opt->svr_username = defGetString(def);
188 
189 		if (strcmp(def->defname, "password") == 0)
190 			opt->svr_password = defGetString(def);
191 
192 		if (strcmp(def->defname, "dbname") == 0)
193 			opt->svr_database = defGetString(def);
194 
195 		if (strcmp(def->defname, "table_name") == 0)
196 			opt->svr_table = defGetString(def);
197 
198 		if (strcmp(def->defname, "secure_auth") == 0)
199 			opt->svr_sa = defGetBoolean(def);
200 
201 		if (strcmp(def->defname, "init_command") == 0)
202 			opt->svr_init_command = defGetString(def);
203 
204 		if (strcmp(def->defname, "max_blob_size") == 0)
205 			opt->max_blob_size = strtoul(defGetString(def), NULL, 0);
206 
207 		if (strcmp(def->defname, "use_remote_estimate") == 0)
208 			opt->use_remote_estimate = defGetBoolean(def);
209 
210 		if (strcmp(def->defname, "ssl_key") == 0)
211 			opt->ssl_key = defGetString(def);
212 
213 		if (strcmp(def->defname, "ssl_cert") == 0)
214 			opt->ssl_cert = defGetString(def);
215 
216 		if (strcmp(def->defname, "ssl_ca") == 0)
217 			opt->ssl_ca = defGetString(def);
218 
219 		if (strcmp(def->defname, "ssl_capath") == 0)
220 			opt->ssl_capath = defGetString(def);
221 
222 		if (strcmp(def->defname, "ssl_cipher") == 0)
223 			opt->ssl_cipher = defGetString(def);
224 	}
225 
226 	/* Default values, if required */
227 	if (!opt->svr_address)
228 		opt->svr_address = "127.0.0.1";
229 
230 	if (!opt->svr_port)
231 		opt->svr_port = MYSQL_SERVER_PORT;
232 
233 	/*
234 	 * When we don't have a table name or database name provided in the
235 	 * FOREIGN TABLE options, then use a foreign table name as the target table
236 	 * name and the namespace of the foreign table as a database name.
237 	 */
238 	if (f_table)
239 	{
240 		if (!opt->svr_table)
241 			opt->svr_table = get_rel_name(foreignoid);
242 
243 		if (!opt->svr_database)
244 			opt->svr_database = get_namespace_name(get_rel_namespace(foreignoid));
245 	}
246 
247 	return opt;
248 }
249