]> granicus.if.org Git - postgresql/blobdiff - src/bin/psql/common.c
Cause psql to report both the returned data and the command status tag
[postgresql] / src / bin / psql / common.c
index 9362886c4ce8ac214dfb7727840fd2237e813f90..3233dfc2faeaf48fb5b360c3832172c16dfaab68 100644 (file)
@@ -1,52 +1,71 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright 2000 by PostgreSQL Global Development Group
+ * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/bin/psql/common.c,v 1.42 2002/08/10 19:35:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.124 2006/08/13 21:10:04 tgl Exp $
  */
 #include "postgres_fe.h"
-
 #include "common.h"
 
-#include <errno.h>
-#include <stdarg.h>
-#include <sys/time.h>
+#include <ctype.h>
 #ifndef HAVE_STRDUP
 #include <strdup.h>
 #endif
 #include <signal.h>
 #ifndef WIN32
+#include <sys/time.h>
 #include <unistd.h>                            /* for write() */
-#include <setjmp.h>
 #else
 #include <io.h>                                        /* for _write() */
 #include <win32.h>
+#include <sys/timeb.h>                 /* for _ftime() */
 #endif
 
-#include "libpq-fe.h"
 #include "pqsignal.h"
 
 #include "settings.h"
-#include "variables.h"
+#include "command.h"
 #include "copy.h"
-#include "prompt.h"
-#include "print.h"
-#include "mainloop.h"
+#include "mb/pg_wchar.h"
+
+
+/* Workarounds for Windows */
+/* Probably to be moved up the source tree in the future, perhaps to be replaced by
+ * more specific checks like configure-style HAVE_GETTIMEOFDAY macros.
+ */
+#ifndef WIN32
+
+typedef struct timeval TimevalStruct;
+
+#define GETTIMEOFDAY(T) gettimeofday(T, NULL)
+#define DIFF_MSEC(T, U) \
+       ((((int) ((T)->tv_sec - (U)->tv_sec)) * 1000000.0 + \
+         ((int) ((T)->tv_usec - (U)->tv_usec))) / 1000.0)
+#else
+
+typedef struct _timeb TimevalStruct;
+
+#define GETTIMEOFDAY(T) _ftime(T)
+#define DIFF_MSEC(T, U) \
+       (((T)->time - (U)->time) * 1000.0 + \
+        ((T)->millitm - (U)->millitm))
+#endif
+
 
-extern bool prompt_state;
+static bool command_no_begin(const char *query);
 
 /*
  * "Safe" wrapper around strdup()
  */
 char *
-xstrdup(const char *string)
+pg_strdup(const char *string)
 {
        char       *tmp;
 
        if (!string)
        {
-               fprintf(stderr, gettext("%s: xstrdup: cannot duplicate null pointer (internal error)\n"),
+               fprintf(stderr, _("%s: pg_strdup: cannot duplicate null pointer (internal error)\n"),
                                pset.progname);
                exit(EXIT_FAILURE);
        }
@@ -59,7 +78,43 @@ xstrdup(const char *string)
        return tmp;
 }
 
+void *
+pg_malloc(size_t size)
+{
+       void       *tmp;
+
+       tmp = malloc(size);
+       if (!tmp)
+       {
+               psql_error("out of memory\n");
+               exit(EXIT_FAILURE);
+       }
+       return tmp;
+}
 
+void *
+pg_malloc_zero(size_t size)
+{
+       void       *tmp;
+
+       tmp = pg_malloc(size);
+       memset(tmp, 0, size);
+       return tmp;
+}
+
+void *
+pg_calloc(size_t nmemb, size_t size)
+{
+       void       *tmp;
+
+       tmp = calloc(nmemb, size);
+       if (!tmp)
+       {
+               psql_error("out of memory");
+               exit(EXIT_FAILURE);
+       }
+       return tmp;
+}
 
 /*
  * setQFout
@@ -110,10 +165,7 @@ setQFout(const char *fname)
 
        /* Direct signals */
 #ifndef WIN32
-       if (pset.queryFoutPipe)
-               pqsignal(SIGPIPE, SIG_IGN);
-       else
-               pqsignal(SIGPIPE, SIG_DFL);
+       pqsignal(SIGPIPE, pset.queryFoutPipe ? SIG_IGN : SIG_DFL);
 #endif
 
        return status;
@@ -136,16 +188,16 @@ psql_error(const char *fmt,...)
                fflush(pset.queryFout);
 
        if (pset.inputfile)
-               fprintf(stderr, "%s:%s:%u: ", pset.progname, pset.inputfile, pset.lineno);
+               fprintf(stderr, "%s:%s:" UINT64_FORMAT ": ", pset.progname, pset.inputfile, pset.lineno);
        va_start(ap, fmt);
-       vfprintf(stderr, gettext(fmt), ap);
+       vfprintf(stderr, _(fmt), ap);
        va_end(ap);
 }
 
 
 
 /*
- * for backend INFO, WARNING, ERROR
+ * for backend Notice messages (INFO, WARNING, etc)
  */
 void
 NoticeProcessor(void *arg, const char *message)
