]> granicus.if.org Git - postgresql/blobdiff - src/bin/psql/command.c
Move logging.h and logging.c from src/fe_utils/ to src/common/.
[postgresql] / src / bin / psql / command.c
index 5d90ca28edf446ad9ac944d14d9fc2d50d119ef4..a2c0ec0b7ff7d7e4ed6750aae45aa3cb377db637 100644 (file)
@@ -1,24 +1,17 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2014, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
  *
  * src/bin/psql/command.c
  */
 #include "postgres_fe.h"
 #include "command.h"
 
-#ifdef __BORLANDC__                            /* needed for BCC */
-#undef mkdir
-#endif
-
 #include <ctype.h>
 #include <time.h>
-#ifdef HAVE_PWD_H
 #include <pwd.h>
-#endif
 #ifndef WIN32
-#include <sys/types.h>                 /* for umask() */
 #include <sys/stat.h>                  /* for stat() */
 #include <fcntl.h>                             /* open() flags */
 #include <unistd.h>                            /* for geteuid(), getpid(), stat() */
 #include <io.h>
 #include <fcntl.h>
 #include <direct.h>
-#include <sys/types.h>                 /* for umask() */
 #include <sys/stat.h>                  /* for stat() */
 #endif
-#ifdef USE_OPENSSL
-#include <openssl/ssl.h>
-#endif
 
+#include "catalog/pg_class_d.h"
 #include "portability/instr_time.h"
 
 #include "libpq-fe.h"
 #include "pqexpbuffer.h"
-#include "dumputils.h"
+#include "common/logging.h"
+#include "fe_utils/print.h"
+#include "fe_utils/string_utils.h"
 
 #include "common.h"
 #include "copy.h"
+#include "crosstabview.h"
 #include "describe.h"
 #include "help.h"
 #include "input.h"
 #include "large_obj.h"
 #include "mainloop.h"
-#include "print.h"
-#include "psqlscan.h"
+#include "psqlscanslash.h"
 #include "settings.h"
 #include "variables.h"
 
+/*
+ * Editable database object types.
+ */
+typedef enum EditableObjectType
+{
+       EditableFunction,
+       EditableView
+} EditableObjectType;
 
-/* functions for use in this file */
+/* local function declarations */
 static backslashResult exec_command(const char *cmd,
                         PsqlScanState scan_state,
-                        PQExpBuffer query_buf);
+                        ConditionalStack cstack,
+                        PQExpBuffer query_buf,
+                        PQExpBuffer previous_buf);
+static backslashResult exec_command_a(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_C(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_connect(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_cd(PsqlScanState scan_state, bool active_branch,
+                               const char *cmd);
+static backslashResult exec_command_conninfo(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_copy(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_copyright(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch,
+                          const char *cmd);
+static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch,
+                                 PQExpBuffer query_buf, PQExpBuffer previous_buf);
+static backslashResult exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
+                                  PQExpBuffer query_buf, bool is_func);
+static backslashResult exec_command_echo(PsqlScanState scan_state, bool active_branch,
+                                 const char *cmd);
+static backslashResult exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack,
+                                 PQExpBuffer query_buf);
+static backslashResult exec_command_else(PsqlScanState scan_state, ConditionalStack cstack,
+                                 PQExpBuffer query_buf);
+static backslashResult exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack,
+                                  PQExpBuffer query_buf);
+static backslashResult exec_command_encoding(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch,
+                          const char *cmd);
+static backslashResult exec_command_gdesc(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_html(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_include(PsqlScanState scan_state, bool active_branch,
+                                        const char *cmd);
+static backslashResult exec_command_if(PsqlScanState scan_state, ConditionalStack cstack,
+                               PQExpBuffer query_buf);
+static backslashResult exec_command_list(PsqlScanState scan_state, bool active_branch,
+                                 const char *cmd);
+static backslashResult exec_command_lo(PsqlScanState scan_state, bool active_branch,
+                               const char *cmd);
+static backslashResult exec_command_out(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_print(PsqlScanState scan_state, bool active_branch,
+                                  PQExpBuffer query_buf, PQExpBuffer previous_buf);
+static backslashResult exec_command_password(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_prompt(PsqlScanState scan_state, bool active_branch,
+                                       const char *cmd);
+static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch,
+                                  PQExpBuffer query_buf);
+static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch,
+                                       const char *cmd);
+static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_branch,
+                                  const char *cmd, bool is_func);
+static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch,
+                                  const char *cmd);
+static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch,
+                                  const char *cmd,
+                                  PQExpBuffer query_buf, PQExpBuffer previous_buf);
+static backslashResult exec_command_watch(PsqlScanState scan_state, bool active_branch,
+                                  PQExpBuffer query_buf, PQExpBuffer previous_buf);
+static backslashResult exec_command_x(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_z(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_shell_escape(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch);
+static char *read_connect_arg(PsqlScanState scan_state);
+static PQExpBuffer gather_boolean_expression(PsqlScanState scan_state);
+static bool is_true_boolean_expression(PsqlScanState scan_state, const char *name);
+static void ignore_boolean_expression(PsqlScanState scan_state);
+static void ignore_slash_options(PsqlScanState scan_state);
+static void ignore_slash_filepipe(PsqlScanState scan_state);
+static void ignore_slash_whole_line(PsqlScanState scan_state);
+static bool is_branching_command(const char *cmd);
+static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
+                                         PQExpBuffer query_buf);
+static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
+                                  PQExpBuffer query_buf);
+static void copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf);
+static bool do_connect(enum trivalue reuse_previous_specification,
+                  char *dbname, char *user, char *host, char *port);
 static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
                int lineno, bool *edited);
-static bool do_connect(char *dbname, char *user, char *host, char *port);
 static bool do_shell(const char *command);
-static bool do_watch(PQExpBuffer query_buf, long sleep);
-static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
-static bool get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf);
-static int     strip_lineno_from_funcdesc(char *func);
+static bool do_watch(PQExpBuffer query_buf, double sleep);
+static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
+                                 Oid *obj_oid);
+static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid,
+                                         PQExpBuffer buf);
+static int     strip_lineno_from_objdesc(char *obj);
+static int     count_lines_in_buf(PQExpBuffer buf);
+static void print_with_linenumbers(FILE *output, char *lines,
+                                          const char *header_keyword);
 static void minimal_error_message(PGresult *res);
 
 static void printSSLInfo(void);
+static void printGSSInfo(void);
 static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
+static char *pset_value_string(const char *param, struct printQueryOpt *popt);
 
 #ifdef WIN32
 static void checkWin32Codepage(void);
@@ -86,9 +180,18 @@ static void checkWin32Codepage(void);
  * just after the '\'.  The lexer is advanced past the command and all
  * arguments on return.
  *
- * 'query_buf' contains the query-so-far, which may be modified by
+ * cstack is the current \if stack state.  This will be examined, and
+ * possibly modified by conditional commands.
+ *
+ * query_buf contains the query-so-far, which may be modified by
  * execution of the backslash command (for example, \r clears it).
- * query_buf can be NULL if there is no query so far.
+ *
+ * previous_buf contains the query most recently sent to the server
+ * (empty if none yet).  This should not be modified here, but some
+ * commands copy its content into query_buf.
+ *
+ * query_buf and previous_buf will be NULL when executing a "-c"
+ * command-line option.
  *
  * Returns a status code indicating what action is desired, see command.h.
  *----------
@@ -96,39 +199,49 @@ static void checkWin32Codepage(void);
 
 backslashResult
 HandleSlashCmds(PsqlScanState scan_state,
-                               PQExpBuffer query_buf)
+                               ConditionalStack cstack,
+                               PQExpBuffer query_buf,
+                               PQExpBuffer previous_buf)
 {
-       backslashResult status = PSQL_CMD_SKIP_LINE;
+       backslashResult status;
        char       *cmd;
        char       *arg;
 
        Assert(scan_state != NULL);
+       Assert(cstack != NULL);
 
        /* Parse off the command name */
        cmd = psql_scan_slash_command(scan_state);
 
        /* And try to execute it */
-       status = exec_command(cmd, scan_state, query_buf);
+       status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
 
        if (status == PSQL_CMD_UNKNOWN)
        {
+               pg_log_error("invalid command \\%s", cmd);
                if (pset.cur_cmd_interactive)
-                       psql_error("Invalid command \\%s. Try \\? for help.\n", cmd);
-               else
-                       psql_error("invalid command \\%s\n", cmd);
+                       pg_log_info("Try \\? for help.");
                status = PSQL_CMD_ERROR;
        }
 
        if (status != PSQL_CMD_ERROR)
        {
-               /* eat any remaining arguments after a valid command */
-               /* note we suppress evaluation of backticks here */
+               /*
+                * Eat any remaining arguments after a valid command.  We want to
+                * suppress evaluation of backticks in this situation, so transiently
+                * push an inactive conditional-stack entry.
+                */
+               bool            active_branch = conditional_active(cstack);
+
+               conditional_stack_push(cstack, IFSTATE_IGNORED);
                while ((arg = psql_scan_slash_option(scan_state,
-                                                                                        OT_NO_EVAL, NULL, false)))
+                                                                                        OT_NORMAL, NULL, false)))
                {
-                       psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
+                       if (active_branch)
+                               pg_log_warning("\\%s: extra argument \"%s\" ignored", cmd, arg);
                        free(arg);
                }
+               conditional_stack_pop(cstack);
        }
        else
        {
@@ -149,56 +262,172 @@ HandleSlashCmds(PsqlScanState scan_state,
        return status;
 }
 
+
 /*
- * Read and interpret an argument to the \connect slash command.
+ * Subroutine to actually try to execute a backslash command.
+ *
+ * The typical "success" result code is PSQL_CMD_SKIP_LINE, although some
+ * commands return something else.  Failure results are PSQL_CMD_ERROR,
+ * unless PSQL_CMD_UNKNOWN is more appropriate.
  */
-static char *
-read_connect_arg(PsqlScanState scan_state)
+static backslashResult
+exec_command(const char *cmd,
+                        PsqlScanState scan_state,
+                        ConditionalStack cstack,
+                        PQExpBuffer query_buf,
+                        PQExpBuffer previous_buf)
 {
-       char       *result;
-       char            quote;
+       backslashResult status;
+       bool            active_branch = conditional_active(cstack);
 
        /*
-        * Ideally we should treat the arguments as SQL identifiers.  But for
-        * backwards compatibility with 7.2 and older pg_dump files, we have to
-        * take unquoted arguments verbatim (don't downcase them). For now,
-        * double-quoted arguments may be stripped of double quotes (as if SQL
-        * identifiers).  By 7.4 or so, pg_dump files can be expected to
-        * double-quote all mixed-case \connect arguments, and then we can get rid
-        * of OT_SQLIDHACK.
+        * In interactive mode, warn when we're ignoring a command within a false
+        * \if-branch.  But we continue on, so as to parse and discard the right
+        * amount of parameter text.  Each individual backslash command subroutine
+        * is responsible for doing nothing after discarding appropriate
+        * arguments, if !active_branch.
         */
-       result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &quote, true);
-
-       if (!result)
-               return NULL;
+       if (pset.cur_cmd_interactive && !active_branch &&
+               !is_branching_command(cmd))
+       {
+               pg_log_warning("\\%s command ignored; use \\endif or Ctrl-C to exit current \\if block",
+                                  cmd);
+       }
 
-       if (quote)
-               return result;
+       if (strcmp(cmd, "a") == 0)
+               status = exec_command_a(scan_state, active_branch);
+       else if (strcmp(cmd, "C") == 0)
+               status = exec_command_C(scan_state, active_branch);
+       else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+               status = exec_command_connect(scan_state, active_branch);
+       else if (strcmp(cmd, "cd") == 0)
+               status = exec_command_cd(scan_state, active_branch, cmd);
+       else if (strcmp(cmd, "conninfo") == 0)
+               status = exec_command_conninfo(scan_state, active_branch);
+       else if (pg_strcasecmp(cmd, "copy") == 0)
+               status = exec_command_copy(scan_state, active_branch);
+       else if (strcmp(cmd, "copyright") == 0)
+               status = exec_command_copyright(scan_state, active_branch);
+       else if (strcmp(cmd, "crosstabview") == 0)
+               status = exec_command_crosstabview(scan_state, active_branch);
+       else if (cmd[0] == 'd')
+               status = exec_command_d(scan_state, active_branch, cmd);
+       else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+               status = exec_command_edit(scan_state, active_branch,
+                                                                  query_buf, previous_buf);
+       else if (strcmp(cmd, "ef") == 0)
+               status = exec_command_ef_ev(scan_state, active_branch, query_buf, true);
+       else if (strcmp(cmd, "ev") == 0)
+               status = exec_command_ef_ev(scan_state, active_branch, query_buf, false);
+       else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+               status = exec_command_echo(scan_state, active_branch, cmd);
+       else if (strcmp(cmd, "elif") == 0)
+               status = exec_command_elif(scan_state, cstack, query_buf);
+       else if (strcmp(cmd, "else") == 0)
+               status = exec_command_else(scan_state, cstack, query_buf);
+       else if (strcmp(cmd, "endif") == 0)
+               status = exec_command_endif(scan_state, cstack, query_buf);
+       else if (strcmp(cmd, "encoding") == 0)
+               status = exec_command_encoding(scan_state, active_branch);
+       else if (strcmp(cmd, "errverbose") == 0)
+               status = exec_command_errverbose(scan_state, active_branch);
+       else if (strcmp(cmd, "f") == 0)
+               status = exec_command_f(scan_state, active_branch);
+       else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+               status = exec_command_g(scan_state, active_branch, cmd);
+       else if (strcmp(cmd, "gdesc") == 0)
+               status = exec_command_gdesc(scan_state, active_branch);
+       else if (strcmp(cmd, "gexec") == 0)
+               status = exec_command_gexec(scan_state, active_branch);
+       else if (strcmp(cmd, "gset") == 0)
+               status = exec_command_gset(scan_state, active_branch);
+       else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+               status = exec_command_help(scan_state, active_branch);
+       else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+               status = exec_command_html(scan_state, active_branch);
+       else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 ||
+                        strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+               status = exec_command_include(scan_state, active_branch, cmd);
+       else if (strcmp(cmd, "if") == 0)
+               status = exec_command_if(scan_state, cstack, query_buf);
+       else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
+                        strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+               status = exec_command_list(scan_state, active_branch, cmd);
+       else if (strncmp(cmd, "lo_", 3) == 0)
+               status = exec_command_lo(scan_state, active_branch, cmd);
+       else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+               status = exec_command_out(scan_state, active_branch);
+       else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+               status = exec_command_print(scan_state, active_branch,
+                                                                       query_buf, previous_buf);
+       else if (strcmp(cmd, "password") == 0)
+               status = exec_command_password(scan_state, active_branch);
+       else if (strcmp(cmd, "prompt") == 0)
+               status = exec_command_prompt(scan_state, active_branch, cmd);
+       else if (strcmp(cmd, "pset") == 0)
+               status = exec_command_pset(scan_state, active_branch);
+       else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+               status = exec_command_quit(scan_state, active_branch);
+       else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+               status = exec_command_reset(scan_state, active_branch, query_buf);
+       else if (strcmp(cmd, "s") == 0)
+               status = exec_command_s(scan_state, active_branch);
+       else if (strcmp(cmd, "set") == 0)
+               status = exec_command_set(scan_state, active_branch);
+       else if (strcmp(cmd, "setenv") == 0)
+               status = exec_command_setenv(scan_state, active_branch, cmd);
+       else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+               status = exec_command_sf_sv(scan_state, active_branch, cmd, true);
+       else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+               status = exec_command_sf_sv(scan_state, active_branch, cmd, false);
+       else if (strcmp(cmd, "t") == 0)
+               status = exec_command_t(scan_state, active_branch);
+       else if (strcmp(cmd, "T") == 0)
+               status = exec_command_T(scan_state, active_branch);
+       else if (strcmp(cmd, "timing") == 0)
+               status = exec_command_timing(scan_state, active_branch);
+       else if (strcmp(cmd, "unset") == 0)
+               status = exec_command_unset(scan_state, active_branch, cmd);
+       else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+               status = exec_command_write(scan_state, active_branch, cmd,
+                                                                       query_buf, previous_buf);
+       else if (strcmp(cmd, "watch") == 0)
+               status = exec_command_watch(scan_state, active_branch,
+                                                                       query_buf, previous_buf);
+       else if (strcmp(cmd, "x") == 0)
+               status = exec_command_x(scan_state, active_branch);
+       else if (strcmp(cmd, "z") == 0)
+               status = exec_command_z(scan_state, active_branch);
+       else if (strcmp(cmd, "!") == 0)
+               status = exec_command_shell_escape(scan_state, active_branch);
+       else if (strcmp(cmd, "?") == 0)
+               status = exec_command_slash_command_help(scan_state, active_branch);
+       else
+               status = PSQL_CMD_UNKNOWN;
 
-       if (*result == '\0' || strcmp(result, "-") == 0)
-               return NULL;
+       /*
+        * All the commands that return PSQL_CMD_SEND want to execute previous_buf
+        * if query_buf is empty.  For convenience we implement that here, not in
+        * the individual command subroutines.
+        */
+       if (status == PSQL_CMD_SEND)
+               copy_previous_query(query_buf, previous_buf);
 
-       return result;
+       return status;
 }
 
 
 /*
- * Subroutine to actually try to execute a backslash command.
+ * \a -- toggle field alignment
+ *
+ * This makes little sense but we keep it around.
  */
 static backslashResult
