On Wed, May 27, 2026 at 02:39:53PM -0700, Jacob Champion wrote:
> On Tue, May 26, 2026 at 12:55 PM Nathan Bossart
> <[email protected]> wrote:
>> I wonder how difficult it would be to teach the protocol to advise clients
>> when prepared statements are deallocated...
> 
> Probably not too difficult. But it seems like most, if not all, of the
> stuff in the DISCARD ALL umbrella is a target for a feature like
> that... This feels a lot like the perennial request for proxies to be
> able to separate their own context from the per-application/per-user
> contexts running on top of them.

Here is a work-in-progress patch set that goes this direction.  This
introduces a callback mechanism in libpq that is used to handle statement
deallocation notifications.  Older servers/clients fall back to
PQexecParams(), which is slower, but the alternative is to leave PQnfn()
and related code around indefinitely.

I'm wondering whether this new message type is general enough.  For
example, perhaps we could make an extensible message type for tracking
various things.  And I want to ensure this is useful for other clients,
too.

-- 
nathan
>From ed962c55faf3b6ef28af6672591f460ad2289273 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Thu, 28 May 2026 15:42:20 -0500
Subject: [PATCH v2 1/3] tell client when prep stmts are deallocated

---
 src/backend/commands/prepare.c                | 30 +++++++++++++
 src/include/libpq/pqcomm.h                    |  2 +-
 src/include/libpq/protocol.h                  |  1 +
 src/interfaces/libpq/exports.txt              |  1 +
 src/interfaces/libpq/fe-connect.c             | 30 +++++++++++++
 src/interfaces/libpq/fe-protocol3.c           | 43 +++++++++++++++++++
 src/interfaces/libpq/fe-trace.c               | 10 +++++
 src/interfaces/libpq/libpq-fe.h               |  6 +++
 src/interfaces/libpq/libpq-int.h              |  2 +
 .../modules/libpq_pipeline/libpq_pipeline.c   | 12 +++---
 .../libpq_pipeline/traces/prepared.trace      |  1 +
 11 files changed, 131 insertions(+), 7 deletions(-)

diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 876aad2100a..4ca85b10f9e 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -26,6 +26,9 @@
 #include "commands/explain_state.h"
 #include "commands/prepare.h"
 #include "funcapi.h"
+#include "libpq/libpq-be.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -512,6 +515,27 @@ DeallocateQuery(DeallocateStmt *stmt)
                DropAllPreparedStatements();
 }
 
+/*
+ * Tell the client that a prepared statement has been deallocated.  Pass an
+ * empty string to indicate that all statements were deallocated.
+ *
+ * This is only sent to clients that are using protocol version 3.3 or later.
+ */
+static void
+SendStmtDeallocMsg(const char *name)
+{
+       StringInfoData buf;
+
+       if (whereToSendOutput != DestRemote)
+               return;
+       if (!MyProcPort || MyProcPort->proto < PG_PROTOCOL(3, 3))
+               return;
+
+       pq_beginmessage(&buf, PqMsg_PrepStmtDeallocated);
+       pq_sendstring(&buf, name);
+       pq_endmessage(&buf);
+}
+
 /*
  * Internal version of DEALLOCATE
  *
@@ -530,6 +554,9 @@ DropPreparedStatement(const char *stmt_name, bool showError)
                /* Release the plancache entry */
                DropCachedPlan(entry->plansource);
 
+               /* Alert the client */
+               SendStmtDeallocMsg(entry->stmt_name);
+
                /* Now we can remove the hash table entry */
                hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, 
NULL);
        }
@@ -548,6 +575,9 @@ DropAllPreparedStatements(void)
        if (!prepared_queries)
                return;
 
+       /* Alert the client */
+       SendStmtDeallocMsg("");
+
        /* walk over cache */
        hash_seq_init(&seq, prepared_queries);
        while ((entry = hash_seq_search(&seq)) != NULL)
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index a29c9c94d79..28e7944cdf4 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -92,7 +92,7 @@ is_unixsock_path(const char *path)
  * The earliest and latest frontend/backend protocol version supported.
  */
 #define PG_PROTOCOL_EARLIEST   PG_PROTOCOL(3,0)
