Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * option.c
4 : * FDW and GUC option handling for postgres_fdw
5 : *
6 : * Portions Copyright (c) 2012-2023, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * contrib/postgres_fdw/option.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
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 "libpq/libpq-be.h"
22 : #include "postgres_fdw.h"
23 : #include "utils/builtins.h"
24 : #include "utils/guc.h"
25 : #include "utils/varlena.h"
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 : * GUC parameters
51 : */
52 : char *pgfdw_application_name = NULL;
53 :
54 : /*
55 : * Helper functions
56 : */
57 : static void InitPgFdwOptions(void);
58 : static bool is_valid_option(const char *keyword, Oid context);
59 : static bool is_libpq_option(const char *keyword);
60 :
61 : #include "miscadmin.h"
62 :
63 : /*
64 : * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
65 : * USER MAPPING or FOREIGN TABLE that uses postgres_fdw.
66 : *
3699 tgl 67 ECB : * Raise an ERROR if the option or its value is considered invalid.
68 : */
3699 tgl 69 GIC 3 : PG_FUNCTION_INFO_V1(postgres_fdw_validator);
3699 tgl 70 ECB :
71 : Datum
3699 tgl 72 CBC 263 : postgres_fdw_validator(PG_FUNCTION_ARGS)
3699 tgl 73 ECB : {
3699 tgl 74 GIC 263 : List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
75 263 : Oid catalog = PG_GETARG_OID(1);
76 : ListCell *cell;
3699 tgl 77 ECB :
78 : /* Build our options lists if we didn't yet. */
3699 tgl 79 GIC 263 : InitPgFdwOptions();
80 :
81 : /*
82 : * Check that only options supported by postgres_fdw, and allowed for the
3699 tgl 83 ECB : * current object type, are given.
84 : */
3699 tgl 85 CBC 841 : foreach(cell, options_list)
86 : {
87 590 : DefElem *def = (DefElem *) lfirst(cell);
88 :
3699 tgl 89 GIC 590 : if (!is_valid_option(def->defname, catalog))
90 : {
91 : /*
92 : * Unknown option specified, complain about it. Provide a hint
93 : * with a valid option that looks similar, if there is one.
94 : */
95 : PgFdwOption *opt;
96 : const char *closest_match;
97 : ClosestMatchState match_state;
205 peter 98 GNC 3 : bool has_valid_options = false;
99 :
100 3 : initClosestMatch(&match_state, def->defname, 4);
3699 tgl 101 CBC 189 : for (opt = postgres_fdw_options; opt->keyword; opt++)
102 : {
103 186 : if (catalog == opt->optcontext)
104 : {
205 peter 105 GNC 52 : has_valid_options = true;
106 52 : updateClosestMatch(&match_state, opt->keyword);
107 : }
3699 tgl 108 ECB : }
109 :
205 peter 110 GNC 3 : closest_match = getClosestMatch(&match_state);
3699 tgl 111 GIC 3 : ereport(ERROR,
112 : (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
3699 tgl 113 ECB : errmsg("invalid option \"%s\"", def->defname),
114 : has_valid_options ? closest_match ?
115 : errhint("Perhaps you meant the option \"%s\".",
116 : closest_match) : 0 :
117 : errhint("There are no valid options in this context.")));
118 : }
119 :
120 : /*
121 : * Validate option value, when we can do so without any context.
122 : */
3588 tgl 123 GIC 587 : if (strcmp(def->defname, "use_remote_estimate") == 0 ||
739 efujita 124 557 : strcmp(def->defname, "updatable") == 0 ||
731 fujii 125 553 : strcmp(def->defname, "truncatable") == 0 ||
737 fujii 126 CBC 548 : strcmp(def->defname, "async_capable") == 0 ||
409 efujita 127 546 : strcmp(def->defname, "parallel_commit") == 0 ||
3 efujita 128 GNC 542 : strcmp(def->defname, "parallel_abort") == 0 ||
737 fujii 129 CBC 538 : strcmp(def->defname, "keep_connections") == 0)
3699 tgl 130 ECB : {
3588 131 : /* these accept only boolean values */
3699 tgl 132 CBC 66 : (void) defGetBoolean(def);
3699 tgl 133 ECB : }
3699 tgl 134 GIC 521 : else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
135 515 : strcmp(def->defname, "fdw_tuple_cost") == 0)
3699 tgl 136 CBC 10 : {
137 : /*
620 fujii 138 ECB : * These must have a floating point value greater than or equal to
139 : * zero.
140 : */
141 : char *value;
142 : double real_val;
143 : bool is_parsed;
144 :
641 fujii 145 GIC 12 : value = defGetString(def);
146 12 : is_parsed = parse_real(value, &real_val, 0, NULL);
147 :
148 12 : if (!is_parsed)
641 fujii 149 CBC 2 : ereport(ERROR,
641 fujii 150 ECB : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
151 : errmsg("invalid value for floating point option \"%s\": %s",
152 : def->defname, value)));
153 :
641 fujii 154 GIC 10 : if (real_val < 0)
3699 tgl 155 UIC 0 : ereport(ERROR,
156 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
157 : errmsg("\"%s\" must be a floating point value greater than or equal to zero",
3699 tgl 158 ECB : def->defname)));
3699 tgl 159 EUB : }
2714 tgl 160 GIC 509 : else if (strcmp(def->defname, "extensions") == 0)
161 : {
162 : /* check list syntax, warn about uninstalled extensions */
163 31 : (void) ExtractExtensionList(defGetString(def), true);
2714 tgl 164 ECB : }
641 fujii 165 GIC 478 : else if (strcmp(def->defname, "fetch_size") == 0 ||
166 473 : strcmp(def->defname, "batch_size") == 0)
2622 rhaas 167 CBC 24 : {
168 : char *value;
641 fujii 169 ECB : int int_val;
170 : bool is_parsed;
171 :
641 fujii 172 GIC 26 : value = defGetString(def);
173 26 : is_parsed = parse_int(value, &int_val, 0, NULL);
174 :
175 26 : if (!is_parsed)
2622 rhaas 176 CBC 2 : ereport(ERROR,
641 fujii 177 ECB : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
178 : errmsg("invalid value for integer option \"%s\": %s",
179 : def->defname, value)));
809 tomas.vondra 180 :
641 fujii 181 GIC 24 : if (int_val <= 0)
809 tomas.vondra 182 UIC 0 : ereport(ERROR,
183 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
184 : errmsg("\"%s\" must be an integer value greater than zero",
809 tomas.vondra 185 ECB : def->defname)));
809 tomas.vondra 186 EUB : }
1206 andrew 187 GIC 452 : else if (strcmp(def->defname, "password_required") == 0)
188 : {
1060 tgl 189 5 : bool pw_required = defGetBoolean(def);
190 :
1206 andrew 191 ECB : /*
192 : * Only the superuser may set this option on a user mapping, or
193 : * alter a user mapping on which this option is set. We allow a
194 : * user to clear this option if it's set - in fact, we don't have
195 : * a choice since we can't see the old mapping when validating an
196 : * alter.
197 : */
1206 andrew 198 GIC 5 : if (!superuser() && !pw_required)
199 1 : ereport(ERROR,
200 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
201 : errmsg("password_required=false is superuser-only"),
263 michael 202 ECB : errhint("User mappings with the password_required option set to false may only be created or modified by the superuser.")));
1206 andrew 203 : }
1182 andrew 204 GIC 447 : else if (strcmp(def->defname, "sslcert") == 0 ||
205 441 : strcmp(def->defname, "sslkey") == 0)
206 : {
207 : /* similarly for sslcert / sslkey on user mapping */
1182 andrew 208 CBC 12 : if (catalog == UserMappingRelationId && !superuser())
209 2 : ereport(ERROR,
210 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
211 : errmsg("sslcert and sslkey are superuser-only"),
263 michael 212 ECB : errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser.")));
1182 andrew 213 : }
100 tomas.vondra 214 GNC 435 : else if (strcmp(def->defname, "analyze_sampling") == 0)
215 : {
216 : char *value;
217 :
218 6 : value = defGetString(def);
219 :
220 : /* we recognize off/auto/random/system/bernoulli */
221 6 : if (strcmp(value, "off") != 0 &&
222 5 : strcmp(value, "auto") != 0 &&
223 4 : strcmp(value, "random") != 0 &&
224 3 : strcmp(value, "system") != 0 &&
225 2 : strcmp(value, "bernoulli") != 0)
226 1 : ereport(ERROR,
227 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
228 : errmsg("invalid value for string option \"%s\": %s",
229 : def->defname, value)));
230 : }
231 : }
232 :
3699 tgl 233 GIC 251 : PG_RETURN_VOID();
234 : }
3699 tgl 235 ECB :
236 : /*
237 : * Initialize option lists.
238 : */
239 : static void
3699 tgl 240 GIC 387 : InitPgFdwOptions(void)
241 : {
3699 tgl 242 ECB : int num_libpq_opts;
243 : PQconninfoOption *lopt;
244 : PgFdwOption *popt;
245 :
246 : /* non-libpq FDW-specific FDW options */
247 : static const PgFdwOption non_libpq_options[] = {
248 : {"schema_name", ForeignTableRelationId, false},
249 : {"table_name", ForeignTableRelationId, false},
250 : {"column_name", AttributeRelationId, false},
251 : /* use_remote_estimate is available on both server and table */
252 : {"use_remote_estimate", ForeignServerRelationId, false},
253 : {"use_remote_estimate", ForeignTableRelationId, false},
254 : /* cost factors */
255 : {"fdw_startup_cost", ForeignServerRelationId, false},
256 : {"fdw_tuple_cost", ForeignServerRelationId, false},
257 : /* shippable extensions */
258 : {"extensions", ForeignServerRelationId, false},
259 : /* updatable is available on both server and table */
260 : {"updatable", ForeignServerRelationId, false},
3588 261 : {"updatable", ForeignTableRelationId, false},
262 : /* truncatable is available on both server and table */
263 : {"truncatable", ForeignServerRelationId, false},
264 : {"truncatable", ForeignTableRelationId, false},
265 : /* fetch_size is available on both server and table */
266 : {"fetch_size", ForeignServerRelationId, false},
267 : {"fetch_size", ForeignTableRelationId, false},
268 : /* batch_size is available on both server and table */
269 : {"batch_size", ForeignServerRelationId, false},
270 : {"batch_size", ForeignTableRelationId, false},
271 : /* async_capable is available on both server and table */
272 : {"async_capable", ForeignServerRelationId, false},
273 : {"async_capable", ForeignTableRelationId, false},
274 : {"parallel_commit", ForeignServerRelationId, false},
275 : {"parallel_abort", ForeignServerRelationId, false},
276 : {"keep_connections", ForeignServerRelationId, false},
277 : {"password_required", UserMappingRelationId, false},
278 :
279 : /* sampling is available on both server and table */
280 : {"analyze_sampling", ForeignServerRelationId, false},
281 : {"analyze_sampling", ForeignTableRelationId, false},
282 :
283 : /*
284 : * sslcert and sslkey are in fact libpq options, but we repeat them
285 : * here to allow them to appear in both foreign server context (when
286 : * we generate libpq options) and user mapping context (from here).
287 : */
288 : {"sslcert", UserMappingRelationId, true},
289 : {"sslkey", UserMappingRelationId, true},
290 :
291 : {NULL, InvalidOid, false}
292 : };
293 :
294 : /* Prevent redundant initialization. */
3699 tgl 295 GIC 387 : if (postgres_fdw_options)
296 383 : return;
297 :
298 : /*
299 : * Get list of valid libpq options.
300 : *
301 : * To avoid unnecessary work, we get the list once and use it throughout
302 : * the lifetime of this backend process. We don't need to care about
303 : * memory context issues, because PQconndefaults allocates with malloc.
304 : */
305 4 : libpq_options = PQconndefaults();
306 4 : if (!libpq_options) /* assume reason for failure is OOM */
3699 tgl 307 UIC 0 : ereport(ERROR,
308 : (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
309 : errmsg("out of memory"),
310 : errdetail("Could not get libpq's default connection options.")));
311 :
312 : /* Count how many libpq options are available. */
3699 tgl 313 GIC 4 : num_libpq_opts = 0;
314 160 : for (lopt = libpq_options; lopt->keyword; lopt++)
315 156 : num_libpq_opts++;
316 :
317 : /*
318 : * Construct an array which consists of all valid options for
319 : * postgres_fdw, by appending FDW-specific options to libpq options.
320 : *
3699 tgl 321 ECB : * We use plain malloc here to allocate postgres_fdw_options because it
322 : * lives as long as the backend process does. Besides, keeping
323 : * libpq_options in memory allows us to avoid copying every keyword
324 : * string.
325 : */
3699 tgl 326 GIC 4 : postgres_fdw_options = (PgFdwOption *)
327 4 : malloc(sizeof(PgFdwOption) * num_libpq_opts +
328 : sizeof(non_libpq_options));
329 4 : if (postgres_fdw_options == NULL)
3699 tgl 330 UIC 0 : ereport(ERROR,
3699 tgl 331 ECB : (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
332 : errmsg("out of memory")));
3699 tgl 333 EUB :
3699 tgl 334 GIC 4 : popt = postgres_fdw_options;
335 160 : for (lopt = libpq_options; lopt->keyword; lopt++)
336 : {
337 : /* Hide debug options, as well as settings we override internally. */
338 156 : if (strchr(lopt->dispchar, 'D') ||
3699 tgl 339 CBC 152 : strcmp(lopt->keyword, "fallback_application_name") == 0 ||
340 148 : strcmp(lopt->keyword, "client_encoding") == 0)
341 12 : continue;
342 :
343 : /* We don't have to copy keyword string, as described above. */
3699 tgl 344 GIC 144 : popt->keyword = lopt->keyword;
345 :
346 : /*
347 : * "user" and any secret options are allowed only on user mappings.
348 : * Everything else is a server option.
349 : */
350 144 : if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
351 12 : popt->optcontext = UserMappingRelationId;
3699 tgl 352 ECB : else
3699 tgl 353 CBC 132 : popt->optcontext = ForeignServerRelationId;
3699 tgl 354 GIC 144 : popt->is_libpq_opt = true;
3699 tgl 355 ECB :
3699 tgl 356 GBC 144 : popt++;
357 : }
358 :
359 : /* Append FDW-specific options and dummy terminator. */
3699 tgl 360 CBC 4 : memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
3699 tgl 361 ECB : }
362 :
363 : /*
364 : * Check whether the given option is one of the valid postgres_fdw options.
365 : * context is the Oid of the catalog holding the object the option is for.
366 : */
367 : static bool
3699 tgl 368 GIC 590 : is_valid_option(const char *keyword, Oid context)
369 : {
3699 tgl 370 ECB : PgFdwOption *opt;
371 :
2118 tgl 372 GIC 590 : Assert(postgres_fdw_options); /* must be initialized already */
373 :
3699 374 17587 : for (opt = postgres_fdw_options; opt->keyword; opt++)
375 : {
3699 tgl 376 CBC 17584 : if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
377 587 : return true;
378 : }
3699 tgl 379 ECB :
3699 tgl 380 CBC 3 : return false;
381 : }
3699 tgl 382 ECB :
383 : /*
384 : * Check whether the given option is one of the valid libpq options.
385 : */
386 : static bool
3699 tgl 387 GIC 211 : is_libpq_option(const char *keyword)
388 : {
389 : PgFdwOption *opt;
390 :
2118 391 211 : Assert(postgres_fdw_options); /* must be initialized already */
392 :
3699 393 5478 : for (opt = postgres_fdw_options; opt->keyword; opt++)
3699 tgl 394 ECB : {
3699 tgl 395 GIC 5411 : if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
396 144 : return true;
397 : }
3699 tgl 398 ECB :
3699 tgl 399 GIC 67 : return false;
3699 tgl 400 ECB : }
401 :
402 : /*
403 : * Generate key-value arrays which include only libpq options from the
404 : * given list (which can contain any kind of options). Caller must have
405 : * allocated large-enough arrays. Returns number of options found.
406 : */
407 : int
3699 tgl 408 GIC 124 : ExtractConnectionOptions(List *defelems, const char **keywords,
409 : const char **values)
410 : {
411 : ListCell *lc;
412 : int i;
3699 tgl 413 ECB :
414 : /* Build our options lists if we didn't yet. */
3699 tgl 415 GIC 124 : InitPgFdwOptions();
416 :
3699 tgl 417 CBC 124 : i = 0;
3699 tgl 418 GIC 335 : foreach(lc, defelems)
3699 tgl 419 ECB : {
3699 tgl 420 GIC 211 : DefElem *d = (DefElem *) lfirst(lc);
3699 tgl 421 ECB :
3699 tgl 422 CBC 211 : if (is_libpq_option(d->defname))
423 : {
3699 tgl 424 GIC 144 : keywords[i] = d->defname;
3699 tgl 425 CBC 144 : values[i] = defGetString(d);
3699 tgl 426 GIC 144 : i++;
427 : }
428 : }
429 124 : return i;
430 : }
431 :
432 : /*
433 : * Parse a comma-separated string and return a List of the OIDs of the
2714 tgl 434 ECB : * extensions named in the string. If any names in the list cannot be
435 : * found, report a warning if warnOnMissing is true, else just silently
436 : * ignore them.
437 : */
438 : List *
2714 tgl 439 GIC 855 : ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
440 : {
2714 tgl 441 CBC 855 : List *extensionOids = NIL;
442 : List *extlist;
2714 tgl 443 ECB : ListCell *lc;
444 :
445 : /* SplitIdentifierString scribbles on its input, so pstrdup first */
2714 tgl 446 CBC 855 : if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
447 : {
2714 tgl 448 ECB : /* syntax error in name list */
2714 tgl 449 GIC 1 : ereport(ERROR,
2714 tgl 450 ECB : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
451 : errmsg("parameter \"%s\" must be a list of extension names",
452 : "extensions")));
453 : }
454 :
2714 tgl 455 CBC 1709 : foreach(lc, extlist)
456 : {
2714 tgl 457 GIC 855 : const char *extension_name = (const char *) lfirst(lc);
458 855 : Oid extension_oid = get_extension_oid(extension_name, true);
459 :
460 855 : if (OidIsValid(extension_oid))
461 : {
462 853 : extensionOids = lappend_oid(extensionOids, extension_oid);
463 : }
464 2 : else if (warnOnMissing)
2714 tgl 465 ECB : {
2714 tgl 466 GIC 2 : ereport(WARNING,
2714 tgl 467 ECB : (errcode(ERRCODE_UNDEFINED_OBJECT),
468 : errmsg("extension \"%s\" is not installed",
469 : extension_name)));
470 : }
471 : }
472 :
2714 tgl 473 GIC 854 : list_free(extlist);
474 854 : return extensionOids;
2714 tgl 475 ECB : }
476 :
477 : /*
478 : * Replace escape sequences beginning with % character in the given
479 : * application_name with status information, and return it.
480 : *
471 fujii 481 : * This function always returns a palloc'd string, so the caller is
482 : * responsible for pfreeing it.
483 : */
484 : char *
471 fujii 485 GIC 16 : process_pgfdw_appname(const char *appname)
471 fujii 486 ECB : {
487 : const char *p;
488 : StringInfoData buf;
489 :
471 fujii 490 CBC 16 : initStringInfo(&buf);
491 :
492 221 : for (p = appname; *p != '\0'; p++)
493 : {
471 fujii 494 GIC 205 : if (*p != '%')
495 : {
496 : /* literal char, just copy */
497 196 : appendStringInfoChar(&buf, *p);
498 196 : continue;
471 fujii 499 ECB : }
500 :
501 : /* must be a '%', so skip to the next char */
471 fujii 502 GIC 9 : p++;
503 9 : if (*p == '\0')
471 fujii 504 UIC 0 : break; /* format error - ignore it */
471 fujii 505 GIC 9 : else if (*p == '%')
506 : {
507 : /* string contains %% */
508 1 : appendStringInfoChar(&buf, '%');
509 1 : continue;
510 : }
471 fujii 511 ECB :
512 : /* process the option */
471 fujii 513 GIC 8 : switch (*p)
514 : {
515 1 : case 'a':
471 fujii 516 CBC 1 : appendStringInfoString(&buf, application_name);
471 fujii 517 GIC 1 : break;
415 fujii 518 CBC 2 : case 'c':
415 fujii 519 GIC 2 : appendStringInfo(&buf, "%lx.%x", (long) (MyStartTime), MyProcPid);
415 fujii 520 CBC 2 : break;
415 fujii 521 GIC 2 : case 'C':
522 2 : appendStringInfoString(&buf, cluster_name);
415 fujii 523 CBC 2 : break;
471 524 1 : case 'd':
47 michael 525 GIC 1 : if (MyProcPort)
526 : {
527 1 : const char *dbname = MyProcPort->database_name;
47 michael 528 ECB :
47 michael 529 CBC 1 : if (dbname)
47 michael 530 GBC 1 : appendStringInfoString(&buf, dbname);
47 michael 531 ECB : else
47 michael 532 UIC 0 : appendStringInfoString(&buf, "[unknown]");
533 : }
471 fujii 534 CBC 1 : break;
535 1 : case 'p':
471 fujii 536 GIC 1 : appendStringInfo(&buf, "%d", MyProcPid);
537 1 : break;
538 1 : case 'u':
47 michael 539 CBC 1 : if (MyProcPort)
540 : {
541 1 : const char *username = MyProcPort->user_name;
47 michael 542 ECB :
47 michael 543 CBC 1 : if (username)
544 1 : appendStringInfoString(&buf, username);
47 michael 545 ECB : else
47 michael 546 LBC 0 : appendStringInfoString(&buf, "[unknown]");
47 michael 547 ECB : }
471 fujii 548 CBC 1 : break;
471 fujii 549 LBC 0 : default:
471 fujii 550 ECB : /* format error - ignore it */
471 fujii 551 LBC 0 : break;
552 : }
471 fujii 553 ECB : }
554 :
471 fujii 555 CBC 16 : return buf.data;
471 fujii 556 ECB : }
557 :
579 fujii 558 EUB : /*
559 : * Module load callback
579 fujii 560 ECB : */
561 : void
579 fujii 562 CBC 4 : _PG_init(void)
579 fujii 563 ECB : {
564 : /*
565 : * Unlike application_name GUC, don't set GUC_IS_NAME flag nor check_hook
566 : * to allow postgres_fdw.application_name to be any string more than
567 : * NAMEDATALEN characters and to include non-ASCII characters. Instead,
568 : * remote server truncates application_name of remote connection to less
569 : * than NAMEDATALEN and replaces any non-ASCII characters in it with a '?'
570 : * character.
571 : */
579 fujii 572 GBC 4 : DefineCustomStringVariable("postgres_fdw.application_name",
573 : "Sets the application name to be used on the remote server.",
579 fujii 574 ECB : NULL,
579 fujii 575 EUB : &pgfdw_application_name,
576 : NULL,
577 : PGC_USERSET,
578 : 0,
579 : NULL,
580 : NULL,
579 fujii 581 ECB : NULL);
582 :
412 tgl 583 GIC 4 : MarkGUCPrefixReserved("postgres_fdw");
579 fujii 584 4 : }
|