]> 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 1c26eb6b84ed7f72506a3b7be475a57aa76129f3..3233dfc2faeaf48fb5b360c3832172c16dfaab68 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2005, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.95 2005/01/01 05:43:08 momjian 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"
 #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"
 
 
@@ -48,7 +42,6 @@ typedef struct timeval TimevalStruct;
 #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;
@@ -59,12 +52,9 @@ typedef struct _timeb TimevalStruct;
         ((T)->millitm - (U)->millitm))
 #endif
 
-extern bool prompt_state;
-
 
 static bool command_no_begin(const char *query);
 
-
 /*
  * "Safe" wrapper around strdup()
  */
@@ -75,7 +65,7 @@ pg_strdup(const char *string)
 
        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);
        }
@@ -198,9 +188,9 @@ 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);
 }
 
@@ -221,74 +211,100 @@ 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 PQcancel() is written to make it
- * so. We use write() to print to stderr because it's better to use simple
+ * 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 other
+ * 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)
  */
-static PGcancel *cancelConn = NULL;
+volatile bool sigint_interrupt_enabled = false;
+
+sigjmp_buf     sigint_interrupt_jmp;
+
+static PGcancel * volatile cancelConn = NULL;
+
 #ifdef WIN32
 static CRITICAL_SECTION cancelConnLock;
 #endif
 
-volatile bool cancel_pressed = false;
-
 #define write_stderr(str)      write(fileno(stderr), str, strlen(str))
 
 
 #ifndef WIN32
 