-#define PG_PROTOCOL_LATEST             PG_PROTOCOL(3,2)
+#define PG_PROTOCOL_LATEST             PG_PROTOCOL(3,3)
 
 /*
  * Reserved protocol numbers, which have special semantics:
diff --git a/src/include/libpq/protocol.h b/src/include/libpq/protocol.h
index eae8f0e7238..7ea331f7210 100644
--- a/src/include/libpq/protocol.h
+++ b/src/include/libpq/protocol.h
@@ -53,6 +53,7 @@
 #define PqMsg_FunctionCallResponse     'V'
 #define PqMsg_CopyBothResponse         'W'
 #define PqMsg_ReadyForQuery                    'Z'
+#define PqMsg_PrepStmtDeallocated      'i'
 #define PqMsg_NoData                           'n'
 #define PqMsg_PortalSuspended          's'
 #define PqMsg_ParameterDescription     't'
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 1e3d5bd5867..effd73ca3e6 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -211,3 +211,4 @@ PQdefaultAuthDataHook     208
 PQfullProtocolVersion     209
 appendPQExpBufferVA       210
 PQgetThreadLock           211
+PQaddPrepStmtDeallocCallback 212
diff --git a/src/interfaces/libpq/fe-connect.c 
b/src/interfaces/libpq/fe-connect.c
index 4272d386e64..5e41c21c6f6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -8374,6 +8374,11 @@ pqParseProtocolVersion(const char *value, 
ProtocolVersion *result, PGconn *conn,
                *result = PG_PROTOCOL(3, 2);
                return true;
        }
+       if (strcmp(value, "3.3") == 0)
+       {
+               *result = PG_PROTOCOL(3, 3);
+               return true;
+       }
 
        libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
                                                        context, value);
@@ -8426,3 +8431,28 @@ PQgetThreadLock(void)
        Assert(pg_g_threadlock);
        return pg_g_threadlock;
 }
+
+/*
+ * Adds a prepared statement deallocation callback to the connection's list of
+ * callbacks.  These are invoked when the server sends us
+ * PqMsg_PrepStmtDeallocated messages.
+ */
+bool
+PQaddPrepStmtDeallocCallback(PGconn *conn, PQprepStmtDeallocCallback cb)
+{
+       if (!conn)
+               return false;
+
+       /* Add to end to preserve registration order */
+       for (int i = 0; i < lengthof(conn->prepStmtDeallocCallbacks); i++)
+       {
+               if (conn->prepStmtDeallocCallbacks[i])
+                       continue;
+
+               conn->prepStmtDeallocCallbacks[i] = cb;
+               return true;
+       }
+
+       libpq_append_conn_error(conn, "maximum number of prepared statement 
deallocation callbacks already registered");
+       return false;
+}
diff --git a/src/interfaces/libpq/fe-protocol3.c 
b/src/interfaces/libpq/fe-protocol3.c
index 840e018cd18..0407d10362d 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -61,6 +61,32 @@ static size_t build_startup_packet(const PGconn *conn, char 
*packet,
                                                                   const 
PQEnvironmentOption *options);
 
 
+/*
+ * Attempt to read a PrepStmtDeallocated message and invoke the connection's
+ * registered callbacks.  This is possible in several places, so we break it
+ * out as a subroutine.
+ *
+ * Entry: 'i' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message and invoked callbacks, or
+ *       EOF if not enough data.
+ */
+static int
+getPrepStmtDeallocated(PGconn *conn)
+{
+       if (pqGets(&conn->workBuffer, conn))
+               return EOF;
+
+       for (int i = 0; i < lengthof(conn->prepStmtDeallocCallbacks); i++)
+       {
+               if (!conn->prepStmtDeallocCallbacks[i])
+                       break;
+
+               (conn->prepStmtDeallocCallbacks[i]) (conn, 
conn->workBuffer.data);
+       }
+
+       return 0;
+}
+
 /*
  * parseInput: if appropriate, parse input data from backend
  * until input is exhausted or a stopping state is reached.
@@ -184,6 +210,11 @@ pqParseInput3(PGconn *conn)
                                if (getParameterStatus(conn))
                                        return;
                        }
+                       else if (id == PqMsg_PrepStmtDeallocated)
+                       {
+                               if (getPrepStmtDeallocated(conn))
+                                       return;
+                       }
                        else
                        {
                                /* Any other case is unexpected and we 
summarily skip it */
@@ -305,6 +336,10 @@ pqParseInput3(PGconn *conn)
                                        if (getParameterStatus(conn))
                                                return;
                                        break;
+                               case PqMsg_PrepStmtDeallocated:
+                                       if (getPrepStmtDeallocated(conn))
+                                               return;
+                                       break;
                                case PqMsg_BackendKeyData:
 
                                        /*
@@ -1905,6 +1940,10 @@ getCopyDataMessage(PGconn *conn)
                                if (getParameterStatus(conn))
                                        return 0;
                                break;
+                       case PqMsg_PrepStmtDeallocated:
+                               if (getPrepStmtDeallocated(conn))
+                                       return 0;
+                               break;
                        case PqMsg_CopyData:
                                return msgLength;
                        case PqMsg_CopyDone:
@@ -2409,6 +2448,10 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
                                if (getParameterStatus(conn))
                                        continue;
                                break;
+                       case PqMsg_PrepStmtDeallocated:
+                               if (getPrepStmtDeallocated(conn))
+                                       continue;
+                               break;
                        default:
                                /* The backend violates the protocol. */
                                libpq_append_conn_error(conn, "protocol error: 
id=0x%x", id);
diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c
index c348b08c39b..e9f734187a2 100644
--- a/src/interfaces/libpq/fe-trace.c
+++ b/src/interfaces/libpq/fe-trace.c
@@ -543,6 +543,13 @@ pqTraceOutput_ParameterStatus(FILE *f, const char 
*message, int *cursor)
        pqTraceOutputString(f, message, cursor, false);
 }
 
+static void
+pqTraceOutput_PrepStmtDeallocated(FILE *f, const char *message, int *cursor)
+{
+       fprintf(f, "PrepStmtDeallocated\t");
+       pqTraceOutputString(f, message, cursor, false);
+}
+
 static void
 pqTraceOutput_ParameterDescription(FILE *f, const char *message, int *cursor, 
bool regress)
 {
@@ -793,6 +800,9 @@ pqTraceOutputMessage(PGconn *conn, const char *message, 
bool toServer)
                        else
                                pqTraceOutput_ParameterStatus(conn->Pfdebug, 
message, &logCursor);
                        break;
+               case PqMsg_PrepStmtDeallocated:
+                       pqTraceOutput_PrepStmtDeallocated(conn->Pfdebug, 
message, &logCursor);
+                       break;
                case PqMsg_ParameterDescription:
                        pqTraceOutput_ParameterDescription(conn->Pfdebug, 
message, &logCursor, regress);
                        break;
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 8ecb9b4a4c7..c57bb8806cf 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -486,6 +486,12 @@ typedef void (*pgthreadlock_t) (int acquire);
 extern pgthreadlock_t PQregisterThreadLock(pgthreadlock_t newhandler);
 extern pgthreadlock_t PQgetThreadLock(void);
 
+/* callbacks for prepared statement deallocation notifications */
+typedef void (*PQprepStmtDeallocCallback) (PGconn *conn, const char *name);
+
+extern bool PQaddPrepStmtDeallocCallback(PGconn *conn,
+                                                                               
 PQprepStmtDeallocCallback cb);
+
 /* === in fe-trace.c === */
 extern void PQtrace(PGconn *conn, FILE *debug_port);
 extern void PQuntrace(PGconn *conn);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 461b39620c3..7eca941ddcc 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -532,6 +532,8 @@ struct pg_conn
        void            (*cleanup_async_auth) (PGconn *conn);
        pgsocket        altsock;                /* alternative socket for 
client to poll */
 
+       /* Callbacks for prep stmt deallocs (16 ought to be enough for anybody) 
*/
+       PQprepStmtDeallocCallback prepStmtDeallocCallbacks[16];
 
        /* Transient state needed while establishing connection */
        PGTargetServerType target_server_type;  /* desired session properties */
diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c 
b/src/test/modules/libpq_pipeline/libpq_pipeline.c
index ee3e2ec7570..b61f33e7cd9 100644
--- a/src/test/modules/libpq_pipeline/libpq_pipeline.c
+++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c
@@ -1363,7 +1363,7 @@ test_protocol_version(PGconn *conn)
        Assert(max_protocol_version_index >= 0);
 
        /*
-        * Test default protocol_version (GREASE - should negotiate down to 3.2)
+        * Test default protocol_version (GREASE - should negotiate down to 3.3)
         */
        vals[max_protocol_version_index] = "";
        conn = PQconnectdbParams(keywords, vals, false);
@@ -1373,8 +1373,8 @@ test_protocol_version(PGconn *conn)
                                 PQerrorMessage(conn));
 
        protocol_version = PQfullProtocolVersion(conn);
-       if (protocol_version != 30002)
-               pg_fatal("expected 30002, got %d", protocol_version);
+       if (protocol_version != 30003)
+               pg_fatal("expected 30003, got %d", protocol_version);
 
        PQfinish(conn);
 
@@ -1423,7 +1423,7 @@ test_protocol_version(PGconn *conn)
        PQfinish(conn);
 
        /*
-        * Test max_protocol_version=latest. 'latest' currently means '3.2'.
+        * Test max_protocol_version=latest. 'latest' currently means '3.3'.
         */
        vals[max_protocol_version_index] = "latest";
        conn = PQconnectdbParams(keywords, vals, false);
@@ -1433,8 +1433,8 @@ test_protocol_version(PGconn *conn)
                                 PQerrorMessage(conn));
 
        protocol_version = PQfullProtocolVersion(conn);
-       if (protocol_version != 30002)
-               pg_fatal("expected 30002, got %d", protocol_version);
+       if (protocol_version != 30003)
+               pg_fatal("expected 30003, got %d", protocol_version);
 
        PQfinish(conn);
 
diff --git a/src/test/modules/libpq_pipeline/traces/prepared.trace 
b/src/test/modules/libpq_pipeline/traces/prepared.trace
index aeb5de109e0..5d36fb0056d 100644
--- a/src/test/modules/libpq_pipeline/traces/prepared.trace
+++ b/src/test/modules/libpq_pipeline/traces/prepared.trace
@@ -7,6 +7,7 @@ B       113     RowDescription   4 "?column?" NNNN 0 NNNN 4 -1 
0 "?column?" NNNN 0 NNNN 655
 B      5       ReadyForQuery    I
 F      16      Close    S "select_one"
 F      4       Sync
+B      15      PrepStmtDeallocated      "select_one"
 B      4       CloseComplete
 B      5       ReadyForQuery    I
 F      16      Describe         S "select_one"
-- 
2.50.1 (Apple Git-155)

>From 1f35373081aa9b2c5143e7af366211302eb997fc Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Fri, 22 May 2026 10:40:38 -0700
Subject: [PATCH v2 2/3] stop using PQfn() in libpq's LO interface

---
 src/interfaces/libpq/fe-connect.c |   7 +-
 src/interfaces/libpq/fe-lobj.c    | 672 ++++++++++++++----------------
 src/interfaces/libpq/libpq-int.h  |  22 +-
 src/tools/pgindent/typedefs.list  |   1 +
 4 files changed, 319 insertions(+), 383 deletions(-)

diff --git a/src/interfaces/libpq/fe-connect.c 
b/src/interfaces/libpq/fe-connect.c
index 5e41c21c6f6..9d0e0fd6798 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn)
        conn->in_hot_standby = PG_BOOL_UNKNOWN;
        conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
        conn->sversion = 0;
-
-       /* Drop large-object lookup data */
-       free(conn->lobjfuncs);
-       conn->lobjfuncs = NULL;
+       conn->lobjprepared = false;
+       conn->lobjprepmap = 0;
+       conn->lo_dealloc_cb_set = false;
 
        /* Reset assorted other per-connection state */
        conn->last_sqlstate[0] = '\0';
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 12a32fcbaf3..0d3d7c1c3ed 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -41,10 +41,116 @@
 
 #define LO_BUFSIZE               8192
 
+typedef enum PGlobjfunctype
+{
+       LO_OPEN,
+       LO_CLOSE,
+       LO_CREAT,
+       LO_CREATE,
+       LO_UNLINK,
+       LO_LSEEK,
+       LO_LSEEK64,
+       LO_TELL,
+       LO_TELL64,
+       LO_TRUNCATE,
+       LO_TRUNCATE64,
+       LO_READ,
+       LO_WRITE,
+} PGlobjfunctype;
+
+typedef struct PGlobjfuncs
+{
+       char       *name;
+       char       *query;
+       int                     version;
+} PGlobjfuncs;
+
+static const PGlobjfuncs lobjfuncs[] =
+{
+       [LO_OPEN] = {
+               "libpq_internal_lo_open",
+               "SELECT pg_catalog.lo_open($1::pg_catalog.oid, 
$2::pg_catalog.int4)"
+       },
+       [LO_CLOSE] = {
+               "libpq_internal_lo_close",
+               "SELECT pg_catalog.lo_close($1::pg_catalog.int4)"
+       },
+       [LO_CREAT] = {
+               "libpq_internal_lo_creat",
+               "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)"
+       },
+       [LO_CREATE] = {
+               "libpq_internal_lo_create",
+               "SELECT pg_catalog.lo_create($1::pg_catalog.oid)",
+               80100
+       },
+       [LO_UNLINK] = {
+               "libpq_internal_lo_unlink",
+               "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)"
+       },
+       [LO_LSEEK] = {
+               "libpq_internal_lo_lseek",
+               "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, 
$2::pg_catalog.int4, $3::pg_catalog.int4)"
+       },
+       [LO_LSEEK64] = {
+               "libpq_internal_lo_lseek64",
+               "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, 
$2::pg_catalog.int8, $3::pg_catalog.int4)",
+               90300
+       },
+       [LO_TELL] = {
+               "libpq_internal_lo_tell",
+               "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)"
+       },
+       [LO_TELL64] = {
+               "libpq_internal_lo_tell64",
+               "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)",
+               90300
+       },
+       [LO_TRUNCATE] = {
+               "libpq_internal_lo_truncate",
+               "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, 
$2::pg_catalog.int4)",
+               80300
+       },
+       [LO_TRUNCATE64] = {
+               "libpq_internal_lo_truncate64",
+               "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, 
$2::pg_catalog.int8)",
+               90300
+       },
+       [LO_READ] = {
+               "libpq_internal_loread",
+               "SELECT pg_catalog.loread($1::pg_catalog.int4, 
$2::pg_catalog.int4)"
+       },
+       [LO_WRITE] = {
+               "libpq_internal_lowrite",
+               "SELECT pg_catalog.lowrite($1::pg_catalog.int4, 
$2::pg_catalog.bytea)"
+       }
+};
+
 static int     lo_initialize(PGconn *conn);
 static Oid     lo_import_internal(PGconn *conn, const char *filename, Oid oid);
-static int64_t lo_hton64(int64_t host64);
-static int64_t lo_ntoh64(int64_t net64);
+
+static bool
+lo_result_is_valid(const PGresult *res, int len)
+{
+       return PQresultStatus(res) == PGRES_TUPLES_OK &&
+               PQntuples(res) == 1 &&
+               PQnfields(res) == 1 &&
+               PQgetisnull(res, 0, 0) == 0 &&
+               PQfformat(res, 0) == 1 &&
+               (len == -1 || PQgetlength(res, 0, 0) == len);
+}
+
+static PGresult *
+lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv,
+               const int *argLens, const int *argFmts)
+{
+       if (conn->lobjprepared)
+               return PQexecPrepared(conn, lobjfuncs[type].name, nargs,
+                                                         (const char *const *) 
argv, argLens, argFmts, 1);
+       else
+               return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL,
+                                                       (const char *const *) 
argv, argLens, argFmts, 1);
+}
 
 /*
  * lo_open
@@ -57,26 +163,26 @@ int
 lo_open(PGconn *conn, Oid lobjId, int mode)
 {
        int                     fd;
-       int                     result_len;
-       PQArgBlock      argv[2];
+       char       *argv[2];
+       int                     argLens[] = {4, 4};
+       int                     argFmts[] = {1, 1};
        PGresult   *res;
 
        if (lo_initialize(conn) < 0)
                return -1;
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = lobjId;
+       lobjId = pg_hton32(lobjId);
+       argv[0] = (char *) &lobjId;
 
-       argv[1].isint = 1;
-       argv[1].len = 4;
-       argv[1].u.integer = mode;
+       mode = pg_hton32(mode);
+       argv[1] = (char *) &mode;
 
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, 
argv, 2);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(fd)))
        {
+               memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd));
                PQclear(res);
-               return fd;
+               return pg_ntoh32(fd);
        }
        else
        {
@@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
 int
 lo_close(PGconn *conn, int fd)
 {
-       PQArgBlock      argv[1];
+       char       *argv[1];
+       int                     argLens[] = {4};
+       int                     argFmts[] = {1};
        PGresult   *res;
        int                     retval;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return -1;
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = fd;
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_close,
-                          &retval, &result_len, 1, argv, 1);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       fd = pg_hton32(fd);
+       argv[0] = (char *) &fd;
+
+       res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return retval;
+               return pg_ntoh32(retval);
        }
        else
        {
@@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd)
 int
 lo_truncate(PGconn *conn, int fd, size_t len)
 {
-       PQArgBlock      argv[2];
+       char       *argv[2];
+       int                     len32 = len;
+       int                     argLens[] = {4, 4};
+       int                     argFmts[] = {1, 1};
        PGresult   *res;
        int                     retval;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return -1;
 
-       /* Must check this on-the-fly because it's not there pre-8.3 */
-       if (conn->lobjfuncs->fn_lo_truncate == 0)
+       if (PQserverVersion(conn) < 80300)
        {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
+               libpq_append_conn_error(conn, "server does not support function 
\"%s\"",
                                                                "lo_truncate");
                return -1;
        }
@@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len)
                return -1;
        }
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = fd;
+       fd = pg_hton32(fd);
+       argv[0] = (char *) &fd;
 
