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

Reply via email to