From c06aa2636a47812ef17d8be7d7e43a8a49e88403 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 17 Feb 2024 17:38:47 -0800
Subject: [PATCH v3 10/10] heavy-wip: Allow string buffer reuse in send
 functions

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/include/libpq/pqformat.h         | 19 +++++++++++++++++--
 src/backend/access/common/printtup.c |  9 +++++++--
 src/backend/commands/copyto.c        |  8 +++++++-
 src/backend/utils/adt/int.c          | 10 ++++++----
 src/backend/utils/adt/int8.c         |  9 +++++----
 src/backend/utils/adt/varlena.c      | 10 ++++++----
 src/backend/utils/fmgr/fmgr.c        | 17 ++++++++++++++++-
 7 files changed, 64 insertions(+), 18 deletions(-)

diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h
index bc35bf3f28a..7792691b4cf 100644
--- a/src/include/libpq/pqformat.h
+++ b/src/include/libpq/pqformat.h
@@ -40,8 +40,16 @@ static inline void
 pq_begintypsend(StringInfo buf)
 {
 	initStringInfo(buf);
-	/* Reserve four bytes for the bytea length word */
-	appendStringInfoSpaces(buf, 4);
+
+	/*
+	 * Reserve four bytes for the bytea length word.  We don't need to fill
+	 * them with anything (pq_endtypsend will do that), and this function is
+	 * enough of a hot spot that it's worth cheating to save some cycles. Note
+	 * in particular that we don't bother to guarantee that the buffer is
+	 * null-terminated.
+	 */
+	Assert(buf->maxlen > 4);
+	buf->len = 4;
 }
 
 /* --------------------------------
@@ -61,6 +69,13 @@ pq_begintypsend_with_size(StringInfo buf, int size)
 	appendStringInfoSpaces(buf, 4);
 }
 
+static inline void
+pq_begintypsend_res(StringInfo buf)
+{
+	Assert(buf && buf->data && buf->len == 0);
+
+	buf->len = 4;
+}
 
 /* --------------------------------
  *		pq_endtypsend	- finish constructing a bytea result
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index bee3fc26220..d7d6d12f242 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -69,6 +69,7 @@ typedef struct
 	int			nattrs;
 	PrinttupAttrInfo *myinfo;	/* Cached info about each attr */
 	StringInfoData buf;			/* output buffer (*not* in tmpcontext) */
+	StringInfoData fieldbuf;	/* FIXME */
 	MemoryContext tmpcontext;	/* Memory context for per-row workspace */
 } DR_printtup;
 
@@ -128,6 +129,8 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	initStringInfo(&myState->buf);
 
+	initStringInfo(&myState->fieldbuf);
+
 	/*
 	 * Create a temporary memory context that we can reset once per row to
 	 * recover palloc'd memory.  This avoids any problems with leaks inside
@@ -289,7 +292,7 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 			fmgr_info(thisState->typoutput, &thisState->finfo);
 			InitFunctionCallInfoData(thisState->fcinfo_data.fcinfo,
 									 &thisState->finfo, 1, InvalidOid,
-									 NULL, NULL);
+									 (Node *) &myState->fieldbuf, NULL);
 		}
 		else if (format == 1)
 		{
@@ -299,7 +302,7 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 			fmgr_info(thisState->typsend, &thisState->finfo);
 			InitFunctionCallInfoData(thisState->fcinfo_data.fcinfo,
 									 &thisState->finfo, 1, InvalidOid,
-									 NULL, NULL);
+									 (Node *) &myState->fieldbuf, NULL);
 		}
 		else
 			ereport(ERROR,
@@ -319,6 +322,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 	DR_printtup *myState = (DR_printtup *) self;
 	MemoryContext oldcontext;
 	StringInfo	buf = &myState->buf;
+	StringInfo	fieldbuf = &myState->fieldbuf;
 	int			natts = typeinfo->natts;
 	int			i;
 
@@ -398,6 +402,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 
 			pq_sendint32(buf, outputlen);
 			pq_sendbytes(buf, VARDATA(outputbytes), outputlen);
+			resetStringInfo(fieldbuf);
 		}
 	}
 
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index b5cada5cb75..dd552db4828 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -110,6 +110,8 @@ typedef struct CopyToStateData
 	 */
 	MemoryContext copycontext;	/* per-copy execution context */
 
+	StringInfoData attribute_buf;
+
 	CopyOutAttributeInfo *out_attributes;
 
 	MemoryContext rowcontext;	/* per-row evaluation context */
@@ -782,6 +784,8 @@ DoCopyTo(CopyToState cstate)
 	/* We use fe_msgbuf as a per-row buffer regardless of copy_dest */
 	cstate->fe_msgbuf = makeStringInfo();
 
+	initStringInfo(&cstate->attribute_buf);
+
 	/* Get info about the columns we need to process. */
 	cstate->out_attributes =
 		(CopyOutAttributeInfo *) palloc(num_phys_attrs * sizeof(CopyOutAttributeInfo));
@@ -810,7 +814,7 @@ DoCopyTo(CopyToState cstate)
 		fmgr_info(out_func_oid, &attr->out_finfo);
 		InitFunctionCallInfoData(attr->out_fcinfo.fcinfo, &attr->out_finfo,
 								 1, InvalidOid,
-								 NULL, NULL);
+								 (Node *) &cstate->attribute_buf, NULL);
 	}
 
 	/*
@@ -938,6 +942,7 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
 	MemoryContext oldcontext;
 	ListCell   *cur;
 	char	   *string;
+	StringInfo	attribute_buf = &cstate->attribute_buf;
 
 	MemoryContextReset(cstate->rowcontext);
 	oldcontext = MemoryContextSwitchTo(cstate->rowcontext);
@@ -1021,6 +1026,7 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
 
 				CopySendInt32(cstate, outputlen);
 				CopySendData(cstate, VARDATA(outputbytes), outputlen);
+				resetStringInfo(attribute_buf);
 			}
 		}
 	}
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 761dc4acce9..2fdf2b247de 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -322,11 +322,13 @@ Datum
 int4send(PG_FUNCTION_ARGS)
 {
 	int32		arg1 = PG_GETARG_INT32(0);
-	StringInfoData buf;
+	StringInfo	buf;
 
-	pq_begintypsend_with_size(&buf, 4);
-	pq_sendint32(&buf, arg1);
-	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+	buf = (StringInfo) fcinfo->context;
+
+	pq_begintypsend_res(buf);
+	pq_sendint32(buf, arg1);
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
 }
 
 
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 5423cb2ecde..a171125b110 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -95,11 +95,12 @@ Datum
 int8send(PG_FUNCTION_ARGS)
 {
 	int64		arg1 = PG_GETARG_INT64(0);
-	StringInfoData buf;
+	StringInfo	buf;
 
-	pq_begintypsend_with_size(&buf, 8);
-	pq_sendint64(&buf, arg1);
-	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+	buf = (StringInfo) fcinfo->context;
+	pq_begintypsend_res(buf);
+	pq_sendint64(buf, arg1);
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
 }
 
 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index ea696c1dc71..387e368726e 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -619,11 +619,13 @@ Datum
 textsend(PG_FUNCTION_ARGS)
 {
 	text	   *t = PG_GETARG_TEXT_PP(0);
-	StringInfoData buf;
+	StringInfo	buf = (StringInfo) fcinfo->context;
 
-	pq_begintypsend_with_size(&buf, VARSIZE_ANY_EXHDR(t));
-	pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
-	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+	Assert(fcinfo->context);
+
+	pq_begintypsend_res(buf);
+	pq_sendtext(buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
 }
 
 
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index e48a86be54b..5677a9a6bdf 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1743,7 +1743,22 @@ ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
 bytea *
 SendFunctionCall(FmgrInfo *flinfo, Datum val)
 {
-	return DatumGetByteaP(FunctionCall1(flinfo, val));
+	StringInfoData buf;
+	Datum		result;
+
+	LOCAL_FCINFO(fcinfo, 1);
+
+	initStringInfo(&buf);
+
+	InitFunctionCallInfoData(*fcinfo, flinfo, 1, InvalidOid, (Node *) &buf, NULL);
+	fcinfo->args[0].value = val;
+	fcinfo->args[0].isnull = false;
+	result = FunctionCallInvoke(fcinfo);
+
+	if (fcinfo->isnull)
+		elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+	return DatumGetByteaP(result);
 }
 
 /*
-- 
2.38.0

