Hi,

A review comment in another thread [1] by Michael Paquier about the
usage of get_call_result_type() instead of explicit building of
TupleDesc made me think about using it more widely. Actually, the
get_call_result_type() looks at the function definitions to figure the
column names and build the required TupleDesc, usage of which avoids
duplication of the column names between pg_proc.dat/function
definitions and source code. Also, it saves a good number of LOC ~415
[2] and the size of all the object files put together gets reduced by
~4MB, which means, the postgres binary becomes leaner by ~4MB [3]. I'm
attaching a patch for these changes.

While on this, I observed that BlessTupleDesc() is called in many
(~12) places right after get_call_result_type() which actually does
the job of BlessTupleDesc() before returning the TupleDesc. I think we
can get rid of BlessTupleDesc() after get_call_result_type(). I'm
attaching a patch for these changes too.

cirrus-ci members are happy with these patches, please see here
https://github.com/BRupireddy/postgres/tree/use_get_call_result_type()_more_widely_v1.

Thoughts?

[1] https://www.postgresql.org/message-id/Y41De5NnF2sxmJPI%40paquier.xyz

[2] 21 files changed, 97 insertions(+), 514 deletions(-)

[3] Source code is built with CFLAGS = -O3.
PATCHED:
   text    data     bss     dec     hex filename
   1043       0       0    1043     413 contrib/old_snapshot/time_mapping.o
   7192       0       0    7192    1c18 contrib/pg_visibility/pg_visibility.o
   7144       0     120    7264    1c60 src/backend/access/transam/commit_ts.o
  19681      24     248   19953    4df1 src/backend/access/transam/multixact.o
  20595       0      88   20683    50cb src/backend/access/transam/twophase.o
   6162       0      24    6186    182a src/backend/access/transam/xlogfuncs.o
  45540    2736       8   48284    bc9c src/backend/catalog/objectaddress.o
   9943       0       0    9943    26d7 src/backend/catalog/pg_publication.o
  18239       0      16   18255    474f src/backend/commands/sequence.o
   6429       0       0    6429    191d src/backend/tsearch/wparser.o
  47049    1840      52   48941    bf2d src/backend/utils/adt/acl.o
  43066     168     784   44018    abf2 src/backend/utils/adt/datetime.o
   6843       0       0    6843    1abb src/backend/utils/adt/genfile.o
   6904     120       0    7024    1b70 src/backend/utils/adt/lockfuncs.o
  10512    7008       0   17520    4470 src/backend/utils/adt/misc.o
   1569       0       0    1569     621 src/backend/utils/adt/partitionfuncs.o
  16266       0       0   16266    3f8a src/backend/utils/adt/pgstatfuncs.o
  40985       0       0   40985    a019 src/backend/utils/adt/tsvector_op.o
   8322       0       0    8322    2082 src/backend/utils/misc/guc_funcs.o
   2109       0       0    2109     83d src/backend/utils/misc/pg_controldata.o
   2354       0       0    2354     932
src/test/modules/test_predtest/test_predtest.o
  9586047  226936  205536 10018519 98ded7 src/backend/postgres

HEAD:
   text    data     bss     dec     hex filename
   1019       0       0    1019     3fb contrib/old_snapshot/time_mapping.o
   7159       0       0    7159    1bf7 contrib/pg_visibility/pg_visibility.o
   6655       0     120    6775    1a77 src/backend/access/transam/commit_ts.o
  19636      24     248   19908    4dc4 src/backend/access/transam/multixact.o
  20663       0      88   20751    510f src/backend/access/transam/twophase.o
   6206       0      24    6230    1856 src/backend/access/transam/xlogfuncs.o
  45700    2736       8   48444    bd3c src/backend/catalog/objectaddress.o
   9952       0       0    9952    26e0 src/backend/catalog/pg_publication.o
  18487       0      16   18503    4847 src/backend/commands/sequence.o
   6143       0       0    6143    17ff src/backend/tsearch/wparser.o
  47123    1840      52   49015    bf77 src/backend/utils/adt/acl.o
  43099     168     784   44051    ac13 src/backend/utils/adt/datetime.o
   7016       0       0    7016    1b68 src/backend/utils/adt/genfile.o
   7413     120       0    7533    1d6d src/backend/utils/adt/lockfuncs.o
  10698    7008       0   17706    452a src/backend/utils/adt/misc.o
   1593       0       0    1593     639 src/backend/utils/adt/partitionfuncs.o
  17194       0       0   17194    432a src/backend/utils/adt/pgstatfuncs.o
  40798       0       0   40798    9f5e src/backend/utils/adt/tsvector_op.o
   8871       0       0    8871    22a7 src/backend/utils/misc/guc_funcs.o
   3918       0       0    3918     f4e src/backend/utils/misc/pg_controldata.o
   2636       0       0    2636     a4c
src/test/modules/test_predtest/test_predtest.o
  9589943  226936  205536 10022415 98ee0f src/backend/postgres

