]> granicus.if.org Git - postgresql/blobdiff - src/bin/psql/common.c
Show psql timing output even in quiet mode
[postgresql] / src / bin / psql / common.c
index 6b7f683b055e237ca56931ab5815d6948a80eb1f..ed929372420fba4c9bf9077cec112f103b15971c 100644 (file)
@@ -1,67 +1,36 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2006, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2010, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.118 2006/05/26 19:51:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.147 2010/07/28 04:39:14 petere Exp $
  */
 #include "postgres_fe.h"
 #include "common.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 "portability/instr_time.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;
+#include "mbprint.h"
 
-#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 ExecQueryUsingCursor(const char *query, double *elapsed_msec);
 static bool command_no_begin(const char *query);
+static bool is_select_command(const char *query);
 
 /*
  * "Safe" wrapper around strdup()
@@ -196,7 +165,7 @@ 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, _(fmt), ap);
        va_end(ap);
@@ -219,55 +188,78 @@ 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;
-
-       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 */
 }
+
+void
+setup_cancel_handler(void)
+{
+       pqsignal(SIGINT, handle_sigint);
+}
 #else                                                  /* WIN32 */
 
 static BOOL WINAPI
@@ -278,15 +270,17 @@ consoleHandler(DWORD dwCtrlType)
        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
@@ -304,23 +298,12 @@ consoleHandler(DWORD dwCtrlType)
                return FALSE;
 }
 
-void
-setup_win32_locks(void)
-{
-       InitializeCriticalSection(&cancelConnLock);
-}
-
 void
 setup_cancel_handler(void)
 {
-       static bool done = false;
+       InitializeCriticalSection(&cancelConnLock);
 
-       /* only need one handler per process */
-       if (!done)
-       {
-               SetConsoleCtrlHandler(consoleHandler, TRUE);
-               done = true;
-       }
+       SetConsoleCtrlHandler(consoleHandler, TRUE);
 }
 #endif   /* WIN32 */
 
@@ -386,16 +369,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);
 
@@ -413,15 +402,19 @@ 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
@@ -432,18 +425,15 @@ ResetCancelConn(void)
  * 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.
+ * 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)
+AcceptResult(const PGresult *result)
 {
        bool            OK = true;
 
-       ResetCancelConn();
-
        if (!result)
                OK = false;
        else
@@ -453,15 +443,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 active during copy out state.
-                                * The matching ResetCancelConn() is in handleCopyOut.
-                                */
-                               SetCancelConn();
+                               /* Fine, do nothing */
                                break;
 
                        default:
@@ -504,7 +487,6 @@ PGresult *
 PSQLexec(const char *query, bool start_xact)
 {
        PGresult   *res;
-       int                     echo_hidden;
 
        if (!pset.db)
        {
@@ -512,8 +494,7 @@ PSQLexec(const char *query, bool start_xact)
                return NULL;
        }
 
-       echo_hidden = SwitchVariable(pset.vars, "ECHO_HIDDEN", "noexec", NULL);
-       if (echo_hidden != VAR_NOTSET)
+       if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
        {
                printf(_("********* QUERY **********\n"
                                 "%s\n"
@@ -528,14 +509,15 @@ PSQLexec(const char *query, bool start_xact)
                        fflush(pset.logfile);
                }
 
-               if (echo_hidden == 1)   /* noexec? */
+               if (pset.echo_hidden == PSQL_ECHO_HIDDEN_NOEXEC)
                        return NULL;
        }
 
        SetCancelConn();
 
-       if (start_xact && PQtransactionStatus(pset.db) == PQTRANS_IDLE &&
-               !GetVariableBool(pset.vars, "AUTOCOMMIT"))
+       if (start_xact &&
+               !pset.autocommit &&
+               PQtransactionStatus(pset.db) == PQTRANS_IDLE)
        {
                res = PQexec(pset.db, "BEGIN");
                if (PQresultStatus(res) != PGRES_COMMAND_OK)
@@ -550,7 +532,9 @@ PSQLexec(const char *query, bool start_xact)
 
        res = PQexec(pset.db, query);
 
-       if (!AcceptResult(res, query) && res)
+       ResetCancelConn();
+
+       if (!AcceptResult(res))
        {
                PQclear(res);
                res = NULL;
@@ -571,8 +555,13 @@ PrintNotifications(void)
 
        while ((notify = PQnotifies(pset.db)))
        {
-               fprintf(pset.queryFout, _("Asynchronous notification \"%s\" received from server process with PID %d.\n"),
-                               notify->relname, notify->be_pid);
+               /* for backward compatibility, only show payload if nonempty */
+               if (notify->extra[0])
+                       fprintf(pset.queryFout, _("Asynchronous notification \"%s\" with payload \"%s\" received from server process with PID %d.\n"),
+                                       notify->relname, notify->extra, notify->be_pid);
+               else
+                       fprintf(pset.queryFout, _("Asynchronous notification \"%s\" received from server process with PID %d.\n"),
+                                       notify->relname, notify->be_pid);
                fflush(pset.queryFout);
                PQfreemem(notify);
        }
@@ -592,6 +581,7 @@ PrintQueryTuples(const PGresult *results)
        /* write output to \g argument, if any */
        if (pset.gfname)
        {
+               /* keep this code in sync with ExecQueryUsingCursor */
                FILE       *queryFout_copy = pset.queryFout;
                bool            queryFoutPipe_copy = pset.queryFoutPipe;
 
@@ -648,12 +638,16 @@ ProcessCopyResult(PGresult *results)
                        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:
@@ -668,6 +662,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 (!pset.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
  *
@@ -679,6 +703,7 @@ static bool
 PrintQueryResults(PGresult *results)
 {
        bool            success = false;
+       const char *cmdstatus;
 
        if (!results)
                return false;
@@ -686,33 +711,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));
-                               }
-                               if (pset.logfile)
-                                       fprintf(pset.logfile, "%s\n", PQcmdStatus(results));
-                               SetVariable(pset.vars, "LASTOID", buf);
-                               break;
-                       }
+                       PrintQueryStatus(results);
+                       success = true;
+                       break;
 
                case PGRES_EMPTY_QUERY:
                        success = true;
@@ -750,13 +762,11 @@ bool
 SendQuery(const char *query)
 {
        PGresult   *results;
-       TimevalStruct before,
-                               after;
+       PGTransactionStatusType transaction_status;
+       double          elapsed_msec = 0;
        bool            OK,
                                on_error_rollback_savepoint = false;
-       PGTransactionStatusType transaction_status;
        static bool on_error_rollback_warning = false;
-       const char *rollback_str;
 
        if (!pset.db)
        {
@@ -764,7 +774,7 @@ SendQuery(const char *query)
                return false;
        }
 
-       if (GetVariableBool(pset.vars, "SINGLESTEP"))
+       if (pset.singlestep)
        {
                char            buf[3];
 
@@ -777,7 +787,7 @@ SendQuery(const char *query)
                        if (buf[0] == 'x')
                                return false;
        }
-       else if (VariableEquals(pset.vars, "ECHO", "queries"))
+       else if (pset.echo == PSQL_ECHO_QUERIES)
        {
                puts(query);
                fflush(stdout);
@@ -797,7 +807,7 @@ SendQuery(const char *query)
        transaction_status = PQtransactionStatus(pset.db);
 
        if (transaction_status == PQTRANS_IDLE &&
-               !GetVariableBool(pset.vars, "AUTOCOMMIT") &&
+               !pset.autocommit &&
                !command_no_begin(query))
        {
                results = PQexec(pset.db, "BEGIN");
@@ -813,16 +823,14 @@ SendQuery(const char *query)
        }
 
        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.on_error_rollback != PSQL_ERROR_ROLLBACK_OFF &&
                (pset.cur_cmd_interactive ||
-                pg_strcasecmp(rollback_str, "interactive") != 0))
+                pset.on_error_rollback == PSQL_ERROR_ROLLBACK_ON))
        {
                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);
+                       fprintf(stderr, _("The server (version %d.%d) does not support savepoints for ON_ERROR_ROLLBACK.\n"),
+                                       pset.sversion / 10000, (pset.sversion / 100) % 100);
                        on_error_rollback_warning = true;
                }
                else
@@ -840,34 +848,57 @@ SendQuery(const char *query)
                }
        }
 
