]> granicus.if.org Git - postgresql/blobdiff - src/bin/psql/command.c
Add missing newlines at end of error messages
[postgresql] / src / bin / psql / command.c
index de9cd976ea960330f63bfef0225ee5ebfac44f72..3608b725963e56fa9a228abd5500b9c0177e5b11 100644 (file)
@@ -1,14 +1,14 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2006, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2011, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.164 2006/03/05 15:58:51 momjian Exp $
+ * src/bin/psql/command.c
  */
 #include "postgres_fe.h"
 #include "command.h"
 
-#ifdef WIN32_CLIENT_ONLY               /* needed for BCC */
+#ifdef __BORLANDC__                            /* needed for BCC */
 #undef mkdir
 #endif
 
 #include <io.h>
 #include <fcntl.h>
 #include <direct.h>
-#ifndef WIN32_CLIENT_ONLY
 #include <sys/types.h>                 /* for umask() */
 #include <sys/stat.h>                  /* for stat() */
 #endif
+#ifdef USE_SSL
+#include <openssl/ssl.h>
 #endif
 
+#include "portability/instr_time.h"
+
 #include "libpq-fe.h"
 #include "pqexpbuffer.h"
 #include "dumputils.h"
 #include "psqlscan.h"
 #include "settings.h"
 #include "variables.h"
-#include "mb/pg_wchar.h"
 
 
 /* functions for use in this file */
 static backslashResult exec_command(const char *cmd,
                         PsqlScanState scan_state,
                         PQExpBuffer query_buf);
-static bool do_edit(const char *filename_arg, PQExpBuffer query_buf);
-static bool do_connect(const char *new_dbname, const char *new_user, const char *new_host, const char *new_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 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 void minimal_error_message(PGresult *res);
+
+static void printSSLInfo(void);
+
+#ifdef WIN32
+static void checkWin32Codepage(void);
+#endif
+
 
 
 /*----------
  * HandleSlashCmds:
  *
- * Handles all the different commands that start with '\',
- * ordinarily called by MainLoop().
+ * Handles all the different commands that start with '\'.
+ * Ordinarily called by MainLoop().
  *
  * scan_state is a lexer working state that is set to continue scanning
  * just after the '\'.  The lexer is advanced past the command and all
@@ -93,30 +107,6 @@ HandleSlashCmds(PsqlScanState scan_state,
        /* And try to execute it */
        status = exec_command(cmd, scan_state, query_buf);
 
-       if (status == PSQL_CMD_UNKNOWN && strlen(cmd) > 1)
-       {
-               /*
-                * If the command was not recognized, try to parse it as a one-letter
-                * command with immediately following argument (a still-supported, but
-                * no longer encouraged, syntax).
-                */
-               char            new_cmd[2];
-
-               /* don't change cmd until we know it's okay */
-               new_cmd[0] = cmd[0];
-               new_cmd[1] = '\0';
-
-               psql_scan_slash_pushback(scan_state, cmd + 1);
-
-               status = exec_command(new_cmd, scan_state, query_buf);
-
-               if (status != PSQL_CMD_UNKNOWN)
-               {
-                       /* adjust cmd for possible messages below */
-                       cmd[1] = '\0';
-               }
-       }
-
        if (status == PSQL_CMD_UNKNOWN)
        {
                if (pset.cur_cmd_interactive)
@@ -150,9 +140,45 @@ HandleSlashCmds(PsqlScanState scan_state,
 
        free(cmd);
 
+       /* some commands write to queryFout, so make sure output is sent */
+       fflush(pset.queryFout);
+
        return status;
 }
 
+/*
+ * Read and interpret an argument to the \connect slash command.
+ */
+static char *
+read_connect_arg(PsqlScanState scan_state)
+{
+       char       *result;
+       char            quote;
+
+       /*
+        * 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.
+        */
+       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)
+               return NULL;
+
+       return result;
+}
+
+
 /*
  * Subroutine to actually try to execute a backslash command.
  */
@@ -163,7 +189,6 @@ exec_command(const char *cmd,
 {
        bool            success = true; /* indicate here if the command ran ok or
                                                                 * failed */
-       bool            quiet = QUIET();
        backslashResult status = PSQL_CMD_SKIP_LINE;
 
        /*
@@ -173,9 +198,9 @@ exec_command(const char *cmd,
        if (strcmp(cmd, "a") == 0)
        {
                if (pset.popt.topt.format != PRINT_ALIGNED)
-                       success = do_pset("format", "aligned", &pset.popt, quiet);
+                       success = do_pset("format", "aligned", &pset.popt, pset.quiet);
                else
-                       success = do_pset("format", "unaligned", &pset.popt, quiet);
+                       success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
        }
 
        /* \C -- override table title (formerly change HTML caption) */
@@ -184,21 +209,24 @@ exec_command(const char *cmd,
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_NORMAL, NULL, true);
 
-               success = do_pset("title", opt, &pset.popt, quiet);
+               success = do_pset("title", opt, &pset.popt, pset.quiet);
                free(opt);
        }
 
-       /*----------
-        * \c or \connect -- connect to new database or as different user,
-        * and/or new host and/or port
+       /*
+        * \c or \connect -- connect to database using the specified parameters.
+        *
+        * \c dbname user host port
         *
-        * \c foo bar [-]  [-]        connect to db "foo" as user "bar" on current host and port
-        * \c foo [-]  [-]  [-]       connect to db "foo" as current user on current host and port
-        * \c - bar  [-]  [-]         connect to current db as user "bar" on current host and port
-        * \c - - host.domain.tld [-] connect to default db as default user on host.domain.tld on default port
-        * \c - - - 5555              connect to default db as default user on default host at port 5555
-        * \c              connect to default db as default user
-        *----------
+        * 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)
        {
@@ -206,66 +234,13 @@ exec_command(const char *cmd,
                                   *opt2,
                                   *opt3,
                                   *opt4;
-               char            opt1q,
-                                       opt2q,
-                                       opt3q,
-                                       opt4q;
 
-               /*
-                * 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.
-                */
-               opt1 = psql_scan_slash_option(scan_state,
-                                                                         OT_SQLIDHACK, &opt1q, true);
-               opt2 = psql_scan_slash_option(scan_state,
-                                                                         OT_SQLIDHACK, &opt2q, true);
-               opt3 = psql_scan_slash_option(scan_state,
-                                                                         OT_SQLIDHACK, &opt3q, true);
-               opt4 = psql_scan_slash_option(scan_state,
-                                                                         OT_SQLIDHACK, &opt4q, true);
-
-               if (opt4)
-                       /* gave port */
-                       success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 ||
-                                                                                       strcmp(opt1, "") == 0) ? "" : opt1,
-                                                                !opt2q && (strcmp(opt2, "-") == 0 ||
-                                                                                       strcmp(opt2, "") == 0) ? "" : opt2,
-                                                                !opt3q && (strcmp(opt3, "-") == 0 ||
-                                                                                       strcmp(opt3, "") == 0) ? "" : opt3,
-                                                                !opt3q && (strcmp(opt3, "-") == 0 ||
-                                                                                       strcmp(opt3, "") == 0) ? "" : opt3);
-               if (opt3)
-                       /* gave host */
-                       success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 ||
-                                                                                       strcmp(opt1, "") == 0) ? "" : opt1,
-                                                                !opt2q && (strcmp(opt2, "-") == 0 ||
-                                                                                       strcmp(opt2, "") == 0) ? "" : opt2,
-                                                                !opt3q && (strcmp(opt3, "-") == 0 ||
-                                                                                       strcmp(opt3, "") == 0) ? "" : opt3,
-                                                                NULL);
-               if (opt2)
-                       /* gave username */
-                       success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 ||
-                                                                                       strcmp(opt1, "") == 0) ? "" : opt1,
-                                                                !opt2q && (strcmp(opt2, "-") == 0 ||
-                                                                                       strcmp(opt2, "") == 0) ? "" : opt2,
-                                                                NULL,
-                                                                NULL);
-               else if (opt1)
-                       /* gave database name */
-                       success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 ||
-                                                                                       strcmp(opt1, "") == 0) ? "" : opt1,
-                                                                "",
-                                                                NULL,
-                                                                NULL);
-               else
-                       /* connect to default db as default user */
-                       success = do_connect(NULL, NULL, NULL, NULL);
+               opt1 = read_connect_arg(scan_state);
+               opt2 = read_connect_arg(scan_state);
+               opt3 = read_connect_arg(scan_state);
+               opt4 = read_connect_arg(scan_state);
+
+               success = do_connect(opt1, opt2, opt3, opt4);
 
                free(opt1);
                free(opt2);
