Another similar issue was reported on the PgBouncer prepared statement PR[1].

It's related to an issue that was already reported on the
mailinglist[2]. It turns out that invalidation of the argument types
is also important in some cases. The newly added 3rd patch in this
series addresses that issue.

[1]: https://github.com/pgbouncer/pgbouncer/pull/845#discussion_r1309454695
[2]: 
https://www.postgresql.org/message-id/flat/CA%2Bmi_8YAGf9qibDFTRNKgaTwaBa1OUcteKqLAxfMmKFbo3GHZg%40mail.gmail.com
From 3bfc47cfa333158c2e0a2e0603e311d141fc473b Mon Sep 17 00:00:00 2001
From: Jelte Fennema <jelte.fennema@microsoft.com>
Date: Fri, 25 Aug 2023 17:09:38 +0200
Subject: [PATCH v3 1/3] Support prepared statement invalidation when result
 types change

The cached plan for a prepared statements can get invalidated when DDL
changes the tables used in the query, or when search_path changes. When
this happens the prepared statement can still be executed, but it will
be replanned in the new context. This means that the prepared statement
will do something different e.g. in case of search_path changes it will
select data from a completely different table. This won't throw an
error, because it is considered the responsibility of the operator and
query writers that the query will still do the intended thing.

However, we would throw an error if the the result of the query is of a
different type than it was before. This was not documented anywhere and
can thus be a surprising error to hit. But it's actually not needed for
this to be an error, as long as we send the correct RowDescription there
does not have to be a problem for clients when the result types or
column counts change.

This patch starts to allow a prepared statement to continue to work even
when the result type changes.

Without this change all clients that automatically prepare queries as a
performance optimization will need to handle or avoid the error somehow,
often resulting in deallocating and re-preparing queries when its
usually not necessary. With this change connection poolers can also
safely prepare the same query only once on a connection and share this
one prepared query across clients that prepared that exact same query.
---
 src/backend/tcop/postgres.c             | 26 ++++++++++++++++++++++++-
 src/backend/tcop/pquery.c               | 16 +++++++++++++++
 src/backend/utils/cache/plancache.c     |  5 -----
 src/test/regress/expected/plancache.out | 24 +++++++++++++++++------
 src/test/regress/sql/plancache.sql      |  7 +++----
 5 files changed, 62 insertions(+), 16 deletions(-)

diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 21b9763183e..8ea654a4c26 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1252,7 +1252,31 @@ exec_simple_query(const char *query_string)
 					format = 1; /* BINARY */
 			}
 		}
-		PortalSetResultFormat(portal, 1, &format);
+
+		if (portal->strategy == PORTAL_UTIL_SELECT)
+		{
+			/*
+			 * For SELECT-like queries we clear the tupDesc now, and it will
+			 * be filled in later by FillPortalStore, because the tupDesc
+			 * might change due to replanning when ExecuteQuery calls
+			 * GetCachedPlan. So we should only fetch the tupDesc after the
+			 * query is actually executed. This also means that we cannot set
+			 * the result format for the output tuple yet, so we temporarily
+			 * store the desired format in portal->formats. Then after
+			 * creating the actual tupDesc we call PortalSetResultFormat,
+			 * using this format.
+			 */
+			FreeTupleDesc(portal->tupDesc);
+			portal->tupDesc = NULL;
+
+			portal->formats = (int16 *) MemoryContextAlloc(portal->portalContext,
+														   sizeof(int16));
+			portal->formats[0] = format;
+		}
+		else
+		{
+			PortalSetResultFormat(portal, 1, &format);
+		}
 
 		/*
 		 * Now we can create the destination receiver object.
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 5565f200c3d..4f7633faf46 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1030,6 +1030,22 @@ FillPortalStore(Portal portal, bool isTopLevel)
 		case PORTAL_UTIL_SELECT:
 			PortalRunUtility(portal, linitial_node(PlannedStmt, portal->stmts),
 							 isTopLevel, true, treceiver, &qc);
+
+			if (!portal->tupDesc)
+			{
+				/*
+				 * Now fill in the tupDesc and formats fields of the portal.
+				 * We delayed this because for EXECUTE queries we only know
+				 * the tuple after they were executed, because its original
+				 * plan might get replaced with a new one that returns
+				 * different columns, due to the table having changed since
+				 * the last PREPARE/EXECUTE command.
+				 */
+				PlannedStmt *pstmt = PortalGetPrimaryStmt(portal);
+
+				portal->tupDesc = UtilityTupleDescriptor(pstmt->utilityStmt);
+				PortalSetResultFormat(portal, 1, portal->formats);
+			}
 			break;
 
 		default:
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 7d4168f82f5..a6fbe235381 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -717,11 +717,6 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
 	else if (resultDesc == NULL || plansource->resultDesc == NULL ||
 			 !equalTupleDescs(resultDesc, plansource->resultDesc))
 	{
-		/* can we give a better error message? */
-		if (plansource->fixed_result)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("cached plan must not change result type")));
 		oldcxt = MemoryContextSwitchTo(plansource->context);
 		if (resultDesc)
 			resultDesc = CreateTupleDescCopy(resultDesc);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196c..77ae17b074a 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -49,14 +49,26 @@ EXECUTE prepstmt2(123);
  123 | 4567890123456789
 (2 rows)
 
--- prepared statements should prevent change in output tupdesc,
--- since clients probably aren't expecting that to change on the fly
-ALTER TABLE pcachetest ADD COLUMN q3 bigint;
+-- prepared statements should work even if the output tupdesc changes
+ALTER TABLE pcachetest ADD COLUMN q3 bigint DEFAULT 20;
 EXECUTE prepstmt;
-ERROR:  cached plan must not change result type
+        q1        |        q2         | q3 
+------------------+-------------------+----
+ 4567890123456789 | -4567890123456789 | 20
+ 4567890123456789 |               123 | 20
+              123 |               456 | 20
+              123 |  4567890123456789 | 20
+ 4567890123456789 |  4567890123456789 | 20
+(5 rows)
+
 EXECUTE prepstmt2(123);
-ERROR:  cached plan must not change result type
--- but we're nice guys and will let you undo your mistake
+ q1  |        q2        | q3 
+-----+------------------+----
+ 123 |              456 | 20
+ 123 | 4567890123456789 | 20
+(2 rows)
+
+-- changing it back should also work fine
 ALTER TABLE pcachetest DROP COLUMN q3;
 EXECUTE prepstmt;
         q1        |        q2         
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc64..cab0c42a142 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -27,14 +27,13 @@ CREATE TEMP TABLE pcachetest AS SELECT * FROM int8_tbl ORDER BY 2;
 EXECUTE prepstmt;
 EXECUTE prepstmt2(123);
 
--- prepared statements should prevent change in output tupdesc,
--- since clients probably aren't expecting that to change on the fly
-ALTER TABLE pcachetest ADD COLUMN q3 bigint;
+-- prepared statements should work even if the output tupdesc changes
+ALTER TABLE pcachetest ADD COLUMN q3 bigint DEFAULT 20;
 
 EXECUTE prepstmt;
 EXECUTE prepstmt2(123);
 
--- but we're nice guys and will let you undo your mistake
+-- changing it back should also work fine
 ALTER TABLE pcachetest DROP COLUMN q3;
 
 EXECUTE prepstmt;

base-commit: ac22a9545ca906e70a819b54e76de38817c93aaf
-- 
2.34.1

From 19ea84c5601730a44852e5ec10932b8922df7ebd Mon Sep 17 00:00:00 2001
From: Jelte Fennema <jelte.fennema@microsoft.com>
Date: Thu, 7 Sep 2023 18:51:30 +0200
Subject: [PATCH v3 3/3] Support changing argument types of prepared statemnts

Prepared statement invalidations might require argument types to change,
this starts supporting that.
---
 src/backend/commands/prepare.c          | 16 +++++--
 src/backend/tcop/postgres.c             | 18 ++++++++
 src/backend/utils/cache/plancache.c     | 57 ++++++++++++++++++++-----
 src/include/utils/plancache.h           | 10 +++++
 src/test/regress/expected/plancache.out | 31 +++++++++++++-
 src/test/regress/sql/plancache.sql      | 13 +++++-
 6 files changed, 130 insertions(+), 15 deletions(-)

diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index da3115beb99..21e5a1d5ff3 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -108,6 +108,10 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt,
 
 			argtypes[i++] = toid;
 		}
+
+		plansource->orig_num_params = nargs;
+		plansource->orig_param_types = MemoryContextAlloc(plansource->context, nargs * sizeof(Oid));
+		memcpy(plansource->orig_param_types, argtypes, nargs * sizeof(Oid));
 	}
 
 	/*
@@ -160,10 +164,13 @@ ExecuteQuery(ParseState *pstate,
 	char	   *query_string;
 	int			eflags;
 	long		count;
+	List	   *revalidationResult;
 
 	/* Look it up in the hash table */
 	entry = FetchPreparedStatement(stmt->name, true);
 
+	revalidationResult = RevalidateCachedQuery(entry->plansource, NULL);
+
 	/* Evaluate parameters, if any */
 	if (entry->plansource->num_params > 0)
 	{
@@ -188,7 +195,7 @@ ExecuteQuery(ParseState *pstate,
 									   entry->plansource->query_string);
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(entry->plansource, paramLI, NULL, NULL);
+	cplan = GetCachedPlanFromRevalidated(entry->plansource, paramLI, NULL, NULL, revalidationResult);
 	plan_list = cplan->stmt_list;
 
 	/*
@@ -577,6 +584,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	instr_time	planduration;
 	BufferUsage bufusage_start,
 				bufusage;
+	List	   *revalidationResult;
 
 	if (es->buffers)
 		bufusage_start = pgBufferUsage;
@@ -585,6 +593,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	/* Look it up in the hash table */
 	entry = FetchPreparedStatement(execstmt->name, true);
 
+	revalidationResult = RevalidateCachedQuery(entry->plansource, NULL);
+
 	query_string = entry->plansource->query_string;
 
 	/* Evaluate parameters, if any */
@@ -608,8 +618,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	}
 
 	/* Replan if needed, and acquire a transient refcount */
-	cplan = GetCachedPlan(entry->plansource, paramLI,
-						  CurrentResourceOwner, queryEnv);
+	cplan = GetCachedPlanFromRevalidated(entry->plansource, paramLI,
+										 CurrentResourceOwner, queryEnv, revalidationResult);
 
 	INSTR_TIME_SET_CURRENT(planduration);
 	INSTR_TIME_SUBTRACT(planduration, planstart);
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 9371a8f6e51..27ca2b0601d 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1535,6 +1535,9 @@ exec_parse_message(const char *query_string,	/* string to execute */
 			snapshot_set = true;
 		}
 
+		psrc->orig_num_params = numParams;
+		psrc->orig_param_types = MemoryContextAlloc(psrc->context, numParams * sizeof(Oid));
+
 		/*
 		 * Analyze and rewrite the query.  Note that the originally specified
 		 * parameter set is not required to be complete, so we have to use
@@ -2639,6 +2642,21 @@ exec_describe_statement_message(const char *stmt_name)
 
 		pstmt = FetchPreparedStatement(stmt_name, true);
 		psrc = pstmt->plansource;
+
+		/*
+		 * Revalidate the cached query, because maybe the argument types
+		 * should be changed due to plan invalidation. This mostly helps for
+		 * cases where the current session did some DDL that invalidated the
+		 * plan or if it changed the search_path.
+		 *
+		 * This approach cannot fully avoid all argument type errors. It's
+		 * still possible that another backend changes one of the tables that
+		 * is used in the prepared statement, in between this backend its
+		 * Describe and Bind calls. This is very unlikely in practice though,
+		 * and at least on the next Describe+Bind iteration the problem would
+		 * not happen.
+		 */
+		RevalidateCachedQuery(psrc, NULL);
 	}
 	else
 	{
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 1106215b781..679eff61917 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -101,8 +101,6 @@ static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list);
 static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list);
 
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
-static List *RevalidateCachedQuery(CachedPlanSource *plansource,
-								   QueryEnvironment *queryEnv);
 static bool CheckCachedPlan(CachedPlanSource *plansource);
 static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 								   ParamListInfo boundParams, QueryEnvironment *queryEnv);
@@ -551,7 +549,7 @@ ReleaseGenericPlan(CachedPlanSource *plansource)
  * had to do re-analysis, and NIL otherwise.  (This is returned just to save
  * a tree copying step in a subsequent BuildCachedPlan call.)
  */
-static List *
+List *
 RevalidateCachedQuery(CachedPlanSource *plansource,
 					  QueryEnvironment *queryEnv)
 {
@@ -688,11 +686,26 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
 											  plansource->parserSetupArg,
 											  queryEnv);
 	else
-		tlist = pg_analyze_and_rewrite_fixedparams(rawtree,
-												   plansource->query_string,
-												   plansource->param_types,
-												   plansource->num_params,
-												   queryEnv);
+	{
+		int			num_params = plansource->orig_num_params;
+		Oid		   *param_types = palloc_array(Oid, num_params);
+
+		memcpy(param_types, plansource->orig_param_types, sizeof(Oid) * num_params);
+
+		/*
+		 * Analyze and rewrite the query.  Note that the originally specified
+		 * parameter set is not required to be complete, so we have to use
+		 * pg_analyze_and_rewrite_varparams(). We cannot reuse the previously
+		 * filled in param_types, because the filled in argument types might
+		 * need to change due to the invalidation.
+		 */
+		tlist = pg_analyze_and_rewrite_varparams(rawtree,
+												 plansource->query_string,
+												 &param_types,
+												 &num_params,
+												 queryEnv);
+		memcpy(plansource->param_types, param_types, sizeof(Oid) * num_params);
+	}
 
 	/* Release snapshot if we got one */
 	if (snapshot_set)
@@ -1136,9 +1149,7 @@ CachedPlan *
 GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 			  ResourceOwner owner, QueryEnvironment *queryEnv)
 {
-	CachedPlan *plan = NULL;
 	List	   *qlist;
-	bool		customplan;
 
 	/* Assert caller is doing things in a sane order */
 	Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
@@ -1150,6 +1161,32 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 	/* Make sure the querytree list is valid and we have parse-time locks */
 	qlist = RevalidateCachedQuery(plansource, queryEnv);
 
+	return GetCachedPlanFromRevalidated(plansource, boundParams, owner, queryEnv, qlist);
+}
+
+/*
+ * GetCachedPlanFromRevalidated: is the same as get GetCachedPlan, but requires
+ * te caller to first revalidate the query. This is needed for callers that
+ * need to use the revalidated plan to generate boundParams.
+ */
+CachedPlan *
+GetCachedPlanFromRevalidated(CachedPlanSource *plansource,
+							 ParamListInfo boundParams,
+							 ResourceOwner owner,
+							 QueryEnvironment *queryEnv,
+							 List *revalidationResult)
+{
+	CachedPlan *plan = NULL;
+	bool		customplan;
+	List	   *qlist = revalidationResult;
+
+	/* Assert caller is doing things in a sane order */
+	Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+	Assert(plansource->is_complete);
+	/* This seems worth a real test, though */
+	if (owner && !plansource->is_saved)
+		elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan");
+
 	/* Decide whether to use a custom plan */
 	customplan = choose_custom_plan(plansource, boundParams);
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 36fd0104aec..0e33f620438 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -101,6 +101,9 @@ typedef struct CachedPlanSource
 	CommandTag	commandTag;		/* 'nuff said */
 	Oid		   *param_types;	/* array of parameter type OIDs, or NULL */
 	int			num_params;		/* length of param_types array */
+	Oid		   *orig_param_types;	/* array of original parameter type OIDs,
+									 * or NULL */
+	int			orig_num_params;	/* length of orig_param_types array */
 	ParserSetupHook parserSetup;	/* alternative parameter spec method */
 	void	   *parserSetupArg;
 	int			cursor_options; /* cursor options used for planning */
@@ -219,6 +222,13 @@ extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
 								 ParamListInfo boundParams,
 								 ResourceOwner owner,
 								 QueryEnvironment *queryEnv);
