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 *
12 peter@eisentraut.org 71 :GNC 70 : get_prompt(promptStatus_t prompt_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];
9817 bruce@momjian.us 76 :CBC 70 : bool esc = false;
8585 77 : 70 : const char *prompt_string = "? ";
78 : : static size_t last_prompt1_width = 0;
79 : :
12 peter@eisentraut.org 80 [ + + - - ]:GNC 70 : switch (prompt_status)
81 : : {
8585 bruce@momjian.us 82 :CBC 68 : case PROMPT_READY:
7327 tgl@sss.pgh.pa.us 83 : 68 : prompt_string = pset.prompt1;
8585 bruce@momjian.us 84 : 68 : break;
85 : :
86 : 2 : case PROMPT_CONTINUE:
87 : : case PROMPT_SINGLEQUOTE:
88 : : case PROMPT_DOUBLEQUOTE:
89 : : case PROMPT_DOLLARQUOTE:
90 : : case PROMPT_COMMENT:
91 : : case PROMPT_PAREN:
7327 tgl@sss.pgh.pa.us 92 : 2 : prompt_string = pset.prompt2;
8585 bruce@momjian.us 93 : 2 : break;
94 : :
8585 bruce@momjian.us 95 :UBC 0 : case PROMPT_COPY:
7327 tgl@sss.pgh.pa.us 96 : 0 : prompt_string = pset.prompt3;
8585 bruce@momjian.us 97 : 0 : break;
98 : : }
99 : :
9817 bruce@momjian.us 100 :CBC 70 : destination[0] = '\0';
101 : :
12 peter@eisentraut.org 102 :GNC 70 : for (const char *p = prompt_string;
7164 peter_e@gmx.net 103 [ + + + - ]:CBC 700 : *p && strlen(destination) < sizeof(destination) - 1;
9817 bruce@momjian.us 104 : 630 : p++)
105 : : {
7164 peter_e@gmx.net 106 : 630 : memset(buf, 0, sizeof(buf));
9817 bruce@momjian.us 107 [ + + ]: 630 : if (esc)
108 : : {
109 [ + - - - : 280 : switch (*p)
- - - - -
- - + - +
- - + - -
- - ]
110 : : {
111 : : /* Current database */
112 : 70 : case '/':
9746 peter_e@gmx.net 113 [ + - ]: 70 : if (pset.db)
7164 114 : 70 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
9817 bruce@momjian.us 115 : 70 : break;
9817 bruce@momjian.us 116 :UBC 0 : case '~':
8448 117 [ # # ]: 0 : if (pset.db)
118 : : {
119 : : const char *var;
120 : :
121 [ # # # # ]: 0 : if (strcmp(PQdb(pset.db), PQuser(pset.db)) == 0 ||
122 [ # # ]: 0 : ((var = getenv("PGDATABASE")) && strcmp(var, PQdb(pset.db)) == 0))
7164 peter_e@gmx.net 123 : 0 : strlcpy(buf, "~", sizeof(buf));
124 : : else
125 : 0 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
126 : : }
8448 bruce@momjian.us 127 : 0 : break;
128 : :
129 : : /* Whitespace of the same width as the last PROMPT1 */
2497 tmunro@postgresql.or 130 : 0 : case 'w':
131 [ # # ]: 0 : if (pset.db)
132 : 0 : memset(buf, ' ',
133 : 0 : Min(last_prompt1_width, sizeof(buf) - 1));
134 : 0 : break;
135 : :
136 : : /* DB server hostname (long/short) */
9817 bruce@momjian.us 137 : 0 : case 'M':
138 : : case 'm':
9746 peter_e@gmx.net 139 [ # # ]: 0 : if (pset.db)
140 : : {
9096 bruce@momjian.us 141 : 0 : const char *host = PQhost(pset.db);
142 : :
143 : : /* INET socket */
2125 peter@eisentraut.org 144 [ # # # # : 0 : if (host && host[0] && !is_unixsock_path(host))
# # ]
145 : : {
7164 peter_e@gmx.net 146 : 0 : strlcpy(buf, host, sizeof(buf));
9817 bruce@momjian.us 147 [ # # ]: 0 : if (*p == 'm')
148 : 0 : buf[strcspn(buf, ".")] = '\0';
149 : : }
150 : : /* UNIX socket */
151 : : else
152 : : {
9268 peter_e@gmx.net 153 [ # # ]: 0 : if (!host
9096 bruce@momjian.us 154 [ # # ]: 0 : || strcmp(host, DEFAULT_PGSOCKET_DIR) == 0
9268 peter_e@gmx.net 155 [ # # ]: 0 : || *p == 'm')
7164 156 : 0 : strlcpy(buf, "[local]", sizeof(buf));
157 : : else
158 : 0 : snprintf(buf, sizeof(buf), "[local:%s]", host);
159 : : }
160 : : }
9817 bruce@momjian.us 161 : 0 : break;
162 : : /* DB server port number */
163 : 0 : case '>':
9746 peter_e@gmx.net 164 [ # # # # ]: 0 : if (pset.db && PQport(pset.db))
7164 165 : 0 : strlcpy(buf, PQport(pset.db), sizeof(buf));
9817 bruce@momjian.us 166 : 0 : break;
167 : : /* DB server user name */
168 : 0 : case 'n':
9746 peter_e@gmx.net 169 [ # # ]: 0 : if (pset.db)
7164 170 : 0 : strlcpy(buf, session_username(), sizeof(buf));
9817 bruce@momjian.us 171 : 0 : break;
172 : : /* search_path */
327 nathan@postgresql.or 173 : 0 : case 'S':
174 [ # # ]: 0 : if (pset.db)
175 : : {
176 : 0 : const char *sp = PQparameterStatus(pset.db, "search_path");
177 : :
178 : : /* Use ? for versions that don't report search_path. */
179 [ # # ]: 0 : strlcpy(buf, sp ? sp : "?", sizeof(buf));
180 : : }
181 : 0 : break;
182 : : /* service name */
641 michael@paquier.xyz 183 : 0 : case 's':
184 : : {
438 185 : 0 : const char *service_name = GetVariable(pset.vars, "SERVICE");
186 : :
187 [ # # ]: 0 : if (service_name)
188 : 0 : strlcpy(buf, service_name, sizeof(buf));
189 : : }
641 190 : 0 : break;
191 : : /* backend pid */
4093 andres@anarazel.de 192 : 0 : case 'p':
193 [ # # ]: 0 : if (pset.db)
194 : : {
3755 rhaas@postgresql.org 195 : 0 : int pid = PQbackendPID(pset.db);
196 : :
4093 andres@anarazel.de 197 [ # # ]: 0 : if (pid)
198 : 0 : snprintf(buf, sizeof(buf), "%d", pid);
199 : : }
200 : 0 : break;
201 : : /* pipeline status */
572 michael@paquier.xyz 202 : 0 : case 'P':
234 203 [ # # ]: 0 : if (pset.db)
204 : : {
12 peter@eisentraut.org 205 :UNC 0 : PGpipelineStatus plstatus = PQpipelineStatus(pset.db);
206 : :
207 [ # # ]: 0 : if (plstatus == PQ_PIPELINE_ON)
572 michael@paquier.xyz 208 :UBC 0 : strlcpy(buf, "on", sizeof(buf));
12 peter@eisentraut.org 209 [ # # ]:UNC 0 : else if (plstatus == PQ_PIPELINE_ABORTED)
572 michael@paquier.xyz 210 :UBC 0 : strlcpy(buf, "abort", sizeof(buf));
211 : : else
212 : 0 : strlcpy(buf, "off", sizeof(buf));
213 : : }
234 214 : 0 : break;
9817 bruce@momjian.us 215 : 0 : case '0':
216 : : case '1':
217 : : case '2':
218 : : case '3':
219 : : case '4':
220 : : case '5':
221 : : case '6':
222 : : case '7':
2791 peter@eisentraut.org 223 : 0 : *buf = (char) strtol(p, unconstify(char **, &p), 8);
7783 bruce@momjian.us 224 : 0 : --p;
8485 tgl@sss.pgh.pa.us 225 : 0 : break;
9817 bruce@momjian.us 226 :CBC 70 : case 'R':
12 peter@eisentraut.org 227 [ + + - - :GNC 70 : switch (prompt_status)
- - + - ]
228 : : {
9817 bruce@momjian.us 229 :CBC 68 : case PROMPT_READY:
3461 tgl@sss.pgh.pa.us 230 [ + - - + ]: 68 : if (cstack != NULL && !conditional_active(cstack))
3461 tgl@sss.pgh.pa.us 231 :UBC 0 : buf[0] = '@';
3461 tgl@sss.pgh.pa.us 232 [ - + ]:CBC 68 : else if (!pset.db)
9817 bruce@momjian.us 233 :UBC 0 : buf[0] = '!';
7327 tgl@sss.pgh.pa.us 234 [ + - ]:CBC 68 : else if (!pset.singleline)
9817 bruce@momjian.us 235 : 68 : buf[0] = '=';
236 : : else
9817 bruce@momjian.us 237 :UBC 0 : buf[0] = '^';
9817 bruce@momjian.us 238 :CBC 68 : break;
239 : 1 : case PROMPT_CONTINUE:
240 : 1 : buf[0] = '-';
241 : 1 : break;
9817 bruce@momjian.us 242 :UBC 0 : case PROMPT_SINGLEQUOTE:
243 : 0 : buf[0] = '\'';
244 : 0 : break;
245 : 0 : case PROMPT_DOUBLEQUOTE:
246 : 0 : buf[0] = '"';
247 : 0 : break;
8244 tgl@sss.pgh.pa.us 248 : 0 : case PROMPT_DOLLARQUOTE:
249 : 0 : buf[0] = '$';
250 : 0 : break;
9817 bruce@momjian.us 251 : 0 : case PROMPT_COMMENT:
252 : 0 : buf[0] = '*';
253 : 0 : break;
9657 bruce@momjian.us 254 :CBC 1 : case PROMPT_PAREN:
255 : 1 : buf[0] = '(';
256 : 1 : break;
9817 bruce@momjian.us 257 :UBC 0 : default:
258 : 0 : buf[0] = '\0';
259 : 0 : break;
260 : : }
8485 tgl@sss.pgh.pa.us 261 :CBC 70 : break;
229 fujii@postgresql.org 262 :UBC 0 : case 'i':
263 [ # # ]: 0 : if (pset.db)
264 : : {
265 : 0 : const char *hs = PQparameterStatus(pset.db, "in_hot_standby");
266 : :
267 [ # # ]: 0 : if (hs)
268 : : {
269 [ # # ]: 0 : if (strcmp(hs, "on") == 0)
270 : 0 : strlcpy(buf, "standby", sizeof(buf));
271 : : else
272 : 0 : strlcpy(buf, "primary", sizeof(buf));
273 : : }
274 : : /* Use ? for versions that don't report in_hot_standby */
275 : : else
276 : 0 : buf[0] = '?';
277 : : }
278 : 0 : break;
8387 peter_e@gmx.net 279 :CBC 70 : case 'x':
8485 tgl@sss.pgh.pa.us 280 [ - + ]: 70 : if (!pset.db)
8485 tgl@sss.pgh.pa.us 281 :UBC 0 : buf[0] = '?';
282 : : else
8448 bruce@momjian.us 283 [ + - - - ]:CBC 70 : switch (PQtransactionStatus(pset.db))
284 : : {
285 : 70 : case PQTRANS_IDLE:
286 : 70 : buf[0] = '\0';
287 : 70 : break;
8448 bruce@momjian.us 288 :UBC 0 : case PQTRANS_ACTIVE:
289 : : case PQTRANS_INTRANS:
290 : 0 : buf[0] = '*';
291 : 0 : break;
292 : 0 : case PQTRANS_INERROR:
293 : 0 : buf[0] = '!';
294 : 0 : break;
295 : 0 : default:
296 : 0 : buf[0] = '?';
297 : 0 : break;
298 : : }
8485 tgl@sss.pgh.pa.us 299 :CBC 70 : break;
300 : :
4401 andres@anarazel.de 301 :UBC 0 : case 'l':
302 : 0 : snprintf(buf, sizeof(buf), UINT64_FORMAT, pset.stmt_lineno);
303 : 0 : break;
304 : :
9817 bruce@momjian.us 305 : 0 : case '?':
306 : : /* not here yet */
307 : 0 : break;
308 : :
9817 bruce@momjian.us 309 :CBC 70 : case '#':
8485 tgl@sss.pgh.pa.us 310 [ + - ]: 70 : if (is_superuser())
311 : 70 : buf[0] = '#';
312 : : else
8485 tgl@sss.pgh.pa.us 313 :UBC 0 : buf[0] = '>';
8485 tgl@sss.pgh.pa.us 314 :CBC 70 : break;
315 : :
316 : : /* execute command */
9817 bruce@momjian.us 317 :UBC 0 : case '`':
318 : : {
2482 alvherre@alvh.no-ip. 319 : 0 : int cmdend = strcspn(p + 1, "`");
320 : 0 : char *file = pnstrdup(p + 1, cmdend);
321 : : FILE *fd;
322 : :
1483 tgl@sss.pgh.pa.us 323 : 0 : fflush(NULL);
324 : 0 : fd = popen(file, "r");
9817 bruce@momjian.us 325 [ # # ]: 0 : if (fd)
326 : : {
5987 tgl@sss.pgh.pa.us 327 [ # # ]: 0 : if (fgets(buf, sizeof(buf), fd) == NULL)
328 : 0 : buf[0] = '\0';
9817 bruce@momjian.us 329 : 0 : pclose(fd);
330 : : }
331 : :
332 : : /* strip trailing newline and carriage return */
2599 michael@paquier.xyz 333 : 0 : (void) pg_strip_crlf(buf);
334 : :
81 peter@eisentraut.org 335 :UNC 0 : pfree(file);
9817 bruce@momjian.us 336 :UBC 0 : p += cmdend + 1;
337 : 0 : break;
338 : : }
339 : :
340 : : /* interpolate variable */
9722 peter_e@gmx.net 341 : 0 : case ':':
342 : : {
2482 alvherre@alvh.no-ip. 343 : 0 : int nameend = strcspn(p + 1, ":");
344 : 0 : char *name = pnstrdup(p + 1, nameend);
345 : : const char *val;
346 : :
9746 peter_e@gmx.net 347 : 0 : val = GetVariable(pset.vars, name);
9817 bruce@momjian.us 348 [ # # ]: 0 : if (val)
7164 peter_e@gmx.net 349 : 0 : strlcpy(buf, val, sizeof(buf));
81 peter@eisentraut.org 350 :UNC 0 : pfree(name);
9817 bruce@momjian.us 351 :UBC 0 : p += nameend + 1;
352 : 0 : break;
353 : : }
354 : :
8057 355 : 0 : case '[':
356 : : case ']':
357 : : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
358 : :
359 : : /*
360 : : * readline >=4.0 undocumented feature: non-printing
361 : : * characters in prompt strings must be marked as such, in
362 : : * order to properly display the line during editing.
363 : : */
7565 tgl@sss.pgh.pa.us 364 [ # # ]: 0 : buf[0] = (*p == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
365 : 0 : buf[1] = '\0';
366 : : #endif /* USE_READLINE */
8057 bruce@momjian.us 367 : 0 : break;
368 : :
9817 369 : 0 : default:
370 : 0 : buf[0] = *p;
371 : 0 : buf[1] = '\0';
8279 tgl@sss.pgh.pa.us 372 : 0 : break;
373 : : }
9817 bruce@momjian.us 374 :CBC 280 : esc = false;
375 : : }
376 [ + + ]: 350 : else if (*p == '%')
377 : 280 : esc = true;
378 : : else
379 : : {
380 : 70 : buf[0] = *p;
381 : 70 : buf[1] = '\0';
382 : 70 : esc = false;
383 : : }
384 : :
385 [ + + ]: 630 : if (!esc)
7164 peter_e@gmx.net 386 : 350 : strlcat(destination, buf, sizeof(destination));
387 : : }
388 : :
389 : : /* Compute the visible width of PROMPT1, for PROMPT2's %w */
2497 tmunro@postgresql.or 390 [ + + ]: 70 : if (prompt_string == pset.prompt1)
391 : : {
12 peter@eisentraut.org 392 :GNC 68 : char *d = destination;
393 : 68 : char *end = d + strlen(d);
2497 tmunro@postgresql.or 394 :CBC 68 : bool visible = true;
395 : :
396 : 68 : last_prompt1_width = 0;
12 peter@eisentraut.org 397 [ + + ]:GNC 816 : while (*d)
398 : : {
399 : : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
400 [ - + ]: 748 : if (*d == RL_PROMPT_START_IGNORE)
401 : : {
2497 tmunro@postgresql.or 402 :UBC 0 : visible = false;
12 peter@eisentraut.org 403 :UNC 0 : ++d;
404 : : }
12 peter@eisentraut.org 405 [ - + ]:GNC 748 : else if (*d == RL_PROMPT_END_IGNORE)
406 : : {
2497 tmunro@postgresql.or 407 :UBC 0 : visible = true;
12 peter@eisentraut.org 408 :UNC 0 : ++d;
409 : : }
410 : : else
411 : : #endif
412 : : {
413 : : int chlen,
414 : : chwidth;
415 : :
12 peter@eisentraut.org 416 :GNC 748 : chlen = PQmblen(d, pset.encoding);
417 [ - + ]: 748 : if (d + chlen > end)
2497 tmunro@postgresql.or 418 :UBC 0 : break; /* Invalid string */
419 : :
2497 tmunro@postgresql.or 420 [ + - ]:CBC 748 : if (visible)
421 : : {
12 peter@eisentraut.org 422 :GNC 748 : chwidth = PQdsplen(d, pset.encoding);
423 : :
424 [ - + ]: 748 : if (*d == '\n')
2414 tmunro@postgresql.or 425 :UBC 0 : last_prompt1_width = 0;
2414 tmunro@postgresql.or 426 [ + - ]:CBC 748 : else if (chwidth > 0)
2497 427 : 748 : last_prompt1_width += chwidth;
428 : : }
429 : :
12 peter@eisentraut.org 430 :GNC 748 : d += chlen;
431 : : }
432 : : }
433 : : }
434 : :
9817 bruce@momjian.us 435 :CBC 70 : return destination;
436 : : }
|