You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

option.c 9.7KB

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