/src/postgres/src/common/percentrepl.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * percentrepl.c |
4 | | * Common routines to replace percent placeholders in strings |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/common/percentrepl.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | |
16 | | #ifndef FRONTEND |
17 | | #include "postgres.h" |
18 | | #else |
19 | | #include "postgres_fe.h" |
20 | | #include "common/logging.h" |
21 | | #endif |
22 | | |
23 | | #include "common/percentrepl.h" |
24 | | #include "lib/stringinfo.h" |
25 | | |
26 | | /* |
27 | | * replace_percent_placeholders |
28 | | * |
29 | | * Replace percent-letter placeholders in input string with the supplied |
30 | | * values. For example, to replace %f with foo and %b with bar, call |
31 | | * |
32 | | * replace_percent_placeholders(instr, "param_name", "bf", bar, foo); |
33 | | * |
34 | | * The return value is palloc'd. |
35 | | * |
36 | | * "%%" is replaced by a single "%". |
37 | | * |
38 | | * This throws an error for an unsupported placeholder or a "%" at the end of |
39 | | * the input string. |
40 | | * |
41 | | * A value may be NULL. If the corresponding placeholder is found in the |
42 | | * input string, it will be treated as if an unsupported placeholder was used. |
43 | | * This allows callers to share a "letters" specification but vary the |
44 | | * actually supported placeholders at run time. |
45 | | * |
46 | | * This functions is meant for cases where all the values are readily |
47 | | * available or cheap to compute and most invocations will use most values |
48 | | * (for example for archive_command). Also, it requires that all values are |
49 | | * strings. It won't be a good match for things like log prefixes or prompts |
50 | | * that use a mix of data types and any invocation will only use a few of the |
51 | | * possible values. |
52 | | * |
53 | | * param_name is the name of the underlying GUC parameter, for error |
54 | | * reporting. At the moment, this function is only used for GUC parameters. |
55 | | * If other kinds of uses were added, the error reporting would need to be |
56 | | * revised. |
57 | | */ |
58 | | char * |
59 | | replace_percent_placeholders(const char *instr, const char *param_name, const char *letters,...) |
60 | 0 | { |
61 | 0 | StringInfoData result; |
62 | |
|
63 | 0 | initStringInfo(&result); |
64 | |
|
65 | 0 | for (const char *sp = instr; *sp; sp++) |
66 | 0 | { |
67 | 0 | if (*sp == '%') |
68 | 0 | { |
69 | 0 | if (sp[1] == '%') |
70 | 0 | { |
71 | | /* Convert %% to a single % */ |
72 | 0 | sp++; |
73 | 0 | appendStringInfoChar(&result, *sp); |
74 | 0 | } |
75 | 0 | else if (sp[1] == '\0') |
76 | 0 | { |
77 | | /* Incomplete escape sequence, expected a character afterward */ |
78 | | #ifdef FRONTEND |
79 | | pg_log_error("invalid value for parameter \"%s\": \"%s\"", param_name, instr); |
80 | | pg_log_error_detail("String ends unexpectedly after escape character \"%%\"."); |
81 | | exit(1); |
82 | | #else |
83 | 0 | ereport(ERROR, |
84 | 0 | errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
85 | 0 | errmsg("invalid value for parameter \"%s\": \"%s\"", param_name, instr), |
86 | 0 | errdetail("String ends unexpectedly after escape character \"%%\".")); |
87 | 0 | #endif |
88 | 0 | } |
89 | 0 | else |
90 | 0 | { |
91 | | /* Look up placeholder character */ |
92 | 0 | bool found = false; |
93 | 0 | va_list ap; |
94 | |
|
95 | 0 | sp++; |
96 | |
|
97 | 0 | va_start(ap, letters); |
98 | 0 | for (const char *lp = letters; *lp; lp++) |
99 | 0 | { |
100 | 0 | char *val = va_arg(ap, char *); |
101 | |
|
102 | 0 | if (*sp == *lp) |
103 | 0 | { |
104 | 0 | if (val) |
105 | 0 | { |
106 | 0 | appendStringInfoString(&result, val); |
107 | 0 | found = true; |
108 | 0 | } |
109 | | /* If val is NULL, we will report an error. */ |
110 | 0 | break; |
111 | 0 | } |
112 | 0 | } |
113 | 0 | va_end(ap); |
114 | 0 | if (!found) |
115 | 0 | { |
116 | | /* Unknown placeholder */ |
117 | | #ifdef FRONTEND |
118 | | pg_log_error("invalid value for parameter \"%s\": \"%s\"", param_name, instr); |
119 | | pg_log_error_detail("String contains unexpected placeholder \"%%%c\".", *sp); |
120 | | exit(1); |
121 | | #else |
122 | 0 | ereport(ERROR, |
123 | 0 | errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
124 | 0 | errmsg("invalid value for parameter \"%s\": \"%s\"", param_name, instr), |
125 | 0 | errdetail("String contains unexpected placeholder \"%%%c\".", *sp)); |
126 | 0 | #endif |
127 | 0 | } |
128 | 0 | } |
129 | 0 | } |
130 | 0 | else |
131 | 0 | { |
132 | 0 | appendStringInfoChar(&result, *sp); |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | 0 | return result.data; |
137 | 0 | } |