From e2e2a9db4c6581b3839fc8139f7d33c6770316b9 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Sun, 11 Jun 2006 23:06:00 +0000 Subject: [PATCH] Code review for psql multiline history patch(es). Fix memory leak, failure to enter commands in history if canceled by control-C, other infelicities. --- src/bin/psql/command.c | 12 +--- src/bin/psql/help.c | 3 +- src/bin/psql/input.c | 127 ++++++++++++++++------------------ src/bin/psql/input.h | 10 +-- src/bin/psql/mainloop.c | 134 +++++++++++++++++------------------- src/bin/psql/prompt.c | 3 +- src/bin/psql/tab-complete.c | 4 +- 7 files changed, 132 insertions(+), 161 deletions(-) diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 3c5bd6740f1..a9d8a07f835 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.169 2006/06/07 22:24:45 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.170 2006/06/11 23:06:00 tgl Exp $ */ #include "postgres_fe.h" #include "command.h" @@ -1361,7 +1361,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf) } else { - /* read file back in */ + /* read file back into query_buf */ char line[1024]; resetPQExpBuffer(query_buf); @@ -1374,14 +1374,6 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf) error = true; } -#ifdef USE_READLINE -#ifdef HAVE_REPLACE_HISTORY_ENTRY - - replace_history_entry(where_history(), query_buf->data, NULL); -#else - add_history(query_buf->data); -#endif -#endif fclose(stream); } diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index dd3bdef0434..6bc0b73d08e 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -3,11 +3,10 @@ * * Copyright (c) 2000-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.110 2006/03/05 15:58:51 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.111 2006/06/11 23:06:00 tgl Exp $ */ #include "postgres_fe.h" #include "common.h" -#include "pqexpbuffer.h" #include "input.h" #include "print.h" #include "help.h" diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c index 1dc36d9dbc5..b970175b805 100644 --- a/src/bin/psql/input.c +++ b/src/bin/psql/input.c @@ -3,12 +3,12 @@ * * Copyright (c) 2000-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/input.c,v 1.53 2006/03/21 13:38:12 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/psql/input.c,v 1.54 2006/06/11 23:06:00 tgl Exp $ */ #include "postgres_fe.h" -#include "pqexpbuffer.h" #include "input.h" +#include "pqexpbuffer.h" #include "settings.h" #include "tab-complete.h" #include "common.h" @@ -78,110 +78,89 @@ GetHistControlConfig(void) #endif -static char * -gets_basic(const char prompt[]) -{ - fputs(prompt, stdout); - fflush(stdout); - return gets_fromFile(stdin); -} - - /* * gets_interactive() * - * Gets a line of interactive input, using readline of desired. + * Gets a line of interactive input, using readline if desired. * The result is malloc'ed. */ char * gets_interactive(const char *prompt) { #ifdef USE_READLINE - char *s; - if (useReadline) + { /* On some platforms, readline is declared as readline(char *) */ - s = readline((char *) prompt); - else - s = gets_basic(prompt); - - return s; -#else - return gets_basic(prompt); + return readline((char *) prompt); + } #endif + + fputs(prompt, stdout); + fflush(stdout); + return gets_fromFile(stdin); } -/* Put the line in the history buffer and also add the trailing \n */ +/* + * Append the line to the history buffer, making sure there is a trailing '\n' + */ void -pg_append_history(char *s, PQExpBuffer history_buf) +pg_append_history(const char *s, PQExpBuffer history_buf) { #ifdef USE_READLINE - - int slen; - if (useReadline && useHistory && s && s[0]) + if (useHistory && s && s[0]) { - slen = strlen(s); - if (s[slen-1] == '\n') - appendPQExpBufferStr(history_buf, s); - else - { - appendPQExpBufferStr(history_buf, s); + appendPQExpBufferStr(history_buf, s); + if (s[strlen(s) - 1] != '\n') appendPQExpBufferChar(history_buf, '\n'); - } } #endif } /* - * Feed the string to readline + * 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_write_history(char *s) +pg_send_history(PQExpBuffer history_buf) { #ifdef USE_READLINE - static char *prev_hist; - int slen, i; - - if (useReadline && useHistory ) + static char *prev_hist = NULL; + + char *s = history_buf->data; + + if (useHistory && s[0]) { - enum histcontrol HC; - - /* Flushing of empty buffer should do nothing */ - if (*s == 0) - return; - - prev_hist = NULL; - - HC = GetHistControlConfig(); + enum histcontrol HC = GetHistControlConfig(); if (((HC & hctl_ignorespace) && s[0] == ' ') || - ((HC & hctl_ignoredups) && prev_hist && strcmp(s, prev_hist) == 0)) + ((HC & hctl_ignoredups) && prev_hist && strcmp(s, prev_hist) == 0)) { /* Ignore this line as far as history is concerned */ } else { - free(prev_hist); - slen = strlen(s); - /* Trim the trailing \n's */ - for (i = slen-1; i >= 0 && s[i] == '\n'; i--) + int i; + + /* 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'; + /* 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); } } -#endif -} -void -pg_clear_history(PQExpBuffer history_buf) -{ -#ifdef USE_READLINE - if (useReadline && useHistory) - resetPQExpBuffer(history_buf); + resetPQExpBuffer(history_buf); #endif } @@ -219,6 +198,9 @@ gets_fromFile(FILE *source) #ifdef USE_READLINE +/* + * Convert newlines to NL_IN_HISTORY for safe saving in readline history file + */ static void encode_history(void) { @@ -232,6 +214,9 @@ encode_history(void) *cur_ptr = NL_IN_HISTORY; } +/* + * Reverse the above encoding + */ static void decode_history(void) { @@ -285,9 +270,10 @@ initializeInput(int flags) } if (psql_history) + { read_history(psql_history); - - decode_history(); + decode_history(); + } } #endif @@ -299,11 +285,13 @@ initializeInput(int flags) } -/* This function is designed for saving the readline history when user - * run \s command or when psql finishes. - * We have an argument named encodeFlag to handle those cases differently - * In that case of call via \s we don't really need to encode \n as \x01, - * but when we save history for Readline we must do that conversion +/* + * This function is for saving the readline history when user + * runs \s command or when psql finishes. + * + * We have an argument named encodeFlag to handle the cases differently. + * In case of call via \s we don't really need to encode \n as \x01, + * but when we save history for Readline we must do that conversion. */ bool saveHistory(char *fname, bool encodeFlag) @@ -316,7 +304,8 @@ saveHistory(char *fname, bool encodeFlag) if (write_history(fname) == 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 by this installation\n"); diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h index 4fbc86079b4..0c6ede0cd5a 100644 --- a/src/bin/psql/input.h +++ b/src/bin/psql/input.h @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/input.h,v 1.27 2006/03/21 13:38:12 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/psql/input.h,v 1.28 2006/06/11 23:06:00 tgl Exp $ */ #ifndef INPUT_H #define INPUT_H @@ -32,6 +32,8 @@ #endif #endif +#include "pqexpbuffer.h" + char *gets_interactive(const char *prompt); char *gets_fromFile(FILE *source); @@ -39,9 +41,7 @@ char *gets_fromFile(FILE *source); void initializeInput(int flags); bool saveHistory(char *fname, bool encodeFlag); -void pg_append_history(char *s, PQExpBuffer history_buf); -void pg_clear_history(PQExpBuffer history_buf); -void pg_write_history(char *s); - +void pg_append_history(const char *s, PQExpBuffer history_buf); +void pg_send_history(PQExpBuffer history_buf); #endif /* INPUT_H */ diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c index 6c02ba1ccae..af297ae2a91 100644 --- a/src/bin/psql/mainloop.c +++ b/src/bin/psql/mainloop.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/mainloop.c,v 1.78 2006/06/07 13:18:37 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/psql/mainloop.c,v 1.79 2006/06/11 23:06:00 tgl Exp $ */ #include "postgres_fe.h" #include "mainloop.h" @@ -37,12 +37,12 @@ MainLoop(FILE *source) PQExpBuffer query_buf; /* buffer for query being accumulated */ PQExpBuffer previous_buf; /* if there isn't anything in the new buffer * yet, use this one for \e, etc. */ - PQExpBuffer history_buf; + PQExpBuffer history_buf; /* earlier lines of a multi-line command, + * not yet saved to readline history */ char *line; /* current line of input */ int added_nl_pos; bool success; - - volatile bool line_saved_in_history; + bool line_saved_in_history; volatile int successResult = EXIT_SUCCESS; volatile backslashResult slashCmdStatus = PSQL_CMD_UNKNOWN; volatile promptStatus_t prompt_status = PROMPT_READY; @@ -70,7 +70,6 @@ MainLoop(FILE *source) query_buf = createPQExpBuffer(); previous_buf = createPQExpBuffer(); history_buf = createPQExpBuffer(); - if (!query_buf || !previous_buf || !history_buf) { psql_error("out of memory\n"); @@ -80,8 +79,6 @@ MainLoop(FILE *source) /* main loop to get queries and execute them */ while (successResult == EXIT_SUCCESS) { - line_saved_in_history = false; - /* * Welcome code for Control-C */ @@ -97,7 +94,7 @@ MainLoop(FILE *source) successResult = EXIT_USER; break; } - pg_clear_history(history_buf); + cancel_pressed = false; } @@ -113,8 +110,6 @@ MainLoop(FILE *source) count_eof = 0; slashCmdStatus = PSQL_CMD_UNKNOWN; prompt_status = PROMPT_READY; - if (pset.cur_cmd_interactive) - pg_write_history(history_buf->data); if (pset.cur_cmd_interactive) putc('\n', stdout); @@ -135,33 +130,10 @@ MainLoop(FILE *source) fflush(stdout); - if (slashCmdStatus == PSQL_CMD_NEWEDIT) - { - /* - * just returned from editing the line? then just copy to the - * input buffer - */ - line = pg_strdup(query_buf->data); - /* reset parsing state since we are rescanning whole line */ - resetPQExpBuffer(query_buf); - psql_scan_reset(scan_state); - slashCmdStatus = PSQL_CMD_UNKNOWN; - prompt_status = PROMPT_READY; - - if (pset.cur_cmd_interactive) - { - /* - * Pass all the contents of history_buf to readline - * and free the history buffer. - */ - pg_write_history(history_buf->data); - pg_clear_history(history_buf); - pg_write_history(line); - line_saved_in_history = true; - } - } - /* otherwise, get another line */ - else if (pset.cur_cmd_interactive) + /* + * get another line + */ + if (pset.cur_cmd_interactive) { /* May need to reset prompt, eg after \r command */ if (query_buf->len == 0) @@ -230,7 +202,8 @@ MainLoop(FILE *source) */ psql_scan_setup(scan_state, line, strlen(line)); success = true; - + line_saved_in_history = false; + while (success || !die_on_error) { PsqlScanResult scan_result; @@ -247,6 +220,17 @@ MainLoop(FILE *source) (scan_result == PSCAN_EOL && GetVariableBool(pset.vars, "SINGLELINE"))) { + /* + * Save query in history. We use history_buf to accumulate + * multi-line queries into a single history entry. + */ + if (pset.cur_cmd_interactive && !line_saved_in_history) + { + pg_append_history(line, history_buf); + pg_send_history(history_buf); + line_saved_in_history = true; + } + /* execute query */ success = SendQuery(query_buf->data); slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; @@ -265,12 +249,27 @@ MainLoop(FILE *source) * If we added a newline to query_buf, and nothing else has * been inserted in query_buf by the lexer, then strip off the * newline again. This avoids any change to query_buf when a - * line contains only a backslash command. + * line contains only a backslash command. Also, in this + * situation we force out any previous lines as a separate + * history entry; we don't want SQL and backslash commands + * intermixed in history if at all possible. */ if (query_buf->len == added_nl_pos) + { query_buf->data[--query_buf->len] = '\0'; + pg_send_history(history_buf); + } added_nl_pos = -1; + /* save backslash command in history */ + if (pset.cur_cmd_interactive && !line_saved_in_history) + { + pg_append_history(line, history_buf); + pg_send_history(history_buf); + line_saved_in_history = true; + } + + /* execute backslash command */ slashCmdStatus = HandleSlashCmds(scan_state, query_buf->len > 0 ? query_buf : previous_buf); @@ -295,44 +294,32 @@ MainLoop(FILE *source) /* flush any paren nesting info after forced send */ psql_scan_reset(scan_state); } + else if (slashCmdStatus == PSQL_CMD_NEWEDIT) + { + /* rescan query_buf as new input */ + psql_scan_finish(scan_state); + free(line); + line = pg_strdup(query_buf->data); + resetPQExpBuffer(query_buf); + /* reset parsing state since we are rescanning whole line */ + psql_scan_reset(scan_state); + psql_scan_setup(scan_state, line, strlen(line)); + line_saved_in_history = false; + prompt_status = PROMPT_READY; + } + else if (slashCmdStatus == PSQL_CMD_TERMINATE) + break; } - /* - * If we append to history a backslash command that is inside - * a multi-line query, then when we recall the history, the - * backslash command will make the query invalid, so we write - * backslash commands immediately rather than keeping them - * as part of the current multi-line query. We do the test - * down here so we can check for \g and other 'execute' - * backslash commands, which should be appended. - */ - if (!line_saved_in_history && pset.cur_cmd_interactive) - { - /* Sending a command (PSQL_CMD_SEND) zeros the length */ - if (scan_result == PSCAN_BACKSLASH) - pg_write_history(line); - else - pg_append_history(line, history_buf); - line_saved_in_history = true; - } - - /* fall out of loop on \q or if lexer reached EOL */ - if (slashCmdStatus == PSQL_CMD_TERMINATE || - scan_result == PSCAN_INCOMPLETE || + /* fall out of loop if lexer reached EOL */ + if (scan_result == PSCAN_INCOMPLETE || scan_result == PSCAN_EOL) break; } - if ((pset.cur_cmd_interactive && prompt_status == PROMPT_READY) || - (GetVariableBool(pset.vars, "SINGLELINE") && prompt_status == PROMPT_CONTINUE)) - { - /* - * Pass all the contents of history_buf to readline - * and free the history buffer. - */ - pg_write_history(history_buf->data); - pg_clear_history(history_buf); - } + /* Add line to pending history if we didn't execute anything yet */ + if (pset.cur_cmd_interactive && !line_saved_in_history) + pg_append_history(line, history_buf); psql_scan_finish(scan_state); free(line); @@ -359,6 +346,11 @@ MainLoop(FILE *source) if (query_buf->len > 0 && !pset.cur_cmd_interactive && successResult == EXIT_SUCCESS) { + /* save query in history */ + if (pset.cur_cmd_interactive) + pg_send_history(history_buf); + + /* execute query */ success = SendQuery(query_buf->data); if (!success && die_on_error) diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c index f7d591b0545..07917c53450 100644 --- a/src/bin/psql/prompt.c +++ b/src/bin/psql/prompt.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/prompt.c,v 1.44 2006/04/19 16:02:17 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/psql/prompt.c,v 1.45 2006/06/11 23:06:00 tgl Exp $ */ #include "postgres_fe.h" #include "prompt.h" @@ -12,7 +12,6 @@ #include "settings.h" #include "common.h" -#include "pqexpbuffer.h" #include "input.h" #include "variables.h" diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 653e57023d8..36d2ff22020 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.152 2006/05/28 02:27:08 alvherre Exp $ + * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.153 2006/06/11 23:06:00 tgl Exp $ */ /*---------------------------------------------------------------------- @@ -43,7 +43,6 @@ #include "postgres_fe.h" #include "tab-complete.h" -#include "pqexpbuffer.h" #include "input.h" /* If we don't have this, we might as well forget about the whole thing: */ @@ -51,6 +50,7 @@ #include <ctype.h> #include "libpq-fe.h" +#include "pqexpbuffer.h" #include "common.h" #include "settings.h" -- GitLab