summaryrefslogtreecommitdiff
path: root/src/bin/psql/common.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2004-03-14 04:25:18 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2004-03-14 04:25:18 +0000
commit7665d1bc166c78ff5f8b8352201005c0ba385b26 (patch)
tree6cd8ee8464ecf84a8e47ea13cbf35e3f6cb7cc92 /src/bin/psql/common.c
parent181d4d410aaa2c3f86ea513766d7558703e0a0b6 (diff)
Teach psql to show the location of syntax errors visually, per recent
discussions. Patch by Fabien Coelho and Tom Lane. Still needs to be taught about multi-screen-column kanji characters; Tatsuo has promised to provide the needed infrastructure for that.
Diffstat (limited to 'src/bin/psql/common.c')
-rw-r--r--src/bin/psql/common.c219
1 files changed, 215 insertions, 4 deletions
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 3ac3a511ff3..511107b575d 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -3,7 +3,7 @@
*
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.82 2004/01/25 03:07:22 neilc Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.83 2004/03/14 04:25:17 tgl Exp $
*/
#include "postgres_fe.h"
#include "common.h"
@@ -346,6 +346,216 @@ ResetCancelConn(void)
/*
+ * 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 (query == NULL)
+ return; /* nothing to do */
+ sp = PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION);
+ if (sp == NULL)
+ return; /* no syntax error location */
+ /*
+ * We punt if the report contains any CONTEXT. This typically means that
+ * the syntax error is from inside a function, and the cursor position
+ * is not relevant to the original query string.
+ */
+ if (PQresultErrorField(result, PG_DIAG_CONTEXT) != NULL)
+ return;
+
+ 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 += 1; /* XXX fix me when we have screen width info */
+ 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 += 1; /* XXX fix me when we have screen width info */
+ }
+
+ /* 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
*
* Checks whether a result is valid, giving an error message if necessary;
@@ -355,7 +565,7 @@ ResetCancelConn(void)
* 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;
@@ -386,6 +596,7 @@ AcceptResult(const PGresult *result)
if (!OK)
{
psql_error("%s", PQerrorMessage(pset.db));
+ ReportSyntaxErrorPosition(result, query);
CheckConnection();
}
@@ -449,7 +660,7 @@ PSQLexec(const char *query, bool start_xact)
res = PQexec(pset.db, query);
- if (!AcceptResult(res) && res)
+ if (!AcceptResult(res, query) && res)
{
PQclear(res);
res = NULL;
@@ -695,7 +906,7 @@ SendQuery(const char *query)
results = PQexec(pset.db, query);
/* these operations are included in the timing result: */
- OK = (AcceptResult(results) && ProcessCopyResult(results));
+ OK = (AcceptResult(results, query) && ProcessCopyResult(results));
if (pset.timing)
GETTIMEOFDAY(&after);