From cfa569866f3e76b28ae8933f5735e76b9f632993 Mon Sep 17 00:00:00 2001 From: Nathan Bossart 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 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 @@ + + + + + <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 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; 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 @@ + 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 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)