@@ -159,43 +211,288 @@ NoticeProcessor(void *arg, const char *message)
 /*
  * Code to support query cancellation
  *
- * Before we start a query, we enable a SIGINT signal catcher that sends a
+ * Before we start a query, we enable the SIGINT signal catcher to send a
  * cancel request to the backend. Note that sending the cancel directly from
- * the signal handler is safe because PQrequestCancel() is written to make it
- * so. We use write() to print to stdout because it's better to use simple
+ * the signal handler is safe because PQcancel() is written to make it
+ * so. We use write() to report to stderr because it's better to use simple
  * facilities in a signal handler.
+ *
+ * On win32, the signal cancelling happens on a separate thread, because
+ * that's how SetConsoleCtrlHandler works. The PQcancel function is safe
+ * for this (unlike PQrequestCancel). However, a CRITICAL_SECTION is required
+ * to protect the PGcancel structure against being changed while the signal
+ * thread is using it.
+ *
+ * SIGINT is supposed to abort all long-running psql operations, not only
+ * database queries.  In most places, this is accomplished by checking
+ * cancel_pressed during long-running loops.  However, that won't work when
+ * blocked on user input (in readline() or fgets()).  In those places, we
+ * set sigint_interrupt_enabled TRUE while blocked, instructing the signal
+ * catcher to longjmp through sigint_interrupt_jmp.  We assume readline and
+ * fgets are coded to handle possible interruption.  (XXX currently this does
+ * not work on win32, so control-C is less useful there)
  */
-PGconn    *cancelConn;
-volatile bool cancel_pressed;
+volatile bool sigint_interrupt_enabled = false;
 
-#ifndef WIN32
+sigjmp_buf     sigint_interrupt_jmp;
 
-#define write_stderr(String) write(fileno(stderr), String, strlen(String))
+static PGcancel * volatile cancelConn = NULL;
 
-void
+#ifdef WIN32
+static CRITICAL_SECTION cancelConnLock;
+#endif
+
+#define write_stderr(str)      write(fileno(stderr), str, strlen(str))
+
+
+#ifndef WIN32
+
+static void
 handle_sigint(SIGNAL_ARGS)
 {
        int                     save_errno = errno;
+       char            errbuf[256];
 
-       /* Don't muck around if copying in or prompting for a password. */
-       if ((copy_in_state && pset.cur_cmd_interactive) || prompt_state)
-               return;
-
-       if (cancelConn == NULL)
-               siglongjmp(main_loop_jmp, 1);
+       /* if we are waiting for input, longjmp out of it */
+       if (sigint_interrupt_enabled)
+       {
+               sigint_interrupt_enabled = false;
+               siglongjmp(sigint_interrupt_jmp, 1);
+       }
 
+       /* else, set cancel flag to stop any long-running loops */
        cancel_pressed = true;
 
-       if (PQrequestCancel(cancelConn))
-               write_stderr("Cancel request sent\n");
-       else
+       /* and send QueryCancel if we are processing a database query */
+       if (cancelConn != NULL)
        {
-               write_stderr("Could not send cancel request: ");
-               write_stderr(PQerrorMessage(cancelConn));
+               if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
+                       write_stderr("Cancel request sent\n");
+               else
+               {
+                       write_stderr("Could not send cancel request: ");
+                       write_stderr(errbuf);
+               }
        }
+
        errno = save_errno;                     /* just in case the write changed it */
 }
