1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-12 02:37:31 +03:00

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.
This commit is contained in:
Tom Lane
2004-03-14 04:25:18 +00:00
parent 181d4d410a
commit 7665d1bc16
5 changed files with 503 additions and 12 deletions

View File

@@ -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"
@@ -345,6 +345,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
*
@@ -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);