On Wed, Jan 6, 2016 at 5:06 AM, Jim Nasby <jim.na...@bluetreble.com> wrote:
> On 1/5/16 9:16 PM, Tom Lane wrote: > >> Jim Nasby <jim.na...@bluetreble.com> writes: >> >>> FWIW, I suspect very few people know about the verbosity setting (I >>> didn't until a few months ago...) Maybe psql should hint about it the >>> first time an error is reported in a session. >>> >> >> Actually, what'd be really handy IMO is something to regurgitate the >> most recent error in verbose mode, without making a permanent session >> state change. Something like >> >> regression=# insert into bar values(1); >> ERROR: insert or update on table "bar" violates foreign key constraint >> "bar_f1_fkey" >> DETAIL: Key (f1)=(1) is not present in table "foo". >> regression=# \saywhat >> ERROR: 23503: insert or update on table "bar" violates foreign key >> constraint "bar_f1_fkey" >> DETAIL: Key (f1)=(1) is not present in table "foo". >> SCHEMA NAME: public >> TABLE NAME: bar >> CONSTRAINT NAME: bar_f1_fkey >> LOCATION: ri_ReportViolation, ri_triggers.c:3326 >> regression=# >> >> Not sure how hard that would be to do within psql's current structure. >> > > At first glance, it looks like it just means changing AcceptResult() to > use PQresultErrorField in addition to PQresultErrorMessage, and stuffing > the results somewhere. And of course adding \saywhat to the psql parser, > but maybe someone more versed in psql code can verify that. > > If it is that simple, looks like another good beginner hacker task. :) Sorry, I couldn't resist it: I was too excited to learn such option existed. :-) Please find attached a POC patch, using \errverbose for the command name. Unfortunately, I didn't see a good way to contain the change in psql only and had to change libpq, adding new interface PQresultBuildErrorMessage(). Also, what I don't like very much is that we need to keep track of the last result from last SendQuery() in psql's pset. So in my view this is sort of a dirty hack that works nonetheless. Cheers! -- Alex
From 402866a6899791e7f410ed747e3b0018eed717e0 Mon Sep 17 00:00:00 2001 From: Oleksandr Shulgin <oleksandr.shul...@zalando.de> Date: Wed, 6 Jan 2016 14:58:17 +0100 Subject: [PATCH] POC: \errverbose in psql --- src/bin/psql/command.c | 20 ++++++ src/bin/psql/common.c | 6 +- src/bin/psql/settings.h | 1 + src/bin/psql/startup.c | 1 + src/interfaces/libpq/exports.txt | 1 + src/interfaces/libpq/fe-exec.c | 21 ++++++ src/interfaces/libpq/fe-protocol3.c | 131 +++++++++++++++++++----------------- src/interfaces/libpq/libpq-fe.h | 1 + src/interfaces/libpq/libpq-int.h | 2 + 9 files changed, 122 insertions(+), 62 deletions(-) diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 9750a5b..9ae5ae5 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -822,6 +822,24 @@ exec_command(const char *cmd, } } + /* \errverbose -- display verbose error message from last result */ + else if (strcmp(cmd, "errverbose") == 0) + { + char *errmsg; + + if (pset.last_result && PQresultStatus(pset.last_result) == PGRES_FATAL_ERROR) + { + /* increase error verbosity for a moment */ + PQsetErrorVerbosity(pset.db, PQERRORS_VERBOSE); + errmsg = PQresultBuildErrorMessage(pset.db, pset.last_result); + PQsetErrorVerbosity(pset.db, pset.verbosity); + + psql_error("%s", errmsg); + + PQfreemem(errmsg); + } + } + /* \f -- change field separator */ else if (strcmp(cmd, "f") == 0) { @@ -1865,6 +1883,8 @@ do_connect(char *dbname, char *user, char *host, char *port) { PQfinish(o_conn); pset.db = NULL; + PQclear(pset.last_result); + pset.last_result = NULL; } } diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 2cb2e9b..85658e6 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -315,6 +315,8 @@ CheckConnection(void) psql_error("Failed.\n"); PQfinish(pset.db); pset.db = NULL; + PQclear(pset.last_result); + pset.last_result = NULL; ResetCancelConn(); UnsyncVariables(); } @@ -1154,7 +1156,9 @@ SendQuery(const char *query) } } - PQclear(results); + /* XXX: can we have PQclear that would free everything except errfields? */ + PQclear(pset.last_result); + pset.last_result = results; /* Possible microtiming output */ if (pset.timing) diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h index 20a6470..8b88fbb 100644 --- a/src/bin/psql/settings.h +++ b/src/bin/psql/settings.h @@ -80,6 +80,7 @@ enum trivalue typedef struct _psqlSettings { PGconn *db; /* connection to backend */ + PGresult *last_result; /* last result from running a query, if any */ int encoding; /* client_encoding */ FILE *queryFout; /* where to send the query results */ bool queryFoutPipe; /* queryFout is from a popen() */ diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 6916f6f..7586ee81 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -130,6 +130,7 @@ main(int argc, char *argv[]) pset.progname = get_progname(argv[0]); pset.db = NULL; + pset.last_result = NULL; setDecimalLocale(); pset.encoding = PQenv2encoding(); pset.queryFout = stdout; diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index c69a4d5..042bdd1 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -170,3 +170,4 @@ PQsslStruct 167 PQsslAttributeNames 168 PQsslAttribute 169 PQsetErrorContextVisibility 170 +PQresultBuildErrorMessage 171 diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 41937c0..ca2fb50 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -2591,6 +2591,27 @@ PQresStatus(ExecStatusType status) } char * +PQresultBuildErrorMessage(const PGconn *conn, const PGresult *res) +{ + char *errmsg; + PQExpBufferData errbuf; + + if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) + { + initPQExpBuffer(&errbuf); + pqBuildErrorNoticeMessage3(conn, res, &errbuf); + + errmsg = strdup(errbuf.data); + + termPQExpBuffer(&errbuf); + } + else + errmsg = strdup("requires libpq protocol version 3"); + + return errmsg; +} + +char * PQresultErrorMessage(const PGresult *res) { if (!res || !res->errMsg) diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 43898a4..7e674c7 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -865,9 +865,6 @@ pqGetErrorNotice3(PGconn *conn, bool isError) PGresult *res = NULL; 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 @@ -910,20 +907,67 @@ pqGetErrorNotice3(PGconn *conn, bool isError) * Also, save the SQLSTATE in conn->last_sqlstate. */ resetPQExpBuffer(&workBuf); + pqBuildErrorNoticeMessage3(conn, res, &workBuf); + + /* + * Either save error as current async result, or just emit the notice. + */ + if (isError) + { + if (res) + res->errMsg = pqResultStrdup(res, workBuf.data); + pqClearAsyncResult(conn); + conn->result = res; + if (PQExpBufferDataBroken(workBuf)) + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory")); + else + appendPQExpBufferStr(&conn->errorMessage, workBuf.data); + } + else + { + /* if we couldn't allocate the result set, just discard the NOTICE */ + if (res) + { + /* We can cheat a little here and not copy the message. */ + res->errMsg = workBuf.data; + if (res->noticeHooks.noticeRec != NULL) + (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res); + PQclear(res); + } + } + + termPQExpBuffer(&workBuf); + return 0; + +fail: + PQclear(res); + termPQExpBuffer(&workBuf); + return EOF; +} + +void +pqBuildErrorNoticeMessage3(const PGconn *conn, const PGresult *res, PQExpBuffer workBuf) +{ + const char *val; + const char *querytext = NULL; + int querypos = 0; + val = PQresultErrorField(res, PG_DIAG_SEVERITY); if (val) - appendPQExpBuffer(&workBuf, "%s: ", val); + appendPQExpBuffer(workBuf, "%s: ", val); val = PQresultErrorField(res, PG_DIAG_SQLSTATE); if (val) { + /* XXX: multiple calls seems OK, but we violate the constness of conn */ if (strlen(val) < sizeof(conn->last_sqlstate)) - strcpy(conn->last_sqlstate, val); + strcpy(((PGconn *) conn)->last_sqlstate, val); if (conn->verbosity == PQERRORS_VERBOSE) - appendPQExpBuffer(&workBuf, "%s: ", val); + appendPQExpBuffer(workBuf, "%s: ", val); } val = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY); if (val) - appendPQExpBufferStr(&workBuf, val); + appendPQExpBufferStr(workBuf, val); val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION); if (val) { @@ -937,7 +981,7 @@ pqGetErrorNotice3(PGconn *conn, bool isError) { /* emit position as text addition to primary message */ /* translator: %s represents a digit string */ - appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), + appendPQExpBuffer(workBuf, libpq_gettext(" at character %s"), val); } } @@ -956,32 +1000,33 @@ pqGetErrorNotice3(PGconn *conn, bool isError) { /* emit position as text addition to primary message */ /* translator: %s represents a digit string */ - appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), + appendPQExpBuffer(workBuf, libpq_gettext(" at character %s"), val); } } } - appendPQExpBufferChar(&workBuf, '\n'); + appendPQExpBufferChar(workBuf, '\n'); if (conn->verbosity != PQERRORS_TERSE) { if (querytext && querypos > 0) - reportErrorPosition(&workBuf, querytext, querypos, + reportErrorPosition(workBuf, querytext, querypos, conn->client_encoding); val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), val); + appendPQExpBuffer(workBuf, libpq_gettext("DETAIL: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"), val); + appendPQExpBuffer(workBuf, libpq_gettext("HINT: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("QUERY: %s\n"), val); + appendPQExpBuffer(workBuf, libpq_gettext("QUERY: %s\n"), val); if (conn->show_context == PQSHOW_CONTEXT_ALWAYS || - (conn->show_context == PQSHOW_CONTEXT_ERRORS && isError)) + (conn->show_context == PQSHOW_CONTEXT_ERRORS && + res->resultStatus == PGRES_FATAL_ERROR)) { val = PQresultErrorField(res, PG_DIAG_CONTEXT); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"), + appendPQExpBuffer(workBuf, libpq_gettext("CONTEXT: %s\n"), val); } } @@ -989,23 +1034,23 @@ pqGetErrorNotice3(PGconn *conn, bool isError) { val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(workBuf, libpq_gettext("SCHEMA NAME: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_TABLE_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(workBuf, libpq_gettext("TABLE NAME: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(workBuf, libpq_gettext("COLUMN NAME: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_DATATYPE_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(workBuf, libpq_gettext("DATATYPE NAME: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(workBuf, libpq_gettext("CONSTRAINT NAME: %s\n"), val); } if (conn->verbosity == PQERRORS_VERBOSE) @@ -1018,51 +1063,15 @@ pqGetErrorNotice3(PGconn *conn, bool isError) val = PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION); if (val || valf || vall) { - appendPQExpBufferStr(&workBuf, libpq_gettext("LOCATION: ")); + appendPQExpBufferStr(workBuf, libpq_gettext("LOCATION: ")); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("%s, "), val); + appendPQExpBuffer(workBuf, libpq_gettext("%s, "), val); if (valf && vall) /* unlikely we'd have just one */ - appendPQExpBuffer(&workBuf, libpq_gettext("%s:%s"), + appendPQExpBuffer(workBuf, libpq_gettext("%s:%s"), valf, vall); - appendPQExpBufferChar(&workBuf, '\n'); + appendPQExpBufferChar(workBuf, '\n'); } } - - /* - * Either save error as current async result, or just emit the notice. - */ - if (isError) - { - if (res) - res->errMsg = pqResultStrdup(res, workBuf.data); - pqClearAsyncResult(conn); - conn->result = res; - if (PQExpBufferDataBroken(workBuf)) - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory")); - else - appendPQExpBufferStr(&conn->errorMessage, workBuf.data); - } - else - { - /* if we couldn't allocate the result set, just discard the NOTICE */ - if (res) - { - /* We can cheat a little here and not copy the message. */ - res->errMsg = workBuf.data; - if (res->noticeHooks.noticeRec != NULL) - (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res); - PQclear(res); - } - } - - termPQExpBuffer(&workBuf); - return 0; - -fail: - PQclear(res); - termPQExpBuffer(&workBuf); - return EOF; } /* diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 6bf34b3..5d89767 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -462,6 +462,7 @@ extern PGresult *PQfn(PGconn *conn, /* Accessor functions for PGresult objects */ extern ExecStatusType PQresultStatus(const PGresult *res); extern char *PQresStatus(ExecStatusType status); +extern char *PQresultBuildErrorMessage(const PGconn *conn, const PGresult *res); extern char *PQresultErrorMessage(const PGresult *res); extern char *PQresultErrorField(const PGresult *res, int fieldcode); extern int PQntuples(const PGresult *res); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 6c9bbf7..c7a9e96 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -575,6 +575,8 @@ extern char *pqBuildStartupPacket3(PGconn *conn, int *packetlen, const PQEnvironmentOption *options); extern void pqParseInput3(PGconn *conn); extern int pqGetErrorNotice3(PGconn *conn, bool isError); +extern void pqBuildErrorNoticeMessage3(const PGconn *conn, const PGresult *res, + PQExpBuffer workBuf); extern int pqGetCopyData3(PGconn *conn, char **buffer, int async); extern int pqGetline3(PGconn *conn, char *s, int maxlen); extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize); -- 2.5.0
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers