Make dblink try harder to form useful error messages
authorJoe Conway <mail@joeconway.com>
Thu, 22 Dec 2016 17:47:46 +0000 (09:47 -0800)
committerJoe Conway <mail@joeconway.com>
Thu, 22 Dec 2016 17:47:46 +0000 (09:47 -0800)
When libpq encounters a connection-level error, e.g. runs out of memory
while forming a result, there will be no error associated with PGresult,
but a message will be placed into PGconn's error buffer. postgres_fdw
takes care to use the PGconn error message when PGresult does not have
one, but dblink has been negligent in that regard. Modify dblink to mirror
what postgres_fdw has been doing.

Back-patch to all supported branches.

Author: Joe Conway
Reviewed-By: Tom Lane
Discussion: https://postgr.es/m/02fa2d90-2efd-00bc-fefc-c23c00eb671e%40joeconway.com

contrib/dblink/dblink.c

index 8912a5c4a6cafd0ec436e6b108186b8afe16c9d4..cdc39c10fc78acfb2b2fb4592ed73440f8f0349c 100644 (file)
@@ -113,7 +113,8 @@ static Relation get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclM
 static char *generate_relation_name(Relation rel);
 static void dblink_connstr_check(const char *connstr);
 static void dblink_security_check(PGconn *conn, remoteConn *rconn);
-static void dblink_res_error(const char *conname, PGresult *res, const char *dblink_context_msg, bool fail);
+static void dblink_res_error(PGconn *conn, const char *conname, PGresult *res,
+                            const char *dblink_context_msg, bool fail);
 static char *get_connect_string(const char *servername);
 static char *escape_param_str(const char *from);
 static void validate_pkattnums(Relation rel,
@@ -428,7 +429,7 @@ dblink_open(PG_FUNCTION_ARGS)
    res = PQexec(conn, buf.data);
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
    {
-       dblink_res_error(conname, res, "could not open cursor", fail);
+       dblink_res_error(conn, conname, res, "could not open cursor", fail);
        PG_RETURN_TEXT_P(cstring_to_text("ERROR"));
    }
 
@@ -497,7 +498,7 @@ dblink_close(PG_FUNCTION_ARGS)
    res = PQexec(conn, buf.data);
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
    {
-       dblink_res_error(conname, res, "could not close cursor", fail);
+       dblink_res_error(conn, conname, res, "could not close cursor", fail);
        PG_RETURN_TEXT_P(cstring_to_text("ERROR"));
    }
 
@@ -600,7 +601,8 @@ dblink_fetch(PG_FUNCTION_ARGS)
        (PQresultStatus(res) != PGRES_COMMAND_OK &&
         PQresultStatus(res) != PGRES_TUPLES_OK))
    {
-       dblink_res_error(conname, res, "could not fetch from cursor", fail);
+       dblink_res_error(conn, conname, res,
+                        "could not fetch from cursor", fail);
        return (Datum) 0;
    }
    else if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -751,8 +753,8 @@ dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
                if (PQresultStatus(res) != PGRES_COMMAND_OK &&
                    PQresultStatus(res) != PGRES_TUPLES_OK)
                {
-                   dblink_res_error(conname, res, "could not execute query",
-                                    fail);
+                   dblink_res_error(conn, conname, res,
+                                    "could not execute query", fail);
                    /* if fail isn't set, we'll return an empty query result */
                }
                else
@@ -999,7 +1001,8 @@ materializeQueryResult(FunctionCallInfo fcinfo,
            PGresult   *res1 = res;
 
            res = NULL;
-           dblink_res_error(conname, res1, "could not execute query", fail);
+           dblink_res_error(conn, conname, res1,
+                            "could not execute query", fail);
            /* if fail isn't set, we'll return an empty query result */
        }
        else if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -1434,7 +1437,8 @@ dblink_exec(PG_FUNCTION_ARGS)
            (PQresultStatus(res) != PGRES_COMMAND_OK &&
             PQresultStatus(res) != PGRES_TUPLES_OK))
        {
-           dblink_res_error(conname, res, "could not execute command", fail);
+           dblink_res_error(conn, conname, res,
+                            "could not execute command", fail);
 
            /*
             * and save a copy of the command status string to return as our
@@ -2665,7 +2669,8 @@ dblink_connstr_check(const char *connstr)
 }
 
 static void
-dblink_res_error(const char *conname, PGresult *res, const char *dblink_context_msg, bool fail)
+dblink_res_error(PGconn *conn, const char *conname, PGresult *res,
+                const char *dblink_context_msg, bool fail)
 {
    int         level;
    char       *pg_diag_sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
@@ -2699,6 +2704,14 @@ dblink_res_error(const char *conname, PGresult *res, const char *dblink_context_
    xpstrdup(message_hint, pg_diag_message_hint);
    xpstrdup(message_context, pg_diag_context);
 
+   /*
+    * If we don't get a message from the PGresult, try the PGconn.  This
+    * is needed because for connection-level failures, PQexec may just
+    * return NULL, not a PGresult at all.
+    */
+   if (message_primary == NULL)
+       message_primary = PQerrorMessage(conn);
+
    if (res)
        PQclear(res);