diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 3344348f048..295196adfaf 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -264,7 +264,8 @@ EOF
- Do not use readline for line editing and do not use the history.
+ Do not use Readline for line editing and do
+ not use the command history.
This can be useful to turn off tab expansion when cutting and pasting.
@@ -2255,12 +2256,13 @@ lo_import 152801
\s [ filename ]
- Print or save the command line history to filename. If filename is omitted, the history
- is written to the standard output. This option is only available
- if psql is configured to use the
- GNU Readline library.
+ Print psql's command line history
+ to filename.
+ If filename is omitted,
+ the history is written to the standard output (using the pager if
+ appropriate). This command is not available
+ if psql was built
+ without Readline support.
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 0faeffb3ff7..290598581eb 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1039,20 +1039,8 @@ exec_command(const char *cmd,
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
-#if defined(WIN32) && !defined(__CYGWIN__)
-
- /*
- * XXX This does not work for all terminal environments or for output
- * containing non-ASCII characters; see comments in simple_prompt().
- */
-#define DEVTTY "con"
-#else
-#define DEVTTY "/dev/tty"
-#endif
-
expand_tilde(&fname);
- /* This scrolls off the screen when using /dev/tty */
- success = saveHistory(fname ? fname : DEVTTY, -1, false, false);
+ success = printHistory(fname, pset.popt.topt.pager);
if (success && !pset.quiet && fname)
printf(gettext("Wrote history to file \"%s/%s\".\n"),
pset.dirname ? pset.dirname : ".", fname);
diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c
index cbb0e9a45bb..6d83681fbd7 100644
--- a/src/bin/psql/input.c
+++ b/src/bin/psql/input.c
@@ -11,6 +11,7 @@
#include
#endif
#include
+#include
#include "input.h"
#include "settings.h"
@@ -222,23 +223,73 @@ gets_fromFile(FILE *source)
#ifdef USE_READLINE
+
+/*
+ * Macros to iterate over each element of the history list in order
+ *
+ * You would think this would be simple enough, but in its inimitable fashion
+ * libedit has managed to break it: in libreadline we must use next_history()
+ * to go from oldest to newest, but in libedit we must use previous_history().
+ * To detect what to do, we make a trial call of previous_history(): if it
+ * fails, then either next_history() is what to use, or there's zero or one
+ * history entry so that it doesn't matter which direction we go.
+ *
+ * In case that wasn't disgusting enough: the code below is not as obvious as
+ * it might appear. In some libedit releases history_set_pos(0) fails until
+ * at least one add_history() call has been done. This is not an issue for
+ * printHistory() or encode_history(), which cannot be invoked before that has
+ * happened. In decode_history(), that's not so, and what actually happens is
+ * that we are sitting on the newest entry to start with, previous_history()
+ * fails, and we iterate over all the entries using next_history(). So the
+ * decode_history() loop iterates over the entries in the wrong order when
+ * using such a libedit release, and if there were another attempt to use
+ * BEGIN_ITERATE_HISTORY() before some add_history() call had happened, it
+ * wouldn't work. Fortunately we don't care about either of those things.
+ *
+ * Usage pattern is:
+ *
+ * BEGIN_ITERATE_HISTORY(varname);
+ * {
+ * loop body referencing varname->line;
+ * }
+ * END_ITERATE_HISTORY();
+ */
+#define BEGIN_ITERATE_HISTORY(VARNAME) \
+ do { \
+ HIST_ENTRY *VARNAME; \
+ bool use_prev_; \
+ \
+ history_set_pos(0); \
+ use_prev_ = (previous_history() != NULL); \
+ history_set_pos(0); \
+ for (VARNAME = current_history(); VARNAME != NULL; \
+ VARNAME = use_prev_ ? previous_history() : next_history()) \
+ { \
+ (void) 0
+
+#define END_ITERATE_HISTORY() \
+ } \
+ } while(0)
+
+
/*
* 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())
+ BEGIN_ITERATE_HISTORY(cur_hist);
{
+ char *cur_ptr;
+
/* 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;
+ }
}
+ END_ITERATE_HISTORY();
}
/*
@@ -247,17 +298,18 @@ encode_history(void)
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())
+ BEGIN_ITERATE_HISTORY(cur_hist);
{
+ char *cur_ptr;
+
/* 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';
+ }
}
+ END_ITERATE_HISTORY();
}
#endif /* USE_READLINE */
@@ -323,22 +375,15 @@ initializeInput(int flags)
/*
- * This function saves the readline history when user
- * runs \s command or when psql exits.
+ * This function saves the readline history 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, int max_lines, bool appendFlag, bool encodeFlag)
+static bool
+saveHistory(char *fname, int max_lines)
{
#ifdef USE_READLINE
@@ -348,11 +393,15 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
* where write_history will fail because it tries to chmod the target
* file.
*/
- if (useHistory && fname &&
- strcmp(fname, DEVNULL) != 0)
+ if (strcmp(fname, DEVNULL) != 0)
{
- if (encodeFlag)
- encode_history();
+ /*
+ * Encode \n, since otherwise readline will reload multiline history
+ * entries as separate lines. (libedit doesn't really need this, but
+ * we do it anyway since it's too hard to tell which implementation we
+ * are using.)
+ */
+ encode_history();
/*
* On newer versions of libreadline, truncate the history file as
@@ -366,7 +415,6 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
* 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;
@@ -391,8 +439,7 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
if (errno == 0)
return true;
}
- else
-#endif
+#else /* don't have append support */
{
/* truncate what we have ... */
if (max_lines >= 0)
@@ -403,19 +450,73 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
if (errno == 0)
return true;
}
+#endif
psql_error("could not save history to file \"%s\": %s\n",
fname, strerror(errno));
}
-#else
- /* only get here in \s case, so complain */
- psql_error("history is not supported by this installation\n");
#endif
return false;
}
+/*
+ * Print history to the specified file, or to the console if fname is NULL
+ * (psql \s command)
+ *
+ * We used to use saveHistory() for this purpose, but that doesn't permit
+ * use of a pager; moreover libedit's implementation behaves incompatibly
+ * (preferring to encode its output) and may fail outright when the target
+ * file is specified as /dev/tty.
+ */
+bool
+printHistory(const char *fname, unsigned short int pager)
+{
+#ifdef USE_READLINE
+ FILE *output;
+ bool is_pager;
+
+ if (!useHistory)
+ return false;
+
+ if (fname == NULL)
+ {
+ /* use pager, if enabled, when printing to console */
+ output = PageOutput(INT_MAX, pager);
+ is_pager = true;
+ }
+ else
+ {
+ output = fopen(fname, "w");
+ if (output == NULL)
+ {
+ psql_error("could not save history to file \"%s\": %s\n",
+ fname, strerror(errno));
+ return false;
+ }
+ is_pager = false;
+ }
+
+ BEGIN_ITERATE_HISTORY(cur_hist);
+ {
+ fprintf(output, "%s\n", cur_hist->line);
+ }
+ END_ITERATE_HISTORY();
+
+ if (is_pager)
+ ClosePager(output);
+ else
+ fclose(output);
+
+ return true;
+#else
+ psql_error("history is not supported by this installation\n");
+ return false;
+#endif
+}
+
+
static void
finishInput(void)
{
@@ -425,7 +526,7 @@ finishInput(void)
int hist_size;
hist_size = GetVariableNum(pset.vars, "HISTSIZE", 500, -1, true);
- saveHistory(psql_history, hist_size, true, true);
+ (void) saveHistory(psql_history, hist_size);
free(psql_history);
psql_history = NULL;
}
diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h
index ef2cc499527..33b6c0712fe 100644
--- a/src/bin/psql/input.h
+++ b/src/bin/psql/input.h
@@ -42,7 +42,8 @@ char *gets_interactive(const char *prompt);
char *gets_fromFile(FILE *source);
void initializeInput(int flags);
-bool saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag);
+
+bool printHistory(const char *fname, unsigned short int pager);
void pg_append_history(const char *s, PQExpBuffer history_buf);
void pg_send_history(PQExpBuffer history_buf);