Commit 3cc38ca7 authored by Tom Lane's avatar Tom Lane

Add psql \errverbose command to see last server error at full verbosity.

Often, upon getting an unexpected error in psql, one's first wish is that
the verbosity setting had been higher; for example, to be able to see the
schema-name field or the server code location info.  Up to now the only way
has been to adjust the VERBOSITY variable and repeat the failing query.
That's a pain, and it doesn't work if the error isn't reproducible.

This commit adds a psql feature that redisplays the most recent server
error at full verbosity, without needing to make any variable changes or
re-execute the failed command.  We just need to hang onto the latest error
PGresult in case the user executes \errverbose, and then apply libpq's
new PQresultVerboseErrorMessage() function to it.  This will consume
some trivial amount of psql memory, but otherwise the cost when the
feature isn't used should be negligible.

Alex Shulgin, reviewed by Daniel Vérité, some improvements by me
parent e3161b23
...@@ -1717,6 +1717,20 @@ Tue Oct 26 21:40:57 CEST 1999 ...@@ -1717,6 +1717,20 @@ Tue Oct 26 21:40:57 CEST 1999
</varlistentry> </varlistentry>
<varlistentry>
<term><literal>\errverbose</literal></term>
<listitem>
<para>
Repeats the most recent server error message at maximum
verbosity, as though <varname>VERBOSITY</varname> were set
to <literal>verbose</> and <varname>SHOW_CONTEXT</varname> were
set to <literal>always</>.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><literal>\f [ <replaceable class="parameter">string</replaceable> ]</literal></term> <term><literal>\f [ <replaceable class="parameter">string</replaceable> ]</literal></term>
...@@ -3244,6 +3258,8 @@ bar ...@@ -3244,6 +3258,8 @@ bar
that context will be shown in error messages, but not in notice or that context will be shown in error messages, but not in notice or
warning messages). This setting has no effect warning messages). This setting has no effect
when <varname>VERBOSITY</> is set to <literal>terse</>. when <varname>VERBOSITY</> is set to <literal>terse</>.
(See also <command>\errverbose</>, for use when you want a verbose
version of the error you just got.)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -3286,6 +3302,8 @@ bar ...@@ -3286,6 +3302,8 @@ bar
This variable can be set to the values <literal>default</>, This variable can be set to the values <literal>default</>,
<literal>verbose</>, or <literal>terse</> to control the verbosity <literal>verbose</>, or <literal>terse</> to control the verbosity
of error reports. of error reports.
(See also <command>\errverbose</>, for use when you want a verbose
version of the error you just got.)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -822,6 +822,28 @@ exec_command(const char *cmd, ...@@ -822,6 +822,28 @@ exec_command(const char *cmd,
} }
} }
/* \errverbose -- display verbose message from last failed query */
else if (strcmp(cmd, "errverbose") == 0)
{
if (pset.last_error_result)
{
char *msg;
msg = PQresultVerboseErrorMessage(pset.last_error_result,
PQERRORS_VERBOSE,
PQSHOW_CONTEXT_ALWAYS);
if (msg)
{
psql_error("%s", msg);
PQfreemem(msg);
}
else
puts(_("out of memory"));
}
else
puts(_("There was no previous error."));
}
/* \f -- change field separator */ /* \f -- change field separator */
else if (strcmp(cmd, "f") == 0) else if (strcmp(cmd, "f") == 0)
{ {
......
...@@ -497,6 +497,33 @@ AcceptResult(const PGresult *result) ...@@ -497,6 +497,33 @@ AcceptResult(const PGresult *result)
} }
/*
* ClearOrSaveResult
*
* If the result represents an error, remember it for possible display by
* \errverbose. Otherwise, just PQclear() it.
*/
static void
ClearOrSaveResult(PGresult *result)
{
if (result)
{
switch (PQresultStatus(result))
{
case PGRES_NONFATAL_ERROR:
case PGRES_FATAL_ERROR:
if (pset.last_error_result)
PQclear(pset.last_error_result);
pset.last_error_result = result;
break;
default:
PQclear(result);
break;
}
}
}
/* /*
* PSQLexec * PSQLexec
...@@ -548,7 +575,7 @@ PSQLexec(const char *query) ...@@ -548,7 +575,7 @@ PSQLexec(const char *query)
if (!AcceptResult(res)) if (!AcceptResult(res))
{ {
PQclear(res); ClearOrSaveResult(res);
res = NULL; res = NULL;
} }
...@@ -590,7 +617,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt) ...@@ -590,7 +617,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
if (!AcceptResult(res)) if (!AcceptResult(res))
{ {
PQclear(res); ClearOrSaveResult(res);
return 0; return 0;
} }
...@@ -1077,11 +1104,11 @@ SendQuery(const char *query) ...@@ -1077,11 +1104,11 @@ SendQuery(const char *query)
if (PQresultStatus(results) != PGRES_COMMAND_OK) if (PQresultStatus(results) != PGRES_COMMAND_OK)
{ {
psql_error("%s", PQerrorMessage(pset.db)); psql_error("%s", PQerrorMessage(pset.db));
PQclear(results); ClearOrSaveResult(results);
ResetCancelConn(); ResetCancelConn();
goto sendquery_cleanup; goto sendquery_cleanup;
} }
PQclear(results); ClearOrSaveResult(results);
transaction_status = PQtransactionStatus(pset.db); transaction_status = PQtransactionStatus(pset.db);
} }
...@@ -1102,11 +1129,11 @@ SendQuery(const char *query) ...@@ -1102,11 +1129,11 @@ SendQuery(const char *query)
if (PQresultStatus(results) != PGRES_COMMAND_OK) if (PQresultStatus(results) != PGRES_COMMAND_OK)
{ {
psql_error("%s", PQerrorMessage(pset.db)); psql_error("%s", PQerrorMessage(pset.db));
PQclear(results); ClearOrSaveResult(results);
ResetCancelConn(); ResetCancelConn();
goto sendquery_cleanup; goto sendquery_cleanup;
} }
PQclear(results); ClearOrSaveResult(results);
on_error_rollback_savepoint = true; on_error_rollback_savepoint = true;
} }
} }
...@@ -1202,7 +1229,7 @@ SendQuery(const char *query) ...@@ -1202,7 +1229,7 @@ SendQuery(const char *query)
if (PQresultStatus(svptres) != PGRES_COMMAND_OK) if (PQresultStatus(svptres) != PGRES_COMMAND_OK)
{ {
psql_error("%s", PQerrorMessage(pset.db)); psql_error("%s", PQerrorMessage(pset.db));
PQclear(svptres); ClearOrSaveResult(svptres);
OK = false; OK = false;
PQclear(results); PQclear(results);
...@@ -1213,7 +1240,7 @@ SendQuery(const char *query) ...@@ -1213,7 +1240,7 @@ SendQuery(const char *query)
} }
} }
PQclear(results); ClearOrSaveResult(results);
/* Possible microtiming output */ /* Possible microtiming output */
if (pset.timing) if (pset.timing)
...@@ -1299,7 +1326,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) ...@@ -1299,7 +1326,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
results = PQexec(pset.db, "BEGIN"); results = PQexec(pset.db, "BEGIN");
OK = AcceptResult(results) && OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK); (PQresultStatus(results) == PGRES_COMMAND_OK);
PQclear(results); ClearOrSaveResult(results);
if (!OK) if (!OK)
return false; return false;
started_txn = true; started_txn = true;
...@@ -1313,7 +1340,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) ...@@ -1313,7 +1340,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
results = PQexec(pset.db, buf.data); results = PQexec(pset.db, buf.data);
OK = AcceptResult(results) && OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK); (PQresultStatus(results) == PGRES_COMMAND_OK);
PQclear(results); ClearOrSaveResult(results);
termPQExpBuffer(&buf); termPQExpBuffer(&buf);
if (!OK) if (!OK)
goto cleanup; goto cleanup;
...@@ -1384,7 +1411,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) ...@@ -1384,7 +1411,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
OK = AcceptResult(results); OK = AcceptResult(results);
Assert(!OK); Assert(!OK);
PQclear(results); ClearOrSaveResult(results);
break; break;
} }
...@@ -1392,7 +1419,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) ...@@ -1392,7 +1419,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
{ {
/* StoreQueryTuple will complain if not exactly one row */ /* StoreQueryTuple will complain if not exactly one row */
OK = StoreQueryTuple(results); OK = StoreQueryTuple(results);
PQclear(results); ClearOrSaveResult(results);
break; break;
} }
...@@ -1415,7 +1442,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) ...@@ -1415,7 +1442,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
printQuery(results, &my_popt, fout, is_pager, pset.logfile); printQuery(results, &my_popt, fout, is_pager, pset.logfile);
PQclear(results); ClearOrSaveResult(results);
/* after the first result set, disallow header decoration */ /* after the first result set, disallow header decoration */
my_popt.topt.start_table = false; my_popt.topt.start_table = false;
...@@ -1473,14 +1500,14 @@ cleanup: ...@@ -1473,14 +1500,14 @@ cleanup:
OK = AcceptResult(results) && OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK); (PQresultStatus(results) == PGRES_COMMAND_OK);
} }
PQclear(results); ClearOrSaveResult(results);
if (started_txn) if (started_txn)
{ {
results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK"); results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
OK &= AcceptResult(results) && OK &= AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK); (PQresultStatus(results) == PGRES_COMMAND_OK);
PQclear(results); ClearOrSaveResult(results);
} }
if (pset.timing) if (pset.timing)
......
...@@ -168,10 +168,11 @@ slashUsage(unsigned short int pager) ...@@ -168,10 +168,11 @@ slashUsage(unsigned short int pager)
* Use "psql --help=commands | wc" to count correctly. It's okay to count * Use "psql --help=commands | wc" to count correctly. It's okay to count
* the USE_READLINE line even in builds without that. * the USE_READLINE line even in builds without that.
*/ */
output = PageOutput(109, pager ? &(pset.popt.topt) : NULL); output = PageOutput(110, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n")); fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n")); fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
fprintf(output, _(" \\errverbose show most recent error message at maximum verbosity\n"));
fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\n")); fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\n"));
fprintf(output, _(" \\gset [PREFIX] execute query and store results in psql variables\n")); fprintf(output, _(" \\gset [PREFIX] execute query and store results in psql variables\n"));
fprintf(output, _(" \\q quit psql\n")); fprintf(output, _(" \\q quit psql\n"));
......
...@@ -86,6 +86,8 @@ typedef struct _psqlSettings ...@@ -86,6 +86,8 @@ typedef struct _psqlSettings
FILE *copyStream; /* Stream to read/write for \copy command */ FILE *copyStream; /* Stream to read/write for \copy command */
PGresult *last_error_result; /* most recent error result, if any */
printQueryOpt popt; printQueryOpt popt;
char *gfname; /* one-shot file output argument for \g */ char *gfname; /* one-shot file output argument for \g */
......
...@@ -135,6 +135,7 @@ main(int argc, char *argv[]) ...@@ -135,6 +135,7 @@ main(int argc, char *argv[])
pset.queryFout = stdout; pset.queryFout = stdout;
pset.queryFoutPipe = false; pset.queryFoutPipe = false;
pset.copyStream = NULL; pset.copyStream = NULL;
pset.last_error_result = NULL;
pset.cur_cmd_source = stdin; pset.cur_cmd_source = stdin;
pset.cur_cmd_interactive = false; pset.cur_cmd_interactive = false;
......
...@@ -1280,7 +1280,7 @@ psql_completion(const char *text, int start, int end) ...@@ -1280,7 +1280,7 @@ psql_completion(const char *text, int start, int end)
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL", "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\drds", "\\ds", "\\dS", "\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\drds", "\\ds", "\\dS",
"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy", "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
"\\e", "\\echo", "\\ef", "\\encoding", "\\ev", "\\e", "\\echo", "\\ef", "\\encoding", "\\errverbose", "\\ev",
"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l", "\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r", "\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment