]> granicus.if.org Git - postgresql/blobdiff - src/bin/psql/input.c
pgindent run for 9.0
[postgresql] / src / bin / psql / input.c
index ae5ed22aa6955b50ad82aed6e7dfb460644e69bd..d28fe9c0ba4ef8f0047871477b01e8d123ae6922 100644 (file)
@@ -1,74 +1,87 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2004, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2010, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/input.c,v 1.40 2004/10/06 19:04:10 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/input.c,v 1.69 2010/02/26 02:01:18 momjian Exp $
  */
 #include "postgres_fe.h"
-#include "input.h"
 
-#include <errno.h>
+#ifndef WIN32
+#include <unistd.h>
+#endif
+#include <fcntl.h>
 
-#include "pqexpbuffer.h"
+#include "input.h"
 #include "settings.h"
 #include "tab-complete.h"
 #include "common.h"
 
+#ifndef WIN32
+#define PSQLHISTORY ".psql_history"
+#else
+#define PSQLHISTORY "psql_history"
+#endif
+
 /* Runtime options for turning off readline and history */
 /* (of course there is no runtime command for doing that :) */
 #ifdef USE_READLINE
 static bool useReadline;
 static bool useHistory;
 
-enum histcontrol
-{
-       hctl_none = 0,
-       hctl_ignorespace = 1,
-       hctl_ignoredups = 2,
-       hctl_ignoreboth = hctl_ignorespace | hctl_ignoredups
-};
+static char *psql_history;
+
+static int     history_lines_added;
+
+
+/*
+ *     Preserve newlines in saved queries by mapping '\n' to NL_IN_HISTORY
+ *
+ *     It is assumed NL_IN_HISTORY will never be entered by the user
+ *     nor appear inside a multi-byte string.  0x00 is not properly
+ *     handled by the readline routines so it can not be used
+ *     for this purpose.
+ */
+#define NL_IN_HISTORY  0x01
 #endif
 
 #ifdef HAVE_ATEXIT
 static void finishInput(void);
-
 #else
 /* designed for use with on_exit() */
 static void finishInput(int, void *);
 #endif
 
-#define PSQLHISTORY ".psql_history"
-
 
-#ifdef USE_READLINE
-static enum histcontrol
-GetHistControlConfig(void)
+/*
+ * gets_interactive()
+ *
+ * Gets a line of interactive input, using readline if desired.
+ * The result is a malloc'd string.
+ *
+ * Caller *must* have set up sigint_interrupt_jmp before calling.
+ */
+char *
+gets_interactive(const char *prompt)
 {
-       enum histcontrol HC;
-       const char *var;
-
-       var = GetVariable(pset.vars, "HISTCONTROL");
-
-       if (!var)
-               HC = hctl_none;
-       else if (strcmp(var, "ignorespace") == 0)
-               HC = hctl_ignorespace;
-       else if (strcmp(var, "ignoredups") == 0)
-               HC = hctl_ignoredups;
-       else if (strcmp(var, "ignoreboth") == 0)
-               HC = hctl_ignoreboth;
-       else
-               HC = hctl_none;
+#ifdef USE_READLINE
+       if (useReadline)
+       {
+               char       *result;
 
-       return HC;
-}
-#endif
+               /* Enable SIGINT to longjmp to sigint_interrupt_jmp */
+               sigint_interrupt_enabled = true;
 
+               /* On some platforms, readline is declared as readline(char *) */
+               result = readline((char *) prompt);
+
+               /* Disable SIGINT again */
+               sigint_interrupt_enabled = false;
+
+               return result;
+       }
+#endif
 
