[PATCH v2 3/3] remove support for PQfn
Nathan Bossart <nathan@postgresql.org>
From: Nathan Bossart <nathan@postgresql.org>
To:
Date: 2026-05-22T19:00:50Z
Lists: pgsql-hackers
---
doc/src/sgml/libpq.sgml | 119 +-------------
src/backend/tcop/fastpath.c | 3 +-
src/include/tcop/dest.h | 4 +-
src/interfaces/libpq/fe-exec.c | 54 +-----
src/interfaces/libpq/fe-protocol3.c | 246 ----------------------------
src/interfaces/libpq/libpq-int.h | 5 -
6 files changed, 12 insertions(+), 419 deletions(-)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..812e9089bfd 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -3738,7 +3738,7 @@ PGresult *PQdescribePrepared(PGconn *conn, const char *stmtName);
<xref linkend="libpq-PQparamtype"/> can be applied to this
<structname>PGresult</structname> to obtain information about the parameters
of the prepared statement, and the functions
- <xref linkend="libpq-PQnfields"/>, <xref linkend="libpq-PQfname"/>,
+ <xref linkend="libpq-PQnfields"/>,
<xref linkend="libpq-PQftype"/>, etc. provide information about the
result columns (if any) of the statement.
</para>
@@ -5887,7 +5887,7 @@ int PQflush(PGconn *conn);
are permitted, command strings containing multiple SQL commands are
disallowed, and so is <literal>COPY</literal>.
Using synchronous command execution functions
- such as <function>PQfn</function>,
+ such as
<function>PQexec</function>,
<function>PQexecParams</function>,
<function>PQprepare</function>,
@@ -7046,121 +7046,6 @@ int PQrequestCancel(PGconn *conn);
</sect2>
</sect1>
- <sect1 id="libpq-fastpath">
- <title>The Fast-Path Interface</title>
-
- <indexterm zone="libpq-fastpath">
- <primary>fast path</primary>
- </indexterm>
-
- <para>
- <productname>PostgreSQL</productname> provides a fast-path interface
- to send simple function calls to the server.
- </para>
-
- <warning>
- <para>
- This interface is unsafe and should not be used. When
- <parameter>result_is_int</parameter> is set to <literal>0</literal>,
- <function>PQfn</function> may write data beyond the end of
- <parameter>result_buf</parameter>, regardless of whether the buffer has
- enough space for the requested number of bytes. Furthermore, it is
- obsolete, as one can achieve similar
- performance and greater functionality by setting up a prepared
- statement to define the function call. Then, executing the statement
- with binary transmission of parameters and results substitutes for a
- fast-path function call.
- </para>
- </warning>
-
- <para>
- The function <function id="libpq-PQfn">PQfn</function><indexterm><primary>PQfn</primary></indexterm>
- requests execution of a server function via the fast-path interface:
-<synopsis>
-PGresult *PQfn(PGconn *conn,
- int fnid,
- int *result_buf,
- int *result_len,
- int result_is_int,
- const PQArgBlock *args,
- int nargs);
-
-typedef struct
-{
- int len;
- int isint;
- union
- {
- int *ptr;
- int integer;
- } u;
-} PQArgBlock;
-</synopsis>
- </para>
-
- <para>
- The <parameter>fnid</parameter> argument is the OID of the function to be
- executed. <parameter>args</parameter> and <parameter>nargs</parameter> define the
- parameters to be passed to the function; they must match the declared
- function argument list. When the <parameter>isint</parameter> field of a
- parameter structure is true, the <parameter>u.integer</parameter> value is sent
- to the server as an integer of the indicated length (this must be
- 2 or 4 bytes); proper byte-swapping occurs. When <parameter>isint</parameter>
- is false, the indicated number of bytes at <parameter>*u.ptr</parameter> are
- sent with no processing; the data must be in the format expected by
- the server for binary transmission of the function's argument data
- type. (The declaration of <parameter>u.ptr</parameter> as being of
- type <type>int *</type> is historical; it would be better to consider
- it <type>void *</type>.)
- <parameter>result_buf</parameter> points to the buffer in which to place
- the function's return value. The caller must have allocated sufficient
- space to store the return value. (There is no check!) The actual result
- length in bytes will be returned in the integer pointed to by
- <parameter>result_len</parameter>. If a 2- or 4-byte integer result
- is expected, set <parameter>result_is_int</parameter> to 1, otherwise
- set it to 0. Setting <parameter>result_is_int</parameter> to 1 causes
- <application>libpq</application> to byte-swap the value if necessary, so that it
- is delivered as a proper <type>int</type> value for the client machine;
- note that a 4-byte integer is delivered into <parameter>*result_buf</parameter>
- for either allowed result size.
- When <parameter>result_is_int</parameter> is 0, the binary-format byte string
- sent by the server is returned unmodified. (In this case it's better
- to consider <parameter>result_buf</parameter> as being of
- type <type>void *</type>.)
- </para>
-
- <para>
- <function>PQfn</function> always returns a valid
- <structname>PGresult</structname> pointer, with
- status <literal>PGRES_COMMAND_OK</literal> for success
- or <literal>PGRES_FATAL_ERROR</literal> if some problem was encountered.
- The result status should be
- checked before the result is used. The caller is responsible for
- freeing the <structname>PGresult</structname> with
- <xref linkend="libpq-PQclear"/> when it is no longer needed.
- </para>
-
- <para>
- To pass a NULL argument to the function, set
- the <parameter>len</parameter> field of that parameter structure
- to <literal>-1</literal>; the <parameter>isint</parameter>
- and <parameter>u</parameter> fields are then irrelevant.
- </para>
-
- <para>
- If the function returns NULL, <parameter>*result_len</parameter> is set
- to <literal>-1</literal>, and <parameter>*result_buf</parameter> is not
- modified.
- </para>
-
- <para>
- Note that it is not possible to handle set-valued results when using
- this interface. Also, the function must be a plain function, not an
- aggregate, window function, or procedure.
- </para>
-
- </sect1>
-
<sect1 id="libpq-notify">
<title>Asynchronous Notification</title>
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 52772bc90a8..2a5c45efffe 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -11,7 +11,8 @@
* src/backend/tcop/fastpath.c
*
* NOTES
- * This cruft is the server side of PQfn.
+ * This cruft is the server side of PQfn, which was removed in v20 but
+ * may still be used by older clients.
*
*-------------------------------------------------------------------------
*/
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 103f27fc3cb..507414421ec 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -12,8 +12,8 @@
*
* - a remote process is the destination when we are
* running a backend with a frontend and the frontend executes
- * PQexec() or PQfn(). In this case, the results are sent
- * to the frontend via the functions in backend/libpq.
+ * PQexec(). In this case, the results are sent to the frontend via
+ * the functions in backend/libpq.
*
* - DestNone is the destination when the system executes
* a query internally. The results are discarded.
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 7b8edacbfde..400e1eef94b 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2986,13 +2986,11 @@ PQendcopy(PGconn *conn)
* nargs : # of arguments in args array.
*
* RETURNS
- * PGresult with status = PGRES_COMMAND_OK if successful.
- * *result_len is > 0 if there is a return value, 0 if not.
- * PGresult with status = PGRES_FATAL_ERROR if backend returns an error.
- * NULL on communications failure. conn->errorMessage will be set.
+ * This function was unsafe and is no longer supported, so it now always
+ * sets *result_len to 0 and returns a PGresult with status set to
+ * PGRES_FATAL_ERROR.
* ----------------
*/
-
PGresult *
PQfn(PGconn *conn,
int fnid,
@@ -3001,51 +2999,11 @@ PQfn(PGconn *conn,
int result_is_int,
const PQArgBlock *args,
int nargs)
-{
- return PQnfn(conn, fnid, result_buf, -1, result_len,
- result_is_int, args, nargs);
-}
-
-/*
- * PQnfn
- * Private version of PQfn() with verification that returned data fits in
- * result_buf when result_is_int == 0. Setting buf_size to -1 disables
- * this verification.
- */
-PGresult *
-PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
- int result_is_int, const PQArgBlock *args, int nargs)
{
*result_len = 0;
-
- if (!conn)
- return NULL;
-
- /*
- * Since this is the beginning of a query cycle, reset the error state.
- * However, in pipeline mode with something already queued, the error
- * buffer belongs to that command and we shouldn't clear it.
- */
- if (conn->cmd_queue_head == NULL)
- pqClearConnErrorState(conn);
-
- if (conn->pipelineStatus != PQ_PIPELINE_OFF)
- {
- libpq_append_conn_error(conn, "%s not allowed in pipeline mode", "PQfn");
- return NULL;
- }
-
- if (conn->sock == PGINVALID_SOCKET || conn->asyncStatus != PGASYNC_IDLE ||
- pgHavePendingResult(conn))
- {
- libpq_append_conn_error(conn, "connection in wrong state");
- return NULL;
- }
-
- return pqFunctionCall3(conn, fnid,
- result_buf, buf_size, result_len,
- result_is_int,
- args, nargs);
+ libpq_append_conn_error(conn, "PQfn() is no longer supported");
+ pqSaveErrorResult(conn);
+ return pqPrepareAsyncResult(conn);
}
/* ====== Pipeline mode support ======== */
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 0407d10362d..f9c55ee7905 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -2235,252 +2235,6 @@ pqEndcopy3(PGconn *conn)
return 1;
}
-
-/*
- * PQfn - Send a function call to the POSTGRES backend.
- *
- * See fe-exec.c for documentation.
- */
-PGresult *
-pqFunctionCall3(PGconn *conn, Oid fnid,
- int *result_buf, int buf_size, int *actual_result_len,
- int result_is_int,
- const PQArgBlock *args, int nargs)
-{
- bool needInput = false;
- ExecStatusType status = PGRES_FATAL_ERROR;
- char id;
- int msgLength;
- int avail;
- int i;
-
- /* already validated by PQfn */
- Assert(conn->pipelineStatus == PQ_PIPELINE_OFF);
-
- /* PQfn already validated connection state */
-
- if (pqPutMsgStart(PqMsg_FunctionCall, conn) < 0 ||
- pqPutInt(fnid, 4, conn) < 0 || /* function id */
- pqPutInt(1, 2, conn) < 0 || /* # of format codes */
- pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */
- pqPutInt(nargs, 2, conn) < 0) /* # of args */
- {
- /* error message should be set up already */
- return NULL;
- }
-
- for (i = 0; i < nargs; ++i)
- { /* len.int4 + contents */
- if (pqPutInt(args[i].len, 4, conn))
- return NULL;
- if (args[i].len == -1)
- continue; /* it's NULL */
-
- if (args[i].isint)
- {
- if (pqPutInt(args[i].u.integer, args[i].len, conn))
- return NULL;
- }
- else
- {
- if (pqPutnchar(args[i].u.ptr, args[i].len, conn))
- return NULL;
- }
- }
-
- if (pqPutInt(1, 2, conn) < 0) /* result format code: BINARY */
- return NULL;
-
- if (pqPutMsgEnd(conn) < 0 ||
- pqFlush(conn))
- return NULL;
-
- for (;;)
- {
- if (needInput)
- {
- /* Wait for some data to arrive (or for the channel to close) */
- if (pqWait(true, false, conn) ||
- pqReadData(conn) < 0)
- break;
- }
-
- /*
- * Scan the message. If we run out of data, loop around to try again.
- */
- needInput = true;
-
- conn->inCursor = conn->inStart;
- if (pqGetc(&id, conn))
- continue;
- if (pqGetInt(&msgLength, 4, conn))
- continue;
-
- /*
- * Try to validate message type/length here. A length less than 4 is
- * definitely broken. Large lengths should only be believed for a few
- * message types.
- */
- if (msgLength < 4)
- {
- handleSyncLoss(conn, id, msgLength);
- break;
- }
- if (msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(id))
- {
- handleSyncLoss(conn, id, msgLength);
- break;
- }
-
- /*
- * Can't process if message body isn't all here yet.
- */
- msgLength -= 4;
- avail = conn->inEnd - conn->inCursor;
- if (avail < msgLength)
- {
- /*
- * Before looping, enlarge the input buffer if needed to hold the
- * whole message. See notes in parseInput.
- */
- if (pqCheckInBufferSpace(conn->inCursor + (size_t) msgLength,
- conn))
- {
- /*
- * Abandon the connection. There's not much else we can
- * safely do; we can't just ignore the message or we could
- * miss important changes to the connection state.
- * pqCheckInBufferSpace() already reported the error.
- */
- handleFatalError(conn);
- break;
- }
- continue;
- }
-
- /*
- * We should see V or E response to the command, but might get N
- * and/or A notices first. We also need to swallow the final Z before
- * returning.
- */
- switch (id)
- {
- case PqMsg_FunctionCallResponse:
- if (pqGetInt(actual_result_len, 4, conn))
- continue;
- if (*actual_result_len != -1)
- {
- if (result_is_int)
- {
- if (pqGetInt(result_buf, *actual_result_len, conn))
- continue;
- }
- else
- {
- /*
- * If the server returned too much data for the
- * buffer, something fishy is going on. Abandon ship.
- */
- if (buf_size != -1 && *actual_result_len > buf_size)
- {
- libpq_append_conn_error(conn, "server returned too much data");
- handleFatalError(conn);
- return pqPrepareAsyncResult(conn);
- }
-
- if (pqGetnchar(result_buf,
- *actual_result_len,
- conn))
- continue;
- }
- }
- /* correctly finished function result message */
- status = PGRES_COMMAND_OK;
- break;
- case PqMsg_ErrorResponse:
- if (pqGetErrorNotice3(conn, true))
- continue;
- status = PGRES_FATAL_ERROR;
- break;
- case PqMsg_NotificationResponse:
- /* handle notify and go back to processing return values */
- if (getNotify(conn))
- continue;
- break;
- case PqMsg_NoticeResponse:
- /* handle notice and go back to processing return values */
- if (pqGetErrorNotice3(conn, false))
- continue;
- break;
- case PqMsg_ReadyForQuery:
- if (getReadyForQuery(conn))
- continue;
-
- /* consume the message */
- pqParseDone(conn, conn->inStart + 5 + msgLength);
-
- /*
- * If we already have a result object (probably an error), use
- * that. Otherwise, if we saw a function result message,
- * report COMMAND_OK. Otherwise, the backend violated the
- * protocol, so complain.
- */
- if (!pgHavePendingResult(conn))
- {
- if (status == PGRES_COMMAND_OK)
- {
- conn->result = PQmakeEmptyPGresult(conn, status);
- if (!conn->result)
- {
- libpq_append_conn_error(conn, "out of memory");
- pqSaveErrorResult(conn);
- }
- }
- else
- {
- libpq_append_conn_error(conn, "protocol error: no function result");
- pqSaveErrorResult(conn);
- }
- }
- /* and we're out */
- return pqPrepareAsyncResult(conn);
- case PqMsg_ParameterStatus:
- if (getParameterStatus(conn))
- continue;
- break;
- case PqMsg_PrepStmtDeallocated:
- if (getPrepStmtDeallocated(conn))
- continue;
- break;
- default:
- /* The backend violates the protocol. */
- libpq_append_conn_error(conn, "protocol error: id=0x%x", id);
- pqSaveErrorResult(conn);
-
- /*
- * We can't call parsing done due to the protocol violation
- * (so message tracing wouldn't work), but trust the specified
- * message length as what to skip.
- */
- conn->inStart += 5 + msgLength;
- return pqPrepareAsyncResult(conn);
- }
-
- /* Completed parsing this message, keep going */
- pqParseDone(conn, conn->inStart + 5 + msgLength);
- needInput = false;
- }
-
- /*
- * We fall out of the loop only upon failing to read data.
- * conn->errorMessage has been set by pqWait or pqReadData. We want to
- * append it to any already-received error message.
- */
- pqSaveErrorResult(conn);
- return pqPrepareAsyncResult(conn);
-}
-
-
/*
* Construct startup packet
*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 7a32c14de5e..980ef5374b5 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -768,11 +768,6 @@ extern int pqGetCopyData3(PGconn *conn, char **buffer, int async);
extern int pqGetline3(PGconn *conn, char *s, int maxlen);
extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);
extern int pqEndcopy3(PGconn *conn);
-extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid,
- int *result_buf, int buf_size,
- int *actual_result_len,
- int result_is_int,
- const PQArgBlock *args, int nargs);
/* === in fe-cancel.c === */
--
2.50.1 (Apple Git-155)
--wfzLmV/Wl0ehOJBp--