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