/*
* 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()
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);
/*
* 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
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
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 */
*
* 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);
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
* 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
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:
PSQLexec(const char *query, bool start_xact)
{
PGresult *res;
- int echo_hidden;
if (!pset.db)
{
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"
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)
res = PQexec(pset.db, query);
- if (!AcceptResult(res, query) && res)
+ ResetCancelConn();
+
+ if (!AcceptResult(res))
{
PQclear(res);
res = NULL;
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);
}
/* 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;
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:
}
+/*
+ * 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
*
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:
- {
- 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;
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)
{
return false;
}
- if (GetVariableBool(pset.vars, "SINGLESTEP"))
+ if (pset.singlestep)
{
char buf[3];
if (buf[0] == 'x')
return false;
}
- else if (VariableEquals(pset.vars, "ECHO", "queries"))
+ else if (pset.echo == PSQL_ECHO_QUERIES)
{
puts(query);
fflush(stdout);
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");
}
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
}
}
- 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
{
/*
* 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 */
}
+/*
+ * 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.
*/
* 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;
}
+/*
+ * 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.
*
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)