@@ -312,7 +287,7 @@ exec_command(const char *cmd,
                }
 
                if (pset.dirname)
-                       pfree(pset.dirname);
+                       free(pset.dirname);
                pset.dirname = pg_strdup(dir);
                canonicalize_path(pset.dirname);
 
@@ -320,13 +295,50 @@ exec_command(const char *cmd,
                        free(opt);
        }
 
+       /* \conninfo -- display information about the current connection */
+       else if (strcmp(cmd, "conninfo") == 0)
+       {
+               char       *db = PQdb(pset.db);
+               char       *host = 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 */
+                       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));
+                       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));
+               }
+       }
+
        /* \copy */
        else if (pg_strcasecmp(cmd, "copy") == 0)
        {
+               /* Default fetch-it-all-and-print mode */
+               instr_time      before,
+                                       after;
+
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_WHOLE_LINE, NULL, false);
 
+               if (pset.timing)
+                       INSTR_TIME_SET_CURRENT(before);
+
                success = do_copy(opt);
+
+               if (pset.timing && success)
+               {
+                       INSTR_TIME_SET_CURRENT(after);
+                       INSTR_TIME_SUBTRACT(after, before);
+                       printf(_("Time: %.3f ms\n"), INSTR_TIME_GET_MILLISEC(after));
+               }
+
                free(opt);
        }
 