-       argv[1].isint = 1;
-       argv[1].len = 4;
-       argv[1].u.integer = (int) len;
+       len32 = pg_hton32(len32);
+       argv[1] = (char *) &len32;
 
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
-                          &retval, &result_len, 1, argv, 2);
-
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return retval;
+               return pg_ntoh32(retval);
        }
        else
        {
@@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len)
 int
 lo_truncate64(PGconn *conn, int fd, int64_t len)
 {
-       PQArgBlock      argv[2];
+       char       *argv[2];
+       int                     argLens[] = {4, 8};
+       int                     argFmts[] = {1, 1};
        PGresult   *res;
        int                     retval;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return -1;
 
-       if (conn->lobjfuncs->fn_lo_truncate64 == 0)
+       if (PQserverVersion(conn) < 90300)
        {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
+               libpq_append_conn_error(conn, "server does not support function 
\"%s\"",
                                                                
"lo_truncate64");
                return -1;
        }
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = fd;
-
-       len = lo_hton64(len);
-       argv[1].isint = 0;
-       argv[1].len = 8;
-       argv[1].u.ptr = (int *) &len;
+       fd = pg_hton32(fd);
+       argv[0] = (char *) &fd;
 
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64,
-                          &retval, &result_len, 1, argv, 2);
+       len = pg_hton64(len);
+       argv[1] = (char *) &len;
 
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return retval;
+               return pg_ntoh32(retval);
        }
        else
        {
@@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
 int
 lo_read(PGconn *conn, int fd, char *buf, size_t len)
 {
-       PQArgBlock      argv[2];
+       char       *argv[2];
+       int                     len32 = len;
+       int                     argLens[] = {4, 4};
+       int                     argFmts[] = {1, 1};
        PGresult   *res;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return -1;
@@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len)
                return -1;
        }
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = fd;
+       fd = pg_hton32(fd);
+       argv[0] = (char *) &fd;
 
-       argv[1].isint = 1;
-       argv[1].len = 4;
-       argv[1].u.integer = (int) len;
+       len32 = pg_hton32(len32);
+       argv[1] = (char *) &len32;
 
-       res = PQnfn(conn, conn->lobjfuncs->fn_lo_read,
-                               (void *) buf, len, &result_len, 0, argv, 2);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, -1))
        {
+               int                     result_len = PQgetlength(res, 0, 0);
+
+               if (result_len > len)
+                       result_len = -1;
+               else
+                       memcpy(buf, PQgetvalue(res, 0, 0), result_len);
+
                PQclear(res);
                return result_len;
        }
@@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len)
 int
 lo_write(PGconn *conn, int fd, const char *buf, size_t len)
 {
-       PQArgBlock      argv[2];
+       char       *argv[2];
+       int                     argLens[] = {4, len};
+       int                     argFmts[] = {1, 1};
        PGresult   *res;
-       int                     result_len;
        int                     retval;
 
        if (lo_initialize(conn) < 0)
@@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t 
len)
                return -1;
        }
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = fd;
+       fd = pg_hton32(fd);
+       argv[0] = (char *) &fd;
 
-       argv[1].isint = 0;
-       argv[1].len = (int) len;
-       argv[1].u.ptr = (int *) unconstify(char *, buf);
+       argv[1] = unconstify(char *, buf);
 
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
-                          &retval, &result_len, 1, argv, 2);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return retval;
+               return pg_ntoh32(retval);
        }
        else
        {
@@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t 
len)
 int
 lo_lseek(PGconn *conn, int fd, int offset, int whence)
 {
-       PQArgBlock      argv[3];
+       char       *argv[3];
+       int                     argLens[] = {4, 4, 4};
+       int                     argFmts[] = {1, 1, 1};
        PGresult   *res;
        int                     retval;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return -1;
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = fd;
+       fd = pg_hton32(fd);
+       argv[0] = (char *) &fd;
 
-       argv[1].isint = 1;
-       argv[1].len = 4;
-       argv[1].u.integer = offset;
+       offset = pg_hton32(offset);
+       argv[1] = (char *) &offset;
 
-       argv[2].isint = 1;
-       argv[2].len = 4;
-       argv[2].u.integer = whence;
+       whence = pg_hton32(whence);
+       argv[2] = (char *) &whence;
 
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek,
-                          &retval, &result_len, 1, argv, 3);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return retval;
+               return pg_ntoh32(retval);
        }
        else
        {
@@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
 int64_t
 lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
 {
-       PQArgBlock      argv[3];
+       char       *argv[3];
+       int                     argLens[] = {4, 8, 4};
+       int                     argFmts[3] = {1, 1, 1};
        PGresult   *res;
        int64           retval;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return -1;
 
-       if (conn->lobjfuncs->fn_lo_lseek64 == 0)
+       if (PQserverVersion(conn) < 90300)
        {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
+               libpq_append_conn_error(conn, "server does not support function 
\"%s\"",
                                                                "lo_lseek64");
                return -1;
        }
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = fd;
+       fd = pg_hton32(fd);
+       argv[0] = (char *) &fd;
 
-       offset = lo_hton64(offset);
-       argv[1].isint = 0;
-       argv[1].len = 8;
-       argv[1].u.ptr = (int *) &offset;
+       offset = pg_hton64(offset);
+       argv[1] = (char *) &offset;
 
-       argv[2].isint = 1;
-       argv[2].len = 4;
-       argv[2].u.integer = whence;
+       whence = pg_hton32(whence);
+       argv[2] = (char *) &whence;
 
-       res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64,
-                               (void *) &retval, sizeof(retval), &result_len, 
0, argv, 3);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
+       res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return lo_ntoh64(retval);
+               return pg_ntoh64(retval);
        }
        else
        {
@@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int 
whence)
 Oid
 lo_creat(PGconn *conn, int mode)
 {
-       PQArgBlock      argv[1];
+       char       *argv[1];
+       int                     argLens[] = {4};
+       int                     argFmts[] = {1};
        PGresult   *res;
        int                     retval;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return InvalidOid;
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = mode;
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_creat,
-                          &retval, &result_len, 1, argv, 1);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       mode = pg_hton32(mode);
+       argv[0] = (char *) &mode;
+
+       res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return (Oid) retval;
+               return (Oid) pg_ntoh32(retval);
        }
        else
        {
@@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode)
 Oid
 lo_create(PGconn *conn, Oid lobjId)
 {
-       PQArgBlock      argv[1];
+       char       *argv[1];
+       int                     argLens[] = {4};
+       int                     argFmts[] = {1};
        PGresult   *res;
        int                     retval;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return InvalidOid;
 
-       /* Must check this on-the-fly because it's not there pre-8.1 */
-       if (conn->lobjfuncs->fn_lo_create == 0)
+       if (PQserverVersion(conn) < 80100)
        {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
+               libpq_append_conn_error(conn, "server does not support function 
\"%s\"",
                                                                "lo_create");
                return InvalidOid;
        }
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = lobjId;
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_create,
-                          &retval, &result_len, 1, argv, 1);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       lobjId = pg_hton32(lobjId);
+       argv[0] = (char *) &lobjId;
+
+       res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return (Oid) retval;
+               return (Oid) pg_ntoh32(retval);
        }
        else
        {
@@ -515,23 +617,23 @@ int
 lo_tell(PGconn *conn, int fd)
 {
        int                     retval;
-       PQArgBlock      argv[1];
+       char       *argv[1];
+       int                     argLens[] = {4};
+       int                     argFmts[] = {1};
        PGresult   *res;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return -1;
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = fd;
+       fd = pg_hton32(fd);
+       argv[0] = (char *) &fd;
 
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_tell,
-                          &retval, &result_len, 1, argv, 1);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return retval;
+               return pg_ntoh32(retval);
        }
        else
        {
@@ -548,30 +650,30 @@ int64_t
 lo_tell64(PGconn *conn, int fd)
 {
        int64           retval;
-       PQArgBlock      argv[1];
+       char       *argv[1];
+       int                     argLens[] = {4};
+       int                     argFmts[] = {1};
        PGresult   *res;
-       int                     result_len;
 
        if (lo_initialize(conn) < 0)
                return -1;
 
-       if (conn->lobjfuncs->fn_lo_tell64 == 0)
+       if (PQserverVersion(conn) < 90300)
        {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
+               libpq_append_conn_error(conn, "server does not support 
functions \"%s\"",
                                                                "lo_tell64");
                return -1;
        }
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = fd;
+       fd = pg_hton32(fd);
+       argv[0] = (char *) &fd;
 
-       res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64,
-                               (void *) &retval, sizeof(retval), &result_len, 
0, argv, 1);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
+       res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return lo_ntoh64(retval);
+               return pg_ntoh64(retval);
        }
        else
        {
@@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd)
 int
 lo_unlink(PGconn *conn, Oid lobjId)
 {
-       PQArgBlock      argv[1];
+       char       *argv[1];
+       int                     argLens[] = {4};
+       int                     argFmts[] = {1};
        PGresult   *res;
-       int                     result_len;
        int                     retval;
 
        if (lo_initialize(conn) < 0)
                return -1;
 
-       argv[0].isint = 1;
-       argv[0].len = 4;
-       argv[0].u.integer = lobjId;
+       lobjId = pg_hton32(lobjId);
+       argv[0] = (char *) &lobjId;
 
-       res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
-                          &retval, &result_len, 1, argv, 1);
-       if (PQresultStatus(res) == PGRES_COMMAND_OK)
+       res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts);
+       if (lo_result_is_valid(res, sizeof(retval)))
        {
+               memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
                PQclear(res);
-               return retval;
+               return pg_ntoh32(retval);
        }
        else
        {
@@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename)
        return result;
 }
 
+/*
+ * LO prepared statement deallocation callback.  Resets conn->lobjprepared and
+ * the corresponding bits in conn->lobjprepmap so the next call to
+ * lo_initialize() re-prepares as needed.
+ */
+static void
+lo_dealloc_callback(PGconn *conn, const char *name)
+{
+       if (name[0] == '\0')
+       {
+               conn->lobjprepared = false;
+               conn->lobjprepmap = 0;
+               return;
+       }
+
+       for (int i = 0; i < lengthof(lobjfuncs); i++)
+       {
+               if (strcmp(name, lobjfuncs[i].name) != 0)
+                       continue;
+
+               conn->lobjprepmap &= ~(1 << i);
+               break;
+       }
+}
 
 /*
  * lo_initialize
@@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename)
  * Initialize for a new large-object operation on an existing connection.
  * Return 0 if OK, -1 on failure.
  *
- * If we haven't previously done so, we collect the function OIDs from
- * pg_proc for all functions that are required for large object operations.
+ * If we haven't previously done so, we prepared statements for all the
+ * functions that are required for large object operations.
  */
 static int
 lo_initialize(PGconn *conn)
 {
-       PGresult   *res;
-       PGlobjfuncs *lobjfuncs;
-       int                     n;
-       const char *query;
-       const char *fname;
-       Oid                     foid;
-
        /* Nothing we can do with no connection */
        if (conn == NULL)
                return -1;
@@ -857,208 +976,41 @@ lo_initialize(PGconn *conn)
        pqClearConnErrorState(conn);
 
        /* Nothing else to do if we already collected info */
-       if (conn->lobjfuncs != NULL)
+       if (conn->lobjprepared)
                return 0;
 
-       /*
-        * Allocate the structure to hold the function OIDs.  We don't store it
-        * into the PGconn until it's successfully filled.
-        */
-       lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs));
-       if (lobjfuncs == NULL)
-       {
-               libpq_append_conn_error(conn, "out of memory");
-               return -1;
-       }
-       MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs));
-
-       /*
-        * Execute the query to get all the functions at once.  (Not all of them
-        * may exist in older server versions.)
-        */
-       query = "select proname, oid from pg_catalog.pg_proc "
-               "where proname in ("
-               "'lo_open', "
-               "'lo_close', "
-               "'lo_creat', "
-               "'lo_create', "
-               "'lo_unlink', "
-               "'lo_lseek', "
-               "'lo_lseek64', "
-               "'lo_tell', "
-               "'lo_tell64', "
-               "'lo_truncate', "
-               "'lo_truncate64', "
-               "'loread', "
-               "'lowrite') "
-               "and pronamespace = (select oid from pg_catalog.pg_namespace "
-               "where nspname = 'pg_catalog')";
+       /* Use PQexecParams() on servers that don't have dealloc notifications 
*/
+       if (PQfullProtocolVersion(conn) < 30003)
+               return 0;
 
-       res = PQexec(conn, query);
-       if (res == NULL)
+       if (!conn->lo_dealloc_cb_set)
        {
-               free(lobjfuncs);
-               return -1;
+               PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback);
+               conn->lo_dealloc_cb_set = true;
        }
 
-       if (res->resultStatus != PGRES_TUPLES_OK)
+       for (int i = 0; i < lengthof(lobjfuncs); i++)
        {
-               free(lobjfuncs);
-               PQclear(res);
-               libpq_append_conn_error(conn, "query to initialize large object 
functions did not return data");
-               return -1;
-       }
+               PGresult   *res;
 
-       /*
-        * Examine the result and put the OID's into the struct
-        */
-       for (n = 0; n < PQntuples(res); n++)
-       {
-               fname = PQgetvalue(res, n, 0);
-               foid = (Oid) atoi(PQgetvalue(res, n, 1));
-               if (strcmp(fname, "lo_open") == 0)
-                       lobjfuncs->fn_lo_open = foid;
-               else if (strcmp(fname, "lo_close") == 0)
-                       lobjfuncs->fn_lo_close = foid;
-               else if (strcmp(fname, "lo_creat") == 0)
-                       lobjfuncs->fn_lo_creat = foid;
-               else if (strcmp(fname, "lo_create") == 0)
-                       lobjfuncs->fn_lo_create = foid;
-               else if (strcmp(fname, "lo_unlink") == 0)
-                       lobjfuncs->fn_lo_unlink = foid;
-               else if (strcmp(fname, "lo_lseek") == 0)
-                       lobjfuncs->fn_lo_lseek = foid;
-               else if (strcmp(fname, "lo_lseek64") == 0)
-                       lobjfuncs->fn_lo_lseek64 = foid;
-               else if (strcmp(fname, "lo_tell") == 0)
-                       lobjfuncs->fn_lo_tell = foid;
-               else if (strcmp(fname, "lo_tell64") == 0)
-                       lobjfuncs->fn_lo_tell64 = foid;
-               else if (strcmp(fname, "lo_truncate") == 0)
-                       lobjfuncs->fn_lo_truncate = foid;
-               else if (strcmp(fname, "lo_truncate64") == 0)
-                       lobjfuncs->fn_lo_truncate64 = foid;
-               else if (strcmp(fname, "loread") == 0)
-                       lobjfuncs->fn_lo_read = foid;
-               else if (strcmp(fname, "lowrite") == 0)
-                       lobjfuncs->fn_lo_write = foid;
-       }
+               if (conn->lobjprepmap & (1 << i))
+                       continue;
 
-       PQclear(res);
+               if (PQserverVersion(conn) < lobjfuncs[i].version)
+                       continue;
 
-       /*
-        * Finally check that we got all required large object interface 
functions
-        * (ones that have been added later than the stone age are instead 
checked
-        * only if used)
-        */
-       if (lobjfuncs->fn_lo_open == 0)
-       {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
-                                                               "lo_open");
-               free(lobjfuncs);
-               return -1;
-       }
-       if (lobjfuncs->fn_lo_close == 0)
-       {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
-                                                               "lo_close");
-               free(lobjfuncs);
-               return -1;
-       }
-       if (lobjfuncs->fn_lo_creat == 0)
-       {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
-                                                               "lo_creat");
-               free(lobjfuncs);
-               return -1;
-       }
-       if (lobjfuncs->fn_lo_unlink == 0)
-       {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
-                                                               "lo_unlink");
-               free(lobjfuncs);
-               return -1;
-       }
-       if (lobjfuncs->fn_lo_lseek == 0)
-       {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
-                                                               "lo_lseek");
-               free(lobjfuncs);
-               return -1;
-       }
-       if (lobjfuncs->fn_lo_tell == 0)
-       {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
-                                                               "lo_tell");
-               free(lobjfuncs);
-               return -1;
-       }
-       if (lobjfuncs->fn_lo_read == 0)
-       {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
-                                                               "loread");
-               free(lobjfuncs);
-               return -1;
-       }
-       if (lobjfuncs->fn_lo_write == 0)
-       {
-               libpq_append_conn_error(conn, "cannot determine OID of function 
%s",
-                                                               "lowrite");
-               free(lobjfuncs);
-               return -1;
+               res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, 
NULL);
+               if (PQresultStatus(res) != PGRES_COMMAND_OK)
+               {
+                       PQclear(res);
+                       libpq_append_conn_error(conn, "query to prepare large 
object statements failed");
+                       return -1;
+               }
+
+               PQclear(res);
+               conn->lobjprepmap |= (1 << i);
        }
 
