/*
* psql - the PostgreSQL interactive terminal
*
- * Copyright (c) 2000-2005, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2006, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.95 2005/01/01 05:43:08 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.124 2006/08/13 21:10:04 tgl Exp $
*/
#include "postgres_fe.h"
#include "common.h"
#ifndef WIN32
#include <sys/time.h>
#include <unistd.h> /* for write() */
-#include <setjmp.h>
#else
#include <io.h> /* for _write() */
#include <win32.h>
#include <sys/timeb.h> /* for _ftime() */
#endif
-#include "libpq-fe.h"
#include "pqsignal.h"
#include "settings.h"
-#include "variables.h"
#include "command.h"
#include "copy.h"
-#include "prompt.h"
-#include "print.h"
-#include "mainloop.h"
#include "mb/pg_wchar.h"
#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;
((T)->millitm - (U)->millitm))
#endif
-extern bool prompt_state;
-
static bool command_no_begin(const char *query);
-
/*
* "Safe" wrapper around strdup()
*/
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);
}
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 PQcancel() is written to make it
- * so. We use write() to print to stderr because it's better to use simple
+ * so. We use write() to report to stderr because it's better to use simple
* facilities in a signal handler.
*
* On win32, the signal cancelling happens on a separate thread, because
* that's how SetConsoleCtrlHandler works. The PQcancel function is safe
* for this (unlike PQrequestCancel). However, a CRITICAL_SECTION is required
- * to protect the PGcancel structure against being changed while the other
+ * to protect the PGcancel structure against being changed while the signal
* thread is using it.
+ *
+ * SIGINT is supposed to abort all long-running psql operations, not only
+ * database queries. In most places, this is accomplished by checking
+ * cancel_pressed during long-running loops. However, that won't work when
+ * blocked on user input (in readline() or fgets()). In those places, we
+ * set sigint_interrupt_enabled TRUE while blocked, instructing the signal
+ * catcher to longjmp through sigint_interrupt_jmp. We assume readline and
+ * fgets are coded to handle possible interruption. (XXX currently this does
+ * not work on win32, so control-C is less useful there)
*/
-static PGcancel *cancelConn = NULL;
+volatile bool sigint_interrupt_enabled = false;
+
+sigjmp_buf sigint_interrupt_jmp;
+
+static PGcancel * volatile cancelConn = NULL;
+
#ifdef WIN32
static CRITICAL_SECTION cancelConnLock;
#endif
-volatile bool cancel_pressed = false;
-
#define write_stderr(str) write(fileno(stderr), str, strlen(str))
#ifndef WIN32
-void
+static void
handle_sigint(SIGNAL_ARGS)
{
int save_errno = errno;
- char errbuf[256];
-
- /* Don't muck around if prompting for a password. */
- if (prompt_state)
- return;
+ char errbuf[256];
- if (cancelConn == NULL)
- siglongjmp(main_loop_jmp, 1);
+ /* if we are waiting for input, longjmp out of it */
+ if (sigint_interrupt_enabled)
+ {
+ sigint_interrupt_enabled = false;
+ siglongjmp(sigint_interrupt_jmp, 1);
+ }
+ /* else, set cancel flag to stop any long-running loops */
cancel_pressed = true;
- if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
- write_stderr("Cancel request sent\n");
- else
+ /* and send QueryCancel if we are processing a database query */
+ if (cancelConn != NULL)
{
- write_stderr("Could not send cancel request: ");
- write_stderr(errbuf);
+ if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
+ write_stderr("Cancel request sent\n");
+ else
+ {
+ write_stderr("Could not send cancel request: ");
+ write_stderr(errbuf);
+ }
}
+
errno = save_errno; /* just in case the write changed it */
}
-#else /* WIN32 */
+void
+setup_cancel_handler(void)
+{
+ pqsignal(SIGINT, handle_sigint);
+}
+
+#else /* WIN32 */
static BOOL WINAPI
consoleHandler(DWORD dwCtrlType)
{
- char errbuf[256];
+ char errbuf[256];
if (dwCtrlType == CTRL_C_EVENT ||
dwCtrlType == CTRL_BREAK_EVENT)
{
- if (prompt_state)
- return TRUE;
+ /*
+ * Can't longjmp here, because we are in wrong thread :-(
+ */
+
+ /* set cancel flag to stop any long-running loops */
+ cancel_pressed = true;
- /* Perform query cancel */
+ /* and send QueryCancel if we are processing a database query */
EnterCriticalSection(&cancelConnLock);
if (cancelConn != NULL)
{
- cancel_pressed = true;
-
if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
write_stderr("Cancel request sent\n");
else
}
void
-setup_win32_locks(void)
+setup_cancel_handler(void)
{
InitializeCriticalSection(&cancelConnLock);
-}
-void
-setup_cancel_handler(void)
-{
SetConsoleCtrlHandler(consoleHandler, TRUE);
}
-#endif /* WIN32 */
+#endif /* WIN32 */
/* ConnectionUp
exit(EXIT_BADCONN);
}
- fputs(gettext("The connection to the server was lost. Attempting reset: "), stderr);
+ fputs(_("The connection to the server was lost. Attempting reset: "), stderr);
PQreset(pset.db);
OK = ConnectionUp();
if (!OK)
{
- fputs(gettext("Failed.\n"), stderr);
+ fputs(_("Failed.\n"), stderr);
PQfinish(pset.db);
pset.db = NULL;
ResetCancelConn();
UnsyncVariables();
}
else
- fputs(gettext("Succeeded.\n"), stderr);
+ fputs(_("Succeeded.\n"), stderr);
}
return OK;
*
* 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
}
-/*
- * on errors, print syntax error position if available.
- *
- * the query is expected to be in the client encoding.
- */
-static void
-ReportSyntaxErrorPosition(const PGresult *result, const char *query)
-{
-#define DISPLAY_SIZE 60 /* screen width limit, in screen cols */
-#define MIN_RIGHT_CUT 10 /* try to keep this far away from EOL */
-
- int loc = 0;
- const char *sp;
- int clen,
- slen,
- i,
- *qidx,
- *scridx,
- qoffset,
- scroffset,
- ibeg,
- iend,
- loc_line;
- char *wquery;
- bool beg_trunc,
- end_trunc;
- PQExpBufferData msg;
-
- if (pset.verbosity == PQERRORS_TERSE)
- return;
-
- sp = PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION);
- if (sp == NULL)
- {
- sp = PQresultErrorField(result, PG_DIAG_INTERNAL_POSITION);
- if (sp == NULL)
- return; /* no syntax error */
- query = PQresultErrorField(result, PG_DIAG_INTERNAL_QUERY);
- }
- if (query == NULL)
- return; /* nothing to reference location to */
-
- if (sscanf(sp, "%d", &loc) != 1)
- {
- psql_error("INTERNAL ERROR: unexpected statement position \"%s\"\n",
- sp);
- return;
- }
-
- /* Make a writable copy of the query, and a buffer for messages. */
- wquery = pg_strdup(query);
-
- initPQExpBuffer(&msg);
-
- /*
- * The returned cursor position is measured in logical characters.
- * Each character might occupy multiple physical bytes in the string,
- * and in some Far Eastern character sets it might take more than one
- * screen column as well. We compute the starting byte offset and
- * starting screen column of each logical character, and store these
- * in qidx[] and scridx[] respectively.
- */
-
- /* we need a safe allocation size... */
- slen = strlen(query) + 1;
-
- qidx = (int *) pg_malloc(slen * sizeof(int));
- scridx = (int *) pg_malloc(slen * sizeof(int));
-
- qoffset = 0;
- scroffset = 0;
- for (i = 0; query[qoffset] != '\0'; i++)
- {
- qidx[i] = qoffset;
- scridx[i] = scroffset;
- scroffset += PQdsplen(&query[qoffset], pset.encoding);
- qoffset += PQmblen(&query[qoffset], pset.encoding);
- }
- qidx[i] = qoffset;
- scridx[i] = scroffset;
- clen = i;
- psql_assert(clen < slen);
-
- /* convert loc to zero-based offset in qidx/scridx arrays */
- loc--;
-
- /* do we have something to show? */
- if (loc >= 0 && loc <= clen)
- {
- /* input line number of our syntax error. */
- loc_line = 1;
- /* first included char of extract. */
- ibeg = 0;
- /* last-plus-1 included char of extract. */
- iend = clen;
-
- /*
- * Replace tabs with spaces in the writable copy. (Later we might
- * want to think about coping with their variable screen width,
- * but not today.)
- *
- * Extract line number and begin and end indexes of line containing
- * error location. There will not be any newlines or carriage
- * returns in the selected extract.
- */
- for (i = 0; i < clen; i++)
- {
- /* character length must be 1 or it's not ASCII */
- if ((qidx[i + 1] - qidx[i]) == 1)
- {
- if (wquery[qidx[i]] == '\t')
- wquery[qidx[i]] = ' ';
- else if (wquery[qidx[i]] == '\r' || wquery[qidx[i]] == '\n')
- {
- if (i < loc)
- {
- /*
- * count lines before loc. Each \r or \n counts
- * as a line except when \r \n appear together.
- */
- if (wquery[qidx[i]] == '\r' ||
- i == 0 ||
- (qidx[i] - qidx[i - 1]) != 1 ||
- wquery[qidx[i - 1]] != '\r')
- loc_line++;
- /* extract beginning = last line start before loc. */
- ibeg = i + 1;
- }
- else
- {
- /* set extract end. */
- iend = i;
- /* done scanning. */
- break;
- }
- }
- }
- }
-
- /* If the line extracted is too long, we truncate it. */
- beg_trunc = false;
- end_trunc = false;
- if (scridx[iend] - scridx[ibeg] > DISPLAY_SIZE)
- {
- /*
- * We first truncate right if it is enough. This code might
- * be off a space or so on enforcing MIN_RIGHT_CUT if there's
- * a wide character right there, but that should be okay.
- */
- if (scridx[ibeg] + DISPLAY_SIZE >= scridx[loc] + MIN_RIGHT_CUT)
- {
- while (scridx[iend] - scridx[ibeg] > DISPLAY_SIZE)
- iend--;
- end_trunc = true;
- }
- else
- {
- /* Truncate right if not too close to loc. */
- while (scridx[loc] + MIN_RIGHT_CUT < scridx[iend])
- {
- iend--;
- end_trunc = true;
- }
-
- /* Truncate left if still too long. */
- while (scridx[iend] - scridx[ibeg] > DISPLAY_SIZE)
- {
- ibeg++;
- beg_trunc = true;
- }
- }
- }
-
- /* the extract MUST contain the target position! */
- psql_assert(ibeg <= loc && loc <= iend);
-
- /* truncate working copy at desired endpoint */
- wquery[qidx[iend]] = '\0';
-
- /* Begin building the finished message. */
- printfPQExpBuffer(&msg, gettext("LINE %d: "), loc_line);
- if (beg_trunc)
- appendPQExpBufferStr(&msg, "...");
-
- /*
- * While we have the prefix in the msg buffer, compute its screen
- * width.
- */
- scroffset = 0;
- for (i = 0; i < msg.len; i += PQmblen(&msg.data[i], pset.encoding))
- scroffset += PQdsplen(&msg.data[i], pset.encoding);
-
- /* Finish and emit the message. */
- appendPQExpBufferStr(&msg, &wquery[qidx[ibeg]]);
- if (end_trunc)
- appendPQExpBufferStr(&msg, "...");
-
- psql_error("%s\n", msg.data);
-
- /* Now emit the cursor marker line. */
- scroffset += scridx[loc] - scridx[ibeg];
- resetPQExpBuffer(&msg);
- for (i = 0; i < scroffset; i++)
- appendPQExpBufferChar(&msg, ' ');
- appendPQExpBufferChar(&msg, '^');
-
- psql_error("%s\n", msg.data);
- }
-
- /* Clean up. */
- termPQExpBuffer(&msg);
-
- free(wquery);
- free(qidx);
- free(scridx);
-}
-
-
/*
* AcceptResult
*
case PGRES_TUPLES_OK:
case PGRES_EMPTY_QUERY:
case PGRES_COPY_IN:
- /* Fine, do nothing */
- break;
-
case PGRES_COPY_OUT:
- /* keep cancel connection for copy out state */
- SetCancelConn();
+ /* Fine, do nothing */
break;
default:
if (!OK)
{
- psql_error("%s", PQerrorMessage(pset.db));
- ReportSyntaxErrorPosition(result, query);
+ const char *error = PQerrorMessage(pset.db);
+
+ if (strlen(error))
+ psql_error("%s", error);
+
CheckConnection();
}
* 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".
*/
echo_hidden = SwitchVariable(pset.vars, "ECHO_HIDDEN", "noexec", NULL);
if (echo_hidden != VAR_NOTSET)
{
- printf("********* QUERY **********\n"
- "%s\n"
- "**************************\n\n", query);
+ printf(_("********* QUERY **********\n"
+ "%s\n"
+ "**************************\n\n"), query);
fflush(stdout);
+ if (pset.logfile)
+ {
+ fprintf(pset.logfile,
+ _("********* QUERY **********\n"
+ "%s\n"
+ "**************************\n\n"), query);
+ fflush(pset.logfile);
+ }
if (echo_hidden == 1) /* noexec? */
return NULL;
while ((notify = PQnotifies(pset.db)))
{
- fprintf(pset.queryFout, gettext("Asynchronous notification \"%s\" received from server process with PID %d.\n"),
+ fprintf(pset.queryFout, _("Asynchronous notification \"%s\" received from server process with PID %d.\n"),
notify->relname, notify->be_pid);
fflush(pset.queryFout);
PQfreemem(notify);
static bool
PrintQueryTuples(const PGresult *results)
{
+ printQueryOpt my_popt = pset.popt;
+
/* write output to \g argument, if any */
if (pset.gfname)
{
return false;
}
- printQuery(results, &pset.popt, pset.queryFout);
+ printQuery(results, &my_popt, pset.queryFout, pset.logfile);
/* close file/pipe, restore old setting */
setQFout(NULL);
pset.gfname = NULL;
}
else
- printQuery(results, &pset.popt, pset.queryFout);
+ printQuery(results, &my_popt, pset.queryFout, pset.logfile);
return true;
}
break;
case PGRES_COPY_OUT:
+ SetCancelConn();
success = handleCopyOut(pset.db, pset.queryFout);
+ ResetCancelConn();
break;
case PGRES_COPY_IN:
- success = handleCopyIn(pset.db, pset.cur_cmd_source);
+ SetCancelConn();
+ success = handleCopyIn(pset.db, pset.cur_cmd_source,
+ PQbinaryTuples(results));
+ ResetCancelConn();
break;
default:
}
+/*
+ * PrintQueryStatus: report command status as required
+ *
+ * Note: Utility function for use by PrintQueryResults() only.
+ */
+static void
+PrintQueryStatus(PGresult *results)
+{
+ char buf[16];
+
+ if (!QUIET())
+ {
+ if (pset.popt.topt.format == PRINT_HTML)
+ {
+ fputs("<p>", pset.queryFout);
+ html_escaped_print(PQcmdStatus(results), pset.queryFout);
+ fputs("</p>\n", pset.queryFout);
+ }
+ else
+ fprintf(pset.queryFout, "%s\n", PQcmdStatus(results));
+ }
+
+ if (pset.logfile)
+ fprintf(pset.logfile, "%s\n", PQcmdStatus(results));
+
+ snprintf(buf, sizeof(buf), "%u", (unsigned int) PQoidValue(results));
+ SetVariable(pset.vars, "LASTOID", buf);
+}
+
+
/*
* PrintQueryResults: print out query results as required
*
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));
- }
- SetVariable(pset.vars, "LASTOID", buf);
- break;
- }
+ PrintQueryStatus(results);
+ success = true;
+ break;
case PGRES_EMPTY_QUERY:
success = true;
PGresult *results;
TimevalStruct before,
after;
- bool OK;
+ bool OK,
+ on_error_rollback_savepoint = false;
+ PGTransactionStatusType transaction_status;
+ static bool on_error_rollback_warning = false;
+ const char *rollback_str;
if (!pset.db)
{
{
char buf[3];
- printf(gettext("***(Single step mode: verify command)*******************************************\n"
- "%s\n"
- "***(press return to proceed or enter x and return to cancel)********************\n"),
+ printf(_("***(Single step mode: verify command)*******************************************\n"
+ "%s\n"
+ "***(press return to proceed or enter x and return to cancel)********************\n"),
query);
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) != NULL)
fflush(stdout);
}
+ if (pset.logfile)
+ {
+ fprintf(pset.logfile,
+ _("********* QUERY **********\n"
+ "%s\n"
+ "**************************\n\n"), query);
+ fflush(pset.logfile);
+ }
+
SetCancelConn();
- if (PQtransactionStatus(pset.db) == PQTRANS_IDLE &&
+ transaction_status = PQtransactionStatus(pset.db);
+
+ if (transaction_status == PQTRANS_IDLE &&
!GetVariableBool(pset.vars, "AUTOCOMMIT") &&
!command_no_begin(query))
{
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)
if (OK)
OK = PrintQueryResults(results);
+ /* If we made a temporary savepoint, possibly release/rollback */
+ if (on_error_rollback_savepoint)
+ {
+ PGresult *svptres;
+
+ transaction_status = PQtransactionStatus(pset.db);
+
+ /* We always rollback on an error */
+ if (transaction_status == PQTRANS_INERROR)
+ svptres = PQexec(pset.db, "ROLLBACK TO pg_psql_temporary_savepoint");
+ /* If they are no longer in a transaction, then do nothing */
+ else if (transaction_status != PQTRANS_INTRANS)
+ svptres = NULL;
+ else
+ {
+ /*
+ * Do nothing if they are messing with savepoints themselves: If
+ * the user did RELEASE or ROLLBACK, our savepoint is gone. If
+ * they issued a SAVEPOINT, releasing ours would remove theirs.
+ */
+ if (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
+ strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
+ strcmp(PQcmdStatus(results), "ROLLBACK") == 0)
+ svptres = NULL;
+ else
+ svptres = PQexec(pset.db, "RELEASE pg_psql_temporary_savepoint");
+ }
+ if (svptres && PQresultStatus(svptres) != PGRES_COMMAND_OK)
+ {
+ psql_error("%s", PQerrorMessage(pset.db));
+ PQclear(results);
+ PQclear(svptres);
+ ResetCancelConn();
+ return false;
+ }
+
+ PQclear(svptres);
+ }
+
PQclear(results);
/* Possible microtiming output */
if (OK && pset.timing)
- printf(gettext("Time: %.3f ms\n"), DIFF_MSEC(&after, &before));
+ printf(_("Time: %.3f ms\n"), DIFF_MSEC(&after, &before));
/* check for events that may occur during query execution */
static const char *
skip_white_space(const char *query)
{
- int cnestlevel = 0; /* slash-star comment nest level */
+ int cnestlevel = 0; /* slash-star comment nest level */
while (*query)
{
- int mblen = PQmblen(query, pset.encoding);
+ int mblen = PQmblen(query, pset.encoding);
/*
- * Note: we assume the encoding is a superset of ASCII, so that
- * for example "query[0] == '/'" is meaningful. However, we do NOT
- * assume that the second and subsequent bytes of a multibyte
- * character couldn't look like ASCII characters; so it is critical
- * to advance by mblen, not 1, whenever we haven't exactly identified
- * the character we are skipping over.
+ * Note: we assume the encoding is a superset of ASCII, so that for
+ * example "query[0] == '/'" is meaningful. However, we do NOT assume
+ * that the second and subsequent bytes of a multibyte character
+ * couldn't look like ASCII characters; so it is critical to advance
+ * by mblen, not 1, whenever we haven't exactly identified the
+ * character we are skipping over.
*/
if (isspace((unsigned char) *query))
query += mblen;
else if (cnestlevel == 0 && query[0] == '-' && query[1] == '-')
{
query += 2;
+
/*
- * We have to skip to end of line since any slash-star inside
- * the -- comment does NOT start a slash-star comment.
+ * We have to skip to end of line since any slash-star inside the
+ * -- comment does NOT start a slash-star comment.
*/
while (*query)
{
wordlen += PQmblen(&query[wordlen], pset.encoding);
/*
- * Transaction control commands. These should include every keyword
- * that gives rise to a TransactionStmt in the backend grammar, except
- * for the savepoint-related commands.
+ * Transaction control commands. These should include every keyword that
+ * gives rise to a TransactionStmt in the backend grammar, except for the
+ * savepoint-related commands.
*
- * (We assume that START must be START TRANSACTION, since there is
+ * (We assume that START must be START TRANSACTION, since there is
* presently no other "START foo" command.)
*/
if (wordlen == 5 && pg_strncasecmp(query, "abort", 5) == 0)
return true;
if (wordlen == 8 && pg_strncasecmp(query, "rollback", 8) == 0)
return true;
+ if (wordlen == 7 && pg_strncasecmp(query, "prepare", 7) == 0)
+ {
+ /* PREPARE TRANSACTION is a TC command, PREPARE foo is not */
+ query += wordlen;
+
+ query = skip_white_space(query);
+
+ wordlen = 0;
+ while (isalpha((unsigned char) query[wordlen]))
+ wordlen += PQmblen(&query[wordlen], pset.encoding);
+
+ if (wordlen == 11 && pg_strncasecmp(query, "transaction", 11) == 0)
+ return true;
+ return false;
+ }
/*
- * Commands not allowed within transactions. The statements checked
- * for here should be exactly those that call PreventTransactionChain()
- * in the backend.
+ * Commands not allowed within transactions. The statements checked for
+ * here should be exactly those that call PreventTransactionChain() in the
+ * backend.
*
- * Note: we are a bit sloppy about CLUSTER, which is transactional in
- * some variants but not others.
+ * Note: we are a bit sloppy about CLUSTER, which is transactional in some
+ * variants but not others.
*/
if (wordlen == 6 && pg_strncasecmp(query, "vacuum", 6) == 0)
return true;
return true;
/*
- * Note: these tests will match REINDEX TABLESPACE, which isn't really
- * a valid command so we don't care much. The other five possible
- * matches are correct.
+ * Note: these tests will match CREATE SYSTEM, DROP SYSTEM, and REINDEX
+ * TABLESPACE, which aren't really valid commands so we don't care much.
+ * The other six possible matches are correct.
*/
if ((wordlen == 6 && pg_strncasecmp(query, "create", 6) == 0) ||
(wordlen == 4 && pg_strncasecmp(query, "drop", 4) == 0) ||
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;
}
}
-char
-parse_char(char **buf)
-{
- long l;
-
- l = strtol(*buf, buf, 0);
- --*buf;
- return (char) l;
-}
-
-
/*
* Test if the current user is a database superuser.
*
}
+/*
+ * 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.
*
if (!filename || !(*filename))
return NULL;
- /* MSDOS uses tilde for short versions of long file names, so skip it. */
+ /*
+ * WIN32 doesn't use tilde expansion for file names. Also, it uses tilde
+ * for short versions of long file names, though the tilde is usually
+ * toward the end, not at the beginning.
+ */
#ifndef WIN32
/* try tilde expansion */
*p = '\0';
if (*(fn + 1) == '\0')
- get_home_path(home);
+ get_home_path(home); /* ~ or ~/ only */
else if ((pw = getpwnam(fn + 1)) != NULL)
- StrNCpy(home, pw->pw_dir, MAXPGPATH);
+ StrNCpy(home, pw->pw_dir, MAXPGPATH); /* ~user */
*p = oldp;
if (strlen(home) != 0)