3
3
*
4
4
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
5
5
*
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 $
7
7
*/
8
8
#include "postgres_fe.h"
9
9
#include "common.h"
@@ -345,6 +345,216 @@ ResetCancelConn(void)
345
345
}
346
346
347
347
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
+
348
558
/*
349
559
* AcceptResult
350
560
*
@@ -355,7 +565,7 @@ ResetCancelConn(void)
355
565
* Returns true for valid result, false for error state.
356
566
*/
357
567
static bool
358
- AcceptResult (const PGresult * result )
568
+ AcceptResult (const PGresult * result , const char * query )
359
569
{
360
570
bool OK = true;
361
571
@@ -386,6 +596,7 @@ AcceptResult(const PGresult *result)
386
596
if (!OK )
387
597
{
388
598
psql_error ("%s" ,PQerrorMessage (pset .db ));
599
+ ReportSyntaxErrorPosition (result ,query );
389
600
CheckConnection ();
390
601
}
391
602
@@ -449,7 +660,7 @@ PSQLexec(const char *query, bool start_xact)
449
660
450
661
res = PQexec (pset .db ,query );
451
662
452
- if (!AcceptResult (res )&& res )
663
+ if (!AcceptResult (res , query )&& res )
453
664
{
454
665
PQclear (res );
455
666
res = NULL ;
@@ -695,7 +906,7 @@ SendQuery(const char *query)
695
906
results = PQexec (pset .db ,query );
696
907
697
908
/* these operations are included in the timing result: */
698
- OK = (AcceptResult (results )&& ProcessCopyResult (results ));
909
+ OK = (AcceptResult (results , query )&& ProcessCopyResult (results ));
699
910
700
911
if (pset .timing )
701
912
GETTIMEOFDAY (& after );