+extern CachedPlan *GetCachedPlanFromRevalidated(CachedPlanSource *plansource,
+												ParamListInfo boundParams,
+												ResourceOwner owner,
+												QueryEnvironment *queryEnv,
+												List *revalidationResult);
+extern List *RevalidateCachedQuery(CachedPlanSource *plansource,
+								   QueryEnvironment *queryEnv);
 extern void ReleaseCachedPlan(CachedPlan *plan, ResourceOwner owner);
 
 extern bool CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 77ae17b074a..ccbd7c07b3e 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -15,7 +15,7 @@ EXECUTE prepstmt;
 (5 rows)
 
 -- and one with parameters
-PREPARE prepstmt2(bigint) AS SELECT * FROM pcachetest WHERE q1 = $1;
+PREPARE prepstmt2(unknown) AS SELECT * FROM pcachetest WHERE q1 = $1;
 EXECUTE prepstmt2(123);
  q1  |        q2        
 -----+------------------
@@ -114,6 +114,35 @@ EXECUTE vprep;
  4567890123456789 |  2283945061728394
 (5 rows)
 
+DROP VIEW pcacheview;
+-- If the new plan requires new argument types that should also work resolved
+-- argument types of the prepared statement (because we use unknown as the
+-- type)
+ALTER TABLE pcachetest ALTER COLUMN q1 TYPE text;
+EXECUTE prepstmt2('123');
+ q1  |        q2        
+-----+------------------
+ 123 |              456
+ 123 | 4567890123456789
+(2 rows)
+
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) EXECUTE prepstmt2('123');
+                   QUERY PLAN                   
+------------------------------------------------
+ Seq Scan on pcachetest (actual rows=2 loops=1)
+   Filter: (q1 = $1)
+   Rows Removed by Filter: 3
+(3 rows)
+
+ALTER TABLE pcachetest ALTER COLUMN q1 TYPE bigint USING q1::bigint;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) EXECUTE prepstmt2(123);
+                   QUERY PLAN                   
+------------------------------------------------
+ Seq Scan on pcachetest (actual rows=2 loops=1)
+   Filter: (q1 = $1)
+   Rows Removed by Filter: 3
+(3 rows)
+
 -- Check basic SPI plan invalidation
 create function cache_test(int) returns int as $$
 declare total int;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index cab0c42a142..e4cbdf2bc41 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -10,7 +10,7 @@ PREPARE prepstmt AS SELECT * FROM pcachetest;
 EXECUTE prepstmt;
 
 -- and one with parameters
-PREPARE prepstmt2(bigint) AS SELECT * FROM pcachetest WHERE q1 = $1;
+PREPARE prepstmt2(unknown) AS SELECT * FROM pcachetest WHERE q1 = $1;
 
 EXECUTE prepstmt2(123);
 
@@ -53,6 +53,17 @@ CREATE OR REPLACE TEMP VIEW pcacheview AS
 
 EXECUTE vprep;
 