--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
From e1df78cc86e3d38a2814d6cd89f6b86a8de4a284 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Tue, 13 Dec 2022 07:04:22 +0000
Subject: [PATCH v1] Use get_call_result_type() more widely

Usage of get_call_result_type() in more places avoids explicit
creation of tuple descriptor which requires matching column names
from pg_proc.dat/function definitions. It saves a good amount of
LOC ~415 and the size of all the object files put together gets
reduced by ~4MB, which means, the postgres binary can become
leaner by ~4MB.
---
 contrib/old_snapshot/time_mapping.c           |  26 +----
 contrib/pg_visibility/pg_visibility.c         |   6 +-
 src/backend/access/transam/commit_ts.c        |  26 +----
 src/backend/access/transam/multixact.c        |   9 +-
 src/backend/access/transam/twophase.c         |  17 +--
 src/backend/access/transam/xlogfuncs.c        |  21 +---
 src/backend/catalog/objectaddress.c           |  42 +------
 src/backend/catalog/pg_publication.c          |  13 +--
 src/backend/commands/sequence.c               |  19 +--
 src/backend/tsearch/wparser.c                 |  29 +++--
 src/backend/utils/adt/acl.c                   |  18 +--
 src/backend/utils/adt/datetime.c              |  17 +--
 src/backend/utils/adt/genfile.c               |  20 +---
 src/backend/utils/adt/lockfuncs.c             |  40 +------
 src/backend/utils/adt/misc.c                  |  31 +----
 src/backend/utils/adt/partitionfuncs.c        |  14 +--
 src/backend/utils/adt/pgstatfuncs.c           |  78 ++-----------
 src/backend/utils/adt/tsvector_op.c           |  15 +--
 src/backend/utils/misc/guc_funcs.c            |  42 +------
 src/backend/utils/misc/pg_controldata.c       | 108 ++----------------
 .../modules/test_predtest/test_predtest.c     |  20 +---
 21 files changed, 97 insertions(+), 514 deletions(-)

diff --git a/contrib/old_snapshot/time_mapping.c b/contrib/old_snapshot/time_mapping.c
index 2d8cb742c3..97efccddbb 100644
--- a/contrib/old_snapshot/time_mapping.c
+++ b/contrib/old_snapshot/time_mapping.c
@@ -38,7 +38,6 @@ PG_MODULE_MAGIC;
 PG_FUNCTION_INFO_V1(pg_old_snapshot_time_mapping);
 
 static OldSnapshotTimeMapping *GetOldSnapshotTimeMapping(void);
-static TupleDesc MakeOldSnapshotTimeMappingTupleDesc(void);
 static HeapTuple MakeOldSnapshotTimeMappingTuple(TupleDesc tupdesc,
 												 OldSnapshotTimeMapping *mapping);
 
@@ -54,12 +53,15 @@ pg_old_snapshot_time_mapping(PG_FUNCTION_ARGS)
 	if (SRF_IS_FIRSTCALL())
 	{
 		MemoryContext oldcontext;
+		TupleDesc tupdesc;
 
 		funcctx = SRF_FIRSTCALL_INIT();
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 		mapping = GetOldSnapshotTimeMapping();
 		funcctx->user_fctx = mapping;
-		funcctx->tuple_desc = MakeOldSnapshotTimeMappingTupleDesc();
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
 		MemoryContextSwitchTo(oldcontext);
 	}
 
@@ -101,26 +103,6 @@ GetOldSnapshotTimeMapping(void)
 	return mapping;
 }
 
-/*
- * Build a tuple descriptor for the pg_old_snapshot_time_mapping() SRF.
- */
-static TupleDesc
-MakeOldSnapshotTimeMappingTupleDesc(void)
-{
-	TupleDesc	tupdesc;
-
-	tupdesc = CreateTemplateTupleDesc(NUM_TIME_MAPPING_COLUMNS);
-
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "array_offset",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "end_timestamp",
-					   TIMESTAMPTZOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "newest_xmin",
-					   XIDOID, -1, 0);
-
-	return BlessTupleDesc(tupdesc);
-}
-
 /*
  * Convert one entry from the old snapshot time mapping to a HeapTuple.
  */
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index a95f73ec79..81f262a5f4 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -291,10 +291,8 @@ pg_visibility_map_summary(PG_FUNCTION_ARGS)
 		ReleaseBuffer(vmbuffer);
 	relation_close(rel, AccessShareLock);
 
-	tupdesc = CreateTemplateTupleDesc(2);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "all_visible", INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "all_frozen", INT8OID, -1, 0);
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	values[0] = Int64GetDatum(all_visible);
 	values[1] = Int64GetDatum(all_frozen);
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 9aa4675cb7..5c30de57ac 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -422,18 +422,8 @@ pg_last_committed_xact(PG_FUNCTION_ARGS)
 	/* and construct a tuple with our data */
 	xid = GetLatestCommitTsData(&ts, &nodeid);
 
