Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * psql - the PostgreSQL interactive terminal
3 : : *
4 : : * Copyright (c) 2000-2025, 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 - service
38 : : * %/ - current database
39 : : * %~ - like %/ but "~" when database name equals user name
40 : : * %w - whitespace of the same width as the most recent output of PROMPT1
41 : : * %# - "#" if superuser, ">" otherwise
42 : : * %R - in prompt1 normally =, or ^ if single line mode,
43 : : * or a ! if session is not connected to a database;
44 : : * in prompt2 -, *, ', or ";
45 : : * in prompt3 nothing
46 : : * %x - transaction status: empty, *, !, ? (unknown or no connection)
47 : : * %l - The line number inside the current statement, starting from 1.
48 : : * %? - the error code of the last query (not yet implemented)
49 : : * %% - a percent sign
50 : : *
51 : : * %[0-9] - the character with the given decimal code
52 : : * %0[0-7] - the character with the given octal code
53 : : * %0x[0-9A-Fa-f] - the character with the given hexadecimal code
54 : : *
55 : : * %`command` - The result of executing command in /bin/sh with trailing
56 : : * newline stripped.
57 : : * %:name: - The value of the psql variable 'name'
58 : : * (those will not be rescanned for more escape sequences!)
59 : : *
60 : : * %[ ... %] - tell readline that the contained text is invisible
61 : : *
62 : : * If the application-wide prompts become NULL somehow, the returned string
63 : : * will be empty (not NULL!).
64 : : *--------------------------
65 : : */
66 : :
67 : : char *
3082 tgl@sss.pgh.pa.us 68 :CBC 58 : get_prompt(promptStatus_t status, ConditionalStack cstack)
69 : : {
70 : : #define MAX_PROMPT_SIZE 256
71 : : static char destination[MAX_PROMPT_SIZE + 1];
72 : : char buf[MAX_PROMPT_SIZE + 1];
9438 bruce@momjian.us 73 : 58 : bool esc = false;
74 : : const char *p;
8206 75 : 58 : const char *prompt_string = "? ";
76 : : static size_t last_prompt1_width = 0;
77 : :
78 [ + + - - ]: 58 : switch (status)
79 : : {
80 : 56 : case PROMPT_READY:
6948 tgl@sss.pgh.pa.us 81 : 56 : prompt_string = pset.prompt1;
8206 bruce@momjian.us 82 : 56 : break;
83 : :
84 : 2 : case PROMPT_CONTINUE:
85 : : case PROMPT_SINGLEQUOTE:
86 : : case PROMPT_DOUBLEQUOTE:
87 : : case PROMPT_DOLLARQUOTE:
88 : : case PROMPT_COMMENT:
89 : : case PROMPT_PAREN:
6948 tgl@sss.pgh.pa.us 90 : 2 : prompt_string = pset.prompt2;
8206 bruce@momjian.us 91 : 2 : break;
92 : :
8206 bruce@momjian.us 93 :UBC 0 : case PROMPT_COPY:
6948 tgl@sss.pgh.pa.us 94 : 0 : prompt_string = pset.prompt3;
8206 bruce@momjian.us 95 : 0 : break;
96 : : }
97 : :
9438 bruce@momjian.us 98 :CBC 58 : destination[0] = '\0';
99 : :
100 : 58 : for (p = prompt_string;
6785 peter_e@gmx.net 101 [ + + + - ]: 580 : *p && strlen(destination) < sizeof(destination) - 1;
9438 bruce@momjian.us 102 : 522 : p++)
103 : : {
6785 peter_e@gmx.net 104 : 522 : memset(buf, 0, sizeof(buf));
9438 bruce@momjian.us 105 [ + + ]: 522 : if (esc)
106 : : {
107 [ + - - - : 232 : switch (*p)
- - - - -
- + + - -
+ - - -
- ]
108 : : {
109 : : /* Current database */
110 : 58 : case '/':
9367 peter_e@gmx.net 111 [ + - ]: 58 : if (pset.db)
6785 112 : 58 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
9438 bruce@momjian.us 113 : 58 : break;
9438 bruce@momjian.us 114 :UBC 0 : case '~':
8069 115 [ # # ]: 0 : if (pset.db)
116 : : {
117 : : const char *var;
118 : :
119 [ # # # # ]: 0 : if (strcmp(PQdb(pset.db), PQuser(pset.db)) == 0 ||
120 [ # # ]: 0 : ((var = getenv("PGDATABASE")) && strcmp(var, PQdb(pset.db)) == 0))
6785 peter_e@gmx.net 121 : 0 : strlcpy(buf, "~", sizeof(buf));
122 : : else
123 : 0 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
124 : : }
8069 bruce@momjian.us 125 : 0 : break;
126 : :
127 : : /* Whitespace of the same width as the last PROMPT1 */
2118 tmunro@postgresql.or 128 : 0 : case 'w':
129 [ # # ]: 0 : if (pset.db)
130 : 0 : memset(buf, ' ',
131 : 0 : Min(last_prompt1_width, sizeof(buf) - 1));
132 : 0 : break;
133 : :
134 : : /* DB server hostname (long/short) */
9438 bruce@momjian.us 135 : 0 : case 'M':
136 : : case 'm':
9367 peter_e@gmx.net 137 [ # # ]: 0 : if (pset.db)
138 : : {
8717 bruce@momjian.us 139 : 0 : const char *host = PQhost(pset.db);
140 : :
141 : : /* INET socket */
1746 peter@eisentraut.org 142 [ # # # # : 0 : if (host && host[0] && !is_unixsock_path(host))
# # ]
143 : : {
6785 peter_e@gmx.net 144 : 0 : strlcpy(buf, host, sizeof(buf));
9438 bruce@momjian.us 145 [ # # ]: 0 : if (*p == 'm')
146 : 0 : buf[strcspn(buf, ".")] = '\0';
147 : : }
148 : : /* UNIX socket */
149 : : else
150 : : {
8889 peter_e@gmx.net 151 [ # # ]: 0 : if (!host
8717 bruce@momjian.us 152 [ # # ]: 0 : || strcmp(host, DEFAULT_PGSOCKET_DIR) == 0
8889 peter_e@gmx.net 153 [ # # ]: 0 : || *p == 'm')
6785 154 : 0 : strlcpy(buf, "[local]", sizeof(buf));
155 : : else
156 : 0 : snprintf(buf, sizeof(buf), "[local:%s]", host);
157 : : }
158 : : }
9438 bruce@momjian.us 159 : 0 : break;
160 : : /* DB server port number */
161 : 0 : case '>':
9367 peter_e@gmx.net 162 [ # # # # ]: 0 : if (pset.db && PQport(pset.db))
6785 163 : 0 : strlcpy(buf, PQport(pset.db), sizeof(buf));
9438 bruce@momjian.us 164 : 0 : break;
165 : : /* DB server user name */
166 : 0 : case 'n':
9367 peter_e@gmx.net 167 [ # # ]: 0 : if (pset.db)
6785 168 : 0 : strlcpy(buf, session_username(), sizeof(buf));
9438 bruce@momjian.us 169 : 0 : break;
170 : : /* service name */
262 michael@paquier.xyz 171 : 0 : case 's':
172 : : {
59 173 : 0 : const char *service_name = GetVariable(pset.vars, "SERVICE");
174 : :
175 [ # # ]: 0 : if (service_name)
176 : 0 : strlcpy(buf, service_name, sizeof(buf));
177 : : }
262 178 : 0 : break;
179 : : /* backend pid */
3714 andres@anarazel.de 180 : 0 : case 'p':
181 [ # # ]: 0 : if (pset.db)
182 : : {
3376 rhaas@postgresql.org 183 : 0 : int pid = PQbackendPID(pset.db);
184 : :
3714 andres@anarazel.de 185 [ # # ]: 0 : if (pid)
186 : 0 : snprintf(buf, sizeof(buf), "%d", pid);
187 : : }
188 : 0 : break;
189 : : /* pipeline status */
193 michael@paquier.xyz 190 : 0 : case 'P':
191 : : {
192 : 0 : PGpipelineStatus status = PQpipelineStatus(pset.db);
193 : :
194 [ # # ]: 0 : if (status == PQ_PIPELINE_ON)
195 : 0 : strlcpy(buf, "on", sizeof(buf));
196 [ # # ]: 0 : else if (status == PQ_PIPELINE_ABORTED)
197 : 0 : strlcpy(buf, "abort", sizeof(buf));
198 : : else
199 : 0 : strlcpy(buf, "off", sizeof(buf));
200 : 0 : break;
201 : : }
202 : :
9438 bruce@momjian.us 203 : 0 : case '0':
204 : : case '1':
205 : : case '2':
206 : : case '3':
207 : : case '4':
208 : : case '5':
209 : : case '6':
210 : : case '7':
2412 peter@eisentraut.org 211 : 0 : *buf = (char) strtol(p, unconstify(char **, &p), 8);
7404 bruce@momjian.us 212 : 0 : --p;
8106 tgl@sss.pgh.pa.us 213 : 0 : break;
9438 bruce@momjian.us 214 :CBC 58 : case 'R':
215 [ + + - - : 58 : switch (status)
- - + - ]
216 : : {
217 : 56 : case PROMPT_READY:
3082 tgl@sss.pgh.pa.us 218 [ + - - + ]: 56 : if (cstack != NULL && !conditional_active(cstack))
3082 tgl@sss.pgh.pa.us 219 :UBC 0 : buf[0] = '@';
3082 tgl@sss.pgh.pa.us 220 [ - + ]:CBC 56 : else if (!pset.db)
9438 bruce@momjian.us 221 :UBC 0 : buf[0] = '!';
6948 tgl@sss.pgh.pa.us 222 [ + - ]:CBC 56 : else if (!pset.singleline)
9438 bruce@momjian.us 223 : 56 : buf[0] = '=';
224 : : else
9438 bruce@momjian.us 225 :UBC 0 : buf[0] = '^';
9438 bruce@momjian.us 226 :CBC 56 : break;
227 : 1 : case PROMPT_CONTINUE:
228 : 1 : buf[0] = '-';
229 : 1 : break;
9438 bruce@momjian.us 230 :UBC 0 : case PROMPT_SINGLEQUOTE:
231 : 0 : buf[0] = '\'';
232 : 0 : break;
233 : 0 : case PROMPT_DOUBLEQUOTE:
234 : 0 : buf[0] = '"';
235 : 0 : break;
7865 tgl@sss.pgh.pa.us 236 : 0 : case PROMPT_DOLLARQUOTE:
237 : 0 : buf[0] = '$';
238 : 0 : break;
9438 bruce@momjian.us 239 : 0 : case PROMPT_COMMENT:
240 : 0 : buf[0] = '*';
241 : 0 : break;
9278 bruce@momjian.us 242 :CBC 1 : case PROMPT_PAREN:
243 : 1 : buf[0] = '(';
244 : 1 : break;
9438 bruce@momjian.us 245 :UBC 0 : default:
246 : 0 : buf[0] = '\0';
247 : 0 : break;
248 : : }
8106 tgl@sss.pgh.pa.us 249 :CBC 58 : break;
250 : :
8008 peter_e@gmx.net 251 : 58 : case 'x':
8106 tgl@sss.pgh.pa.us 252 [ - + ]: 58 : if (!pset.db)
8106 tgl@sss.pgh.pa.us 253 :UBC 0 : buf[0] = '?';
254 : : else
8069 bruce@momjian.us 255 [ + - - - ]:CBC 58 : switch (PQtransactionStatus(pset.db))
256 : : {
257 : 58 : case PQTRANS_IDLE:
258 : 58 : buf[0] = '\0';
259 : 58 : break;
8069 bruce@momjian.us 260 :UBC 0 : case PQTRANS_ACTIVE:
261 : : case PQTRANS_INTRANS:
262 : 0 : buf[0] = '*';
263 : 0 : break;
264 : 0 : case PQTRANS_INERROR:
265 : 0 : buf[0] = '!';
266 : 0 : break;
267 : 0 : default:
268 : 0 : buf[0] = '?';
269 : 0 : break;
270 : : }
8106 tgl@sss.pgh.pa.us 271 :CBC 58 : break;
272 : :
4022 andres@anarazel.de 273 :UBC 0 : case 'l':
274 : 0 : snprintf(buf, sizeof(buf), UINT64_FORMAT, pset.stmt_lineno);
275 : 0 : break;
276 : :
9438 bruce@momjian.us 277 : 0 : case '?':
278 : : /* not here yet */
279 : 0 : break;
280 : :
9438 bruce@momjian.us 281 :CBC 58 : case '#':
8106 tgl@sss.pgh.pa.us 282 [ + - ]: 58 : if (is_superuser())
283 : 58 : buf[0] = '#';
284 : : else
8106 tgl@sss.pgh.pa.us 285 :UBC 0 : buf[0] = '>';
8106 tgl@sss.pgh.pa.us 286 :CBC 58 : break;
287 : :
288 : : /* execute command */
9438 bruce@momjian.us 289 :UBC 0 : case '`':
290 : : {
2103 alvherre@alvh.no-ip. 291 : 0 : int cmdend = strcspn(p + 1, "`");
292 : 0 : char *file = pnstrdup(p + 1, cmdend);
293 : : FILE *fd;
294 : :
1104 tgl@sss.pgh.pa.us 295 : 0 : fflush(NULL);
296 : 0 : fd = popen(file, "r");
9438 bruce@momjian.us 297 [ # # ]: 0 : if (fd)
298 : : {
5608 tgl@sss.pgh.pa.us 299 [ # # ]: 0 : if (fgets(buf, sizeof(buf), fd) == NULL)
300 : 0 : buf[0] = '\0';
9438 bruce@momjian.us 301 : 0 : pclose(fd);
302 : : }
303 : :
304 : : /* strip trailing newline and carriage return */
2220 michael@paquier.xyz 305 : 0 : (void) pg_strip_crlf(buf);
306 : :
9438 bruce@momjian.us 307 : 0 : free(file);
308 : 0 : p += cmdend + 1;
309 : 0 : break;
310 : : }
311 : :
312 : : /* interpolate variable */
9343 peter_e@gmx.net 313 : 0 : case ':':
314 : : {
2103 alvherre@alvh.no-ip. 315 : 0 : int nameend = strcspn(p + 1, ":");
316 : 0 : char *name = pnstrdup(p + 1, nameend);
317 : : const char *val;
318 : :
9367 peter_e@gmx.net 319 : 0 : val = GetVariable(pset.vars, name);
9438 bruce@momjian.us 320 [ # # ]: 0 : if (val)
6785 peter_e@gmx.net 321 : 0 : strlcpy(buf, val, sizeof(buf));
9438 bruce@momjian.us 322 : 0 : free(name);
323 : 0 : p += nameend + 1;
324 : 0 : break;
325 : : }
326 : :
7678 327 : 0 : case '[':
328 : : case ']':
329 : : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
330 : :
331 : : /*
332 : : * readline >=4.0 undocumented feature: non-printing
333 : : * characters in prompt strings must be marked as such, in
334 : : * order to properly display the line during editing.
335 : : */
7186 tgl@sss.pgh.pa.us 336 [ # # ]: 0 : buf[0] = (*p == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
337 : 0 : buf[1] = '\0';
338 : : #endif /* USE_READLINE */
7678 bruce@momjian.us 339 : 0 : break;
340 : :
9438 341 : 0 : default:
342 : 0 : buf[0] = *p;
343 : 0 : buf[1] = '\0';
7900 tgl@sss.pgh.pa.us 344 : 0 : break;
345 : : }
9438 bruce@momjian.us 346 :CBC 232 : esc = false;
347 : : }
348 [ + + ]: 290 : else if (*p == '%')
349 : 232 : esc = true;
350 : : else
351 : : {
352 : 58 : buf[0] = *p;
353 : 58 : buf[1] = '\0';
354 : 58 : esc = false;
355 : : }
356 : :
357 [ + + ]: 522 : if (!esc)
6785 peter_e@gmx.net 358 : 290 : strlcat(destination, buf, sizeof(destination));
359 : : }
360 : :
361 : : /* Compute the visible width of PROMPT1, for PROMPT2's %w */
2118 tmunro@postgresql.or 362 [ + + ]: 58 : if (prompt_string == pset.prompt1)
363 : : {
364 : 56 : char *p = destination;
365 : 56 : char *end = p + strlen(p);
366 : 56 : bool visible = true;
367 : :
368 : 56 : last_prompt1_width = 0;
369 [ + + ]: 672 : while (*p)
370 : : {
371 : : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
372 [ - + ]: 616 : if (*p == RL_PROMPT_START_IGNORE)
373 : : {
2118 tmunro@postgresql.or 374 :UBC 0 : visible = false;
375 : 0 : ++p;
376 : : }
2118 tmunro@postgresql.or 377 [ - + ]:CBC 616 : else if (*p == RL_PROMPT_END_IGNORE)
378 : : {
2118 tmunro@postgresql.or 379 :UBC 0 : visible = true;
380 : 0 : ++p;
381 : : }
382 : : else
383 : : #endif
384 : : {
385 : : int chlen,
386 : : chwidth;
387 : :
2118 tmunro@postgresql.or 388 :CBC 616 : chlen = PQmblen(p, pset.encoding);
389 [ - + ]: 616 : if (p + chlen > end)
2118 tmunro@postgresql.or 390 :UBC 0 : break; /* Invalid string */
391 : :
2118 tmunro@postgresql.or 392 [ + - ]:CBC 616 : if (visible)
393 : : {
394 : 616 : chwidth = PQdsplen(p, pset.encoding);
395 : :
2035 396 [ - + ]: 616 : if (*p == '\n')
2035 tmunro@postgresql.or 397 :UBC 0 : last_prompt1_width = 0;
2035 tmunro@postgresql.or 398 [ + - ]:CBC 616 : else if (chwidth > 0)
2118 399 : 616 : last_prompt1_width += chwidth;
400 : : }
401 : :
402 : 616 : p += chlen;
403 : : }
404 : : }
405 : : }
406 : :
9438 bruce@momjian.us 407 : 58 : return destination;
408 : : }
|