]> granicus.if.org Git - postgresql/blob - src/bin/psql/common.c
another round of user interface cleanups
[postgresql] / src / bin / psql / common.c
1 /*
2  * psql - the PostgreSQL interactive terminal
3  *
4  * Copyright 2000 by PostgreSQL Global Development Team
5  *
6  * $Header: /cvsroot/pgsql/src/bin/psql/common.c,v 1.10 2000/01/19 20:08:33 petere Exp $
7  */
8 #include <c.h>
9 #include "common.h"
10
11 #include <errno.h>
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <stdarg.h>
16 #ifdef HAVE_TERMIOS_H
17 #include <termios.h>
18 #endif
19 #ifndef HAVE_STRDUP
20 #include <strdup.h>
21 #endif
22 #include <signal.h>
23 #ifndef WIN32
24 #include <unistd.h>                             /* for write() */
25 #else
26 #include <io.h>                 /* for _write() */
27 #endif
28
29 #include <libpq-fe.h>
30 #include <postgres_ext.h>
31 #include <pqsignal.h>
32 #include <version.h>
33
34 #include "settings.h"
35 #include "variables.h"
36 #include "copy.h"
37 #include "prompt.h"
38 #include "print.h"
39
40 #ifdef WIN32
41 #define popen(x,y) _popen(x,y)
42 #define pclose(x) _pclose(x)
43 #define write(a,b,c) _write(a,b,c)
44 #endif
45
46
47
48 /* xstrdup()
49  *
50  * "Safe" wrapper around strdup()
51  * (Using this also avoids writing #ifdef HAVE_STRDUP in every file :)
52  */
53 char *
54 xstrdup(const char *string)
55 {
56         char       *tmp;
57
58         if (!string)
59         {
60                 fprintf(stderr, "%s: xstrdup: cannot duplicate null pointer\n", pset.progname);
61                 exit(EXIT_FAILURE);
62         }
63         tmp = strdup(string);
64         if (!tmp)
65         {
66         psql_error("out of memory\n");
67                 exit(EXIT_FAILURE);
68         }
69         return tmp;
70 }
71
72
73
74 /*
75  * setQFout
76  * -- handler for -o command line option and \o command
77  *
78  * Tries to open file fname (or pipe if fname starts with '|')
79  * and stores the file handle in pset)
80  * Upon failure, sets stdout and returns false.
81  */
82 bool
83 setQFout(const char *fname)
84 {
85         bool            status = true;
86
87         /* Close old file/pipe */
88         if (pset.queryFout && pset.queryFout != stdout && pset.queryFout != stderr)
89         {
90                 if (pset.queryFoutPipe)
91                         pclose(pset.queryFout);
92                 else
93                         fclose(pset.queryFout);
94         }
95
96         /* If no filename, set stdout */
97         if (!fname || fname[0] == '\0')
98         {
99                 pset.queryFout = stdout;
100                 pset.queryFoutPipe = false;
101         }
102         else if (*fname == '|')
103         {
104                 pset.queryFout = popen(fname + 1, "w");
105                 pset.queryFoutPipe = true;
106         }
107         else
108         {
109                 pset.queryFout = fopen(fname, "w");
110                 pset.queryFoutPipe = false;
111         }
112
113         if (!(pset.queryFout))
114         {
115                 psql_error("%s: %s\n", fname, strerror(errno));
116                 pset.queryFout = stdout;
117                 pset.queryFoutPipe = false;
118                 status = false;
119         }
120
121         /* Direct signals */
122 #ifndef WIN32
123         if (pset.queryFoutPipe)
124                 pqsignal(SIGPIPE, SIG_IGN);
125         else
126                 pqsignal(SIGPIPE, SIG_DFL);
127 #endif
128
129         return status;
130 }
131
132
133
134 /*
135  * Error reporting for scripts. Errors should look like
136  *   filename:lineno: message
137  *
138  */
139 void
140 psql_error(const char *fmt, ...)
141 {
142     va_list ap;
143
144     fflush(stdout);
145     if (pset.queryFout!=stdout)
146         fflush(pset.queryFout);
147
148     if (pset.inputfile)
149         fprintf(stderr, "%s:%s:%u: ", pset.progname, pset.inputfile, pset.lineno);
150     va_start(ap, fmt);
151     vfprintf(stderr, fmt, ap);
152     va_end(ap);
153 }
154
155 /* for backend NOTICES */
156
157 void
158 NoticeProcessor(void * arg, const char * message)
159 {
160     (void)arg; /* not used */
161     psql_error("%s", message);
162 }
163
164
165
166 /*
167  * simple_prompt
168  *
169  * Generalized function especially intended for reading in usernames and
170  * password interactively. Reads from stdin.
171  *
172  * prompt:              The prompt to print
173  * maxlen:              How many characters to accept
174  * echo:                Set to false if you want to hide what is entered (for passwords)
175  *
176  * Returns a malloc()'ed string with the input (w/o trailing newline).
177  */
178 char *
179 simple_prompt(const char *prompt, int maxlen, bool echo)
180 {
181         int                     length;
182         char       *destination;
183
184 #ifdef HAVE_TERMIOS_H
185         struct termios t_orig,
186                                 t;
187
188 #endif
189
190         destination = (char *) malloc(maxlen + 2);
191         if (!destination)
192                 return NULL;
193         if (prompt)
194                 fputs(prompt, stdout);
195
196 #ifdef HAVE_TERMIOS_H
197         if (!echo)
198         {
199                 tcgetattr(0, &t);
200                 t_orig = t;
201                 t.c_lflag &= ~ECHO;
202                 tcsetattr(0, TCSADRAIN, &t);
203         }
204 #endif
205
206         fgets(destination, maxlen, stdin);
207
208 #ifdef HAVE_TERMIOS_H
209         if (!echo)
210         {
211                 tcsetattr(0, TCSADRAIN, &t_orig);
212                 puts("");
213         }
214 #endif
215
216         length = strlen(destination);
217         if (length > 0 && destination[length - 1] != '\n')
218         {
219                 /* eat rest of the line */
220                 char            buf[512];
221
222                 do
223                 {
224                         fgets(buf, 512, stdin);
225                 } while (buf[strlen(buf) - 1] != '\n');
226         }
227
228         if (length > 0 && destination[length - 1] == '\n')
229                 /* remove trailing newline */
230                 destination[length - 1] = '\0';
231
232         return destination;
233 }
234
235
236
237 /*
238  * Code to support query cancellation
239  *
240  * Before we start a query, we enable a SIGINT signal catcher that sends a
241  * cancel request to the backend. Note that sending the cancel directly from
242  * the signal handler is safe because PQrequestCancel() is written to make it
243  * so. We have to be very careful what else we do in the signal handler. This
244  * includes using write() for output.
245  */
246
247 static PGconn *cancelConn;
248
249 #define write_stderr(String) write(fileno(stderr), String, strlen(String))
250
251 static void
252 handle_sigint(SIGNAL_ARGS)
253 {
254         if (cancelConn == NULL)
255                 return;
256         /* Try to send cancel request */
257         if (PQrequestCancel(cancelConn))
258                 write_stderr("\nCancel request sent\n");
259         else
260         {
261                 write_stderr("\nCould not send cancel request: ");
262                 write_stderr(PQerrorMessage(cancelConn));
263         }
264 }
265
266
267
268 /*
269  * PSQLexec
270  *
271  * This is the way to send "backdoor" queries (those not directly entered
272  * by the user). It is subject to -E but not -e.
273  */
274 PGresult   *
275 PSQLexec(const char *query)
276 {
277         PGresult   *res;
278         const char *var;
279
280         if (!pset.db)
281         {
282         psql_error("You are currently not connected to a database.\n");
283                 return NULL;
284         }
285
286         var = GetVariable(pset.vars, "ECHO_HIDDEN");
287         if (var)
288         {
289                 printf("********* QUERY *********\n%s\n*************************\n\n", query);
290                 fflush(stdout);
291         }
292
293         if (var && strcmp(var, "noexec") == 0)
294                 return NULL;
295
296         cancelConn = pset.db;
297 #ifndef WIN32
298         pqsignal(SIGINT, handle_sigint);        /* control-C => cancel */
299 #endif
300
301         res = PQexec(pset.db, query);
302
303 #ifndef WIN32
304         pqsignal(SIGINT, SIG_DFL);      /* now control-C is back to normal */
305 #endif
306
307         if (PQstatus(pset.db) == CONNECTION_BAD)
308         {
309         if (!pset.cur_cmd_interactive)
310         {
311             psql_error("connection to server was lost");
312             exit(EXIT_BADCONN);
313         }
314                 fputs("The connection to the server was lost. Attempting reset: ", stderr);
315                 PQreset(pset.db);
316                 if (PQstatus(pset.db) == CONNECTION_BAD)
317                 {
318                         fputs("Failed.\n", stderr);
319                         PQfinish(pset.db);
320                         PQclear(res);
321                         pset.db = NULL;
322             SetVariable(pset.vars, "DBNAME", NULL);
323             SetVariable(pset.vars, "HOST", NULL);
324             SetVariable(pset.vars, "PORT", NULL);
325             SetVariable(pset.vars, "USER", NULL);
326                         return NULL;
327                 }
328                 else
329                         fputs("Succeeded.\n", stderr);
330         }
331
332         if (res && (PQresultStatus(res) == PGRES_COMMAND_OK ||
333                                 PQresultStatus(res) == PGRES_TUPLES_OK ||
334                                 PQresultStatus(res) == PGRES_COPY_IN ||
335                                 PQresultStatus(res) == PGRES_COPY_OUT)
336                 )
337                 return res;
338         else
339         {
340         psql_error("%s", PQerrorMessage(pset.db));
341                 PQclear(res);
342                 return NULL;
343         }
344 }
345
346
347
348 /*
349  * SendQuery: send the query string to the backend
350  * (and print out results)
351  *
352  * Note: This is the "front door" way to send a query. That is, use it to
353  * send queries actually entered by the user. These queries will be subject to
354  * single step mode.
355  * To send "back door" queries (generated by slash commands, etc.) in a
356  * controlled way, use PSQLexec().
357  *
358  * Returns true if the query executed successfully, false otherwise.
359  */
360 bool
361 SendQuery(const char *query)
362 {
363         bool            success = false;
364         PGresult   *results;
365         PGnotify   *notify;
366
367         if (!pset.db)
368         {
369         psql_error("you are currently not connected to a database.\n");
370                 return false;
371         }
372
373         if (GetVariableBool(pset.vars, "SINGLESTEP"))
374         {
375                 char            buf[3];
376
377                 printf("***(Single step mode: Verify query)*********************************************\n"
378                "%s\n"
379                "***(press return to proceed or enter x and return to cancel)********************\n",
380                query);
381                 fflush(stdout);
382                 fgets(buf, 3, stdin);
383                 if (buf[0] == 'x')
384                         return false;
385         }
386     else
387     {
388         const char * var = GetVariable(pset.vars, "ECHO");
389         if (var && strcmp(var, "brief")==0)
390             puts(query);
391     }
392
393         cancelConn = pset.db;
394 #ifndef WIN32
395         pqsignal(SIGINT, handle_sigint);
396 #endif
397
398         results = PQexec(pset.db, query);
399
400 #ifndef WIN32
401         pqsignal(SIGINT, SIG_DFL);
402 #endif
403
404         if (results == NULL)
405         {
406                 fputs(PQerrorMessage(pset.db), pset.queryFout);
407                 success = false;
408         }
409         else
410         {
411                 switch (PQresultStatus(results))
412                 {
413                         case PGRES_TUPLES_OK:
414                 /* write output to \g argument, if any */
415                                 if (pset.gfname)
416                                 {
417                                         FILE * queryFout_copy = pset.queryFout;
418                     bool queryFoutPipe_copy = pset.queryFoutPipe;
419                     pset.queryFout = NULL; /* so it doesn't get closed */
420
421                     /* open file/pipe */
422                                         if (!setQFout(pset.gfname))
423                                         {
424                                                 success = false;
425                                                 break;
426                                         }
427
428                                         printQuery(results, &pset.popt, pset.queryFout);
429
430                                         /* close file/pipe */
431                                         setQFout(NULL);
432
433                                         free(pset.gfname);
434                                         pset.gfname = NULL;
435
436                                         pset.queryFout = queryFout_copy;
437                     pset.queryFoutPipe = queryFoutPipe_copy;
438
439                                         success = true;
440                                         break;
441                                 }
442                                 else
443                                 {
444                                         success = true;
445                                         printQuery(results, &pset.popt, pset.queryFout);
446                                 }
447                                 break;
448                         case PGRES_EMPTY_QUERY:
449                                 success = true;
450                                 break;
451                         case PGRES_COMMAND_OK:
452             {
453                 char buf[10];
454
455                                 success = true;
456                 sprintf(buf, "%u", (unsigned int)PQoidValue(results));
457                 if (!QUIET())
458                     fprintf(pset.queryFout, "%s\n", PQcmdStatus(results));
459                 SetVariable(pset.vars, "LASTOID", buf);
460                                 break;
461             }
462                         case PGRES_COPY_OUT:
463                                 success = handleCopyOut(pset.db, pset.queryFout);
464                                 break;
465
466                         case PGRES_COPY_IN:
467                                 if (pset.cur_cmd_interactive && !QUIET())
468                                         puts("Enter data to be copied followed by a newline.\n"
469                                                  "End with a backslash and a period on a line by itself.");
470
471                                 success = handleCopyIn(pset.db, pset.cur_cmd_source,
472                                                                            pset.cur_cmd_interactive ? get_prompt(PROMPT_COPY) : NULL);
473                                 break;
474
475                         case PGRES_NONFATAL_ERROR:
476                         case PGRES_FATAL_ERROR:
477                         case PGRES_BAD_RESPONSE:
478                                 success = false;
479                 psql_error("%s", PQerrorMessage(pset.db));
480                                 break;
481                 }
482
483         fflush(pset.queryFout);
484
485                 if (PQstatus(pset.db) == CONNECTION_BAD)
486                 {
487             if (!pset.cur_cmd_interactive)
488             {
489                 psql_error("connection to server was lost");
490                 exit(EXIT_BADCONN);
491             }
492                         fputs("The connection to the server was lost. Attempting reset: ", stderr);
493                         PQreset(pset.db);
494                         if (PQstatus(pset.db) == CONNECTION_BAD)
495                         {
496                                 fputs("Failed.\n", stderr);
497                                 PQfinish(pset.db);
498                                 PQclear(results);
499                                 pset.db = NULL;
500                 SetVariable(pset.vars, "DBNAME", NULL);
501                 SetVariable(pset.vars, "HOST", NULL);
502                 SetVariable(pset.vars, "PORT", NULL);
503                 SetVariable(pset.vars, "USER", NULL);
504                                 return false;
505                         }
506                         else
507                                 fputs("Succeeded.\n", stderr);
508                 }
509         
510                 /* check for asynchronous notification returns */
511                 while ((notify = PQnotifies(pset.db)) != NULL)
512                 {
513                         fprintf(pset.queryFout, "Asynchronous NOTIFY '%s' from backend with pid '%d' received.\n",
514                                         notify->relname, notify->be_pid);
515                         free(notify);
516             fflush(pset.queryFout);
517                 }
518
519                 if (results)
520                         PQclear(results);
521     }
522
523         return success;
524 }