-       /*
-        * Put the structure into the connection control
-        */
-       conn->lobjfuncs = lobjfuncs;
+       conn->lobjprepared = true;
        return 0;
 }
-
-/*
- * lo_hton64
- *       converts a 64-bit integer from host byte order to network byte order
- */
-static int64_t
-lo_hton64(int64_t host64)
-{
-       union
-       {
-               int64           i64;
-               uint32          i32[2];
-       }                       swap;
-       uint32          t;
-
-       /* High order half first, since we're doing MSB-first */
-       t = (uint32) (host64 >> 32);
-       swap.i32[0] = pg_hton32(t);
-
-       /* Now the low order half */
-       t = (uint32) host64;
-       swap.i32[1] = pg_hton32(t);
-
-       return swap.i64;
-}
-
-/*
- * lo_ntoh64
- *       converts a 64-bit integer from network byte order to host byte order
- */
-static int64_t
-lo_ntoh64(int64_t net64)
-{
-       union
-       {
-               int64           i64;
-               uint32          i32[2];
-       }                       swap;
-       int64           result;
-
-       swap.i64 = net64;
-
-       result = (uint32) pg_ntoh32(swap.i32[0]);
-       result <<= 32;
-       result |= (uint32) pg_ntoh32(swap.i32[1]);
-
-       return result;
-}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 7eca941ddcc..7a32c14de5e 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -275,24 +275,6 @@ typedef struct pgParameterStatus
        /* Note: name and value are stored in same malloc block as struct is */
 } pgParameterStatus;
 
-/* large-object-access data ... allocated only if large-object code is used. */
-typedef struct pgLobjfuncs
-{
-       Oid                     fn_lo_open;             /* OID of backend 
function lo_open              */
-       Oid                     fn_lo_close;    /* OID of backend function 
lo_close             */
-       Oid                     fn_lo_creat;    /* OID of backend function 
lo_creat             */
-       Oid                     fn_lo_create;   /* OID of backend function 
lo_create    */
-       Oid                     fn_lo_unlink;   /* OID of backend function 
lo_unlink    */
-       Oid                     fn_lo_lseek;    /* OID of backend function 
lo_lseek             */
-       Oid                     fn_lo_lseek64;  /* OID of backend function 
lo_lseek64   */
-       Oid                     fn_lo_tell;             /* OID of backend 
function lo_tell              */
-       Oid                     fn_lo_tell64;   /* OID of backend function 
lo_tell64    */
-       Oid                     fn_lo_truncate; /* OID of backend function 
lo_truncate  */
-       Oid                     fn_lo_truncate64;       /* OID of function 
lo_truncate64 */
-       Oid                     fn_lo_read;             /* OID of backend 
function LOread               */
-       Oid                     fn_lo_write;    /* OID of backend function 
LOwrite              */
-} PGlobjfuncs;
-
 /*
  * PGdataValue represents a data field value being passed to a row processor.
  * It could be either text or binary data; text data is not zero-terminated.
@@ -564,7 +546,9 @@ struct pg_conn
        PGTernaryBool in_hot_standby;   /* in_hot_standby */
        PGVerbosity verbosity;          /* error/notice message verbosity */
        PGContextVisibility show_context;       /* whether to show CONTEXT 
field */
-       PGlobjfuncs *lobjfuncs;         /* private state for large-object 
access fns */
+       bool            lobjprepared;   /* whether LO statements have been 
prepared */
+       uint32          lobjprepmap;    /* bitmap of prepared LO statements */
+       bool            lo_dealloc_cb_set;      /* whether prep stmt dealloc 
callback set */
        pg_prng_state prng_state;       /* prng state for load balancing 
connections */
 
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8cf40c87043..86b90010d2a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1983,6 +1983,7 @@ PGcmdQueueEntry
 PGconn
 PGdataValue
 PGlobjfuncs
+PGlobjfunctype
 PGnotify
 PGoauthBearerRequest
 PGoauthBearerRequestV2
-- 
2.50.1 (Apple Git-155)

>From 2a965c838ae820bf448106ed391bb7d84c6dc00f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Fri, 22 May 2026 12:00:50 -0700
Subject: [PATCH v2 3/3] remove support for PQfn

---
 doc/src/sgml/libpq.sgml             | 119 +-------------
 src/backend/tcop/fastpath.c         |   3 +-
 src/include/tcop/dest.h             |   4 +-
 src/interfaces/libpq/fe-exec.c      |  54 +-----
 src/interfaces/libpq/fe-protocol3.c | 246 ----------------------------
 src/interfaces/libpq/libpq-int.h    |   5 -
 6 files changed, 12 insertions(+), 419 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..812e9089bfd 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -3738,7 +3738,7 @@ PGresult *PQdescribePrepared(PGconn *conn, const char 
*stmtName);
         <xref linkend="libpq-PQparamtype"/> can be applied to this
         <structname>PGresult</structname> to obtain information about the 
parameters
         of the prepared statement, and the functions
-        <xref linkend="libpq-PQnfields"/>, <xref linkend="libpq-PQfname"/>,
+        <xref linkend="libpq-PQnfields"/>,
         <xref linkend="libpq-PQftype"/>, etc. provide information about the
         result columns (if any) of the statement.
        </para>
@@ -5887,7 +5887,7 @@ int PQflush(PGconn *conn);
     are permitted, command strings containing multiple SQL commands are
     disallowed, and so is <literal>COPY</literal>.
     Using synchronous command execution functions
-    such as <function>PQfn</function>,
+    such as
     <function>PQexec</function>,
     <function>PQexecParams</function>,
     <function>PQprepare</function>,
@@ -7046,121 +7046,6 @@ int PQrequestCancel(PGconn *conn);
   </sect2>
  </sect1>
 
- <sect1 id="libpq-fastpath">
-  <title>The Fast-Path Interface</title>
-
-  <indexterm zone="libpq-fastpath">
-   <primary>fast path</primary>
-  </indexterm>
-
-  <para>
-   <productname>PostgreSQL</productname> provides a fast-path interface
-   to send simple function calls to the server.
-  </para>
-
-  <warning>
-   <para>
-    This interface is unsafe and should not be used.  When
-    <parameter>result_is_int</parameter> is set to <literal>0</literal>,
-    <function>PQfn</function> may write data beyond the end of
-    <parameter>result_buf</parameter>, regardless of whether the buffer has
-    enough space for the requested number of bytes.  Furthermore, it is
-    obsolete, as one can achieve similar
-    performance and greater functionality by setting up a prepared
-    statement to define the function call.  Then, executing the statement
-    with binary transmission of parameters and results substitutes for a
-    fast-path function call.
-   </para>
-  </warning>
-
-  <para>
-   The function <function 
id="libpq-PQfn">PQfn</function><indexterm><primary>PQfn</primary></indexterm>
-   requests execution of a server function via the fast-path interface:
-<synopsis>
-PGresult *PQfn(PGconn *conn,
-               int fnid,
-               int *result_buf,
-               int *result_len,
-               int result_is_int,
-               const PQArgBlock *args,
-               int nargs);
-
-typedef struct
-{
-    int len;
-    int isint;
-    union
-    {
-        int *ptr;
-        int integer;
-    } u;
-} PQArgBlock;
-</synopsis>
-  </para>
-
-  <para>
-   The <parameter>fnid</parameter> argument is the OID of the function to be
-   executed.  <parameter>args</parameter> and <parameter>nargs</parameter> 
define the
-   parameters to be passed to the function; they must match the declared
-   function argument list.  When the <parameter>isint</parameter> field of a
-   parameter structure is true, the <parameter>u.integer</parameter> value is 
sent
-   to the server as an integer of the indicated length (this must be
-   2 or 4 bytes); proper byte-swapping occurs.  When 
<parameter>isint</parameter>
-   is false, the indicated number of bytes at <parameter>*u.ptr</parameter> are
-   sent with no processing; the data must be in the format expected by
-   the server for binary transmission of the function's argument data
-   type.  (The declaration of <parameter>u.ptr</parameter> as being of
-   type <type>int *</type> is historical; it would be better to consider
-   it <type>void *</type>.)
-   <parameter>result_buf</parameter> points to the buffer in which to place
-   the function's return value.  The caller must have allocated sufficient
-   space to store the return value.  (There is no check!) The actual result
-   length in bytes will be returned in the integer pointed to by
-   <parameter>result_len</parameter>.  If a 2- or 4-byte integer result
-   is expected, set <parameter>result_is_int</parameter> to 1, otherwise
-   set it to 0.  Setting <parameter>result_is_int</parameter> to 1 causes
-   <application>libpq</application> to byte-swap the value if necessary, so 
that it
-   is delivered as a proper <type>int</type> value for the client machine;
-   note that a 4-byte integer is delivered into 
<parameter>*result_buf</parameter>
-   for either allowed result size.
-   When <parameter>result_is_int</parameter> is 0, the binary-format byte 
string
-   sent by the server is returned unmodified. (In this case it's better
-   to consider <parameter>result_buf</parameter> as being of
-   type <type>void *</type>.)
-  </para>
-
-  <para>
-   <function>PQfn</function> always returns a valid
-   <structname>PGresult</structname> pointer, with
-   status <literal>PGRES_COMMAND_OK</literal> for success
-   or <literal>PGRES_FATAL_ERROR</literal> if some problem was encountered.
-   The result status should be
-   checked before the result is used.   The caller is responsible for
-   freeing  the  <structname>PGresult</structname>  with
-   <xref linkend="libpq-PQclear"/> when it is no longer needed.
-  </para>
-
-  <para>
-   To pass a NULL argument to the function, set
-   the <parameter>len</parameter> field of that parameter structure
-   to <literal>-1</literal>; the <parameter>isint</parameter>
-   and <parameter>u</parameter> fields are then irrelevant.
-  </para>
-
-  <para>
-   If the function returns NULL, <parameter>*result_len</parameter> is set
-   to <literal>-1</literal>, and <parameter>*result_buf</parameter> is not
-   modified.
-  </para>
-
-  <para>
-   Note that it is not possible to handle set-valued results when using
-   this interface.  Also, the function must be a plain function, not an
-   aggregate, window function, or procedure.
-  </para>
-
- </sect1>
-
  <sect1 id="libpq-notify">
   <title>Asynchronous Notification</title>
 
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 52772bc90a8..2a5c45efffe 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -11,7 +11,8 @@
  *       src/backend/tcop/fastpath.c
  *
  * NOTES
- *       This cruft is the server side of PQfn.
+ *       This cruft is the server side of PQfn, which was removed in v20 but
+ *       may still be used by older clients.
  *
  *-------------------------------------------------------------------------
  */
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 103f27fc3cb..507414421ec 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -12,8 +12,8 @@
  *
  *       - a remote process is the destination when we are
  *             running a backend with a frontend and the frontend executes
- *             PQexec() or PQfn().  In this case, the results are sent
- *             to the frontend via the functions in backend/libpq.
+ *             PQexec().  In this case, the results are sent to the frontend 
via
+ *             the functions in backend/libpq.
  *
  *       - DestNone is the destination when the system executes
  *             a query internally.  The results are discarded.
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 7b8edacbfde..400e1eef94b 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2986,13 +2986,11 @@ PQendcopy(PGconn *conn)
  *             nargs                   : # of arguments in args array.
  *
  * RETURNS
- *             PGresult with status = PGRES_COMMAND_OK if successful.
- *                     *result_len is > 0 if there is a return value, 0 if not.
- *             PGresult with status = PGRES_FATAL_ERROR if backend returns an 
error.
- *             NULL on communications failure.  conn->errorMessage will be set.
+ *             This function was unsafe and is no longer supported, so it now 
always
+ *             sets *result_len to 0 and returns a PGresult with status set to
+ *             PGRES_FATAL_ERROR.
  * ----------------
  */
-
 PGresult *
 PQfn(PGconn *conn,
         int fnid,
@@ -3001,51 +2999,11 @@ PQfn(PGconn *conn,
         int result_is_int,
         const PQArgBlock *args,
         int nargs)
-{
-       return PQnfn(conn, fnid, result_buf, -1, result_len,
-                                result_is_int, args, nargs);
-}
-
-/*
- * PQnfn
- *             Private version of PQfn() with verification that returned data 
fits in
- *             result_buf when result_is_int == 0.  Setting buf_size to -1 
disables
- *             this verification.
- */
-PGresult *
-PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
-         int result_is_int, const PQArgBlock *args, int nargs)
 {
        *result_len = 0;
-
-       if (!conn)
-               return NULL;
-
-       /*
-        * Since this is the beginning of a query cycle, reset the error state.
-        * However, in pipeline mode with something already queued, the error
-        * buffer belongs to that command and we shouldn't clear it.
-        */
-       if (conn->cmd_queue_head == NULL)
-               pqClearConnErrorState(conn);
-
-       if (conn->pipelineStatus != PQ_PIPELINE_OFF)
-       {
-               libpq_append_conn_error(conn, "%s not allowed in pipeline 
mode", "PQfn");
-               return NULL;
-       }
-
-       if (conn->sock == PGINVALID_SOCKET || conn->asyncStatus != PGASYNC_IDLE 
||
-               pgHavePendingResult(conn))
-       {
-               libpq_append_conn_error(conn, "connection in wrong state");
-               return NULL;
-       }
-
-       return pqFunctionCall3(conn, fnid,
-                                                  result_buf, buf_size, 
result_len,
-                                                  result_is_int,
-                                                  args, nargs);
+       libpq_append_conn_error(conn, "PQfn() is no longer supported");
+       pqSaveErrorResult(conn);
+       return pqPrepareAsyncResult(conn);
 }
 
 /* ====== Pipeline mode support ======== */
diff --git a/src/interfaces/libpq/fe-protocol3.c 
b/src/interfaces/libpq/fe-protocol3.c
index 0407d10362d..f9c55ee7905 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -2235,252 +2235,6 @@ pqEndcopy3(PGconn *conn)
        return 1;
 }
 