-void
+static void
 handle_sigint(SIGNAL_ARGS)
 {
        int                     save_errno = errno;
-       char        errbuf[256];
-
-       /* Don't muck around if prompting for a password. */
-       if (prompt_state)
-               return;
+       char            errbuf[256];
 
-       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 (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
-               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(errbuf);
+               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 */
 }
 
-#else /* WIN32 */
+void
+setup_cancel_handler(void)
+{
+       pqsignal(SIGINT, handle_sigint);
+}
+
+#else                                                  /* WIN32 */
 
 static BOOL WINAPI
 consoleHandler(DWORD dwCtrlType)
 {
-       char        errbuf[256];
+       char            errbuf[256];
 
        if (dwCtrlType == CTRL_C_EVENT ||
                dwCtrlType == CTRL_BREAK_EVENT)
        {
-               if (prompt_state)
-                       return TRUE;
+               /*
+                * Can't longjmp here, because we are in wrong thread :-(
+                */
+
+               /* set cancel flag to stop any long-running loops */
+               cancel_pressed = true;
 
-               /* Perform query cancel */
+               /* and send QueryCancel if we are processing a database query */
                EnterCriticalSection(&cancelConnLock);
                if (cancelConn != NULL)
                {
-                       cancel_pressed = true;
-
                        if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
                                write_stderr("Cancel request sent\n");
                        else
@@ -307,18 +323,14 @@ consoleHandler(DWORD dwCtrlType)
 }
 
 void
-setup_win32_locks(void)
+setup_cancel_handler(void)
 {
        InitializeCriticalSection(&cancelConnLock);
-}
 
-void
-setup_cancel_handler(void)
-{
        SetConsoleCtrlHandler(consoleHandler, TRUE);
 }
 
-#endif /* WIN32 */
+#endif   /* WIN32 */
 
 
 /* ConnectionUp
@@ -357,19 +369,19 @@ CheckConnection(void)
                        exit(EXIT_BADCONN);
                }
 
-               fputs(gettext("The connection to the server was lost. Attempting reset: "), stderr);
+               fputs(_("The connection to the server was lost. Attempting reset: "), stderr);
                PQreset(pset.db);
                OK = ConnectionUp();
                if (!OK)
                {
-                       fputs(gettext("Failed.\n"), stderr);
+                       fputs(_("Failed.\n"), stderr);
                        PQfinish(pset.db);
                        pset.db = NULL;
                        ResetCancelConn();
                        UnsyncVariables();
                }
                else
-                       fputs(gettext("Succeeded.\n"), stderr);
+                       fputs(_("Succeeded.\n"), stderr);
        }
 
        return OK;
@@ -382,16 +394,22 @@ CheckConnection(void)
  *
  * Set cancelConn to point to the current database connection.
  */
-static void
+void
 SetCancelConn(void)
 {
+       PGcancel *oldCancelConn;
+
 #ifdef WIN32
        EnterCriticalSection(&cancelConnLock);
 #endif
 
        /* Free the old one if we have one */
-       if (cancelConn != NULL)
-               PQfreeCancel(cancelConn);
+       oldCancelConn = cancelConn;
+       /* be sure handle_sigint doesn't use pointer while freeing */
+       cancelConn = NULL;
+
+       if (oldCancelConn != NULL)
+               PQfreeCancel(oldCancelConn);
 
        cancelConn = PQgetCancel(pset.db);
 
@@ -409,239 +427,25 @@ SetCancelConn(void)
 void
 ResetCancelConn(void)
 {
+       PGcancel *oldCancelConn;
+
 #ifdef WIN32
        EnterCriticalSection(&cancelConnLock);
 #endif
 
-       if (cancelConn)
-               PQfreeCancel(cancelConn);
-
+       oldCancelConn = cancelConn;
+       /* be sure handle_sigint doesn't use pointer while freeing */
        cancelConn = NULL;
 
+       if (oldCancelConn != NULL)
+               PQfreeCancel(oldCancelConn);
+
 #ifdef WIN32
        LeaveCriticalSection(&cancelConnLock);
 #endif
 }
 
 
-/*
- * on errors, print syntax error position if available.
- *
- * the query is expected to be in the client encoding.
- */
-static void
-ReportSyntaxErrorPosition(const PGresult *result, const char *query)
-{
-#define DISPLAY_SIZE   60              /* screen width limit, in screen cols */
-#define MIN_RIGHT_CUT  10              /* try to keep this far away from EOL */
-
-       int                     loc = 0;
-       const char *sp;
-       int                     clen,
-                               slen,
-                               i,
-                          *qidx,
-                          *scridx,
-                               qoffset,
-                               scroffset,
-                               ibeg,
-                               iend,
-                               loc_line;
-       char       *wquery;
-       bool            beg_trunc,
-                               end_trunc;
-       PQExpBufferData msg;
-
-       if (pset.verbosity == PQERRORS_TERSE)
-               return;
-
-       sp = PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION);
-       if (sp == NULL)
-       {
-               sp = PQresultErrorField(result, PG_DIAG_INTERNAL_POSITION);
-               if (sp == NULL)
-                       return;                         /* no syntax error */
-               query = PQresultErrorField(result, PG_DIAG_INTERNAL_QUERY);
-       }
-       if (query == NULL)
-               return;                                 /* nothing to reference location to */
-
-       if (sscanf(sp, "%d", &loc) != 1)
-       {
-               psql_error("INTERNAL ERROR: unexpected statement position \"%s\"\n",
-                                  sp);
-               return;
-       }
-
-       /* Make a writable copy of the query, and a buffer for messages. */
-       wquery = pg_strdup(query);
-
-       initPQExpBuffer(&msg);
-
-       /*
-        * The returned cursor position is measured in logical characters.
-        * Each character might occupy multiple physical bytes in the string,
-        * and in some Far Eastern character sets it might take more than one
-        * screen column as well.  We compute the starting byte offset and
-        * starting screen column of each logical character, and store these
-        * in qidx[] and scridx[] respectively.
-        */
-
-       /* we need a safe allocation size... */
-       slen = strlen(query) + 1;
-
-       qidx = (int *) pg_malloc(slen * sizeof(int));
-       scridx = (int *) pg_malloc(slen * sizeof(int));
-
-       qoffset = 0;
-       scroffset = 0;
-       for (i = 0; query[qoffset] != '\0'; i++)
-       {
-               qidx[i] = qoffset;
-               scridx[i] = scroffset;
-               scroffset += PQdsplen(&query[qoffset], pset.encoding);
-               qoffset += PQmblen(&query[qoffset], pset.encoding);
-       }
-       qidx[i] = qoffset;
-       scridx[i] = scroffset;
-       clen = i;
-       psql_assert(clen < slen);
-
-       /* convert loc to zero-based offset in qidx/scridx arrays */
-       loc--;
-
-       /* do we have something to show? */
-       if (loc >= 0 && loc <= clen)
-       {
-               /* input line number of our syntax error. */
-               loc_line = 1;
-               /* first included char of extract. */
-               ibeg = 0;
-               /* last-plus-1 included char of extract. */
-               iend = clen;
-
-               /*
-                * Replace tabs with spaces in the writable copy.  (Later we might
-                * want to think about coping with their variable screen width,
-                * but not today.)
-                *
-                * Extract line number and begin and end indexes of line containing
-                * error location.      There will not be any newlines or carriage
-                * returns in the selected extract.
-                */
-               for (i = 0; i < clen; i++)
-               {
-                       /* character length must be 1 or it's not ASCII */
-                       if ((qidx[i + 1] - qidx[i]) == 1)
-                       {
-                               if (wquery[qidx[i]] == '\t')
-                                       wquery[qidx[i]] = ' ';
-                               else if (wquery[qidx[i]] == '\r' || wquery[qidx[i]] == '\n')
-                               {
-                                       if (i < loc)
-                                       {
-                                               /*
-                                                * count lines before loc.      Each \r or \n counts
-                                                * as a line except when \r \n appear together.
-                                                */
-                                               if (wquery[qidx[i]] == '\r' ||
-                                                       i == 0 ||
-                                                       (qidx[i] - qidx[i - 1]) != 1 ||
-                                                       wquery[qidx[i - 1]] != '\r')
-                                                       loc_line++;
-                                               /* extract beginning = last line start before loc. */
-                                               ibeg = i + 1;
-                                       }
-                                       else
-                                       {
-                                               /* set extract end. */
-                                               iend = i;
-                                               /* done scanning. */
-                                               break;
-                                       }
-                               }
-                       }
-               }
-
-               /* If the line extracted is too long, we truncate it. */
-               beg_trunc = false;
-               end_trunc = false;
-               if (scridx[iend] - scridx[ibeg] > DISPLAY_SIZE)
-               {
-                       /*
-                        * We first truncate right if it is enough.  This code might
-                        * be off a space or so on enforcing MIN_RIGHT_CUT if there's
-                        * a wide character right there, but that should be okay.
-                        */
-                       if (scridx[ibeg] + DISPLAY_SIZE >= scridx[loc] + MIN_RIGHT_CUT)
-                       {
-                               while (scridx[iend] - scridx[ibeg] > DISPLAY_SIZE)
-                                       iend--;
-                               end_trunc = true;
-                       }
-                       else
-                       {
-                               /* Truncate right if not too close to loc. */
-                               while (scridx[loc] + MIN_RIGHT_CUT < scridx[iend])
-                               {
-                                       iend--;
-                                       end_trunc = true;
-                               }
-
-                               /* Truncate left if still too long. */
-                               while (scridx[iend] - scridx[ibeg] > DISPLAY_SIZE)
-                               {
-                                       ibeg++;
-                                       beg_trunc = true;
-                               }
-                       }
-               }
-
-               /* the extract MUST contain the target position! */
-               psql_assert(ibeg <= loc && loc <= iend);
-
-               /* truncate working copy at desired endpoint */
-               wquery[qidx[iend]] = '\0';
-
-               /* Begin building the finished message. */
-               printfPQExpBuffer(&msg, gettext("LINE %d: "), loc_line);
-               if (beg_trunc)
-                       appendPQExpBufferStr(&msg, "...");
-
-               /*
-                * While we have the prefix in the msg buffer, compute its screen
-                * width.
-                */
-               scroffset = 0;
-               for (i = 0; i < msg.len; i += PQmblen(&msg.data[i], pset.encoding))
-                       scroffset += PQdsplen(&msg.data[i], pset.encoding);
-
-               /* Finish and emit the message. */
-               appendPQExpBufferStr(&msg, &wquery[qidx[ibeg]]);
-               if (end_trunc)
-                       appendPQExpBufferStr(&msg, "...");
-
-               psql_error("%s\n", msg.data);
-
-               /* Now emit the cursor marker line. */
-               scroffset += scridx[loc] - scridx[ibeg];
-               resetPQExpBuffer(&msg);
-               for (i = 0; i < scroffset; i++)
-                       appendPQExpBufferChar(&msg, ' ');
-               appendPQExpBufferChar(&msg, '^');
-
-               psql_error("%s\n", msg.data);
-       }
-
-       /* Clean up. */
-       termPQExpBuffer(&msg);
-
-       free(wquery);
-       free(qidx);
-       free(scridx);
-}
-
-
 /*
  * AcceptResult
  *
@@ -667,12 +471,8 @@ AcceptResult(const PGresult *result, const char *query)
                        case PGRES_TUPLES_OK:
                        case PGRES_EMPTY_QUERY:
                        case PGRES_COPY_IN:
-                               /* Fine, do nothing */
-                               break;
-
                        case PGRES_COPY_OUT:
-                               /* keep cancel connection for copy out state */
-                               SetCancelConn();
+                               /* Fine, do nothing */
                                break;
 
                        default:
@@ -682,8 +482,11 @@ AcceptResult(const PGresult *result, const char *query)
 
        if (!OK)
        {
-               psql_error("%s", PQerrorMessage(pset.db));
-               ReportSyntaxErrorPosition(result, query);
+               const char *error = PQerrorMessage(pset.db);
+
+               if (strlen(error))
+                       psql_error("%s", error);
+
                CheckConnection();
        }
 
@@ -702,6 +505,9 @@ AcceptResult(const PGresult *result, const char *query)
  * 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".
  */
@@ -720,10 +526,18 @@ PSQLexec(const char *query, bool start_xact)
        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;
@@ -768,7 +582,7 @@ PrintNotifications(void)
 
        while ((notify = PQnotifies(pset.db)))
        {
-               fprintf(pset.queryFout, gettext("Asynchronous notification \"%s\" received from server process with PID %d.\n"),
+               fprintf(pset.queryFout, _("Asynchronous notification \"%s\" received from server process with PID %d.\n"),
                                notify->relname, notify->be_pid);
                fflush(pset.queryFout);
                PQfreemem(notify);
@@ -784,6 +598,8 @@ PrintNotifications(void)
 static bool
 PrintQueryTuples(const PGresult *results)
 {
+       printQueryOpt my_popt = pset.popt;
+
        /* write output to \g argument, if any */
        if (pset.gfname)
        {
@@ -800,7 +616,7 @@ PrintQueryTuples(const PGresult *results)
                        return false;
                }
 
-               printQuery(results, &pset.popt, pset.queryFout);
+               printQuery(results, &my_popt, pset.queryFout, pset.logfile);
 
                /* close file/pipe, restore old setting */
                setQFout(NULL);
@@ -812,7 +628,7 @@ PrintQueryTuples(const PGresult *results)
                pset.gfname = NULL;
        }
        else
-               printQuery(results, &pset.popt, pset.queryFout);
+               printQuery(results, &my_popt, pset.queryFout, pset.logfile);
 
        return true;
 }
@@ -843,11 +659,16 @@ ProcessCopyResult(PGresult *results)
                        break;
 
                case PGRES_COPY_OUT:
+                       SetCancelConn();
                        success = handleCopyOut(pset.db, pset.queryFout);
+                       ResetCancelConn();
                        break;
 
                case PGRES_COPY_IN:
-                       success = handleCopyIn(pset.db, pset.cur_cmd_source);
+                       SetCancelConn();
+                       success = handleCopyIn(pset.db, pset.cur_cmd_source,
+                                                                  PQbinaryTuples(results));
+                       ResetCancelConn();
                        break;
 
                default:
@@ -862,6 +683,36 @@ ProcessCopyResult(PGresult *results)
 }
 
 
+/*
+ * 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
  *
@@ -873,6 +724,7 @@ static bool
 PrintQueryResults(PGresult *results)
 {
        bool            success = false;
+       const char *cmdstatus;
 
        if (!results)
                return false;
@@ -880,31 +732,20 @@ PrintQueryResults(PGresult *results)
        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:
-                       {
-                               char            buf[10];
-
-                               success = true;
-                               snprintf(buf, sizeof(buf),
-                                                "%u", (unsigned int) PQoidValue(results));
-                               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));
-                               }
-                               SetVariable(pset.vars, "LASTOID", buf);
-                               break;
-                       }
+                       PrintQueryStatus(results);
+                       success = true;
+                       break;
 
                case PGRES_EMPTY_QUERY:
                        success = true;
@@ -944,7 +785,11 @@ SendQuery(const char *query)
        PGresult   *results;
        TimevalStruct before,
                                after;
-       bool            OK;
+       bool            OK,
+                               on_error_rollback_savepoint = false;
+       PGTransactionStatusType transaction_status;
+       static bool on_error_rollback_warning = false;
+       const char *rollback_str;
 
        if (!pset.db)
        {
@@ -956,9 +801,9 @@ SendQuery(const char *query)
        {
                char            buf[3];
 
-               printf(gettext("***(Single step mode: verify command)*******************************************\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)
@@ -971,9 +816,20 @@ SendQuery(const char *query)
                fflush(stdout);
        }
 
+       if (pset.logfile)
+       {
+               fprintf(pset.logfile,
+                               _("********* QUERY **********\n"
+                                 "%s\n"
+                                 "**************************\n\n"), query);
+               fflush(pset.logfile);
+       }
+
        SetCancelConn();
 
-       if (PQtransactionStatus(pset.db) == PQTRANS_IDLE &&
+       transaction_status = PQtransactionStatus(pset.db);
+
+       if (transaction_status == PQTRANS_IDLE &&
                !GetVariableBool(pset.vars, "AUTOCOMMIT") &&
                !command_no_begin(query))
        {
@@ -986,6 +842,35 @@ SendQuery(const char *query)
                        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)
@@ -1003,11 +888,50 @@ SendQuery(const char *query)
        if (OK)
                OK = PrintQueryResults(results);
 
+       /* If we made a temporary savepoint, possibly release/rollback */
+       if (on_error_rollback_savepoint)
+       {
+               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);
+       }
+
        PQclear(results);
 
        /* Possible microtiming output */
        if (OK && pset.timing)
-               printf(gettext("Time: %.3f ms\n"), DIFF_MSEC(&after, &before));
+               printf(_("Time: %.3f ms\n"), DIFF_MSEC(&after, &before));
 
        /* check for events that may occur during query execution */
 
@@ -1033,19 +957,19 @@ SendQuery(const char *query)
 static const char *
 skip_white_space(const char *query)
 {
-       int                     cnestlevel = 0;         /* slash-star comment nest level */
+       int                     cnestlevel = 0; /* slash-star comment nest level */
 
        while (*query)
        {
-               int             mblen = PQmblen(query, pset.encoding);
+               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.
+                * 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;
@@ -1062,9 +986,10 @@ skip_white_space(const char *query)
                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.
+                        * We have to skip to end of line since any slash-star inside the
+                        * -- comment does NOT start a slash-star comment.
                         */
                        while (*query)
                        {
@@ -1111,11 +1036,11 @@ command_no_begin(const char *query)
                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.
+        * 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 
+        * (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)
@@ -1130,14 +1055,29 @@ command_no_begin(const char *query)
                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;
+
+               query = skip_white_space(query);
+
+               wordlen = 0;
+               while (isalpha((unsigned char) query[wordlen]))
+                       wordlen += PQmblen(&query[wordlen], pset.encoding);
+
+               if (wordlen == 11 && pg_strncasecmp(query, "transaction", 11) == 0)
+                       return true;
+               return false;
+       }
 
        /*
-        * Commands not allowed within transactions.  The statements checked
-        * for here should be exactly those that call PreventTransactionChain()
-        * in the backend.
+        * 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.
+        * 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;
@@ -1145,9 +1085,9 @@ command_no_begin(const char *query)
                return true;
 
        /*
-        * Note: these tests will match REINDEX TABLESPACE, which isn't really
-        * a valid command so we don't care much.  The other five possible
-        * matches are correct.
+        * 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) ||
@@ -1163,6 +1103,8 @@ command_no_begin(const char *query)
 
                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;
        }
@@ -1171,17 +1113,6 @@ command_no_begin(const char *query)
 }
 
 
-char
-parse_char(char **buf)
-{
-       long            l;
-
-       l = strtol(*buf, buf, 0);
-       --*buf;
-       return (char) l;
-}
-
-
 /*
  * Test if the current user is a database superuser.
  *
@@ -1205,6 +1136,29 @@ is_superuser(void)
 }
 
 
+/*
+ * 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.
  *
@@ -1239,7 +1193,11 @@ expand_tilde(char **filename)
        if (!filename || !(*filename))
                return NULL;
 
-       /* MSDOS uses tilde for short versions of long file names, so skip it. */
+       /*
+        * 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 */
@@ -1262,9 +1220,9 @@ expand_tilde(char **filename)
                *p = '\0';
 
                if (*(fn + 1) == '\0')
-                       get_home_path(home);
+                       get_home_path(home);    /* ~ or ~/ only */
                else if ((pw = getpwnam(fn + 1)) != NULL)
-                       StrNCpy(home, pw->pw_dir, MAXPGPATH);
+                       StrNCpy(home, pw->pw_dir, MAXPGPATH);           /* ~user */
 
                *p = oldp;
                if (strlen(home) != 0)