-       if (pset.timing)
-               GETTIMEOFDAY(&before);
+       if (pset.fetch_count <= 0 || !is_select_command(query))
+       {
+               /* Default fetch-it-all-and-print mode */
+               instr_time      before,
+                                       after;
 
-       results = PQexec(pset.db, query);
+               if (pset.timing)
+                       INSTR_TIME_SET_CURRENT(before);
 
-       /* these operations are included in the timing result: */
-       OK = (AcceptResult(results, query) && ProcessCopyResult(results));
+               results = PQexec(pset.db, query);
 
-       if (pset.timing)
-               GETTIMEOFDAY(&after);
+               /* these operations are included in the timing result: */
+               ResetCancelConn();
+               OK = (AcceptResult(results) && ProcessCopyResult(results));
 
-       /* but printing results isn't: */
-       if (OK)
-               OK = PrintQueryResults(results);
+               if (pset.timing)
+               {
+                       INSTR_TIME_SET_CURRENT(after);
+                       INSTR_TIME_SUBTRACT(after, before);
+                       elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
+               }
 
-       PQclear(results);
+               /* but printing results isn't: */
+               if (OK)
+                       OK = PrintQueryResults(results);
+       }
+       else
+       {
+               /* Fetch-in-segments mode */
+               OK = ExecQueryUsingCursor(query, &elapsed_msec);
+               ResetCancelConn();
+               results = NULL;                 /* PQclear(NULL) does nothing */
+       }
 
        /* If we made a temporary savepoint, possibly release/rollback */
        if (on_error_rollback_savepoint)
        {
+               const char *svptcmd;
+
                transaction_status = PQtransactionStatus(pset.db);
 
-               /* We always rollback on an error */
                if (transaction_status == PQTRANS_INERROR)
-                       results = PQexec(pset.db, "ROLLBACK TO pg_psql_temporary_savepoint");
-               /* If they are no longer in a transaction, then do nothing */
+               {
+                       /* We always rollback on an error */
+                       svptcmd = "ROLLBACK TO pg_psql_temporary_savepoint";
+               }
                else if (transaction_status != PQTRANS_INTRANS)
-                       results = NULL;
+               {
+                       /* If they are no longer in a transaction, then do nothing */
+                       svptcmd = NULL;
+               }
                else
                {
                        /*
@@ -875,26 +906,38 @@ SendQuery(const char *query)
                         * 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)
-                               results = NULL;
+                       if (results &&
+                               (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
+                                strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
+                                strcmp(PQcmdStatus(results), "ROLLBACK") == 0))
+                               svptcmd = NULL;
                        else
-                               results = PQexec(pset.db, "RELEASE pg_psql_temporary_savepoint");
+                               svptcmd = "RELEASE pg_psql_temporary_savepoint";
                }
-               if (PQresultStatus(results) != PGRES_COMMAND_OK)
+
+               if (svptcmd)
                {
-                       psql_error("%s", PQerrorMessage(pset.db));
-                       PQclear(results);
-                       ResetCancelConn();
-                       return false;
+                       PGresult   *svptres;
+
+                       svptres = PQexec(pset.db, svptcmd);
+                       if (PQresultStatus(svptres) != PGRES_COMMAND_OK)
+                       {
+                               psql_error("%s", PQerrorMessage(pset.db));
+                               PQclear(svptres);
+
+                               PQclear(results);
+                               ResetCancelConn();
+                               return false;
+                       }
+                       PQclear(svptres);
                }
-               PQclear(results);
        }
 
+       PQclear(results);
+
        /* Possible microtiming output */
        if (OK && pset.timing)
-               printf(_("Time: %.3f ms\n"), DIFF_MSEC(&after, &before));
+               printf(_("Time: %.3f ms\n"), elapsed_msec);
 
        /* check for events that may occur during query execution */
 
@@ -914,6 +957,230 @@ SendQuery(const char *query)
 }
 
 
+/*
+ * ExecQueryUsingCursor: run a SELECT-like query using a cursor
+ *
+ * This feature allows result sets larger than RAM to be dealt with.
+ *
+ * Returns true if the query executed successfully, false otherwise.
+ *
+ * If pset.timing is on, total query time (exclusive of result-printing) is
+ * stored into *elapsed_msec.
+ */
+static bool
+ExecQueryUsingCursor(const char *query, double *elapsed_msec)
+{
+       bool            OK = true;
+       PGresult   *results;
+       PQExpBufferData buf;
+       printQueryOpt my_popt = pset.popt;
+       FILE       *queryFout_copy = pset.queryFout;
+       bool            queryFoutPipe_copy = pset.queryFoutPipe;
+       bool            started_txn = false;
+       bool            did_pager = false;
+       int                     ntuples;
+       char            fetch_cmd[64];
+       instr_time      before,
+                               after;
+       int                     flush_error;
+
+       *elapsed_msec = 0;
+
+       /* initialize print options for partial table output */
+       my_popt.topt.start_table = true;
+       my_popt.topt.stop_table = false;
+       my_popt.topt.prior_records = 0;
+
+       if (pset.timing)
+               INSTR_TIME_SET_CURRENT(before);
+
+       /* if we're not in a transaction, start one */
+       if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
+       {
+               results = PQexec(pset.db, "BEGIN");
+               OK = AcceptResult(results) &&
+                       (PQresultStatus(results) == PGRES_COMMAND_OK);
+               PQclear(results);
+               if (!OK)
+                       return false;
+               started_txn = true;
+       }
+
+       /* Send DECLARE CURSOR */
+       initPQExpBuffer(&buf);
+       appendPQExpBuffer(&buf, "DECLARE _psql_cursor NO SCROLL CURSOR FOR\n%s",
+                                         query);
+
+       results = PQexec(pset.db, buf.data);
+       OK = AcceptResult(results) &&
+               (PQresultStatus(results) == PGRES_COMMAND_OK);
+       PQclear(results);
+       termPQExpBuffer(&buf);
+       if (!OK)
+               goto cleanup;
+
+       if (pset.timing)
+       {
+               INSTR_TIME_SET_CURRENT(after);
+               INSTR_TIME_SUBTRACT(after, before);
+               *elapsed_msec += INSTR_TIME_GET_MILLISEC(after);
+       }
+
+       snprintf(fetch_cmd, sizeof(fetch_cmd),
+                        "FETCH FORWARD %d FROM _psql_cursor",
+                        pset.fetch_count);
+
+       /* prepare to write output to \g argument, if any */
+       if (pset.gfname)
+       {
+               /* keep this code in sync with PrintQueryTuples */
+               pset.queryFout = stdout;        /* so it doesn't get closed */
+
+               /* open file/pipe */
+               if (!setQFout(pset.gfname))
+               {
+                       pset.queryFout = queryFout_copy;
+                       pset.queryFoutPipe = queryFoutPipe_copy;
+                       OK = false;
+                       goto cleanup;
+               }
+       }
+
+       /* clear any pre-existing error indication on the output stream */
+       clearerr(pset.queryFout);
+
+       for (;;)
+       {
+               if (pset.timing)
+                       INSTR_TIME_SET_CURRENT(before);
+
+               /* get FETCH_COUNT tuples at a time */
+               results = PQexec(pset.db, fetch_cmd);
+
+               if (pset.timing)
+               {
+                       INSTR_TIME_SET_CURRENT(after);
+                       INSTR_TIME_SUBTRACT(after, before);
+                       *elapsed_msec += INSTR_TIME_GET_MILLISEC(after);
+               }
+
+               if (PQresultStatus(results) != PGRES_TUPLES_OK)
+               {
+                       /* shut down pager before printing error message */
+                       if (did_pager)
+                       {
+                               ClosePager(pset.queryFout);
+                               pset.queryFout = queryFout_copy;
+                               pset.queryFoutPipe = queryFoutPipe_copy;
+                               did_pager = false;
+                       }
+
+                       OK = AcceptResult(results);
+                       psql_assert(!OK);
+                       PQclear(results);
+                       break;
+               }
+
+               ntuples = PQntuples(results);
+
+               if (ntuples < pset.fetch_count)
+               {
+                       /* this is the last result set, so allow footer decoration */
+                       my_popt.topt.stop_table = true;
+               }
+               else if (pset.queryFout == stdout && !did_pager)
+               {
+                       /*
+                        * If query requires multiple result sets, hack to ensure that
+                        * only one pager instance is used for the whole mess
+                        */
+                       pset.queryFout = PageOutput(100000, my_popt.topt.pager);
+                       did_pager = true;
+               }
+
+               printQuery(results, &my_popt, pset.queryFout, pset.logfile);
+
+               PQclear(results);
+
+               /* after the first result set, disallow header decoration */
+               my_popt.topt.start_table = false;
+               my_popt.topt.prior_records += ntuples;
+
+               /*
+                * Make sure to flush the output stream, so intermediate results are
+                * visible to the client immediately.  We check the results because if
+                * the pager dies/exits/etc, there's no sense throwing more data at
+                * it.
+                */
+               flush_error = fflush(pset.queryFout);
+
+               /*
+                * Check if we are at the end, if a cancel was pressed, or if there
+                * were any errors either trying to flush out the results, or more
+                * generally on the output stream at all.  If we hit any errors
+                * writing things to the stream, we presume $PAGER has disappeared and
+                * stop bothering to pull down more data.
+                */
+               if (ntuples < pset.fetch_count || cancel_pressed || flush_error ||
+                       ferror(pset.queryFout))
+                       break;
+       }
+
+       /* close \g argument file/pipe, restore old setting */
+       if (pset.gfname)
+       {
+               /* keep this code in sync with PrintQueryTuples */
+               setQFout(NULL);
+
+               pset.queryFout = queryFout_copy;
+               pset.queryFoutPipe = queryFoutPipe_copy;
+
+               free(pset.gfname);
+               pset.gfname = NULL;
+       }
+       else if (did_pager)
+       {
+               ClosePager(pset.queryFout);
+               pset.queryFout = queryFout_copy;
+               pset.queryFoutPipe = queryFoutPipe_copy;
+       }
+
+cleanup:
+       if (pset.timing)
+               INSTR_TIME_SET_CURRENT(before);
+
+       /*
+        * We try to close the cursor on either success or failure, but on failure
+        * ignore the result (it's probably just a bleat about being in an aborted
+        * transaction)
+        */
+       results = PQexec(pset.db, "CLOSE _psql_cursor");
+       if (OK)
+       {
+               OK = AcceptResult(results) &&
+                       (PQresultStatus(results) == PGRES_COMMAND_OK);
+       }
+       PQclear(results);
+
+       if (started_txn)
+       {
+               results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
+               OK &= AcceptResult(results) &&
+                       (PQresultStatus(results) == PGRES_COMMAND_OK);
+               PQclear(results);
+       }
+
+       if (pset.timing)
+       {
+               INSTR_TIME_SET_CURRENT(after);
+               INSTR_TIME_SUBTRACT(after, before);
+               *elapsed_msec += INSTR_TIME_GET_MILLISEC(after);
+       }
+
+       return OK;
+}
+
+
 /*
  * Advance the given char pointer over white space and SQL comments.
  */
@@ -1038,22 +1305,71 @@ command_no_begin(const char *query)
         * 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;
+       {
+               /* CLUSTER with any arguments is allowed in transactions */
+               query += wordlen;
+
+               query = skip_white_space(query);
+
+               if (isalpha((unsigned char) query[0]))
+                       return false;           /* has additional words */
+               return true;                    /* it's CLUSTER without arguments */
+       }
+
+       if (wordlen == 6 && pg_strncasecmp(query, "create", 6) == 0)
+       {
+               query += wordlen;
+
+               query = skip_white_space(query);
+
+               wordlen = 0;
+               while (isalpha((unsigned char) query[wordlen]))
+                       wordlen += PQmblen(&query[wordlen], pset.encoding);
+
+               if (wordlen == 8 && pg_strncasecmp(query, "database", 8) == 0)
+                       return true;
+               if (wordlen == 10 && pg_strncasecmp(query, "tablespace", 10) == 0)
+                       return true;
+
+               /* CREATE [UNIQUE] INDEX CONCURRENTLY isn't allowed in xacts */
+               if (wordlen == 6 && pg_strncasecmp(query, "unique", 6) == 0)
+               {
+                       query += wordlen;
+
+                       query = skip_white_space(query);
+
+                       wordlen = 0;
+                       while (isalpha((unsigned char) query[wordlen]))
+                               wordlen += PQmblen(&query[wordlen], pset.encoding);
+               }
+
+               if (wordlen == 5 && pg_strncasecmp(query, "index", 5) == 0)
+               {
+                       query += wordlen;
+
+                       query = skip_white_space(query);
+
+                       wordlen = 0;
+                       while (isalpha((unsigned char) query[wordlen]))
+                               wordlen += PQmblen(&query[wordlen], pset.encoding);
+
+                       if (wordlen == 12 && pg_strncasecmp(query, "concurrently", 12) == 0)
+                               return true;
+               }
+
+               return false;
+       }
 
        /*
-        * 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.
+        * Note: these tests will match DROP SYSTEM and REINDEX TABLESPACE, which
+        * aren't really valid commands so we don't care much. The other four
+        * possible matches are correct.
         */
-       if ((wordlen == 6 && pg_strncasecmp(query, "create", 6) == 0) ||
-               (wordlen == 4 && pg_strncasecmp(query, "drop", 4) == 0) ||
+       if ((wordlen == 4 && pg_strncasecmp(query, "drop", 4) == 0) ||
                (wordlen == 7 && pg_strncasecmp(query, "reindex", 7) == 0))
        {
                query += wordlen;
@@ -1076,6 +1392,43 @@ command_no_begin(const char *query)
 }
 
 
+/*
+ * Check whether the specified command is a SELECT (or VALUES).
+ */
+static bool
+is_select_command(const char *query)
+{
+       int                     wordlen;
+
+       /*
+        * First advance over any whitespace, comments and left parentheses.
+        */
+       for (;;)
+       {
+               query = skip_white_space(query);
+               if (query[0] == '(')
+                       query++;
+               else
+                       break;
+       }
+
+       /*
+        * Check word length (since "selectx" is not "select").
+        */
+       wordlen = 0;
+       while (isalpha((unsigned char) query[wordlen]))
+               wordlen += PQmblen(&query[wordlen], pset.encoding);
+
+       if (wordlen == 6 && pg_strncasecmp(query, "select", 6) == 0)
+               return true;
+
+       if (wordlen == 6 && pg_strncasecmp(query, "values", 6) == 0)
+               return true;
+
+       return false;
+}
+
+
 /*
  * Test if the current user is a database superuser.
  *
@@ -1185,7 +1538,7 @@ expand_tilde(char **filename)
                if (*(fn + 1) == '\0')
                        get_home_path(home);    /* ~ or ~/ only */
                else if ((pw = getpwnam(fn + 1)) != NULL)
-                       StrNCpy(home, pw->pw_dir, MAXPGPATH);           /* ~user */
+                       strlcpy(home, pw->pw_dir, sizeof(home));        /* ~user */
 
                *p = oldp;
                if (strlen(home) != 0)