Skip to content

Commit 7665d1b

Browse files
committed
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.
1 parent 181d4d4 commit 7665d1b

File tree

5 files changed

+503
-12
lines changed

5 files changed

+503
-12
lines changed

src/bin/psql/common.c

+215-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
55
*
6-
* $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.82 2004/01/25 03:07:22 neilc Exp $
6+
* $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.83 2004/03/14 04:25:17 tgl Exp $
77
*/
88
#include "postgres_fe.h"
99
#include "common.h"
@@ -345,6 +345,216 @@ ResetCancelConn(void)
345345
}
346346

347347

348+
/*
349+
* on errors, print syntax error position if available.
350+
*
351+
* the query is expected to be in the client encoding.
352+
*/
353+
static void
354+
ReportSyntaxErrorPosition(const PGresult *result, const char *query)
355+
{
356+
#define DISPLAY_SIZE 60 /* screen width limit, in screen cols */
357+
#define MIN_RIGHT_CUT 10 /* try to keep this far away from EOL */
358+
359+
int loc = 0;
360+
const char *sp;
361+
int clen, slen, i, *qidx, *scridx, qoffset, scroffset, ibeg, iend,
362+
loc_line;
363+
char *wquery;
364+
bool beg_trunc, end_trunc;
365+
PQExpBufferData msg;
366+
367+
if (query == NULL)
368+
return; /* nothing to do */
369+
sp = PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION);
370+
if (sp == NULL)
371+
return; /* no syntax error location */
372+
/*
373+
* We punt if the report contains any CONTEXT. This typically means that
374+
* the syntax error is from inside a function, and the cursor position
375+
* is not relevant to the original query string.
376+
*/
377+
if (PQresultErrorField(result, PG_DIAG_CONTEXT) != NULL)
378+
return;
379+
380+
if (sscanf(sp, "%d", &loc) != 1)
381+
{
382+
psql_error("INTERNAL ERROR: unexpected statement position \"%s\"\n",
383+
sp);
384+
return;
385+
}
386+
387+
/* Make a writable copy of the query, and a buffer for messages. */
388+
wquery = pg_strdup(query);
389+
390+
initPQExpBuffer(&msg);
391+
392+
/*
393+
* The returned cursor position is measured in logical characters.
394+
* Each character might occupy multiple physical bytes in the string,
395+
* and in some Far Eastern character sets it might take more than one
396+
* screen column as well. We compute the starting byte offset and
397+
* starting screen column of each logical character, and store these
398+
* in qidx[] and scridx[] respectively.
399+
*/
400+
401+
/* we need a safe allocation size... */
402+
slen = strlen(query) + 1;
403+
404+
qidx = (int *) pg_malloc(slen * sizeof(int));
405+
scridx = (int *) pg_malloc(slen * sizeof(int));
406+
407+
qoffset = 0;
408+
scroffset = 0;
409+
for (i = 0; query[qoffset] != '\0'; i++)
410+
{
411+
qidx[i] = qoffset;
412+
scridx[i] = scroffset;
413+
scroffset += 1; /* XXX fix me when we have screen width info */
414+
qoffset += PQmblen(&query[qoffset], pset.encoding);
415+
}
416+
qidx[i] = qoffset;
417+
scridx[i] = scroffset;
418+
clen = i;
419+
psql_assert(clen < slen);
420+
421+
/* convert loc to zero-based offset in qidx/scridx arrays */
422+
loc--;
423+
424+
/* do we have something to show? */
425+
if (loc >= 0 && loc <= clen)
426+
{
427+
/* input line number of our syntax error. */
428+
loc_line = 1;
429+
/* first included char of extract. */
430+
ibeg = 0;
431+
/* last-plus-1 included char of extract. */
432+
iend = clen;
433+
434+
/*
435+
* Replace tabs with spaces in the writable copy. (Later we might
436+
* want to think about coping with their variable screen width,
437+
* but not today.)
438+
*
439+
* Extract line number and begin and end indexes of line containing
440+
* error location. There will not be any newlines or carriage
441+
* returns in the selected extract.
442+
*/
443+
for (i=0; i<clen; i++)
444+
{
445+
/* character length must be 1 or it's not ASCII */
446+
if ((qidx[i+1]-qidx[i]) == 1)
447+
{
448+
if (wquery[qidx[i]] == '\t')
449+
wquery[qidx[i]] = ' ';
450+
else if (wquery[qidx[i]] == '\r' || wquery[qidx[i]] == '\n')
451+
{
452+
if (i < loc)
453+
{
454+
/*
455+
* count lines before loc. Each \r or \n counts
456+
* as a line except when \r \n appear together.
457+
*/
458+
if (wquery[qidx[i]] == '\r' ||
459+
i == 0 ||
460+
(qidx[i]-qidx[i-1]) != 1 ||
461+
wquery[qidx[i-1]] != '\r')
462+
loc_line++;
463+
/* extract beginning = last line start before loc. */
464+
ibeg = i+1;
465+
}
466+
else
467+
{
468+
/* set extract end. */
469+
iend = i;
470+
/* done scanning. */
471+
break;
472+
}
473+
}
474+
}
475+
}
476+
477+
/* If the line extracted is too long, we truncate it. */
478+
beg_trunc = false;
479+
end_trunc = false;
480+
if (scridx[iend]-scridx[ibeg] > DISPLAY_SIZE)
481+
{
482+
/*
483+
* We first truncate right if it is enough. This code might
484+
* be off a space or so on enforcing MIN_RIGHT_CUT if there's
485+
* a wide character right there, but that should be okay.
486+
*/
487+
if (scridx[ibeg]+DISPLAY_SIZE >= scridx[loc]+MIN_RIGHT_CUT)
488+
{
489+
while (scridx[iend]-scridx[ibeg] > DISPLAY_SIZE)
490+
iend--;
491+
end_trunc = true;
492+
}
493+
else
494+
{
495+
/* Truncate right if not too close to loc. */
496+
while (scridx[loc]+MIN_RIGHT_CUT < scridx[iend])
497+
{
498+
iend--;
499+
end_trunc = true;
500+
}
501+
502+
/* Truncate left if still too long. */
503+
while (scridx[iend]-scridx[ibeg] > DISPLAY_SIZE)
504+
{
505+
ibeg++;
506+
beg_trunc = true;
507+
}
508+
}
509+
}
510+
511+
/* the extract MUST contain the target position! */
512+
psql_assert(ibeg<=loc && loc<=iend);
513+
514+
/* truncate working copy at desired endpoint */
515+
wquery[qidx[iend]] = '\0';
516+
517+
/* Begin building the finished message. */
518+
printfPQExpBuffer(&msg, gettext("LINE %d: "), loc_line);
519+
if (beg_trunc)
520+
appendPQExpBufferStr(&msg, "...");
521+
522+
/*
523+
* While we have the prefix in the msg buffer, compute its screen
524+
* width.
525+
*/
526+
scroffset = 0;
527+
for (i = 0; i < msg.len; i += PQmblen(&msg.data[i], pset.encoding))
528+
{
529+
scroffset += 1; /* XXX fix me when we have screen width info */
530+
}
531+
532+
/* Finish and emit the message. */
533+
appendPQExpBufferStr(&msg, &wquery[qidx[ibeg]]);
534+
if (end_trunc)
535+
appendPQExpBufferStr(&msg, "...");
536+
537+
psql_error("%s\n", msg.data);
538+
539+
/* Now emit the cursor marker line. */
540+
scroffset += scridx[loc] - scridx[ibeg];
541+
resetPQExpBuffer(&msg);
542+
for (i = 0; i < scroffset; i++)
543+
appendPQExpBufferChar(&msg, ' ');
544+
appendPQExpBufferChar(&msg, '^');
545+
546+
psql_error("%s\n", msg.data);
547+
}
548+
549+
/* Clean up. */
550+
termPQExpBuffer(&msg);
551+
552+
free(wquery);
553+
free(qidx);
554+
free(scridx);
555+
}
556+
557+
348558
/*
349559
* AcceptResult
350560
*
@@ -355,7 +565,7 @@ ResetCancelConn(void)
355565
* Returns true for valid result, false for error state.
356566
*/
357567
static bool
358-
AcceptResult(const PGresult *result)
568+
AcceptResult(const PGresult *result, const char *query)
359569
{
360570
bool OK = true;
361571

@@ -386,6 +596,7 @@ AcceptResult(const PGresult *result)
386596
if (!OK)
387597
{
388598
psql_error("%s", PQerrorMessage(pset.db));
599+
ReportSyntaxErrorPosition(result, query);
389600
CheckConnection();
390601
}
391602

@@ -449,7 +660,7 @@ PSQLexec(const char *query, bool start_xact)
449660

450661
res = PQexec(pset.db, query);
451662

452-
if (!AcceptResult(res) && res)
663+
if (!AcceptResult(res, query) && res)
453664
{
454665
PQclear(res);
455666
res = NULL;
@@ -695,7 +906,7 @@ SendQuery(const char *query)
695906
results = PQexec(pset.db, query);
696907

697908
/* these operations are included in the timing result: */
698-
OK = (AcceptResult(results) && ProcessCopyResult(results));
909+
OK = (AcceptResult(results, query) && ProcessCopyResult(results));
699910

700911
if (pset.timing)
701912
GETTIMEOFDAY(&after);

0 commit comments

Comments
 (0)