*
* Copyright (c) 2000-2004, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.90 2004/08/29 05:06:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.91 2004/09/20 18:51:19 tgl Exp $
*/
#include "postgres_fe.h"
#include "common.h"
extern bool prompt_state;
-static bool is_transact_command(const char *query);
+static bool command_no_begin(const char *query);
/*
if (PQtransactionStatus(pset.db) == PQTRANS_IDLE &&
!GetVariableBool(pset.vars, "AUTOCOMMIT") &&
- !is_transact_command(query))
+ !command_no_begin(query))
{
results = PQexec(pset.db, "BEGIN");
if (PQresultStatus(results) != PGRES_COMMAND_OK)
return OK;
}
+
/*
- * check whether a query string begins with BEGIN/COMMIT/ROLLBACK/START XACT
+ * Advance the given char pointer over white space and SQL comments.
*/
-static bool
-is_transact_command(const char *query)
+static const char *
+skip_white_space(const char *query)
{
- int wordlen;
+ int cnestlevel = 0; /* slash-star comment nest level */
- /*
- * First we must advance over any whitespace and comments.
- */
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++;
- else if (query[0] == '-' && query[1] == '-')
+ query += mblen;
+ else if (query[0] == '/' && query[1] == '*')
{
+ cnestlevel++;
query += 2;
- while (*query && *query != '\n')
- query++;
}
- else if (query[0] == '/' && query[1] == '*')
+ 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[0] == '*' && query[1] == '/')
+ if (*query == '\n')
{
- query += 2;
+ query++;
break;
}
- else
- query++;
+ query += PQmblen(query, pset.encoding);
}
}
+ else if (cnestlevel > 0)
+ query += mblen;
else
break; /* found first token */
}
+ 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;
+
/*
- * Check word length ("beginx" is not "begin").
+ * 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++;
+ 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 == 8 && pg_strncasecmp(query, "rollback", 8) == 0)
+ if (wordlen == 3 && pg_strncasecmp(query, "end", 3) == 0)
return true;
- if (wordlen == 5 && pg_strncasecmp(query, "abort", 5) == 0)
+ if (wordlen == 8 && pg_strncasecmp(query, "rollback", 8) == 0)
return true;
- if (wordlen == 3 && pg_strncasecmp(query, "end", 3) == 0)
+
+ /*
+ * 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 == 5 && pg_strncasecmp(query, "start", 5) == 0)
+ if (wordlen == 7 && pg_strncasecmp(query, "cluster", 7) == 0)
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.
+ */
+ 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 == 10 && pg_strncasecmp(query, "tablespace", 10) == 0)
+ return true;
+ }
+
return false;
}