-#endif   /* not WIN32 */
+
+void
+setup_cancel_handler(void)
+{
+       pqsignal(SIGINT, handle_sigint);
+}
+
+#else                                                  /* WIN32 */
+
+static BOOL WINAPI
+consoleHandler(DWORD dwCtrlType)
+{
+       char            errbuf[256];
+
+       if (dwCtrlType == CTRL_C_EVENT ||
+               dwCtrlType == CTRL_BREAK_EVENT)
+       {
+               /*
+                * Can't longjmp here, because we are in wrong thread :-(
+                */
+
+               /* set cancel flag to stop any long-running loops */
+               cancel_pressed = true;
+
+               /* and send QueryCancel if we are processing a database query */
+               EnterCriticalSection(&cancelConnLock);
+               if (cancelConn != NULL)
+               {
+                       if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
+                               write_stderr("Cancel request sent\n");
+                       else
+                       {
+                               write_stderr("Could not send cancel request: ");
+                               write_stderr(errbuf);
+                       }
+               }
+               LeaveCriticalSection(&cancelConnLock);
+
+               return TRUE;
+       }
+       else
+               /* Return FALSE for any signals not being handled */
+               return FALSE;
+}
+
+void
+setup_cancel_handler(void)
+{
+       InitializeCriticalSection(&cancelConnLock);
+
+       SetConsoleCtrlHandler(consoleHandler, TRUE);
+}
+
+#endif   /* WIN32 */
+
+
+/* ConnectionUp
+ *
+ * Returns whether our backend connection is still there.
+ */
+static bool
+ConnectionUp(void)
+{
+       return PQstatus(pset.db) != CONNECTION_BAD;
+}
+
+
+
+/* CheckConnection
+ *
+ * Verify that we still have a good connection to the backend, and if not,
+ * see if it can be restored.
+ *
+ * Returns true if either the connection was still there, or it could be
+ * restored successfully; false otherwise.     If, however, there was no
+ * connection and the session is non-interactive, this will exit the program
+ * with a code of EXIT_BADCONN.
+ */
+static bool
+CheckConnection(void)
+{
+       bool            OK;
+
+       OK = ConnectionUp();
+       if (!OK)
+       {
+               if (!pset.cur_cmd_interactive)
+               {
+                       psql_error("connection to server was lost\n");
+                       exit(EXIT_BADCONN);
+               }
+
+               fputs(_("The connection to the server was lost. Attempting reset: "), stderr);
+               PQreset(pset.db);
+               OK = ConnectionUp();
+               if (!OK)
+               {
+                       fputs(_("Failed.\n"), stderr);
+                       PQfinish(pset.db);
+                       pset.db = NULL;
+                       ResetCancelConn();
+                       UnsyncVariables();
+               }
+               else
+                       fputs(_("Succeeded.\n"), stderr);
+       }
+
+       return OK;
+}
+
+
+
+/*
+ * SetCancelConn
+ *
+ * Set cancelConn to point to the current database connection.
+ */
+void
+SetCancelConn(void)
+{
+       PGcancel *oldCancelConn;
+
+#ifdef WIN32
+       EnterCriticalSection(&cancelConnLock);
+#endif
+
+       /* Free the old one if we have one */
+       oldCancelConn = cancelConn;
+       /* be sure handle_sigint doesn't use pointer while freeing */
+       cancelConn = NULL;
+
+       if (oldCancelConn != NULL)
+               PQfreeCancel(oldCancelConn);
+
+       cancelConn = PQgetCancel(pset.db);
+
+#ifdef WIN32
+       LeaveCriticalSection(&cancelConnLock);
+#endif
+}
+
+
+/*
+ * ResetCancelConn
+ *
+ * Free the current cancel connection, if any, and set to NULL.
+ */
+void
+ResetCancelConn(void)
+{
+       PGcancel *oldCancelConn;
+
+#ifdef WIN32
+       EnterCriticalSection(&cancelConnLock);
+#endif
+
+       oldCancelConn = cancelConn;
+       /* be sure handle_sigint doesn't use pointer while freeing */
+       cancelConn = NULL;
+
+       if (oldCancelConn != NULL)
+               PQfreeCancel(oldCancelConn);
+
+#ifdef WIN32
+       LeaveCriticalSection(&cancelConnLock);
+#endif
+}
+
+
+/*
+ * AcceptResult
+ *
+ * Checks whether a result is valid, giving an error message if necessary;
+ * resets cancelConn as needed, and ensures that the connection to the backend
+ * is still up.
+ *
+ * Returns true for valid result, false for error state.
+ */
+static bool
+AcceptResult(const PGresult *result, const char *query)
+{
+       bool            OK = true;
+
+       ResetCancelConn();
+
+       if (!result)
+               OK = false;
+       else
+               switch (PQresultStatus(result))
+               {
+                       case PGRES_COMMAND_OK:
+                       case PGRES_TUPLES_OK:
+                       case PGRES_EMPTY_QUERY:
+                       case PGRES_COPY_IN:
+                       case PGRES_COPY_OUT:
+                               /* Fine, do nothing */
+                               break;
+
+                       default:
+                               OK = false;
+                               break;
+               }
+
+       if (!OK)
+       {
+               const char *error = PQerrorMessage(pset.db);
+
+               if (strlen(error))
+                       psql_error("%s", error);
+
+               CheckConnection();
+       }
+
+       return OK;
+}
+
 
 
 /*
@@ -203,12 +500,22 @@ handle_sigint(SIGNAL_ARGS)
  *
  * This is the way to send "backdoor" queries (those not directly entered
  * by the user). It is subject to -E but not -e.
+ *
+ * In autocommit-off mode, a new transaction block is started if start_xact
+ * is true; nothing special is done when start_xact is false.  Typically,
+ * start_xact = false is used for SELECTs and explicit BEGIN/COMMIT commands.
+ *
+ * Caller is responsible for handling the ensuing processing if a COPY
+ * command is sent.
+ *
+ * Note: we don't bother to check PQclientEncoding; it is assumed that no
+ * caller uses this path to issue "SET CLIENT_ENCODING".
  */
 PGresult *
-PSQLexec(const char *query)
+PSQLexec(const char *query, bool start_xact)
 {
        PGresult   *res;
-       const char *var;
+       int                     echo_hidden;
 
        if (!pset.db)
        {
@@ -216,66 +523,249 @@ PSQLexec(const char *query)
                return NULL;
        }
 
-       var = GetVariable(pset.vars, "ECHO_HIDDEN");
-       if (var)
+       echo_hidden = SwitchVariable(pset.vars, "ECHO_HIDDEN", "noexec", NULL);
+       if (echo_hidden != VAR_NOTSET)
        {
-               printf("********* QUERY **********\n"
-                          "%s\n"
-                          "**************************\n\n", query);
+               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 (echo_hidden == 1)   /* noexec? */
+                       return NULL;
        }
 
-       if (var && strcmp(var, "noexec") == 0)
-               return NULL;
+       SetCancelConn();
+
+       if (start_xact && PQtransactionStatus(pset.db) == PQTRANS_IDLE &&
+               !GetVariableBool(pset.vars, "AUTOCOMMIT"))
+       {
+               res = PQexec(pset.db, "BEGIN");
+               if (PQresultStatus(res) != PGRES_COMMAND_OK)
+               {
+                       psql_error("%s", PQerrorMessage(pset.db));
+                       PQclear(res);
+                       ResetCancelConn();
+                       return NULL;
+               }
+               PQclear(res);
+       }
 
-       cancelConn = pset.db;
        res = PQexec(pset.db, query);
-       if (PQresultStatus(res) == PGRES_COPY_IN)
-               copy_in_state = true;
-       /* keep cancel connection for copy out state */
-       if (PQresultStatus(res) != PGRES_COPY_OUT)
-               cancelConn = NULL;
-
-       if (res && (PQresultStatus(res) == PGRES_COMMAND_OK ||
-                               PQresultStatus(res) == PGRES_TUPLES_OK ||
-                               PQresultStatus(res) == PGRES_COPY_IN ||
-                               PQresultStatus(res) == PGRES_COPY_OUT)
-               )
-               return res;
-       else
+
+       if (!AcceptResult(res, query) && res)
        {
-               psql_error("%s", PQerrorMessage(pset.db));
                PQclear(res);
+               res = NULL;
+       }
+
+       return res;
+}
+
+
+
+/*
+ * PrintNotifications: check for asynchronous notifications, and print them out
+ */
+static void
+PrintNotifications(void)
+{
+       PGnotify   *notify;
+
+       while ((notify = PQnotifies(pset.db)))
+       {
+               fprintf(pset.queryFout, _("Asynchronous notification \"%s\" received from server process with PID %d.\n"),
+                               notify->relname, notify->be_pid);
+               fflush(pset.queryFout);
+               PQfreemem(notify);
+       }
+}
+
+
+/*
+ * PrintQueryTuples: assuming query result is OK, print its tuples
+ *
+ * Returns true if successful, false otherwise.
+ */
+static bool
+PrintQueryTuples(const PGresult *results)
+{
+       printQueryOpt my_popt = pset.popt;
+
+       /* write output to \g argument, if any */
+       if (pset.gfname)
+       {
+               FILE       *queryFout_copy = pset.queryFout;
+               bool            queryFoutPipe_copy = pset.queryFoutPipe;
 
-               if (PQstatus(pset.db) == CONNECTION_BAD)
+               pset.queryFout = stdout;        /* so it doesn't get closed */
+
+               /* open file/pipe */
+               if (!setQFout(pset.gfname))
                {
-                       if (!pset.cur_cmd_interactive)
-                       {
-                               psql_error("connection to server was lost\n");
-                               exit(EXIT_BADCONN);
-                       }
-                       fputs(gettext("The connection to the server was lost. Attempting reset: "), stderr);
-                       PQreset(pset.db);
-                       if (PQstatus(pset.db) == CONNECTION_BAD)
-                       {
-                               fputs(gettext("Failed.\n"), stderr);
-                               PQfinish(pset.db);
-                               pset.db = NULL;
-                               SetVariable(pset.vars, "DBNAME", NULL);
-                               SetVariable(pset.vars, "HOST", NULL);
-                               SetVariable(pset.vars, "PORT", NULL);
-                               SetVariable(pset.vars, "USER", NULL);
-                               SetVariable(pset.vars, "ENCODING", NULL);
-                       }
-                       else
-                               fputs(gettext("Succeeded.\n"), stderr);
+                       pset.queryFout = queryFout_copy;
+                       pset.queryFoutPipe = queryFoutPipe_copy;
+                       return false;
                }
 
-               return NULL;
+               printQuery(results, &my_popt, pset.queryFout, pset.logfile);
+
+               /* close file/pipe, restore old setting */
+               setQFout(NULL);
+
+               pset.queryFout = queryFout_copy;
+               pset.queryFoutPipe = queryFoutPipe_copy;
+
+               free(pset.gfname);
+               pset.gfname = NULL;
+       }
+       else
+               printQuery(results, &my_popt, pset.queryFout, pset.logfile);
+
+       return true;
+}
+
+
+/*
+ * ProcessCopyResult: if command was a COPY FROM STDIN/TO STDOUT, handle it
+ *
+ * Note: Utility function for use by SendQuery() only.
+ *
+ * Returns true if the query executed successfully, false otherwise.
+ */
+static bool
+ProcessCopyResult(PGresult *results)
+{
+       bool            success = false;
+
+       if (!results)
+               return false;
+
+       switch (PQresultStatus(results))
+       {
+               case PGRES_TUPLES_OK:
+               case PGRES_COMMAND_OK:
+               case PGRES_EMPTY_QUERY:
+                       /* nothing to do here */
+                       success = true;
+                       break;
+
+               case PGRES_COPY_OUT:
+                       SetCancelConn();
+                       success = handleCopyOut(pset.db, pset.queryFout);
+                       ResetCancelConn();
+                       break;
+
+               case PGRES_COPY_IN:
+                       SetCancelConn();
+                       success = handleCopyIn(pset.db, pset.cur_cmd_source,
+                                                                  PQbinaryTuples(results));
+                       ResetCancelConn();
+                       break;
+
+               default:
+                       break;
        }
+
+       /* may need this to recover from conn loss during COPY */
+       if (!CheckConnection())
+               return false;
+
+       return success;
 }
 
 