@@ -338,44 +350,64 @@ exec_command(const char *cmd,
        else if (cmd[0] == 'd')
        {
                char       *pattern;
-               bool            show_verbose;
+               bool            show_verbose,
+                                       show_system;
 
                /* We don't do SQLID reduction on the pattern yet */
                pattern = psql_scan_slash_option(scan_state,
                                                                                 OT_NORMAL, NULL, true);
 
                show_verbose = strchr(cmd, '+') ? true : false;
+               show_system = strchr(cmd, 'S') ? true : false;
 
                switch (cmd[1])
                {
                        case '\0':
                        case '+':
+                       case 'S':
                                if (pattern)
-                                       success = describeTableDetails(pattern, show_verbose);
+                                       success = describeTableDetails(pattern, show_verbose, show_system);
                                else
                                        /* standard listing of interesting things */
-                                       success = listTables("tvs", NULL, show_verbose);
+                                       success = listTables("tvsE", NULL, show_verbose, show_system);
                                break;
                        case 'a':
-                               success = describeAggregates(pattern, show_verbose);
+                               success = describeAggregates(pattern, show_verbose, show_system);
                                break;
                        case 'b':
                                success = describeTablespaces(pattern, show_verbose);
                                break;
                        case 'c':
-                               success = listConversions(pattern);
+                               success = listConversions(pattern, show_system);
                                break;
                        case 'C':
                                success = listCasts(pattern);
                                break;
                        case 'd':
-                               success = objectDescription(pattern);
+                               if (strncmp(cmd, "ddp", 3) == 0)
+                                       success = listDefaultACLs(pattern);
+                               else
+                                       success = objectDescription(pattern, show_system);
                                break;
                        case 'D':
-                               success = listDomains(pattern);
+                               success = listDomains(pattern, show_system);
                                break;
-                       case 'f':
-                               success = describeFunctions(pattern, show_verbose);
+                       case 'f':                       /* function subsystem */
+                               switch (cmd[2])
+                               {
+                                       case '\0':
+                                       case '+':
+                                       case 'S':
+                                       case 'a':
+                                       case 'n':
+                                       case 't':
+                                       case 'w':
+                                               success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
+                                               break;
+                                       default:
+                                               status = PSQL_CMD_UNKNOWN;
+                                               break;
+                               }
                                break;
                        case 'g':
                                /* no longer distinct from \du */
@@ -384,29 +416,94 @@ exec_command(const char *cmd,
                        case 'l':
                                success = do_lo_list();
                                break;
+                       case 'L':
+                               success = listLanguages(pattern, show_verbose, show_system);
+                               break;
                        case 'n':
-                               success = listSchemas(pattern, show_verbose);
+                               success = listSchemas(pattern, show_verbose, show_system);
                                break;
                        case 'o':
-                               success = describeOperators(pattern);
+                               success = describeOperators(pattern, show_system);
+                               break;
+                       case 'O':
+                               success = listCollations(pattern, show_verbose, show_system);
                                break;
                        case 'p':
                                success = permissionsList(pattern);
                                break;
                        case 'T':
-                               success = describeTypes(pattern, show_verbose);
+                               success = describeTypes(pattern, show_verbose, show_system);
                                break;
                        case 't':
                        case 'v':
                        case 'i':
                        case 's':
-                       case 'S':
-                               success = listTables(&cmd[1], pattern, show_verbose);
+                       case 'E':
+                               success = listTables(&cmd[1], pattern, show_verbose, show_system);
+                               break;
+                       case 'r':
+                               if (cmd[2] == 'd' && cmd[3] == 's')
+                               {
+                                       char       *pattern2 = NULL;
+
+                                       if (pattern)
+                                               pattern2 = psql_scan_slash_option(scan_state,
+                                                                                                         OT_NORMAL, NULL, true);
+                                       success = listDbRoleSettings(pattern, pattern2);
+                               }
+                               else
+                                       success = PSQL_CMD_UNKNOWN;
                                break;
                        case 'u':
                                success = describeRoles(pattern, show_verbose);
                                break;
-
+                       case 'F':                       /* text search subsystem */
+                               switch (cmd[2])
+                               {
+                                       case '\0':
+                                       case '+':
+                                               success = listTSConfigs(pattern, show_verbose);
+                                               break;
+                                       case 'p':
+                                               success = listTSParsers(pattern, show_verbose);
+                                               break;
+                                       case 'd':
+                                               success = listTSDictionaries(pattern, show_verbose);
+                                               break;
+                                       case 't':
+                                               success = listTSTemplates(pattern, show_verbose);
+                                               break;
+                                       default:
+                                               status = PSQL_CMD_UNKNOWN;
+                                               break;
+                               }
+                               break;
+                       case 'e':                       /* SQL/MED subsystem */
+                               switch (cmd[2])
+                               {
+                                       case 's':
+                                               success = listForeignServers(pattern, show_verbose);
+                                               break;
+                                       case 'u':
+                                               success = listUserMappings(pattern, show_verbose);
+                                               break;
+                                       case 'w':
+                                               success = listForeignDataWrappers(pattern, show_verbose);
+                                               break;
+                                       case 't':
+                                               success = listForeignTables(pattern, show_verbose);
+                                               break;
+                                       default:
+                                               status = PSQL_CMD_UNKNOWN;
+                                               break;
+                               }
+                               break;
+                       case 'x':                       /* Extensions */
+                               if (show_verbose)
+                                       success = listExtensionContents(pattern);
+                               else
+                                       success = listExtensions(pattern);
+                               break;
                        default:
                                status = PSQL_CMD_UNKNOWN;
                }
@@ -417,13 +514,11 @@ exec_command(const char *cmd,
 
 
        /*
-        * \e or \edit -- edit the current query buffer (or a file and make it the
-        * query buffer
+        * \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)
        {
-               char       *fname;
-
                if (!query_buf)
                {
                        psql_error("no query buffer\n");
@@ -431,13 +526,142 @@ exec_command(const char *cmd,
                }
                else
                {
+                       char       *fname;
+                       char       *ln = NULL;
+                       int                     lineno = -1;
+
                        fname = psql_scan_slash_option(scan_state,
                                                                                   OT_NORMAL, NULL, true);
-                       expand_tilde(&fname);
                        if (fname)
-                               canonicalize_path(fname);
-                       status = do_edit(fname, query_buf) ? PSQL_CMD_NEWEDIT : PSQL_CMD_ERROR;
-                       free(fname);
+                       {
+                               /* try to get separate lineno arg */
+                               ln = psql_scan_slash_option(scan_state,
+                                                                                       OT_NORMAL, NULL, true);
+                               if (ln == NULL)
+                               {
+                                       /* only one arg; maybe it is lineno not fname */
+                                       if (fname[0] &&
+                                               strspn(fname, "0123456789") == strlen(fname))
+                                       {
+                                               /* all digits, so assume it is lineno */
+                                               ln = fname;
+                                               fname = NULL;
+                                       }
+                               }
+                       }
+                       if (ln)
+                       {
+                               lineno = atoi(ln);
+                               if (lineno < 1)
+                               {
+                                       psql_error("invalid line number: %s\n", ln);
+                                       status = PSQL_CMD_ERROR;
+                               }
+                       }
+                       if (status != PSQL_CMD_ERROR)
+                       {
+                               expand_tilde(&fname);
+                               if (fname)
+                                       canonicalize_path(fname);
+                               if (do_edit(fname, query_buf, lineno, NULL))
+                                       status = PSQL_CMD_NEWEDIT;
+                               else
+                                       status = PSQL_CMD_ERROR;
+                       }
+                       if (fname)
+                               free(fname);
+                       if (ln)
+                               free(ln);
+               }
+       }
+
+       /*
+        * \ef -- edit the named function, or present a blank CREATE FUNCTION
+        * template if no argument is given
+        */
+       else if (strcmp(cmd, "ef") == 0)
+       {
+               int                     lineno = -1;
+
+               if (!query_buf)
+               {
+                       psql_error("no query buffer\n");
+                       status = PSQL_CMD_ERROR;
+               }
+               else
+               {
+                       char       *func;
+                       Oid                     foid = InvalidOid;
+
+                       func = psql_scan_slash_option(scan_state,
+                                                                                 OT_WHOLE_LINE, NULL, true);
+                       lineno = strip_lineno_from_funcdesc(func);
+                       if (lineno == 0)
+                       {
+                               /* error already reported */
+                               status = PSQL_CMD_ERROR;
+                       }
+                       else if (!func)
+                       {
+                               /* 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");
+                       }
+                       else if (!lookup_function_oid(pset.db, func, &foid))
+                       {
+                               /* error already reported */
+                               status = PSQL_CMD_ERROR;
+                       }
+                       else if (!get_create_function_cmd(pset.db, foid, query_buf))
+                       {
+                               /* error already reported */
+                               status = PSQL_CMD_ERROR;
+                       }
+                       else if (lineno > 0)
+                       {
+                               /*
+                                * 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.  Increment lineno by the number of lines before that
+                                * line, so that it becomes relative to the first line of the
+                                * function definition.
+                                */
+                               const char *lines = query_buf->data;
+
+                               while (*lines != '\0')
+                               {
+                                       if (strncmp(lines, "AS ", 3) == 0)
+                                               break;
+                                       lineno++;
+                                       /* find start of next line */
+                                       lines = strchr(lines, '\n');
+                                       if (!lines)
+                                               break;
+                                       lines++;
+                               }
+                       }
+
+                       if (func)
+                               free(func);
+               }
+
+               if (status != PSQL_CMD_ERROR)
+               {
+                       bool            edited = false;
+
+                       if (!do_edit(NULL, query_buf, lineno, &edited))
+                               status = PSQL_CMD_ERROR;
+                       else if (!edited)
+                               puts(_("No changes"));
+                       else
+                               status = PSQL_CMD_NEWEDIT;
                }
        }
 
@@ -508,7 +732,7 @@ exec_command(const char *cmd,
                char       *fname = psql_scan_slash_option(scan_state,
                                                                                                   OT_NORMAL, NULL, false);
 
-               success = do_pset("fieldsep", fname, &pset.popt, quiet);
+               success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
                free(fname);
        }
 
@@ -534,6 +758,17 @@ exec_command(const char *cmd,
        {
                char       *opt = psql_scan_slash_option(scan_state,
                                                                                                 OT_WHOLE_LINE, NULL, false);
+               size_t          len;
+
+               /* strip any trailing spaces and semicolons */
+               if (opt)
+               {
+                       len = strlen(opt);
+                       while (len > 0 &&
+                                  (isspace((unsigned char) opt[len - 1])
+                                       || opt[len - 1] == ';'))
+                               opt[--len] = '\0';
+               }
 
                helpSQL(opt, pset.popt.topt.pager);
                free(opt);
@@ -543,14 +778,15 @@ exec_command(const char *cmd,
        else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
        {
                if (pset.popt.topt.format != PRINT_HTML)
-                       success = do_pset("format", "html", &pset.popt, quiet);
+                       success = do_pset("format", "html", &pset.popt, pset.quiet);
                else
-                       success = do_pset("format", "aligned", &pset.popt, quiet);
+                       success = do_pset("format", "aligned", &pset.popt, pset.quiet);
        }
 
 
-       /* \i is include file */
-       else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
+       /* \i and \ir include files */
+       else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+                       || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
        {
                char       *fname = psql_scan_slash_option(scan_state,
                                                                                                   OT_NORMAL, NULL, true);
@@ -562,8 +798,12 @@ exec_command(const char *cmd,
                }
                else
                {
+                       bool    include_relative;
+
+                       include_relative = (strcmp(cmd, "ir") == 0
+                                                               || strcmp(cmd, "include_relative") == 0);
                        expand_tilde(&fname);
-                       success = (process_file(fname, false) == EXIT_SUCCESS);
+                       success = (process_file(fname, false, include_relative) == EXIT_SUCCESS);
                        free(fname);
                }
        }
@@ -653,7 +893,7 @@ exec_command(const char *cmd,
        {
                if (query_buf && query_buf->len > 0)
                        puts(query_buf->data);
-               else if (!quiet)
+               else if (!pset.quiet)
                        puts(_("Query buffer is empty."));
                fflush(stdout);
        }
@@ -696,8 +936,9 @@ exec_command(const char *cmd,
                                PGresult   *res;
 
                                initPQExpBuffer(&buf);
-                               printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD '%s';",
-                                                                 fmtId(user), encrypted_password);
+                               printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ",
+                                                                 fmtId(user));
+                               appendStringLiteralConn(&buf, encrypted_password, pset.db);
                                res = PSQLexec(buf.data, false);
                                termPQExpBuffer(&buf);
                                if (!res)
@@ -712,6 +953,59 @@ exec_command(const char *cmd,
                free(pw2);
        }
 
+       /* \prompt -- prompt and set variable */
+       else if (strcmp(cmd, "prompt") == 0)
+       {
+               char       *opt,
+                                  *prompt_text = NULL;
+               char       *arg1,
+                                  *arg2;
+
+               arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+               arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+
+               if (!arg1)
+               {
+                       psql_error("\\%s: missing required argument\n", cmd);
+                       success = false;
+               }
+               else
+               {
+                       char       *result;
+
+                       if (arg2)
+                       {
+                               prompt_text = arg1;
+                               opt = arg2;
+                       }
+                       else
+                               opt = arg1;
+
+                       if (!pset.inputfile)
+                               result = simple_prompt(prompt_text, 4096, true);
+                       else
+                       {
+                               if (prompt_text)
+                               {
+                                       fputs(prompt_text, stdout);
+                                       fflush(stdout);
+                               }
+                               result = gets_fromFile(stdin);
+                       }
+
+                       if (!SetVariable(pset.vars, opt, result))
+                       {
+                               psql_error("\\%s: error\n", cmd);
+                               success = false;
+                       }
+
+                       free(result);
+                       if (prompt_text)
+                               free(prompt_text);
+                       free(opt);
+               }
+       }
+
        /* \pset -- set printing parameters */
        else if (strcmp(cmd, "pset") == 0)
        {
@@ -726,7 +1020,7 @@ exec_command(const char *cmd,
                        success = false;
                }
                else
-                       success = do_pset(opt0, opt1, &pset.popt, quiet);
+                       success = do_pset(opt0, opt1, &pset.popt, pset.quiet);
 
                free(opt0);
                free(opt1);
@@ -741,7 +1035,7 @@ exec_command(const char *cmd,
        {
                resetPQExpBuffer(query_buf);
                psql_scan_reset(scan_state);
-               if (!quiet)
+               if (!pset.quiet)
                        puts(_("Query buffer reset (cleared)."));
        }
 
@@ -753,8 +1047,8 @@ exec_command(const char *cmd,
 
                expand_tilde(&fname);
                /* This scrolls off the screen when using /dev/tty */
-               success = saveHistory(fname ? fname : DEVTTY);
-               if (success && !quiet && fname)
+               success = saveHistory(fname ? fname : DEVTTY, -1, false, false);
+               if (success && !pset.quiet && fname)
                        printf(gettext("Wrote history to file \"%s/%s\".\n"),
                                   pset.dirname ? pset.dirname : ".", fname);
                if (!fname)
@@ -800,13 +1094,7 @@ exec_command(const char *cmd,
                                free(opt);
                        }
 
-                       if (SetVariable(pset.vars, opt0, newval))
-                       {
-                               /* Check for special variables */
-                               if (strcmp(opt0, "VERBOSITY") == 0)
-                                       SyncVerbosityVariable();
-                       }
-                       else
+                       if (!SetVariable(pset.vars, opt0, newval))
                        {
                                psql_error("\\%s: error\n", cmd);
                                success = false;
@@ -816,10 +1104,130 @@ exec_command(const char *cmd,
                free(opt0);
        }
 
+       /* \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;
+
+               func_buf = createPQExpBuffer();
+               func = psql_scan_slash_option(scan_state,
+                                                                         OT_WHOLE_LINE, NULL, true);
+               if (!func)
+               {
+                       psql_error("function name is required\n");
+                       status = PSQL_CMD_ERROR;
+               }
+               else if (!lookup_function_oid(pset.db, func, &foid))
+               {
+                       /* error already reported */
+                       status = PSQL_CMD_ERROR;
+               }
+               else if (!get_create_function_cmd(pset.db, foid, func_buf))
+               {
+                       /* error already reported */
+                       status = PSQL_CMD_ERROR;
+               }
+               else
+               {
+                       FILE       *output;
+                       bool            is_pager;
+
+                       /* Select output stream: stdout, pager, or file */
+                       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++;
+                               }
+
+                               output = PageOutput(lineno, pset.popt.topt.pager);
+                               is_pager = true;
+                       }
+                       else
+                       {
+                               /* use previously set output file, without pager */
+                               output = pset.queryFout;
+                               is_pager = false;
+                       }
+
+                       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.
+                                */
+                               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;
+                               }
+                       }
+                       else
+                       {
+                               /* just send the function definition to output */
+                               fputs(func_buf->data, output);
+                       }
+
+                       if (is_pager)
+                               ClosePager(output);
+               }
+
+               if (func)
+                       free(func);
+               destroyPQExpBuffer(func_buf);
+       }
+
        /* \t -- turn off headers and row count */
        else if (strcmp(cmd, "t") == 0)
-               success = do_pset("tuples_only", NULL, &pset.popt, quiet);
+       {
+               char       *opt = psql_scan_slash_option(scan_state,
+                                                                                                OT_NORMAL, NULL, true);
 
+               success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
+               free(opt);
+       }
 
        /* \T -- define html <table ...> attributes */
        else if (strcmp(cmd, "T") == 0)
@@ -827,21 +1235,28 @@ exec_command(const char *cmd,
                char       *value = psql_scan_slash_option(scan_state,
                                                                                                   OT_NORMAL, NULL, false);
 
-               success = do_pset("tableattr", value, &pset.popt, quiet);
+               success = do_pset("tableattr", value, &pset.popt, pset.quiet);
                free(value);
        }
 
        /* \timing -- toggle timing of queries */
        else if (strcmp(cmd, "timing") == 0)
        {
-               pset.timing = !pset.timing;
-               if (!quiet)
+               char       *opt = psql_scan_slash_option(scan_state,
+                                                                                                OT_NORMAL, NULL, false);
+
+               if (opt)
+                       pset.timing = ParseVariableBool(opt);
+               else
+                       pset.timing = !pset.timing;
+               if (!pset.quiet)
                {
                        if (pset.timing)
                                puts(_("Timing is on."));
                        else
                                puts(_("Timing is off."));
                }
+               free(opt);
        }
 
        /* \unset */
@@ -930,7 +1345,13 @@ exec_command(const char *cmd,
 
        /* \x -- toggle expanded table representation */
        else if (strcmp(cmd, "x") == 0)
-               success = do_pset("expanded", NULL, &pset.popt, quiet);
+       {
+               char       *opt = psql_scan_slash_option(scan_state,
+                                                                                                OT_NORMAL, NULL, true);
+
+               success = do_pset("expanded", opt, &pset.popt, pset.quiet);
+               free(opt);
+       }
 
        /* \z -- list table rights (equivalent to \dp) */
        else if (strcmp(cmd, "z") == 0)
@@ -985,168 +1406,301 @@ exec_command(const char *cmd,
        return status;
 }
 
-
-
-/* do_connect
- * -- handler for \connect
- *
- * Connects to a database (new_dbname) as a certain user (new_user).
- * The new user can be NULL. A db name of "-" is the same as the old one.
- * (That is, the one currently in pset. But pset.db can also be NULL. A NULL
- * dbname is handled by libpq.)
- * Returns true if all ok, false if the new connection couldn't be established.
- * The old connection will be kept if the session is interactive.
+/*
+ * Ask the user for a password; 'username' is the username the
+ * password is for, if one has been explicitly specified. Returns a
+ * malloc'd string.
  */
-static bool
-do_connect(const char *new_dbname, const char *new_user, const char *new_host, const char *new_port)
+static char *
+prompt_for_password(const char *username)
 {
-       PGconn     *oldconn = pset.db;
-       const char *dbparam = NULL;
-       const char *userparam = NULL;
-       const char *hostparam = NULL;
-       const char *portparam = NULL;
-       const char *pwparam = NULL;
-       char       *password_prompt = NULL;
-       char       *prompted_password = NULL;
-       bool            need_pass;
-       bool            success = false;
-
-       /* Delete variables (in case we fail before setting them anew) */
-       UnsyncVariables();
-
-       /* If dbname is "" then use old name, else new one (even if NULL) */
-       if (oldconn && new_dbname && PQdb(oldconn) && strcmp(new_dbname, "") == 0)
-               dbparam = PQdb(oldconn);
-       else
-               dbparam = new_dbname;
+       char       *result;
 
-       /* If user is "" then use the old one */
-       if (new_user && PQuser(oldconn) && strcmp(new_user, "") == 0)
-               userparam = PQuser(oldconn);
+       if (username == NULL)
+               result = simple_prompt("Password: ", 100, false);
        else
-               userparam = new_user;
+       {
+               char       *prompt_text;
 
-       /* If host is "" then use the old one */
-       if (new_host && PQhost(oldconn) && strcmp(new_host, "") == 0)
-               hostparam = PQhost(oldconn);
-       else
-               hostparam = new_host;
+               prompt_text = malloc(strlen(username) + 100);
+               snprintf(prompt_text, strlen(username) + 100,
+                                _("Password for user %s: "), username);
+               result = simple_prompt(prompt_text, 100, false);
+               free(prompt_text);
+       }
 
-       /* If port is "" then use the old one */
-       if (new_port && PQport(oldconn) && strcmp(new_port, "") == 0)
-               portparam = PQport(oldconn);
-       else
-               portparam = new_port;
+       return result;
+}
 
-       if (userparam == NULL)
-               password_prompt = strdup("Password: ");
-       else
-       {
-               password_prompt = malloc(strlen(_("Password for user %s: ")) - 2 +
-                                                                strlen(userparam) + 1);
-               sprintf(password_prompt, _("Password for user %s: "), userparam);
-       }
+static bool
+param_is_newly_set(const char *old_val, const char *new_val)
+{
+       if (new_val == NULL)
+               return false;
 
-       /* need to prompt for password? */
-       if (pset.getPassword)
-               pwparam = prompted_password = simple_prompt(password_prompt, 100, false);
+       if (old_val == NULL || strcmp(old_val, new_val) != 0)
+               return true;
+
+       return false;
+}
+
+/*
+ * 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.
+ *
+ * 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)
+{
+       PGconn     *o_conn = pset.db,
+                          *n_conn;
+       char       *password = NULL;
+
+       if (!dbname)
+               dbname = PQdb(o_conn);
+       if (!user)
+               user = PQuser(o_conn);
+       if (!host)
+               host = PQhost(o_conn);
+       if (!port)
+               port = PQport(o_conn);
 
        /*
-        * Use old password (if any) if no new one given and we are reconnecting
-        * as same user
+        * 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.
+        *
+        * XXX: this behavior leads to spurious connection attempts recorded in
+        * the postmaster's log.  But libpq offers no API that would let us obtain
+        * a password and then continue with the first connection attempt.
         */
-       if (!pwparam && oldconn && PQuser(oldconn) && userparam &&
-               strcmp(PQuser(oldconn), userparam) == 0)
-               pwparam = PQpass(oldconn);
+       if (pset.getPassword == TRI_YES)
+       {
+               password = prompt_for_password(user);
+       }
+       else if (o_conn && user && strcmp(PQuser(o_conn), user) == 0)
+       {
+               password = strdup(PQpass(o_conn));
+       }
 
-       do
+       while (true)
        {
-               need_pass = false;
-               pset.db = PQsetdbLogin(hostparam, portparam,
-                                                          NULL, NULL, dbparam, userparam, pwparam);
+#define PARAMS_ARRAY_SIZE      8
+               const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords));
+               const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values));
+
+               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;
+
+               n_conn = PQconnectdbParams(keywords, values, true);
+
+               free(keywords);
+               free(values);
+
+               /* We can immediately discard the password -- no longer needed */
+               if (password)
+                       free(password);
+
+               if (PQstatus(n_conn) == CONNECTION_OK)
+                       break;
+
+               /*
+                * Connection attempt failed; either retry the connection attempt with
+                * a new password, or give up.
+                */
+               if (!password && PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO)
+               {
+                       PQfinish(n_conn);
+                       password = prompt_for_password(user);
+                       continue;
+               }
 
-               if (PQstatus(pset.db) == CONNECTION_BAD &&
-                       strcmp(PQerrorMessage(pset.db), PQnoPasswordSupplied) == 0 &&
-                       !feof(stdin))
+               /*
+                * Failed to connect to the database. In interactive mode, keep the
+                * previous connection to the DB; in scripting mode, close our
+                * previous connection as well.
+                */
+               if (pset.cur_cmd_interactive)
                {
-                       PQfinish(pset.db);
-                       need_pass = true;
-                       free(prompted_password);
-                       prompted_password = NULL;
-                       pwparam = prompted_password = simple_prompt(password_prompt, 100, false);
+                       psql_error("%s", PQerrorMessage(n_conn));
+
+                       /* pset.db is left unmodified */
+                       if (o_conn)
+                               fputs(_("Previous connection kept\n"), stderr);
+               }
+               else
+               {
+                       psql_error("\\connect: %s", PQerrorMessage(n_conn));
+                       if (o_conn)
+                       {
+                               PQfinish(o_conn);
+                               pset.db = NULL;
+                       }
                }
-       } while (need_pass);
 
-       free(prompted_password);
-       free(password_prompt);
+               PQfinish(n_conn);
+               return false;
+       }
 
        /*
-        * If connection failed, try at least keep the old one. That's probably
-        * more convenient than just kicking you out of the program.
+        * Replace the old connection with the new one, and update
+        * connection-dependent variables.
         */
-       if (!pset.db || PQstatus(pset.db) == CONNECTION_BAD)
+       PQsetNoticeProcessor(n_conn, NoticeProcessor, NULL);
+       pset.db = n_conn;
+       SyncVariables();
+       connection_warnings(false); /* Must be after SyncVariables */
+
+       /* Tell the user about the new connection */
+       if (!pset.quiet)
        {
-               if (pset.cur_cmd_interactive)
+               if (param_is_newly_set(PQhost(o_conn), PQhost(pset.db)) ||
+                       param_is_newly_set(PQport(o_conn), PQport(pset.db)))
                {
-                       psql_error("%s", PQerrorMessage(pset.db));
-                       PQfinish(pset.db);
-                       if (oldconn)
-                       {
-                               fputs(_("Previous connection kept\n"), stderr);
-                               pset.db = oldconn;
-                       }
+                       char       *host = PQhost(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));
                        else
-                               pset.db = NULL;
+                               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
-               {
-                       /*
-                        * we don't want unpredictable things to happen in scripting mode
-                        */
-                       psql_error("\\connect: %s", PQerrorMessage(pset.db));
-                       PQfinish(pset.db);
-                       if (oldconn)
-                               PQfinish(oldconn);
-                       pset.db = NULL;
-               }
+                       printf(_("You are now connected to database \"%s\" as user \"%s\".\n"),
+                                  PQdb(pset.db), PQuser(pset.db));
        }
