Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * psql - the PostgreSQL interactive terminal
3 : : *
4 : : * Copyright (c) 2000-2026, PostgreSQL Global Development Group
5 : : *
6 : : * src/bin/psql/prompt.c
7 : : */
8 : : #include "postgres_fe.h"
9 : :
10 : : #ifdef WIN32
11 : : #include <io.h>
12 : : #include <win32.h>
13 : : #endif
14 : :
15 : : #include "common.h"
16 : : #include "common/string.h"
17 : : #include "input.h"
18 : : #include "libpq/pqcomm.h"
19 : : #include "prompt.h"
20 : : #include "settings.h"
21 : :
22 : : /*--------------------------
23 : : * get_prompt
24 : : *
25 : : * Returns a statically allocated prompt made by interpolating certain
26 : : * tcsh style escape sequences into pset.vars "PROMPT1|2|3".
27 : : * (might not be completely multibyte safe)
28 : : *
29 : : * Defined interpolations are:
30 : : * %M - database server "hostname.domainname", "[local]" for AF_UNIX
31 : : * sockets, "[local:/dir/name]" if not default
32 : : * %m - like %M, but hostname only (before first dot), or always "[local]"
33 : : * %p - backend pid
34 : : * %P - pipeline status: on, off or abort
35 : : * %> - database server port number
36 : : * %n - database user name
37 : : * %S - search_path
38 : : * %s - service
39 : : * %/ - current database
40 : : * %~ - like %/ but "~" when database name equals user name
41 : : * %w - whitespace of the same width as the most recent output of PROMPT1
42 : : * %# - "#" if superuser, ">" otherwise
43 : : * %R - in prompt1 normally =, or ^ if single line mode,
44 : : * or a ! if session is not connected to a database;
45 : : * in prompt2 -, *, ', or ";
46 : : * in prompt3 nothing
47 : : * %i - "standby" or "primary" depending on the server's in_hot_standby
48 : : * status, or "?" if unavailable (empty if unknown)
49 : : * %x - transaction status: empty, *, !, ? (unknown or no connection)
50 : : * %l - The line number inside the current statement, starting from 1.
51 : : * %? - the error code of the last query (not yet implemented)
52 : : * %% - a percent sign
53 : : *
54 : : * %[0-9] - the character with the given decimal code
55 : : * %0[0-7] - the character with the given octal code
56 : : * %0x[0-9A-Fa-f] - the character with the given hexadecimal code
57 : : *
58 : : * %`command` - The result of executing command in /bin/sh with trailing
59 : : * newline stripped.
60 : : * %:name: - The value of the psql variable 'name'
61 : : * (those will not be rescanned for more escape sequences!)
62 : : *
63 : : * %[ ... %] - tell readline that the contained text is invisible
64 : : *
65 : : * If the application-wide prompts become NULL somehow, the returned string
66 : : * will be empty (not NULL!).
67 : : *--------------------------
68 : : */
69 : :
70 : : char *
3272 tgl@sss.pgh.pa.us 71 :CBC 67 : get_prompt(promptStatus_t status, ConditionalStack cstack)
72 : : {
73 : : #define MAX_PROMPT_SIZE 256
74 : : static char destination[MAX_PROMPT_SIZE + 1];
75 : : char buf[MAX_PROMPT_SIZE + 1];
9628 bruce@momjian.us 76 : 67 : bool esc = false;
77 : : const char *p;
8396 78 : 67 : const char *prompt_string = "? ";
79 : : static size_t last_prompt1_width = 0;
80 : :
81 [ + + - - ]: 67 : switch (status)
82 : : {
83 : 65 : case PROMPT_READY:
7138 tgl@sss.pgh.pa.us 84 : 65 : prompt_string = pset.prompt1;
8396 bruce@momjian.us 85 : 65 : break;
86 : :
87 : 2 : case PROMPT_CONTINUE:
88 : : case PROMPT_SINGLEQUOTE:
89 : : case PROMPT_DOUBLEQUOTE:
90 : : case PROMPT_DOLLARQUOTE:
91 : : case PROMPT_COMMENT:
92 : : case PROMPT_PAREN:
7138 tgl@sss.pgh.pa.us 93 : 2 : prompt_string = pset.prompt2;
8396 bruce@momjian.us 94 : 2 : break;
95 : :
8396 bruce@momjian.us 96 :UBC 0 : case PROMPT_COPY:
7138 tgl@sss.pgh.pa.us 97 : 0 : prompt_string = pset.prompt3;
8396 bruce@momjian.us 98 : 0 : break;
99 : : }
100 : :
9628 bruce@momjian.us 101 :CBC 67 : destination[0] = '\0';
102 : :
103 : 67 : for (p = prompt_string;
6975 peter_e@gmx.net 104 [ + + + - ]: 670 : *p && strlen(destination) < sizeof(destination) - 1;
9628 bruce@momjian.us 105 : 603 : p++)
106 : : {
6975 peter_e@gmx.net 107 : 603 : memset(buf, 0, sizeof(buf));
9628 bruce@momjian.us 108 [ + + ]: 603 : if (esc)
109 : : {
110 [ + - - - : 268 : switch (*p)
- - - - -
- - + - +
- - + - -
- - ]
111 : : {
112 : : /* Current database */
113 : 67 : case '/':
9557 peter_e@gmx.net 114 [ + - ]: 67 : if (pset.db)
6975 115 : 67 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
9628 bruce@momjian.us 116 : 67 : break;
9628 bruce@momjian.us 117 :UBC 0 : case '~':
8259 118 [ # # ]: 0 : if (pset.db)
119 : : {
120 : : const char *var;
121 : :
122 [ # # # # ]: 0 : if (strcmp(PQdb(pset.db), PQuser(pset.db)) == 0 ||
123 [ # # ]: 0 : ((var = getenv("PGDATABASE")) && strcmp(var, PQdb(pset.db)) == 0))
6975 peter_e@gmx.net 124 : 0 : strlcpy(buf, "~", sizeof(buf));
125 : : else
126 : 0 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
127 : : }
8259 bruce@momjian.us 128 : 0 : break;
129 : :
130 : : /* Whitespace of the same width as the last PROMPT1 */
2308 tmunro@postgresql.or 131 : 0 : case 'w':
132 [ # # ]: 0 : if (pset.db)
133 : 0 : memset(buf, ' ',
134 : 0 : Min(last_prompt1_width, sizeof(buf) - 1));
135 : 0 : break;
136 : :
137 : : /* DB server hostname (long/short) */
9628 bruce@momjian.us 138 : 0 : case 'M':
139 : : case 'm':
9557 peter_e@gmx.net 140 [ # # ]: 0 : if (pset.db)
141 : : {
8907 bruce@momjian.us 142 : 0 : const char *host = PQhost(pset.db);
143 : :
144 : : /* INET socket */
1936 peter@eisentraut.org 145 [ # # # # : 0 : if (host && host[0] && !is_unixsock_path(host))
# # ]
146 : : {
6975 peter_e@gmx.net 147 : 0 : strlcpy(buf, host, sizeof(buf));
9628 bruce@momjian.us 148 [ # # ]: 0 : if (*p == 'm')
149 : 0 : buf[strcspn(buf, ".")] = '\0';
150 : : }
151 : : /* UNIX socket */
152 : : else
153 : : {
9079 peter_e@gmx.net 154 [ # # ]: 0 : if (!host
8907 bruce@momjian.us 155 [ # # ]: 0 : || strcmp(host, DEFAULT_PGSOCKET_DIR) == 0
9079 peter_e@gmx.net 156 [ # # ]: 0 : || *p == 'm')
6975 157 : 0 : strlcpy(buf, "[local]", sizeof(buf));
158 : : else
159 : 0 : snprintf(buf, sizeof(buf), "[local:%s]", host);
160 : : }
161 : : }
9628 bruce@momjian.us 162 : 0 : break;
163 : : /* DB server port number */
164 : 0 : case '>':
9557 peter_e@gmx.net 165 [ # # # # ]: 0 : if (pset.db && PQport(pset.db))
6975 166 : 0 : strlcpy(buf, PQport(pset.db), sizeof(buf));
9628 bruce@momjian.us 167 : 0 : break;
168 : : /* DB server user name */
169 : 0 : case 'n':
9557 peter_e@gmx.net 170 [ # # ]: 0 : if (pset.db)
6975 171 : 0 : strlcpy(buf, session_username(), sizeof(buf));
9628 bruce@momjian.us 172 : 0 : break;
173 : : /* search_path */
138 nathan@postgresql.or 174 :UNC 0 : case 'S':
175 [ # # ]: 0 : if (pset.db)
176 : : {
177 : 0 : const char *sp = PQparameterStatus(pset.db, "search_path");
178 : :
179 : : /* Use ? for versions that don't report search_path. */
180 [ # # ]: 0 : strlcpy(buf, sp ? sp : "?", sizeof(buf));
181 : : }
182 : 0 : break;
183 : : /* service name */
452 michael@paquier.xyz 184 :UBC 0 : case 's':
185 : : {
249 186 : 0 : const char *service_name = GetVariable(pset.vars, "SERVICE");
187 : :
188 [ # # ]: 0 : if (service_name)
189 : 0 : strlcpy(buf, service_name, sizeof(buf));
190 : : }
452 191 : 0 : break;
192 : : /* backend pid */
3904 andres@anarazel.de 193 : 0 : case 'p':
194 [ # # ]: 0 : if (pset.db)
195 : : {
3566 rhaas@postgresql.org 196 : 0 : int pid = PQbackendPID(pset.db);
197 : :
3904 andres@anarazel.de 198 [ # # ]: 0 : if (pid)
199 : 0 : snprintf(buf, sizeof(buf), "%d", pid);
200 : : }
201 : 0 : break;
202 : : /* pipeline status */
383 michael@paquier.xyz 203 : 0 : case 'P':
45 204 [ # # ]: 0 : if (pset.db)
205 : : {
383 206 : 0 : PGpipelineStatus status = PQpipelineStatus(pset.db);
207 : :
208 [ # # ]: 0 : if (status == PQ_PIPELINE_ON)
209 : 0 : strlcpy(buf, "on", sizeof(buf));
210 [ # # ]: 0 : else if (status == PQ_PIPELINE_ABORTED)
211 : 0 : strlcpy(buf, "abort", sizeof(buf));
212 : : else
213 : 0 : strlcpy(buf, "off", sizeof(buf));
214 : : }
45 215 : 0 : break;
9628 bruce@momjian.us 216 : 0 : case '0':
217 : : case '1':
218 : : case '2':
219 : : case '3':
220 : : case '4':
221 : : case '5':
222 : : case '6':
223 : : case '7':
2602 peter@eisentraut.org 224 : 0 : *buf = (char) strtol(p, unconstify(char **, &p), 8);
7594 bruce@momjian.us 225 : 0 : --p;
8296 tgl@sss.pgh.pa.us 226 : 0 : break;
9628 bruce@momjian.us 227 :CBC 67 : case 'R':
228 [ + + - - : 67 : switch (status)
- - + - ]
229 : : {
230 : 65 : case PROMPT_READY:
3272 tgl@sss.pgh.pa.us 231 [ + - - + ]: 65 : if (cstack != NULL && !conditional_active(cstack))
3272 tgl@sss.pgh.pa.us 232 :UBC 0 : buf[0] = '@';
3272 tgl@sss.pgh.pa.us 233 [ - + ]:CBC 65 : else if (!pset.db)
9628 bruce@momjian.us 234 :UBC 0 : buf[0] = '!';
7138 tgl@sss.pgh.pa.us 235 [ + - ]:CBC 65 : else if (!pset.singleline)
9628 bruce@momjian.us 236 : 65 : buf[0] = '=';
237 : : else
9628 bruce@momjian.us 238 :UBC 0 : buf[0] = '^';
9628 bruce@momjian.us 239 :CBC 65 : break;
240 : 1 : case PROMPT_CONTINUE:
241 : 1 : buf[0] = '-';
242 : 1 : break;
9628 bruce@momjian.us 243 :UBC 0 : case PROMPT_SINGLEQUOTE:
244 : 0 : buf[0] = '\'';
245 : 0 : break;
246 : 0 : case PROMPT_DOUBLEQUOTE:
247 : 0 : buf[0] = '"';
248 : 0 : break;
8055 tgl@sss.pgh.pa.us 249 : 0 : case PROMPT_DOLLARQUOTE:
250 : 0 : buf[0] = '$';
251 : 0 : break;
9628 bruce@momjian.us 252 : 0 : case PROMPT_COMMENT:
253 : 0 : buf[0] = '*';
254 : 0 : break;
9468 bruce@momjian.us 255 :CBC 1 : case PROMPT_PAREN:
256 : 1 : buf[0] = '(';
257 : 1 : break;
9628 bruce@momjian.us 258 :UBC 0 : default:
259 : 0 : buf[0] = '\0';
260 : 0 : break;
261 : : }
8296 tgl@sss.pgh.pa.us 262 :CBC 67 : break;
40 fujii@postgresql.org 263 :UNC 0 : case 'i':
264 [ # # ]: 0 : if (pset.db)
265 : : {
266 : 0 : const char *hs = PQparameterStatus(pset.db, "in_hot_standby");
267 : :
268 [ # # ]: 0 : if (hs)
269 : : {
270 [ # # ]: 0 : if (strcmp(hs, "on") == 0)
271 : 0 : strlcpy(buf, "standby", sizeof(buf));
272 : : else
273 : 0 : strlcpy(buf, "primary", sizeof(buf));
274 : : }
275 : : /* Use ? for versions that don't report in_hot_standby */
276 : : else
277 : 0 : buf[0] = '?';
278 : : }
279 : 0 : break;
8198 peter_e@gmx.net 280 :CBC 67 : case 'x':
8296 tgl@sss.pgh.pa.us 281 [ - + ]: 67 : if (!pset.db)
8296 tgl@sss.pgh.pa.us 282 :UBC 0 : buf[0] = '?';
283 : : else
8259 bruce@momjian.us 284 [ + - - - ]:CBC 67 : switch (PQtransactionStatus(pset.db))
285 : : {
286 : 67 : case PQTRANS_IDLE:
287 : 67 : buf[0] = '\0';
288 : 67 : break;
8259 bruce@momjian.us 289 :UBC 0 : case PQTRANS_ACTIVE:
290 : : case PQTRANS_INTRANS:
291 : 0 : buf[0] = '*';
292 : 0 : break;
293 : 0 : case PQTRANS_INERROR:
294 : 0 : buf[0] = '!';
295 : 0 : break;
296 : 0 : default:
297 : 0 : buf[0] = '?';
298 : 0 : break;
299 : : }
8296 tgl@sss.pgh.pa.us 300 :CBC 67 : break;
301 : :
4212 andres@anarazel.de 302 :UBC 0 : case 'l':
303 : 0 : snprintf(buf, sizeof(buf), UINT64_FORMAT, pset.stmt_lineno);
304 : 0 : break;
305 : :
9628 bruce@momjian.us 306 : 0 : case '?':
307 : : /* not here yet */
308 : 0 : break;
309 : :
9628 bruce@momjian.us 310 :CBC 67 : case '#':
8296 tgl@sss.pgh.pa.us 311 [ + - ]: 67 : if (is_superuser())
312 : 67 : buf[0] = '#';
313 : : else
8296 tgl@sss.pgh.pa.us 314 :UBC 0 : buf[0] = '>';
8296 tgl@sss.pgh.pa.us 315 :CBC 67 : break;
316 : :
317 : : /* execute command */
9628 bruce@momjian.us 318 :UBC 0 : case '`':
319 : : {
2293 alvherre@alvh.no-ip. 320 : 0 : int cmdend = strcspn(p + 1, "`");
321 : 0 : char *file = pnstrdup(p + 1, cmdend);
322 : : FILE *fd;
323 : :
1294 tgl@sss.pgh.pa.us 324 : 0 : fflush(NULL);
325 : 0 : fd = popen(file, "r");
9628 bruce@momjian.us 326 [ # # ]: 0 : if (fd)
327 : : {
5798 tgl@sss.pgh.pa.us 328 [ # # ]: 0 : if (fgets(buf, sizeof(buf), fd) == NULL)
329 : 0 : buf[0] = '\0';
9628 bruce@momjian.us 330 : 0 : pclose(fd);
331 : : }
332 : :
333 : : /* strip trailing newline and carriage return */
2410 michael@paquier.xyz 334 : 0 : (void) pg_strip_crlf(buf);
335 : :
9628 bruce@momjian.us 336 : 0 : free(file);
337 : 0 : p += cmdend + 1;
338 : 0 : break;
339 : : }
340 : :
341 : : /* interpolate variable */
9533 peter_e@gmx.net 342 : 0 : case ':':
343 : : {
2293 alvherre@alvh.no-ip. 344 : 0 : int nameend = strcspn(p + 1, ":");
345 : 0 : char *name = pnstrdup(p + 1, nameend);
346 : : const char *val;
347 : :
9557 peter_e@gmx.net 348 : 0 : val = GetVariable(pset.vars, name);
9628 bruce@momjian.us 349 [ # # ]: 0 : if (val)
6975 peter_e@gmx.net 350 : 0 : strlcpy(buf, val, sizeof(buf));
9628 bruce@momjian.us 351 : 0 : free(name);
352 : 0 : p += nameend + 1;
353 : 0 : break;
354 : : }
355 : :
7868 356 : 0 : case '[':
357 : : case ']':
358 : : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
359 : :
360 : : /*
361 : : * readline >=4.0 undocumented feature: non-printing
362 : : * characters in prompt strings must be marked as such, in
363 : : * order to properly display the line during editing.
364 : : */
7376 tgl@sss.pgh.pa.us 365 [ # # ]: 0 : buf[0] = (*p == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
366 : 0 : buf[1] = '\0';
367 : : #endif /* USE_READLINE */
7868 bruce@momjian.us 368 : 0 : break;
369 : :
9628 370 : 0 : default:
371 : 0 : buf[0] = *p;
372 : 0 : buf[1] = '\0';
8090 tgl@sss.pgh.pa.us 373 : 0 : break;
374 : : }
9628 bruce@momjian.us 375 :CBC 268 : esc = false;
376 : : }
377 [ + + ]: 335 : else if (*p == '%')
378 : 268 : esc = true;
379 : : else
380 : : {
381 : 67 : buf[0] = *p;
382 : 67 : buf[1] = '\0';
383 : 67 : esc = false;
384 : : }
385 : :
386 [ + + ]: 603 : if (!esc)
6975 peter_e@gmx.net 387 : 335 : strlcat(destination, buf, sizeof(destination));
388 : : }
389 : :
390 : : /* Compute the visible width of PROMPT1, for PROMPT2's %w */
2308 tmunro@postgresql.or 391 [ + + ]: 67 : if (prompt_string == pset.prompt1)
392 : : {
393 : 65 : char *p = destination;
394 : 65 : char *end = p + strlen(p);
395 : 65 : bool visible = true;
396 : :
397 : 65 : last_prompt1_width = 0;
398 [ + + ]: 780 : while (*p)
399 : : {
400 : : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
401 [ - + ]: 715 : if (*p == RL_PROMPT_START_IGNORE)
402 : : {
2308 tmunro@postgresql.or 403 :UBC 0 : visible = false;
404 : 0 : ++p;
405 : : }
2308 tmunro@postgresql.or 406 [ - + ]:CBC 715 : else if (*p == RL_PROMPT_END_IGNORE)
407 : : {
2308 tmunro@postgresql.or 408 :UBC 0 : visible = true;
409 : 0 : ++p;
410 : : }
411 : : else
412 : : #endif
413 : : {
414 : : int chlen,
415 : : chwidth;
416 : :
2308 tmunro@postgresql.or 417 :CBC 715 : chlen = PQmblen(p, pset.encoding);
418 [ - + ]: 715 : if (p + chlen > end)
2308 tmunro@postgresql.or 419 :UBC 0 : break; /* Invalid string */
420 : :
2308 tmunro@postgresql.or 421 [ + - ]:CBC 715 : if (visible)
422 : : {
423 : 715 : chwidth = PQdsplen(p, pset.encoding);
424 : :
2225 425 [ - + ]: 715 : if (*p == '\n')
2225 tmunro@postgresql.or 426 :UBC 0 : last_prompt1_width = 0;
2225 tmunro@postgresql.or 427 [ + - ]:CBC 715 : else if (chwidth > 0)
2308 428 : 715 : last_prompt1_width += chwidth;
429 : : }
430 : :
431 : 715 : p += chlen;
432 : : }
433 : : }
434 : : }
435 : :
9628 bruce@momjian.us 436 : 67 : return destination;
437 : : }
|