+/*
+ * PrintQueryStatus: report command status as required
+ *
+ * Note: Utility function for use by PrintQueryResults() only.
+ */
+static void
+PrintQueryStatus(PGresult *results)
+{
+       char            buf[16];
+
+       if (!QUIET())
+       {
+               if (pset.popt.topt.format == PRINT_HTML)
+               {
+                       fputs("<p>", pset.queryFout);
+                       html_escaped_print(PQcmdStatus(results), pset.queryFout);
+                       fputs("</p>\n", pset.queryFout);
+               }
+               else
+                       fprintf(pset.queryFout, "%s\n", PQcmdStatus(results));
+       }
+
+       if (pset.logfile)
+               fprintf(pset.logfile, "%s\n", PQcmdStatus(results));
+
+       snprintf(buf, sizeof(buf), "%u", (unsigned int) PQoidValue(results));
+       SetVariable(pset.vars, "LASTOID", buf);
+}
+
+
+/*
+ * PrintQueryResults: print out query results as required
+ *
+ * Note: Utility function for use by SendQuery() only.
+ *
+ * Returns true if the query executed successfully, false otherwise.
+ */
+static bool
+PrintQueryResults(PGresult *results)
+{
+       bool            success = false;
+       const char *cmdstatus;
+
+       if (!results)
+               return false;
+
+       switch (PQresultStatus(results))
+       {
+               case PGRES_TUPLES_OK:
+                       /* print the data ... */
+                       success = PrintQueryTuples(results);
+                       /* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
+                       cmdstatus = PQcmdStatus(results);
+                       if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
+                               strncmp(cmdstatus, "UPDATE", 6) == 0 ||
+                               strncmp(cmdstatus, "DELETE", 6) == 0)
+                               PrintQueryStatus(results);
+                       break;
+
+               case PGRES_COMMAND_OK:
+                       PrintQueryStatus(results);
+                       success = true;
+                       break;
+
+               case PGRES_EMPTY_QUERY:
+                       success = true;
+                       break;
+
+               case PGRES_COPY_OUT:
+               case PGRES_COPY_IN:
+                       /* nothing to do here */
+                       success = true;
+                       break;
+
+               default:
+                       break;
+       }
+
+       fflush(pset.queryFout);
+
+       return success;
+}
+
 
 /*
  * SendQuery: send the query string to the backend
@@ -292,11 +782,14 @@ PSQLexec(const char *query)
 bool
 SendQuery(const char *query)
 {
-       bool            success = false;
        PGresult   *results;
-       PGnotify   *notify;
-       struct timeval before,after;
-       struct timezone tz;
+       TimevalStruct before,
+                               after;
+       bool            OK,
+                               on_error_rollback_savepoint = false;
+       PGTransactionStatusType transaction_status;
+       static bool on_error_rollback_warning = false;
+       const char *rollback_str;
 
        if (!pset.db)
        {
@@ -308,166 +801,443 @@ SendQuery(const char *query)
        {
                char            buf[3];
 
-               printf(gettext("***(Single step mode: Verify query)*********************************************\n"
-                                          "%s\n"
-                                          "***(press return to proceed or enter x and return to cancel)********************\n"),
+               printf(_("***(Single step mode: verify command)*******************************************\n"
+                                "%s\n"
+                                "***(press return to proceed or enter x and return to cancel)********************\n"),
                           query);
                fflush(stdout);
                if (fgets(buf, sizeof(buf), stdin) != NULL)
                        if (buf[0] == 'x')
                                return false;
        }
-       else
+       else if (VariableEquals(pset.vars, "ECHO", "queries"))
        {
-               const char *var = GetVariable(pset.vars, "ECHO");
+               puts(query);
+               fflush(stdout);
+       }
 
-               if (var && strncmp(var, "queries", strlen(var)) == 0)
-                       puts(query);
+       if (pset.logfile)
+       {
+               fprintf(pset.logfile,
+                               _("********* QUERY **********\n"
+                                 "%s\n"
+                                 "**************************\n\n"), query);
+               fflush(pset.logfile);
        }
 
-       cancelConn = pset.db;
-       if (pset.timing)
+       SetCancelConn();
+
+       transaction_status = PQtransactionStatus(pset.db);
+
+       if (transaction_status == PQTRANS_IDLE &&
+               !GetVariableBool(pset.vars, "AUTOCOMMIT") &&
+               !command_no_begin(query))
        {
-               gettimeofday(&before, &tz);
+               results = PQexec(pset.db, "BEGIN");
+               if (PQresultStatus(results) != PGRES_COMMAND_OK)
+               {
+                       psql_error("%s", PQerrorMessage(pset.db));
+                       PQclear(results);
+                       ResetCancelConn();
+                       return false;
+               }
+               PQclear(results);
+               transaction_status = PQtransactionStatus(pset.db);
        }
+
+       if (transaction_status == PQTRANS_INTRANS &&
+         (rollback_str = GetVariable(pset.vars, "ON_ERROR_ROLLBACK")) != NULL &&
+       /* !off and !interactive is 'on' */
+               pg_strcasecmp(rollback_str, "off") != 0 &&
+               (pset.cur_cmd_interactive ||
+                pg_strcasecmp(rollback_str, "interactive") != 0))
+       {
+               if (on_error_rollback_warning == false && pset.sversion < 80000)
+               {
+                       fprintf(stderr, _("The server version (%d) does not support savepoints for ON_ERROR_ROLLBACK.\n"),
+                                       pset.sversion);
+                       on_error_rollback_warning = true;
+               }
+               else
+               {
+                       results = PQexec(pset.db, "SAVEPOINT pg_psql_temporary_savepoint");
+                       if (PQresultStatus(results) != PGRES_COMMAND_OK)
+                       {
+                               psql_error("%s", PQerrorMessage(pset.db));
+                               PQclear(results);
+                               ResetCancelConn();
+                               return false;
+                       }
+                       PQclear(results);
+                       on_error_rollback_savepoint = true;
+               }
+       }
+
+       if (pset.timing)
+               GETTIMEOFDAY(&before);
+
        results = PQexec(pset.db, query);
