Age Owner Branch data 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-2024, 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 : : *
67 : : * Raise an ERROR if the option or its value is considered invalid.
68 : : */
4070 tgl@sss.pgh.pa.us 69 :CBC 11 : PG_FUNCTION_INFO_V1(postgres_fdw_validator);
70 : :
71 : : Datum
72 : 276 : postgres_fdw_validator(PG_FUNCTION_ARGS)
73 : : {
74 : 276 : List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
75 : 276 : Oid catalog = PG_GETARG_OID(1);
76 : : ListCell *cell;
77 : :
78 : : /* Build our options lists if we didn't yet. */
79 : 276 : InitPgFdwOptions();
80 : :
81 : : /*
82 : : * Check that only options supported by postgres_fdw, and allowed for the
83 : : * current object type, are given.
84 : : */
85 [ + + + + : 885 : foreach(cell, options_list)
+ + ]
86 : : {
87 : 621 : DefElem *def = (DefElem *) lfirst(cell);
88 : :
89 [ + + ]: 621 : 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;
576 peter@eisentraut.org 98 : 3 : bool has_valid_options = false;
99 : :
100 : 3 : initClosestMatch(&match_state, def->defname, 4);
4070 tgl@sss.pgh.pa.us 101 [ + + ]: 198 : for (opt = postgres_fdw_options; opt->keyword; opt++)
102 : : {
103 [ + + ]: 195 : if (catalog == opt->optcontext)
104 : : {
576 peter@eisentraut.org 105 : 55 : has_valid_options = true;
106 : 55 : updateClosestMatch(&match_state, opt->keyword);
107 : : }
108 : : }
109 : :
110 : 3 : closest_match = getClosestMatch(&match_state);
4070 tgl@sss.pgh.pa.us 111 [ + - + + : 3 : ereport(ERROR,
+ + ]
112 : : (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
113 : : 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 : : */
3959 123 [ + + ]: 618 : if (strcmp(def->defname, "use_remote_estimate") == 0 ||
1110 efujita@postgresql.o 124 [ + + ]: 586 : strcmp(def->defname, "updatable") == 0 ||
1102 fujii@postgresql.org 125 [ + + ]: 582 : strcmp(def->defname, "truncatable") == 0 ||
1108 126 [ + + ]: 577 : strcmp(def->defname, "async_capable") == 0 ||
780 efujita@postgresql.o 127 [ + + ]: 573 : strcmp(def->defname, "parallel_commit") == 0 ||
374 128 [ + + ]: 569 : strcmp(def->defname, "parallel_abort") == 0 ||
1108 fujii@postgresql.org 129 [ + + ]: 565 : strcmp(def->defname, "keep_connections") == 0)
130 : : {
131 : : /* these accept only boolean values */
4070 tgl@sss.pgh.pa.us 132 : 70 : (void) defGetBoolean(def);
133 : : }
134 [ + + ]: 548 : else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
135 [ + + ]: 541 : strcmp(def->defname, "fdw_tuple_cost") == 0)
136 : 11 : {
137 : : /*
138 : : * 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 : :
1012 fujii@postgresql.org 145 : 13 : value = defGetString(def);
146 : 13 : is_parsed = parse_real(value, &real_val, 0, NULL);
147 : :
148 [ + + ]: 13 : if (!is_parsed)
149 [ + - ]: 2 : ereport(ERROR,
150 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
151 : : errmsg("invalid value for floating point option \"%s\": %s",
152 : : def->defname, value)));
153 : :
154 [ - + ]: 11 : if (real_val < 0)
4070 tgl@sss.pgh.pa.us 155 [ # # ]:UBC 0 : ereport(ERROR,
156 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
157 : : errmsg("\"%s\" must be a floating point value greater than or equal to zero",
158 : : def->defname)));
159 : : }
3085 tgl@sss.pgh.pa.us 160 [ + + ]:CBC 535 : else if (strcmp(def->defname, "extensions") == 0)
161 : : {
162 : : /* check list syntax, warn about uninstalled extensions */
163 : 31 : (void) ExtractExtensionList(defGetString(def), true);
164 : : }
1012 fujii@postgresql.org 165 [ + + ]: 504 : else if (strcmp(def->defname, "fetch_size") == 0 ||
166 [ + + ]: 499 : strcmp(def->defname, "batch_size") == 0)
2993 rhaas@postgresql.org 167 : 23 : {
168 : : char *value;
169 : : int int_val;
170 : : bool is_parsed;
171 : :
1012 fujii@postgresql.org 172 : 25 : value = defGetString(def);
173 : 25 : is_parsed = parse_int(value, &int_val, 0, NULL);
174 : :
175 [ + + ]: 25 : if (!is_parsed)
2993 rhaas@postgresql.org 176 [ + - ]: 2 : ereport(ERROR,
177 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
178 : : errmsg("invalid value for integer option \"%s\": %s",
179 : : def->defname, value)));
180 : :
1012 fujii@postgresql.org 181 [ - + ]: 23 : if (int_val <= 0)
1180 tomas.vondra@postgre 182 [ # # ]:UBC 0 : ereport(ERROR,
183 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
184 : : errmsg("\"%s\" must be an integer value greater than zero",
185 : : def->defname)));
186 : : }
1577 andrew@dunslane.net 187 [ + + ]:CBC 479 : else if (strcmp(def->defname, "password_required") == 0)
188 : : {
1431 tgl@sss.pgh.pa.us 189 : 6 : bool pw_required = defGetBoolean(def);
190 : :
191 : : /*
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 : : */
1577 andrew@dunslane.net 198 [ + + + + ]: 6 : if (!superuser() && !pw_required)
199 [ + - ]: 1 : ereport(ERROR,
200 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
201 : : errmsg("password_required=false is superuser-only"),
202 : : errhint("User mappings with the password_required option set to false may only be created or modified by the superuser.")));
203 : : }
1553 204 [ + + ]: 473 : else if (strcmp(def->defname, "sslcert") == 0 ||
205 [ + + ]: 467 : strcmp(def->defname, "sslkey") == 0)
206 : : {
207 : : /* similarly for sslcert / sslkey on user mapping */
208 [ + + + + ]: 12 : if (catalog == UserMappingRelationId && !superuser())
209 [ + - ]: 2 : ereport(ERROR,
210 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
211 : : errmsg("sslcert and sslkey are superuser-only"),
212 : : errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser.")));
213 : : }
471 tomas.vondra@postgre 214 [ + + ]: 461 : 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 : :
4070 tgl@sss.pgh.pa.us 233 : 264 : PG_RETURN_VOID();
234 : : }
235 : :
236 : : /*
237 : : * Initialize option lists.
238 : : */
239 : : static void
240 : 416 : InitPgFdwOptions(void)
241 : : {
242 : : 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},
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 : : /*
292 : : * gssdelegation is also a libpq option but should be allowed in a
293 : : * user mapping context too
294 : : */
295 : : {"gssdelegation", UserMappingRelationId, true},
296 : :
297 : : {NULL, InvalidOid, false}
298 : : };
299 : :
300 : : /* Prevent redundant initialization. */
301 [ + + ]: 416 : if (postgres_fdw_options)
302 : 400 : return;
303 : :
304 : : /*
305 : : * Get list of valid libpq options.
306 : : *
307 : : * To avoid unnecessary work, we get the list once and use it throughout
308 : : * the lifetime of this backend process. We don't need to care about
309 : : * memory context issues, because PQconndefaults allocates with malloc.
310 : : */
311 : 16 : libpq_options = PQconndefaults();
312 [ - + ]: 16 : if (!libpq_options) /* assume reason for failure is OOM */
4070 tgl@sss.pgh.pa.us 313 [ # # ]:UBC 0 : ereport(ERROR,
314 : : (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
315 : : errmsg("out of memory"),
316 : : errdetail("Could not get libpq's default connection options.")));
317 : :
318 : : /* Count how many libpq options are available. */
4070 tgl@sss.pgh.pa.us 319 :CBC 16 : num_libpq_opts = 0;
320 [ + + ]: 672 : for (lopt = libpq_options; lopt->keyword; lopt++)
321 : 656 : num_libpq_opts++;
322 : :
323 : : /*
324 : : * Construct an array which consists of all valid options for
325 : : * postgres_fdw, by appending FDW-specific options to libpq options.
326 : : *
327 : : * We use plain malloc here to allocate postgres_fdw_options because it
328 : : * lives as long as the backend process does. Besides, keeping
329 : : * libpq_options in memory allows us to avoid copying every keyword
330 : : * string.
331 : : */
332 : 16 : postgres_fdw_options = (PgFdwOption *)
333 : 16 : malloc(sizeof(PgFdwOption) * num_libpq_opts +
334 : : sizeof(non_libpq_options));
335 [ - + ]: 16 : if (postgres_fdw_options == NULL)
4070 tgl@sss.pgh.pa.us 336 [ # # ]:UBC 0 : ereport(ERROR,
337 : : (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
338 : : errmsg("out of memory")));
339 : :
4070 tgl@sss.pgh.pa.us 340 :CBC 16 : popt = postgres_fdw_options;
341 [ + + ]: 672 : for (lopt = libpq_options; lopt->keyword; lopt++)
342 : : {
343 : : /* Hide debug options, as well as settings we override internally. */
344 [ + + ]: 656 : if (strchr(lopt->dispchar, 'D') ||
345 [ + + ]: 640 : strcmp(lopt->keyword, "fallback_application_name") == 0 ||
346 [ + + ]: 624 : strcmp(lopt->keyword, "client_encoding") == 0)
347 : 48 : continue;
348 : :
349 : : /* We don't have to copy keyword string, as described above. */
350 : 608 : popt->keyword = lopt->keyword;
351 : :
352 : : /*
353 : : * "user" and any secret options are allowed only on user mappings.
354 : : * Everything else is a server option.
355 : : */
356 [ + + + + ]: 608 : if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
357 : 48 : popt->optcontext = UserMappingRelationId;
358 : : else
359 : 560 : popt->optcontext = ForeignServerRelationId;
360 : 608 : popt->is_libpq_opt = true;
361 : :
362 : 608 : popt++;
363 : : }
364 : :
365 : : /* Append FDW-specific options and dummy terminator. */
366 : 16 : memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
367 : : }
368 : :
369 : : /*
370 : : * Check whether the given option is one of the valid postgres_fdw options.
371 : : * context is the Oid of the catalog holding the object the option is for.
372 : : */
373 : : static bool
374 : 621 : is_valid_option(const char *keyword, Oid context)
375 : : {
376 : : PgFdwOption *opt;
377 : :
2489 378 [ - + ]: 621 : Assert(postgres_fdw_options); /* must be initialized already */
379 : :
4070 380 [ + + ]: 19157 : for (opt = postgres_fdw_options; opt->keyword; opt++)
381 : : {
382 [ + + + + ]: 19154 : if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
383 : 618 : return true;
384 : : }
385 : :
386 : 3 : return false;
387 : : }
388 : :
389 : : /*
390 : : * Check whether the given option is one of the valid libpq options.
391 : : */
392 : : static bool
393 : 245 : is_libpq_option(const char *keyword)
394 : : {
395 : : PgFdwOption *opt;
396 : :
2489 397 [ - + ]: 245 : Assert(postgres_fdw_options); /* must be initialized already */
398 : :
4070 399 [ + + ]: 6214 : for (opt = postgres_fdw_options; opt->keyword; opt++)
400 : : {
401 [ + + + + ]: 6142 : if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
402 : 173 : return true;
403 : : }
404 : :
405 : 72 : return false;
406 : : }
407 : :
408 : : /*
409 : : * Generate key-value arrays which include only libpq options from the
410 : : * given list (which can contain any kind of options). Caller must have
411 : : * allocated large-enough arrays. Returns number of options found.
412 : : */
413 : : int
414 : 140 : ExtractConnectionOptions(List *defelems, const char **keywords,
415 : : const char **values)
416 : : {
417 : : ListCell *lc;
418 : : int i;
419 : :
420 : : /* Build our options lists if we didn't yet. */
421 : 140 : InitPgFdwOptions();
422 : :
423 : 140 : i = 0;
424 [ + + + + : 385 : foreach(lc, defelems)
+ + ]
425 : : {
426 : 245 : DefElem *d = (DefElem *) lfirst(lc);
427 : :
428 [ + + ]: 245 : if (is_libpq_option(d->defname))
429 : : {
430 : 173 : keywords[i] = d->defname;
431 : 173 : values[i] = defGetString(d);
432 : 173 : i++;
433 : : }
434 : : }
435 : 140 : return i;
436 : : }
437 : :
438 : : /*
439 : : * Parse a comma-separated string and return a List of the OIDs of the
440 : : * extensions named in the string. If any names in the list cannot be
441 : : * found, report a warning if warnOnMissing is true, else just silently
442 : : * ignore them.
443 : : */
444 : : List *
3085 445 : 930 : ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
446 : : {
447 : 930 : List *extensionOids = NIL;
448 : : List *extlist;
449 : : ListCell *lc;
450 : :
451 : : /* SplitIdentifierString scribbles on its input, so pstrdup first */
452 [ + + ]: 930 : if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
453 : : {
454 : : /* syntax error in name list */
455 [ + - ]: 1 : ereport(ERROR,
456 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
457 : : errmsg("parameter \"%s\" must be a list of extension names",
458 : : "extensions")));
459 : : }
460 : :
461 [ + - + + : 1859 : foreach(lc, extlist)
+ + ]
462 : : {
463 : 930 : const char *extension_name = (const char *) lfirst(lc);
464 : 930 : Oid extension_oid = get_extension_oid(extension_name, true);
465 : :
466 [ + + ]: 930 : if (OidIsValid(extension_oid))
467 : : {
468 : 928 : extensionOids = lappend_oid(extensionOids, extension_oid);
469 : : }
470 [ + - ]: 2 : else if (warnOnMissing)
471 : : {
472 [ + - ]: 2 : ereport(WARNING,
473 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
474 : : errmsg("extension \"%s\" is not installed",
475 : : extension_name)));
476 : : }
477 : : }
478 : :
479 : 929 : list_free(extlist);
480 : 929 : return extensionOids;
481 : : }
482 : :
483 : : /*
484 : : * Replace escape sequences beginning with % character in the given
485 : : * application_name with status information, and return it.
486 : : *
487 : : * This function always returns a palloc'd string, so the caller is
488 : : * responsible for pfreeing it.
489 : : */
490 : : char *
842 fujii@postgresql.org 491 : 16 : process_pgfdw_appname(const char *appname)
492 : : {
493 : : const char *p;
494 : : StringInfoData buf;
495 : :
496 : 16 : initStringInfo(&buf);
497 : :
498 [ + + ]: 221 : for (p = appname; *p != '\0'; p++)
499 : : {
500 [ + + ]: 205 : if (*p != '%')
501 : : {
502 : : /* literal char, just copy */
503 : 196 : appendStringInfoChar(&buf, *p);
504 : 196 : continue;
505 : : }
506 : :
507 : : /* must be a '%', so skip to the next char */
508 : 9 : p++;
509 [ - + ]: 9 : if (*p == '\0')
842 fujii@postgresql.org 510 :UBC 0 : break; /* format error - ignore it */
842 fujii@postgresql.org 511 [ + + ]:CBC 9 : else if (*p == '%')
512 : : {
513 : : /* string contains %% */
514 : 1 : appendStringInfoChar(&buf, '%');
515 : 1 : continue;
516 : : }
517 : :
518 : : /* process the option */
519 [ + + + + : 8 : switch (*p)
+ + - ]
520 : : {
521 : 1 : case 'a':
522 : 1 : appendStringInfoString(&buf, application_name);
523 : 1 : break;
786 524 : 2 : case 'c':
525 : 2 : appendStringInfo(&buf, "%lx.%x", (long) (MyStartTime), MyProcPid);
526 : 2 : break;
527 : 2 : case 'C':
528 : 2 : appendStringInfoString(&buf, cluster_name);
529 : 2 : break;
842 530 : 1 : case 'd':
418 michael@paquier.xyz 531 [ + - ]: 1 : if (MyProcPort)
532 : : {
533 : 1 : const char *dbname = MyProcPort->database_name;
534 : :
535 [ + - ]: 1 : if (dbname)
536 : 1 : appendStringInfoString(&buf, dbname);
537 : : else
418 michael@paquier.xyz 538 :UBC 0 : appendStringInfoString(&buf, "[unknown]");
539 : : }
842 fujii@postgresql.org 540 :CBC 1 : break;
541 : 1 : case 'p':
542 : 1 : appendStringInfo(&buf, "%d", MyProcPid);
543 : 1 : break;
544 : 1 : case 'u':
418 michael@paquier.xyz 545 [ + - ]: 1 : if (MyProcPort)
546 : : {
547 : 1 : const char *username = MyProcPort->user_name;
548 : :
549 [ + - ]: 1 : if (username)
550 : 1 : appendStringInfoString(&buf, username);
551 : : else
418 michael@paquier.xyz 552 :UBC 0 : appendStringInfoString(&buf, "[unknown]");
553 : : }
842 fujii@postgresql.org 554 :CBC 1 : break;
842 fujii@postgresql.org 555 :UBC 0 : default:
556 : : /* format error - ignore it */
557 : 0 : break;
558 : : }
559 : : }
560 : :
842 fujii@postgresql.org 561 :CBC 16 : return buf.data;
562 : : }
563 : :
564 : : /*
565 : : * Module load callback
566 : : */
567 : : void
950 568 : 16 : _PG_init(void)
569 : : {
570 : : /*
571 : : * Unlike application_name GUC, don't set GUC_IS_NAME flag nor check_hook
572 : : * to allow postgres_fdw.application_name to be any string more than
573 : : * NAMEDATALEN characters and to include non-ASCII characters. Instead,
574 : : * remote server truncates application_name of remote connection to less
575 : : * than NAMEDATALEN and replaces any non-ASCII characters in it with a '?'
576 : : * character.
577 : : */
578 : 16 : DefineCustomStringVariable("postgres_fdw.application_name",
579 : : "Sets the application name to be used on the remote server.",
580 : : NULL,
581 : : &pgfdw_application_name,
582 : : NULL,
583 : : PGC_USERSET,
584 : : 0,
585 : : NULL,
586 : : NULL,
587 : : NULL);
588 : :
783 tgl@sss.pgh.pa.us 589 : 16 : MarkGUCPrefixReserved("postgres_fdw");
950 fujii@postgresql.org 590 : 16 : }
|