+DROP VIEW pcacheview;
+-- If the new plan requires new argument types that should also work resolved
+-- argument types of the prepared statement (because we use unknown as the
+-- type)
+ALTER TABLE pcachetest ALTER COLUMN q1 TYPE text;
+EXECUTE prepstmt2('123');
+
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) EXECUTE prepstmt2('123');
+ALTER TABLE pcachetest ALTER COLUMN q1 TYPE bigint USING q1::bigint;
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) EXECUTE prepstmt2(123);
+
 -- Check basic SPI plan invalidation
 
 create function cache_test(int) returns int as $$
-- 
2.34.1

From 39ab995282e74422a69b0091e129ae051619b96e Mon Sep 17 00:00:00 2001
From: Jelte Fennema <jelte.fennema@microsoft.com>
Date: Fri, 25 Aug 2023 19:48:24 +0200
Subject: [PATCH v3 2/3] Completely remove fixed_result from CachedPlanSource

Because of the previous patch, no fixed_result plans exist anymore. This
removes all references. It's done in a separate commit to ease
reviewing, if this gets merged we might want to squash them together.
---
 src/backend/commands/prepare.c      | 12 +-----------
 src/backend/executor/spi.c          |  6 ++----
 src/backend/tcop/postgres.c         |  6 +-----
 src/backend/utils/cache/plancache.c |  7 +------
 src/include/utils/plancache.h       |  4 +---
 5 files changed, 6 insertions(+), 29 deletions(-)

diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 18f70319fc4..da3115beb99 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -127,8 +127,7 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt,
 					   nargs,
 					   NULL,
 					   NULL,
-					   CURSOR_OPT_PARALLEL_OK,	/* allow parallel mode */
-					   true);	/* fixed result */
+					   CURSOR_OPT_PARALLEL_OK); /* allow parallel mode */
 
 	/*
 	 * Save the results.
@@ -165,10 +164,6 @@ ExecuteQuery(ParseState *pstate,
 	/* Look it up in the hash table */
 	entry = FetchPreparedStatement(stmt->name, true);
 
-	/* Shouldn't find a non-fixed-result cached plan */
-	if (!entry->plansource->fixed_result)
-		elog(ERROR, "EXECUTE does not support variable-result cached plans");
-
 	/* Evaluate parameters, if any */
 	if (entry->plansource->num_params > 0)
 	{
@@ -469,7 +464,6 @@ FetchPreparedStatementResultDesc(PreparedStatement *stmt)
 	 * Since we don't allow prepared statements' result tupdescs to change,
 	 * there's no need to worry about revalidating the cached plan here.
 	 */
-	Assert(stmt->plansource->fixed_result);
 	if (stmt->plansource->resultDesc)
 		return CreateTupleDescCopy(stmt->plansource->resultDesc);
 	else
@@ -591,10 +585,6 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	/* Look it up in the hash table */
 	entry = FetchPreparedStatement(execstmt->name, true);
 
-	/* Shouldn't find a non-fixed-result cached plan */
-	if (!entry->plansource->fixed_result)
-		elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans");
-
 	query_string = entry->plansource->query_string;
 
 	/* Evaluate parameters, if any */
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 33975687b38..52128290741 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2285,8 +2285,7 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
 						   plan->nargs,
 						   plan->parserSetup,
 						   plan->parserSetupArg,
-						   plan->cursor_options,
-						   false);	/* not fixed result */
+						   plan->cursor_options);	/* not fixed result */
 
 		plancache_list = lappend(plancache_list, plansource);
 	}
@@ -2522,8 +2521,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 							   plan->nargs,
 							   plan->parserSetup,
 							   plan->parserSetupArg,
-							   plan->cursor_options,
-							   false);	/* not fixed result */
+							   plan->cursor_options);
 		}
 
 		/*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 8ea654a4c26..9371a8f6e51 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1576,8 +1576,7 @@ exec_parse_message(const char *query_string,	/* string to execute */
 					   numParams,
 					   NULL,
 					   NULL,
-					   CURSOR_OPT_PARALLEL_OK,	/* allow parallel mode */
-					   true);	/* fixed result */
+					   CURSOR_OPT_PARALLEL_OK); /* allow parallel mode */
 
 	/* If we got a cancel signal during analysis, quit */
 	CHECK_FOR_INTERRUPTS();
@@ -2651,9 +2650,6 @@ exec_describe_statement_message(const char *stmt_name)
 					 errmsg("unnamed prepared statement does not exist")));
 	}
 
-	/* Prepared statements shouldn't have changeable result descs */
-	Assert(psrc->fixed_result);
-
 	/*
 	 * If we are in aborted transaction state, we can't run
 	 * SendRowDescriptionMessage(), because that needs catalog accesses.
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index a6fbe235381..1106215b781 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -203,7 +203,6 @@ CreateCachedPlan(RawStmt *raw_parse_tree,
 	plansource->parserSetup = NULL;
 	plansource->parserSetupArg = NULL;
 	plansource->cursor_options = 0;
-	plansource->fixed_result = false;
 	plansource->resultDesc = NULL;
 	plansource->context = source_context;
 	plansource->query_list = NIL;
@@ -271,7 +270,6 @@ CreateOneShotCachedPlan(RawStmt *raw_parse_tree,
 	plansource->parserSetup = NULL;
 	plansource->parserSetupArg = NULL;
 	plansource->cursor_options = 0;
-	plansource->fixed_result = false;
 	plansource->resultDesc = NULL;
 	plansource->context = CurrentMemoryContext;
 	plansource->query_list = NIL;
@@ -346,8 +344,7 @@ CompleteCachedPlan(CachedPlanSource *plansource,
 				   int num_params,
 				   ParserSetupHook parserSetup,
 				   void *parserSetupArg,
-				   int cursor_options,
-				   bool fixed_result)
+				   int cursor_options)
 {
 	MemoryContext source_context = plansource->context;
 	MemoryContext oldcxt = CurrentMemoryContext;
@@ -430,7 +427,6 @@ CompleteCachedPlan(CachedPlanSource *plansource,
 	plansource->parserSetup = parserSetup;
 	plansource->parserSetupArg = parserSetupArg;
 	plansource->cursor_options = cursor_options;
-	plansource->fixed_result = fixed_result;
 	plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list);
 
 	MemoryContextSwitchTo(oldcxt);
@@ -1547,7 +1543,6 @@ CopyCachedPlan(CachedPlanSource *plansource)
 	newsource->parserSetup = plansource->parserSetup;
 	newsource->parserSetupArg = plansource->parserSetupArg;
 	newsource->cursor_options = plansource->cursor_options;
-	newsource->fixed_result = plansource->fixed_result;
 	if (plansource->resultDesc)
 		newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
 	else
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 916e59d9fef..36fd0104aec 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -104,7 +104,6 @@ typedef struct CachedPlanSource
 	ParserSetupHook parserSetup;	/* alternative parameter spec method */
 	void	   *parserSetupArg;
 	int			cursor_options; /* cursor options used for planning */
-	bool		fixed_result;	/* disallow change in result tupdesc? */
 	TupleDesc	resultDesc;		/* result type; NULL = doesn't return tuples */
 	MemoryContext context;		/* memory context holding all above */
 	/* These fields describe the current analyzed-and-rewritten query tree: */
@@ -201,8 +200,7 @@ extern void CompleteCachedPlan(CachedPlanSource *plansource,
 							   int num_params,
 							   ParserSetupHook parserSetup,
 							   void *parserSetupArg,
-							   int cursor_options,
-							   bool fixed_result);
+							   int cursor_options);
 
 extern void SaveCachedPlan(CachedPlanSource *plansource);
 extern void DropCachedPlan(CachedPlanSource *plansource);
-- 
2.34.1

Reply via email to