+
+       /* these operations are included in the timing result: */
+       OK = (AcceptResult(results, query) && ProcessCopyResult(results));
+
        if (pset.timing)
+               GETTIMEOFDAY(&after);
+
+       /* but printing results isn't: */
+       if (OK)
+               OK = PrintQueryResults(results);
+
+       /* If we made a temporary savepoint, possibly release/rollback */
+       if (on_error_rollback_savepoint)
        {
-               gettimeofday(&after, &tz);
+               PGresult   *svptres;
+
+               transaction_status = PQtransactionStatus(pset.db);
+
+               /* We always rollback on an error */
+               if (transaction_status == PQTRANS_INERROR)
+                       svptres = PQexec(pset.db, "ROLLBACK TO pg_psql_temporary_savepoint");
+               /* If they are no longer in a transaction, then do nothing */
+               else if (transaction_status != PQTRANS_INTRANS)
+                       svptres = NULL;
+               else
+               {
+                       /*
+                        * Do nothing if they are messing with savepoints themselves: If
+                        * the user did RELEASE or ROLLBACK, our savepoint is gone. If
+                        * they issued a SAVEPOINT, releasing ours would remove theirs.
+                        */
+                       if (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
+                               strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
+                               strcmp(PQcmdStatus(results), "ROLLBACK") == 0)
+                               svptres = NULL;
+                       else
+                               svptres = PQexec(pset.db, "RELEASE pg_psql_temporary_savepoint");
+               }
+               if (svptres && PQresultStatus(svptres) != PGRES_COMMAND_OK)
+               {
+                       psql_error("%s", PQerrorMessage(pset.db));
+                       PQclear(results);
+                       PQclear(svptres);
+                       ResetCancelConn();
+                       return false;
+               }
+
+               PQclear(svptres);
        }
-       if (PQresultStatus(results) == PGRES_COPY_IN)
-               copy_in_state = true;
-       /* keep cancel connection for copy out state */
-       if (PQresultStatus(results) != PGRES_COPY_OUT)
-               cancelConn = NULL;
 
-       if (results == NULL)
+       PQclear(results);
+
+       /* Possible microtiming output */
+       if (OK && pset.timing)
+               printf(_("Time: %.3f ms\n"), DIFF_MSEC(&after, &before));
+
+       /* check for events that may occur during query execution */
+
+       if (pset.encoding != PQclientEncoding(pset.db) &&
+               PQclientEncoding(pset.db) >= 0)
        {
-               fputs(PQerrorMessage(pset.db), pset.queryFout);
-               success = false;
+               /* track effects of SET CLIENT_ENCODING */
+               pset.encoding = PQclientEncoding(pset.db);
+               pset.popt.topt.encoding = pset.encoding;
+               SetVariable(pset.vars, "ENCODING",
+                                       pg_encoding_to_char(pset.encoding));
        }
-       else
+
+       PrintNotifications();
+
+       return OK;
+}
+
+
+/*
+ * Advance the given char pointer over white space and SQL comments.
+ */
+static const char *
+skip_white_space(const char *query)
+{
+       int                     cnestlevel = 0; /* slash-star comment nest level */
+
+       while (*query)
        {
-               switch (PQresultStatus(results))
+               int                     mblen = PQmblen(query, pset.encoding);
+
+               /*
+                * Note: we assume the encoding is a superset of ASCII, so that for
+                * example "query[0] == '/'" is meaningful.  However, we do NOT assume
+                * that the second and subsequent bytes of a multibyte character
+                * couldn't look like ASCII characters; so it is critical to advance
+                * by mblen, not 1, whenever we haven't exactly identified the
+                * character we are skipping over.
+                */
+               if (isspace((unsigned char) *query))
+                       query += mblen;
+               else if (query[0] == '/' && query[1] == '*')
                {
-                       case PGRES_TUPLES_OK:
-                               /* write output to \g argument, if any */
-                               if (pset.gfname)
+                       cnestlevel++;
+                       query += 2;
+               }
+               else if (cnestlevel > 0 && query[0] == '*' && query[1] == '/')
+               {
+                       cnestlevel--;
+                       query += 2;
+               }
+               else if (cnestlevel == 0 && query[0] == '-' && query[1] == '-')
+               {
+                       query += 2;
+
+                       /*
+                        * We have to skip to end of line since any slash-star inside the
+                        * -- comment does NOT start a slash-star comment.
+                        */
+                       while (*query)
+                       {
+                               if (*query == '\n')
                                {
-                                       FILE       *queryFout_copy = pset.queryFout;
-                                       bool            queryFoutPipe_copy = pset.queryFoutPipe;
+                                       query++;
+                                       break;
+                               }
+                               query += PQmblen(query, pset.encoding);
+                       }
+               }
+               else if (cnestlevel > 0)
+                       query += mblen;
+               else
+                       break;                          /* found first token */
+       }
 
-                                       pset.queryFout = stdout;        /* so it doesn't get
-                                                                                                * closed */
+       return query;
+}
 