-       else
+
+       if (o_conn)
+               PQfinish(o_conn);
+       return true;
+}
+
+
+void
+connection_warnings(bool in_startup)
+{
+       if (!pset.quiet && !pset.notty)
        {
-               if (!QUIET())
+               int                     client_ver = parse_version(PG_VERSION);
+
+               if (pset.sversion != client_ver)
                {
-                       if ((hostparam == new_host) && (portparam == new_port)) /* no new host or port */
-                       {
-                               if (userparam != new_user)      /* no new user */
-                                       printf(_("You are now connected to database \"%s\".\n"), dbparam);
-                               else if (dbparam != new_dbname)         /* no new db */
-                                       printf(_("You are now connected as new user \"%s\".\n"), new_user);
-                               else
-                                       /* both new */
-                                       printf(_("You are now connected to database \"%s\" as user \"%s\".\n"),
-                                                  PQdb(pset.db), PQuser(pset.db));
-                       }
-                       else /* At least one of host and port are new */
+                       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");
+                       if (!server_version)
                        {
-                               printf(
-                                       _("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port %s.\n"),
-                                       PQdb(pset.db), PQuser(pset.db), PQhost(pset.db),
-                                       PQport(pset.db));
+                               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;
                        }
+
+                       printf(_("%s (%s, server %s)\n"),
+                                  pset.progname, PG_VERSION, server_version);
                }
+               /* For version match, only print psql banner on startup. */
+               else if (in_startup)
+                       printf("%s (%s)\n", pset.progname, PG_VERSION);
 
-               if (oldconn)
-                       PQfinish(oldconn);
+               if (pset.sversion / 100 != client_ver / 100)
+                       printf(_("WARNING: %s version %d.%d, server version %d.%d.\n"
+                                        "         Some psql features might not work.\n"),
+                                pset.progname, client_ver / 10000, (client_ver / 100) % 100,
+                                  pset.sversion / 10000, (pset.sversion / 100) % 100);
 
-               success = true;
+#ifdef WIN32
+               checkWin32Codepage();
+#endif
+               printSSLInfo();
        }
+}
 
-       PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
 
-       /* Update variables */
-       SyncVariables();
+/*
+ * printSSLInfo
+ *
+ * Prints information about the current SSL connection, if SSL is in use
+ */
+static void
+printSSLInfo(void)
+{
+#ifdef USE_SSL
+       int                     sslbits = -1;
+       SSL                *ssl;
+
+       ssl = PQgetssl(pset.db);
+       if (!ssl)
+               return;                                 /* no SSL */
+
+       SSL_get_cipher_bits(ssl, &sslbits);
+       printf(_("SSL connection (cipher: %s, bits: %d)\n"),
+                  SSL_get_cipher(ssl), sslbits);
+#else
+
+       /*
+        * 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
+}
+
+
+/*
+ * checkWin32Codepage
+ *
+ * Prints a warning when win32 console codepage differs from Windows codepage
+ */
+#ifdef WIN32
+static void
+checkWin32Codepage(void)
+{
+       unsigned int wincp,
+                               concp;
 
-       return success;
+       wincp = GetACP();
+       concp = GetConsoleCP();
+       if (wincp != concp)
+       {
+               printf(_("WARNING: Console code page (%u) differs from Windows code page (%u)\n"
+                                "         8-bit characters might not work correctly. See psql reference\n"
+                                "         page \"Notes for Windows users\" for details.\n"),
+                          concp, wincp);
+       }
 }
+#endif
 
 
 /*
@@ -1161,6 +1715,7 @@ SyncVariables(void)
        /* get stuff from connection */
        pset.encoding = PQclientEncoding(pset.db);
        pset.popt.topt.encoding = pset.encoding;
+       pset.sversion = PQserverVersion(pset.db);
 
        SetVariable(pset.vars, "DBNAME", PQdb(pset.db));
        SetVariable(pset.vars, "USER", PQuser(pset.db));
@@ -1169,7 +1724,7 @@ SyncVariables(void)
        SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding));
 
        /* send stuff to it, too */
-       SyncVerbosityVariable();
+       PQsetErrorVerbosity(pset.db, pset.verbosity);
 }
 
 /*
@@ -1187,32 +1742,6 @@ UnsyncVariables(void)
        SetVariable(pset.vars, "ENCODING", NULL);
 }
 
-/*
- * Update connection state from VERBOSITY variable
- */
-void
-SyncVerbosityVariable(void)
-{
-       switch (SwitchVariable(pset.vars, "VERBOSITY",
-                                                  "default", "terse", "verbose", NULL))
-       {
-               case 1:                 /* default */
-                       pset.verbosity = PQERRORS_DEFAULT;
-                       break;
-               case 2:                 /* terse */
-                       pset.verbosity = PQERRORS_TERSE;
-                       break;
-               case 3:                 /* verbose */
-                       pset.verbosity = PQERRORS_VERBOSE;
-                       break;
-               default:                                /* not set or unrecognized value */
-                       pset.verbosity = PQERRORS_DEFAULT;
-                       break;
-       }
-
-       PQsetErrorVerbosity(pset.db, pset.verbosity);
-}
-
 
 /*
  * do_edit -- handler for \e
@@ -1220,11 +1749,11 @@ SyncVerbosityVariable(void)
  * If you do not specify a filename, the current query buffer will be copied
  * into a temporary one.
  */
-
 static bool