-	/*
-	 * Construct a tuple descriptor for the result row.  This must match this
-	 * function's pg_proc entry!
-	 */
-	tupdesc = CreateTemplateTupleDesc(3);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xid",
-					   XIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "timestamp",
-					   TIMESTAMPTZOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "roident",
-					   OIDOID, -1, 0);
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	if (!TransactionIdIsNormal(xid))
 	{
@@ -476,16 +466,8 @@ pg_xact_commit_timestamp_origin(PG_FUNCTION_ARGS)
 
 	found = TransactionIdGetCommitTsData(xid, &ts, &nodeid);
 
-	/*
-	 * Construct a tuple descriptor for the result row.  This must match this
-	 * function's pg_proc entry!
-	 */
-	tupdesc = CreateTemplateTupleDesc(2);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "timestamp",
-					   TIMESTAMPTZOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "roident",
-					   OIDOID, -1, 0);
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	if (!found)
 	{
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index e1191a7564..19b95b8241 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -3373,12 +3373,9 @@ pg_get_multixact_members(PG_FUNCTION_ARGS)
 												false);
 		multi->iter = 0;
 
-		tupdesc = CreateTemplateTupleDesc(2);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xid",
-						   XIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "mode",
-						   TEXTOID, -1, 0);
-
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funccxt->tuple_desc = tupdesc;
 		funccxt->attinmeta = TupleDescGetAttInMetadata(tupdesc);
 		funccxt->user_fctx = multi;
 
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 5017f4451e..cf4e7cb912 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -745,20 +745,9 @@ pg_prepared_xact(PG_FUNCTION_ARGS)
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
 		/* build tupdesc for result tuples */
-		/* this had better match pg_prepared_xacts view in system_views.sql */
-		tupdesc = CreateTemplateTupleDesc(5);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "transaction",
-						   XIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "gid",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "prepared",
-						   TIMESTAMPTZOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "ownerid",
-						   OIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 5, "dbid",
-						   OIDOID, -1, 0);
-
-		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
 
 		/*
 		 * Collect all the 2PC status information that we will format and send
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 487d5d9cac..bad08270fb 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -356,8 +356,8 @@ pg_walfile_name_offset(PG_FUNCTION_ARGS)
 	char		xlogfilename[MAXFNAMELEN];
 	Datum		values[2];
 	bool		isnull[2];
-	TupleDesc	resultTupleDesc;
-	HeapTuple	resultHeapTuple;
+	TupleDesc	tupdesc;
+	HeapTuple	tuple;
 	Datum		result;
 
 	if (RecoveryInProgress())
@@ -367,17 +367,8 @@ pg_walfile_name_offset(PG_FUNCTION_ARGS)
 				 errhint("%s cannot be executed during recovery.",
 						 "pg_walfile_name_offset()")));
 
-	/*
-	 * Construct a tuple descriptor for the result row.  This must match this
-	 * function's pg_proc entry!
-	 */
-	resultTupleDesc = CreateTemplateTupleDesc(2);
-	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "file_name",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "file_offset",
-					   INT4OID, -1, 0);
-
-	resultTupleDesc = BlessTupleDesc(resultTupleDesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/*
 	 * xlogfilename
@@ -400,9 +391,9 @@ pg_walfile_name_offset(PG_FUNCTION_ARGS)
 	/*
 	 * Tuple jam: Having first prepared your Datums, then squash together
 	 */
-	resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull);
+	tuple = heap_form_tuple(tupdesc, values, isnull);
 
-	result = HeapTupleGetDatum(resultHeapTuple);
+	result = HeapTupleGetDatum(tuple);
 
 	PG_RETURN_DATUM(result);
 }
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index fe97fbf79d..109bdfb33f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2397,14 +2397,8 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 	if (relation)
 		relation_close(relation, AccessShareLock);
 
-	tupdesc = CreateTemplateTupleDesc(3);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "classid",
-					   OIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "objid",
-					   OIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "objsubid",
-					   INT4OID, -1, 0);
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	values[0] = ObjectIdGetDatum(addr.classId);
 	values[1] = ObjectIdGetDatum(addr.objectId);
@@ -4244,21 +4238,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	/*
-	 * Construct a tuple descriptor for the result row.  This must match this
-	 * function's pg_proc entry!
-	 */
-	tupdesc = CreateTemplateTupleDesc(4);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "type",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "schema",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "name",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "identity",
-					   TEXTOID, -1, 0);
-
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	if (is_objectclass_supported(address.classId))
 	{
@@ -4374,19 +4355,8 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	/*
-	 * Construct a tuple descriptor for the result row.  This must match this
-	 * function's pg_proc entry!
-	 */
-	tupdesc = CreateTemplateTupleDesc(3);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "type",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "object_names",
-					   TEXTARRAYOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "object_args",
-					   TEXTARRAYOID, -1, 0);
-
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* object type, which can never be NULL */
 	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 59967098b3..913244616a 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1087,16 +1087,9 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
 				tables = filter_partitions(tables);
 		}
 
