From 2a965c838ae820bf448106ed391bb7d84c6dc00f Mon Sep 17 00:00:00 2001 From: Nathan Bossart 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); can be applied to this PGresult to obtain information about the parameters of the prepared statement, and the functions - , , + , , etc. provide information about the result columns (if any) of the statement. @@ -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..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)