-editFile(const char *fname)
+editFile(const char *fname, int lineno)
 {
        const char *editorName;
+       const char *editor_lineno_arg = NULL;
        char       *sys;
        int                     result;
 
@@ -1239,6 +1768,29 @@ editFile(const char *fname)
        if (!editorName)
                editorName = DEFAULT_EDITOR;
 
+       /* Get line number argument, if we need it. */
+       if (lineno > 0)
+       {
+               editor_lineno_arg = getenv("PSQL_EDITOR_LINENUMBER_ARG");
+#ifdef DEFAULT_EDITOR_LINENUMBER_ARG
+               if (!editor_lineno_arg)
+                       editor_lineno_arg = DEFAULT_EDITOR_LINENUMBER_ARG;
+#endif
+               if (!editor_lineno_arg)
+               {
+                       psql_error("environment variable PSQL_EDITOR_LINENUMBER_ARG must be set to specify a line number\n");
+                       return false;
+               }
+       }
+
+       /* Allocate sufficient memory for command line. */
+       if (lineno > 0)
+               sys = pg_malloc(strlen(editorName)
+                                               + strlen(editor_lineno_arg) + 10                /* for integer */
+                                               + 1 + strlen(fname) + 10 + 1);
+       else
+               sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+
        /*
         * On Unix the EDITOR value should *not* be quoted, since it might include
         * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
@@ -1246,12 +1798,20 @@ editFile(const char *fname)
         * severe brain damage in their command shell plus the fact that standard
         * program paths include spaces.
         */
-       sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
 #ifndef WIN32
-       sprintf(sys, "exec %s '%s'", editorName, fname);
+       if (lineno > 0)
+               sprintf(sys, "exec %s %s%d '%s'",
+                               editorName, editor_lineno_arg, lineno, fname);
+       else
+               sprintf(sys, "exec %s '%s'",
+                               editorName, fname);
 #else
-       sprintf(sys, "%s\"%s\" \"%s\"%s",
-                       SYSTEMQUOTE, editorName, fname, SYSTEMQUOTE);
+       if (lineno > 0)
+               sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE,
+                               editorName, editor_lineno_arg, lineno, fname);
+       else
+               sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE,
+                               editorName, fname);
 #endif
        result = system(sys);
        if (result == -1)
@@ -1266,7 +1826,8 @@ editFile(const char *fname)
 
 /* call this one */
 static bool
-do_edit(const char *filename_arg, PQExpBuffer query_buf)
+do_edit(const char *filename_arg, PQExpBuffer query_buf,
+               int lineno, bool *edited)
 {
        char            fnametmp[MAXPGPATH];
        FILE       *stream = NULL;
@@ -1274,10 +1835,8 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf)
        bool            error = false;
        int                     fd;
 
-#ifndef WIN32_CLIENT_ONLY
        struct stat before,
                                after;
-#endif
 
        if (filename_arg)
                fname = filename_arg;
@@ -1296,7 +1855,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf)
                ret = GetTempPath(MAXPGPATH, tmpdir);
                if (ret == 0 || ret > MAXPGPATH)
                {
-                       psql_error("cannot locate temporary directory: %s",
+                       psql_error("could not locate temporary directory: %s\n",
                                           !ret ? strerror(errno) : "");
                        return false;
                }
@@ -1352,19 +1911,16 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf)
                }
        }
 
-#ifndef WIN32_CLIENT_ONLY
        if (!error && stat(fname, &before) != 0)
        {
                psql_error("%s: %s\n", fname, strerror(errno));
                error = true;
        }
-#endif
 
        /* call editor */
        if (!error)
-               error = !editFile(fname);
+               error = !editFile(fname, lineno);
 
-#ifndef WIN32_CLIENT_ONLY
        if (!error && stat(fname, &after) != 0)
        {
                psql_error("%s: %s\n", fname, strerror(errno));
@@ -1373,10 +1929,6 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf)
 
        if (!error && before.st_mtime != after.st_mtime)
        {
-#else
-       if (!error)
-       {
-#endif
                stream = fopen(fname, PG_BINARY_R);
                if (!stream)
                {
@@ -1385,7 +1937,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf)
                }
                else
                {
-                       /* read file back in */
+                       /* read file back into query_buf */
                        char            line[1024];
 
                        resetPQExpBuffer(query_buf);
@@ -1397,18 +1949,13 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf)
                                psql_error("%s: %s\n", fname, strerror(errno));
                                error = true;
                        }
+                       else if (edited)
+                       {
+                               *edited = true;
+                       }
 
-#ifdef USE_READLINE
-#ifdef HAVE_REPLACE_HISTORY_ENTRY
-
-                       replace_history_entry(where_history(), query_buf->data, NULL);
-#else
-                       add_history(query_buf->data);
-#endif
-#endif
                        fclose(stream);
                }
-
        }
 
        /* remove temp file */
@@ -1430,22 +1977,49 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf)
  * process_file
  *
  * Read commands from filename and then them to the main processing loop
- * Handler for \i, but can be used for other things as well.  Returns
+ * Handler for \i and \ir, but can be used for other things as well.  Returns
  * MainLoop() error code.
+ *
+ * If use_relative_path is true and filename is not an absolute path, then open
+ * the file from where the currently processed file (if any) is located.
  */
 int
-process_file(char *filename, bool single_txn)
+process_file(char *filename, bool single_txn, bool use_relative_path)
 {
        FILE       *fd;
        int                     result;
        char       *oldfilename;
-       PGresult   *res; 
+       char            relpath[MAXPGPATH];
+       PGresult   *res;
 
        if (!filename)
                return EXIT_FAILURE;
 
-       canonicalize_path(filename);
-       fd = fopen(filename, PG_BINARY_R);
+       if (strcmp(filename, "-") != 0)
+       {
+               canonicalize_path(filename);
+
+               /*
+                * If we were asked to resolve the pathname relative to the location
+                * of the currently executing script, and there is one, and this is
+                * a relative pathname, then prepend all but the last pathname
+                * component of the current script to this pathname.
+                */
+               if (use_relative_path && pset.inputfile && !is_absolute_path(filename)
+                       && !has_drive_prefix(filename))
+               {
+                       snprintf(relpath, MAXPGPATH, "%s", pset.inputfile);
+                       get_parent_directory(relpath);
+                       join_path_components(relpath, relpath, filename);
+                       canonicalize_path(relpath);
+
+                       filename = relpath;
+               }
+
+               fd = fopen(filename, PG_BINARY_R);
+       }
+       else
+               fd = stdin;
 
        if (!fd)
        {
@@ -1456,13 +2030,40 @@ process_file(char *filename, bool single_txn)
        oldfilename = pset.inputfile;
        pset.inputfile = filename;
 
-    if (single_txn)
-        res = PSQLexec("BEGIN", false);
+       if (single_txn)
+       {
+               if ((res = PSQLexec("BEGIN", false)) == NULL)
+               {
+                       if (pset.on_error_stop)
+                       {
+                               result = EXIT_USER;
+                               goto error;
+                       }
+               }
+               else
+                       PQclear(res);
+       }
+
        result = MainLoop(fd);
-    if (single_txn)
-        res = PSQLexec("COMMIT", false);
 
-       fclose(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;
        return result;
 }
@@ -1487,6 +2088,9 @@ _align2string(enum printFormat in)
                case PRINT_ALIGNED:
                        return "aligned";
                        break;
+               case PRINT_WRAPPED:
+                       return "wrapped";
+                       break;
                case PRINT_HTML:
                        return "html";
                        break;
@@ -1520,6 +2124,8 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
                        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)
@@ -1528,7 +2134,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
                        popt->topt.format = PRINT_TROFF_MS;
                else
                {
-                       psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
+                       psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
                        return false;
                }
 
@@ -1536,6 +2142,28 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
                        printf(_("Output format is %s.\n"), _align2string(popt->topt.format));
        }
 
