diff options
Diffstat (limited to 'src/interfaces')
| -rw-r--r-- | src/interfaces/libpq/fe-connect.c | 4 | ||||
| -rw-r--r-- | src/interfaces/libpq/fe-exec.c | 23 | ||||
| -rw-r--r-- | src/interfaces/libpq/fe-protocol3.c | 247 | ||||
| -rw-r--r-- | src/interfaces/libpq/libpq-int.h | 3 |
4 files changed, 267 insertions, 10 deletions
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index ef708658d7..98d834b595 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.327 2006/03/05 15:59:08 momjian Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.328 2006/03/14 22:48:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1918,6 +1918,8 @@ freePGconn(PGconn *conn) free(conn->krbsrvname); #endif /* Note that conn->Pfdebug is not ours to close or free */ + if (conn->last_query) + free(conn->last_query); pg_freeaddrinfo_all(conn->addrlist_family, conn->addrlist); notify = conn->notifyHead; while (notify != NULL) diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 26d2b58fd9..7f09ff6dd2 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.181 2006/03/05 15:59:09 momjian Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.182 2006/03/14 22:48:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -667,6 +667,12 @@ PQsendQuery(PGconn *conn, const char *query) /* remember we are using simple query protocol */ conn->queryclass = PGQUERY_SIMPLE; + /* and remember the query text too, if possible */ + /* if insufficient memory, last_query just winds up NULL */ + if (conn->last_query) + free(conn->last_query); + conn->last_query = strdup(query); + /* * Give the data a push. In nonblock mode, don't complain if we're unable * to send it all; PQgetResult() will do any additional flushing needed. @@ -788,6 +794,12 @@ PQsendPrepare(PGconn *conn, /* remember we are doing just a Parse */ conn->queryclass = PGQUERY_PREPARE; + /* and remember the query text too, if possible */ + /* if insufficient memory, last_query just winds up NULL */ + if (conn->last_query) + free(conn->last_query); + conn->last_query = strdup(query); + /* * Give the data a push. In nonblock mode, don't complain if we're unable * to send it all; PQgetResult() will do any additional flushing needed. @@ -1017,6 +1029,15 @@ PQsendQueryGuts(PGconn *conn, /* remember we are using extended query protocol */ conn->queryclass = PGQUERY_EXTENDED; + /* and remember the query text too, if possible */ + /* if insufficient memory, last_query just winds up NULL */ + if (conn->last_query) + free(conn->last_query); + if (command) + conn->last_query = strdup(command); + else + conn->last_query = NULL; + /* * Give the data a push. In nonblock mode, don't complain if we're unable * to send it all; PQgetResult() will do any additional flushing needed. diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 0fbb3738ad..35f015ecce 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -8,13 +8,12 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.25 2006/03/05 15:59:09 momjian Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.26 2006/03/14 22:48:23 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres_fe.h" -#include <errno.h> #include <ctype.h> #include <fcntl.h> @@ -51,6 +50,8 @@ static int getParameterStatus(PGconn *conn); static int getNotify(PGconn *conn); static int getCopyStart(PGconn *conn, ExecStatusType copytype); static int getReadyForQuery(PGconn *conn); +static void reportErrorPosition(PQExpBuffer msg, const char *query, + int loc, int encoding); static int build_startup_packet(const PGconn *conn, char *packet, const PQEnvironmentOption *options); @@ -614,6 +615,8 @@ pqGetErrorNotice3(PGconn *conn, bool isError) PQExpBufferData workBuf; char id; const char *val; + const char *querytext = NULL; + int querypos = 0; /* * Since the fields might be pretty long, we create a temporary @@ -666,22 +669,46 @@ pqGetErrorNotice3(PGconn *conn, bool isError) val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION); if (val) { - /* translator: %s represents a digit string */ - appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), val); + if (conn->verbosity != PQERRORS_TERSE && conn->last_query != NULL) + { + /* emit position as a syntax cursor display */ + querytext = conn->last_query; + querypos = atoi(val); + } + else + { + /* emit position as text addition to primary message */ + /* translator: %s represents a digit string */ + appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), + val); + } } else { val = PQresultErrorField(res, PG_DIAG_INTERNAL_POSITION); if (val) { - /* translator: %s represents a digit string */ - appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), - val); + querytext = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY); + if (conn->verbosity != PQERRORS_TERSE && querytext != NULL) + { + /* emit position as a syntax cursor display */ + querypos = atoi(val); + } + else + { + /* emit position as text addition to primary message */ + /* translator: %s represents a digit string */ + appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), + val); + } } } appendPQExpBufferChar(&workBuf, '\n'); if (conn->verbosity != PQERRORS_TERSE) { + if (querytext && querypos > 0) + reportErrorPosition(&workBuf, querytext, querypos, + conn->client_encoding); val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL); if (val) appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), val); @@ -746,6 +773,212 @@ fail: return EOF; } +/* + * Add an error-location display to the error message under construction. + * + * The cursor location is measured in logical characters; the query string + * is presumed to be in the specified encoding. + */ +static void +reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding) +{ +#define DISPLAY_SIZE 60 /* screen width limit, in screen cols */ +#define MIN_RIGHT_CUT 10 /* try to keep this far away from EOL */ + + char *wquery; + int clen, + slen, + i, + w, + *qidx, + *scridx, + qoffset, + scroffset, + ibeg, + iend, + loc_line; + bool beg_trunc, + end_trunc; + + /* Need a writable copy of the query */ + wquery = strdup(query); + if (wquery == NULL) + return; /* fail silently if out of memory */ + + /* + * 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 *) malloc(slen * sizeof(int)); + if (qidx == NULL) + { + free(wquery); + return; + } + scridx = (int *) malloc(slen * sizeof(int)); + if (scridx == NULL) + { + free(qidx); + free(wquery); + return; + } + + qoffset = 0; + scroffset = 0; + for (i = 0; query[qoffset] != '\0'; i++) + { + qidx[i] = qoffset; + scridx[i] = scroffset; + w = pg_encoding_dsplen(encoding, &query[qoffset]); + /* treat control chars as width 1; see tab hack below */ + if (w <= 0) + w = 1; + scroffset += w; + qoffset += pg_encoding_mblen(encoding, &query[qoffset]); + } + qidx[i] = qoffset; + scridx[i] = scroffset; + clen = i; + + /* 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; + } + } + } + + /* truncate working copy at desired endpoint */ + wquery[qidx[iend]] = '\0'; + + /* Begin building the finished message. */ + i = msg->len; + appendPQExpBuffer(msg, libpq_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 < msg->len; i += pg_encoding_mblen(encoding, &msg->data[i])) + { + w = pg_encoding_dsplen(encoding, &msg->data[i]); + if (w <= 0) + w = 1; + scroffset += w; + } + + /* Finish up the LINE message line. */ + appendPQExpBufferStr(msg, &wquery[qidx[ibeg]]); + if (end_trunc) + appendPQExpBufferStr(msg, "..."); + appendPQExpBufferChar(msg, '\n'); + + /* Now emit the cursor marker line. */ + scroffset += scridx[loc] - scridx[ibeg]; + for (i = 0; i < scroffset; i++) + appendPQExpBufferChar(msg, ' '); + appendPQExpBufferChar(msg, '^'); + appendPQExpBufferChar(msg, '\n'); + } + + /* Clean up. */ + free(scridx); + free(qidx); + free(wquery); +} + /* * Attempt to read a ParameterStatus message. diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 4ae59c9e9a..39533452ec 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.111 2006/03/05 15:59:10 momjian Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.112 2006/03/14 22:48:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -278,6 +278,7 @@ struct pg_conn PGAsyncStatusType asyncStatus; PGTransactionStatusType xactStatus; /* never changes to ACTIVE */ PGQueryClass queryclass; + char *last_query; /* last SQL command, or NULL if unknown */ bool options_valid; /* true if OK to attempt connection */ bool nonblocking; /* whether this connection is using nonblock * sending semantics */ |
