From 63486371b3531be66bb8e0c62ef1b8cc7e95abb5 Mon Sep 17 00:00:00 2001 From: Nathan Bossart 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 @@ + + + + + <application>libpq</application> Fast-Path Interface Removed + + + fast path + + + + In PostgreSQL 19 and below, + libpq supported a fast-path interface to send + simple function calls to the server. This interface was unsafe and + obsolete, and thus was removed in PostgreSQL 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. + + + 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; 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 @@ + 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 COPY. Using synchronous command execution functions - such as PQfn, + such as PQexec, PQexecParams, PQprepare, @@ -7046,121 +7046,6 @@ int PQrequestCancel(PGconn *conn); - - The Fast-Path Interface - - - fast path - - - - PostgreSQL provides a fast-path interface - to send simple function calls to the server. - - - - - This interface is unsafe and should not be used. When - result_is_int is set to 0, - PQfn may write data beyond the end of - result_buf, 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. - - - - - The function PQfnPQfn - requests execution of a server function via the fast-path interface: - -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; - - - - - The fnid argument is the OID of the function to be - executed. args and nargs define the - parameters to be passed to the function; they must match the declared - function argument list. When the isint field of a - parameter structure is true, the u.integer 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 isint - is false, the indicated number of bytes at *u.ptr 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 u.ptr as being of - type int * is historical; it would be better to consider - it void *.) - result_buf 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 - result_len. If a 2- or 4-byte integer result - is expected, set result_is_int to 1, otherwise - set it to 0. Setting result_is_int to 1 causes - libpq to byte-swap the value if necessary, so that it - is delivered as a proper int value for the client machine; - note that a 4-byte integer is delivered into *result_buf - for either allowed result size. - When result_is_int is 0, the binary-format byte string - sent by the server is returned unmodified. (In this case it's better - to consider result_buf as being of - type void *.) - - - - PQfn always returns a valid - PGresult pointer, with - status PGRES_COMMAND_OK for success - or PGRES_FATAL_ERROR if some problem was encountered. - The result status should be - checked before the result is used. The caller is responsible for - freeing the PGresult with - when it is no longer needed. - - - - To pass a NULL argument to the function, set - the len field of that parameter structure - to -1; the isint - and u fields are then irrelevant. - - - - If the function returns NULL, *result_len is set - to -1, and *result_buf is not - modified. - - - - 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. - - - - Asynchronous Notification 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)