+       /* set table line style */
+       else if (strcmp(param, "linestyle") == 0)
+       {
+               if (!value)
+                       ;
+               else if (pg_strncasecmp("ascii", value, vallen) == 0)
+                       popt->topt.line_style = &pg_asciiformat;
+               else if (pg_strncasecmp("old-ascii", value, vallen) == 0)
+                       popt->topt.line_style = &pg_asciiformat_old;
+               else if (pg_strncasecmp("unicode", value, vallen) == 0)
+                       popt->topt.line_style = &pg_utf8format;
+               else
+               {
+                       psql_error("\\pset: allowed line styles are ascii, old-ascii, unicode\n");
+                       return false;
+               }
+
+               if (!quiet)
+                       printf(_("Line style is %s.\n"),
+                                  get_line_style(&popt->topt)->name);
+       }
+
        /* set border style/width */
        else if (strcmp(param, "border") == 0)
        {
@@ -1549,7 +2177,10 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
        /* set expanded/vertical mode */
        else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0)
        {
-               popt->topt.expanded = !popt->topt.expanded;
+               if (value)
+                       popt->topt.expanded = ParseVariableBool(value);
+               else
+                       popt->topt.expanded = !popt->topt.expanded;
                if (!quiet)
                        printf(popt->topt.expanded
                                   ? _("Expanded display is on.\n")
@@ -1559,7 +2190,10 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
        /* locale-aware numeric output */
        else if (strcmp(param, "numericlocale") == 0)
        {
-               popt->topt.numericLocale = !popt->topt.numericLocale;
+               if (value)
+                       popt->topt.numericLocale = ParseVariableBool(value);
+               else
+                       popt->topt.numericLocale = !popt->topt.numericLocale;
                if (!quiet)
                {
                        if (popt->topt.numericLocale)
@@ -1613,7 +2247,10 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
        /* toggle between full and tuples-only format */
        else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
        {
-               popt->topt.tuples_only = !popt->topt.tuples_only;
+               if (value)
+                       popt->topt.tuples_only = ParseVariableBool(value);
+               else
+                       popt->topt.tuples_only = !popt->topt.tuples_only;
                if (!quiet)
                {
                        if (popt->topt.tuples_only)
@@ -1664,6 +2301,11 @@ 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;
                else if (popt->topt.pager == 1)
                        popt->topt.pager = 0;
                else
@@ -1682,7 +2324,10 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
        /* disable "(x rows)" footer */
        else if (strcmp(param, "footer") == 0)
        {
-               popt->default_footer = !popt->default_footer;
+               if (value)
+                       popt->default_footer = ParseVariableBool(value);
+               else
+                       popt->default_footer = !popt->default_footer;
                if (!quiet)
                {
                        if (popt->default_footer)
@@ -1692,6 +2337,16 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
                }
        }
 
+       /* set border style/width */
+       else if (strcmp(param, "columns") == 0)
+       {
+               if (value)
+                       popt->topt.columns = atoi(value);
+
+               if (!quiet)
+                       printf(_("Target width for \"wrapped\" format is %d.\n"), popt->topt.columns);
+       }
+
        else
        {
                psql_error("\\pset: unknown option: %s\n", param);
@@ -1737,9 +2392,8 @@ do_shell(const char *command)
                /* See EDITOR handling comment for an explaination */
                                "exec %s", shellName);
 #else
-               sprintf(sys,
                /* See EDITOR handling comment for an explaination */
-                               "%s\"%s\"%s", SYSTEMQUOTE, shellName, SYSTEMQUOTE);
+               sprintf(sys, SYSTEMQUOTE "\"%s\"" SYSTEMQUOTE, shellName);
 #endif
                result = system(sys);
                free(sys);
@@ -1754,3 +2408,164 @@ do_shell(const char *command)
        }
        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.
+ */
+static bool
+lookup_function_oid(PGconn *conn, const char *desc, Oid *foid)
+{
+       bool            result = true;
+       PQExpBuffer query;
+       PGresult   *res;
+
+       query = createPQExpBuffer();
+       printfPQExpBuffer(query, "SELECT ");
+       appendStringLiteralConn(query, desc, conn);
+       appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
+                                         strchr(desc, '(') ? "regprocedure" : "regproc");
+
+       res = PQexec(conn, query->data);
+       if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
+               *foid = atooid(PQgetvalue(res, 0, 0));
+       else
+       {
+               minimal_error_message(res);
+               result = false;
+       }
+
+       PQclear(res);
+       destroyPQExpBuffer(query);
+
+       return result;
+}
+
+/*
+ * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
+ * function with the given OID.  If successful, the result is stored in buf.
+ */
+static bool
+get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf)
+{
+       bool            result = true;
+       PQExpBuffer query;
+       PGresult   *res;
+
+       query = createPQExpBuffer();
+       printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
+
+       res = PQexec(conn, query->data);
+       if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
+       {
+               resetPQExpBuffer(buf);
+               appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0));
+       }
+       else
+       {
+               minimal_error_message(res);
+               result = false;
+       }
+
+       PQclear(res);
+       destroyPQExpBuffer(query);
+
+       return result;
+}
+
+/*
+ * If the given argument of \ef 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.)
+ *
+ * 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)
+{
+       char       *c;
+       int                     lineno;
+
+       if (!func || func[0] == '\0')
+               return -1;
+
+       c = func + strlen(func) - 1;
+
+       /*
+        * This business of parsing backwards is dangerous as can be in a
+        * multibyte environment: there is no reason to believe that we are
+        * looking at the first byte of a character, nor are we necessarily
+        * working in a "safe" encoding.  Fortunately the bitpatterns we are
+        * looking for are unlikely to occur as non-first bytes, but beware of
+        * trying to expand the set of cases that can be recognized.  We must
+        * guard the <ctype.h> macros by using isascii() first, too.
+        */
+
+       /* skip trailing whitespace */
+       while (c > func && 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))
+               return -1;
+
+       /* find start of digit string */
+       while (c > func && 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) ||
+               !(isspace((unsigned char) *c) || *c == ')'))
+               return -1;
+
+       /* parse digit string */
+       c++;
+       lineno = atoi(c);
+       if (lineno < 1)
+       {
+               psql_error("invalid line number: %s\n", c);
+               return 0;
+       }
+
+       /* strip digit string from func */
+       *c = '\0';
+
+       return lineno;
+}
+
+/*
+ * Report just the primary error; this is to avoid cluttering the output
+ * with, for instance, a redisplay of the internally generated query
+ */
+static void
+minimal_error_message(PGresult *res)
+{
+       PQExpBuffer msg;
+       const char *fld;
+
+       msg = createPQExpBuffer();
+
+       fld = PQresultErrorField(res, PG_DIAG_SEVERITY);
+       if (fld)
+               printfPQExpBuffer(msg, "%s:  ", fld);
+       else
+               printfPQExpBuffer(msg, "ERROR:  ");
+       fld = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
+       if (fld)
+               appendPQExpBufferStr(msg, fld);
+       else
+               appendPQExpBufferStr(msg, "(not available)");
+       appendPQExpBufferStr(msg, "\n");
+
+       psql_error("%s", msg->data);
+
+       destroyPQExpBuffer(msg);
+}