agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feedfuture of PQfn()
20+ messages / 5 participants
[nested] [flat]
* future of PQfn()
@ 2026-05-26 16:05 Nathan Bossart <nathandbossart@gmail.com>
0 siblings, 3 replies; 20+ messages in thread
From: Nathan Bossart @ 2026-05-26 16:05 UTC (permalink / raw)
To: pgsql-hackers
PQfn() was marked "somewhat obsolete" in commit efc3a25bb0 (2003), and was
later marked "unsafe" in commit bd48114937 (2026). I looked around for
third-party code that uses this interface but found none, and the only
internal usage is for the frontend LO interface. As part of the latter
commit, a special PQnfn() function was added for use by the fronend LO
functions, but this was not exported by libpq.
Given the above, I'd like to propose retiring PQfn() in v20. Since it's an
exported symbol, we can't just delete the code, but we could have it
unconditionally error. Assuming folks are okay with that, I'm wondering
what we should do with the relevant documentation. Should we leave a stub
with a note about its removal, or should we just wipe all mentions? I'm
currently leaning towards leaving a note, but I could see the argument
that's not even worth doing given the lack of uptake.
The other question is what to do with the frontend LO code. The simplest
thing we can do is to leave PQnfn() around as an internal function that is
only used by this interface. Alternatively, we could take our own advice
and used a prepared statement with binary transmission of params/results,
but that has two key problems: 1) potential name collisions with
user-created prepared statements and 2) breakage after DISCARD/DEALLOCATE,
which I haven't come up with a good way to deal with. Another approach we
could take is to just send the query via PQexecParams(), but a simple test
(creating and unlinking 10K LOs) showed a ~41% slowdown compared to HEAD.
So, I guess we'll need to keep PQnfn() around for now...
Thoughts?
--
nathan
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-26 16:53 Christoph Berg <myon@debian.org>
parent: Nathan Bossart <nathandbossart@gmail.com>
2 siblings, 1 reply; 20+ messages in thread
From: Christoph Berg @ 2026-05-26 16:53 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: pgsql-hackers
Re: Nathan Bossart
> PQfn() was marked "somewhat obsolete" in commit efc3a25bb0 (2003), and was
> later marked "unsafe" in commit bd48114937 (2026). I looked around for
> third-party code that uses this interface but found none
I found some references via Debian's codesearch:
https://sources.debian.org/src/fpc/3.2.2+dfsg-50/fpcsrc/packages/postgres/src/postgres3dyn.pp?hl=141...
PQfn : function (conn:PPGconn; fnid:longint; result_buf:Plongint; result_len:Plongint; result_is_int:longint;args:PPQArgBlock; nargs:longint):PPGresult;cdecl;
{ Accessor functions for PGresult objects }
https://sources.debian.org/src/clisp/1:2.49.20250504.gitf662209-2/modules/postgresql/postgresql.lisp...
;; "Fast path" interface --- not really recommended for application use
(def-call-out PQfn (:return-type PGresult)
(:arguments (conn PGconn) (fnid int) (result_buf (c-ptr int) :out)
(result_len (c-ptr int) :out) (result_is_int int)
(args (c-array-ptr PQArgBlock)) ; at least nargs
(nargs int)))
https://sources.debian.org/src/rust-pq-sys/0.7.5-1/src/bindings_linux_32.rs?hl=745#L745
unsafe extern "C" {
pub fn PQfn(
conn: *mut PGconn,
fnid: ::std::os::raw::c_int,
result_buf: *mut ::std::os::raw::c_int,
result_len: *mut ::std::os::raw::c_int,
result_is_int: ::std::os::raw::c_int,
args: *const PQArgBlock,
nargs: ::std::os::raw::c_int,
) -> *mut PGresult;
}
https://sources.debian.org/src/vala/0.56.19-1/vapi/libpq.vapi?hl=349#L349
[CCode (cname = "PQfn")]
public Result fn (int fnid, [CCode (array_length = false)] int[] result_buf, out int result_len, int result_is_int, ArgBlock[] args);
https://sources.debian.org/src/libdbi-drivers/0.9.0-13/drivers/pgsql/dbd_pgsql.h?hl=650#L650
#define PGSQL_CUSTOM_FUNCTIONS { \
...
"PQfn", \
All seem to be libpq wrappers.
Then there is a handful of packages with a copy of libpq-fe.h that
includes the function declaration.
... plus two other mentioning PQfn as "unimplemented".
> Given the above, I'd like to propose retiring PQfn() in v20. Since it's an
> exported symbol, we can't just delete the code, but we could have it
> unconditionally error.
Keeping the symbol but making it return an error seems ok from the
above I think.
Christoph
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-26 16:55 Christoph Berg <myon@debian.org>
parent: Christoph Berg <myon@debian.org>
0 siblings, 0 replies; 20+ messages in thread
From: Christoph Berg @ 2026-05-26 16:55 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; pgsql-hackers
Re: To Nathan Bossart
> I found some references via Debian's codesearch:
I meant to include the URL for that:
https://codesearch.debian.net/search?q=PQfn%5Cb&literal=0
PQnfn has no hits outside of postgresql-18.
Christoph
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-26 17:42 Jacob Champion <jacob.champion@enterprisedb.com>
parent: Nathan Bossart <nathandbossart@gmail.com>
2 siblings, 1 reply; 20+ messages in thread
From: Jacob Champion @ 2026-05-26 17:42 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: pgsql-hackers
On Tue, May 26, 2026 at 9:05 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
> Given the above, I'd like to propose retiring PQfn() in v20. Since it's an
> exported symbol, we can't just delete the code, but we could have it
> unconditionally error.
That seems reasonable to me. (We may want to exercise the
PqMsg_FunctionCall message more explicitly in our test suite at the
same time we do that...)
> Assuming folks are okay with that, I'm wondering
> what we should do with the relevant documentation. Should we leave a stub
> with a note about its removal, or should we just wipe all mentions? I'm
> currently leaning towards leaving a note, but I could see the argument
> that's not even worth doing given the lack of uptake.
I think we can probably wipe it out, personally.
> The other question is what to do with the frontend LO code. The simplest
> thing we can do is to leave PQnfn() around as an internal function that is
> only used by this interface. Alternatively, we could take our own advice
> and used a prepared statement with binary transmission of params/results,
> but that has two key problems: 1) potential name collisions with
> user-created prepared statements and 2) breakage after DISCARD/DEALLOCATE,
> which I haven't come up with a good way to deal with. Another approach we
> could take is to just send the query via PQexecParams(), but a simple test
> (creating and unlinking 10K LOs) showed a ~41% slowdown compared to HEAD.
> So, I guess we'll need to keep PQnfn() around for now...
Short-term, keeping it around seems fine.
Long-term, it doesn't feel great that the alternatives we tell other
people to use are... worse. Surely other clients of libpq run into the
layering violation problem with prepared statements, as well?
--Jacob
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-26 19:55 Nathan Bossart <nathandbossart@gmail.com>
parent: Jacob Champion <jacob.champion@enterprisedb.com>
0 siblings, 1 reply; 20+ messages in thread
From: Nathan Bossart @ 2026-05-26 19:55 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: pgsql-hackers
On Tue, May 26, 2026 at 10:42:47AM -0700, Jacob Champion wrote:
> Short-term, keeping it around seems fine.
>
> Long-term, it doesn't feel great that the alternatives we tell other
> people to use are... worse. Surely other clients of libpq run into the
> layering violation problem with prepared statements, as well?
Yup. Here's a related note in JDBC:
https://github.com/pgjdbc/pgjdbc/blob/cf2d89ec/docs/content/documentation/server-prepare.md?plain=1#...
I wonder how difficult it would be to teach the protocol to advise clients
when prepared statements are deallocated...
FWIW I'm less concerned about the name collision problem. I was thinking
we could just document that libpq manages statements with a prefix like
"libpq_internal_". Any problems in that area seem likely to be intentional
breakage that we needn't worry about.
--
nathan
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-26 21:36 Jelte Fennema-Nio <postgres@jeltef.nl>
parent: Nathan Bossart <nathandbossart@gmail.com>
2 siblings, 1 reply; 20+ messages in thread
From: Jelte Fennema-Nio @ 2026-05-26 21:36 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: pgsql-hackers
On Tue, 26 May 2026 at 18:05, Nathan Bossart <nathandbossart@gmail.com>
wrote:
> Another approach we
> could take is to just send the query via PQexecParams(), but a simple test
> (creating and unlinking 10K LOs) showed a ~41% slowdown compared to HEAD.
> So, I guess we'll need to keep PQnfn() around for now...
>
> Thoughts?
I had a small WIP patch (fully AI generated and not yet vetted by me)
lying around for unrelated reasons to make the extended protocol perform
closer to the simple protocol with pgbench --select-only by using
CreateOneShotCachedPlan to create the plan for unnamed prepared
statements (see attached).
Could you share the simple LO test you were running here and/or rerun it
with this patch applied? I'd love to know if the patch reduces the
slowdown significantly, or if something else is the bottleneck.
Attachments:
[text/x-patch] v1-0001-Speed-up-extended-protocol-by-using-oneshot-cache.patch (4.8K, ../../DISXKTNN3JUM.30KZW8W94LPVO@jeltef.nl/2-v1-0001-Speed-up-extended-protocol-by-using-oneshot-cache.patch)
download | inline diff:
From 3586deb8bbcc25ac0c4de5c7ef6421ffddc93839 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <postgres@jeltef.nl>
Date: Tue, 26 May 2026 16:07:57 +0200
Subject: [PATCH v1] Speed up extended protocol by using oneshot cached plan
DISCLAIMER: Fully written by AI. I still need to check if the code is
fully correct. The approach seems sensible though, and meson test
passes on my machine.
On my laptop on master I get much lower perf with the extended protocol
than the simple protocol in a basic pgbench --select-only test:
pgbench -i
pgbench --select-only -T 10 --protocol simple
pgbench --select-only -T 10 --protocol extended
For simple I get ~56k TPS and for extended I only get ~49k TPS
With this change I get ~53k TPS with extended so a ~8% improvement.
---
src/backend/tcop/postgres.c | 57 ++++++++++++++++++++++++++++++++-----
1 file changed, 50 insertions(+), 7 deletions(-)
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index dbef734a93f..3ab9d7ab3b2 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1513,10 +1513,27 @@ exec_parse_message(const char *query_string, /* string to execute */
/*
* Create the CachedPlanSource before we do parse analysis, since it
- * needs to see the unmodified raw parse tree.
+ * needs to see the unmodified raw parse tree. For unnamed statements
+ * use a one-shot plan: this skips the deep copy of the raw parse
+ * tree, the deep copy of the rewritten query tree in BuildCachedPlan,
+ * and the SaveCachedPlan reparent into CacheMemoryContext. The
+ * trade-off is no invalidation support, which is acceptable for the
+ * common case where the unnamed statement is Parse+Bind+Execute'd in
+ * a single transaction.
*/
- psrc = CreateCachedPlan(raw_parse_tree, query_string,
- CreateCommandTag(raw_parse_tree->stmt));
+ if (is_named)
+ psrc = CreateCachedPlan(raw_parse_tree, query_string,
+ CreateCommandTag(raw_parse_tree->stmt));
+ else
+ {
+ /*
+ * CreateOneShotCachedPlan doesn't copy query_string, so we must
+ * stash it in unnamed_stmt_context (CurrentMemoryContext) so it
+ * outlives the MessageContext reset between Parse and Bind.
+ */
+ psrc = CreateOneShotCachedPlan(raw_parse_tree, pstrdup(query_string),
+ CreateCommandTag(raw_parse_tree->stmt));
+ }
/*
* Set up a snapshot if parse analysis will need one.
@@ -1546,8 +1563,12 @@ exec_parse_message(const char *query_string, /* string to execute */
{
/* Empty input string. This is legal. */
raw_parse_tree = NULL;
- psrc = CreateCachedPlan(raw_parse_tree, query_string,
- CMDTAG_UNKNOWN);
+ if (is_named)
+ psrc = CreateCachedPlan(raw_parse_tree, query_string,
+ CMDTAG_UNKNOWN);
+ else
+ psrc = CreateOneShotCachedPlan(raw_parse_tree, pstrdup(query_string),
+ CMDTAG_UNKNOWN);
querytree_list = NIL;
}
@@ -1584,9 +1605,13 @@ exec_parse_message(const char *query_string, /* string to execute */
else
{
/*
- * We just save the CachedPlanSource into unnamed_stmt_psrc.
+ * For unnamed statements we use a one-shot CachedPlanSource, so we
+ * can't SaveCachedPlan it. Instead reparent its context (which is
+ * unnamed_stmt_context) under CacheMemoryContext so that it survives
+ * the MessageContext reset between Parse and the following Bind.
+ * drop_unnamed_stmt() will MemoryContextDelete it explicitly.
*/
- SaveCachedPlan(psrc);
+ MemoryContextSetParent(psrc->context, CacheMemoryContext);
unnamed_stmt_psrc = psrc;
}
@@ -2028,8 +2053,18 @@ exec_bind_message(StringInfo input_message)
* Obtain a plan from the CachedPlanSource. Any cruft from (re)planning
* will be generated in MessageContext. The plan refcount will be
* assigned to the Portal, so it will be released at portal destruction.
+ *
+ * For one-shot plans (used for unnamed prepared statements),
+ * BuildCachedPlan does not allocate a dedicated plan_context; the plan
+ * ends up in CurrentMemoryContext. Switch to the portal's context for
+ * the call so the plan survives MessageContext resets between Bind and
+ * Execute (e.g. in pipelined extended-protocol traffic).
*/
+ if (psrc->is_oneshot)
+ MemoryContextSwitchTo(portal->portalContext);
cplan = GetCachedPlan(psrc, params, NULL, NULL);
+ if (psrc->is_oneshot)
+ MemoryContextSwitchTo(MessageContext);
/*
* Now we can define the portal.
@@ -2906,8 +2941,16 @@ drop_unnamed_stmt(void)
{
CachedPlanSource *psrc = unnamed_stmt_psrc;
+ /*
+ * For one-shot plans DropCachedPlan does not free the context (it's
+ * owned by the caller), so capture it and delete it ourselves.
+ */
+ MemoryContext oneshot_ctx = psrc->is_oneshot ? psrc->context : NULL;
+
unnamed_stmt_psrc = NULL;
DropCachedPlan(psrc);
+ if (oneshot_ctx)
+ MemoryContextDelete(oneshot_ctx);
}
}
base-commit: 6aa26be288fa811270dfc1e39c015c23a97688b4
--
2.54.0
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-27 18:23 Nathan Bossart <nathandbossart@gmail.com>
parent: Jelte Fennema-Nio <postgres@jeltef.nl>
0 siblings, 0 replies; 20+ messages in thread
From: Nathan Bossart @ 2026-05-27 18:23 UTC (permalink / raw)
To: Jelte Fennema-Nio <postgres@jeltef.nl>; +Cc: pgsql-hackers
On Tue, May 26, 2026 at 11:36:49PM +0200, Jelte Fennema-Nio wrote:
> Could you share the simple LO test you were running here and/or rerun it
> with this patch applied? I'd love to know if the patch reduces the
> slowdown significantly, or if something else is the bottleneck.
The test is just:
int main()
{
PCconn *conn = PQsetdb(NULL, NULL, NULL, NULL, "postgres");
for (int i = 0; i < 1000000; i++)
lo_create(conn, i);
for (int i = 0; i < 1000000; i++)
lo_unlink(conn, i);
}
Before applying your patch -> 0.457 seconds (best of ~10)
After applying your patch -> 0.444 seconds (best of ~10)
For reference, HEAD runs this test in 0.319 seconds.
I've attached my work-in-progress patches here, in case you're interested.
0001 switches the frontend LO interface to use prepared statements, and
0002 removes PQfn() entirely. 0003 applies on top of those two and
switches the frontend LO interface to use PQexecParams() instead.
--
nathan
From af1f2430f55c753549669d938f70ca58baa48f3e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 22 May 2026 10:40:38 -0700
Subject: [PATCH v1 1/3] stop using PQfn() in libpq's LO interface
---
src/interfaces/libpq/fe-connect.c | 5 +-
src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++----------------
src/interfaces/libpq/libpq-int.h | 20 +-
3 files changed, 299 insertions(+), 382 deletions(-)
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 4272d386e64..01c9735f276 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -692,10 +692,7 @@ 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;
/* 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..41573c22c80 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -41,10 +41,101 @@
#define LO_BUFSIZE 8192
+typedef struct PGlobjfuncs
+{
+ char *name;
+ char *args;
+ int nargs;
+ int version;
+} PGlobjfuncs;
+
+static const PGlobjfuncs lobjfuncs[] =
+{
+ {
+ "lo_open",
+ "$1::pg_catalog.oid, $2::pg_catalog.int4",
+ 2
+ },
+ {
+ "lo_close",
+ "$1::pg_catalog.int4",
+ 1
+ },
+ {
+ "lo_creat",
+ "$1::pg_catalog.int4",
+ 1
+ },
+ {
+ "lo_create",
+ "$1::pg_catalog.oid",
+ 1,
+ 80100
+ },
+ {
+ "lo_unlink",
+ "$1::pg_catalog.oid",
+ 1
+ },
+ {
+ "lo_lseek",
+ "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4",
+ 3
+ },
+ {
+ "lo_lseek64",
+ "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4",
+ 3,
+ 90300
+ },
+ {
+ "lo_tell",
+ "$1::pg_catalog.int4",
+ 1
+ },
+ {
+ "lo_tell64",
+ "$1::pg_catalog.int4",
+ 1,
+ 90300
+ },
+ {
+ "lo_truncate",
+ "$1::pg_catalog.int4, $2::pg_catalog.int4",
+ 2,
+ 80300
+ },
+ {
+ "lo_truncate64",
+ "$1::pg_catalog.int4, $2::pg_catalog.int8",
+ 2,
+ 90300
+ },
+ {
+ "loread",
+ "$1::pg_catalog.int4, $2::pg_catalog.int4",
+ 2
+ },
+ {
+ "lowrite",
+ "$1::pg_catalog.int4, $2::pg_catalog.bytea",
+ 2
+ }
+};
+
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);
+}
/*
* lo_open
@@ -57,26 +148,28 @@ 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 argFormats[] = {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;
+
+ mode = pg_hton32(mode);
+ argv[1] = (char *) &mode;
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = mode;
+ res = PQexecPrepared(conn, "libpq_internal_lo_open", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +188,26 @@ 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 argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_close", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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 +226,18 @@ lo_close(PGconn *conn, int fd)
int
lo_truncate(PGconn *conn, int fd, size_t len)
{
- PQArgBlock argv[2];
+ char *argv[2];
+ int argLens[] = {4, 4};
+ int argFormats[] = {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 +257,20 @@ 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;
+ len = pg_hton32(len);
+ argv[1] = (char *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
- &retval, &result_len, 1, argv, 2);
+ res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +289,36 @@ 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 argFormats[] = {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;
+ fd = pg_hton32(fd);
+ argv[0] = (char *) &fd;
- len = lo_hton64(len);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &len;
+ len = pg_hton64(len);
+ argv[1] = (char *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64,
- &retval, &result_len, 1, argv, 2);
+ res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +338,10 @@ 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 argLens[] = {4, 4};
+ int argFormats[] = {1, 1};
PGresult *res;
- int result_len;
if (lo_initialize(conn) < 0)
return -1;
@@ -263,18 +358,24 @@ 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;
+
+ len = pg_hton32(len);
+ argv[1] = (char *) &len;
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ res = PQexecPrepared(conn, "libpq_internal_loread", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- res = PQnfn(conn, conn->lobjfuncs->fn_lo_read,
- (void *) buf, len, &result_len, 0, argv, 2);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +395,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 argFormats[] = {1, 1};
PGresult *res;
- int result_len;
int retval;
if (lo_initialize(conn) < 0)
@@ -314,20 +416,19 @@ 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] = unconstify(char *, buf);
- argv[1].isint = 0;
- argv[1].len = (int) len;
- argv[1].u.ptr = (int *) unconstify(char *, buf);
+ res = PQexecPrepared(conn, "libpq_internal_lowrite", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
- &retval, &result_len, 1, argv, 2);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +444,32 @@ 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 argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lseek", 3,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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 +485,38 @@ 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 argFormats[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 = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3,
+ (const char *const *) argv, argLens, argFormats, 1);
+ if (lo_result_is_valid(res, sizeof(retval)))
{
+ memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh32(retval);
}
else
{
@@ -437,23 +536,26 @@ 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 argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_creat", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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,33 @@ lo_creat(PGconn *conn, int mode)
Oid
lo_create(PGconn *conn, Oid lobjId)
{
- PQArgBlock argv[1];
+ char *argv[1];
+ int argLens[] = {4};
+ int argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_create", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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 +619,25 @@ int
lo_tell(PGconn *conn, int fd)
{
int retval;
- PQArgBlock argv[1];
+ char *argv[1];
+ int argLens[] = {4};
+ int argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_tell", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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 +654,32 @@ int64_t
lo_tell64(PGconn *conn, int fd)
{
int64 retval;
- PQArgBlock argv[1];
+ char *argv[1];
+ int argLens[] = {4};
+ int argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
- 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)
+ 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 +696,26 @@ lo_tell64(PGconn *conn, int fd)
int
lo_unlink(PGconn *conn, Oid lobjId)
{
- PQArgBlock argv[1];
+ char *argv[1];
+ int argLens[] = {4};
+ int argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
- &retval, &result_len, 1, argv, 1);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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
{
@@ -836,19 +946,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;
@@ -856,209 +959,44 @@ lo_initialize(PGconn *conn)
/* Since this is the beginning of a query cycle, reset the error state */
pqClearConnErrorState(conn);
- /* Nothing else to do if we already collected info */
- if (conn->lobjfuncs != NULL)
+ /* Nothing else to do if we already prepared the statements */
+ 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)
+ for (int i = 0; i < lengthof(lobjfuncs); i++)
{
- libpq_append_conn_error(conn, "out of memory");
- return -1;
- }
- MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs));
+ PGresult *res;
+ ExecStatusType status;
+ PQExpBufferData pname;
+ PQExpBufferData pquery;
- /*
- * 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')";
+ if (PQserverVersion(conn) < lobjfuncs[i].version)
+ continue;
- res = PQexec(conn, query);
- if (res == NULL)
- {
- free(lobjfuncs);
- return -1;
- }
+ initPQExpBuffer(&pname);
+ initPQExpBuffer(&pquery);
- if (res->resultStatus != PGRES_TUPLES_OK)
- {
- free(lobjfuncs);
- PQclear(res);
- libpq_append_conn_error(conn, "query to initialize large object functions did not return data");
- return -1;
- }
+ appendPQExpBuffer(&pname, "libpq_internal_%s",
+ lobjfuncs[i].name);
+ appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)",
+ lobjfuncs[i].name, lobjfuncs[i].args);
- /*
- * 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;
- }
+ res = PQprepare(conn, pname.data, pquery.data,
+ lobjfuncs[i].nargs, NULL);
+ status = PQresultStatus(res);
+ if (res)
+ PQclear(res);
- PQclear(res);
+ termPQExpBuffer(&pname);
+ termPQExpBuffer(&pquery);
- /*
- * 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);
+ if (status == PGRES_COMMAND_OK)
+ continue;
+
+ libpq_append_conn_error(conn, "query to prepare large object statements failed");
return -1;
}
- /*
- * 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 461b39620c3..5248e895bc0 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.
@@ -562,7 +544,7 @@ 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 */
pg_prng_state prng_state; /* prng state for load balancing connections */
--
2.50.1 (Apple Git-155)
From 4a8c81ac2a700dfebaa944e0de52974ea91f477a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 22 May 2026 12:00:50 -0700
Subject: [PATCH v1 2/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 | 242 ----------------------------
src/interfaces/libpq/libpq-int.h | 5 -
6 files changed, 12 insertions(+), 415 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 840e018cd18..08910a459c0 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -2196,248 +2196,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;
- 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 5248e895bc0..9dd0f42b6b7 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -764,11 +764,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)
From e5823beb79599bf17ba45f19fcca54c75f85c82d Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 26 May 2026 11:12:49 -0500
Subject: [PATCH v1 3/3] LO frontend PQexecParams
---
src/interfaces/libpq/fe-connect.c | 1 -
src/interfaces/libpq/fe-lobj.c | 236 ++++--------------------------
src/interfaces/libpq/libpq-int.h | 1 -
3 files changed, 26 insertions(+), 212 deletions(-)
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 01c9735f276..9f6aa01dc0e 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -692,7 +692,6 @@ pqDropServerData(PGconn *conn)
conn->in_hot_standby = PG_BOOL_UNKNOWN;
conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
conn->sversion = 0;
- conn->lobjprepared = 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 41573c22c80..17c1d699a48 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -41,89 +41,6 @@
#define LO_BUFSIZE 8192
-typedef struct PGlobjfuncs
-{
- char *name;
- char *args;
- int nargs;
- int version;
-} PGlobjfuncs;
-
-static const PGlobjfuncs lobjfuncs[] =
-{
- {
- "lo_open",
- "$1::pg_catalog.oid, $2::pg_catalog.int4",
- 2
- },
- {
- "lo_close",
- "$1::pg_catalog.int4",
- 1
- },
- {
- "lo_creat",
- "$1::pg_catalog.int4",
- 1
- },
- {
- "lo_create",
- "$1::pg_catalog.oid",
- 1,
- 80100
- },
- {
- "lo_unlink",
- "$1::pg_catalog.oid",
- 1
- },
- {
- "lo_lseek",
- "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4",
- 3
- },
- {
- "lo_lseek64",
- "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4",
- 3,
- 90300
- },
- {
- "lo_tell",
- "$1::pg_catalog.int4",
- 1
- },
- {
- "lo_tell64",
- "$1::pg_catalog.int4",
- 1,
- 90300
- },
- {
- "lo_truncate",
- "$1::pg_catalog.int4, $2::pg_catalog.int4",
- 2,
- 80300
- },
- {
- "lo_truncate64",
- "$1::pg_catalog.int4, $2::pg_catalog.int8",
- 2,
- 90300
- },
- {
- "loread",
- "$1::pg_catalog.int4, $2::pg_catalog.int4",
- 2
- },
- {
- "lowrite",
- "$1::pg_catalog.int4, $2::pg_catalog.bytea",
- 2
- }
-};
-
-static int lo_initialize(PGconn *conn);
static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid);
static bool
@@ -153,17 +70,14 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
int argFormats[] = {1, 1};
PGresult *res;
- if (lo_initialize(conn) < 0)
- return -1;
-
lobjId = pg_hton32(lobjId);
argv[0] = (char *) &lobjId;
mode = pg_hton32(mode);
argv[1] = (char *) &mode;
- res = PQexecPrepared(conn, "libpq_internal_lo_open", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(fd)))
{
@@ -194,14 +108,11 @@ lo_close(PGconn *conn, int fd)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
fd = pg_hton32(fd);
argv[0] = (char *) &fd;
- res = PQexecPrepared(conn, "libpq_internal_lo_close", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_close($1::pg_catalog.int4)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -232,9 +143,6 @@ lo_truncate(PGconn *conn, int fd, size_t len)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
if (PQserverVersion(conn) < 80300)
{
libpq_append_conn_error(conn, "server does not support function \"%s\"",
@@ -263,8 +171,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
len = pg_hton32(len);
argv[1] = (char *) &len;
- res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -295,9 +203,6 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
if (PQserverVersion(conn) < 90300)
{
libpq_append_conn_error(conn, "server does not support function \"%s\"",
@@ -311,8 +216,8 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
len = pg_hton64(len);
argv[1] = (char *) &len;
- res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -343,9 +248,6 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len)
int argFormats[] = {1, 1};
PGresult *res;
- if (lo_initialize(conn) < 0)
- return -1;
-
/*
* Long ago, somebody thought it'd be a good idea to declare this function
* as taking size_t ... but the underlying backend function only accepts a
@@ -364,8 +266,8 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len)
len = pg_hton32(len);
argv[1] = (char *) &len;
- res = PQexecPrepared(conn, "libpq_internal_loread", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, -1))
{
@@ -401,9 +303,6 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
/*
* Long ago, somebody thought it'd be a good idea to declare this function
* as taking size_t ... but the underlying backend function only accepts a
@@ -421,8 +320,8 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
argv[1] = unconstify(char *, buf);
- res = PQexecPrepared(conn, "libpq_internal_lowrite", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -450,9 +349,6 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
fd = pg_hton32(fd);
argv[0] = (char *) &fd;
@@ -462,8 +358,8 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
whence = pg_hton32(whence);
argv[2] = (char *) &whence;
- res = PQexecPrepared(conn, "libpq_internal_lseek", 3,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)", 3, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -491,9 +387,6 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
PGresult *res;
int64 retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
if (PQserverVersion(conn) < 90300)
{
libpq_append_conn_error(conn, "server does not support function \"%s\"",
@@ -510,8 +403,8 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
whence = pg_hton32(whence);
argv[2] = (char *) &whence;
- res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", 3, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
@@ -542,14 +435,11 @@ lo_creat(PGconn *conn, int mode)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return InvalidOid;
-
mode = pg_hton32(mode);
argv[0] = (char *) &mode;
- res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -581,9 +471,6 @@ lo_create(PGconn *conn, Oid lobjId)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return InvalidOid;
-
if (PQserverVersion(conn) < 80100)
{
libpq_append_conn_error(conn, "server does not support function \"%s\"",
@@ -594,8 +481,8 @@ lo_create(PGconn *conn, Oid lobjId)
lobjId = pg_hton32(lobjId);
argv[0] = (char *) &lobjId;
- res = PQexecPrepared(conn, "libpq_internal_lo_create", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -624,14 +511,11 @@ lo_tell(PGconn *conn, int fd)
int argFormats[] = {1};
PGresult *res;
- if (lo_initialize(conn) < 0)
- return -1;
-
fd = pg_hton32(fd);
argv[0] = (char *) &fd;
- res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -659,9 +543,6 @@ lo_tell64(PGconn *conn, int fd)
int argFormats[] = {1};
PGresult *res;
- if (lo_initialize(conn) < 0)
- return -1;
-
if (PQserverVersion(conn) < 90300)
{
libpq_append_conn_error(conn, "server does not support functions \"%s\"",
@@ -672,8 +553,8 @@ lo_tell64(PGconn *conn, int fd)
fd = pg_hton32(fd);
argv[0] = (char *) &fd;
- res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -702,14 +583,11 @@ lo_unlink(PGconn *conn, Oid lobjId)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
lobjId = pg_hton32(lobjId);
argv[0] = (char *) &lobjId;
- res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -938,65 +816,3 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename)
return result;
}
-
-
-/*
- * lo_initialize
- *
- * 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 prepared statements for all the
- * functions that are required for large object operations.
- */
-static int
-lo_initialize(PGconn *conn)
-{
- /* Nothing we can do with no connection */
- if (conn == NULL)
- return -1;
-
- /* Since this is the beginning of a query cycle, reset the error state */
- pqClearConnErrorState(conn);
-
- /* Nothing else to do if we already prepared the statements */
- if (conn->lobjprepared)
- return 0;
-
- for (int i = 0; i < lengthof(lobjfuncs); i++)
- {
- PGresult *res;
- ExecStatusType status;
- PQExpBufferData pname;
- PQExpBufferData pquery;
-
- if (PQserverVersion(conn) < lobjfuncs[i].version)
- continue;
-
- initPQExpBuffer(&pname);
- initPQExpBuffer(&pquery);
-
- appendPQExpBuffer(&pname, "libpq_internal_%s",
- lobjfuncs[i].name);
- appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)",
- lobjfuncs[i].name, lobjfuncs[i].args);
-
- res = PQprepare(conn, pname.data, pquery.data,
- lobjfuncs[i].nargs, NULL);
- status = PQresultStatus(res);
- if (res)
- PQclear(res);
-
- termPQExpBuffer(&pname);
- termPQExpBuffer(&pquery);
-
- if (status == PGRES_COMMAND_OK)
- continue;
-
- libpq_append_conn_error(conn, "query to prepare large object statements failed");
- return -1;
- }
-
- conn->lobjprepared = true;
- return 0;
-}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9dd0f42b6b7..cf968f1519b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -544,7 +544,6 @@ struct pg_conn
PGTernaryBool in_hot_standby; /* in_hot_standby */
PGVerbosity verbosity; /* error/notice message verbosity */
PGContextVisibility show_context; /* whether to show CONTEXT field */
- bool lobjprepared; /* whether LO statements have been prepared */
pg_prng_state prng_state; /* prng state for load balancing connections */
--
2.50.1 (Apple Git-155)
Attachments:
[text/plain] v1-0001-stop-using-PQfn-in-libpq-s-LO-interface.patch (25.7K, ../../ahc2iM2KRMyOWBPS@nathan/2-v1-0001-stop-using-PQfn-in-libpq-s-LO-interface.patch)
download | inline diff:
From af1f2430f55c753549669d938f70ca58baa48f3e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 22 May 2026 10:40:38 -0700
Subject: [PATCH v1 1/3] stop using PQfn() in libpq's LO interface
---
src/interfaces/libpq/fe-connect.c | 5 +-
src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++----------------
src/interfaces/libpq/libpq-int.h | 20 +-
3 files changed, 299 insertions(+), 382 deletions(-)
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 4272d386e64..01c9735f276 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -692,10 +692,7 @@ 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;
/* 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..41573c22c80 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -41,10 +41,101 @@
#define LO_BUFSIZE 8192
+typedef struct PGlobjfuncs
+{
+ char *name;
+ char *args;
+ int nargs;
+ int version;
+} PGlobjfuncs;
+
+static const PGlobjfuncs lobjfuncs[] =
+{
+ {
+ "lo_open",
+ "$1::pg_catalog.oid, $2::pg_catalog.int4",
+ 2
+ },
+ {
+ "lo_close",
+ "$1::pg_catalog.int4",
+ 1
+ },
+ {
+ "lo_creat",
+ "$1::pg_catalog.int4",
+ 1
+ },
+ {
+ "lo_create",
+ "$1::pg_catalog.oid",
+ 1,
+ 80100
+ },
+ {
+ "lo_unlink",
+ "$1::pg_catalog.oid",
+ 1
+ },
+ {
+ "lo_lseek",
+ "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4",
+ 3
+ },
+ {
+ "lo_lseek64",
+ "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4",
+ 3,
+ 90300
+ },
+ {
+ "lo_tell",
+ "$1::pg_catalog.int4",
+ 1
+ },
+ {
+ "lo_tell64",
+ "$1::pg_catalog.int4",
+ 1,
+ 90300
+ },
+ {
+ "lo_truncate",
+ "$1::pg_catalog.int4, $2::pg_catalog.int4",
+ 2,
+ 80300
+ },
+ {
+ "lo_truncate64",
+ "$1::pg_catalog.int4, $2::pg_catalog.int8",
+ 2,
+ 90300
+ },
+ {
+ "loread",
+ "$1::pg_catalog.int4, $2::pg_catalog.int4",
+ 2
+ },
+ {
+ "lowrite",
+ "$1::pg_catalog.int4, $2::pg_catalog.bytea",
+ 2
+ }
+};
+
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);
+}
/*
* lo_open
@@ -57,26 +148,28 @@ 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 argFormats[] = {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;
+
+ mode = pg_hton32(mode);
+ argv[1] = (char *) &mode;
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = mode;
+ res = PQexecPrepared(conn, "libpq_internal_lo_open", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +188,26 @@ 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 argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_close", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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 +226,18 @@ lo_close(PGconn *conn, int fd)
int
lo_truncate(PGconn *conn, int fd, size_t len)
{
- PQArgBlock argv[2];
+ char *argv[2];
+ int argLens[] = {4, 4};
+ int argFormats[] = {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 +257,20 @@ 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;
+ len = pg_hton32(len);
+ argv[1] = (char *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
- &retval, &result_len, 1, argv, 2);
+ res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +289,36 @@ 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 argFormats[] = {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;
+ fd = pg_hton32(fd);
+ argv[0] = (char *) &fd;
- len = lo_hton64(len);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &len;
+ len = pg_hton64(len);
+ argv[1] = (char *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64,
- &retval, &result_len, 1, argv, 2);
+ res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +338,10 @@ 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 argLens[] = {4, 4};
+ int argFormats[] = {1, 1};
PGresult *res;
- int result_len;
if (lo_initialize(conn) < 0)
return -1;
@@ -263,18 +358,24 @@ 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;
+
+ len = pg_hton32(len);
+ argv[1] = (char *) &len;
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ res = PQexecPrepared(conn, "libpq_internal_loread", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- res = PQnfn(conn, conn->lobjfuncs->fn_lo_read,
- (void *) buf, len, &result_len, 0, argv, 2);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +395,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 argFormats[] = {1, 1};
PGresult *res;
- int result_len;
int retval;
if (lo_initialize(conn) < 0)
@@ -314,20 +416,19 @@ 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] = unconstify(char *, buf);
- argv[1].isint = 0;
- argv[1].len = (int) len;
- argv[1].u.ptr = (int *) unconstify(char *, buf);
+ res = PQexecPrepared(conn, "libpq_internal_lowrite", 2,
+ (const char *const *) argv, argLens, argFormats, 1);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
- &retval, &result_len, 1, argv, 2);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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 +444,32 @@ 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 argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lseek", 3,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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 +485,38 @@ 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 argFormats[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 = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3,
+ (const char *const *) argv, argLens, argFormats, 1);
+ if (lo_result_is_valid(res, sizeof(retval)))
{
+ memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh32(retval);
}
else
{
@@ -437,23 +536,26 @@ 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 argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_creat", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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,33 @@ lo_creat(PGconn *conn, int mode)
Oid
lo_create(PGconn *conn, Oid lobjId)
{
- PQArgBlock argv[1];
+ char *argv[1];
+ int argLens[] = {4};
+ int argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_create", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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 +619,25 @@ int
lo_tell(PGconn *conn, int fd)
{
int retval;
- PQArgBlock argv[1];
+ char *argv[1];
+ int argLens[] = {4};
+ int argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_tell", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
+
+ 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 +654,32 @@ int64_t
lo_tell64(PGconn *conn, int fd)
{
int64 retval;
- PQArgBlock argv[1];
+ char *argv[1];
+ int argLens[] = {4};
+ int argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
- 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)
+ 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 +696,26 @@ lo_tell64(PGconn *conn, int fd)
int
lo_unlink(PGconn *conn, Oid lobjId)
{
- PQArgBlock argv[1];
+ char *argv[1];
+ int argLens[] = {4};
+ int argFormats[] = {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 = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1,
+ (const char *const *) argv, argLens, argFormats, 1);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
- &retval, &result_len, 1, argv, 1);
- if (PQresultStatus(res) == PGRES_COMMAND_OK)
+ 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
{
@@ -836,19 +946,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;
@@ -856,209 +959,44 @@ lo_initialize(PGconn *conn)
/* Since this is the beginning of a query cycle, reset the error state */
pqClearConnErrorState(conn);
- /* Nothing else to do if we already collected info */
- if (conn->lobjfuncs != NULL)
+ /* Nothing else to do if we already prepared the statements */
+ 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)
+ for (int i = 0; i < lengthof(lobjfuncs); i++)
{
- libpq_append_conn_error(conn, "out of memory");
- return -1;
- }
- MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs));
+ PGresult *res;
+ ExecStatusType status;
+ PQExpBufferData pname;
+ PQExpBufferData pquery;
- /*
- * 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')";
+ if (PQserverVersion(conn) < lobjfuncs[i].version)
+ continue;
- res = PQexec(conn, query);
- if (res == NULL)
- {
- free(lobjfuncs);
- return -1;
- }
+ initPQExpBuffer(&pname);
+ initPQExpBuffer(&pquery);
- if (res->resultStatus != PGRES_TUPLES_OK)
- {
- free(lobjfuncs);
- PQclear(res);
- libpq_append_conn_error(conn, "query to initialize large object functions did not return data");
- return -1;
- }
+ appendPQExpBuffer(&pname, "libpq_internal_%s",
+ lobjfuncs[i].name);
+ appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)",
+ lobjfuncs[i].name, lobjfuncs[i].args);
- /*
- * 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;
- }
+ res = PQprepare(conn, pname.data, pquery.data,
+ lobjfuncs[i].nargs, NULL);
+ status = PQresultStatus(res);
+ if (res)
+ PQclear(res);
- PQclear(res);
+ termPQExpBuffer(&pname);
+ termPQExpBuffer(&pquery);
- /*
- * 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);
+ if (status == PGRES_COMMAND_OK)
+ continue;
+
+ libpq_append_conn_error(conn, "query to prepare large object statements failed");
return -1;
}
- /*
- * 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 461b39620c3..5248e895bc0 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.
@@ -562,7 +544,7 @@ 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 */
pg_prng_state prng_state; /* prng state for load balancing connections */
--
2.50.1 (Apple Git-155)
[text/plain] v1-0002-remove-support-for-PQfn.patch (17.0K, ../../ahc2iM2KRMyOWBPS@nathan/3-v1-0002-remove-support-for-PQfn.patch)
download | inline diff:
From 4a8c81ac2a700dfebaa944e0de52974ea91f477a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 22 May 2026 12:00:50 -0700
Subject: [PATCH v1 2/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 | 242 ----------------------------
src/interfaces/libpq/libpq-int.h | 5 -
6 files changed, 12 insertions(+), 415 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 840e018cd18..08910a459c0 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -2196,248 +2196,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;
- 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 5248e895bc0..9dd0f42b6b7 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -764,11 +764,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)
[text/plain] v1-0003-LO-frontend-PQexecParams.patch (13.0K, ../../ahc2iM2KRMyOWBPS@nathan/4-v1-0003-LO-frontend-PQexecParams.patch)
download | inline diff:
From e5823beb79599bf17ba45f19fcca54c75f85c82d Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 26 May 2026 11:12:49 -0500
Subject: [PATCH v1 3/3] LO frontend PQexecParams
---
src/interfaces/libpq/fe-connect.c | 1 -
src/interfaces/libpq/fe-lobj.c | 236 ++++--------------------------
src/interfaces/libpq/libpq-int.h | 1 -
3 files changed, 26 insertions(+), 212 deletions(-)
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 01c9735f276..9f6aa01dc0e 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -692,7 +692,6 @@ pqDropServerData(PGconn *conn)
conn->in_hot_standby = PG_BOOL_UNKNOWN;
conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
conn->sversion = 0;
- conn->lobjprepared = 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 41573c22c80..17c1d699a48 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -41,89 +41,6 @@
#define LO_BUFSIZE 8192
-typedef struct PGlobjfuncs
-{
- char *name;
- char *args;
- int nargs;
- int version;
-} PGlobjfuncs;
-
-static const PGlobjfuncs lobjfuncs[] =
-{
- {
- "lo_open",
- "$1::pg_catalog.oid, $2::pg_catalog.int4",
- 2
- },
- {
- "lo_close",
- "$1::pg_catalog.int4",
- 1
- },
- {
- "lo_creat",
- "$1::pg_catalog.int4",
- 1
- },
- {
- "lo_create",
- "$1::pg_catalog.oid",
- 1,
- 80100
- },
- {
- "lo_unlink",
- "$1::pg_catalog.oid",
- 1
- },
- {
- "lo_lseek",
- "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4",
- 3
- },
- {
- "lo_lseek64",
- "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4",
- 3,
- 90300
- },
- {
- "lo_tell",
- "$1::pg_catalog.int4",
- 1
- },
- {
- "lo_tell64",
- "$1::pg_catalog.int4",
- 1,
- 90300
- },
- {
- "lo_truncate",
- "$1::pg_catalog.int4, $2::pg_catalog.int4",
- 2,
- 80300
- },
- {
- "lo_truncate64",
- "$1::pg_catalog.int4, $2::pg_catalog.int8",
- 2,
- 90300
- },
- {
- "loread",
- "$1::pg_catalog.int4, $2::pg_catalog.int4",
- 2
- },
- {
- "lowrite",
- "$1::pg_catalog.int4, $2::pg_catalog.bytea",
- 2
- }
-};
-
-static int lo_initialize(PGconn *conn);
static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid);
static bool
@@ -153,17 +70,14 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
int argFormats[] = {1, 1};
PGresult *res;
- if (lo_initialize(conn) < 0)
- return -1;
-
lobjId = pg_hton32(lobjId);
argv[0] = (char *) &lobjId;
mode = pg_hton32(mode);
argv[1] = (char *) &mode;
- res = PQexecPrepared(conn, "libpq_internal_lo_open", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(fd)))
{
@@ -194,14 +108,11 @@ lo_close(PGconn *conn, int fd)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
fd = pg_hton32(fd);
argv[0] = (char *) &fd;
- res = PQexecPrepared(conn, "libpq_internal_lo_close", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_close($1::pg_catalog.int4)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -232,9 +143,6 @@ lo_truncate(PGconn *conn, int fd, size_t len)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
if (PQserverVersion(conn) < 80300)
{
libpq_append_conn_error(conn, "server does not support function \"%s\"",
@@ -263,8 +171,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
len = pg_hton32(len);
argv[1] = (char *) &len;
- res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -295,9 +203,6 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
if (PQserverVersion(conn) < 90300)
{
libpq_append_conn_error(conn, "server does not support function \"%s\"",
@@ -311,8 +216,8 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
len = pg_hton64(len);
argv[1] = (char *) &len;
- res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -343,9 +248,6 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len)
int argFormats[] = {1, 1};
PGresult *res;
- if (lo_initialize(conn) < 0)
- return -1;
-
/*
* Long ago, somebody thought it'd be a good idea to declare this function
* as taking size_t ... but the underlying backend function only accepts a
@@ -364,8 +266,8 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len)
len = pg_hton32(len);
argv[1] = (char *) &len;
- res = PQexecPrepared(conn, "libpq_internal_loread", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, -1))
{
@@ -401,9 +303,6 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
/*
* Long ago, somebody thought it'd be a good idea to declare this function
* as taking size_t ... but the underlying backend function only accepts a
@@ -421,8 +320,8 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
argv[1] = unconstify(char *, buf);
- res = PQexecPrepared(conn, "libpq_internal_lowrite", 2,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)", 2, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -450,9 +349,6 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
fd = pg_hton32(fd);
argv[0] = (char *) &fd;
@@ -462,8 +358,8 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
whence = pg_hton32(whence);
argv[2] = (char *) &whence;
- res = PQexecPrepared(conn, "libpq_internal_lseek", 3,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)", 3, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -491,9 +387,6 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
PGresult *res;
int64 retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
if (PQserverVersion(conn) < 90300)
{
libpq_append_conn_error(conn, "server does not support function \"%s\"",
@@ -510,8 +403,8 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
whence = pg_hton32(whence);
argv[2] = (char *) &whence;
- res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", 3, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval));
@@ -542,14 +435,11 @@ lo_creat(PGconn *conn, int mode)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return InvalidOid;
-
mode = pg_hton32(mode);
argv[0] = (char *) &mode;
- res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -581,9 +471,6 @@ lo_create(PGconn *conn, Oid lobjId)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return InvalidOid;
-
if (PQserverVersion(conn) < 80100)
{
libpq_append_conn_error(conn, "server does not support function \"%s\"",
@@ -594,8 +481,8 @@ lo_create(PGconn *conn, Oid lobjId)
lobjId = pg_hton32(lobjId);
argv[0] = (char *) &lobjId;
- res = PQexecPrepared(conn, "libpq_internal_lo_create", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -624,14 +511,11 @@ lo_tell(PGconn *conn, int fd)
int argFormats[] = {1};
PGresult *res;
- if (lo_initialize(conn) < 0)
- return -1;
-
fd = pg_hton32(fd);
argv[0] = (char *) &fd;
- res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -659,9 +543,6 @@ lo_tell64(PGconn *conn, int fd)
int argFormats[] = {1};
PGresult *res;
- if (lo_initialize(conn) < 0)
- return -1;
-
if (PQserverVersion(conn) < 90300)
{
libpq_append_conn_error(conn, "server does not support functions \"%s\"",
@@ -672,8 +553,8 @@ lo_tell64(PGconn *conn, int fd)
fd = pg_hton32(fd);
argv[0] = (char *) &fd;
- res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -702,14 +583,11 @@ lo_unlink(PGconn *conn, Oid lobjId)
PGresult *res;
int retval;
- if (lo_initialize(conn) < 0)
- return -1;
-
lobjId = pg_hton32(lobjId);
argv[0] = (char *) &lobjId;
- res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1,
- (const char *const *) argv, argLens, argFormats, 1);
+ res = PQexecParams(conn, "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)", 1, NULL,
+ (const char *const *) argv, argLens, argFormats, 1);
if (lo_result_is_valid(res, sizeof(retval)))
{
@@ -938,65 +816,3 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename)
return result;
}
-
-
-/*
- * lo_initialize
- *
- * 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 prepared statements for all the
- * functions that are required for large object operations.
- */
-static int
-lo_initialize(PGconn *conn)
-{
- /* Nothing we can do with no connection */
- if (conn == NULL)
- return -1;
-
- /* Since this is the beginning of a query cycle, reset the error state */
- pqClearConnErrorState(conn);
-
- /* Nothing else to do if we already prepared the statements */
- if (conn->lobjprepared)
- return 0;
-
- for (int i = 0; i < lengthof(lobjfuncs); i++)
- {
- PGresult *res;
- ExecStatusType status;
- PQExpBufferData pname;
- PQExpBufferData pquery;
-
- if (PQserverVersion(conn) < lobjfuncs[i].version)
- continue;
-
- initPQExpBuffer(&pname);
- initPQExpBuffer(&pquery);
-
- appendPQExpBuffer(&pname, "libpq_internal_%s",
- lobjfuncs[i].name);
- appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)",
- lobjfuncs[i].name, lobjfuncs[i].args);
-
- res = PQprepare(conn, pname.data, pquery.data,
- lobjfuncs[i].nargs, NULL);
- status = PQresultStatus(res);
- if (res)
- PQclear(res);
-
- termPQExpBuffer(&pname);
- termPQExpBuffer(&pquery);
-
- if (status == PGRES_COMMAND_OK)
- continue;
-
- libpq_append_conn_error(conn, "query to prepare large object statements failed");
- return -1;
- }
-
- conn->lobjprepared = true;
- return 0;
-}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9dd0f42b6b7..cf968f1519b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -544,7 +544,6 @@ struct pg_conn
PGTernaryBool in_hot_standby; /* in_hot_standby */
PGVerbosity verbosity; /* error/notice message verbosity */
PGContextVisibility show_context; /* whether to show CONTEXT field */
- bool lobjprepared; /* whether LO statements have been prepared */
pg_prng_state prng_state; /* prng state for load balancing connections */
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-27 21:39 Jacob Champion <jacob.champion@enterprisedb.com>
parent: Nathan Bossart <nathandbossart@gmail.com>
0 siblings, 2 replies; 20+ messages in thread
From: Jacob Champion @ 2026-05-27 21:39 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: pgsql-hackers
On Tue, May 26, 2026 at 12:55 PM Nathan Bossart
<nathandbossart@gmail.com> 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.
--Jacob
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-28 15:17 Nathan Bossart <nathandbossart@gmail.com>
parent: Jacob Champion <jacob.champion@enterprisedb.com>
1 sibling, 0 replies; 20+ messages in thread
From: Nathan Bossart @ 2026-05-28 15:17 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: pgsql-hackers
On Wed, May 27, 2026 at 02:39:53PM -0700, Jacob Champion wrote:
> On Tue, May 26, 2026 at 12:55 PM Nathan Bossart
> <nathandbossart@gmail.com> 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.
I've been thinking through a bunch of options here, and a new protocol
message seems like the best choice, if for no other reason than it solves a
general problem for clients. I'm working on a patch that I'll post in a
new thread.
--
nathan
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-29 15:14 Nathan Bossart <nathandbossart@gmail.com>
parent: Jacob Champion <jacob.champion@enterprisedb.com>
1 sibling, 1 reply; 20+ messages in thread
From: Nathan Bossart @ 2026-05-29 15:14 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: pgsql-hackers
On Wed, May 27, 2026 at 02:39:53PM -0700, Jacob Champion wrote:
> On Tue, May 26, 2026 at 12:55 PM Nathan Bossart
> <nathandbossart@gmail.com> 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 <nathan@postgresql.org>
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 <nathan@postgresql.org>
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 <nathan@postgresql.org>
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)
Attachments:
[text/plain] v2-0001-tell-client-when-prep-stmts-are-deallocated.patch (12.1K, ../../ahmtVYcf4EYwz02U@nathan/2-v2-0001-tell-client-when-prep-stmts-are-deallocated.patch)
download | inline diff:
From ed962c55faf3b6ef28af6672591f460ad2289273 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
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)
[text/plain] v2-0002-stop-using-PQfn-in-libpq-s-LO-interface.patch (27.2K, ../../ahmtVYcf4EYwz02U@nathan/3-v2-0002-stop-using-PQfn-in-libpq-s-LO-interface.patch)
download | inline diff:
From 1f35373081aa9b2c5143e7af366211302eb997fc Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
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)
[text/plain] v2-0003-remove-support-for-PQfn.patch (17.1K, ../../ahmtVYcf4EYwz02U@nathan/4-v2-0003-remove-support-for-PQfn.patch)
download | inline diff:
From 2a965c838ae820bf448106ed391bb7d84c6dc00f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
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)
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-29 15:43 Jacob Champion <jacob.champion@enterprisedb.com>
parent: Nathan Bossart <nathandbossart@gmail.com>
0 siblings, 1 reply; 20+ messages in thread
From: Jacob Champion @ 2026-05-29 15:43 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: pgsql-hackers
On Fri, May 29, 2026 at 8:14 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
> Here is a work-in-progress patch set that goes this direction.
At a high level, I think advertising support for a single new message
needs to be done in a protocol extension rather than a minor version
bump.
> 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.
IMO there's no hurry in getting rid of that path. If we decide to go
this direction, a fallback to PQnfn() seems like it'd fine for a few
releases; we could eventually swap to a PQexecParams() fallback and
get rid of the extra code once the older servers have aged out.
> 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.
If it's just a general notification message, what does negotiating
"support" mean? Is best-effort notification okay, if the client has no
idea what a future message type means, or if the server doesn't send
the specific type of message the client is hoping for?
(In general, I'm kind of down on the "notify the client that X
happened" method of working around architectural issues. Maybe that's
what we need to move this specific part forward, but it doesn't feel
like a long-term solution and I don't know that we need to genericize
it without a solid set of use cases.)
--Jacob
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-29 16:10 Nathan Bossart <nathandbossart@gmail.com>
parent: Jacob Champion <jacob.champion@enterprisedb.com>
0 siblings, 1 reply; 20+ messages in thread
From: Nathan Bossart @ 2026-05-29 16:10 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: pgsql-hackers
On Fri, May 29, 2026 at 08:43:07AM -0700, Jacob Champion wrote:
> On Fri, May 29, 2026 at 8:14 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
>> Here is a work-in-progress patch set that goes this direction.
>
> At a high level, I think advertising support for a single new message
> needs to be done in a protocol extension rather than a minor version
> bump.
WFM
>> 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.
>
> IMO there's no hurry in getting rid of that path. If we decide to go
> this direction, a fallback to PQnfn() seems like it'd fine for a few
> releases; we could eventually swap to a PQexecParams() fallback and
> get rid of the extra code once the older servers have aged out.
That's fine with me, too.
>> 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.
>
> If it's just a general notification message, what does negotiating
> "support" mean? Is best-effort notification okay, if the client has no
> idea what a future message type means, or if the server doesn't send
> the specific type of message the client is hoping for?
That's what I had in mind. But if we don't have anything specific in mind
that this mechanism could be extended to support, maybe we shouldn't
bother. Especially if we can just add protocol extensions as necessary.
> (In general, I'm kind of down on the "notify the client that X
> happened" method of working around architectural issues. Maybe that's
> what we need to move this specific part forward, but it doesn't feel
> like a long-term solution and I don't know that we need to genericize
> it without a solid set of use cases.)
I'm certainly open to other ideas, but I'm afraid this is the best I've
come up with in my admittedly limited time thinking about the problem.
--
nathan
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-29 16:33 Jacob Champion <jacob.champion@enterprisedb.com>
parent: Nathan Bossart <nathandbossart@gmail.com>
0 siblings, 1 reply; 20+ messages in thread
From: Jacob Champion @ 2026-05-29 16:33 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: pgsql-hackers
On Fri, May 29, 2026 at 9:11 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
> I'm certainly open to other ideas, but I'm afraid this is the best I've
> come up with in my admittedly limited time thinking about the problem.
No worries -- I hadn't meant to block progress here on protocol
design. I think keeping PQnfn() for the immediate future is a good
plan. I just wanted to plant a seed for getting away from this problem
eventually.
(As for pie-in-the-sky alternative ideas, the ability for middleware
to separate contexts or streams of packets has come up before. libpq
could theoretically mark its own "context" of server-side allocations
that are not touched by an application-context DISCARD.)
--Jacob
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-29 16:42 Nathan Bossart <nathandbossart@gmail.com>
parent: Jacob Champion <jacob.champion@enterprisedb.com>
0 siblings, 1 reply; 20+ messages in thread
From: Nathan Bossart @ 2026-05-29 16:42 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: pgsql-hackers
For future reference in the archives, I'm moving the discussion about 0001
(the prepared statement deallocation notification mechanism) to a new
thread:
https://postgr.es/m/ahm_4eOKkkKJ3Gds%40nathan
On Fri, May 29, 2026 at 09:33:03AM -0700, Jacob Champion wrote:
> On Fri, May 29, 2026 at 9:11 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
>> I'm certainly open to other ideas, but I'm afraid this is the best I've
>> come up with in my admittedly limited time thinking about the problem.
>
> No worries -- I hadn't meant to block progress here on protocol
> design. I think keeping PQnfn() for the immediate future is a good
> plan. I just wanted to plant a seed for getting away from this problem
> eventually.
>
> (As for pie-in-the-sky alternative ideas, the ability for middleware
> to separate contexts or streams of packets has come up before. libpq
> could theoretically mark its own "context" of server-side allocations
> that are not touched by an application-context DISCARD.)
Along these lines, I did consider "pinning" statements or even having
"built-in" ones for libpq. I didn't like the "pinning" idea because that
seemed problematic for connection poolers. And the "built-in" idea seemed
too libpq-centric for what I'd argue is a general problem. The other ideas
involved guessing at what's happening based on the queries or somehow
trying to handle failures due to missing/wrong prepared statements, none of
which felt viable.
--
nathan
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-05-29 17:04 Jacob Champion <jacob.champion@enterprisedb.com>
parent: Nathan Bossart <nathandbossart@gmail.com>
0 siblings, 1 reply; 20+ messages in thread
From: Jacob Champion @ 2026-05-29 17:04 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: pgsql-hackers
On Fri, May 29, 2026 at 9:42 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
> Along these lines, I did consider "pinning" statements or even having
> "built-in" ones for libpq. I didn't like the "pinning" idea because that
> seemed problematic for connection poolers.
Right -- whether a general context, or multiplexed streams, or
explicit pins, proxies would have to be intimately aware of them. They
can then layer their own on top, or make sure different client
requests don't conflict, or release them on client disconnection...
> And the "built-in" idea seemed
> too libpq-centric for what I'd argue is a general problem. The other ideas
> involved guessing at what's happening based on the queries or somehow
> trying to handle failures due to missing/wrong prepared statements, none of
> which felt viable.
Agreed. (Although, for that last point, I wondered whether we could
make this idempotent somehow. I think the answer is "not worth it".)
--Jacob
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-06-03 21:36 Nathan Bossart <nathandbossart@gmail.com>
parent: Jacob Champion <jacob.champion@enterprisedb.com>
0 siblings, 1 reply; 20+ messages in thread
From: Nathan Bossart @ 2026-06-03 21:36 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: pgsql-hackers
While the prepared statement deallocation idea bakes in the other thread, I
thought I'd post a new patch set here. I've reordered things a bit and
left out the conversion to prepared statements for now (although I do have
a patch for that). 0001 removes PQfn() and changes all internal uses to
PQnfn(). 0002 and 0003 are just some additional cleanup that I noticed
as I've been working on this stuff.
--
nathan
From 3d3a983333daa9e2d59887a5fdd7b8806d33c588 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 1 Jun 2026 16:55:00 -0500
Subject: [PATCH v3 1/3] remove PQfn
---
doc/src/sgml/libpq.sgml | 117 +--------------------------------
src/backend/tcop/fastpath.c | 4 +-
src/include/tcop/dest.h | 4 +-
src/interfaces/libpq/fe-exec.c | 42 ++++++++++--
src/interfaces/libpq/fe-lobj.c | 38 +++++------
5 files changed, 60 insertions(+), 145 deletions(-)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..123e7f03902 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -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..5379e4ad9f5 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -11,7 +11,9 @@
* src/backend/tcop/fastpath.c
*
* NOTES
- * This cruft is the server side of PQfn.
+ * This cruft is the server side of PQfn. libpq's PQfn() was retired in
+ * v20 and now always errors, but the server code is retained for the
+ * benefit of 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..2f034d70e65 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2986,10 +2986,10 @@ 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 (unless the connection is in the wrong state, in
+ * which case it returns NULL).
* ----------------
*/
@@ -3002,15 +3002,43 @@ PQfn(PGconn *conn,
const PQArgBlock *args,
int nargs)
{
- return PQnfn(conn, fnid, result_buf, -1, result_len,
- result_is_int, args, 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;
+ }
+
+ libpq_append_conn_error(conn, "PQfn() is no longer supported; use a prepared statement or PQexecParams() with binary results instead");
+ pqSaveErrorResult(conn);
+ return pqPrepareAsyncResult(conn);
}
/*
* 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.
+ * this verification. This is currently only used by the frontend LO
+ * interface and will hopefully be removed down the road.
*/
PGresult *
PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 12a32fcbaf3..1660c969f58 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -72,7 +72,7 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
argv[1].len = 4;
argv[1].u.integer = mode;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -106,8 +106,8 @@ lo_close(PGconn *conn, int fd)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -169,8 +169,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
argv[1].len = 4;
argv[1].u.integer = (int) len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -218,8 +218,8 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -322,8 +322,8 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
argv[1].len = (int) len;
argv[1].u.ptr = (int *) unconstify(char *, buf);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -363,8 +363,8 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
argv[2].len = 4;
argv[2].u.integer = whence;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek,
- &retval, &result_len, 1, argv, 3);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
+ &retval, -1, &result_len, 1, argv, 3);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -448,8 +448,8 @@ lo_creat(PGconn *conn, int mode)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -492,8 +492,8 @@ lo_create(PGconn *conn, Oid lobjId)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -526,8 +526,8 @@ lo_tell(PGconn *conn, int fd)
argv[0].len = 4;
argv[0].u.integer = fd;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_tell,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -600,8 +600,8 @@ lo_unlink(PGconn *conn, Oid lobjId)
argv[0].len = 4;
argv[0].u.integer = lobjId;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
--
2.50.1 (Apple Git-155)
From 8b03849dadebe8d6c31be83b2ab3f5c8736adb32 Mon Sep 17 00:00:00 2001
From: Reviewer <review@example.com>
Date: Tue, 2 Jun 2026 17:22:48 +0000
Subject: [PATCH v3 2/3] remove lo_hton64() and lo_ntoh64()
---
src/interfaces/libpq/fe-lobj.c | 58 +++-------------------------------
1 file changed, 4 insertions(+), 54 deletions(-)
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 1660c969f58..42b2a36bda9 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -43,8 +43,6 @@
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);
/*
* lo_open
@@ -213,7 +211,7 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[0].len = 4;
argv[0].u.integer = fd;
- len = lo_hton64(len);
+ len = pg_hton64(len);
argv[1].isint = 0;
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
@@ -403,7 +401,7 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
argv[0].len = 4;
argv[0].u.integer = fd;
- offset = lo_hton64(offset);
+ offset = pg_hton64(offset);
argv[1].isint = 0;
argv[1].len = 8;
argv[1].u.ptr = (int *) &offset;
@@ -417,7 +415,7 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh64(retval);
}
else
{
@@ -571,7 +569,7 @@ lo_tell64(PGconn *conn, int fd)
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh64(retval);
}
else
{
@@ -1014,51 +1012,3 @@ lo_initialize(PGconn *conn)
conn->lobjfuncs = lobjfuncs;
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;
-}
--
2.50.1 (Apple Git-155)
From e5b1df7a69d0d0f1798ce0dc2beb18fedfdfd081 Mon Sep 17 00:00:00 2001
From: Reviewer <review@example.com>
Date: Tue, 2 Jun 2026 18:24:30 +0000
Subject: [PATCH v3 3/3] add helper functions for fast-path arg setup
---
src/interfaces/libpq/fe-lobj.c | 104 ++++++++++++---------------------
1 file changed, 38 insertions(+), 66 deletions(-)
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 42b2a36bda9..8802c6f8098 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -44,6 +44,22 @@
static int lo_initialize(PGconn *conn);
static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid);
+static inline void
+lo_set_int_arg(PQArgBlock *arg, int value)
+{
+ arg->isint = 1;
+ arg->len = 4;
+ arg->u.integer = value;
+}
+
+static inline void
+lo_set_ptr_arg(PQArgBlock *arg, const void *ptr, int len)
+{
+ arg->isint = 0;
+ arg->len = len;
+ arg->u.ptr = (int *) unconstify(void *, ptr);
+}
+
/*
* lo_open
* opens an existing large object
@@ -62,13 +78,9 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = mode;
+ lo_set_int_arg(&argv[1], mode);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -101,9 +113,7 @@ lo_close(PGconn *conn, int fd)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -159,13 +169,9 @@ lo_truncate(PGconn *conn, int fd, size_t len)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ lo_set_int_arg(&argv[1], (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
&retval, -1, &result_len, 1, argv, 2);
@@ -207,14 +213,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
len = pg_hton64(len);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &len;
+ lo_set_ptr_arg(&argv[1], &len, 8);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
&retval, -1, &result_len, 1, argv, 2);
@@ -261,13 +263,9 @@ 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;
+ lo_set_int_arg(&argv[0], fd);
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ lo_set_int_arg(&argv[1], (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_read,
(void *) buf, len, &result_len, 0, argv, 2);
@@ -312,13 +310,9 @@ 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;
+ lo_set_int_arg(&argv[0], fd);
- argv[1].isint = 0;
- argv[1].len = (int) len;
- argv[1].u.ptr = (int *) unconstify(char *, buf);
+ lo_set_ptr_arg(&argv[1], buf, (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
&retval, -1, &result_len, 1, argv, 2);
@@ -349,17 +343,11 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = offset;
+ lo_set_int_arg(&argv[1], offset);
- argv[2].isint = 1;
- argv[2].len = 4;
- argv[2].u.integer = whence;
+ lo_set_int_arg(&argv[2], whence);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
&retval, -1, &result_len, 1, argv, 3);
@@ -397,18 +385,12 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
offset = pg_hton64(offset);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &offset;
+ lo_set_ptr_arg(&argv[1], &offset, 8);
- argv[2].isint = 1;
- argv[2].len = 4;
- argv[2].u.integer = whence;
+ lo_set_int_arg(&argv[2], whence);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64,
(void *) &retval, sizeof(retval), &result_len, 0, argv, 3);
@@ -443,9 +425,7 @@ lo_creat(PGconn *conn, int mode)
if (lo_initialize(conn) < 0)
return InvalidOid;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = mode;
+ lo_set_int_arg(&argv[0], mode);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -487,9 +467,7 @@ lo_create(PGconn *conn, Oid lobjId)
return InvalidOid;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -520,9 +498,7 @@ lo_tell(PGconn *conn, int fd)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
&retval, -1, &result_len, 1, argv, 1);
@@ -560,9 +536,7 @@ lo_tell64(PGconn *conn, int fd)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64,
(void *) &retval, sizeof(retval), &result_len, 0, argv, 1);
@@ -594,9 +568,7 @@ lo_unlink(PGconn *conn, Oid lobjId)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
&retval, -1, &result_len, 1, argv, 1);
--
2.50.1 (Apple Git-155)
Attachments:
[text/plain] v3-0001-remove-PQfn.patch (13.1K, ../../aiCeYhW4Ksu-BFqC@nathan/2-v3-0001-remove-PQfn.patch)
download | inline diff:
From 3d3a983333daa9e2d59887a5fdd7b8806d33c588 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 1 Jun 2026 16:55:00 -0500
Subject: [PATCH v3 1/3] remove PQfn
---
doc/src/sgml/libpq.sgml | 117 +--------------------------------
src/backend/tcop/fastpath.c | 4 +-
src/include/tcop/dest.h | 4 +-
src/interfaces/libpq/fe-exec.c | 42 ++++++++++--
src/interfaces/libpq/fe-lobj.c | 38 +++++------
5 files changed, 60 insertions(+), 145 deletions(-)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..123e7f03902 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -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..5379e4ad9f5 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -11,7 +11,9 @@
* src/backend/tcop/fastpath.c
*
* NOTES
- * This cruft is the server side of PQfn.
+ * This cruft is the server side of PQfn. libpq's PQfn() was retired in
+ * v20 and now always errors, but the server code is retained for the
+ * benefit of 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..2f034d70e65 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2986,10 +2986,10 @@ 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 (unless the connection is in the wrong state, in
+ * which case it returns NULL).
* ----------------
*/
@@ -3002,15 +3002,43 @@ PQfn(PGconn *conn,
const PQArgBlock *args,
int nargs)
{
- return PQnfn(conn, fnid, result_buf, -1, result_len,
- result_is_int, args, 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;
+ }
+
+ libpq_append_conn_error(conn, "PQfn() is no longer supported; use a prepared statement or PQexecParams() with binary results instead");
+ pqSaveErrorResult(conn);
+ return pqPrepareAsyncResult(conn);
}
/*
* 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.
+ * this verification. This is currently only used by the frontend LO
+ * interface and will hopefully be removed down the road.
*/
PGresult *
PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 12a32fcbaf3..1660c969f58 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -72,7 +72,7 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
argv[1].len = 4;
argv[1].u.integer = mode;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -106,8 +106,8 @@ lo_close(PGconn *conn, int fd)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -169,8 +169,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
argv[1].len = 4;
argv[1].u.integer = (int) len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -218,8 +218,8 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -322,8 +322,8 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
argv[1].len = (int) len;
argv[1].u.ptr = (int *) unconstify(char *, buf);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -363,8 +363,8 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
argv[2].len = 4;
argv[2].u.integer = whence;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek,
- &retval, &result_len, 1, argv, 3);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
+ &retval, -1, &result_len, 1, argv, 3);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -448,8 +448,8 @@ lo_creat(PGconn *conn, int mode)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -492,8 +492,8 @@ lo_create(PGconn *conn, Oid lobjId)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -526,8 +526,8 @@ lo_tell(PGconn *conn, int fd)
argv[0].len = 4;
argv[0].u.integer = fd;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_tell,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -600,8 +600,8 @@ lo_unlink(PGconn *conn, Oid lobjId)
argv[0].len = 4;
argv[0].u.integer = lobjId;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
--
2.50.1 (Apple Git-155)
[text/plain] v3-0002-remove-lo_hton64-and-lo_ntoh64.patch (2.6K, ../../aiCeYhW4Ksu-BFqC@nathan/3-v3-0002-remove-lo_hton64-and-lo_ntoh64.patch)
download | inline diff:
From 8b03849dadebe8d6c31be83b2ab3f5c8736adb32 Mon Sep 17 00:00:00 2001
From: Reviewer <review@example.com>
Date: Tue, 2 Jun 2026 17:22:48 +0000
Subject: [PATCH v3 2/3] remove lo_hton64() and lo_ntoh64()
---
src/interfaces/libpq/fe-lobj.c | 58 +++-------------------------------
1 file changed, 4 insertions(+), 54 deletions(-)
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 1660c969f58..42b2a36bda9 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -43,8 +43,6 @@
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);
/*
* lo_open
@@ -213,7 +211,7 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[0].len = 4;
argv[0].u.integer = fd;
- len = lo_hton64(len);
+ len = pg_hton64(len);
argv[1].isint = 0;
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
@@ -403,7 +401,7 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
argv[0].len = 4;
argv[0].u.integer = fd;
- offset = lo_hton64(offset);
+ offset = pg_hton64(offset);
argv[1].isint = 0;
argv[1].len = 8;
argv[1].u.ptr = (int *) &offset;
@@ -417,7 +415,7 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh64(retval);
}
else
{
@@ -571,7 +569,7 @@ lo_tell64(PGconn *conn, int fd)
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh64(retval);
}
else
{
@@ -1014,51 +1012,3 @@ lo_initialize(PGconn *conn)
conn->lobjfuncs = lobjfuncs;
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;
-}
--
2.50.1 (Apple Git-155)
[text/plain] v3-0003-add-helper-functions-for-fast-path-arg-setup.patch (6.0K, ../../aiCeYhW4Ksu-BFqC@nathan/4-v3-0003-add-helper-functions-for-fast-path-arg-setup.patch)
download | inline diff:
From e5b1df7a69d0d0f1798ce0dc2beb18fedfdfd081 Mon Sep 17 00:00:00 2001
From: Reviewer <review@example.com>
Date: Tue, 2 Jun 2026 18:24:30 +0000
Subject: [PATCH v3 3/3] add helper functions for fast-path arg setup
---
src/interfaces/libpq/fe-lobj.c | 104 ++++++++++++---------------------
1 file changed, 38 insertions(+), 66 deletions(-)
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 42b2a36bda9..8802c6f8098 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -44,6 +44,22 @@
static int lo_initialize(PGconn *conn);
static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid);
+static inline void
+lo_set_int_arg(PQArgBlock *arg, int value)
+{
+ arg->isint = 1;
+ arg->len = 4;
+ arg->u.integer = value;
+}
+
+static inline void
+lo_set_ptr_arg(PQArgBlock *arg, const void *ptr, int len)
+{
+ arg->isint = 0;
+ arg->len = len;
+ arg->u.ptr = (int *) unconstify(void *, ptr);
+}
+
/*
* lo_open
* opens an existing large object
@@ -62,13 +78,9 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = mode;
+ lo_set_int_arg(&argv[1], mode);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -101,9 +113,7 @@ lo_close(PGconn *conn, int fd)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -159,13 +169,9 @@ lo_truncate(PGconn *conn, int fd, size_t len)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ lo_set_int_arg(&argv[1], (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
&retval, -1, &result_len, 1, argv, 2);
@@ -207,14 +213,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
len = pg_hton64(len);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &len;
+ lo_set_ptr_arg(&argv[1], &len, 8);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
&retval, -1, &result_len, 1, argv, 2);
@@ -261,13 +263,9 @@ 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;
+ lo_set_int_arg(&argv[0], fd);
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ lo_set_int_arg(&argv[1], (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_read,
(void *) buf, len, &result_len, 0, argv, 2);
@@ -312,13 +310,9 @@ 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;
+ lo_set_int_arg(&argv[0], fd);
- argv[1].isint = 0;
- argv[1].len = (int) len;
- argv[1].u.ptr = (int *) unconstify(char *, buf);
+ lo_set_ptr_arg(&argv[1], buf, (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
&retval, -1, &result_len, 1, argv, 2);
@@ -349,17 +343,11 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = offset;
+ lo_set_int_arg(&argv[1], offset);
- argv[2].isint = 1;
- argv[2].len = 4;
- argv[2].u.integer = whence;
+ lo_set_int_arg(&argv[2], whence);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
&retval, -1, &result_len, 1, argv, 3);
@@ -397,18 +385,12 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
offset = pg_hton64(offset);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &offset;
+ lo_set_ptr_arg(&argv[1], &offset, 8);
- argv[2].isint = 1;
- argv[2].len = 4;
- argv[2].u.integer = whence;
+ lo_set_int_arg(&argv[2], whence);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64,
(void *) &retval, sizeof(retval), &result_len, 0, argv, 3);
@@ -443,9 +425,7 @@ lo_creat(PGconn *conn, int mode)
if (lo_initialize(conn) < 0)
return InvalidOid;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = mode;
+ lo_set_int_arg(&argv[0], mode);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -487,9 +467,7 @@ lo_create(PGconn *conn, Oid lobjId)
return InvalidOid;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -520,9 +498,7 @@ lo_tell(PGconn *conn, int fd)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
&retval, -1, &result_len, 1, argv, 1);
@@ -560,9 +536,7 @@ lo_tell64(PGconn *conn, int fd)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64,
(void *) &retval, sizeof(retval), &result_len, 0, argv, 1);
@@ -594,9 +568,7 @@ lo_unlink(PGconn *conn, Oid lobjId)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
&retval, -1, &result_len, 1, argv, 1);
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-06-04 10:39 Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
parent: Nathan Bossart <nathandbossart@gmail.com>
0 siblings, 1 reply; 20+ messages in thread
From: Dagfinn Ilmari Mannsåker @ 2026-06-04 10:39 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; pgsql-hackers
Nathan Bossart <nathandbossart@gmail.com> writes:
> - <sect1 id="libpq-fastpath">
> - <title>The Fast-Path Interface</title>
> -
> - <indexterm zone="libpq-fastpath">
> - <primary>fast path</primary>
> - </indexterm>
Should we move this to the "Obsolete or Renamed Features" appendix
(e.g. appendix-obsolete-libpq-fastpath.sgml) with a description of why
it was removed?
- ilmari
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-06-04 14:57 Nathan Bossart <nathandbossart@gmail.com>
parent: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
0 siblings, 1 reply; 20+ messages in thread
From: Nathan Bossart @ 2026-06-04 14:57 UTC (permalink / raw)
To: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; pgsql-hackers
On Thu, Jun 04, 2026 at 11:39:02AM +0100, Dagfinn Ilmari Mannsåker wrote:
> Nathan Bossart <nathandbossart@gmail.com> writes:
>
>> - <sect1 id="libpq-fastpath">
>> - <title>The Fast-Path Interface</title>
>> -
>> - <indexterm zone="libpq-fastpath">
>> - <primary>fast path</primary>
>> - </indexterm>
>
> Should we move this to the "Obsolete or Renamed Features" appendix
> (e.g. appendix-obsolete-libpq-fastpath.sgml) with a description of why
> it was removed?
Yes, I think we should. Done in the attached.
--
nathan
From 63486371b3531be66bb8e0c62ef1b8cc7e95abb5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 1 Jun 2026 16:55:00 -0500
Subject: [PATCH v4 1/3] remove PQfn
---
.../appendix-obsolete-libpq-fastpath.sgml | 24 ++++
doc/src/sgml/appendix-obsolete.sgml | 1 +
doc/src/sgml/filelist.sgml | 1 +
doc/src/sgml/libpq.sgml | 117 +-----------------
src/backend/tcop/fastpath.c | 4 +-
src/include/tcop/dest.h | 4 +-
src/interfaces/libpq/fe-exec.c | 42 +++++--
src/interfaces/libpq/fe-lobj.c | 38 +++---
8 files changed, 86 insertions(+), 145 deletions(-)
create mode 100644 doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
diff --git a/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml b/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
new file mode 100644
index 00000000000..e42c2ece487
--- /dev/null
+++ b/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
@@ -0,0 +1,24 @@
+<!-- doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml -->
+<!--
+ See doc/src/sgml/appendix-obsolete.sgml for why this file exists. Do not change the id attribute.
+-->
+
+<sect1 id="libpq-fastpath">
+ <title><application>libpq</application> Fast-Path Interface Removed</title>
+
+ <indexterm zone="libpq-fastpath">
+ <primary>fast path</primary>
+ </indexterm>
+
+ <para>
+ In <productname>PostgreSQL</productname> 19 and below,
+ <application>libpq</application> supported a fast-path interface to send
+ simple function calls to the server. This interface was unsafe and
+ obsolete, and thus was removed in <productname>PostgreSQL</productname> 20.
+ 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>
+
+</sect1>
diff --git a/doc/src/sgml/appendix-obsolete.sgml b/doc/src/sgml/appendix-obsolete.sgml
index cc002653052..11a033112ef 100644
--- a/doc/src/sgml/appendix-obsolete.sgml
+++ b/doc/src/sgml/appendix-obsolete.sgml
@@ -39,5 +39,6 @@
&obsolete-pgresetxlog;
&obsolete-pgreceivexlog;
&obsolete-auth-radius;
+ &obsolete-libpq-fastpath;
</appendix>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 25a85082759..7dbe741d729 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -209,3 +209,4 @@
<!ENTITY obsolete-pgresetxlog SYSTEM "appendix-obsolete-pgresetxlog.sgml">
<!ENTITY obsolete-pgreceivexlog SYSTEM "appendix-obsolete-pgreceivexlog.sgml">
<!ENTITY obsolete-auth-radius SYSTEM "appendix-obsolete-auth-radius.sgml">
+<!ENTITY obsolete-libpq-fastpath SYSTEM "appendix-obsolete-libpq-fastpath.sgml">
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..123e7f03902 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -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..5379e4ad9f5 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -11,7 +11,9 @@
* src/backend/tcop/fastpath.c
*
* NOTES
- * This cruft is the server side of PQfn.
+ * This cruft is the server side of PQfn. libpq's PQfn() was retired in
+ * v20 and now always errors, but the server code is retained for the
+ * benefit of 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..2f034d70e65 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2986,10 +2986,10 @@ 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 (unless the connection is in the wrong state, in
+ * which case it returns NULL).
* ----------------
*/
@@ -3002,15 +3002,43 @@ PQfn(PGconn *conn,
const PQArgBlock *args,
int nargs)
{
- return PQnfn(conn, fnid, result_buf, -1, result_len,
- result_is_int, args, 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;
+ }
+
+ libpq_append_conn_error(conn, "PQfn() is no longer supported; use a prepared statement or PQexecParams() with binary results instead");
+ pqSaveErrorResult(conn);
+ return pqPrepareAsyncResult(conn);
}
/*
* 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.
+ * this verification. This is currently only used by the frontend LO
+ * interface and will hopefully be removed down the road.
*/
PGresult *
PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 12a32fcbaf3..1660c969f58 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -72,7 +72,7 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
argv[1].len = 4;
argv[1].u.integer = mode;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -106,8 +106,8 @@ lo_close(PGconn *conn, int fd)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -169,8 +169,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
argv[1].len = 4;
argv[1].u.integer = (int) len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -218,8 +218,8 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -322,8 +322,8 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
argv[1].len = (int) len;
argv[1].u.ptr = (int *) unconstify(char *, buf);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -363,8 +363,8 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
argv[2].len = 4;
argv[2].u.integer = whence;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek,
- &retval, &result_len, 1, argv, 3);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
+ &retval, -1, &result_len, 1, argv, 3);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -448,8 +448,8 @@ lo_creat(PGconn *conn, int mode)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -492,8 +492,8 @@ lo_create(PGconn *conn, Oid lobjId)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -526,8 +526,8 @@ lo_tell(PGconn *conn, int fd)
argv[0].len = 4;
argv[0].u.integer = fd;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_tell,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -600,8 +600,8 @@ lo_unlink(PGconn *conn, Oid lobjId)
argv[0].len = 4;
argv[0].u.integer = lobjId;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
--
2.50.1 (Apple Git-155)
From 181028cbf820700f104f48dfc0e94d0c7f5e42e0 Mon Sep 17 00:00:00 2001
From: Reviewer <review@example.com>
Date: Tue, 2 Jun 2026 17:22:48 +0000
Subject: [PATCH v4 2/3] remove lo_hton64() and lo_ntoh64()
---
src/interfaces/libpq/fe-lobj.c | 58 +++-------------------------------
1 file changed, 4 insertions(+), 54 deletions(-)
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 1660c969f58..42b2a36bda9 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -43,8 +43,6 @@
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);
/*
* lo_open
@@ -213,7 +211,7 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[0].len = 4;
argv[0].u.integer = fd;
- len = lo_hton64(len);
+ len = pg_hton64(len);
argv[1].isint = 0;
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
@@ -403,7 +401,7 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
argv[0].len = 4;
argv[0].u.integer = fd;
- offset = lo_hton64(offset);
+ offset = pg_hton64(offset);
argv[1].isint = 0;
argv[1].len = 8;
argv[1].u.ptr = (int *) &offset;
@@ -417,7 +415,7 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh64(retval);
}
else
{
@@ -571,7 +569,7 @@ lo_tell64(PGconn *conn, int fd)
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh64(retval);
}
else
{
@@ -1014,51 +1012,3 @@ lo_initialize(PGconn *conn)
conn->lobjfuncs = lobjfuncs;
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;
-}
--
2.50.1 (Apple Git-155)
From 8e5a937d5e302a56ff2118ce3ea4f69e7db5ae9f Mon Sep 17 00:00:00 2001
From: Reviewer <review@example.com>
Date: Tue, 2 Jun 2026 18:24:30 +0000
Subject: [PATCH v4 3/3] add helper functions for fast-path arg setup
---
src/interfaces/libpq/fe-lobj.c | 110 ++++++++++++---------------------
1 file changed, 38 insertions(+), 72 deletions(-)
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 42b2a36bda9..e8f6d958e84 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -44,6 +44,22 @@
static int lo_initialize(PGconn *conn);
static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid);
+static inline void
+lo_set_int_arg(PQArgBlock *arg, int value)
+{
+ arg->isint = 1;
+ arg->len = 4;
+ arg->u.integer = value;
+}
+
+static inline void
+lo_set_ptr_arg(PQArgBlock *arg, const void *ptr, int len)
+{
+ arg->isint = 0;
+ arg->len = len;
+ arg->u.ptr = (int *) unconstify(void *, ptr);
+}
+
/*
* lo_open
* opens an existing large object
@@ -62,13 +78,8 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
-
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = mode;
+ lo_set_int_arg(&argv[0], lobjId);
+ lo_set_int_arg(&argv[1], mode);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -101,9 +112,7 @@ lo_close(PGconn *conn, int fd)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -159,13 +168,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
-
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ lo_set_int_arg(&argv[0], fd);
+ lo_set_int_arg(&argv[1], (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
&retval, -1, &result_len, 1, argv, 2);
@@ -207,14 +211,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
len = pg_hton64(len);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &len;
+ lo_set_ptr_arg(&argv[1], &len, 8);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
&retval, -1, &result_len, 1, argv, 2);
@@ -261,13 +261,8 @@ 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;
-
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ lo_set_int_arg(&argv[0], fd);
+ lo_set_int_arg(&argv[1], (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_read,
(void *) buf, len, &result_len, 0, argv, 2);
@@ -312,13 +307,8 @@ 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;
-
- argv[1].isint = 0;
- argv[1].len = (int) len;
- argv[1].u.ptr = (int *) unconstify(char *, buf);
+ lo_set_int_arg(&argv[0], fd);
+ lo_set_ptr_arg(&argv[1], buf, (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
&retval, -1, &result_len, 1, argv, 2);
@@ -349,17 +339,9 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
-
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = offset;
-
- argv[2].isint = 1;
- argv[2].len = 4;
- argv[2].u.integer = whence;
+ lo_set_int_arg(&argv[0], fd);
+ lo_set_int_arg(&argv[1], offset);
+ lo_set_int_arg(&argv[2], whence);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
&retval, -1, &result_len, 1, argv, 3);
@@ -397,18 +379,12 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
offset = pg_hton64(offset);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &offset;
+ lo_set_ptr_arg(&argv[1], &offset, 8);
- argv[2].isint = 1;
- argv[2].len = 4;
- argv[2].u.integer = whence;
+ lo_set_int_arg(&argv[2], whence);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64,
(void *) &retval, sizeof(retval), &result_len, 0, argv, 3);
@@ -443,9 +419,7 @@ lo_creat(PGconn *conn, int mode)
if (lo_initialize(conn) < 0)
return InvalidOid;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = mode;
+ lo_set_int_arg(&argv[0], mode);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -487,9 +461,7 @@ lo_create(PGconn *conn, Oid lobjId)
return InvalidOid;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -520,9 +492,7 @@ lo_tell(PGconn *conn, int fd)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
&retval, -1, &result_len, 1, argv, 1);
@@ -560,9 +530,7 @@ lo_tell64(PGconn *conn, int fd)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64,
(void *) &retval, sizeof(retval), &result_len, 0, argv, 1);
@@ -594,9 +562,7 @@ lo_unlink(PGconn *conn, Oid lobjId)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
&retval, -1, &result_len, 1, argv, 1);
--
2.50.1 (Apple Git-155)
Attachments:
[text/plain] v4-0001-remove-PQfn.patch (15.4K, ../../aiGSa7XpUTRFt-be@nathan/2-v4-0001-remove-PQfn.patch)
download | inline diff:
From 63486371b3531be66bb8e0c62ef1b8cc7e95abb5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 1 Jun 2026 16:55:00 -0500
Subject: [PATCH v4 1/3] remove PQfn
---
.../appendix-obsolete-libpq-fastpath.sgml | 24 ++++
doc/src/sgml/appendix-obsolete.sgml | 1 +
doc/src/sgml/filelist.sgml | 1 +
doc/src/sgml/libpq.sgml | 117 +-----------------
src/backend/tcop/fastpath.c | 4 +-
src/include/tcop/dest.h | 4 +-
src/interfaces/libpq/fe-exec.c | 42 +++++--
src/interfaces/libpq/fe-lobj.c | 38 +++---
8 files changed, 86 insertions(+), 145 deletions(-)
create mode 100644 doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
diff --git a/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml b/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
new file mode 100644
index 00000000000..e42c2ece487
--- /dev/null
+++ b/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
@@ -0,0 +1,24 @@
+<!-- doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml -->
+<!--
+ See doc/src/sgml/appendix-obsolete.sgml for why this file exists. Do not change the id attribute.
+-->
+
+<sect1 id="libpq-fastpath">
+ <title><application>libpq</application> Fast-Path Interface Removed</title>
+
+ <indexterm zone="libpq-fastpath">
+ <primary>fast path</primary>
+ </indexterm>
+
+ <para>
+ In <productname>PostgreSQL</productname> 19 and below,
+ <application>libpq</application> supported a fast-path interface to send
+ simple function calls to the server. This interface was unsafe and
+ obsolete, and thus was removed in <productname>PostgreSQL</productname> 20.
+ 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>
+
+</sect1>
diff --git a/doc/src/sgml/appendix-obsolete.sgml b/doc/src/sgml/appendix-obsolete.sgml
index cc002653052..11a033112ef 100644
--- a/doc/src/sgml/appendix-obsolete.sgml
+++ b/doc/src/sgml/appendix-obsolete.sgml
@@ -39,5 +39,6 @@
&obsolete-pgresetxlog;
&obsolete-pgreceivexlog;
&obsolete-auth-radius;
+ &obsolete-libpq-fastpath;
</appendix>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 25a85082759..7dbe741d729 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -209,3 +209,4 @@
<!ENTITY obsolete-pgresetxlog SYSTEM "appendix-obsolete-pgresetxlog.sgml">
<!ENTITY obsolete-pgreceivexlog SYSTEM "appendix-obsolete-pgreceivexlog.sgml">
<!ENTITY obsolete-auth-radius SYSTEM "appendix-obsolete-auth-radius.sgml">
+<!ENTITY obsolete-libpq-fastpath SYSTEM "appendix-obsolete-libpq-fastpath.sgml">
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..123e7f03902 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -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..5379e4ad9f5 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -11,7 +11,9 @@
* src/backend/tcop/fastpath.c
*
* NOTES
- * This cruft is the server side of PQfn.
+ * This cruft is the server side of PQfn. libpq's PQfn() was retired in
+ * v20 and now always errors, but the server code is retained for the
+ * benefit of 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..2f034d70e65 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2986,10 +2986,10 @@ 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 (unless the connection is in the wrong state, in
+ * which case it returns NULL).
* ----------------
*/
@@ -3002,15 +3002,43 @@ PQfn(PGconn *conn,
const PQArgBlock *args,
int nargs)
{
- return PQnfn(conn, fnid, result_buf, -1, result_len,
- result_is_int, args, 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;
+ }
+
+ libpq_append_conn_error(conn, "PQfn() is no longer supported; use a prepared statement or PQexecParams() with binary results instead");
+ pqSaveErrorResult(conn);
+ return pqPrepareAsyncResult(conn);
}
/*
* 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.
+ * this verification. This is currently only used by the frontend LO
+ * interface and will hopefully be removed down the road.
*/
PGresult *
PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 12a32fcbaf3..1660c969f58 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -72,7 +72,7 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
argv[1].len = 4;
argv[1].u.integer = mode;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -106,8 +106,8 @@ lo_close(PGconn *conn, int fd)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -169,8 +169,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
argv[1].len = 4;
argv[1].u.integer = (int) len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -218,8 +218,8 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -322,8 +322,8 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
argv[1].len = (int) len;
argv[1].u.ptr = (int *) unconstify(char *, buf);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -363,8 +363,8 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
argv[2].len = 4;
argv[2].u.integer = whence;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek,
- &retval, &result_len, 1, argv, 3);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
+ &retval, -1, &result_len, 1, argv, 3);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -448,8 +448,8 @@ lo_creat(PGconn *conn, int mode)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -492,8 +492,8 @@ lo_create(PGconn *conn, Oid lobjId)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -526,8 +526,8 @@ lo_tell(PGconn *conn, int fd)
argv[0].len = 4;
argv[0].u.integer = fd;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_tell,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -600,8 +600,8 @@ lo_unlink(PGconn *conn, Oid lobjId)
argv[0].len = 4;
argv[0].u.integer = lobjId;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
--
2.50.1 (Apple Git-155)
[text/plain] v4-0002-remove-lo_hton64-and-lo_ntoh64.patch (2.6K, ../../aiGSa7XpUTRFt-be@nathan/3-v4-0002-remove-lo_hton64-and-lo_ntoh64.patch)
download | inline diff:
From 181028cbf820700f104f48dfc0e94d0c7f5e42e0 Mon Sep 17 00:00:00 2001
From: Reviewer <review@example.com>
Date: Tue, 2 Jun 2026 17:22:48 +0000
Subject: [PATCH v4 2/3] remove lo_hton64() and lo_ntoh64()
---
src/interfaces/libpq/fe-lobj.c | 58 +++-------------------------------
1 file changed, 4 insertions(+), 54 deletions(-)
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 1660c969f58..42b2a36bda9 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -43,8 +43,6 @@
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);
/*
* lo_open
@@ -213,7 +211,7 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[0].len = 4;
argv[0].u.integer = fd;
- len = lo_hton64(len);
+ len = pg_hton64(len);
argv[1].isint = 0;
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
@@ -403,7 +401,7 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
argv[0].len = 4;
argv[0].u.integer = fd;
- offset = lo_hton64(offset);
+ offset = pg_hton64(offset);
argv[1].isint = 0;
argv[1].len = 8;
argv[1].u.ptr = (int *) &offset;
@@ -417,7 +415,7 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh64(retval);
}
else
{
@@ -571,7 +569,7 @@ lo_tell64(PGconn *conn, int fd)
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
- return lo_ntoh64(retval);
+ return pg_ntoh64(retval);
}
else
{
@@ -1014,51 +1012,3 @@ lo_initialize(PGconn *conn)
conn->lobjfuncs = lobjfuncs;
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;
-}
--
2.50.1 (Apple Git-155)
[text/plain] v4-0003-add-helper-functions-for-fast-path-arg-setup.patch (6.0K, ../../aiGSa7XpUTRFt-be@nathan/4-v4-0003-add-helper-functions-for-fast-path-arg-setup.patch)
download | inline diff:
From 8e5a937d5e302a56ff2118ce3ea4f69e7db5ae9f Mon Sep 17 00:00:00 2001
From: Reviewer <review@example.com>
Date: Tue, 2 Jun 2026 18:24:30 +0000
Subject: [PATCH v4 3/3] add helper functions for fast-path arg setup
---
src/interfaces/libpq/fe-lobj.c | 110 ++++++++++++---------------------
1 file changed, 38 insertions(+), 72 deletions(-)
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 42b2a36bda9..e8f6d958e84 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -44,6 +44,22 @@
static int lo_initialize(PGconn *conn);
static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid);
+static inline void
+lo_set_int_arg(PQArgBlock *arg, int value)
+{
+ arg->isint = 1;
+ arg->len = 4;
+ arg->u.integer = value;
+}
+
+static inline void
+lo_set_ptr_arg(PQArgBlock *arg, const void *ptr, int len)
+{
+ arg->isint = 0;
+ arg->len = len;
+ arg->u.ptr = (int *) unconstify(void *, ptr);
+}
+
/*
* lo_open
* opens an existing large object
@@ -62,13 +78,8 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
-
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = mode;
+ lo_set_int_arg(&argv[0], lobjId);
+ lo_set_int_arg(&argv[1], mode);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -101,9 +112,7 @@ lo_close(PGconn *conn, int fd)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -159,13 +168,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
-
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ lo_set_int_arg(&argv[0], fd);
+ lo_set_int_arg(&argv[1], (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
&retval, -1, &result_len, 1, argv, 2);
@@ -207,14 +211,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
len = pg_hton64(len);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &len;
+ lo_set_ptr_arg(&argv[1], &len, 8);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
&retval, -1, &result_len, 1, argv, 2);
@@ -261,13 +261,8 @@ 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;
-
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = (int) len;
+ lo_set_int_arg(&argv[0], fd);
+ lo_set_int_arg(&argv[1], (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_read,
(void *) buf, len, &result_len, 0, argv, 2);
@@ -312,13 +307,8 @@ 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;
-
- argv[1].isint = 0;
- argv[1].len = (int) len;
- argv[1].u.ptr = (int *) unconstify(char *, buf);
+ lo_set_int_arg(&argv[0], fd);
+ lo_set_ptr_arg(&argv[1], buf, (int) len);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
&retval, -1, &result_len, 1, argv, 2);
@@ -349,17 +339,9 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
-
- argv[1].isint = 1;
- argv[1].len = 4;
- argv[1].u.integer = offset;
-
- argv[2].isint = 1;
- argv[2].len = 4;
- argv[2].u.integer = whence;
+ lo_set_int_arg(&argv[0], fd);
+ lo_set_int_arg(&argv[1], offset);
+ lo_set_int_arg(&argv[2], whence);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
&retval, -1, &result_len, 1, argv, 3);
@@ -397,18 +379,12 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
offset = pg_hton64(offset);
- argv[1].isint = 0;
- argv[1].len = 8;
- argv[1].u.ptr = (int *) &offset;
+ lo_set_ptr_arg(&argv[1], &offset, 8);
- argv[2].isint = 1;
- argv[2].len = 4;
- argv[2].u.integer = whence;
+ lo_set_int_arg(&argv[2], whence);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64,
(void *) &retval, sizeof(retval), &result_len, 0, argv, 3);
@@ -443,9 +419,7 @@ lo_creat(PGconn *conn, int mode)
if (lo_initialize(conn) < 0)
return InvalidOid;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = mode;
+ lo_set_int_arg(&argv[0], mode);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -487,9 +461,7 @@ lo_create(PGconn *conn, Oid lobjId)
return InvalidOid;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
&retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
@@ -520,9 +492,7 @@ lo_tell(PGconn *conn, int fd)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
&retval, -1, &result_len, 1, argv, 1);
@@ -560,9 +530,7 @@ lo_tell64(PGconn *conn, int fd)
return -1;
}
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = fd;
+ lo_set_int_arg(&argv[0], fd);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64,
(void *) &retval, sizeof(retval), &result_len, 0, argv, 1);
@@ -594,9 +562,7 @@ lo_unlink(PGconn *conn, Oid lobjId)
if (lo_initialize(conn) < 0)
return -1;
- argv[0].isint = 1;
- argv[0].len = 4;
- argv[0].u.integer = lobjId;
+ lo_set_int_arg(&argv[0], lobjId);
res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
&retval, -1, &result_len, 1, argv, 1);
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-08-06 18:41 Nathan Bossart <nathandbossart@gmail.com>
parent: Nathan Bossart <nathandbossart@gmail.com>
0 siblings, 1 reply; 20+ messages in thread
From: Nathan Bossart @ 2026-08-06 18:41 UTC (permalink / raw)
To: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; pgsql-hackers
Here is a rebased 0001 with a proper commit message. I went ahead and
committed 0002, and I've decided to abandon 0003 (it doesn't seem worth the
energy).
--
nathan
From cfa569866f3e76b28ae8933f5735e76b9f632993 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Thu, 6 Aug 2026 13:40:36 -0500
Subject: [PATCH v5 1/1] Remove PQfn().
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
PQfn() has been documented as somewhat obsolete since commit
efc3a25bb0 (2003), and commit bd48114937 recently marked it unsafe
because it may write past the end of result_buf when result_is_int
is 0. Furthermore, searches of publicly available code turned up
no callers, only language bindings that expose the function
without using it, so it seems unlikely that removing it will cause
much trouble in practice.
Since the symbol is exported, deleting it outright isn't an option,
so instead this commit teaches PQfn() to fail unconditionally with
an error that suggests alternatives. The documentation for the
fast-path interface is replaced with a stub in the "Obsolete or
Renamed Features" appendix. The server's fast-path support is
retained for the benefit of older clients.
The frontend large object interface, the only in-tree caller, now
uses PQnfn(), the private version of PQfn() added by commit
bd48114937. I considered converting this code away from the
fast-path protocol entirely, but prepared statements can be
deallocated out from under libpq (e.g., by DISCARD ALL), and
PQexecParams() proved noticeably slower, so that is left as a
future exercise.
Reviewed-by: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
Discussion: https://postgr.es/m/ahXE28klgxIJXBLq%40nathan
---
.../appendix-obsolete-libpq-fastpath.sgml | 24 ++++
doc/src/sgml/appendix-obsolete.sgml | 1 +
doc/src/sgml/filelist.sgml | 1 +
doc/src/sgml/libpq.sgml | 117 +-----------------
src/backend/tcop/fastpath.c | 4 +-
src/include/tcop/dest.h | 4 +-
src/interfaces/libpq/fe-exec.c | 42 +++++--
src/interfaces/libpq/fe-lobj.c | 38 +++---
8 files changed, 86 insertions(+), 145 deletions(-)
create mode 100644 doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
diff --git a/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml b/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
new file mode 100644
index 00000000000..e42c2ece487
--- /dev/null
+++ b/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
@@ -0,0 +1,24 @@
+<!-- doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml -->
+<!--
+ See doc/src/sgml/appendix-obsolete.sgml for why this file exists. Do not change the id attribute.
+-->
+
+<sect1 id="libpq-fastpath">
+ <title><application>libpq</application> Fast-Path Interface Removed</title>
+
+ <indexterm zone="libpq-fastpath">
+ <primary>fast path</primary>
+ </indexterm>
+
+ <para>
+ In <productname>PostgreSQL</productname> 19 and below,
+ <application>libpq</application> supported a fast-path interface to send
+ simple function calls to the server. This interface was unsafe and
+ obsolete, and thus was removed in <productname>PostgreSQL</productname> 20.
+ 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>
+
+</sect1>
diff --git a/doc/src/sgml/appendix-obsolete.sgml b/doc/src/sgml/appendix-obsolete.sgml
index 759a65cb5e0..23d9cc729ef 100644
--- a/doc/src/sgml/appendix-obsolete.sgml
+++ b/doc/src/sgml/appendix-obsolete.sgml
@@ -40,5 +40,6 @@
&obsolete-pgreceivexlog;
&obsolete-auth-radius;
&obsolete-refint;
+ &obsolete-libpq-fastpath;
</appendix>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 66ea8b988a1..0797dcf96da 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -210,3 +210,4 @@
<!ENTITY obsolete-pgreceivexlog SYSTEM "appendix-obsolete-pgreceivexlog.sgml">
<!ENTITY obsolete-auth-radius SYSTEM "appendix-obsolete-auth-radius.sgml">
<!ENTITY obsolete-refint SYSTEM "appendix-obsolete-refint.sgml">
+<!ENTITY obsolete-libpq-fastpath SYSTEM "appendix-obsolete-libpq-fastpath.sgml">
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..123e7f03902 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -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..5379e4ad9f5 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -11,7 +11,9 @@
* src/backend/tcop/fastpath.c
*
* NOTES
- * This cruft is the server side of PQfn.
+ * This cruft is the server side of PQfn. libpq's PQfn() was retired in
+ * v20 and now always errors, but the server code is retained for the
+ * benefit of 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..2f034d70e65 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2986,10 +2986,10 @@ 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 (unless the connection is in the wrong state, in
+ * which case it returns NULL).
* ----------------
*/
@@ -3002,15 +3002,43 @@ PQfn(PGconn *conn,
const PQArgBlock *args,
int nargs)
{
- return PQnfn(conn, fnid, result_buf, -1, result_len,
- result_is_int, args, 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;
+ }
+
+ libpq_append_conn_error(conn, "PQfn() is no longer supported; use a prepared statement or PQexecParams() with binary results instead");
+ pqSaveErrorResult(conn);
+ return pqPrepareAsyncResult(conn);
}
/*
* 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.
+ * this verification. This is currently only used by the frontend LO
+ * interface and will hopefully be removed down the road.
*/
PGresult *
PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index c914342c010..cad303f31fe 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -70,7 +70,7 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
argv[1].len = 4;
argv[1].u.integer = mode;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -104,8 +104,8 @@ lo_close(PGconn *conn, int fd)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -167,8 +167,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
argv[1].len = 4;
argv[1].u.integer = (int) len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -216,8 +216,8 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -320,8 +320,8 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
argv[1].len = (int) len;
argv[1].u.ptr = (int *) unconstify(char *, buf);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -361,8 +361,8 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
argv[2].len = 4;
argv[2].u.integer = whence;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek,
- &retval, &result_len, 1, argv, 3);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
+ &retval, -1, &result_len, 1, argv, 3);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -446,8 +446,8 @@ lo_creat(PGconn *conn, int mode)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -490,8 +490,8 @@ lo_create(PGconn *conn, Oid lobjId)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -524,8 +524,8 @@ lo_tell(PGconn *conn, int fd)
argv[0].len = 4;
argv[0].u.integer = fd;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_tell,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -598,8 +598,8 @@ lo_unlink(PGconn *conn, Oid lobjId)
argv[0].len = 4;
argv[0].u.integer = lobjId;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
--
2.50.1 (Apple Git-155)
Attachments:
[text/plain] v5-0001-Remove-PQfn.patch (16.8K, ../../anTVPa_Tsz5pr81T@nathan/2-v5-0001-Remove-PQfn.patch)
download | inline diff:
From cfa569866f3e76b28ae8933f5735e76b9f632993 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Thu, 6 Aug 2026 13:40:36 -0500
Subject: [PATCH v5 1/1] Remove PQfn().
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
PQfn() has been documented as somewhat obsolete since commit
efc3a25bb0 (2003), and commit bd48114937 recently marked it unsafe
because it may write past the end of result_buf when result_is_int
is 0. Furthermore, searches of publicly available code turned up
no callers, only language bindings that expose the function
without using it, so it seems unlikely that removing it will cause
much trouble in practice.
Since the symbol is exported, deleting it outright isn't an option,
so instead this commit teaches PQfn() to fail unconditionally with
an error that suggests alternatives. The documentation for the
fast-path interface is replaced with a stub in the "Obsolete or
Renamed Features" appendix. The server's fast-path support is
retained for the benefit of older clients.
The frontend large object interface, the only in-tree caller, now
uses PQnfn(), the private version of PQfn() added by commit
bd48114937. I considered converting this code away from the
fast-path protocol entirely, but prepared statements can be
deallocated out from under libpq (e.g., by DISCARD ALL), and
PQexecParams() proved noticeably slower, so that is left as a
future exercise.
Reviewed-by: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
Discussion: https://postgr.es/m/ahXE28klgxIJXBLq%40nathan
---
.../appendix-obsolete-libpq-fastpath.sgml | 24 ++++
doc/src/sgml/appendix-obsolete.sgml | 1 +
doc/src/sgml/filelist.sgml | 1 +
doc/src/sgml/libpq.sgml | 117 +-----------------
src/backend/tcop/fastpath.c | 4 +-
src/include/tcop/dest.h | 4 +-
src/interfaces/libpq/fe-exec.c | 42 +++++--
src/interfaces/libpq/fe-lobj.c | 38 +++---
8 files changed, 86 insertions(+), 145 deletions(-)
create mode 100644 doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
diff --git a/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml b/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
new file mode 100644
index 00000000000..e42c2ece487
--- /dev/null
+++ b/doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml
@@ -0,0 +1,24 @@
+<!-- doc/src/sgml/appendix-obsolete-libpq-fastpath.sgml -->
+<!--
+ See doc/src/sgml/appendix-obsolete.sgml for why this file exists. Do not change the id attribute.
+-->
+
+<sect1 id="libpq-fastpath">
+ <title><application>libpq</application> Fast-Path Interface Removed</title>
+
+ <indexterm zone="libpq-fastpath">
+ <primary>fast path</primary>
+ </indexterm>
+
+ <para>
+ In <productname>PostgreSQL</productname> 19 and below,
+ <application>libpq</application> supported a fast-path interface to send
+ simple function calls to the server. This interface was unsafe and
+ obsolete, and thus was removed in <productname>PostgreSQL</productname> 20.
+ 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>
+
+</sect1>
diff --git a/doc/src/sgml/appendix-obsolete.sgml b/doc/src/sgml/appendix-obsolete.sgml
index 759a65cb5e0..23d9cc729ef 100644
--- a/doc/src/sgml/appendix-obsolete.sgml
+++ b/doc/src/sgml/appendix-obsolete.sgml
@@ -40,5 +40,6 @@
&obsolete-pgreceivexlog;
&obsolete-auth-radius;
&obsolete-refint;
+ &obsolete-libpq-fastpath;
</appendix>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 66ea8b988a1..0797dcf96da 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -210,3 +210,4 @@
<!ENTITY obsolete-pgreceivexlog SYSTEM "appendix-obsolete-pgreceivexlog.sgml">
<!ENTITY obsolete-auth-radius SYSTEM "appendix-obsolete-auth-radius.sgml">
<!ENTITY obsolete-refint SYSTEM "appendix-obsolete-refint.sgml">
+<!ENTITY obsolete-libpq-fastpath SYSTEM "appendix-obsolete-libpq-fastpath.sgml">
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..123e7f03902 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -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..5379e4ad9f5 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -11,7 +11,9 @@
* src/backend/tcop/fastpath.c
*
* NOTES
- * This cruft is the server side of PQfn.
+ * This cruft is the server side of PQfn. libpq's PQfn() was retired in
+ * v20 and now always errors, but the server code is retained for the
+ * benefit of 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..2f034d70e65 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2986,10 +2986,10 @@ 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 (unless the connection is in the wrong state, in
+ * which case it returns NULL).
* ----------------
*/
@@ -3002,15 +3002,43 @@ PQfn(PGconn *conn,
const PQArgBlock *args,
int nargs)
{
- return PQnfn(conn, fnid, result_buf, -1, result_len,
- result_is_int, args, 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;
+ }
+
+ libpq_append_conn_error(conn, "PQfn() is no longer supported; use a prepared statement or PQexecParams() with binary results instead");
+ pqSaveErrorResult(conn);
+ return pqPrepareAsyncResult(conn);
}
/*
* 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.
+ * this verification. This is currently only used by the frontend LO
+ * interface and will hopefully be removed down the road.
*/
PGresult *
PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index c914342c010..cad303f31fe 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -70,7 +70,7 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
argv[1].len = 4;
argv[1].u.integer = mode;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_open, &fd, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -104,8 +104,8 @@ lo_close(PGconn *conn, int fd)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_close,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -167,8 +167,8 @@ lo_truncate(PGconn *conn, int fd, size_t len)
argv[1].len = 4;
argv[1].u.integer = (int) len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -216,8 +216,8 @@ lo_truncate64(PGconn *conn, int fd, int64_t len)
argv[1].len = 8;
argv[1].u.ptr = (int *) &len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_truncate64,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
@@ -320,8 +320,8 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
argv[1].len = (int) len;
argv[1].u.ptr = (int *) unconstify(char *, buf);
- res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
- &retval, &result_len, 1, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_write,
+ &retval, -1, &result_len, 1, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -361,8 +361,8 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
argv[2].len = 4;
argv[2].u.integer = whence;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek,
- &retval, &result_len, 1, argv, 3);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek,
+ &retval, -1, &result_len, 1, argv, 3);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -446,8 +446,8 @@ lo_creat(PGconn *conn, int mode)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_creat,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -490,8 +490,8 @@ lo_create(PGconn *conn, Oid lobjId)
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);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_create,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -524,8 +524,8 @@ lo_tell(PGconn *conn, int fd)
argv[0].len = 4;
argv[0].u.integer = fd;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_tell,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -598,8 +598,8 @@ lo_unlink(PGconn *conn, Oid lobjId)
argv[0].len = 4;
argv[0].u.integer = lobjId;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
- &retval, &result_len, 1, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_unlink,
+ &retval, -1, &result_len, 1, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: future of PQfn()
@ 2026-08-14 16:32 Nathan Bossart <nathandbossart@gmail.com>
parent: Nathan Bossart <nathandbossart@gmail.com>
0 siblings, 0 replies; 20+ messages in thread
From: Nathan Bossart @ 2026-08-14 16:32 UTC (permalink / raw)
To: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; pgsql-hackers
Committed.
--
nathan
^ permalink raw reply [nested|flat] 20+ messages in thread
end of thread, other threads:[~2026-08-14 16:32 UTC | newest]
Thread overview: 20+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-05-26 16:05 future of PQfn() Nathan Bossart <nathandbossart@gmail.com>
2026-05-26 16:53 ` Christoph Berg <myon@debian.org>
2026-05-26 16:55 ` Christoph Berg <myon@debian.org>
2026-05-26 17:42 ` Jacob Champion <jacob.champion@enterprisedb.com>
2026-05-26 19:55 ` Nathan Bossart <nathandbossart@gmail.com>
2026-05-27 21:39 ` Jacob Champion <jacob.champion@enterprisedb.com>
2026-05-28 15:17 ` Nathan Bossart <nathandbossart@gmail.com>
2026-05-29 15:14 ` Nathan Bossart <nathandbossart@gmail.com>
2026-05-29 15:43 ` Jacob Champion <jacob.champion@enterprisedb.com>
2026-05-29 16:10 ` Nathan Bossart <nathandbossart@gmail.com>
2026-05-29 16:33 ` Jacob Champion <jacob.champion@enterprisedb.com>
2026-05-29 16:42 ` Nathan Bossart <nathandbossart@gmail.com>
2026-05-29 17:04 ` Jacob Champion <jacob.champion@enterprisedb.com>
2026-06-03 21:36 ` Nathan Bossart <nathandbossart@gmail.com>
2026-06-04 10:39 ` Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
2026-06-04 14:57 ` Nathan Bossart <nathandbossart@gmail.com>
2026-08-06 18:41 ` Nathan Bossart <nathandbossart@gmail.com>
2026-08-14 16:32 ` Nathan Bossart <nathandbossart@gmail.com>
2026-05-26 21:36 ` Jelte Fennema-Nio <postgres@jeltef.nl>
2026-05-27 18:23 ` Nathan Bossart <nathandbossart@gmail.com>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox