From 18e5f597fa97c54093bf5a7349ce3595fd2c0a0c Mon Sep 17 00:00:00 2001
From: reshke <reshke@double.cloud>
Date: Tue, 16 Dec 2025 21:07:01 +0000
Subject: [PATCH v1] Add destincation portal parameter to libpq interfaces.

---
 contrib/postgres_fdw/postgres_fdw.c           |  1 +
 doc/src/sgml/libpq.sgml                       | 11 +++++++--
 src/bin/pg_rewind/libpq_source.c              |  2 +-
 src/bin/pgbench/pgbench.c                     |  2 +-
 src/bin/psql/common.c                         |  2 +-
 src/interfaces/ecpg/ecpglib/execute.c         |  1 +
 src/interfaces/libpq/fe-exec.c                | 24 +++++++++++++++----
 src/interfaces/libpq/libpq-fe.h               |  2 ++
 src/test/isolation/isolationtester.c          |  2 +-
 .../modules/libpq_pipeline/libpq_pipeline.c   |  8 +++----
 10 files changed, 40 insertions(+), 15 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 5e178c21b39..67c6f138614 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -4146,6 +4146,7 @@ execute_foreign_modify(EState *estate,
 	 * Execute the prepared statement.
 	 */
 	if (!PQsendQueryPrepared(fmstate->conn,
+							 NULL,
 							 fmstate->p_name,
 							 fmstate->p_nums * (*numSlots),
 							 p_values,
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7ab679a765d..d81d5596971 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -3655,6 +3655,7 @@ PGresult *PQprepare(PGconn *conn,
         parameters, and waits for the result.
 <synopsis>
 PGresult *PQexecPrepared(PGconn *conn,
+                         const char *portalName,
                          const char *stmtName,
                          int nParams,
                          const char * const *paramValues,
@@ -3668,6 +3669,8 @@ PGresult *PQexecPrepared(PGconn *conn,
         <xref linkend="libpq-PQexecPrepared"/> is like <xref linkend="libpq-PQexecParams"/>,
         but the command to be executed is specified by naming a
         previously-prepared statement, instead of giving a query string.
+        Statement will be executed in given destination portal, with unnamed portal
+        being default.
         This feature allows commands that will be used repeatedly to be
         parsed and planned just once, rather than each time they are
         executed.  The statement must have been prepared previously in
@@ -3675,8 +3678,9 @@ PGresult *PQexecPrepared(PGconn *conn,
        </para>
 
        <para>
-        The parameters are identical to <xref linkend="libpq-PQexecParams"/>, except that the
-        name of a prepared statement is given instead of a query string, and the
+        The parameters are almost identical to <xref linkend="libpq-PQexecParams"/>, with a
+        few exceptions. First is that the name of a prepared statement is given instead of a query string.
+        Second, name of destination query portal can be specified. And last, the
         <parameter>paramTypes[]</parameter> parameter is not present (it is not needed since
         the prepared statement's parameter types were determined when it was created).
        </para>
@@ -5421,6 +5425,7 @@ int PQsendPrepare(PGconn *conn,
        parameters, without waiting for the result(s).
 <synopsis>
 int PQsendQueryPrepared(PGconn *conn,
+                        const char *portalName,
                         const char *stmtName,
                         int nParams,
                         const char * const *paramValues,
@@ -5432,6 +5437,8 @@ int PQsendQueryPrepared(PGconn *conn,
        This is similar to <xref linkend="libpq-PQsendQueryParams"/>, but
        the command to be executed is specified by naming a
        previously-prepared statement, instead of giving a query string.
+       Command will be executed in given portal, or in unnamed portal if
+       portalName is NULL.
        The function's parameters are handled identically to
        <xref linkend="libpq-PQexecPrepared"/>.
       </para>
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 575585357b7..533ff6312e4 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -472,7 +472,7 @@ process_queued_fetch_requests(libpq_source *src)
 	params[1] = src->offsets.data;
 	params[2] = src->lengths.data;
 
-	if (PQsendQueryPrepared(src->conn, "fetch_chunks_stmt", 3, params, NULL, NULL, 1) != 1)
+	if (PQsendQueryPrepared(src->conn, NULL, "fetch_chunks_stmt", 3, params, NULL, NULL, 1) != 1)
 		pg_fatal("could not send query: %s", PQerrorMessage(src->conn));
 
 	if (PQsetSingleRowMode(src->conn) != 1)
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 45b340d3da5..c084f19d4cb 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -3183,7 +3183,7 @@ sendCommand(CState *st, Command *command)
 		getQueryParams(&st->variables, command, params);
 
 		pg_log_debug("client %d sending %s", st->id, command->prepname);
-		r = PQsendQueryPrepared(st->con, command->prepname, command->argc - 1,
+		r = PQsendQueryPrepared(st->con, NULL, command->prepname, command->argc - 1,
 								params, NULL, NULL, 0);
 	}
 	else						/* unknown sql mode */
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 1a28ba6c02b..a91acbf5acc 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1596,7 +1596,7 @@ ExecQueryAndProcessResults(const char *query,
 			break;
 		case PSQL_SEND_EXTENDED_QUERY_PREPARED:
 			Assert(pset.stmtName != NULL);
-			success = PQsendQueryPrepared(pset.db, pset.stmtName,
+			success = PQsendQueryPrepared(pset.db, NULL, pset.stmtName,
 										  pset.bind_nparams,
 										  (const char *const *) pset.bind_params,
 										  NULL, NULL, 0);
diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c
index bd10fef5748..dd6d35d3749 100644
--- a/src/interfaces/ecpg/ecpglib/execute.c
+++ b/src/interfaces/ecpg/ecpglib/execute.c
@@ -1603,6 +1603,7 @@ ecpg_execute(struct statement *stmt)
 	if (stmt->statement_type == ECPGst_execute)
 	{
 		stmt->results = PQexecPrepared(stmt->connection->connection,
+									   NULL,
 									   stmt->name,
 									   stmt->nparams,
 									   (const char *const *) stmt->paramvalues,
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 7ab33930a39..4c08fb3bfde 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -68,6 +68,7 @@ static int	PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery);
 static bool PQsendQueryStart(PGconn *conn, bool newQuery);
 static int	PQsendQueryGuts(PGconn *conn,
 							const char *command,
+							const char *portalName,
 							const char *stmtName,
 							int nParams,
 							const Oid *paramTypes,
@@ -1533,6 +1534,7 @@ PQsendQueryParams(PGconn *conn,
 
 	return PQsendQueryGuts(conn,
 						   command,
+						   "",	/* use unnamed portal */
 						   "",	/* use unnamed statement */
 						   nParams,
 						   paramTypes,
@@ -1648,6 +1650,7 @@ sendFailed:
  */
 int
 PQsendQueryPrepared(PGconn *conn,
+					const char *portalName,
 					const char *stmtName,
 					int nParams,
 					const char *const *paramValues,
@@ -1673,6 +1676,7 @@ PQsendQueryPrepared(PGconn *conn,
 
 	return PQsendQueryGuts(conn,
 						   NULL,	/* no command to parse */
+						   portalName,
 						   stmtName,
 						   nParams,
 						   NULL,	/* no param types */
@@ -1773,6 +1777,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 static int
 PQsendQueryGuts(PGconn *conn,
 				const char *command,
+				const char *portalName,
 				const char *stmtName,
 				int nParams,
 				const Oid *paramTypes,
@@ -1783,6 +1788,13 @@ PQsendQueryGuts(PGconn *conn,
 {
 	int			i;
 	PGcmdQueueEntry *entry;
+	const char *	resolvedPortalName;
+
+	/* use unnamed portal, if not explicitly set */
+	if (portalName)
+		resolvedPortalName = portalName;
+	else
+		resolvedPortalName = "";
 
 	entry = pqAllocCmdQueueEntry(conn);
 	if (entry == NULL)
@@ -1822,7 +1834,7 @@ PQsendQueryGuts(PGconn *conn,
 
 	/* Construct the Bind message */
 	if (pqPutMsgStart(PqMsg_Bind, conn) < 0 ||
-		pqPuts("", conn) < 0 ||
+		pqPuts(resolvedPortalName, conn) < 0 ||
 		pqPuts(stmtName, conn) < 0)
 		goto sendFailed;
 
@@ -1889,13 +1901,13 @@ PQsendQueryGuts(PGconn *conn,
 	/* construct the Describe Portal message */
 	if (pqPutMsgStart(PqMsg_Describe, conn) < 0 ||
 		pqPutc('P', conn) < 0 ||
-		pqPuts("", conn) < 0 ||
+		pqPuts(resolvedPortalName, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
 	/* construct the Execute message */
 	if (pqPutMsgStart(PqMsg_Execute, conn) < 0 ||
-		pqPuts("", conn) < 0 ||
+		pqPuts(resolvedPortalName, conn) < 0 ||
 		pqPutInt(0, 4, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
@@ -2334,10 +2346,12 @@ PQprepare(PGconn *conn,
 /*
  * PQexecPrepared
  *		Like PQexec, but execute a previously prepared statement,
- *		using extended query protocol so we can pass parameters
+ *		in given portal, using extended query protocol
+ *		so we can pass parameters.
  */
 PGresult *
 PQexecPrepared(PGconn *conn,
+			   const char *portalName,
 			   const char *stmtName,
 			   int nParams,
 			   const char *const *paramValues,
@@ -2347,7 +2361,7 @@ PQexecPrepared(PGconn *conn,
 {
 	if (!PQexecStart(conn))
 		return NULL;
-	if (!PQsendQueryPrepared(conn, stmtName,
+	if (!PQsendQueryPrepared(conn, portalName, stmtName,
 							 nParams, paramValues, paramLengths,
 							 paramFormats, resultFormat))
 		return NULL;
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 0852584edae..c0ed0ad8bda 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -496,6 +496,7 @@ extern PGresult *PQprepare(PGconn *conn, const char *stmtName,
 						   const char *query, int nParams,
 						   const Oid *paramTypes);
 extern PGresult *PQexecPrepared(PGconn *conn,
+								const char *portalName,
 								const char *stmtName,
 								int nParams,
 								const char *const *paramValues,
@@ -520,6 +521,7 @@ extern int	PQsendPrepare(PGconn *conn, const char *stmtName,
 						  const Oid *paramTypes);
 extern int	PQsendQueryPrepared(PGconn *conn,
 								const char *stmtName,
+								const char *portalName,
 								int nParams,
 								const char *const *paramValues,
 								const int *paramLengths,
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index e01c0c9de93..0896dff912d 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -884,7 +884,7 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags)
 			{
 				bool		waiting;
 
-				res = PQexecPrepared(conns[0].conn, PREP_WAITING, 1,
+				res = PQexecPrepared(conns[0].conn, NULL, PREP_WAITING, 1,
 									 &conns[step->session + 1].backend_pid_str,
 									 NULL, NULL, 0);
 				if (PQresultStatus(res) != PGRES_TUPLES_OK ||
diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c
index b3af70fa09b..e1ce90442d4 100644
--- a/src/test/modules/libpq_pipeline/libpq_pipeline.c
+++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c
@@ -1119,7 +1119,7 @@ test_pipelined_insert(PGconn *conn, int n_rows)
 				/* use up some buffer space with a wide value */
 				snprintf(insert_param_1, MAXINT8LEN, "%lld", 1LL << 62);
 
-				if (PQsendQueryPrepared(conn, "my_insert",
+				if (PQsendQueryPrepared(conn, NULL, "my_insert",
 										2, insert_params, NULL, NULL, 0) == 1)
 				{
 					pg_debug("sent row %d\n", rows_to_send);
@@ -1764,7 +1764,7 @@ test_transaction(PGconn *conn)
 	 * send a ROLLBACK using a prepared stmt. Doesn't work because we need to
 	 * get out of the pipeline-aborted state first.
 	 */
-	if (PQsendQueryPrepared(conn, "rollback", 0, NULL, NULL, NULL, 1) != 1)
+	if (PQsendQueryPrepared(conn, NULL, "rollback", 0, NULL, NULL, NULL, 1) != 1)
 		pg_fatal("failed to execute prepared: %s",
 				 PQerrorMessage(conn));
 
@@ -1795,7 +1795,7 @@ test_transaction(PGconn *conn)
 	 * Send ROLLBACK using prepared stmt. This one works because we just did
 	 * PQpipelineSync above.
 	 */
-	if (PQsendQueryPrepared(conn, "rollback", 0, NULL, NULL, NULL, 1) != 1)
+	if (PQsendQueryPrepared(conn, NULL, "rollback", 0, NULL, NULL, NULL, 1) != 1)
 		pg_fatal("failed to execute prepared: %s",
 				 PQerrorMessage(conn));
 
@@ -2003,7 +2003,7 @@ test_uniqviol(PGconn *conn)
 					sprintf(paramValue0, "%d", ctr++);
 				}
 
-				if (PQsendQueryPrepared(conn, "insertion", 2, paramValues, NULL, NULL, 0) != 1)
+				if (PQsendQueryPrepared(conn, NULL, "insertion", 2, paramValues, NULL, NULL, 0) != 1)
 					pg_fatal("failed to execute prepared query: %s", PQerrorMessage(conn));
 				numsent++;
 
-- 
2.43.0