-
-/*
- * PQfn - Send a function call to the POSTGRES backend.
- *
- * See fe-exec.c for documentation.
- */
-PGresult *
-pqFunctionCall3(PGconn *conn, Oid fnid,
-                               int *result_buf, int buf_size, int 
*actual_result_len,
-                               int result_is_int,
-                               const PQArgBlock *args, int nargs)
-{
-       bool            needInput = false;
-       ExecStatusType status = PGRES_FATAL_ERROR;
-       char            id;
-       int                     msgLength;
-       int                     avail;
-       int                     i;
-
-       /* already validated by PQfn */
-       Assert(conn->pipelineStatus == PQ_PIPELINE_OFF);
-
-       /* PQfn already validated connection state */
-
-       if (pqPutMsgStart(PqMsg_FunctionCall, conn) < 0 ||
-               pqPutInt(fnid, 4, conn) < 0 ||  /* function id */
-               pqPutInt(1, 2, conn) < 0 || /* # of format codes */
-               pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */
-               pqPutInt(nargs, 2, conn) < 0)   /* # of args */
-       {
-               /* error message should be set up already */
-               return NULL;
-       }
-
-       for (i = 0; i < nargs; ++i)
-       {                                                       /* len.int4 + 
contents     */
-               if (pqPutInt(args[i].len, 4, conn))
-                       return NULL;
-               if (args[i].len == -1)
-                       continue;                       /* it's NULL */
-
-               if (args[i].isint)
-               {
-                       if (pqPutInt(args[i].u.integer, args[i].len, conn))
-                               return NULL;
-               }
-               else
-               {
-                       if (pqPutnchar(args[i].u.ptr, args[i].len, conn))
-                               return NULL;
-               }
-       }
-
-       if (pqPutInt(1, 2, conn) < 0)   /* result format code: BINARY */
-               return NULL;
-
-       if (pqPutMsgEnd(conn) < 0 ||
-               pqFlush(conn))
-               return NULL;
-
-       for (;;)
-       {
-               if (needInput)
-               {
-                       /* Wait for some data to arrive (or for the channel to 
close) */
-                       if (pqWait(true, false, conn) ||
-                               pqReadData(conn) < 0)
-                               break;
-               }
-
-               /*
-                * Scan the message. If we run out of data, loop around to try 
again.
-                */
-               needInput = true;
-
-               conn->inCursor = conn->inStart;
-               if (pqGetc(&id, conn))
-                       continue;
-               if (pqGetInt(&msgLength, 4, conn))
-                       continue;
-
-               /*
-                * Try to validate message type/length here.  A length less 
than 4 is
-                * definitely broken.  Large lengths should only be believed 
for a few
-                * message types.
-                */
-               if (msgLength < 4)
-               {
-                       handleSyncLoss(conn, id, msgLength);
-                       break;
-               }
-               if (msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(id))
-               {
-                       handleSyncLoss(conn, id, msgLength);
-                       break;
-               }
-
-               /*
-                * Can't process if message body isn't all here yet.
-                */
-               msgLength -= 4;
-               avail = conn->inEnd - conn->inCursor;
-               if (avail < msgLength)
-               {
-                       /*
-                        * Before looping, enlarge the input buffer if needed 
to hold the
-                        * whole message.  See notes in parseInput.
-                        */
-                       if (pqCheckInBufferSpace(conn->inCursor + (size_t) 
msgLength,
-                                                                        conn))
-                       {
-                               /*
-                                * Abandon the connection.  There's not much 
else we can
-                                * safely do; we can't just ignore the message 
or we could
-                                * miss important changes to the connection 
state.
-                                * pqCheckInBufferSpace() already reported the 
error.
-                                */
-                               handleFatalError(conn);
-                               break;
-                       }
-                       continue;
-               }
-
-               /*
-                * We should see V or E response to the command, but might get N
-                * and/or A notices first. We also need to swallow the final Z 
before
-                * returning.
-                */
-               switch (id)
-               {
-                       case PqMsg_FunctionCallResponse:
-                               if (pqGetInt(actual_result_len, 4, conn))
-                                       continue;
-                               if (*actual_result_len != -1)
-                               {
-                                       if (result_is_int)
-                                       {
-                                               if (pqGetInt(result_buf, 
*actual_result_len, conn))
-                                                       continue;
-                                       }
-                                       else
-                                       {
-                                               /*
-                                                * If the server returned too 
much data for the
-                                                * buffer, something fishy is 
going on.  Abandon ship.
-                                                */
-                                               if (buf_size != -1 && 
*actual_result_len > buf_size)
-                                               {
-                                                       
libpq_append_conn_error(conn, "server returned too much data");
-                                                       handleFatalError(conn);
-                                                       return 
pqPrepareAsyncResult(conn);
-                                               }
-
-                                               if (pqGetnchar(result_buf,
-                                                                          
*actual_result_len,
-                                                                          
conn))
-                                                       continue;
-                                       }
-                               }
-                               /* correctly finished function result message */
-                               status = PGRES_COMMAND_OK;
-                               break;
-                       case PqMsg_ErrorResponse:
-                               if (pqGetErrorNotice3(conn, true))
-                                       continue;
-                               status = PGRES_FATAL_ERROR;
-                               break;
-                       case PqMsg_NotificationResponse:
-                               /* handle notify and go back to processing 
return values */
-                               if (getNotify(conn))
-                                       continue;
-                               break;
-                       case PqMsg_NoticeResponse:
-                               /* handle notice and go back to processing 
return values */
-                               if (pqGetErrorNotice3(conn, false))
-                                       continue;
-                               break;
-                       case PqMsg_ReadyForQuery:
-                               if (getReadyForQuery(conn))
-                                       continue;
-
-                               /* consume the message */
-                               pqParseDone(conn, conn->inStart + 5 + 
msgLength);
-
-                               /*
-                                * If we already have a result object (probably 
an error), use
-                                * that.  Otherwise, if we saw a function 
result message,
-                                * report COMMAND_OK.  Otherwise, the backend 
violated the
-                                * protocol, so complain.
-                                */
-                               if (!pgHavePendingResult(conn))
-                               {
-                                       if (status == PGRES_COMMAND_OK)
-                                       {
-                                               conn->result = 
PQmakeEmptyPGresult(conn, status);
-                                               if (!conn->result)
-                                               {
-                                                       
libpq_append_conn_error(conn, "out of memory");
-                                                       pqSaveErrorResult(conn);
-                                               }
-                                       }
-                                       else
-                                       {
-                                               libpq_append_conn_error(conn, 
"protocol error: no function result");
-                                               pqSaveErrorResult(conn);
-                                       }
-                               }
-                               /* and we're out */
-                               return pqPrepareAsyncResult(conn);
-                       case PqMsg_ParameterStatus:
-                               if (getParameterStatus(conn))
-                                       continue;
-                               break;
-                       case PqMsg_PrepStmtDeallocated:
-                               if (getPrepStmtDeallocated(conn))
-                                       continue;
-                               break;
-                       default:
-                               /* The backend violates the protocol. */
-                               libpq_append_conn_error(conn, "protocol error: 
id=0x%x", id);
-                               pqSaveErrorResult(conn);
-
-                               /*
-                                * We can't call parsing done due to the 
protocol violation
-                                * (so message tracing wouldn't work), but 
trust the specified
-                                * message length as what to skip.
-                                */
-                               conn->inStart += 5 + msgLength;
-                               return pqPrepareAsyncResult(conn);
-               }
-
-               /* Completed parsing this message, keep going */
-               pqParseDone(conn, conn->inStart + 5 + msgLength);
-               needInput = false;
-       }
-
-       /*
-        * We fall out of the loop only upon failing to read data.
-        * conn->errorMessage has been set by pqWait or pqReadData. We want to
-        * append it to any already-received error message.
-        */
-       pqSaveErrorResult(conn);
-       return pqPrepareAsyncResult(conn);
-}
-
-
 /*
  * Construct startup packet
  *
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 7a32c14de5e..980ef5374b5 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -768,11 +768,6 @@ 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);
 extern int     pqEndcopy3(PGconn *conn);
-extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid,
-                                                                int 
*result_buf, int buf_size,
-                                                                int 
*actual_result_len,
-                                                                int 
result_is_int,
-                                                                const 
PQArgBlock *args, int nargs);
 
 /* === in fe-cancel.c === */
 
-- 
2.50.1 (Apple Git-155)

Reply via email to