-exec_command(const char *cmd,
-                        PsqlScanState scan_state,
-                        PQExpBuffer query_buf)
+exec_command_a(PsqlScanState scan_state, bool active_branch)
 {
-       bool            success = true; /* indicate here if the command ran ok or
-                                                                * failed */
-       backslashResult status = PSQL_CMD_SKIP_LINE;
+       bool            success = true;
 
-       /*
-        * \a -- toggle field alignment This makes little sense but we keep it
-        * around.
-        */
-       if (strcmp(cmd, "a") == 0)
+       if (active_branch)
        {
                if (pset.popt.topt.format != PRINT_ALIGNED)
                        success = do_pset("format", "aligned", &pset.popt, pset.quiet);
@@ -206,8 +435,18 @@ exec_command(const char *cmd,
                        success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
        }
 
-       /* \C -- override table title (formerly change HTML caption) */
-       else if (strcmp(cmd, "C") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \C -- override table title (formerly change HTML caption)
+ */
+static backslashResult
+exec_command_C(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_NORMAL, NULL, true);
@@ -215,44 +454,85 @@ exec_command(const char *cmd,
                success = do_pset("title", opt, &pset.popt, pset.quiet);
                free(opt);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /*
-        * \c or \connect -- connect to database using the specified parameters.
-        *
-        * \c dbname user host port
-        *
-        * If any of these parameters are omitted or specified as '-', the current
-        * value of the parameter will be used instead. If the parameter has no
-        * current value, the default value for that parameter will be used. Some
-        * examples:
-        *
-        * \c - - hst           Connect to current database on current port of host
-        * "hst" as current user. \c - usr - prt   Connect to current database on
-        * "prt" port of current host as user "usr". \c dbs                       Connect to
-        * "dbs" database on current port of current host as current user.
-        */
-       else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c [-reuse-previous=BOOL] dbname user host port
+ *
+ * Specifying a parameter as '-' is equivalent to omitting it.  Examples:
+ *
+ * \c - - hst          Connect to current database on current port of
+ *                                     host "hst" as current user.
+ * \c - usr - prt      Connect to current database on port "prt" of current host
+ *                                     as user "usr".
+ * \c dbs                      Connect to database "dbs" on current port of current host
+ *                                     as current user.
+ */
+static backslashResult
+exec_command_connect(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
+               static const char prefix[] = "-reuse-previous=";
                char       *opt1,
                                   *opt2,
                                   *opt3,
                                   *opt4;
+               enum trivalue reuse_previous = TRI_DEFAULT;
 
                opt1 = read_connect_arg(scan_state);
-               opt2 = read_connect_arg(scan_state);
-               opt3 = read_connect_arg(scan_state);
-               opt4 = read_connect_arg(scan_state);
+               if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
+               {
+                       bool            on_off;
+
+                       success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
+                                                                               "-reuse-previous",
+                                                                               &on_off);
+                       if (success)
+                       {
+                               reuse_previous = on_off ? TRI_YES : TRI_NO;
+                               free(opt1);
+                               opt1 = read_connect_arg(scan_state);
+                       }
+               }
 
-               success = do_connect(opt1, opt2, opt3, opt4);
+               if (success)                    /* give up if reuse_previous was invalid */
+               {
+                       opt2 = read_connect_arg(scan_state);
+                       opt3 = read_connect_arg(scan_state);
+                       opt4 = read_connect_arg(scan_state);
+
+                       success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
 
+                       free(opt2);
+                       free(opt3);
+                       free(opt4);
+               }
                free(opt1);
-               free(opt2);
-               free(opt3);
-               free(opt4);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \cd */
-       else if (strcmp(cmd, "cd") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \cd -- change directory
+ */
+static backslashResult
+exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_NORMAL, NULL, true);
@@ -270,9 +550,9 @@ exec_command(const char *cmd,
                        pw = getpwuid(user_id);
                        if (!pw)
                        {
-                               psql_error("could not get home directory for user ID %ld: %s\n",
+                               pg_log_error("could not get home directory for user ID %ld: %s",
                                                   (long) user_id,
-                                                errno ? strerror(errno) : _("user does not exist"));
+                                                  errno ? strerror(errno) : _("user does not exist"));
                                exit(EXIT_FAILURE);
                        }
                        dir = pw->pw_dir;
@@ -283,45 +563,81 @@ exec_command(const char *cmd,
                         * directory, so if someone wants to code this here instead...
                         */
                        dir = "/";
-#endif   /* WIN32 */
+#endif                                                 /* WIN32 */
                }
 
                if (chdir(dir) == -1)
                {
-                       psql_error("\\%s: could not change directory to \"%s\": %s\n",
-                                          cmd, dir, strerror(errno));
+                       pg_log_error("\\%s: could not change directory to \"%s\": %m",
+                                                cmd, dir);
                        success = false;
                }
 
                if (opt)
                        free(opt);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \conninfo -- display information about the current connection */
-       else if (strcmp(cmd, "conninfo") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \conninfo -- display information about the current connection
+ */
+static backslashResult
+exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
+{
+       if (active_branch)
        {
                char       *db = PQdb(pset.db);
-               char       *host = (PQhostaddr(pset.db) != NULL) ? PQhostaddr(pset.db) : PQhost(pset.db);
 
                if (db == NULL)
                        printf(_("You are currently not connected to a database.\n"));
                else
                {
-                       if (host == NULL)
-                               host = DEFAULT_PGSOCKET_DIR;
-                       /* If the host is an absolute path, the connection is via socket */
+                       char       *host = PQhost(pset.db);
+                       char       *hostaddr = PQhostaddr(pset.db);
+
+                       /*
+                        * If the host is an absolute path, the connection is via socket
+                        * unless overridden by hostaddr
+                        */
                        if (is_absolute_path(host))
-                               printf(_("You are connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
-                                          db, PQuser(pset.db), host, PQport(pset.db));
+                       {
+                               if (hostaddr && *hostaddr)
+                                       printf(_("You are connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"),
+                                                  db, PQuser(pset.db), hostaddr, PQport(pset.db));
+                               else
+                                       printf(_("You are connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
+                                                  db, PQuser(pset.db), host, PQport(pset.db));
+                       }
                        else
-                               printf(_("You are connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
-                                          db, PQuser(pset.db), host, PQport(pset.db));
+                       {
+                               if (hostaddr && *hostaddr && strcmp(host, hostaddr) != 0)
+                                       printf(_("You are connected to database \"%s\" as user \"%s\" on host \"%s\" (address \"%s\") at port \"%s\".\n"),
+                                                  db, PQuser(pset.db), host, hostaddr, PQport(pset.db));
+                               else
+                                       printf(_("You are connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
+                                                  db, PQuser(pset.db), host, PQport(pset.db));
+                       }
                        printSSLInfo();
+                       printGSSInfo();
                }
        }
 
-       /* \copy */
-       else if (pg_strcasecmp(cmd, "copy") == 0)
+       return PSQL_CMD_SKIP_LINE;
+}
+
+/*
+ * \copy -- run a COPY command
+ */
+static backslashResult
+exec_command_copy(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_WHOLE_LINE, NULL, false);
@@ -329,13 +645,58 @@ exec_command(const char *cmd,
                success = do_copy(opt);
                free(opt);
        }
+       else
+               ignore_slash_whole_line(scan_state);
 
-       /* \copyright */
-       else if (strcmp(cmd, "copyright") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \copyright -- print copyright notice
+ */
+static backslashResult
+exec_command_copyright(PsqlScanState scan_state, bool active_branch)
+{
+       if (active_branch)
                print_copyright();
 
-       /* \d* commands */
-       else if (cmd[0] == 'd')
+       return PSQL_CMD_SKIP_LINE;
+}
+
+/*
+ * \crosstabview -- execute a query and display results in crosstab
+ */
+static backslashResult
+exec_command_crosstabview(PsqlScanState scan_state, bool active_branch)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+
+       if (active_branch)
+       {
+               int                     i;
+
+               for (i = 0; i < lengthof(pset.ctv_args); i++)
+                       pset.ctv_args[i] = psql_scan_slash_option(scan_state,
+                                                                                                         OT_NORMAL, NULL, true);
+               pset.crosstab_flag = true;
+               status = PSQL_CMD_SEND;
+       }
+       else
+               ignore_slash_options(scan_state);
+
+       return status;
+}
+
+/*
+ * \d* commands
+ */
+static backslashResult
+exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *pattern;
                bool            show_verbose,
@@ -359,6 +720,9 @@ exec_command(const char *cmd,
                                        /* standard listing of interesting things */
                                        success = listTables("tvmsE", NULL, show_verbose, show_system);
                                break;
+                       case 'A':
+                               success = describeAccessMethods(pattern, show_verbose);
+                               break;
                        case 'a':
                                success = describeAggregates(pattern, show_verbose, show_system);
                                break;
@@ -388,6 +752,7 @@ exec_command(const char *cmd,
                                        case 'S':
                                        case 'a':
                                        case 'n':
+                                       case 'p':
                                        case 't':
                                        case 'w':
                                                success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
@@ -399,7 +764,7 @@ exec_command(const char *cmd,
                                break;
                        case 'g':
                                /* no longer distinct from \du */
-                               success = describeRoles(pattern, show_verbose);
+                               success = describeRoles(pattern, show_verbose, show_system);
                                break;
                        case 'l':
                                success = do_lo_list();
@@ -419,6 +784,23 @@ exec_command(const char *cmd,
                        case 'p':
                                success = permissionsList(pattern);
                                break;
+                       case 'P':
+                               {
+                                       switch (cmd[2])
+                                       {
+                                               case '\0':
+                                               case '+':
+                                               case 't':
+                                               case 'i':
+                                               case 'n':
+                                                       success = listPartitionedTables(&cmd[2], pattern, show_verbose);
+                                                       break;
+                                               default:
+                                                       status = PSQL_CMD_UNKNOWN;
+                                                       break;
+                                       }
+                               }
+                               break;
                        case 'T':
                                success = describeTypes(pattern, show_verbose, show_system);
                                break;
@@ -437,14 +819,33 @@ exec_command(const char *cmd,
 
                                        if (pattern)
                                                pattern2 = psql_scan_slash_option(scan_state,
-                                                                                                         OT_NORMAL, NULL, true);
+                                                                                                                 OT_NORMAL, NULL, true);
                                        success = listDbRoleSettings(pattern, pattern2);
+
+                                       if (pattern2)
+                                               free(pattern2);
                                }
                                else
-                                       success = PSQL_CMD_UNKNOWN;
+                                       status = PSQL_CMD_UNKNOWN;
+                               break;
+                       case 'R':
+                               switch (cmd[2])
+                               {
+                                       case 'p':
+                                               if (show_verbose)
+                                                       success = describePublications(pattern);
+                                               else
+                                                       success = listPublications(pattern);
+                                               break;
+                                       case 's':
+                                               success = describeSubscriptions(pattern, show_verbose);
+                                               break;
+                                       default:
+                                               status = PSQL_CMD_UNKNOWN;
+                               }
                                break;
                        case 'u':
-                               success = describeRoles(pattern, show_verbose);
+                               success = describeRoles(pattern, show_verbose, show_system);
                                break;
                        case 'F':                       /* text search subsystem */
                                switch (cmd[2])
@@ -503,17 +904,30 @@ exec_command(const char *cmd,
                if (pattern)
                        free(pattern);
        }
+       else
+               ignore_slash_options(scan_state);
 
+       if (!success)
+               status = PSQL_CMD_ERROR;
 
-       /*
-        * \e or \edit -- edit the current query buffer, or edit a file and make
-        * it the query buffer
-        */
-       else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+       return status;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and
+ * make it the query buffer
+ */
+static backslashResult
+exec_command_edit(PsqlScanState scan_state, bool active_branch,
+                                 PQExpBuffer query_buf, PQExpBuffer previous_buf)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+
+       if (active_branch)
        {
                if (!query_buf)
                {
-                       psql_error("no query buffer\n");
+                       pg_log_error("no query buffer");
                        status = PSQL_CMD_ERROR;
                }
                else
@@ -546,7 +960,7 @@ exec_command(const char *cmd,
                                lineno = atoi(ln);
                                if (lineno < 1)
                                {
-                                       psql_error("invalid line number: %s\n", ln);
+                                       pg_log_error("invalid line number: %s", ln);
                                        status = PSQL_CMD_ERROR;
                                }
                        }
@@ -555,6 +969,10 @@ exec_command(const char *cmd,
                                expand_tilde(&fname);
                                if (fname)
                                        canonicalize_path(fname);
+
+                               /* If query_buf is empty, recall previous query for editing */
+                               copy_previous_query(query_buf, previous_buf);
+
                                if (do_edit(fname, query_buf, lineno, NULL))
                                        status = PSQL_CMD_NEWEDIT;
                                else
@@ -566,61 +984,88 @@ exec_command(const char *cmd,
                                free(ln);
                }
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /*
-        * \ef -- edit the named function, or present a blank CREATE FUNCTION
-        * template if no argument is given
-        */
-       else if (strcmp(cmd, "ef") == 0)
+       return status;
+}
+
+/*
+ * \ef/\ev -- edit the named function/view, or
+ * present a blank CREATE FUNCTION/VIEW template if no argument is given
+ */
+static backslashResult
+exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
+                                  PQExpBuffer query_buf, bool is_func)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+
+       if (active_branch)
        {
+               char       *obj_desc = psql_scan_slash_option(scan_state,
+                                                                                                         OT_WHOLE_LINE,
+                                                                                                         NULL, true);
                int                     lineno = -1;
 
-               if (pset.sversion < 80400)
+               if (pset.sversion < (is_func ? 80400 : 70400))
                {
-                       psql_error("The server (version %d.%d) does not support editing function source.\n",
-                                          pset.sversion / 10000, (pset.sversion / 100) % 100);
+                       char            sverbuf[32];
+
+                       formatPGVersionNumber(pset.sversion, false,
+                                                                 sverbuf, sizeof(sverbuf));
+                       if (is_func)
+                               pg_log_error("The server (version %s) does not support editing function source.",
+                                                  sverbuf);
+                       else
+                               pg_log_error("The server (version %s) does not support editing view definitions.",
+                                                  sverbuf);
                        status = PSQL_CMD_ERROR;
                }
                else if (!query_buf)
                {
-                       psql_error("no query buffer\n");
+                       pg_log_error("no query buffer");
                        status = PSQL_CMD_ERROR;
                }
                else
                {
-                       char       *func;
-                       Oid                     foid = InvalidOid;
+                       Oid                     obj_oid = InvalidOid;
+                       EditableObjectType eot = is_func ? EditableFunction : EditableView;
 
-                       func = psql_scan_slash_option(scan_state,
-                                                                                 OT_WHOLE_LINE, NULL, true);
-                       lineno = strip_lineno_from_funcdesc(func);
+                       lineno = strip_lineno_from_objdesc(obj_desc);
                        if (lineno == 0)
                        {
                                /* error already reported */
                                status = PSQL_CMD_ERROR;
                        }
-                       else if (!func)
+                       else if (!obj_desc)
                        {
                                /* set up an empty command to fill in */
-                               printfPQExpBuffer(query_buf,
-                                                                 "CREATE FUNCTION ( )\n"
-                                                                 " RETURNS \n"
-                                                                 " LANGUAGE \n"
-                                                                 " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
-                                                                 "AS $function$\n"
-                                                                 "\n$function$\n");
+                               resetPQExpBuffer(query_buf);
+                               if (is_func)
+                                       appendPQExpBufferStr(query_buf,
+                                                                                "CREATE FUNCTION ( )\n"
+                                                                                " RETURNS \n"
+                                                                                " LANGUAGE \n"
+                                                                                " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
+                                                                                "AS $function$\n"
+                                                                                "\n$function$\n");
+                               else
+                                       appendPQExpBufferStr(query_buf,
+                                                                                "CREATE VIEW  AS\n"
+                                                                                " SELECT \n"
+                                                                                "  -- something...\n");
                        }
-                       else if (!lookup_function_oid(pset.db, func, &foid))
+                       else if (!lookup_object_oid(eot, obj_desc, &obj_oid))
                        {
                                /* error already reported */
                                status = PSQL_CMD_ERROR;
                        }
-                       else if (!get_create_function_cmd(pset.db, foid, query_buf))
+                       else if (!get_create_object_cmd(eot, obj_oid, query_buf))
                        {
                                /* error already reported */
                                status = PSQL_CMD_ERROR;
                        }
-                       else if (lineno > 0)
+                       else if (is_func && lineno > 0)
                        {
                                /*
                                 * lineno "1" should correspond to the first line of the
@@ -645,9 +1090,6 @@ exec_command(const char *cmd,
                                        lines++;
                                }
                        }
-
-                       if (func)
-                               free(func);
                }
 
                if (status != PSQL_CMD_ERROR)
@@ -661,10 +1103,23 @@ exec_command(const char *cmd,
                        else
                                status = PSQL_CMD_NEWEDIT;
                }
-       }
 
-       /* \echo and \qecho */
-       else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
+               if (obj_desc)
+                       free(obj_desc);
+       }
+       else
+               ignore_slash_whole_line(scan_state);
+
+       return status;
+}
+
+/*
+ * \echo and \qecho -- echo arguments to stdout or query output
+ */
+static backslashResult
+exec_command_echo(PsqlScanState scan_state, bool active_branch, const char *cmd)
+{
+       if (active_branch)
        {
                char       *value;
                char            quoted;
@@ -695,9 +1150,19 @@ exec_command(const char *cmd,
                if (!no_newline)
                        fputs("\n", fout);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \encoding -- set/show client side encoding */
-       else if (strcmp(cmd, "encoding") == 0)
+       return PSQL_CMD_SKIP_LINE;
+}
+
+/*
+ * \encoding -- set/show client side encoding
+ */
+static backslashResult
+exec_command_encoding(PsqlScanState scan_state, bool active_branch)
+{
+       if (active_branch)
        {
                char       *encoding = psql_scan_slash_option(scan_state,
                                                                                                          OT_NORMAL, NULL, false);
@@ -711,7 +1176,7 @@ exec_command(const char *cmd,
                {
                        /* set encoding */
                        if (PQsetClientEncoding(pset.db, encoding) == -1)
-                               psql_error("%s: invalid encoding name or conversion procedure not found\n", encoding);
+                               pg_log_error("%s: invalid encoding name or conversion procedure not found", encoding);
                        else
                        {
                                /* save encoding info into psql internal data */
@@ -723,9 +1188,51 @@ exec_command(const char *cmd,
                        free(encoding);
                }
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \f -- change field separator */
-       else if (strcmp(cmd, "f") == 0)
+       return PSQL_CMD_SKIP_LINE;
+}
+
+/*
+ * \errverbose -- display verbose message from last failed query
+ */
+static backslashResult
+exec_command_errverbose(PsqlScanState scan_state, bool active_branch)
+{
+       if (active_branch)
+       {
+               if (pset.last_error_result)
+               {
+                       char       *msg;
+
+                       msg = PQresultVerboseErrorMessage(pset.last_error_result,
+                                                                                         PQERRORS_VERBOSE,
+                                                                                         PQSHOW_CONTEXT_ALWAYS);
+                       if (msg)
+                       {
+                               pg_log_error("%s", msg);
+                               PQfreemem(msg);
+                       }
+                       else
+                               puts(_("out of memory"));
+               }
+               else
+                       puts(_("There is no previous error."));
+       }
+
+       return PSQL_CMD_SKIP_LINE;
+}
+
+/*
+ * \f -- change field separator
+ */
+static backslashResult
+exec_command_f(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *fname = psql_scan_slash_option(scan_state,
                                                                                                   OT_NORMAL, NULL, false);
@@ -733,9 +1240,22 @@ exec_command(const char *cmd,
                success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
                free(fname);
        }
+       else
+               ignore_slash_options(scan_state);
+
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \g [filename] -- send query, optionally with output to file/pipe
+ * \gx [filename] -- same as \g, with expanded mode forced
+ */
+static backslashResult
+exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
 
-       /* \g [filename] -- send query, optionally with output to file/pipe */
-       else if (strcmp(cmd, "g") == 0)
+       if (active_branch)
        {
                char       *fname = psql_scan_slash_option(scan_state,
                                                                                                   OT_FILEPIPE, NULL, false);
@@ -748,11 +1268,59 @@ exec_command(const char *cmd,
                        pset.gfname = pg_strdup(fname);
                }
                free(fname);
+               if (strcmp(cmd, "gx") == 0)
+                       pset.g_expanded = true;
                status = PSQL_CMD_SEND;
        }
+       else
+               ignore_slash_filepipe(scan_state);
 
-       /* \gset [prefix] -- send query and store result into variables */
-       else if (strcmp(cmd, "gset") == 0)
+       return status;
+}
+
+/*
+ * \gdesc -- describe query result
+ */
+static backslashResult
+exec_command_gdesc(PsqlScanState scan_state, bool active_branch)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+
+       if (active_branch)
+       {
+               pset.gdesc_flag = true;
+               status = PSQL_CMD_SEND;
+       }
+
+       return status;
+}
+
+/*
+ * \gexec -- send query and execute each field of result
+ */
+static backslashResult
+exec_command_gexec(PsqlScanState scan_state, bool active_branch)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+
+       if (active_branch)
+       {
+               pset.gexec_flag = true;
+               status = PSQL_CMD_SEND;
+       }
+
+       return status;
+}
+
+/*
+ * \gset [prefix] -- send query and store result into variables
+ */
+static backslashResult
+exec_command_gset(PsqlScanState scan_state, bool active_branch)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+
+       if (active_branch)
        {
                char       *prefix = psql_scan_slash_option(scan_state,
                                                                                                        OT_NORMAL, NULL, false);
@@ -767,9 +1335,19 @@ exec_command(const char *cmd,
                /* gset_prefix is freed later */
                status = PSQL_CMD_SEND;
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* help */
-       else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+       return status;
+}
+
+/*
+ * \help [topic] -- print help about SQL commands
+ */
+static backslashResult
+exec_command_help(PsqlScanState scan_state, bool active_branch)
+{
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_WHOLE_LINE, NULL, false);
@@ -788,9 +1366,21 @@ exec_command(const char *cmd,
                helpSQL(opt, pset.popt.topt.pager);
                free(opt);
        }
+       else
+               ignore_slash_whole_line(scan_state);
 
-       /* HTML mode */
-       else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+       return PSQL_CMD_SKIP_LINE;
+}
+
+/*
+ * \H and \html -- toggle HTML formatting
+ */
+static backslashResult
+exec_command_html(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                if (pset.popt.topt.format != PRINT_HTML)
                        success = do_pset("format", "html", &pset.popt, pset.quiet);
@@ -798,17 +1388,25 @@ exec_command(const char *cmd,
                        success = do_pset("format", "aligned", &pset.popt, pset.quiet);
        }
 
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \i and \ir -- include a file
+ */
+static backslashResult
+exec_command_include(PsqlScanState scan_state, bool active_branch, const char *cmd)
+{
+       bool            success = true;
 
-       /* \i and \ir include files */
-       else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
-                  || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
+       if (active_branch)
        {
                char       *fname = psql_scan_slash_option(scan_state,
                                                                                                   OT_NORMAL, NULL, true);
 
                if (!fname)
                {
-                       psql_error("\\%s: missing required argument\n", cmd);
+                       pg_log_error("\\%s: missing required argument", cmd);
                        success = false;
                }
                else
@@ -818,14 +1416,258 @@ exec_command(const char *cmd,
                        include_relative = (strcmp(cmd, "ir") == 0
                                                                || strcmp(cmd, "include_relative") == 0);
                        expand_tilde(&fname);
-                       success = (process_file(fname, false, include_relative) == EXIT_SUCCESS);
+                       success = (process_file(fname, include_relative) == EXIT_SUCCESS);
                        free(fname);
                }
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \l is list databases */
-       else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
-                        strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \if <expr> -- beginning of an \if..\endif block
+ *
+ * <expr> is parsed as a boolean expression.  Invalid expressions will emit a
+ * warning and be treated as false.  Statements that follow a false expression
+ * will be parsed but ignored.  Note that in the case where an \if statement
+ * is itself within an inactive section of a block, then the entire inner
+ * \if..\endif block will be parsed but ignored.
+ */
+static backslashResult
+exec_command_if(PsqlScanState scan_state, ConditionalStack cstack,
+                               PQExpBuffer query_buf)
+{
+       if (conditional_active(cstack))
+       {
+               /*
+                * First, push a new active stack entry; this ensures that the lexer
+                * will perform variable substitution and backtick evaluation while
+                * scanning the expression.  (That should happen anyway, since we know
+                * we're in an active outer branch, but let's be sure.)
+                */
+               conditional_stack_push(cstack, IFSTATE_TRUE);
+
+               /* Remember current query state in case we need to restore later */
+               save_query_text_state(scan_state, cstack, query_buf);
+
+               /*
+                * Evaluate the expression; if it's false, change to inactive state.
+                */
+               if (!is_true_boolean_expression(scan_state, "\\if expression"))
+                       conditional_stack_poke(cstack, IFSTATE_FALSE);
+       }
+       else
+       {
+               /*
+                * We're within an inactive outer branch, so this entire \if block
+                * will be ignored.  We don't want to evaluate the expression, so push
+                * the "ignored" stack state before scanning it.
+                */
+               conditional_stack_push(cstack, IFSTATE_IGNORED);
+
+               /* Remember current query state in case we need to restore later */
+               save_query_text_state(scan_state, cstack, query_buf);
+
+               ignore_boolean_expression(scan_state);
+       }
+
+       return PSQL_CMD_SKIP_LINE;
+}
+
+/*
+ * \elif <expr> -- alternative branch in an \if..\endif block
+ *
+ * <expr> is evaluated the same as in \if <expr>.
+ */
+static backslashResult
+exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack,
+                                 PQExpBuffer query_buf)
+{
+       bool            success = true;
+
+       switch (conditional_stack_peek(cstack))
+       {
+               case IFSTATE_TRUE:
+
+                       /*
+                        * Just finished active branch of this \if block.  Update saved
+                        * state so we will keep whatever data was put in query_buf by the
+                        * active branch.
+                        */
+                       save_query_text_state(scan_state, cstack, query_buf);
+
+                       /*
+                        * Discard \elif expression and ignore the rest until \endif.
+                        * Switch state before reading expression to ensure proper lexer
+                        * behavior.
+                        */
+                       conditional_stack_poke(cstack, IFSTATE_IGNORED);
+                       ignore_boolean_expression(scan_state);
+                       break;
+               case IFSTATE_FALSE:
+
+                       /*
+                        * Discard any query text added by the just-skipped branch.
+                        */
+                       discard_query_text(scan_state, cstack, query_buf);
+
+                       /*
+                        * Have not yet found a true expression in this \if block, so this
+                        * might be the first.  We have to change state before examining
+                        * the expression, or the lexer won't do the right thing.
+                        */
+                       conditional_stack_poke(cstack, IFSTATE_TRUE);
+                       if (!is_true_boolean_expression(scan_state, "\\elif expression"))
+                               conditional_stack_poke(cstack, IFSTATE_FALSE);
+                       break;
+               case IFSTATE_IGNORED:
+
+                       /*
+                        * Discard any query text added by the just-skipped branch.
+                        */
+                       discard_query_text(scan_state, cstack, query_buf);
+
+                       /*
+                        * Skip expression and move on.  Either the \if block already had
+                        * an active section, or whole block is being skipped.
+                        */
+                       ignore_boolean_expression(scan_state);
+                       break;
+               case IFSTATE_ELSE_TRUE:
+               case IFSTATE_ELSE_FALSE:
+                       pg_log_error("\\elif: cannot occur after \\else");
+                       success = false;
+                       break;
+               case IFSTATE_NONE:
+                       /* no \if to elif from */
+                       pg_log_error("\\elif: no matching \\if");
+                       success = false;
+                       break;
+       }
+
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \else -- final alternative in an \if..\endif block
+ *
+ * Statements within an \else branch will only be executed if
+ * all previous \if and \elif expressions evaluated to false
+ * and the block was not itself being ignored.
+ */
+static backslashResult
+exec_command_else(PsqlScanState scan_state, ConditionalStack cstack,
+                                 PQExpBuffer query_buf)
+{
+       bool            success = true;
+
+       switch (conditional_stack_peek(cstack))
+       {
+               case IFSTATE_TRUE:
+
+                       /*
+                        * Just finished active branch of this \if block.  Update saved
+                        * state so we will keep whatever data was put in query_buf by the
+                        * active branch.
+                        */
+                       save_query_text_state(scan_state, cstack, query_buf);
+
+                       /* Now skip the \else branch */
+                       conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE);
+                       break;
+               case IFSTATE_FALSE:
+
+                       /*
+                        * Discard any query text added by the just-skipped branch.
+                        */
+                       discard_query_text(scan_state, cstack, query_buf);
+
+                       /*
+                        * We've not found any true \if or \elif expression, so execute
+                        * the \else branch.
+                        */
+                       conditional_stack_poke(cstack, IFSTATE_ELSE_TRUE);
+                       break;
+               case IFSTATE_IGNORED:
+
+                       /*
+                        * Discard any query text added by the just-skipped branch.
+                        */
+                       discard_query_text(scan_state, cstack, query_buf);
+
+                       /*
+                        * Either we previously processed the active branch of this \if,
+                        * or the whole \if block is being skipped.  Either way, skip the
+                        * \else branch.
+                        */
+                       conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE);
+                       break;
+               case IFSTATE_ELSE_TRUE:
+               case IFSTATE_ELSE_FALSE:
+                       pg_log_error("\\else: cannot occur after \\else");
+                       success = false;
+                       break;
+               case IFSTATE_NONE:
+                       /* no \if to else from */
+                       pg_log_error("\\else: no matching \\if");
+                       success = false;
+                       break;
+       }
+
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \endif -- ends an \if...\endif block
+ */
+static backslashResult
+exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack,
+                                  PQExpBuffer query_buf)
+{
+       bool            success = true;
+
+       switch (conditional_stack_peek(cstack))
+       {
+               case IFSTATE_TRUE:
+               case IFSTATE_ELSE_TRUE:
+                       /* Close the \if block, keeping the query text */
+                       success = conditional_stack_pop(cstack);
+                       Assert(success);
+                       break;
+               case IFSTATE_FALSE:
+               case IFSTATE_IGNORED:
+               case IFSTATE_ELSE_FALSE:
+
+                       /*
+                        * Discard any query text added by the just-skipped branch.
+                        */
+                       discard_query_text(scan_state, cstack, query_buf);
+
+                       /* Close the \if block */
+                       success = conditional_stack_pop(cstack);
+                       Assert(success);
+                       break;
+               case IFSTATE_NONE:
+                       /* no \if to end */
+                       pg_log_error("\\endif: no matching \\if");
+                       success = false;
+                       break;
+       }
+
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \l -- list databases
+ */
+static backslashResult
+exec_command_list(PsqlScanState scan_state, bool active_branch, const char *cmd)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *pattern;
                bool            show_verbose;
@@ -840,11 +1682,22 @@ exec_command(const char *cmd,
                if (pattern)
                        free(pattern);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /*
-        * large object things
-        */
-       else if (strncmp(cmd, "lo_", 3) == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \lo_* -- large object operations
+ */
+static backslashResult
+exec_command_lo(PsqlScanState scan_state, bool active_branch, const char *cmd)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt1,
                                   *opt2;
@@ -858,7 +1711,7 @@ exec_command(const char *cmd,
                {
                        if (!opt2)
                        {
-                               psql_error("\\%s: missing required argument\n", cmd);
+                               pg_log_error("\\%s: missing required argument", cmd);
                                success = false;
                        }
                        else
@@ -872,7 +1725,7 @@ exec_command(const char *cmd,
                {
                        if (!opt1)
                        {
-                               psql_error("\\%s: missing required argument\n", cmd);
+                               pg_log_error("\\%s: missing required argument", cmd);
                                success = false;
                        }
                        else
@@ -889,7 +1742,7 @@ exec_command(const char *cmd,
                {
                        if (!opt1)
                        {
-                               psql_error("\\%s: missing required argument\n", cmd);
+                               pg_log_error("\\%s: missing required argument", cmd);
                                success = false;
                        }
                        else
@@ -902,10 +1755,24 @@ exec_command(const char *cmd,
                free(opt1);
                free(opt2);
        }
+       else
+               ignore_slash_options(scan_state);
 
+       if (!success)
+               status = PSQL_CMD_ERROR;
 
-       /* \o -- set query output */
-       else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+       return status;
+}
+
+/*
+ * \o -- set query output
+ */
+static backslashResult
+exec_command_out(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *fname = psql_scan_slash_option(scan_state,
                                                                                                   OT_FILEPIPE, NULL, true);
@@ -914,34 +1781,63 @@ exec_command(const char *cmd,
                success = setQFout(fname);
                free(fname);
        }
+       else
+               ignore_slash_filepipe(scan_state);
 
-       /* \p prints the current query buffer */
-       else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \p -- print the current query buffer
+ */
+static backslashResult
+exec_command_print(PsqlScanState scan_state, bool active_branch,
+                                  PQExpBuffer query_buf, PQExpBuffer previous_buf)
+{
+       if (active_branch)
        {
+               /*
+                * We want to print the same thing \g would execute, but not to change
+                * the query buffer state; so we can't use copy_previous_query().
+                * Also, beware of possibility that buffer pointers are NULL.
+                */
                if (query_buf && query_buf->len > 0)
                        puts(query_buf->data);
+               else if (previous_buf && previous_buf->len > 0)
+                       puts(previous_buf->data);
                else if (!pset.quiet)
                        puts(_("Query buffer is empty."));
                fflush(stdout);
        }
 
-       /* \password -- set user password */
-       else if (strcmp(cmd, "password") == 0)
+       return PSQL_CMD_SKIP_LINE;
+}
+
+/*
+ * \password -- set user password
+ */
+static backslashResult
+exec_command_password(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
-               char       *pw1;
-               char       *pw2;
+               char       *opt0 = psql_scan_slash_option(scan_state,
+                                                                                                 OT_SQLID, NULL, true);
+               char            pw1[100];
+               char            pw2[100];
 
-               pw1 = simple_prompt("Enter new password: ", 100, false);
-               pw2 = simple_prompt("Enter it again: ", 100, false);
+               simple_prompt("Enter new password: ", pw1, sizeof(pw1), false);
+               simple_prompt("Enter it again: ", pw2, sizeof(pw2), false);
 
                if (strcmp(pw1, pw2) != 0)
                {
-                       psql_error("Passwords didn't match.\n");
+                       pg_log_error("Passwords didn't match.");
                        success = false;
                }
                else
                {
-                       char       *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true);
                        char       *user;
                        char       *encrypted_password;
 
@@ -950,11 +1846,11 @@ exec_command(const char *cmd,
                        else
                                user = PQuser(pset.db);
 
-                       encrypted_password = PQencryptPassword(pw1, user);
+                       encrypted_password = PQencryptPasswordConn(pset.db, pw1, user, NULL);
 
                        if (!encrypted_password)
                        {
-                               psql_error("Password encryption failed.\n");
+                               pg_log_info("%s", PQerrorMessage(pset.db));
                                success = false;
                        }
                        else
@@ -966,7 +1862,7 @@ exec_command(const char *cmd,
                                printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ",
                                                                  fmtId(user));
                                appendStringLiteralConn(&buf, encrypted_password, pset.db);
-                               res = PSQLexec(buf.data, false);
+                               res = PSQLexec(buf.data);
                                termPQExpBuffer(&buf);
                                if (!res)
                                        success = false;
@@ -974,17 +1870,27 @@ exec_command(const char *cmd,
                                        PQclear(res);
                                PQfreemem(encrypted_password);
                        }
-
-                       if (opt0)
-                               free(opt0);
                }
 
-               free(pw1);
-               free(pw2);
+               if (opt0)
+                       free(opt0);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \prompt -- prompt and set variable */
-       else if (strcmp(cmd, "prompt") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \prompt -- prompt and set variable
+ */
+static backslashResult
+exec_command_prompt(PsqlScanState scan_state, bool active_branch,
+                                       const char *cmd)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt,
                                   *prompt_text = NULL;
@@ -996,7 +1902,7 @@ exec_command(const char *cmd,
 
                if (!arg1)
                {
-                       psql_error("\\%s: missing required argument\n", cmd);
+                       pg_log_error("\\%s: missing required argument", cmd);
                        success = false;
                }
                else
@@ -1012,7 +1918,10 @@ exec_command(const char *cmd,
                                opt = arg1;
 
                        if (!pset.inputfile)
-                               result = simple_prompt(prompt_text, 4096, true);
+                       {
+                               result = (char *) pg_malloc(4096);
+                               simple_prompt(prompt_text, result, 4096, true);
+                       }
                        else
                        {
                                if (prompt_text)
@@ -1021,23 +1930,40 @@ exec_command(const char *cmd,
                                        fflush(stdout);
                                }
                                result = gets_fromFile(stdin);
+                               if (!result)
+                               {
+                                       pg_log_error("\\%s: could not read value for variable",
+                                                          cmd);
+                                       success = false;
+                               }
                        }
 
-                       if (!SetVariable(pset.vars, opt, result))
-                       {
-                               psql_error("\\%s: error while setting variable\n", cmd);
+                       if (result &&
+                               !SetVariable(pset.vars, opt, result))
                                success = false;
-                       }
 
-                       free(result);
+                       if (result)
+                               free(result);
                        if (prompt_text)
                                free(prompt_text);
                        free(opt);
                }
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \pset -- set printing parameters */
-       else if (strcmp(cmd, "pset") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \pset -- set printing parameters
+ */
+static backslashResult
+exec_command_pset(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt0 = psql_scan_slash_option(scan_state,
                                                                                                  OT_NORMAL, NULL, false);
@@ -1050,15 +1976,24 @@ exec_command(const char *cmd,
 
                        int                     i;
                        static const char *const my_list[] = {
-                               "border", "columns", "expanded", "fieldsep",
-                               "footer", "format", "linestyle", "null",
-                               "numericlocale", "pager", "recordsep",
+                               "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+                               "fieldsep_zero", "footer", "format", "linestyle", "null",
+                               "numericlocale", "pager", "pager_min_lines",
+                               "recordsep", "recordsep_zero",
                                "tableattr", "title", "tuples_only",
+                               "unicode_border_linestyle",
+                               "unicode_column_linestyle",
+                               "unicode_header_linestyle",
                                NULL
                        };
 
                        for (i = 0; my_list[i] != NULL; i++)
-                               printPsetInfo(my_list[i], &pset.popt);
+                       {
+                               char       *val = pset_value_string(my_list[i], &pset.popt);
+
+                               printf("%-24s %s\n", my_list[i], val);
+                               free(val);
+                       }
 
                        success = true;
                }
@@ -1068,13 +2003,34 @@ exec_command(const char *cmd,
                free(opt0);
                free(opt1);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \q or \quit */
-       else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \q or \quit -- exit psql
+ */
+static backslashResult
+exec_command_quit(PsqlScanState scan_state, bool active_branch)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+
+       if (active_branch)
                status = PSQL_CMD_TERMINATE;
 
-       /* reset(clear) the buffer */
-       else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
+       return status;
+}
+
+/*
+ * \r -- reset (clear) the query buffer
+ */
+static backslashResult
+exec_command_reset(PsqlScanState scan_state, bool active_branch,
+                                  PQExpBuffer query_buf)
+{
+       if (active_branch)
        {
                resetPQExpBuffer(query_buf);
                psql_scan_reset(scan_state);
@@ -1082,8 +2038,18 @@ exec_command(const char *cmd,
                        puts(_("Query buffer reset (cleared)."));
        }
 
-       /* \s save history in a file or show it on the screen */
-       else if (strcmp(cmd, "s") == 0)
+       return PSQL_CMD_SKIP_LINE;
+}
+
+/*
+ * \s -- save history in a file or show it on the screen
+ */
+static backslashResult
+exec_command_s(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *fname = psql_scan_slash_option(scan_state,
                                                                                                   OT_NORMAL, NULL, true);
@@ -1096,9 +2062,21 @@ exec_command(const char *cmd,
                        putchar('\n');
                free(fname);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \set -- generalized set variable/option command */
-       else if (strcmp(cmd, "set") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \set -- set variable
+ */
+static backslashResult
+exec_command_set(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt0 = psql_scan_slash_option(scan_state,
                                                                                                  OT_NORMAL, NULL, false);
@@ -1125,29 +2103,34 @@ exec_command(const char *cmd,
                        while ((opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_NORMAL, NULL, false)))
                        {
-                               newval = realloc(newval, strlen(newval) + strlen(opt) + 1);
-                               if (!newval)
-                               {
-                                       psql_error("out of memory\n");
-                                       exit(EXIT_FAILURE);
-                               }
+                               newval = pg_realloc(newval, strlen(newval) + strlen(opt) + 1);
                                strcat(newval, opt);
                                free(opt);
                        }
 
                        if (!SetVariable(pset.vars, opt0, newval))
-                       {
-                               psql_error("\\%s: error while setting variable\n", cmd);
                                success = false;
-                       }
+
                        free(newval);
                }
                free(opt0);
        }
+       else
+               ignore_slash_options(scan_state);
 
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
 
-       /* \setenv -- set environment command */
-       else if (strcmp(cmd, "setenv") == 0)
+/*
+ * \setenv -- set environment variable
+ */
+static backslashResult
+exec_command_setenv(PsqlScanState scan_state, bool active_branch,
+                                       const char *cmd)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *envvar = psql_scan_slash_option(scan_state,
                                                                                                        OT_NORMAL, NULL, false);
@@ -1156,12 +2139,12 @@ exec_command(const char *cmd,
 
                if (!envvar)
                {
-                       psql_error("\\%s: missing required argument\n", cmd);
+                       pg_log_error("\\%s: missing required argument", cmd);
                        success = false;
                }
                else if (strchr(envvar, '=') != NULL)
                {
-                       psql_error("\\%s: environment variable name must not contain \"=\"\n",
+                       pg_log_error("\\%s: environment variable name must not contain \"=\"",
                                           cmd);
                        success = false;
                }
@@ -1189,35 +2172,60 @@ exec_command(const char *cmd,
                free(envvar);
                free(envval);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \sf -- show a function's source code */
-       else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
-       {
-               bool            show_linenumbers = (strcmp(cmd, "sf+") == 0);
-               PQExpBuffer func_buf;
-               char       *func;
-               Oid                     foid = InvalidOid;
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
 
-               func_buf = createPQExpBuffer();
-               func = psql_scan_slash_option(scan_state,
-                                                                         OT_WHOLE_LINE, NULL, true);
-               if (pset.sversion < 80400)
+/*
+ * \sf/\sv -- show a function/view's source code
+ */
+static backslashResult
+exec_command_sf_sv(PsqlScanState scan_state, bool active_branch,
+                                  const char *cmd, bool is_func)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+
+       if (active_branch)
+       {
+               bool            show_linenumbers = (strchr(cmd, '+') != NULL);
+               PQExpBuffer buf;
+               char       *obj_desc;
+               Oid                     obj_oid = InvalidOid;
+               EditableObjectType eot = is_func ? EditableFunction : EditableView;
+
+               buf = createPQExpBuffer();
+               obj_desc = psql_scan_slash_option(scan_state,
+                                                                                 OT_WHOLE_LINE, NULL, true);
+               if (pset.sversion < (is_func ? 80400 : 70400))
                {
-                       psql_error("The server (version %d.%d) does not support showing function source.\n",
-                                          pset.sversion / 10000, (pset.sversion / 100) % 100);
+                       char            sverbuf[32];
+
+                       formatPGVersionNumber(pset.sversion, false,
+                                                                 sverbuf, sizeof(sverbuf));
+                       if (is_func)
+                               pg_log_error("The server (version %s) does not support showing function source.",
+                                                  sverbuf);
+                       else
+                               pg_log_error("The server (version %s) does not support showing view definitions.",
+                                                  sverbuf);
                        status = PSQL_CMD_ERROR;
                }
-               else if (!func)
+               else if (!obj_desc)
                {
-                       psql_error("function name is required\n");
+                       if (is_func)
+                               pg_log_error("function name is required");
+                       else
+                               pg_log_error("view name is required");
                        status = PSQL_CMD_ERROR;
                }
-               else if (!lookup_function_oid(pset.db, func, &foid))
+               else if (!lookup_object_oid(eot, obj_desc, &obj_oid))
                {
                        /* error already reported */
                        status = PSQL_CMD_ERROR;
                }
-               else if (!get_create_function_cmd(pset.db, foid, func_buf))
+               else if (!get_create_object_cmd(eot, obj_oid, buf))
                {
                        /* error already reported */
                        status = PSQL_CMD_ERROR;
@@ -1231,20 +2239,9 @@ exec_command(const char *cmd,
                        if (pset.queryFout == stdout)
                        {
                                /* count lines in function to see if pager is needed */
-                               int                     lineno = 0;
-                               const char *lines = func_buf->data;
-
-                               while (*lines != '\0')
-                               {
-                                       lineno++;
-                                       /* find start of next line */
-                                       lines = strchr(lines, '\n');
-                                       if (!lines)
-                                               break;
-                                       lines++;
-                               }
+                               int                     lineno = count_lines_in_buf(buf);
 
-                               output = PageOutput(lineno, pset.popt.topt.pager);
+                               output = PageOutput(lineno, &(pset.popt.topt));
                                is_pager = true;
                        }
                        else
@@ -1256,63 +2253,45 @@ exec_command(const char *cmd,
 
                        if (show_linenumbers)
                        {
-                               bool            in_header = true;
-                               int                     lineno = 0;
-                               char       *lines = func_buf->data;
-
                                /*
-                                * lineno "1" should correspond to the first line of the
-                                * function body.  We expect that pg_get_functiondef() will
-                                * emit that on a line beginning with "AS ", and that there
-                                * can be no such line before the real start of the function
-                                * body.
-                                *
-                                * Note that this loop scribbles on func_buf.
+                                * For functions, lineno "1" should correspond to the first
+                                * line of the function body.  We expect that
+                                * pg_get_functiondef() will emit that on a line beginning
+                                * with "AS ", and that there can be no such line before the
+                                * real start of the function body.
                                 */
-                               while (*lines != '\0')
-                               {
-                                       char       *eol;
-
-                                       if (in_header && strncmp(lines, "AS ", 3) == 0)
-                                               in_header = false;
-                                       /* increment lineno only for body's lines */
-                                       if (!in_header)
-                                               lineno++;
-
-                                       /* find and mark end of current line */
-                                       eol = strchr(lines, '\n');
-                                       if (eol != NULL)
-                                               *eol = '\0';
-
-                                       /* show current line as appropriate */
-                                       if (in_header)
-                                               fprintf(output, "        %s\n", lines);
-                                       else
-                                               fprintf(output, "%-7d %s\n", lineno, lines);
-
-                                       /* advance to next line, if any */
-                                       if (eol == NULL)
-                                               break;
-                                       lines = ++eol;
-                               }
+                               print_with_linenumbers(output, buf->data,
+                                                                          is_func ? "AS " : NULL);
                        }
                        else
                        {
-                               /* just send the function definition to output */
-                               fputs(func_buf->data, output);
+                               /* just send the definition to output */
+                               fputs(buf->data, output);
                        }
 
                        if (is_pager)
                                ClosePager(output);
                }
 
-               if (func)
-                       free(func);
-               destroyPQExpBuffer(func_buf);
+               if (obj_desc)
+                       free(obj_desc);
+               destroyPQExpBuffer(buf);
        }
+       else
+               ignore_slash_whole_line(scan_state);
 
-       /* \t -- turn off headers and row count */
-       else if (strcmp(cmd, "t") == 0)
+       return status;
+}
+
+/*
+ * \t -- turn off table headers and row count
+ */
+static backslashResult
+exec_command_t(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_NORMAL, NULL, true);
@@ -1320,9 +2299,21 @@ exec_command(const char *cmd,
                success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
                free(opt);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \T -- define html <table ...> attributes */
-       else if (strcmp(cmd, "T") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \T -- define html <table ...> attributes
+ */
+static backslashResult
+exec_command_T(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *value = psql_scan_slash_option(scan_state,
                                                                                                   OT_NORMAL, NULL, false);
@@ -1330,15 +2321,27 @@ exec_command(const char *cmd,
                success = do_pset("tableattr", value, &pset.popt, pset.quiet);
                free(value);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \timing -- toggle timing of queries */
-       else if (strcmp(cmd, "timing") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \timing -- enable/disable timing of queries
+ */
+static backslashResult
+exec_command_timing(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_NORMAL, NULL, false);
 
                if (opt)
-                       pset.timing = ParseVariableBool(opt);
+                       success = ParseVariableBool(opt, "\\timing", &pset.timing);
                else
                        pset.timing = !pset.timing;
                if (!pset.quiet)
@@ -1350,54 +2353,78 @@ exec_command(const char *cmd,
                }
                free(opt);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \unset */
-       else if (strcmp(cmd, "unset") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \unset -- unset variable
+ */
+static backslashResult
+exec_command_unset(PsqlScanState scan_state, bool active_branch,
+                                  const char *cmd)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_NORMAL, NULL, false);
 
                if (!opt)
                {
-                       psql_error("\\%s: missing required argument\n", cmd);
+                       pg_log_error("\\%s: missing required argument", cmd);
                        success = false;
                }
                else if (!SetVariable(pset.vars, opt, NULL))
-               {
-                       psql_error("\\%s: error while setting variable\n", cmd);
                        success = false;
-               }
+
                free(opt);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \w -- write query buffer to file */
-       else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \w -- write query buffer to file
+ */
+static backslashResult
+exec_command_write(PsqlScanState scan_state, bool active_branch,
+                                  const char *cmd,
+                                  PQExpBuffer query_buf, PQExpBuffer previous_buf)
+{
+       backslashResult status = PSQL_CMD_SKIP_LINE;
+
+       if (active_branch)
        {
+               char       *fname = psql_scan_slash_option(scan_state,
+                                                                                                  OT_FILEPIPE, NULL, true);
                FILE       *fd = NULL;
                bool            is_pipe = false;
-               char       *fname = NULL;
 
                if (!query_buf)
                {
-                       psql_error("no query buffer\n");
+                       pg_log_error("no query buffer");
                        status = PSQL_CMD_ERROR;
                }
                else
                {
-                       fname = psql_scan_slash_option(scan_state,
-                                                                                  OT_FILEPIPE, NULL, true);
-                       expand_tilde(&fname);
-
                        if (!fname)
                        {
-                               psql_error("\\%s: missing required argument\n", cmd);
-                               success = false;
+                               pg_log_error("\\%s: missing required argument", cmd);
+                               status = PSQL_CMD_ERROR;
                        }
                        else
                        {
+                               expand_tilde(&fname);
                                if (fname[0] == '|')
                                {
                                        is_pipe = true;
+                                       disable_sigpipe_trap();
                                        fd = popen(&fname[1], "w");
                                }
                                else
@@ -1407,8 +2434,8 @@ exec_command(const char *cmd,
                                }
                                if (!fd)
                                {
-                                       psql_error("%s: %s\n", fname, strerror(errno));
-                                       success = false;
+                                       pg_log_error("%s: %m", fname);
+                                       status = PSQL_CMD_ERROR;
                                }
                        }
                }
@@ -1417,8 +2444,16 @@ exec_command(const char *cmd,
                {
                        int                     result;
 
+                       /*
+                        * We want to print the same thing \g would execute, but not to
+                        * change the query buffer state; so we can't use
+                        * copy_previous_query().  Also, beware of possibility that buffer
+                        * pointers are NULL.
+                        */
                        if (query_buf && query_buf->len > 0)
                                fprintf(fd, "%s\n", query_buf->data);
+                       else if (previous_buf && previous_buf->len > 0)
+                               fprintf(fd, "%s\n", previous_buf->data);
 
                        if (is_pipe)
                                result = pclose(fd);
@@ -1427,39 +2462,70 @@ exec_command(const char *cmd,
 
                        if (result == EOF)
                        {
-                               psql_error("%s: %s\n", fname, strerror(errno));
-                               success = false;
+                               pg_log_error("%s: %m", fname);
+                               status = PSQL_CMD_ERROR;
                        }
                }
 
+               if (is_pipe)
+                       restore_sigpipe_trap();
+
                free(fname);
        }
+       else
+               ignore_slash_filepipe(scan_state);
 
-       /* \watch -- execute a query every N seconds */
-       else if (strcmp(cmd, "watch") == 0)
+       return status;
+}
+
+/*
+ * \watch -- execute a query every N seconds
+ */
+static backslashResult
+exec_command_watch(PsqlScanState scan_state, bool active_branch,
+                                  PQExpBuffer query_buf, PQExpBuffer previous_buf)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_NORMAL, NULL, true);
-               long            sleep = 2;
+               double          sleep = 2;
 
                /* Convert optional sleep-length argument */
                if (opt)
                {
-                       sleep = strtol(opt, NULL, 10);
+                       sleep = strtod(opt, NULL);
                        if (sleep <= 0)
                                sleep = 1;
                        free(opt);
                }
 
+               /* If query_buf is empty, recall and execute previous query */
+               copy_previous_query(query_buf, previous_buf);
+
                success = do_watch(query_buf, sleep);
 
                /* Reset the query buffer as though for \r */
                resetPQExpBuffer(query_buf);
                psql_scan_reset(scan_state);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \x -- set or toggle expanded table representation */
-       else if (strcmp(cmd, "x") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \x -- set or toggle expanded table representation
+ */
+static backslashResult
+exec_command_x(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_NORMAL, NULL, true);
@@ -1467,9 +2533,21 @@ exec_command(const char *cmd,
                success = do_pset("expanded", opt, &pset.popt, pset.quiet);
                free(opt);
        }
+       else
+               ignore_slash_options(scan_state);
+
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \z -- list table privileges (equivalent to \dp)
+ */
+static backslashResult
+exec_command_z(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
 
-       /* \z -- list table rights (equivalent to \dp) */
-       else if (strcmp(cmd, "z") == 0)
+       if (active_branch)
        {
                char       *pattern = psql_scan_slash_option(scan_state,
                                                                                                         OT_NORMAL, NULL, true);
@@ -1478,9 +2556,21 @@ exec_command(const char *cmd,
                if (pattern)
                        free(pattern);
        }
+       else
+               ignore_slash_options(scan_state);
 
-       /* \! -- shell escape */
-       else if (strcmp(cmd, "!") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \! -- execute shell command
+ */
+static backslashResult
+exec_command_shell_escape(PsqlScanState scan_state, bool active_branch)
+{
+       bool            success = true;
+
+       if (active_branch)
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_WHOLE_LINE, NULL, false);
@@ -1488,12 +2578,22 @@ exec_command(const char *cmd,
                success = do_shell(opt);
                free(opt);
        }
+       else
+               ignore_slash_whole_line(scan_state);
 
-       /* \? -- slash command help */
-       else if (strcmp(cmd, "?") == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \? -- print help about backslash commands
+ */
+static backslashResult
+exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch)
+{
+       if (active_branch)
        {
                char       *opt0 = psql_scan_slash_option(scan_state,
-                                                                                                       OT_NORMAL, NULL, false);
+                                                                                                 OT_NORMAL, NULL, false);
 
                if (!opt0 || strcmp(opt0, "commands") == 0)
                        slashUsage(pset.popt.topt.pager);
@@ -1503,34 +2603,236 @@ exec_command(const char *cmd,
                        helpVariables(pset.popt.topt.pager);
                else
                        slashUsage(pset.popt.topt.pager);
+
+               if (opt0)
+                       free(opt0);
        }
+       else
+               ignore_slash_options(scan_state);
+
+       return PSQL_CMD_SKIP_LINE;
+}
 
-#if 0
+
+/*
+ * Read and interpret an argument to the \connect slash command.
+ *
+ * Returns a malloc'd string, or NULL if no/empty argument.
+ */
+static char *
+read_connect_arg(PsqlScanState scan_state)
+{
+       char       *result;
+       char            quote;
 
        /*
-        * These commands don't do anything. I just use them to test the parser.
+        * Ideally we should treat the arguments as SQL identifiers.  But for
+        * backwards compatibility with 7.2 and older pg_dump files, we have to
+        * take unquoted arguments verbatim (don't downcase them). For now,
+        * double-quoted arguments may be stripped of double quotes (as if SQL
+        * identifiers).  By 7.4 or so, pg_dump files can be expected to
+        * double-quote all mixed-case \connect arguments, and then we can get rid
+        * of OT_SQLIDHACK.
         */
-       else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0)
+       result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &quote, true);
+
+       if (!result)
+               return NULL;
+
+       if (quote)
+               return result;
+
+       if (*result == '\0' || strcmp(result, "-") == 0)
        {
-               int                     i = 0;
-               char       *value;
+               free(result);
+               return NULL;
+       }
 
-               while ((value = psql_scan_slash_option(scan_state,
-                                                                                          OT_NORMAL, NULL, true)))
-               {
-                       psql_error("+ opt(%d) = |%s|\n", i++, value);
-                       free(value);
-               }
+       return result;
+}
+
+/*
+ * Read a boolean expression, return it as a PQExpBuffer string.
+ *
+ * Note: anything more or less than one token will certainly fail to be
+ * parsed by ParseVariableBool, so we don't worry about complaining here.
+ * This routine's return data structure will need to be rethought anyway
+ * to support likely future extensions such as "\if defined VARNAME".
+ */
+static PQExpBuffer
+gather_boolean_expression(PsqlScanState scan_state)
+{
+       PQExpBuffer exp_buf = createPQExpBuffer();
+       int                     num_options = 0;
+       char       *value;
+
+       /* collect all arguments for the conditional command into exp_buf */
+       while ((value = psql_scan_slash_option(scan_state,
+                                                                                  OT_NORMAL, NULL, false)) != NULL)
+       {
+               /* add spaces between tokens */
+               if (num_options > 0)
+                       appendPQExpBufferChar(exp_buf, ' ');
+               appendPQExpBufferStr(exp_buf, value);
+               num_options++;
+               free(value);
        }
-#endif
 
-       else
-               status = PSQL_CMD_UNKNOWN;
+       return exp_buf;
+}
 
-       if (!success)
-               status = PSQL_CMD_ERROR;
+/*
+ * Read a boolean expression, return true if the expression
+ * was a valid boolean expression that evaluated to true.
+ * Otherwise return false.
+ *
+ * Note: conditional stack's top state must be active, else lexer will
+ * fail to expand variables and backticks.
+ */
+static bool
+is_true_boolean_expression(PsqlScanState scan_state, const char *name)
+{
+       PQExpBuffer buf = gather_boolean_expression(scan_state);
+       bool            value = false;
+       bool            success = ParseVariableBool(buf->data, name, &value);
 
-       return status;
+       destroyPQExpBuffer(buf);
+       return success && value;
+}
+
+/*
+ * Read a boolean expression, but do nothing with it.
+ *
+ * Note: conditional stack's top state must be INACTIVE, else lexer will
+ * expand variables and backticks, which we do not want here.
+ */
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
+{
+       PQExpBuffer buf = gather_boolean_expression(scan_state);
+
+       destroyPQExpBuffer(buf);
+}
+
+/*
+ * Read and discard "normal" slash command options.
+ *
+ * This should be used for inactive-branch processing of any slash command
+ * that eats one or more OT_NORMAL, OT_SQLID, or OT_SQLIDHACK parameters.
+ * We don't need to worry about exactly how many it would eat, since the
+ * cleanup logic in HandleSlashCmds would silently discard any extras anyway.
+ */
+static void
+ignore_slash_options(PsqlScanState scan_state)
+{
+       char       *arg;
+
+       while ((arg = psql_scan_slash_option(scan_state,
+                                                                                OT_NORMAL, NULL, false)) != NULL)
+               free(arg);
+}
+
+/*
+ * Read and discard FILEPIPE slash command argument.
+ *
+ * This *MUST* be used for inactive-branch processing of any slash command
+ * that takes an OT_FILEPIPE option.  Otherwise we might consume a different
+ * amount of option text in active and inactive cases.
+ */
+static void
+ignore_slash_filepipe(PsqlScanState scan_state)
+{
+       char       *arg = psql_scan_slash_option(scan_state,
+                                                                                        OT_FILEPIPE, NULL, false);
+
+       if (arg)
+               free(arg);
+}
+
+/*
+ * Read and discard whole-line slash command argument.
+ *
+ * This *MUST* be used for inactive-branch processing of any slash command
+ * that takes an OT_WHOLE_LINE option.  Otherwise we might consume a different
+ * amount of option text in active and inactive cases.
+ */
+static void
+ignore_slash_whole_line(PsqlScanState scan_state)
+{
+       char       *arg = psql_scan_slash_option(scan_state,
+                                                                                        OT_WHOLE_LINE, NULL, false);
+
+       if (arg)
+               free(arg);
+}
+
+/*
+ * Return true if the command given is a branching command.
+ */
+static bool
+is_branching_command(const char *cmd)
+{
+       return (strcmp(cmd, "if") == 0 ||
+                       strcmp(cmd, "elif") == 0 ||
+                       strcmp(cmd, "else") == 0 ||
+                       strcmp(cmd, "endif") == 0);
+}
+
+/*
+ * Prepare to possibly restore query buffer to its current state
+ * (cf. discard_query_text).
+ *
+ * We need to remember the length of the query buffer, and the lexer's
+ * notion of the parenthesis nesting depth.
+ */
+static void
+save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
+                                         PQExpBuffer query_buf)
+{
+       if (query_buf)
+               conditional_stack_set_query_len(cstack, query_buf->len);
+       conditional_stack_set_paren_depth(cstack,
+                                                                         psql_scan_get_paren_depth(scan_state));
+}
+
+/*
+ * Discard any query text absorbed during an inactive conditional branch.
+ *
+ * We must discard data that was appended to query_buf during an inactive
+ * \if branch.  We don't have to do anything there if there's no query_buf.
+ *
+ * Also, reset the lexer state to the same paren depth there was before.
+ * (The rest of its state doesn't need attention, since we could not be
+ * inside a comment or literal or partial token.)
+ */
+static void
+discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
+                                  PQExpBuffer query_buf)
+{
+       if (query_buf)
+       {
+               int                     new_len = conditional_stack_get_query_len(cstack);
+
+               Assert(new_len >= 0 && new_len <= query_buf->len);
+               query_buf->len = new_len;
+               query_buf->data[new_len] = '\0';
+       }
+       psql_scan_set_paren_depth(scan_state,
+                                                         conditional_stack_get_paren_depth(cstack));
+}
+
+/*
+ * If query_buf is empty, copy previous_buf into it.
+ *
+ * This is used by various slash commands for which re-execution of a
+ * previous query is a common usage.  For convenience, we allow the
+ * case of query_buf == NULL (and do nothing).
+ */
+static void
+copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf)
+{
+       if (query_buf && query_buf->len == 0)
+               appendPQExpBufferStr(query_buf, previous_buf->data);
 }
 
 /*
@@ -1541,20 +2843,19 @@ exec_command(const char *cmd,
 static char *
 prompt_for_password(const char *username)
 {
-       char       *result;
+       char            buf[100];
 
-       if (username == NULL)
-               result = simple_prompt("Password: ", 100, false);
+       if (username == NULL || username[0] == '\0')
+               simple_prompt("Password: ", buf, sizeof(buf), false);
        else
        {
                char       *prompt_text;
 
                prompt_text = psprintf(_("Password for user %s: "), username);
-               result = simple_prompt(prompt_text, 100, false);
+               simple_prompt(prompt_text, buf, sizeof(buf), false);
                free(prompt_text);
        }
-
-       return result;
+       return pg_strdup(buf);
 }
 
 static bool
@@ -1572,20 +2873,27 @@ param_is_newly_set(const char *old_val, const char *new_val)
 /*
  * do_connect -- handler for \connect
  *
- * Connects to a database with given parameters. If there exists an
- * established connection, NULL values will be replaced with the ones
- * in the current connection. Otherwise NULL will be passed for that
- * parameter to PQconnectdbParams(), so the libpq defaults will be used.
+ * Connects to a database with given parameters. Absent an established
+ * connection, all parameters are required. Given -reuse-previous=off or a
+ * connection string without -reuse-previous=on, NULL values will pass through
+ * to PQconnectdbParams(), so the libpq defaults will be used. Otherwise, NULL
+ * values will be replaced with the ones in the current connection.
  *
  * In interactive mode, if connection fails with the given parameters,
  * the old connection will be kept.
  */
 static bool
-do_connect(char *dbname, char *user, char *host, char *port)
+do_connect(enum trivalue reuse_previous_specification,
+                  char *dbname, char *user, char *host, char *port)
 {
        PGconn     *o_conn = pset.db,
                           *n_conn;
        char       *password = NULL;
+       char       *hostaddr = NULL;
+       bool            keep_password;
+       bool            has_connection_string;
+       bool            reuse_previous;
+       PQExpBufferData connstr;
 
        if (!o_conn && (!dbname || !user || !host || !port))
        {
@@ -1594,25 +2902,89 @@ do_connect(char *dbname, char *user, char *host, char *port)
                 * connect to the wrong database by using defaults, so require all
                 * parameters to be specified.
                 */
-               psql_error("All connection parameters must be supplied because no "
-                                  "database connection exists\n");
+               pg_log_error("All connection parameters must be supplied because no "
+                                        "database connection exists");
                return false;
        }
 
-       if (!dbname)
-               dbname = PQdb(o_conn);
-       if (!user)
-               user = PQuser(o_conn);
-       if (!host)
-               host = PQhost(o_conn);
-       if (!port)
-               port = PQport(o_conn);
+       has_connection_string = dbname ?
+               recognized_connection_string(dbname) : false;
+       switch (reuse_previous_specification)
+       {
+               case TRI_YES:
+                       reuse_previous = true;
+                       break;
+               case TRI_NO:
+                       reuse_previous = false;
+                       break;
+               default:
+                       reuse_previous = !has_connection_string;
+                       break;
+       }
+       /* Silently ignore arguments subsequent to a connection string. */
+       if (has_connection_string)
+       {
+               user = NULL;
+               host = NULL;
+               port = NULL;
+       }
+
+       /* grab missing values from the old connection */
+       if (reuse_previous)
+       {
+               if (!user)
+                       user = PQuser(o_conn);
+               if (host && strcmp(host, PQhost(o_conn)) == 0)
+               {
+                       /*
+                        * if we are targetting the same host, reuse its hostaddr for
+                        * consistency
+                        */
+                       hostaddr = PQhostaddr(o_conn);
+               }
+               if (!host)
+               {
+                       host = PQhost(o_conn);
+                       /* also set hostaddr for consistency */
+                       hostaddr = PQhostaddr(o_conn);
+               }
+               if (!port)
+                       port = PQport(o_conn);
+       }
+
+       /*
+        * Any change in the parameters read above makes us discard the password.
+        * We also discard it if we're to use a conninfo rather than the
+        * positional syntax.
+        */
+       if (has_connection_string)
+               keep_password = false;
+       else
+               keep_password =
+                       (user && PQuser(o_conn) && strcmp(user, PQuser(o_conn)) == 0) &&
+                       (host && PQhost(o_conn) && strcmp(host, PQhost(o_conn)) == 0) &&
+                       (port && PQport(o_conn) && strcmp(port, PQport(o_conn)) == 0);
+
+       /*
+        * Grab missing dbname from old connection.  No password discard if this
+        * changes: passwords aren't (usually) database-specific.
+        */
+       if (!dbname && reuse_previous)
+       {
+               initPQExpBuffer(&connstr);
+               appendPQExpBuffer(&connstr, "dbname=");
+               appendConnStrVal(&connstr, PQdb(o_conn));
+               dbname = connstr.data;
+               /* has_connection_string=true would be a dead store */
+       }
+       else
+               connstr.data = NULL;
 
        /*
         * If the user asked to be prompted for a password, ask for one now. If
         * not, use the password from the old connection, provided the username
-        * has not changed. Otherwise, try to connect without a password first,
-        * and then ask for a password if needed.
+        * etc have not changed. Otherwise, try to connect without a password
+        * first, and then ask for a password if needed.
         *
         * XXX: this behavior leads to spurious connection attempts recorded in
         * the postmaster's log.  But libpq offers no API that would let us obtain
@@ -1620,44 +2992,74 @@ do_connect(char *dbname, char *user, char *host, char *port)
         */
        if (pset.getPassword == TRI_YES)
        {
-               password = prompt_for_password(user);
+               /*
+                * If a connstring or URI is provided, we can't be sure we know which
+                * username will be used, since we haven't parsed that argument yet.
+                * Don't risk issuing a misleading prompt.  As in startup.c, it does
+                * not seem worth working harder, since this getPassword option is
+                * normally only used in noninteractive cases.
+                */
+               password = prompt_for_password(has_connection_string ? NULL : user);
        }
-       else if (o_conn && user && strcmp(PQuser(o_conn), user) == 0)
+       else if (o_conn && keep_password)
        {
-               password = pg_strdup(PQpass(o_conn));
+               password = PQpass(o_conn);
+               if (password && *password)
+                       password = pg_strdup(password);
+               else
+                       password = NULL;
        }
 
        while (true)
        {
-#define PARAMS_ARRAY_SIZE      8
+#define PARAMS_ARRAY_SIZE      9
                const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords));
                const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values));
+               int                     paramnum = -1;
+
+               keywords[++paramnum] = "host";
+               values[paramnum] = host;
+               if (hostaddr && *hostaddr)
+               {
+                       keywords[++paramnum] = "hostaddr";
+                       values[paramnum] = hostaddr;
+               }
+               keywords[++paramnum] = "port";
+               values[paramnum] = port;
+               keywords[++paramnum] = "user";
+               values[paramnum] = user;
 
-               keywords[0] = "host";
-               values[0] = host;
-               keywords[1] = "port";
-               values[1] = port;
-               keywords[2] = "user";
-               values[2] = user;
-               keywords[3] = "password";
-               values[3] = password;
-               keywords[4] = "dbname";
-               values[4] = dbname;
-               keywords[5] = "fallback_application_name";
-               values[5] = pset.progname;
-               keywords[6] = "client_encoding";
-               values[6] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto";
-               keywords[7] = NULL;
-               values[7] = NULL;
+               /*
+                * Position in the array matters when the dbname is a connection
+                * string, because settings in a connection string override earlier
+                * array entries only.  Thus, user= in the connection string always
+                * takes effect, but client_encoding= often will not.
+                *
+                * If you change this code, also change the initial-connection code in
+                * main().  For no good reason, a connection string password= takes
+                * precedence in main() but not here.
+                */
+               keywords[++paramnum] = "dbname";
+               values[paramnum] = dbname;
+               keywords[++paramnum] = "password";
+               values[paramnum] = password;
+               keywords[++paramnum] = "fallback_application_name";
+               values[paramnum] = pset.progname;
+               keywords[++paramnum] = "client_encoding";
+               values[paramnum] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto";
+
+               /* add array terminator */
+               keywords[++paramnum] = NULL;
+               values[paramnum] = NULL;
 
                n_conn = PQconnectdbParams(keywords, values, true);
 
-               free(keywords);
-               free(values);
+               pg_free(keywords);
+               pg_free(values);
 
                /* We can immediately discard the password -- no longer needed */
                if (password)
-                       free(password);
+                       pg_free(password);
 
                if (PQstatus(n_conn) == CONNECTION_OK)
                        break;
@@ -1668,8 +3070,12 @@ do_connect(char *dbname, char *user, char *host, char *port)
                 */
                if (!password && PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO)
                {
+                       /*
+                        * Prompt for password using the username we actually connected
+                        * with --- it might've come out of "dbname" rather than "user".
+                        */
+                       password = prompt_for_password(PQuser(n_conn));
                        PQfinish(n_conn);
-                       password = prompt_for_password(user);
                        continue;
                }
 
@@ -1680,15 +3086,15 @@ do_connect(char *dbname, char *user, char *host, char *port)
                 */
                if (pset.cur_cmd_interactive)
                {
-                       psql_error("%s", PQerrorMessage(n_conn));
+                       pg_log_info("%s", PQerrorMessage(n_conn));
 
                        /* pset.db is left unmodified */
                        if (o_conn)
-                               psql_error("Previous connection kept\n");
+                               pg_log_info("Previous connection kept");
                }
                else
                {
-                       psql_error("\\connect: %s", PQerrorMessage(n_conn));
+                       pg_log_error("\\connect: %s", PQerrorMessage(n_conn));
                        if (o_conn)
                        {
                                PQfinish(o_conn);
@@ -1697,8 +3103,12 @@ do_connect(char *dbname, char *user, char *host, char *port)
                }
 
                PQfinish(n_conn);
+               if (connstr.data)
+                       termPQExpBuffer(&connstr);
                return false;
        }
+       if (connstr.data)
+               termPQExpBuffer(&connstr);
 
        /*
         * Replace the old connection with the new one, and update
@@ -1712,20 +3122,32 @@ do_connect(char *dbname, char *user, char *host, char *port)
        /* Tell the user about the new connection */
        if (!pset.quiet)
        {
-               if (param_is_newly_set(PQhost(o_conn), PQhost(pset.db)) ||
+               if (!o_conn ||
+                       param_is_newly_set(PQhost(o_conn), PQhost(pset.db)) ||
                        param_is_newly_set(PQport(o_conn), PQport(pset.db)))
                {
                        char       *host = PQhost(pset.db);
+                       char       *hostaddr = PQhostaddr(pset.db);
 
-                       if (host == NULL)
-                               host = DEFAULT_PGSOCKET_DIR;
                        /* If the host is an absolute path, the connection is via socket */
                        if (is_absolute_path(host))
-                               printf(_("You are now connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
-                                          PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db));
+                       {
+                               if (hostaddr && *hostaddr)
+                                       printf(_("You are now connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"),
+                                                  PQdb(pset.db), PQuser(pset.db), hostaddr, PQport(pset.db));
+                               else
+                                       printf(_("You are now connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
+                                                  PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db));
+                       }
                        else
-                               printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
-                                          PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db));
+                       {
+                               if (hostaddr && *hostaddr && strcmp(host, hostaddr) != 0)
+                                       printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" (address \"%s\") at port \"%s\".\n"),
+                                                  PQdb(pset.db), PQuser(pset.db), host, hostaddr, PQport(pset.db));
+                               else
+                                       printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
+                                                  PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db));
+                       }
                }
                else
                        printf(_("You are now connected to database \"%s\" as user \"%s\".\n"),
@@ -1744,22 +3166,21 @@ connection_warnings(bool in_startup)
        if (!pset.quiet && !pset.notty)
        {
                int                     client_ver = PG_VERSION_NUM;
+               char            cverbuf[32];
+               char            sverbuf[32];
 
                if (pset.sversion != client_ver)
                {
                        const char *server_version;
-                       char            server_ver_str[16];
 
                        /* Try to get full text form, might include "devel" etc */
                        server_version = PQparameterStatus(pset.db, "server_version");
+                       /* Otherwise fall back on pset.sversion */
                        if (!server_version)
                        {
-                               snprintf(server_ver_str, sizeof(server_ver_str),
-                                                "%d.%d.%d",
-                                                pset.sversion / 10000,
-                                                (pset.sversion / 100) % 100,
-                                                pset.sversion % 100);
-                               server_version = server_ver_str;
+                               formatPGVersionNumber(pset.sversion, true,
+                                                                         sverbuf, sizeof(sverbuf));
+                               server_version = sverbuf;
                        }
 
                        printf(_("%s (%s, server %s)\n"),
@@ -1770,15 +3191,19 @@ connection_warnings(bool in_startup)
                        printf("%s (%s)\n", pset.progname, PG_VERSION);
 
                if (pset.sversion / 100 > client_ver / 100)
-                       printf(_("WARNING: %s major version %d.%d, server major version %d.%d.\n"
+                       printf(_("WARNING: %s major version %s, server major version %s.\n"
                                         "         Some psql features might not work.\n"),
-                                pset.progname, client_ver / 10000, (client_ver / 100) % 100,
-                                  pset.sversion / 10000, (pset.sversion / 100) % 100);
+                                  pset.progname,
+                                  formatPGVersionNumber(client_ver, false,
+                                                                                cverbuf, sizeof(cverbuf)),
+                                  formatPGVersionNumber(pset.sversion, false,
+                                                                                sverbuf, sizeof(sverbuf)));
 
 #ifdef WIN32
                checkWin32Codepage();
 #endif
                printSSLInfo();
+               printGSSInfo();
        }
 }
 
@@ -1791,28 +3216,38 @@ connection_warnings(bool in_startup)
 static void
 printSSLInfo(void)
 {
-#ifdef USE_OPENSSL
-       int                     sslbits = -1;
-       SSL                *ssl;
+       const char *protocol;
+       const char *cipher;
+       const char *bits;
+       const char *compression;
 
-       ssl = PQgetssl(pset.db);
-       if (!ssl)
+       if (!PQsslInUse(pset.db))
                return;                                 /* no SSL */
 
-       SSL_get_cipher_bits(ssl, &sslbits);
-       printf(_("SSL connection (protocol: %s, cipher: %s, bits: %d, compression: %s)\n"),
-                  SSL_get_version(ssl), SSL_get_cipher(ssl), sslbits,
-                  SSL_get_current_compression(ssl) ? _("on") : _("off"));
-#else
+       protocol = PQsslAttribute(pset.db, "protocol");
+       cipher = PQsslAttribute(pset.db, "cipher");
+       bits = PQsslAttribute(pset.db, "key_bits");
+       compression = PQsslAttribute(pset.db, "compression");
 
-       /*
-        * If psql is compiled without SSL but is using a libpq with SSL, we
-        * cannot figure out the specifics about the connection. But we know it's
-        * SSL secured.
-        */
-       if (PQgetssl(pset.db))
-               printf(_("SSL connection (unknown cipher)\n"));
-#endif
+       printf(_("SSL connection (protocol: %s, cipher: %s, bits: %s, compression: %s)\n"),
+                  protocol ? protocol : _("unknown"),
+                  cipher ? cipher : _("unknown"),
+                  bits ? bits : _("unknown"),
+                  (compression && strcmp(compression, "off") != 0) ? _("on") : _("off"));
+}
+
+/*
+ * printGSSInfo
+ *
+ * Prints information about the current GSSAPI connection, if GSSAPI encryption is in use
+ */
+static void
+printGSSInfo(void)
+{
+       if (!PQgssEncInUse(pset.db))
+               return;                                 /* no GSSAPI encryption in use */
+
+       printf(_("GSSAPI Encrypted connection\n"));
 }
 
 
@@ -1850,6 +3285,9 @@ checkWin32Codepage(void)
 void
 SyncVariables(void)
 {
+       char            vbuf[32];
+       const char *server_version;
+
        /* get stuff from connection */
        pset.encoding = PQclientEncoding(pset.db);
        pset.popt.topt.encoding = pset.encoding;
@@ -1861,8 +3299,23 @@ SyncVariables(void)
        SetVariable(pset.vars, "PORT", PQport(pset.db));
        SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding));
 
+       /* this bit should match connection_warnings(): */
+       /* Try to get full text form of version, might include "devel" etc */
+       server_version = PQparameterStatus(pset.db, "server_version");
+       /* Otherwise fall back on pset.sversion */
+       if (!server_version)
+       {
+               formatPGVersionNumber(pset.sversion, true, vbuf, sizeof(vbuf));
+               server_version = vbuf;
+       }
+       SetVariable(pset.vars, "SERVER_VERSION_NAME", server_version);
+
+       snprintf(vbuf, sizeof(vbuf), "%d", pset.sversion);
+       SetVariable(pset.vars, "SERVER_VERSION_NUM", vbuf);
+
        /* send stuff to it, too */
        PQsetErrorVerbosity(pset.db, pset.verbosity);
+       PQsetErrorContextVisibility(pset.db, pset.show_context);
 }
 
 /*
@@ -1878,6 +3331,8 @@ UnsyncVariables(void)
        SetVariable(pset.vars, "HOST", NULL);
        SetVariable(pset.vars, "PORT", NULL);
        SetVariable(pset.vars, "ENCODING", NULL);
+       SetVariable(pset.vars, "SERVER_VERSION_NAME", NULL);
+       SetVariable(pset.vars, "SERVER_VERSION_NUM", NULL);
 }
 
 
@@ -1916,7 +3371,7 @@ editFile(const char *fname, int lineno)
 #endif
                if (!editor_lineno_arg)
                {
-                       psql_error("environment variable PSQL_EDITOR_LINENUMBER_ARG must be set to specify a line number\n");
+                       pg_log_error("environment variable PSQL_EDITOR_LINENUMBER_ARG must be set to specify a line number");
                        return false;
                }
        }
@@ -1945,9 +3400,9 @@ editFile(const char *fname, int lineno)
 #endif
        result = system(sys);
        if (result == -1)
-               psql_error("could not start editor \"%s\"\n", editorName);
+               pg_log_error("could not start editor \"%s\"", editorName);
        else if (result == 127)
-               psql_error("could not start /bin/sh\n");
+               pg_log_error("could not start /bin/sh");
        free(sys);
 
        return result == 0;
@@ -1985,7 +3440,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
                ret = GetTempPath(MAXPGPATH, tmpdir);
                if (ret == 0 || ret > MAXPGPATH)
                {
-                       psql_error("could not locate temporary directory: %s\n",
+                       pg_log_error("could not locate temporary directory: %s",
                                           !ret ? strerror(errno) : "");
                        return false;
                }
@@ -2001,7 +3456,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
                                 "/", (int) getpid());
 #else
                snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d.sql", tmpdir,
-                          "" /* trailing separator already present */ , (int) getpid());
+                                "" /* trailing separator already present */ , (int) getpid());
 #endif
 
                fname = (const char *) fnametmp;
@@ -2012,7 +3467,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
 
                if (fd == -1 || !stream)
                {
-                       psql_error("could not open temporary file \"%s\": %s\n", fname, strerror(errno));
+                       pg_log_error("could not open temporary file \"%s\": %m", fname);
                        error = true;
                }
                else
@@ -2027,21 +3482,21 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
 
                        if (fwrite(query_buf->data, 1, ql, stream) != ql)
                        {
-                               psql_error("%s: %s\n", fname, strerror(errno));
+                               pg_log_error("%s: %m", fname);
 
                                if (fclose(stream) != 0)
-                                       psql_error("%s: %s\n", fname, strerror(errno));
+                                       pg_log_error("%s: %m", fname);
 
                                if (remove(fname) != 0)
-                                       psql_error("%s: %s\n", fname, strerror(errno));
+                                       pg_log_error("%s: %m", fname);
 
                                error = true;
                        }
                        else if (fclose(stream) != 0)
                        {
-                               psql_error("%s: %s\n", fname, strerror(errno));
+                               pg_log_error("%s: %m", fname);
                                if (remove(fname) != 0)
-                                       psql_error("%s: %s\n", fname, strerror(errno));
+                                       pg_log_error("%s: %m", fname);
                                error = true;
                        }
                }
@@ -2049,7 +3504,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
 
        if (!error && stat(fname, &before) != 0)
        {
-               psql_error("%s: %s\n", fname, strerror(errno));
+               pg_log_error("%s: %m", fname);
                error = true;
        }
 
@@ -2059,7 +3514,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
 
        if (!error && stat(fname, &after) != 0)
        {
-               psql_error("%s: %s\n", fname, strerror(errno));
+               pg_log_error("%s: %m", fname);
                error = true;
        }
 
@@ -2068,7 +3523,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
                stream = fopen(fname, PG_BINARY_R);
                if (!stream)
                {
-                       psql_error("%s: %s\n", fname, strerror(errno));
+                       pg_log_error("%s: %m", fname);
                        error = true;
                }
                else
@@ -2082,7 +3537,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
 
                        if (ferror(stream))
                        {
-                               psql_error("%s: %s\n", fname, strerror(errno));
+                               pg_log_error("%s: %m", fname);
                                error = true;
                        }
                        else if (edited)
@@ -2099,7 +3554,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
        {
                if (remove(fname) == -1)
                {
-                       psql_error("%s: %s\n", fname, strerror(errno));
+                       pg_log_error("%s: %m", fname);
                        error = true;
                }
        }
@@ -2120,13 +3575,12 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
  * the file from where the currently processed file (if any) is located.
  */
 int
-process_file(char *filename, bool single_txn, bool use_relative_path)
+process_file(char *filename, bool use_relative_path)
 {
        FILE       *fd;
        int                     result;
        char       *oldfilename;
        char            relpath[MAXPGPATH];
-       PGresult   *res;
 
        if (!filename)
        {
@@ -2158,7 +3612,7 @@ process_file(char *filename, bool single_txn, bool use_relative_path)
 
                if (!fd)
                {
-                       psql_error("%s: %s\n", filename, strerror(errno));
+                       pg_log_error("%s: %m", filename);
                        return EXIT_FAILURE;
                }
        }
@@ -2171,50 +3625,22 @@ process_file(char *filename, bool single_txn, bool use_relative_path)
        oldfilename = pset.inputfile;
        pset.inputfile = filename;
 
-       if (single_txn)
-       {
-               if ((res = PSQLexec("BEGIN", false)) == NULL)
-               {
-                       if (pset.on_error_stop)
-                       {
-                               result = EXIT_USER;
-                               goto error;
-                       }
-               }
-               else
-                       PQclear(res);
-       }
+       pg_logging_config(pset.inputfile ? 0 : PG_LOG_FLAG_TERSE);
 
        result = MainLoop(fd);
 
-       if (single_txn)
-       {
-               if ((res = PSQLexec("COMMIT", false)) == NULL)
-               {
-                       if (pset.on_error_stop)
-                       {
-                               result = EXIT_USER;
-                               goto error;
-                       }
-               }
-               else
-                       PQclear(res);
-       }
-
-error:
        if (fd != stdin)
                fclose(fd);
 
        pset.inputfile = oldfilename;
+
+       pg_logging_config(pset.inputfile ? 0 : PG_LOG_FLAG_TERSE);
+
        return result;
 }
 
 
 
-/*
- * do_pset
- *
- */
 static const char *
 _align2string(enum printFormat in)
 {
@@ -2223,14 +3649,14 @@ _align2string(enum printFormat in)
                case PRINT_NOTHING:
                        return "nothing";
                        break;
-               case PRINT_UNALIGNED:
-                       return "unaligned";
-                       break;
                case PRINT_ALIGNED:
                        return "aligned";
                        break;
-               case PRINT_WRAPPED:
-                       return "wrapped";
+               case PRINT_ASCIIDOC:
+                       return "asciidoc";
+                       break;
+               case PRINT_CSV:
+                       return "csv";
                        break;
                case PRINT_HTML:
                        return "html";
@@ -2241,14 +3667,55 @@ _align2string(enum printFormat in)
                case PRINT_LATEX_LONGTABLE:
                        return "latex-longtable";
                        break;
-               case PRINT_TROFF_MS:
-                       return "troff-ms";
+               case PRINT_TROFF_MS:
+                       return "troff-ms";
+                       break;
+               case PRINT_UNALIGNED:
+                       return "unaligned";
+                       break;
+               case PRINT_WRAPPED:
+                       return "wrapped";
+                       break;
+       }
+       return "unknown";
+}
+
+/*
+ * Parse entered Unicode linestyle.  If ok, update *linestyle and return
+ * true, else return false.
+ */
+static bool
+set_unicode_line_style(const char *value, size_t vallen,
+                                          unicode_linestyle *linestyle)
+{
+       if (pg_strncasecmp("single", value, vallen) == 0)
+               *linestyle = UNICODE_LINESTYLE_SINGLE;
+       else if (pg_strncasecmp("double", value, vallen) == 0)
+               *linestyle = UNICODE_LINESTYLE_DOUBLE;
+       else
+               return false;
+       return true;
+}
+
+static const char *
+_unicode_linestyle2string(int linestyle)
+{
+       switch (linestyle)
+       {
+               case UNICODE_LINESTYLE_SINGLE:
+                       return "single";
+                       break;
+               case UNICODE_LINESTYLE_DOUBLE:
+                       return "double";
                        break;
        }
        return "unknown";
 }
 
-
+/*
+ * do_pset
+ *
+ */
 bool
 do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 {
@@ -2262,28 +3729,61 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
        /* set format */
        if (strcmp(param, "format") == 0)
        {
+               static const struct fmt
+               {
+                       const char *name;
+                       enum printFormat number;
+               }                       formats[] =
+               {
+                       /* remember to update error message below when adding more */
+                       {"aligned", PRINT_ALIGNED},
+                       {"asciidoc", PRINT_ASCIIDOC},
+                       {"csv", PRINT_CSV},
+                       {"html", PRINT_HTML},
+                       {"latex", PRINT_LATEX},
+                       {"troff-ms", PRINT_TROFF_MS},
+                       {"unaligned", PRINT_UNALIGNED},
+                       {"wrapped", PRINT_WRAPPED}
+               };
+
                if (!value)
                        ;
-               else if (pg_strncasecmp("unaligned", value, vallen) == 0)
-                       popt->topt.format = PRINT_UNALIGNED;
-               else if (pg_strncasecmp("aligned", value, vallen) == 0)
-                       popt->topt.format = PRINT_ALIGNED;
-               else if (pg_strncasecmp("wrapped", value, vallen) == 0)
-                       popt->topt.format = PRINT_WRAPPED;
-               else if (pg_strncasecmp("html", value, vallen) == 0)
-                       popt->topt.format = PRINT_HTML;
-               else if (pg_strncasecmp("latex", value, vallen) == 0)
-                       popt->topt.format = PRINT_LATEX;
-               else if (pg_strncasecmp("latex-longtable", value, vallen) == 0)
-                       popt->topt.format = PRINT_LATEX_LONGTABLE;
-               else if (pg_strncasecmp("troff-ms", value, vallen) == 0)
-                       popt->topt.format = PRINT_TROFF_MS;
                else
                {
-                       psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
-                       return false;
-               }
+                       int                     match_pos = -1;
 
+                       for (int i = 0; i < lengthof(formats); i++)
+                       {
+                               if (pg_strncasecmp(formats[i].name, value, vallen) == 0)
+                               {
+                                       if (match_pos < 0)
+                                               match_pos = i;
+                                       else
+                                       {
+                                               pg_log_error("\\pset: ambiguous abbreviation \"%s\" matches both \"%s\" and \"%s\"",
+                                                                  value,
+                                                                  formats[match_pos].name, formats[i].name);
+                                               return false;
+                                       }
+                               }
+                       }
+                       if (match_pos >= 0)
+                               popt->topt.format = formats[match_pos].number;
+                       else if (pg_strncasecmp("latex-longtable", value, vallen) == 0)
+                       {
+                               /*
+                                * We must treat latex-longtable specially because latex is a
+                                * prefix of it; if both were in the table above, we'd think
+                                * "latex" is ambiguous.
+                                */
+                               popt->topt.format = PRINT_LATEX_LONGTABLE;
+                       }
+                       else
+                       {
+                               pg_log_error("\\pset: allowed formats are aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned, wrapped");
+                               return false;
+                       }
+               }
        }
 
        /* set table line style */
@@ -2299,10 +3799,54 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
                        popt->topt.line_style = &pg_utf8format;
                else
                {
-                       psql_error("\\pset: allowed line styles are ascii, old-ascii, unicode\n");
+                       pg_log_error("\\pset: allowed line styles are ascii, old-ascii, unicode");
+                       return false;
+               }
+       }
+
+       /* set unicode border line style */
+       else if (strcmp(param, "unicode_border_linestyle") == 0)
+       {
+               if (!value)
+                       ;
+               else if (set_unicode_line_style(value, vallen,
+                                                                               &popt->topt.unicode_border_linestyle))
+                       refresh_utf8format(&(popt->topt));
+               else
+               {
+                       pg_log_error("\\pset: allowed Unicode border line styles are single, double");
+                       return false;
+               }
+       }
+
+       /* set unicode column line style */
+       else if (strcmp(param, "unicode_column_linestyle") == 0)
+       {
+               if (!value)
+                       ;
+               else if (set_unicode_line_style(value, vallen,
+                                                                               &popt->topt.unicode_column_linestyle))
+                       refresh_utf8format(&(popt->topt));
+               else
+               {
+                       pg_log_error("\\pset: allowed Unicode column line styles are single, double");
                        return false;
                }
+       }
 
+       /* set unicode header line style */
+       else if (strcmp(param, "unicode_header_linestyle") == 0)
+       {
+               if (!value)
+                       ;
+               else if (set_unicode_line_style(value, vallen,
+                                                                               &popt->topt.unicode_header_linestyle))
+                       refresh_utf8format(&(popt->topt));
+               else
+               {
+                       pg_log_error("\\pset: allowed Unicode header line styles are single, double");
+                       return false;
+               }
        }
 
        /* set border style/width */
@@ -2310,25 +3854,56 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
        {
                if (value)
                        popt->topt.border = atoi(value);
-
        }
 
        /* set expanded/vertical mode */
-       else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0)
+       else if (strcmp(param, "x") == 0 ||
+                        strcmp(param, "expanded") == 0 ||
+                        strcmp(param, "vertical") == 0)
        {
                if (value && pg_strcasecmp(value, "auto") == 0)
                        popt->topt.expanded = 2;
                else if (value)
-                       popt->topt.expanded = ParseVariableBool(value);
+               {
+                       bool            on_off;
+
+                       if (ParseVariableBool(value, NULL, &on_off))
+                               popt->topt.expanded = on_off ? 1 : 0;
+                       else
+                       {
+                               PsqlVarEnumError(param, value, "on, off, auto");
+                               return false;
+                       }
+               }
                else
                        popt->topt.expanded = !popt->topt.expanded;
        }
 
+       /* field separator for CSV format */
+       else if (strcmp(param, "csv_fieldsep") == 0)
+       {
+               if (value)
+               {
+                       /* CSV separator has to be a one-byte character */
+                       if (strlen(value) != 1)
+                       {
+                               pg_log_error("\\pset: csv_fieldsep must be a single one-byte character");
+                               return false;
+                       }
+                       if (value[0] == '"' || value[0] == '\n' || value[0] == '\r')
+                       {
+                               pg_log_error("\\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return");
+                               return false;
+                       }
+                       popt->topt.csvFieldSep[0] = value[0];
+               }
+       }
+
        /* locale-aware numeric output */
        else if (strcmp(param, "numericlocale") == 0)
        {
                if (value)
-                       popt->topt.numericLocale = ParseVariableBool(value);
+                       return ParseVariableBool(value, param, &popt->topt.numericLocale);
                else
                        popt->topt.numericLocale = !popt->topt.numericLocale;
        }
@@ -2383,13 +3958,13 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
        else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
        {
                if (value)
-                       popt->topt.tuples_only = ParseVariableBool(value);
+                       return ParseVariableBool(value, param, &popt->topt.tuples_only);
                else
                        popt->topt.tuples_only = !popt->topt.tuples_only;
        }
 
        /* set title override */
-       else if (strcmp(param, "title") == 0)
+       else if (strcmp(param, "C") == 0 || strcmp(param, "title") == 0)
        {
                free(popt->title);
                if (!value)
@@ -2414,21 +3989,34 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
                if (value && pg_strcasecmp(value, "always") == 0)
                        popt->topt.pager = 2;
                else if (value)
-                       if (ParseVariableBool(value))
-                               popt->topt.pager = 1;
-                       else
-                               popt->topt.pager = 0;
+               {
+                       bool            on_off;
+
+                       if (!ParseVariableBool(value, NULL, &on_off))
+                       {
+                               PsqlVarEnumError(param, value, "on, off, always");
+                               return false;
+                       }
+                       popt->topt.pager = on_off ? 1 : 0;
+               }
                else if (popt->topt.pager == 1)
                        popt->topt.pager = 0;
                else
                        popt->topt.pager = 1;
        }
 
+       /* set minimum lines for pager use */
+       else if (strcmp(param, "pager_min_lines") == 0)
+       {
+               if (value)
+                       popt->topt.pager_min_lines = atoi(value);
+       }
+
        /* disable "(x rows)" footer */
        else if (strcmp(param, "footer") == 0)
        {
                if (value)
-                       popt->topt.default_footer = ParseVariableBool(value);
+                       return ParseVariableBool(value, param, &popt->topt.default_footer);
                else
                        popt->topt.default_footer = !popt->topt.default_footer;
        }
@@ -2441,7 +4029,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
        }
        else
        {
-               psql_error("\\pset: unknown option: %s\n", param);
+               pg_log_error("\\pset: unknown option: %s", param);
                return false;
        }
 
@@ -2459,80 +4047,76 @@ printPsetInfo(const char *param, struct printQueryOpt *popt)
 
        /* show border style/width */
        if (strcmp(param, "border") == 0)
-       {
-               if (!popt->topt.border)
-                       printf(_("Border style (%s) unset.\n"), param);
-               else
-                       printf(_("Border style (%s) is %d.\n"), param,
-                                  popt->topt.border);
-       }
+               printf(_("Border style is %d.\n"), popt->topt.border);
 
        /* show the target width for the wrapped format */
        else if (strcmp(param, "columns") == 0)
        {
                if (!popt->topt.columns)
-                       printf(_("Target width (%s) unset.\n"), param);
+                       printf(_("Target width is unset.\n"));
                else
-                       printf(_("Target width (%s) is %d.\n"), param,
-                                  popt->topt.columns);
+                       printf(_("Target width is %d.\n"), popt->topt.columns);
        }
 
        /* show expanded/vertical mode */
        else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0)
        {
                if (popt->topt.expanded == 1)
-                       printf(_("Expanded display (%s) is on.\n"), param);
+                       printf(_("Expanded display is on.\n"));
                else if (popt->topt.expanded == 2)
-                       printf(_("Expanded display (%s) is used automatically.\n"), param);
+                       printf(_("Expanded display is used automatically.\n"));
                else
-                       printf(_("Expanded display (%s) is off.\n"), param);
+                       printf(_("Expanded display is off.\n"));
+       }
+
+       /* show field separator for CSV format */
+       else if (strcmp(param, "csv_fieldsep") == 0)
+       {
+               printf(_("Field separator for CSV is \"%s\".\n"),
+                          popt->topt.csvFieldSep);
        }
 
        /* show field separator for unaligned text */
        else if (strcmp(param, "fieldsep") == 0)
        {
                if (popt->topt.fieldSep.separator_zero)
-                       printf(_("Field separator (%s) is zero byte.\n"), param);
+                       printf(_("Field separator is zero byte.\n"));
                else
-                       printf(_("Field separator (%s) is \"%s\".\n"), param,
+                       printf(_("Field separator is \"%s\".\n"),
                                   popt->topt.fieldSep.separator);
        }
 
        else if (strcmp(param, "fieldsep_zero") == 0)
        {
-               printf(_("Field separator (%s) is zero byte.\n"), param);
+               printf(_("Field separator is zero byte.\n"));
        }
 
        /* show disable "(x rows)" footer */
        else if (strcmp(param, "footer") == 0)
        {
                if (popt->topt.default_footer)
-                       printf(_("Default footer (%s) is on.\n"), param);
+                       printf(_("Default footer is on.\n"));
                else
-                       printf(_("Default footer (%s) is off.\n"), param);
+                       printf(_("Default footer is off.\n"));
        }
 
        /* show format */
        else if (strcmp(param, "format") == 0)
        {
-               if (!popt->topt.format)
-                       printf(_("Output format (%s) is aligned.\n"), param);
-               else
-                       printf(_("Output format (%s) is %s.\n"), param,
-                                  _align2string(popt->topt.format));
+               printf(_("Output format is %s.\n"), _align2string(popt->topt.format));
        }
 
        /* show table line style */
        else if (strcmp(param, "linestyle") == 0)
        {
-               printf(_("Line style (%s) is %s.\n"), param,
+               printf(_("Line style is %s.\n"),
                           get_line_style(&popt->topt)->name);
        }
 
        /* show null display */
        else if (strcmp(param, "null") == 0)
        {
-               printf(_("Null display (%s) is \"%s\".\n"), param,
+               printf(_("Null display is \"%s\".\n"),
                           popt->nullPrint ? popt->nullPrint : "");
        }
 
@@ -2540,70 +4124,98 @@ printPsetInfo(const char *param, struct printQueryOpt *popt)
        else if (strcmp(param, "numericlocale") == 0)
        {
                if (popt->topt.numericLocale)
-                       printf(_("Locale-adjusted numeric output (%s) is on.\n"), param);
+                       printf(_("Locale-adjusted numeric output is on.\n"));
                else
-                       printf(_("Locale-adjusted numeric output (%s) is off.\n"), param);
+                       printf(_("Locale-adjusted numeric output is off.\n"));
        }
 
        /* show toggle use of pager */
        else if (strcmp(param, "pager") == 0)
        {
                if (popt->topt.pager == 1)
-                       printf(_("Pager (%s) is used for long output.\n"), param);
+                       printf(_("Pager is used for long output.\n"));
                else if (popt->topt.pager == 2)
-                       printf(_("Pager (%s) is always used.\n"), param);
+                       printf(_("Pager is always used.\n"));
                else
-                       printf(_("Pager usage (%s) is off.\n"), param);
+                       printf(_("Pager usage is off.\n"));
+       }
+
+       /* show minimum lines for pager use */
+       else if (strcmp(param, "pager_min_lines") == 0)
+       {
+               printf(ngettext("Pager won't be used for less than %d line.\n",
+                                               "Pager won't be used for less than %d lines.\n",
+                                               popt->topt.pager_min_lines),
+                          popt->topt.pager_min_lines);
        }
 
        /* show record separator for unaligned text */
        else if (strcmp(param, "recordsep") == 0)
        {
                if (popt->topt.recordSep.separator_zero)
-                       printf(_("Record separator (%s) is zero byte.\n"), param);
+                       printf(_("Record separator is zero byte.\n"));
                else if (strcmp(popt->topt.recordSep.separator, "\n") == 0)
-                       printf(_("Record separator (%s) is <newline>.\n"), param);
+                       printf(_("Record separator is <newline>.\n"));
                else
-                       printf(_("Record separator (%s) is \"%s\".\n"), param,
+                       printf(_("Record separator is \"%s\".\n"),
                                   popt->topt.recordSep.separator);
        }
 
        else if (strcmp(param, "recordsep_zero") == 0)
        {
-               printf(_("Record separator (%s) is zero byte.\n"), param);
+               printf(_("Record separator is zero byte.\n"));
        }
 
        /* show HTML table tag options */
        else if (strcmp(param, "T") == 0 || strcmp(param, "tableattr") == 0)
        {
                if (popt->topt.tableAttr)
-                       printf(_("Table attributes (%s) are \"%s\".\n"), param,
+                       printf(_("Table attributes are \"%s\".\n"),
                                   popt->topt.tableAttr);
                else
-                       printf(_("Table attributes (%s) unset.\n"), param);
+                       printf(_("Table attributes unset.\n"));
        }
 
        /* show title override */
-       else if (strcmp(param, "title") == 0)
+       else if (strcmp(param, "C") == 0 || strcmp(param, "title") == 0)
        {
                if (popt->title)
-                       printf(_("Title (%s) is \"%s\".\n"), param, popt->title);
+                       printf(_("Title is \"%s\".\n"), popt->title);
                else
-                       printf(_("Title (%s) unset.\n"), param);
+                       printf(_("Title is unset.\n"));
        }
 
        /* show toggle between full and tuples-only format */
        else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
        {
                if (popt->topt.tuples_only)
-                       printf(_("Tuples only (%s) is on.\n"), param);
+                       printf(_("Tuples only is on.\n"));
                else
-                       printf(_("Tuples only (%s) is off.\n"), param);
+                       printf(_("Tuples only is off.\n"));
+       }
+
+       /* Unicode style formatting */
+       else if (strcmp(param, "unicode_border_linestyle") == 0)
+       {
+               printf(_("Unicode border line style is \"%s\".\n"),
+                          _unicode_linestyle2string(popt->topt.unicode_border_linestyle));
+       }
+
+       else if (strcmp(param, "unicode_column_linestyle") == 0)
+       {
+               printf(_("Unicode column line style is \"%s\".\n"),
+                          _unicode_linestyle2string(popt->topt.unicode_column_linestyle));
+       }
+
+       else if (strcmp(param, "unicode_header_linestyle") == 0)
+       {
+               printf(_("Unicode header line style is \"%s\".\n"),
+                          _unicode_linestyle2string(popt->topt.unicode_header_linestyle));
        }
 
        else
        {
-               psql_error("\\pset: unknown option: %s\n", param);
+               pg_log_error("\\pset: unknown option: %s", param);
                return false;
        }
 
@@ -2611,6 +4223,111 @@ printPsetInfo(const char *param, struct printQueryOpt *popt)
 }
 
 
+static const char *
+pset_bool_string(bool val)
+{
+       return val ? "on" : "off";
+}
+
+
+static char *
+pset_quoted_string(const char *str)
+{
+       char       *ret = pg_malloc(strlen(str) * 2 + 3);
+       char       *r = ret;
+
+       *r++ = '\'';
+
+       for (; *str; str++)
+       {
+               if (*str == '\n')
+               {
+                       *r++ = '\\';
+                       *r++ = 'n';
+               }
+               else if (*str == '\'')
+               {
+                       *r++ = '\\';
+                       *r++ = '\'';
+               }
+               else
+                       *r++ = *str;
+       }
+
+       *r++ = '\'';
+       *r = '\0';
+
+       return ret;
+}
+
+
+/*
+ * Return a malloc'ed string for the \pset value.
+ *
+ * Note that for some string parameters, print.c distinguishes between unset
+ * and empty string, but for others it doesn't.  This function should produce
+ * output that produces the correct setting when fed back into \pset.
+ */
+static char *
+pset_value_string(const char *param, struct printQueryOpt *popt)
+{
+       Assert(param != NULL);
+
+       if (strcmp(param, "border") == 0)
+               return psprintf("%d", popt->topt.border);
+       else if (strcmp(param, "columns") == 0)
+               return psprintf("%d", popt->topt.columns);
+       else if (strcmp(param, "csv_fieldsep") == 0)
+               return pset_quoted_string(popt->topt.csvFieldSep);
+       else if (strcmp(param, "expanded") == 0)
+               return pstrdup(popt->topt.expanded == 2
+                                          ? "auto"
+                                          : pset_bool_string(popt->topt.expanded));
+       else if (strcmp(param, "fieldsep") == 0)
+               return pset_quoted_string(popt->topt.fieldSep.separator
+                                                                 ? popt->topt.fieldSep.separator
+                                                                 : "");
+       else if (strcmp(param, "fieldsep_zero") == 0)
+               return pstrdup(pset_bool_string(popt->topt.fieldSep.separator_zero));
+       else if (strcmp(param, "footer") == 0)
+               return pstrdup(pset_bool_string(popt->topt.default_footer));
+       else if (strcmp(param, "format") == 0)
+               return psprintf("%s", _align2string(popt->topt.format));
+       else if (strcmp(param, "linestyle") == 0)
+               return psprintf("%s", get_line_style(&popt->topt)->name);
+       else if (strcmp(param, "null") == 0)
+               return pset_quoted_string(popt->nullPrint
+                                                                 ? popt->nullPrint
+                                                                 : "");
+       else if (strcmp(param, "numericlocale") == 0)
+               return pstrdup(pset_bool_string(popt->topt.numericLocale));
+       else if (strcmp(param, "pager") == 0)
+               return psprintf("%d", popt->topt.pager);
+       else if (strcmp(param, "pager_min_lines") == 0)
+               return psprintf("%d", popt->topt.pager_min_lines);
+       else if (strcmp(param, "recordsep") == 0)
+               return pset_quoted_string(popt->topt.recordSep.separator
+                                                                 ? popt->topt.recordSep.separator
+                                                                 : "");
+       else if (strcmp(param, "recordsep_zero") == 0)
+               return pstrdup(pset_bool_string(popt->topt.recordSep.separator_zero));
+       else if (strcmp(param, "tableattr") == 0)
+               return popt->topt.tableAttr ? pset_quoted_string(popt->topt.tableAttr) : pstrdup("");
+       else if (strcmp(param, "title") == 0)
+               return popt->title ? pset_quoted_string(popt->title) : pstrdup("");
+       else if (strcmp(param, "tuples_only") == 0)
+               return pstrdup(pset_bool_string(popt->topt.tuples_only));
+       else if (strcmp(param, "unicode_border_linestyle") == 0)
+               return pstrdup(_unicode_linestyle2string(popt->topt.unicode_border_linestyle));
+       else if (strcmp(param, "unicode_column_linestyle") == 0)
+               return pstrdup(_unicode_linestyle2string(popt->topt.unicode_column_linestyle));
+       else if (strcmp(param, "unicode_header_linestyle") == 0)
+               return pstrdup(_unicode_linestyle2string(popt->topt.unicode_header_linestyle));
+       else
+               return pstrdup("ERROR");
+}
+
+
 
 #ifndef WIN32
 #define DEFAULT_SHELL "/bin/sh"
@@ -2654,7 +4371,7 @@ do_shell(const char *command)
 
        if (result == 127 || result == -1)
        {
-               psql_error("\\!: failed\n");
+               pg_log_error("\\!: failed");
                return false;
        }
        return true;
@@ -2667,50 +4384,75 @@ do_shell(const char *command)
  * onto a bunch of exec_command's variables to silence stupider compilers.
  */
 static bool
-do_watch(PQExpBuffer query_buf, long sleep)
+do_watch(PQExpBuffer query_buf, double sleep)
 {
+       long            sleep_ms = (long) (sleep * 1000);
        printQueryOpt myopt = pset.popt;
-       char            title[50];
+       const char *strftime_fmt;
+       const char *user_title;
+       char       *title;
+       int                     title_len;
+       int                     res = 0;
 
        if (!query_buf || query_buf->len <= 0)
        {
-               psql_error(_("\\watch cannot be used with an empty query\n"));
+               pg_log_error("\\watch cannot be used with an empty query");
                return false;
        }
 
+       /*
+        * Choose format for timestamps.  We might eventually make this a \pset
+        * option.  In the meantime, using a variable for the format suppresses
+        * overly-anal-retentive gcc warnings about %c being Y2K sensitive.
+        */
+       strftime_fmt = "%c";
+
        /*
         * Set up rendering options, in particular, disable the pager, because
         * nobody wants to be prompted while watching the output of 'watch'.
         */
-       myopt.nullPrint = NULL;
        myopt.topt.pager = 0;
 
+       /*
+        * If there's a title in the user configuration, make sure we have room
+        * for it in the title buffer.  Allow 128 bytes for the timestamp plus 128
+        * bytes for the rest.
+        */
+       user_title = myopt.title;
+       title_len = (user_title ? strlen(user_title) : 0) + 256;
+       title = pg_malloc(title_len);
+
        for (;;)
        {
-               int     res;
                time_t          timer;
+               char            timebuf[128];
                long            i;
 
                /*
-                * Prepare title for output.  XXX would it be better to use the time
-                * of completion of the command?
+                * Prepare title for output.  Note that we intentionally include a
+                * newline at the end of the title; this is somewhat historical but it
+                * makes for reasonably nicely formatted output in simple cases.
                 */
                timer = time(NULL);
-               snprintf(title, sizeof(title), _("Watch every %lds\t%s"),
-                                sleep, asctime(localtime(&timer)));
+               strftime(timebuf, sizeof(timebuf), strftime_fmt, localtime(&timer));
+
+               if (user_title)
+                       snprintf(title, title_len, _("%s\t%s (every %gs)\n"),
+                                        user_title, timebuf, sleep);
+               else
+                       snprintf(title, title_len, _("%s (every %gs)\n"),
+                                        timebuf, sleep);
                myopt.title = title;
 
                /* Run the query and print out the results */
                res = PSQLexecWatch(query_buf->data, &myopt);
 
                /*
-                * PSQLexecWatch handles the case where we can no longer
-                * repeat the query, and returns 0 or -1.
+                * PSQLexecWatch handles the case where we can no longer repeat the
+                * query, and returns 0 or -1.
                 */
-               if (res == 0)
+               if (res <= 0)
                        break;
-               if (res == -1)
-                       return false;
 
                /*
                 * Set up cancellation of 'watch' via SIGINT.  We redo this each time
@@ -2722,47 +4464,107 @@ do_watch(PQExpBuffer query_buf, long sleep)
 
                /*
                 * Enable 'watch' cancellations and wait a while before running the
-                * query again.  Break the sleep into short intervals since pg_usleep
-                * isn't interruptible on some platforms.
+                * query again.  Break the sleep into short intervals (at most 1s)
+                * since pg_usleep isn't interruptible on some platforms.
                 */
                sigint_interrupt_enabled = true;
-               for (i = 0; i < sleep; i++)
+               i = sleep_ms;
+               while (i > 0)
                {
-                       pg_usleep(1000000L);
+                       long            s = Min(i, 1000L);
+
+                       pg_usleep(s * 1000L);
                        if (cancel_pressed)
                                break;
+                       i -= s;
                }
                sigint_interrupt_enabled = false;
        }
 
+       pg_free(title);
+       return (res >= 0);
+}
+
+/*
+ * a little code borrowed from PSQLexec() to manage ECHO_HIDDEN output.
+ * returns true unless we have ECHO_HIDDEN_NOEXEC.
+ */
+static bool
+echo_hidden_command(const char *query)
+{
+       if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
+       {
+               printf(_("********* QUERY **********\n"
+                                "%s\n"
+                                "**************************\n\n"), query);
+               fflush(stdout);
+               if (pset.logfile)
+               {
+                       fprintf(pset.logfile,
+                                       _("********* QUERY **********\n"
+                                         "%s\n"
+                                         "**************************\n\n"), query);
+                       fflush(pset.logfile);
+               }
+
+               if (pset.echo_hidden == PSQL_ECHO_HIDDEN_NOEXEC)
+                       return false;
+       }
        return true;
 }
 
 /*
- * This function takes a function description, e.g. "x" or "x(int)", and
- * issues a query on the given connection to retrieve the function's OID
- * using a cast to regproc or regprocedure (as appropriate). The result,
- * if there is one, is returned at *foid.  Note that we'll fail if the
- * function doesn't exist OR if there are multiple matching candidates
- * OR if there's something syntactically wrong with the function description;
- * unfortunately it can be hard to tell the difference.
+ * Look up the object identified by obj_type and desc.  If successful,
+ * store its OID in *obj_oid and return true, else return false.
+ *
+ * Note that we'll fail if the object doesn't exist OR if there are multiple
+ * matching candidates OR if there's something syntactically wrong with the
+ * object description; unfortunately it can be hard to tell the difference.
  */
 static bool
-lookup_function_oid(PGconn *conn, const char *desc, Oid *foid)
+lookup_object_oid(EditableObjectType obj_type, const char *desc,
+                                 Oid *obj_oid)
 {
        bool            result = true;
-       PQExpBuffer query;
+       PQExpBuffer query = createPQExpBuffer();
        PGresult   *res;
 
-       query = createPQExpBuffer();
-       appendPQExpBufferStr(query, "SELECT ");
-       appendStringLiteralConn(query, desc, conn);
-       appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
-                                         strchr(desc, '(') ? "regprocedure" : "regproc");
+       switch (obj_type)
+       {
+               case EditableFunction:
+
+                       /*
+                        * We have a function description, e.g. "x" or "x(int)".  Issue a
+                        * query to retrieve the function's OID using a cast to regproc or
+                        * regprocedure (as appropriate).
+                        */
+                       appendPQExpBufferStr(query, "SELECT ");
+                       appendStringLiteralConn(query, desc, pset.db);
+                       appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
+                                                         strchr(desc, '(') ? "regprocedure" : "regproc");
+                       break;
+
+               case EditableView:
 
-       res = PQexec(conn, query->data);
+                       /*
+                        * Convert view name (possibly schema-qualified) to OID.  Note:
+                        * this code doesn't check if the relation is actually a view.
+                        * We'll detect that in get_create_object_cmd().
+                        */
+                       appendPQExpBufferStr(query, "SELECT ");
+                       appendStringLiteralConn(query, desc, pset.db);
+                       appendPQExpBuffer(query, "::pg_catalog.regclass::pg_catalog.oid");
+                       break;
+       }
+
+       if (!echo_hidden_command(query->data))
+       {
+               destroyPQExpBuffer(query);
+               return false;
+       }
+       res = PQexec(pset.db, query->data);
        if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
-               *foid = atooid(PQgetvalue(res, 0, 0));
+               *obj_oid = atooid(PQgetvalue(res, 0, 0));
        else
        {
                minimal_error_message(res);
@@ -2776,24 +4578,160 @@ lookup_function_oid(PGconn *conn, const char *desc, Oid *foid)
 }
 
 /*
- * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
- * function with the given OID.  If successful, the result is stored in buf.
+ * Construct a "CREATE OR REPLACE ..." command that describes the specified
+ * database object.  If successful, the result is stored in buf.
  */
 static bool
-get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf)
+get_create_object_cmd(EditableObjectType obj_type, Oid oid,
+                                         PQExpBuffer buf)
 {
        bool            result = true;
-       PQExpBuffer query;
+       PQExpBuffer query = createPQExpBuffer();
        PGresult   *res;
 
-       query = createPQExpBuffer();
-       printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
+       switch (obj_type)
+       {
+               case EditableFunction:
+                       printfPQExpBuffer(query,
+                                                         "SELECT pg_catalog.pg_get_functiondef(%u)",
+                                                         oid);
+                       break;
+
+               case EditableView:
+
+                       /*
+                        * pg_get_viewdef() just prints the query, so we must prepend
+                        * CREATE for ourselves.  We must fully qualify the view name to
+                        * ensure the right view gets replaced.  Also, check relation kind
+                        * to be sure it's a view.
+                        *
+                        * Starting with 9.2, views may have reloptions (security_barrier)
+                        * and from 9.4 onwards they may also have WITH [LOCAL|CASCADED]
+                        * CHECK OPTION.  These are not part of the view definition
+                        * returned by pg_get_viewdef() and so need to be retrieved
+                        * separately.  Materialized views (introduced in 9.3) may have
+                        * arbitrary storage parameter reloptions.
+                        */
+                       if (pset.sversion >= 90400)
+                       {
+                               printfPQExpBuffer(query,
+                                                                 "SELECT nspname, relname, relkind, "
+                                                                 "pg_catalog.pg_get_viewdef(c.oid, true), "
+                                                                 "pg_catalog.array_remove(pg_catalog.array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+                                                                 "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+                                                                 "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption "
+                                                                 "FROM pg_catalog.pg_class c "
+                                                                 "LEFT JOIN pg_catalog.pg_namespace n "
+                                                                 "ON c.relnamespace = n.oid WHERE c.oid = %u",
+                                                                 oid);
+                       }
+                       else if (pset.sversion >= 90200)
+                       {
+                               printfPQExpBuffer(query,
+                                                                 "SELECT nspname, relname, relkind, "
+                                                                 "pg_catalog.pg_get_viewdef(c.oid, true), "
+                                                                 "c.reloptions AS reloptions, "
+                                                                 "NULL AS checkoption "
+                                                                 "FROM pg_catalog.pg_class c "
+                                                                 "LEFT JOIN pg_catalog.pg_namespace n "
+                                                                 "ON c.relnamespace = n.oid WHERE c.oid = %u",
+                                                                 oid);
+                       }
+                       else
+                       {
+                               printfPQExpBuffer(query,
+                                                                 "SELECT nspname, relname, relkind, "
+                                                                 "pg_catalog.pg_get_viewdef(c.oid, true), "
+                                                                 "NULL AS reloptions, "
+                                                                 "NULL AS checkoption "
+                                                                 "FROM pg_catalog.pg_class c "
+                                                                 "LEFT JOIN pg_catalog.pg_namespace n "
+                                                                 "ON c.relnamespace = n.oid WHERE c.oid = %u",
+                                                                 oid);
+                       }
+                       break;
+       }
 
-       res = PQexec(conn, query->data);
+       if (!echo_hidden_command(query->data))
+       {
+               destroyPQExpBuffer(query);
+               return false;
+       }
+       res = PQexec(pset.db, query->data);
        if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
        {
                resetPQExpBuffer(buf);
-               appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0));
+               switch (obj_type)
+               {
+                       case EditableFunction:
+                               appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0));
+                               break;
+
+                       case EditableView:
+                               {
+                                       char       *nspname = PQgetvalue(res, 0, 0);
+                                       char       *relname = PQgetvalue(res, 0, 1);
+                                       char       *relkind = PQgetvalue(res, 0, 2);
+                                       char       *viewdef = PQgetvalue(res, 0, 3);
+                                       char       *reloptions = PQgetvalue(res, 0, 4);
+                                       char       *checkoption = PQgetvalue(res, 0, 5);
+
+                                       /*
+                                        * If the backend ever supports CREATE OR REPLACE
+                                        * MATERIALIZED VIEW, allow that here; but as of today it
+                                        * does not, so editing a matview definition in this way
+                                        * is impossible.
+                                        */
+                                       switch (relkind[0])
+                                       {
+#ifdef NOT_USED
+                                               case RELKIND_MATVIEW:
+                                                       appendPQExpBufferStr(buf, "CREATE OR REPLACE MATERIALIZED VIEW ");
+                                                       break;
+#endif
+                                               case RELKIND_VIEW:
+                                                       appendPQExpBufferStr(buf, "CREATE OR REPLACE VIEW ");
+                                                       break;
+                                               default:
+                                                       pg_log_error("\"%s.%s\" is not a view",
+                                                                          nspname, relname);
+                                                       result = false;
+                                                       break;
+                                       }
+                                       appendPQExpBuffer(buf, "%s.", fmtId(nspname));
+                                       appendPQExpBufferStr(buf, fmtId(relname));
+
+                                       /* reloptions, if not an empty array "{}" */
+                                       if (reloptions != NULL && strlen(reloptions) > 2)
+                                       {
+                                               appendPQExpBufferStr(buf, "\n WITH (");
+                                               if (!appendReloptionsArray(buf, reloptions, "",
+                                                                                                  pset.encoding,
+                                                                                                  standard_strings()))
+                                               {
+                                                       pg_log_error("could not parse reloptions array");
+                                                       result = false;
+                                               }
+                                               appendPQExpBufferChar(buf, ')');
+                                       }
+
+                                       /* View definition from pg_get_viewdef (a SELECT query) */
+                                       appendPQExpBuffer(buf, " AS\n%s", viewdef);
+
+                                       /* Get rid of the semicolon that pg_get_viewdef appends */
+                                       if (buf->len > 0 && buf->data[buf->len - 1] == ';')
+                                               buf->data[--(buf->len)] = '\0';
+
+                                       /* WITH [LOCAL|CASCADED] CHECK OPTION */
+                                       if (checkoption && checkoption[0] != '\0')
+                                               appendPQExpBuffer(buf, "\n WITH %s CHECK OPTION",
+                                                                                 checkoption);
+                               }
+                               break;
+               }
+               /* Make sure result ends with a newline */
+               if (buf->len > 0 && buf->data[buf->len - 1] != '\n')
+                       appendPQExpBufferChar(buf, '\n');
        }
        else
        {
@@ -2808,24 +4746,24 @@ get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf)
 }
 
 /*
- * If the given argument of \ef ends with a line number, delete the line
+ * If the given argument of \ef or \ev ends with a line number, delete the line
  * number from the argument string and return it as an integer.  (We need
- * this kluge because we're too lazy to parse \ef's function name argument
- * carefully --- we just slop it up in OT_WHOLE_LINE mode.)
+ * this kluge because we're too lazy to parse \ef's function or \ev's view
+ * argument carefully --- we just slop it up in OT_WHOLE_LINE mode.)
  *
  * Returns -1 if no line number is present, 0 on error, or a positive value
  * on success.
  */
 static int
-strip_lineno_from_funcdesc(char *func)
+strip_lineno_from_objdesc(char *obj)
 {
        char       *c;
        int                     lineno;
 
-       if (!func || func[0] == '\0')
+       if (!obj || obj[0] == '\0')
                return -1;
 
-       c = func + strlen(func) - 1;
+       c = obj + strlen(obj) - 1;
 
        /*
         * This business of parsing backwards is dangerous as can be in a
@@ -2838,20 +4776,20 @@ strip_lineno_from_funcdesc(char *func)
         */
 
        /* skip trailing whitespace */
-       while (c > func && isascii((unsigned char) *c) && isspace((unsigned char) *c))
+       while (c > obj && isascii((unsigned char) *c) && isspace((unsigned char) *c))
                c--;
 
        /* must have a digit as last non-space char */
-       if (c == func || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
+       if (c == obj || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
                return -1;
 
        /* find start of digit string */
-       while (c > func && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
+       while (c > obj && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
                c--;
 
-       /* digits must be separated from func name by space or closing paren */
-       /* notice also that we are not allowing an empty func name ... */
-       if (c == func || !isascii((unsigned char) *c) ||
+       /* digits must be separated from object name by space or closing paren */
+       /* notice also that we are not allowing an empty object name ... */
+       if (c == obj || !isascii((unsigned char) *c) ||
                !(isspace((unsigned char) *c) || *c == ')'))
                return -1;
 
@@ -2860,16 +4798,84 @@ strip_lineno_from_funcdesc(char *func)
        lineno = atoi(c);
        if (lineno < 1)
        {
-               psql_error("invalid line number: %s\n", c);
+               pg_log_error("invalid line number: %s", c);
                return 0;
        }
 
-       /* strip digit string from func */
+       /* strip digit string from object name */
        *c = '\0';
 
        return lineno;
 }
 
+/*
+ * Count number of lines in the buffer.
+ * This is used to test if pager is needed or not.
+ */
+static int
+count_lines_in_buf(PQExpBuffer buf)
+{
+       int                     lineno = 0;
+       const char *lines = buf->data;
+
+       while (*lines != '\0')
+       {
+               lineno++;
+               /* find start of next line */
+               lines = strchr(lines, '\n');
+               if (!lines)
+                       break;
+               lines++;
+       }
+
+       return lineno;
+}
+
+/*
+ * Write text at *lines to output with line numbers.
+ *
+ * If header_keyword isn't NULL, then line 1 should be the first line beginning
+ * with header_keyword; lines before that are unnumbered.
+ *
+ * Caution: this scribbles on *lines.
+ */
+static void
+print_with_linenumbers(FILE *output, char *lines,
+                                          const char *header_keyword)
+{
+       bool            in_header = (header_keyword != NULL);
+       size_t          header_sz = in_header ? strlen(header_keyword) : 0;
+       int                     lineno = 0;
+
+       while (*lines != '\0')
+       {
+               char       *eol;
+
+               if (in_header && strncmp(lines, header_keyword, header_sz) == 0)
+                       in_header = false;
+
+               /* increment lineno only for body's lines */
+               if (!in_header)
+                       lineno++;
+
+               /* find and mark end of current line */
+               eol = strchr(lines, '\n');
+               if (eol != NULL)
+                       *eol = '\0';
+
+               /* show current line as appropriate */
+               if (in_header)
+                       fprintf(output, "        %s\n", lines);
+               else
+                       fprintf(output, "%-7d %s\n", lineno, lines);
+
+               /* advance to next line, if any */
+               if (eol == NULL)
+                       break;
+               lines = ++eol;
+       }
+}
+
 /*
  * Report just the primary error; this is to avoid cluttering the output
  * with, for instance, a redisplay of the internally generated query
@@ -2892,9 +4898,9 @@ minimal_error_message(PGresult *res)
                appendPQExpBufferStr(msg, fld);
        else
                appendPQExpBufferStr(msg, "(not available)");
-       appendPQExpBufferStr(msg, "\n");
+       appendPQExpBufferChar(msg, '\n');
 
-       psql_error("%s", msg->data);
+       pg_log_error("%s", msg->data);
 
        destroyPQExpBuffer(msg);
 }