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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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)