-static char *
-gets_basic(const char prompt[])
-{
        fputs(prompt, stdout);
        fflush(stdout);
        return gets_fromFile(stdin);
@@ -76,84 +89,183 @@ gets_basic(const char prompt[])
 
 
 /*
- * gets_interactive()
- *
- * Gets a line of interactive input, using readline of desired.
- * The result is malloc'ed.
+ * Append the line to the history buffer, making sure there is a trailing '\n'
  */
-char *
-gets_interactive(const char *prompt)
+void
+pg_append_history(const char *s, PQExpBuffer history_buf)
 {
 #ifdef USE_READLINE
-       char       *s;
+       if (useHistory && s && s[0])
+       {
+               appendPQExpBufferStr(history_buf, s);
+               if (s[strlen(s) - 1] != '\n')
+                       appendPQExpBufferChar(history_buf, '\n');
+       }
+#endif
+}
 
-       static char *prev_hist = NULL;
 
-       if (useReadline)
-               /* On some platforms, readline is declared as readline(char *) */
-               s = readline((char *) prompt);
-       else
-               s = gets_basic(prompt);
+/*
+ * Emit accumulated history entry to readline's history mechanism,
+ * then reset the buffer to empty.
+ *
+ * Note: we write nothing if history_buf is empty, so extra calls to this
+ * function don't hurt.  There must have been at least one line added by
+ * pg_append_history before we'll do anything.
+ */
+void
+pg_send_history(PQExpBuffer history_buf)
+{
+#ifdef USE_READLINE
+       static char *prev_hist = NULL;
 
-       if (useHistory && s && s[0])
-       {
-               enum histcontrol HC;
+       char       *s = history_buf->data;
+       int                     i;
 
-               HC = GetHistControlConfig();
+       /* Trim any trailing \n's (OK to scribble on history_buf) */
+       for (i = strlen(s) - 1; i >= 0 && s[i] == '\n'; i--)
+               ;
+       s[i + 1] = '\0';
 
-               if (((HC & hctl_ignorespace) && s[0] == ' ') ||
-                       ((HC & hctl_ignoredups) && prev_hist && strcmp(s, prev_hist) == 0))
+       if (useHistory && s[0])
+       {
+               if (((pset.histcontrol & hctl_ignorespace) &&
+                        s[0] == ' ') ||
+                       ((pset.histcontrol & hctl_ignoredups) &&
+                        prev_hist && strcmp(s, prev_hist) == 0))
                {
                        /* Ignore this line as far as history is concerned */
                }
                else
                {
-                       free(prev_hist);
+                       /* Save each previous line for ignoredups processing */
+                       if (prev_hist)
+                               free(prev_hist);
                        prev_hist = pg_strdup(s);
+                       /* And send it to readline */
                        add_history(s);
+                       /* Count lines added to history for use later */
+                       history_lines_added++;
                }
        }
 
-       return s;
-#else
-       return gets_basic(prompt);
+       resetPQExpBuffer(history_buf);
 #endif
 }
 
 
-
 /*
  * gets_fromFile
  *
  * Gets a line of noninteractive input from a file (which could be stdin).
+ * The result is a malloc'd string, or NULL on EOF or input error.
+ *
+ * Caller *must* have set up sigint_interrupt_jmp before calling.
+ *
+ * Note: we re-use a static PQExpBuffer for each call. This is to avoid
+ * leaking memory if interrupted by SIGINT.
  */
 char *
 gets_fromFile(FILE *source)
 {
-       PQExpBufferData buffer;
+       static PQExpBuffer buffer = NULL;
+
        char            line[1024];
 
-       initPQExpBuffer(&buffer);
+       if (buffer == NULL)                     /* first time through? */
+               buffer = createPQExpBuffer();
+       else
+               resetPQExpBuffer(buffer);
 
-       while (fgets(line, sizeof(line), source) != NULL)
+       for (;;)
        {
-               appendPQExpBufferStr(&buffer, line);
-               if (buffer.data[buffer.len - 1] == '\n')
+               char       *result;
+
+               /* Enable SIGINT to longjmp to sigint_interrupt_jmp */
+               sigint_interrupt_enabled = true;
+
+               /* Get some data */
+               result = fgets(line, sizeof(line), source);
+
+               /* Disable SIGINT again */
+               sigint_interrupt_enabled = false;
+
+               /* EOF or error? */
+               if (result == NULL)
                {
-                       buffer.data[buffer.len - 1] = '\0';
-                       return buffer.data;
+                       if (ferror(source))
+                       {
+                               psql_error("could not read from input file: %s\n",
+                                                  strerror(errno));
+                               return NULL;
+                       }
+                       break;
+               }
+
+               appendPQExpBufferStr(buffer, line);
+
+               if (PQExpBufferBroken(buffer))
+               {
+                       psql_error("out of memory\n");
+                       return NULL;
+               }
+
+               /* EOL? */
+               if (buffer->data[buffer->len - 1] == '\n')
+               {
+                       buffer->data[buffer->len - 1] = '\0';
+                       return pg_strdup(buffer->data);
                }
        }
 
-       if (buffer.len > 0)
-               return buffer.data;             /* EOF after reading some bufferload(s) */
+       if (buffer->len > 0)            /* EOF after reading some bufferload(s) */
+               return pg_strdup(buffer->data);
 
        /* EOF, so return null */
-       termPQExpBuffer(&buffer);
        return NULL;
 }
 
 
+#ifdef USE_READLINE
+/*
+ * Convert newlines to NL_IN_HISTORY for safe saving in readline history file
+ */
+static void
+encode_history(void)
+{
+       HIST_ENTRY *cur_hist;
+       char       *cur_ptr;
+
+       history_set_pos(0);
+       for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
+       {
+               /* some platforms declare HIST_ENTRY.line as const char * */
+               for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
+                       if (*cur_ptr == '\n')
+                               *cur_ptr = NL_IN_HISTORY;
+       }
+}
+
+/*
+ * Reverse the above encoding
+ */
+static void
+decode_history(void)
+{
+       HIST_ENTRY *cur_hist;
+       char       *cur_ptr;
+
+       history_set_pos(0);
+       for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
+       {
+               /* some platforms declare HIST_ENTRY.line as const char * */
+               for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
+                       if (*cur_ptr == NL_IN_HISTORY)
+                               *cur_ptr = '\n';
+       }
+}
+#endif   /* USE_READLINE */
+
 
 /*
  * Put any startup stuff related to input in here. It's good to maintain
@@ -167,24 +279,36 @@ initializeInput(int flags)
 #ifdef USE_READLINE
        if (flags & 1)
        {
+               const char *histfile;
                char            home[MAXPGPATH];
 
                useReadline = true;
                initialize_readline();
 
                useHistory = true;
-               if (GetVariable(pset.vars, "HISTSIZE") == NULL)
-                       SetVariable(pset.vars, "HISTSIZE", "500");
                using_history();
-               if (get_home_path(home))
+               history_lines_added = 0;
+
+               histfile = GetVariable(pset.vars, "HISTFILE");
+               if (histfile == NULL)
                {
-                       char       *psql_history;
+                       if (get_home_path(home))
+                       {
+                               psql_history = pg_malloc(strlen(home) + 1 +
+                                                                                strlen(PSQLHISTORY) + 1);
+                               snprintf(psql_history, MAXPGPATH, "%s/%s", home, PSQLHISTORY);
+                       }
+               }
+               else
+               {
+                       psql_history = pg_strdup(histfile);
+                       expand_tilde(&psql_history);
+               }
 
-                       psql_history = pg_malloc(strlen(home) + 1 +
-                                                                        strlen(PSQLHISTORY) + 1);
-                       sprintf(psql_history, "%s/%s", home, PSQLHISTORY);
+               if (psql_history)
+               {
                        read_history(psql_history);
-                       free(psql_history);
+                       decode_history();
                }
        }
 #endif
@@ -197,27 +321,100 @@ initializeInput(int flags)
 }
 
 
-
+/*
+ * This function saves the readline history when user
+ * runs \s command or when psql exits.
+ *
+ * fname: pathname of history file.  (Should really be "const char *",
+ * but some ancient versions of readline omit the const-decoration.)
+ *
+ * max_lines: if >= 0, limit history file to that many entries.
+ *
+ * appendFlag: if true, try to append just our new lines to the file.
+ * If false, write the whole available history.
+ *
+ * encodeFlag: whether to encode \n as \x01.  For \s calls we don't wish
+ * to do that, but must do so when saving the final history file.
+ */
 bool
-saveHistory(char *fname)
+saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
 {
 #ifdef USE_READLINE
-       if (useHistory && fname)
+
+       /*
+        * Suppressing the write attempt when HISTFILE is set to /dev/null may
+        * look like a negligible optimization, but it's necessary on e.g. Darwin,
+        * where write_history will fail because it tries to chmod the target
+        * file.
+        */
+       if (useHistory && fname &&
+               strcmp(fname, DEVNULL) != 0)
        {
-               if (write_history(fname) == 0)
-                       return true;
+               if (encodeFlag)
+                       encode_history();
+
+               /*
+                * On newer versions of libreadline, truncate the history file as
+                * needed and then append what we've added.  This avoids overwriting
+                * history from other concurrent sessions (although there are still
+                * race conditions when two sessions exit at about the same time). If
+                * we don't have those functions, fall back to write_history().
+                *
+                * Note: return value of write_history is not standardized across GNU
+                * readline and libedit.  Therefore, check for errno becoming set to
+                * see if the write failed.  Similarly for append_history.
+                */
+#if defined(HAVE_HISTORY_TRUNCATE_FILE) && defined(HAVE_APPEND_HISTORY)
+               if (appendFlag)
+               {
+                       int                     nlines;
+                       int                     fd;
+
+                       /* truncate previous entries if needed */
+                       if (max_lines >= 0)
+                       {
+                               nlines = Max(max_lines - history_lines_added, 0);
+                               (void) history_truncate_file(fname, nlines);
+                       }
+                       /* append_history fails if file doesn't already exist :-( */
+                       fd = open(fname, O_CREAT | O_WRONLY | PG_BINARY, 0600);
+                       if (fd >= 0)
+                               close(fd);
+                       /* append the appropriate number of lines */
+                       if (max_lines >= 0)
+                               nlines = Min(max_lines, history_lines_added);
+                       else
+                               nlines = history_lines_added;
+                       errno = 0;
+                       (void) append_history(nlines, fname);
+                       if (errno == 0)
+                               return true;
+               }
+               else
+#endif
+               {
+                       /* truncate what we have ... */
+                       if (max_lines >= 0)
+                               stifle_history(max_lines);
+                       /* ... and overwrite file.      Tough luck for concurrent sessions. */
+                       errno = 0;
+                       (void) write_history(fname);
+                       if (errno == 0)
+                               return true;
+               }
 
-               psql_error("could not save history to file \"%s\": %s\n", fname, strerror(errno));
+               psql_error("could not save history to file \"%s\": %s\n",
+                                  fname, strerror(errno));
        }
 #else
-               psql_error("history is not supported\n");
+       /* only get here in \s case, so complain */
+       psql_error("history is not supported by this installation\n");
 #endif
 
        return false;
 }
 
 
-
 static void
 #ifdef HAVE_ATEXIT
 finishInput(void)
@@ -226,27 +423,14 @@ finishInput(int exitstatus, void *arg)
 #endif
 {
 #ifdef USE_READLINE
-       if (useHistory)
+       if (useHistory && psql_history)
        {
-               char            home[MAXPGPATH];
-
-               if (get_home_path(home))
-               {
-                       char       *psql_history;
-                       int                     hist_size;
-
-                       psql_history = pg_malloc(strlen(home) + 1 +
-                                                                        strlen(PSQLHISTORY) + 1);
+               int                     hist_size;
 
-                       hist_size = GetVariableNum(pset.vars, "HISTSIZE", -1, -1, true);
-
-                       if (hist_size >= 0)
-                               stifle_history(hist_size);
-
-                       sprintf(psql_history, "%s/%s", home, PSQLHISTORY);
-                       write_history(psql_history);
-                       free(psql_history);
-               }
+               hist_size = GetVariableNum(pset.vars, "HISTSIZE", 500, -1, true);
+               saveHistory(psql_history, hist_size, true, true);
+               free(psql_history);
+               psql_history = NULL;
        }
 #endif
 }