-		/* Construct a tuple descriptor for the result rows. */
-		tupdesc = CreateTemplateTupleDesc(NUM_PUBLICATION_TABLES_ELEM);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relid",
-						   OIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "attrs",
-						   INT2VECTOROID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "qual",
-						   PG_NODE_TREEOID, -1, 0);
-
-		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
 		funcctx->user_fctx = (void *) tables;
 
 		MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 99c9f91cba..c31c9b891a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1762,23 +1762,8 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						get_rel_name(relid))));
 
-	tupdesc = CreateTemplateTupleDesc(7);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "start_value",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "minimum_value",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "maximum_value",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "increment",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "cycle_option",
-					   BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "cache_size",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "data_type",
-					   OIDOID, -1, 0);
-
-	BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	memset(isnull, 0, sizeof(isnull));
 
diff --git a/src/backend/tsearch/wparser.c b/src/backend/tsearch/wparser.c
index 14bb60534f..a82029b8bc 100644
--- a/src/backend/tsearch/wparser.c
+++ b/src/backend/tsearch/wparser.c
@@ -46,7 +46,8 @@ typedef struct HeadlineJsonState
 static text *headline_json_value(void *_state, char *elem_value, int elem_len);
 
 static void
-tt_setup_firstcall(FuncCallContext *funcctx, Oid prsid)
+tt_setup_firstcall(FuncCallContext *funcctx, FunctionCallInfo fcinfo,
+				   Oid prsid)
 {
 	TupleDesc	tupdesc;
 	MemoryContext oldcontext;
@@ -75,6 +76,12 @@ tt_setup_firstcall(FuncCallContext *funcctx, Oid prsid)
 					   TEXTOID, -1, 0);
 
 	funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+	funcctx->tuple_desc = tupdesc;
+	funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
 	MemoryContextSwitchTo(oldcontext);
 }
 
@@ -116,7 +123,7 @@ ts_token_type_byid(PG_FUNCTION_ARGS)
 	if (SRF_IS_FIRSTCALL())
 	{
 		funcctx = SRF_FIRSTCALL_INIT();
-		tt_setup_firstcall(funcctx, PG_GETARG_OID(0));
+		tt_setup_firstcall(funcctx, fcinfo, PG_GETARG_OID(0));
 	}
 
 	funcctx = SRF_PERCALL_SETUP();
@@ -139,7 +146,7 @@ ts_token_type_byname(PG_FUNCTION_ARGS)
 
 		funcctx = SRF_FIRSTCALL_INIT();
 		prsId = get_ts_parser_oid(textToQualifiedNameList(prsname), false);
-		tt_setup_firstcall(funcctx, prsId);
+		tt_setup_firstcall(funcctx, fcinfo, prsId);
 	}
 
 	funcctx = SRF_PERCALL_SETUP();
@@ -164,7 +171,8 @@ typedef struct
 
 
 static void
-prs_setup_firstcall(FuncCallContext *funcctx, Oid prsid, text *txt)
+prs_setup_firstcall(FuncCallContext *funcctx, FunctionCallInfo fcinfo,
+					Oid prsid, text *txt)
 {
 	TupleDesc	tupdesc;
 	MemoryContext oldcontext;
@@ -209,12 +217,9 @@ prs_setup_firstcall(FuncCallContext *funcctx, Oid prsid, text *txt)
 	st->cur = 0;
 
 	funcctx->user_fctx = (void *) st;
-	tupdesc = CreateTemplateTupleDesc(2);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "tokid",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "token",
-					   TEXTOID, -1, 0);
-
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+	funcctx->tuple_desc = tupdesc;
 	funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
 	MemoryContextSwitchTo(oldcontext);
 }
@@ -256,7 +261,7 @@ ts_parse_byid(PG_FUNCTION_ARGS)
 		text	   *txt = PG_GETARG_TEXT_PP(1);
 
 		funcctx = SRF_FIRSTCALL_INIT();
-		prs_setup_firstcall(funcctx, PG_GETARG_OID(0), txt);
+		prs_setup_firstcall(funcctx, fcinfo, PG_GETARG_OID(0), txt);
 		PG_FREE_IF_COPY(txt, 1);
 	}
 
@@ -281,7 +286,7 @@ ts_parse_byname(PG_FUNCTION_ARGS)
 
 		funcctx = SRF_FIRSTCALL_INIT();
 		prsId = get_ts_parser_oid(textToQualifiedNameList(prsname), false);