-                                       /* open file/pipe */
-                                       if (!setQFout(pset.gfname))
-                                       {
-                                               pset.queryFout = queryFout_copy;
-                                               pset.queryFoutPipe = queryFoutPipe_copy;
-                                               success = false;
-                                               break;
-                                       }
 
-                                       printQuery(results, &pset.popt, pset.queryFout);
+/*
+ * Check whether a command is one of those for which we should NOT start
+ * a new transaction block (ie, send a preceding BEGIN).
+ *
+ * These include the transaction control statements themselves, plus
+ * certain statements that the backend disallows inside transaction blocks.
+ */
+static bool
+command_no_begin(const char *query)
+{
+       int                     wordlen;
+
+       /*
+        * First we must advance over any whitespace and comments.
+        */
+       query = skip_white_space(query);
+
+       /*
+        * Check word length (since "beginx" is not "begin").
+        */
+       wordlen = 0;
+       while (isalpha((unsigned char) query[wordlen]))
+               wordlen += PQmblen(&query[wordlen], pset.encoding);
+
+       /*
+        * Transaction control commands.  These should include every keyword that
+        * gives rise to a TransactionStmt in the backend grammar, except for the
+        * savepoint-related commands.
+        *
+        * (We assume that START must be START TRANSACTION, since there is
+        * presently no other "START foo" command.)
+        */
+       if (wordlen == 5 && pg_strncasecmp(query, "abort", 5) == 0)
+               return true;
+       if (wordlen == 5 && pg_strncasecmp(query, "begin", 5) == 0)
+               return true;
+       if (wordlen == 5 && pg_strncasecmp(query, "start", 5) == 0)
+               return true;
+       if (wordlen == 6 && pg_strncasecmp(query, "commit", 6) == 0)
+               return true;
+       if (wordlen == 3 && pg_strncasecmp(query, "end", 3) == 0)
+               return true;
+       if (wordlen == 8 && pg_strncasecmp(query, "rollback", 8) == 0)
+               return true;
+       if (wordlen == 7 && pg_strncasecmp(query, "prepare", 7) == 0)
+       {
+               /* PREPARE TRANSACTION is a TC command, PREPARE foo is not */
+               query += wordlen;
 
-                                       /* close file/pipe, restore old setting */
-                                       setQFout(NULL);
+               query = skip_white_space(query);
 
-                                       pset.queryFout = queryFout_copy;
-                                       pset.queryFoutPipe = queryFoutPipe_copy;
+               wordlen = 0;
+               while (isalpha((unsigned char) query[wordlen]))
+                       wordlen += PQmblen(&query[wordlen], pset.encoding);
 
-                                       free(pset.gfname);
-                                       pset.gfname = NULL;
+               if (wordlen == 11 && pg_strncasecmp(query, "transaction", 11) == 0)
+                       return true;
+               return false;
+       }
 
-                                       success = true;
-                               }
-                               else
-                               {
-                                       printQuery(results, &pset.popt, pset.queryFout);
-                                       success = true;
-                               }
-                               break;
-                       case PGRES_EMPTY_QUERY:
-                               success = true;
-                               break;
-                       case PGRES_COMMAND_OK:
-                               {
-                                       char            buf[10];
+       /*
+        * Commands not allowed within transactions.  The statements checked for
+        * here should be exactly those that call PreventTransactionChain() in the
+        * backend.
+        *
+        * Note: we are a bit sloppy about CLUSTER, which is transactional in some
+        * variants but not others.
+        */
+       if (wordlen == 6 && pg_strncasecmp(query, "vacuum", 6) == 0)
+               return true;
+       if (wordlen == 7 && pg_strncasecmp(query, "cluster", 7) == 0)
+               return true;
+
+       /*
+        * Note: these tests will match CREATE SYSTEM, DROP SYSTEM, and REINDEX
+        * TABLESPACE, which aren't really valid commands so we don't care much.
+        * The other six possible matches are correct.
+        */
+       if ((wordlen == 6 && pg_strncasecmp(query, "create", 6) == 0) ||
+               (wordlen == 4 && pg_strncasecmp(query, "drop", 4) == 0) ||
+               (wordlen == 7 && pg_strncasecmp(query, "reindex", 7) == 0))
+       {
+               query += wordlen;
 
-                                       success = true;
-                                       sprintf(buf, "%u", (unsigned int) PQoidValue(results));
-                                       if (!QUIET())
-                                               fprintf(pset.queryFout, "%s\n", PQcmdStatus(results));
-                                       SetVariable(pset.vars, "LASTOID", buf);
-                                       break;
-                               }
-                       case PGRES_COPY_OUT:
-                               success = handleCopyOut(pset.db, pset.queryFout);
-                               break;
+               query = skip_white_space(query);
 
-                       case PGRES_COPY_IN:
-                               if (pset.cur_cmd_interactive && !QUIET())
-                                       puts(gettext("Enter data to be copied followed by a newline.\n"
-                                                                "End with a backslash and a period on a line by itself."));
+               wordlen = 0;
+               while (isalpha((unsigned char) query[wordlen]))
+                       wordlen += PQmblen(&query[wordlen], pset.encoding);
 
-                               success = handleCopyIn(pset.db, pset.cur_cmd_source,
-                                                                          pset.cur_cmd_interactive ? get_prompt(PROMPT_COPY) : NULL);
-                               break;
+               if (wordlen == 8 && pg_strncasecmp(query, "database", 8) == 0)
+                       return true;
+               if (wordlen == 6 && pg_strncasecmp(query, "system", 6) == 0)
+                       return true;
+               if (wordlen == 10 && pg_strncasecmp(query, "tablespace", 10) == 0)
+                       return true;
+       }
 
-                       case PGRES_NONFATAL_ERROR:
-                       case PGRES_FATAL_ERROR:
-                       case PGRES_BAD_RESPONSE:
-                               success = false;
-                               psql_error("%s", PQerrorMessage(pset.db));
-                               break;
-               }
+       return false;
+}
 
-               fflush(pset.queryFout);
 
-               if (PQstatus(pset.db) == CONNECTION_BAD)
-               {
-                       if (!pset.cur_cmd_interactive)
-                       {
-                               psql_error("connection to server was lost\n");
-                               exit(EXIT_BADCONN);
-                       }
-                       fputs(gettext("The connection to the server was lost. Attempting reset: "), stderr);
-                       PQreset(pset.db);
-                       if (PQstatus(pset.db) == CONNECTION_BAD)
-                       {
-                               fputs(gettext("Failed.\n"), stderr);
-                               PQfinish(pset.db);
-                               PQclear(results);
-                               pset.db = NULL;
-                               SetVariable(pset.vars, "DBNAME", NULL);
-                               SetVariable(pset.vars, "HOST", NULL);
-                               SetVariable(pset.vars, "PORT", NULL);
-                               SetVariable(pset.vars, "USER", NULL);
-                               SetVariable(pset.vars, "ENCODING", NULL);
-                               return false;
-                       }
-                       else
-                               fputs(gettext("Succeeded.\n"), stderr);
-               }
+/*
+ * Test if the current user is a database superuser.
+ *
+ * Note: this will correctly detect superuserness only with a protocol-3.0
+ * or newer backend; otherwise it will always say "false".
+ */
+bool
+is_superuser(void)
+{
+       const char *val;
+
+       if (!pset.db)
+               return false;
 
-               /* check for asynchronous notification returns */
-               while ((notify = PQnotifies(pset.db)) != NULL)
+       val = PQparameterStatus(pset.db, "is_superuser");
+
+       if (val && strcmp(val, "on") == 0)
+               return true;
+
+       return false;
+}
+
+
+/*
+ * Test if the current session uses standard string literals.
+ *
+ * Note: With a pre-protocol-3.0 connection this will always say "false",
+ * which should be the right answer.
+ */
+bool
+standard_strings(void)
+{
+       const char *val;
+
+       if (!pset.db)
+               return false;
+
+       val = PQparameterStatus(pset.db, "standard_conforming_strings");
+
+       if (val && strcmp(val, "on") == 0)
+               return true;
+
+       return false;
+}
+
+
+/*
+ * Return the session user of the current connection.
+ *
+ * Note: this will correctly detect the session user only with a
+ * protocol-3.0 or newer backend; otherwise it will return the
+ * connection user.
+ */
+const char *
+session_username(void)
+{
+       const char *val;
+
+       if (!pset.db)
+               return NULL;
+
+       val = PQparameterStatus(pset.db, "session_authorization");
+       if (val)
+               return val;
+       else
+               return PQuser(pset.db);
+}
+
+
+/* expand_tilde
+ *
+ * substitute '~' with HOME or '~username' with username's home dir
+ *
+ */
+char *
+expand_tilde(char **filename)
+{
+       if (!filename || !(*filename))
+               return NULL;
+
+       /*
+        * WIN32 doesn't use tilde expansion for file names. Also, it uses tilde
+        * for short versions of long file names, though the tilde is usually
+        * toward the end, not at the beginning.
+        */
+#ifndef WIN32
+
+       /* try tilde expansion */
+       if (**filename == '~')
+       {
+               char       *fn;
+               char            oldp,
+                                  *p;
+               struct passwd *pw;
+               char            home[MAXPGPATH];
+
+               fn = *filename;
+               *home = '\0';
+
+               p = fn + 1;
+               while (*p != '/' && *p != '\0')
+                       p++;
+
+               oldp = *p;
+               *p = '\0';
+
+               if (*(fn + 1) == '\0')
+                       get_home_path(home);    /* ~ or ~/ only */
+               else if ((pw = getpwnam(fn + 1)) != NULL)
+                       StrNCpy(home, pw->pw_dir, MAXPGPATH);           /* ~user */
+
+               *p = oldp;
+               if (strlen(home) != 0)
                {
-                       fprintf(pset.queryFout, gettext("Asynchronous NOTIFY '%s' from backend with pid %d received.\n"),
-                                       notify->relname, notify->be_pid);
-                       free(notify);
-                       fflush(pset.queryFout);
-               }
+                       char       *newfn;
 
-               if (results)
-                       PQclear(results);
-       }
+                       newfn = pg_malloc(strlen(home) + strlen(p) + 1);
+                       strcpy(newfn, home);
+                       strcat(newfn, p);
 
-       /* Possible microtiming output */
-       if (pset.timing && success)
-               printf(gettext("Total time: %.2f msec\n"),
-                          ((after.tv_sec-before.tv_sec)*1000000 + after.tv_usec - before.tv_usec) / 1000.0);
+                       free(fn);
+                       *filename = newfn;
+               }
+       }
+#endif
 
-       return success;
+       return *filename;
 }