Add a "SQLSTATE-only" error verbosity option to libpq and psql.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 4 Apr 2019 21:22:02 +0000 (17:22 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 4 Apr 2019 21:22:02 +0000 (17:22 -0400)
This is intended for use mostly in test scripts for external tools,
which could do without cross-PG-version variations in error message
wording.  Of course, the SQLSTATE isn't guaranteed stable either, but
it should be more so than the error message text.

Note: there's a bit of an ABI change for libpq here, but it seems
OK because if somebody compiles against a newer version of libpq-fe.h,
and then tries to pass PQERRORS_SQLSTATE to PQsetErrorVerbosity()
of an older libpq library, it will be accepted and then act like
PQERRORS_DEFAULT, thanks to the way the tests in pqBuildErrorMessage3
have historically been phrased.  That seems acceptable.

Didier Gautheron, reviewed by Dagfinn Ilmari MannsÃ¥ker

Discussion: https://postgr.es/m/CAJRYxuKyj4zA+JGVrtx8OWAuBfE-_wN4sUMK4H49EuPed=mOBw@mail.gmail.com

doc/src/sgml/libpq.sgml
doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/help.c
src/bin/psql/startup.c
src/bin/psql/tab-complete.c
src/interfaces/libpq/fe-protocol3.c
src/interfaces/libpq/libpq-fe.h
src/test/regress/expected/psql.out
src/test/regress/sql/psql.sql

index 0863a02411da6f5efef279108d02a0a177548359..a97af98979b9da8f9c0f362e962f6fe49567e857 100644 (file)
@@ -6014,21 +6014,30 @@ typedef enum
 {
     PQERRORS_TERSE,
     PQERRORS_DEFAULT,
-    PQERRORS_VERBOSE
+    PQERRORS_VERBOSE,
+    PQERRORS_SQLSTATE
 } PGVerbosity;
 
 PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity);
 </synopsis>
 
-      <function>PQsetErrorVerbosity</function> sets the verbosity mode, returning
-      the connection's previous setting.  In <firstterm>TERSE</firstterm> mode,
-      returned messages include severity, primary text, and position only;
-      this will normally fit on a single line.  The default mode produces
-      messages that include the above plus any detail, hint, or context
-      fields (these might span multiple lines).  The <firstterm>VERBOSE</firstterm>
-      mode includes all available fields.  Changing the verbosity does not
-      affect the messages available from already-existing
-      <structname>PGresult</structname> objects, only subsequently-created ones.
+      <function>PQsetErrorVerbosity</function> sets the verbosity mode,
+      returning the connection's previous setting.
+      In <firstterm>TERSE</firstterm> mode, returned messages include
+      severity, primary text, and position only; this will normally fit on a
+      single line.  The default mode produces messages that include the above
+      plus any detail, hint, or context fields (these might span multiple
+      lines).  The <firstterm>VERBOSE</firstterm> mode includes all available
+      fields.  The <firstterm>SQLSTATE</firstterm> mode includes only the
+      error severity and the <symbol>SQLSTATE</symbol> error code, if one is
+      available (if not, the output is like <firstterm>TERSE</firstterm>
+      mode).
+     </para>
+
+     <para>
+      Changing the verbosity setting does not affect the messages available
+      from already-existing <structname>PGresult</structname> objects, only
+      subsequently-created ones.
       (But see <function>PQresultVerboseErrorMessage</function> if you
       want to print a previous error with a different verbosity.)
      </para>
@@ -6061,13 +6070,19 @@ PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibilit
 
       <function>PQsetErrorContextVisibility</function> sets the context display mode,
       returning the connection's previous setting.  This mode controls
-      whether the <literal>CONTEXT</literal> field is included in messages
-      (unless the verbosity setting is <firstterm>TERSE</firstterm>, in which
-      case <literal>CONTEXT</literal> is never shown).  The <firstterm>NEVER</firstterm> mode
+      whether the <literal>CONTEXT</literal> field is included in messages.
+      The <firstterm>NEVER</firstterm> mode
       never includes <literal>CONTEXT</literal>, while <firstterm>ALWAYS</firstterm> always
       includes it if available.  In <firstterm>ERRORS</firstterm> mode (the
-      default), <literal>CONTEXT</literal> fields are included only for error
-      messages, not for notices and warnings.  Changing this mode does not
+      default), <literal>CONTEXT</literal> fields are included only in error
+      messages, not in notices and warnings.
+      (However, if the verbosity setting is <firstterm>TERSE</firstterm>
+      or <firstterm>SQLSTATE</firstterm>, <literal>CONTEXT</literal> fields
+      are omitted regardless of the context display mode.)
+     </para>
+
+     <para>
+      Changing this mode does not
       affect the messages available from
       already-existing <structname>PGresult</structname> objects, only
       subsequently-created ones.
index b29e7547c6a3c26c9e47ec42a09e4ad8edd86659..2bc8bbc2a7403b003c9ba5acd94a497547ac754b 100644 (file)
@@ -3892,7 +3892,8 @@ bar
         messages from the server. The default is <literal>errors</literal> (meaning
         that context will be shown in error messages, but not in notice or
         warning messages).  This setting has no effect
-        when <varname>VERBOSITY</varname> is set to <literal>terse</literal>.
+        when <varname>VERBOSITY</varname> is set to <literal>terse</literal>
+        or <literal>sqlstate</literal>.
         (See also <command>\errverbose</command>, for use when you want a verbose
         version of the error you just got.)
         </para>
@@ -3946,8 +3947,9 @@ bar
         <listitem>
         <para>
         This variable can be set to the values <literal>default</literal>,
-        <literal>verbose</literal>, or <literal>terse</literal> to control the verbosity
-        of error reports.
+        <literal>verbose</literal>, <literal>terse</literal>,
+        or <literal>sqlstate</literal> to control the verbosity of error
+        reports.
         (See also <command>\errverbose</command>, for use when you want a verbose
         version of the error you just got.)
         </para>
index eb3a0f36d94e7c121e0477fcf85a44d8c87db531..63b115fe01845be048c8b02746b8ea0f0da74e6b 100644 (file)
@@ -340,7 +340,7 @@ helpVariables(unsigned short int pager)
     * Windows builds currently print one more line than non-Windows builds.
     * Using the larger number is fine.
     */
-   output = PageOutput(156, pager ? &(pset.popt.topt) : NULL);
+   output = PageOutput(158, pager ? &(pset.popt.topt) : NULL);
 
    fprintf(output, _("List of specially treated variables\n\n"));
 
@@ -414,7 +414,7 @@ helpVariables(unsigned short int pager)
    fprintf(output, _("  USER\n"
                      "    the currently connected database user\n"));
    fprintf(output, _("  VERBOSITY\n"
-                     "    controls verbosity of error reports [default, verbose, terse]\n"));
+                     "    controls verbosity of error reports [default, verbose, terse, sqlstate]\n"));
    fprintf(output, _("  VERSION\n"
                      "  VERSION_NAME\n"
                      "  VERSION_NUM\n"
index ac82087445eb3e6d5695d4bb787fd61f78f4ee92..855133bbcb4703b88ddf4c960f65c4f53c49ff0e 100644 (file)
@@ -1110,13 +1110,15 @@ verbosity_hook(const char *newval)
    Assert(newval != NULL);     /* else substitute hook messed up */
    if (pg_strcasecmp(newval, "default") == 0)
        pset.verbosity = PQERRORS_DEFAULT;
-   else if (pg_strcasecmp(newval, "terse") == 0)
-       pset.verbosity = PQERRORS_TERSE;
    else if (pg_strcasecmp(newval, "verbose") == 0)
        pset.verbosity = PQERRORS_VERBOSE;
+   else if (pg_strcasecmp(newval, "terse") == 0)
+       pset.verbosity = PQERRORS_TERSE;
+   else if (pg_strcasecmp(newval, "sqlstate") == 0)
+       pset.verbosity = PQERRORS_SQLSTATE;
    else
    {
-       PsqlVarEnumError("VERBOSITY", newval, "default, terse, verbose");
+       PsqlVarEnumError("VERBOSITY", newval, "default, verbose, terse, sqlstate");
        return false;
    }
 
index 22576adc51e0713bb4d39cea09c2c79a52c3b29e..7c4e5fbacbe6cb4743f3e804a4eace898b2f059e 100644 (file)
@@ -3652,7 +3652,7 @@ psql_completion(const char *text, int start, int end)
        else if (TailMatchesCS("SHOW_CONTEXT"))
            COMPLETE_WITH_CS("never", "errors", "always");
        else if (TailMatchesCS("VERBOSITY"))
-           COMPLETE_WITH_CS("default", "verbose", "terse");
+           COMPLETE_WITH_CS("default", "verbose", "terse", "sqlstate");
    }
    else if (TailMatchesCS("\\sf*"))
        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
index ec51fee383dc5887eef37f438b39b9fc04654242..d582cfd4e9c707b3fb2fbf63966d711068c67772 100644 (file)
@@ -1017,6 +1017,24 @@ pqBuildErrorMessage3(PQExpBuffer msg, const PGresult *res,
    val = PQresultErrorField(res, PG_DIAG_SEVERITY);
    if (val)
        appendPQExpBuffer(msg, "%s:  ", val);
+
+   if (verbosity == PQERRORS_SQLSTATE)
+   {
+       /*
+        * If we have a SQLSTATE, print that and nothing else.  If not (which
+        * shouldn't happen for server-generated errors, but might possibly
+        * happen for libpq-generated ones), fall back to TERSE format, as
+        * that seems better than printing nothing at all.
+        */
+       val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
+       if (val)
+       {
+           appendPQExpBuffer(msg, "%s\n", val);
+           return;
+       }
+       verbosity = PQERRORS_TERSE;
+   }
+
    if (verbosity == PQERRORS_VERBOSE)
    {
        val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
index 27047ddd1f3b5089bd6259d5461b6143c7cadc06..f44030b4b0d0dcf09e611f2aef42947ca53a4e84 100644 (file)
@@ -112,7 +112,8 @@ typedef enum
 {
    PQERRORS_TERSE,             /* single-line error messages */
    PQERRORS_DEFAULT,           /* recommended style */
-   PQERRORS_VERBOSE            /* all the facts, ma'am */
+   PQERRORS_VERBOSE,           /* all the facts, ma'am */
+   PQERRORS_SQLSTATE           /* only error severity and SQLSTATE code */
 } PGVerbosity;
 
 typedef enum
index aa101de9063efa68ad975df2888360cfa90a5e25..c8b0ae3ede46e68918f3e557bd2da026fa5d05f2 100644 (file)
@@ -4491,6 +4491,26 @@ number of rows: 0
 last error message: table "this_table_does_not_exist" does not exist
 \echo 'last error code:' :LAST_ERROR_SQLSTATE
 last error code: 42P01
+-- nondefault verbosity error settings (except verbose, which is too unstable)
+\set VERBOSITY terse
+SELECT 1 UNION;
+ERROR:  syntax error at or near ";" at character 15
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 42601
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: syntax error at or near ";"
+\set VERBOSITY sqlstate
+SELECT 1/0;
+ERROR:  22012
+\echo 'error:' :ERROR
+error: true
+\echo 'error code:' :SQLSTATE
+error code: 22012
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+last error message: division by zero
+\set VERBOSITY default
 -- working \gdesc
 SELECT 3 AS three, 4 AS four \gdesc
  Column |  Type   
index fb7d17fc76edb00b3d08be8ee8b32a598e36d9e8..a8b2cdc7416cc8115d8df6c77b97e5086f644856 100644 (file)
@@ -1001,6 +1001,21 @@ DROP TABLE this_table_does_not_exist;
 \echo 'last error message:' :LAST_ERROR_MESSAGE
 \echo 'last error code:' :LAST_ERROR_SQLSTATE
 
+-- nondefault verbosity error settings (except verbose, which is too unstable)
+\set VERBOSITY terse
+SELECT 1 UNION;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+
+\set VERBOSITY sqlstate
+SELECT 1/0;
+\echo 'error:' :ERROR
+\echo 'error code:' :SQLSTATE
+\echo 'last error message:' :LAST_ERROR_MESSAGE
+
+\set VERBOSITY default
+
 -- working \gdesc
 SELECT 3 AS three, 4 AS four \gdesc
 \echo 'error:' :ERROR