-		prs_setup_firstcall(funcctx, prsId, txt);
+		prs_setup_firstcall(funcctx, fcinfo, prsId, txt);
 	}
 
 	funcctx = SRF_PERCALL_SETUP();
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index ed1b6a41cf..07a4afd6d0 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -1754,21 +1754,9 @@ aclexplode(PG_FUNCTION_ARGS)
 		funcctx = SRF_FIRSTCALL_INIT();
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
-		/*
-		 * build tupdesc for result tuples (matches out parameters in pg_proc
-		 * entry)
-		 */
-		tupdesc = CreateTemplateTupleDesc(4);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "grantor",
-						   OIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "grantee",
-						   OIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "privilege_type",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_grantable",
-						   BOOLOID, -1, 0);
-
-		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
 
 		/* allocate memory for user context */
 		idx = (int *) palloc(sizeof(int[2]));
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index b5b117a8ca..8afda0e5d2 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -4983,19 +4983,10 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 		*pindex = 0;
 		funcctx->user_fctx = (void *) pindex;
 
-		/*
-		 * build tupdesc for result tuples. This must match this function's
-		 * pg_proc entry!
-		 */
-		tupdesc = CreateTemplateTupleDesc(3);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "abbrev",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "utc_offset",
-						   INTERVALOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "is_dst",
-						   BOOLOID, -1, 0);
-
-		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
+
 		MemoryContextSwitchTo(oldcontext);
 	}
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ab6f67f874..efd948e9ae 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -483,24 +483,8 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					 errmsg("could not stat file \"%s\": %m", filename)));
 	}
 
-	/*
-	 * This record type had better match the output parameters declared for me
-	 * in pg_proc.h.
-	 */
-	tupdesc = CreateTemplateTupleDesc(6);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
-					   "size", INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
-					   "access", TIMESTAMPTZOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3,
-					   "modification", TIMESTAMPTZOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4,
-					   "change", TIMESTAMPTZOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
-					   "creation", TIMESTAMPTZOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
-	BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	memset(isnull, false, sizeof(isnull));
 
diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c
index f9b324efec..5e72c81bf1 100644
--- a/src/backend/utils/adt/lockfuncs.c
+++ b/src/backend/utils/adt/lockfuncs.c
@@ -109,43 +109,9 @@ pg_lock_status(PG_FUNCTION_ARGS)
 		 */
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
-		/* build tupdesc for result tuples */
-		/* this had better match function's declaration in pg_proc.h */
-		tupdesc = CreateTemplateTupleDesc(NUM_LOCK_STATUS_COLUMNS);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "locktype",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database",
-						   OIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "relation",
-						   OIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "page",
-						   INT4OID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 5, "tuple",
-						   INT2OID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 6, "virtualxid",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 7, "transactionid",
-						   XIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 8, "classid",
-						   OIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 9, "objid",
-						   OIDOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 10, "objsubid",
-						   INT2OID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 11, "virtualtransaction",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 12, "pid",
-						   INT4OID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 13, "mode",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 14, "granted",
-						   BOOLOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 15, "fastpath",
-						   BOOLOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 16, "waitstart",
-						   TIMESTAMPTZOID, -1, 0);
-
-		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
 
 		/*
 		 * Collect all the locking information that we will format and send
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index d678d28600..7808fbd448 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -427,18 +427,9 @@ pg_get_keywords(PG_FUNCTION_ARGS)
 		funcctx = SRF_FIRSTCALL_INIT();
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
-		tupdesc = CreateTemplateTupleDesc(5);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "word",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "catcode",
-						   CHAROID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "barelabel",
-						   BOOLOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "catdesc",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 5, "baredesc",
-						   TEXTOID, -1, 0);
-
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
 		funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
 
 		MemoryContextSwitchTo(oldcontext);
@@ -515,20 +506,8 @@ pg_get_catalog_foreign_keys(PG_FUNCTION_ARGS)
 		funcctx = SRF_FIRSTCALL_INIT();
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
-		tupdesc = CreateTemplateTupleDesc(6);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "fktable",
-						   REGCLASSOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "fkcols",
-						   TEXTARRAYOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "pktable",
-						   REGCLASSOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "pkcols",
-						   TEXTARRAYOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 5, "is_array",
-						   BOOLOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 6, "is_opt",
-						   BOOLOID, -1, 0);
-
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
 		/*
diff --git a/src/backend/utils/adt/partitionfuncs.c b/src/backend/utils/adt/partitionfuncs.c
index 96b5ae52d2..84518630a5 100644
--- a/src/backend/utils/adt/partitionfuncs.c
+++ b/src/backend/utils/adt/partitionfuncs.c
@@ -88,17 +88,9 @@ pg_partition_tree(PG_FUNCTION_ARGS)
 		 */
 		partitions = find_all_inheritors(rootrelid, AccessShareLock, NULL);
 
-		tupdesc = CreateTemplateTupleDesc(PG_PARTITION_TREE_COLS);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relid",
-						   REGCLASSOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "parentid",
-						   REGCLASSOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "isleaf",
-						   BOOLOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "level",
-						   INT4OID, -1, 0);
-
-		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
 
 		/* The only state we need is the partition list */
 		funcctx->user_fctx = (void *) partitions;
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 04a5a99002..060e1f606e 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1210,27 +1210,8 @@ pg_stat_get_wal(PG_FUNCTION_ARGS)
 	PgStat_WalStats *wal_stats;
 
 	/* Initialise attributes information in the tuple descriptor */
-	tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_WAL_COLS);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "wal_records",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "wal_fpi",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "wal_bytes",
-					   NUMERICOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "wal_buffers_full",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "wal_write",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "wal_sync",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "wal_write_time",
-					   FLOAT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "wal_sync_time",
-					   FLOAT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "stats_reset",
-					   TIMESTAMPTZOID, -1, 0);
-
-	BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* Get statistics about WAL activity */
 	wal_stats = pgstat_fetch_stat_wal();
@@ -1657,23 +1638,8 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
 	PgStat_ArchiverStats *archiver_stats;
 
 	/* Initialise attributes information in the tuple descriptor */
-	tupdesc = CreateTemplateTupleDesc(7);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "archived_count",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "last_archived_wal",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "last_archived_time",
-					   TIMESTAMPTZOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "failed_count",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "last_failed_wal",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "last_failed_time",
-					   TIMESTAMPTZOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "stats_reset",
-					   TIMESTAMPTZOID, -1, 0);
-
-	BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* Get statistics about the archiver process */
 	archiver_stats = pgstat_fetch_stat_archiver();
@@ -1727,28 +1693,8 @@ pg_stat_get_replication_slot(PG_FUNCTION_ARGS)
 	PgStat_StatReplSlotEntry allzero;
 
 	/* Initialise attributes information in the tuple descriptor */
-	tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_REPLICATION_SLOT_COLS);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "slot_name",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "spill_txns",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "spill_count",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "spill_bytes",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "stream_txns",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "stream_count",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "stream_bytes",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "total_txns",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "total_bytes",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "stats_reset",
-					   TIMESTAMPTZOID, -1, 0);
-	BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	namestrcpy(&slotname, text_to_cstring(slotname_text));
 	slotent = pgstat_fetch_replslot(slotname);
@@ -1800,16 +1746,8 @@ pg_stat_get_subscription_stats(PG_FUNCTION_ARGS)
 	subentry = pgstat_fetch_stat_subscription(subid);
 
 	/* Initialise attributes information in the tuple descriptor */
-	tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_SUBSCRIPTION_STATS_COLS);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "subid",
-					   OIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "apply_error_count",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "sync_error_count",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "stats_reset",
-					   TIMESTAMPTZOID, -1, 0);
-	BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	if (!subentry)
 	{
diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c
index f7c1e3d6d6..caeb85b4ca 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -647,7 +647,9 @@ tsvector_unnest(PG_FUNCTION_ARGS)
 						   INT2ARRAYOID, -1, 0);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "weights",
 						   TEXTARRAYOID, -1, 0);
-		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
 
 		funcctx->user_fctx = PG_GETARG_TSVECTOR_COPY(0);
 
@@ -2302,14 +2304,9 @@ ts_setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *funcctx,
 		}
 	Assert(stat->stackpos <= stat->maxdepth);
 
-	tupdesc = CreateTemplateTupleDesc(3);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "word",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "ndoc",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "nentry",
-					   INT4OID, -1, 0);
-	funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+	funcctx->tuple_desc = tupdesc;
 	funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
 
 	MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/utils/misc/guc_funcs.c b/src/backend/utils/misc/guc_funcs.c
index 23da603fe7..8909192f0f 100644
--- a/src/backend/utils/misc/guc_funcs.c
+++ b/src/backend/utils/misc/guc_funcs.c
@@ -873,45 +873,9 @@ show_all_settings(PG_FUNCTION_ARGS)
 		 */
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
-		/*
-		 * need a tuple descriptor representing NUM_PG_SETTINGS_ATTS columns
-		 * of the appropriate types
-		 */
-		tupdesc = CreateTemplateTupleDesc(NUM_PG_SETTINGS_ATTS);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "unit",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "category",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 5, "short_desc",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 6, "extra_desc",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 7, "context",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 8, "vartype",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 9, "source",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 10, "min_val",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 11, "max_val",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 12, "enumvals",
-						   TEXTARRAYOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 13, "boot_val",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 14, "reset_val",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sourcefile",
-						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 16, "sourceline",
-						   INT4OID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 17, "pending_restart",
-						   BOOLOID, -1, 0);
+		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+		funcctx->tuple_desc = tupdesc;
 
 		/*
 		 * Generate attribute metadata needed later to produce tuples from raw
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index 781f8b8758..0703892ed4 100644
--- a/src/backend/utils/misc/pg_controldata.c
+++ b/src/backend/utils/misc/pg_controldata.c
@@ -38,20 +38,8 @@ pg_control_system(PG_FUNCTION_ARGS)
 	ControlFileData *ControlFile;
 	bool		crc_ok;
 
-	/*
-	 * Construct a tuple descriptor for the result row.  This must match this
-	 * function's pg_proc entry!
-	 */
-	tupdesc = CreateTemplateTupleDesc(4);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_control_version",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "catalog_version_no",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "system_identifier",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "pg_control_last_modified",
-					   TIMESTAMPTZOID, -1, 0);
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* read the control file */
 	ControlFile = get_controlfile(DataDir, &crc_ok);
@@ -88,48 +76,8 @@ pg_control_checkpoint(PG_FUNCTION_ARGS)
 	char		xlogfilename[MAXFNAMELEN];
 	bool		crc_ok;
 
-	/*
-	 * Construct a tuple descriptor for the result row.  This must match this
-	 * function's pg_proc entry!
-	 */
-	tupdesc = CreateTemplateTupleDesc(18);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "checkpoint_lsn",
-					   PG_LSNOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "redo_lsn",
-					   PG_LSNOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "redo_wal_file",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "timeline_id",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "prev_timeline_id",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "full_page_writes",
-					   BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "next_xid",
-					   TEXTOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "next_oid",
-					   OIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "next_multixact_id",
-					   XIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "next_multi_offset",
-					   XIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 11, "oldest_xid",
-					   XIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 12, "oldest_xid_dbid",
-					   OIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 13, "oldest_active_xid",
-					   XIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 14, "oldest_multi_xid",
-					   XIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 15, "oldest_multi_dbid",
-					   OIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 16, "oldest_commit_ts_xid",
-					   XIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 17, "newest_commit_ts_xid",
-					   XIDOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 18, "checkpoint_time",
-					   TIMESTAMPTZOID, -1, 0);
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* Read the control file. */
 	ControlFile = get_controlfile(DataDir, &crc_ok);
@@ -217,22 +165,8 @@ pg_control_recovery(PG_FUNCTION_ARGS)
 	ControlFileData *ControlFile;
 	bool		crc_ok;
 
-	/*
-	 * Construct a tuple descriptor for the result row.  This must match this
-	 * function's pg_proc entry!
-	 */
-	tupdesc = CreateTemplateTupleDesc(5);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "min_recovery_end_lsn",
-					   PG_LSNOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "min_recovery_end_timeline",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "backup_start_lsn",
-					   PG_LSNOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "backup_end_lsn",
-					   PG_LSNOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "end_of_backup_record_required",
-					   BOOLOID, -1, 0);
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* read the control file */
 	ControlFile = get_controlfile(DataDir, &crc_ok);
@@ -270,34 +204,8 @@ pg_control_init(PG_FUNCTION_ARGS)
 	ControlFileData *ControlFile;
 	bool		crc_ok;
 
-	/*
-	 * Construct a tuple descriptor for the result row.  This must match this
-	 * function's pg_proc entry!
-	 */
-	tupdesc = CreateTemplateTupleDesc(11);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "max_data_alignment",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database_block_size",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "blocks_per_segment",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "wal_block_size",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "bytes_per_wal_segment",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "max_identifier_length",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "max_index_columns",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "max_toast_chunk_size",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "large_object_chunk_size",
-					   INT4OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "float8_pass_by_value",
-					   BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 11, "data_page_checksum_version",
-					   INT4OID, -1, 0);
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* read the control file */
 	ControlFile = get_controlfile(DataDir, &crc_ok);
diff --git a/src/test/modules/test_predtest/test_predtest.c b/src/test/modules/test_predtest/test_predtest.c
index 2ce88cb624..abb66180bc 100644
--- a/src/test/modules/test_predtest/test_predtest.c
+++ b/src/test/modules/test_predtest/test_predtest.c
@@ -185,24 +185,8 @@ test_predtest(PG_FUNCTION_ARGS)
 	if (SPI_finish() != SPI_OK_FINISH)
 		elog(ERROR, "SPI_finish failed");
 
-	tupdesc = CreateTemplateTupleDesc(8);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
-					   "strong_implied_by", BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
-					   "weak_implied_by", BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 3,
-					   "strong_refuted_by", BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 4,
-					   "weak_refuted_by", BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
-					   "s_i_holds", BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "w_i_holds", BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
-					   "s_r_holds", BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 8,
-					   "w_r_holds", BOOLOID, -1, 0);
-	tupdesc = BlessTupleDesc(tupdesc);
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	values[0] = BoolGetDatum(strong_implied_by);
 	values[1] = BoolGetDatum(weak_implied_by);
-- 
2.34.1

From 55ca110d878351d30e7cc3a145cc403669efd0e3 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Tue, 13 Dec 2022 07:04:42 +0000
Subject: [PATCH v1] Remove unnecessary BlessTupleDesc() after
 get_call_result_type()

get_call_result_type() already does what BlessTupleDesc() intends
to do for the returned tuple descriptor. Hence, it is unnecessary
to call BlessTupleDesc() it right after get_call_result_type().
---
 contrib/hstore/hstore_op.c       | 6 +-----
 contrib/pageinspect/brinfuncs.c  | 1 -
 contrib/pageinspect/btreefuncs.c | 2 --
 contrib/pageinspect/hashfuncs.c  | 2 --
 contrib/sslinfo/sslinfo.c        | 6 +-----
 src/backend/statistics/mcv.c     | 1 -
 src/test/regress/regress.c       | 1 -
 7 files changed, 2 insertions(+), 17 deletions(-)

diff --git a/contrib/hstore/hstore_op.c b/contrib/hstore/hstore_op.c
index 0d4ec16d1e..2b3a1e6a2f 100644
--- a/contrib/hstore/hstore_op.c
+++ b/contrib/hstore/hstore_op.c
@@ -862,13 +862,9 @@ setup_firstcall(FuncCallContext *funcctx, HStore *hs,
 
 	if (fcinfo)
 	{
-		TupleDesc	tupdesc;
-
 		/* Build a tuple descriptor for our result type */
-		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		if (get_call_result_type(fcinfo, NULL, &funcctx->tuple_desc) != TYPEFUNC_COMPOSITE)
 			elog(ERROR, "return type must be a row type");
-
-		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 	}
 
 	MemoryContextSwitchTo(oldcontext);
diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c
index 12a7217038..b812cdeee3 100644
--- a/contrib/pageinspect/brinfuncs.c
+++ b/contrib/pageinspect/brinfuncs.c
@@ -348,7 +348,6 @@ brin_metapage_info(PG_FUNCTION_ARGS)
 	/* Build a tuple descriptor for our result type */
 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 		elog(ERROR, "return type must be a row type");
-	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* Extract values from the metapage */
 	meta = (BrinMetaPageData *) PageGetContents(page);
diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 9375d55e14..5e083a6f9c 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -537,7 +537,6 @@ bt_page_items_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
 		/* Build a tuple descriptor for our result type */
 		if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
 			elog(ERROR, "return type must be a row type");
-		tupleDesc = BlessTupleDesc(tupleDesc);
 
 		uargs->tupd = tupleDesc;
 
@@ -653,7 +652,6 @@ bt_page_items_bytea(PG_FUNCTION_ARGS)
 		/* Build a tuple descriptor for our result type */
 		if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
 			elog(ERROR, "return type must be a row type");
-		tupleDesc = BlessTupleDesc(tupleDesc);
 
 		uargs->tupd = tupleDesc;
 
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
index 81815392d7..e1364c5518 100644
--- a/contrib/pageinspect/hashfuncs.c
+++ b/contrib/pageinspect/hashfuncs.c
@@ -259,7 +259,6 @@ hash_page_stats(PG_FUNCTION_ARGS)
 	/* Build a tuple descriptor for our result type */
 	if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
 		elog(ERROR, "return type must be a row type");
-	tupleDesc = BlessTupleDesc(tupleDesc);
 
 	j = 0;
 	values[j++] = Int32GetDatum(stat.live_items);
@@ -334,7 +333,6 @@ hash_page_items(PG_FUNCTION_ARGS)
 		/* Build a tuple descriptor for our result type */
 		if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
 			elog(ERROR, "return type must be a row type");
-		tupleDesc = BlessTupleDesc(tupleDesc);
 
 		fctx->attinmeta = TupleDescGetAttInMetadata(tupleDesc);
 
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b9874..6e313ced5e 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -370,9 +370,6 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 	if (SRF_IS_FIRSTCALL())
 	{
-
-		TupleDesc	tupdesc;
-
 		/* create a function context for cross-call persistence */
 		funcctx = SRF_FIRSTCALL_INIT();
 
@@ -385,11 +382,10 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		fctx = (SSLExtensionInfoContext *) palloc(sizeof(SSLExtensionInfoContext));
 
 		/* Construct tuple descriptor */
-		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		if (get_call_result_type(fcinfo, NULL, &fctx->tupdesc) != TYPEFUNC_COMPOSITE)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("function returning record called in context that cannot accept type record")));
-		fctx->tupdesc = BlessTupleDesc(tupdesc);
 
 		/* Set max_calls as a count of extensions in certificate */
 		max_calls = cert != NULL ? X509_get_ext_count(cert) : 0;
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index f5a7c31272..592c76fea7 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -1371,7 +1371,6 @@ pg_stats_ext_mcvlist_items(PG_FUNCTION_ARGS)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("function returning record called in context "
 							"that cannot accept type record")));
-		tupdesc = BlessTupleDesc(tupdesc);
 
 		/*
 		 * generate attribute metadata needed later to produce tuples from raw
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 2977045cc7..4d5e3e0918 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -1133,7 +1133,6 @@ test_enc_conversion(PG_FUNCTION_ARGS)
 	/* Build a tuple descriptor for our result type */
 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 		elog(ERROR, "return type must be a row type");
-	tupdesc = BlessTupleDesc(tupdesc);
 
 	srclen = VARSIZE_ANY_EXHDR(string);
 	src = VARDATA_ANY(string);
-- 
2.34.1

Reply via email to