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