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 */
357567static bool
358- AcceptResult (const PGresult * result )
568+ AcceptResult (const PGresult * result , const char * query )
359569{
360570bool OK = true;
361571
@@ -386,6 +596,7 @@ AcceptResult(const PGresult *result)
386596if (!OK )
387597{
388598psql_error ("%s" ,PQerrorMessage (pset .db ));
599+ ReportSyntaxErrorPosition (result ,query );
389600CheckConnection ();
390601}
391602
@@ -449,7 +660,7 @@ PSQLexec(const char *query, bool start_xact)
449660
450661res = PQexec (pset .db ,query );
451662
452- if (!AcceptResult (res )&& res )
663+ if (!AcceptResult (res , query )&& res )
453664{
454665PQclear (res );
455666res = NULL ;
@@ -695,7 +906,7 @@ SendQuery(const char *query)
695906results = 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
700911if (pset .timing )
701912GETTIMEOFDAY (& after );