From ff2892ecd7ceebde13321bade5b9855433da3295 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Tue, 2 Jun 2020 16:47:26 -0700
Subject: [PATCH v2 7/9] wip: make send/recv calls in printtup.c & copy.c
 cheaper.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/access/common/printtup.c |  37 ++++-
 src/backend/commands/copy.c          | 208 ++++++++++++++++++++-------
 2 files changed, 189 insertions(+), 56 deletions(-)

diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index dd1bac0aa9e..db23bb99c16 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -56,6 +56,15 @@ typedef struct
 	bool		typisvarlena;	/* is it varlena (ie possibly toastable)? */
 	int16		format;			/* format code for this column */
 	FmgrInfo	finfo;			/* Precomputed call info for output fn */
+
+	/* use union with FunctionCallInfoBaseData to guarantee alignment */
+	union
+	{
+		FunctionCallInfoBaseData fcinfo;
+		/* ensure enough space for nargs args is available */
+		char fcinfo_data[SizeForFunctionCallInfo(1)];
+	} fcinfo_data;
+
 } PrinttupAttrInfo;
 
 typedef struct
@@ -355,6 +364,9 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 							  &thisState->typoutput,
 							  &thisState->typisvarlena);
 			fmgr_info(thisState->typoutput, &thisState->finfo);
+			InitFunctionCallInfoData(thisState->fcinfo_data.fcinfo,
+									 &thisState->finfo, 1, InvalidOid,
+									 NULL, NULL);
 		}
 		else if (format == 1)
 		{
@@ -362,6 +374,9 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 									&thisState->typsend,
 									&thisState->typisvarlena);
 			fmgr_info(thisState->typsend, &thisState->finfo);
+			InitFunctionCallInfoData(thisState->fcinfo_data.fcinfo,
+									 &thisState->finfo, 1, InvalidOid,
+									 NULL, NULL);
 		}
 		else
 			ereport(ERROR,
@@ -438,11 +453,25 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 		{
 			/* Binary output */
 			bytea	   *outputbytes;
+			int			outputlen;
+			Datum		result;
+			FunctionCallInfo fcinfo = &thisState->fcinfo_data.fcinfo;
 
-			outputbytes = SendFunctionCall(&thisState->finfo, attr);
-			pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);
-			pq_sendbytes(buf, VARDATA(outputbytes),
-						 VARSIZE(outputbytes) - VARHDRSZ);
+			fcinfo->args[0].value = attr;
+			fcinfo->args[0].isnull = false;
+			result = FunctionCallInvoke(fcinfo);
+
+			/* Check for null result, since caller is clearly not expecting one */
+			if (unlikely(fcinfo->isnull))
+				elog(ERROR, "send function return null");
+
+			outputbytes = DatumGetByteaP(result);
+			outputlen = VARSIZE(outputbytes) - VARHDRSZ;
+
+			Assert(outputlen > 0);
+
+			pq_sendint32(buf, outputlen);
+			pq_sendbytes(buf, VARDATA(outputbytes), outputlen);
 		}
 	}
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index c12855a304a..bec1ce51260 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -95,6 +95,37 @@ typedef enum CopyInsertMethod
 	CIM_MULTI_CONDITIONAL		/* use table_multi_insert only if valid */
 } CopyInsertMethod;
 
+typedef struct CopyInAttributeInfo
+{
+	AttrNumber num;
+	const char *name;
+
+	Oid		   typioparam;
+	int32	   typmod;
+
+	FmgrInfo in_finfo;
+	union
+	{
+		FunctionCallInfoBaseData fcinfo;
+		char fcinfo_data[SizeForFunctionCallInfo(3)];
+	} in_fcinfo;
+
+} CopyInAttributeInfo;
+
+typedef struct CopyOutAttributeInfo
+{
+	AttrNumber num;
+	const char *name;
+	int32	   typid;
+
+	FmgrInfo out_finfo;
+	union
+	{
+		FunctionCallInfoBaseData fcinfo;
+		char fcinfo_data[SizeForFunctionCallInfo(1)];
+	} out_fcinfo;
+} CopyOutAttributeInfo;
+
 /*
  * This struct contains all the state variables used throughout a COPY
  * operation. For simplicity, we use the same struct for all variants of COPY,
@@ -169,15 +200,14 @@ typedef struct CopyStateData
 	/*
 	 * Working state for COPY TO
 	 */
-	FmgrInfo   *out_functions;	/* lookup info for output functions */
+	CopyOutAttributeInfo *out_attributes;
 	MemoryContext rowcontext;	/* per-row evaluation context */
 
 	/*
 	 * Working state for COPY FROM
 	 */
 	AttrNumber	num_defaults;
-	FmgrInfo   *in_functions;	/* array of input functions for each attrs */
-	Oid		   *typioparams;	/* array of element types for in_functions */
+	CopyInAttributeInfo *in_attributes;
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
@@ -368,8 +398,8 @@ static bool CopyReadLineText(CopyState cstate);
 static int	CopyReadAttributesText(CopyState cstate);
 static int	CopyReadAttributesCSV(CopyState cstate);
 static Datum CopyReadBinaryAttribute(CopyState cstate,
-									 int column_no, FmgrInfo *flinfo,
-									 Oid typioparam, int32 typmod,
+									 int column_no,
+									 CopyInAttributeInfo *attr,
 									 bool *isnull);
 static void CopyAttributeOutText(CopyState cstate, char *string);
 static void CopyAttributeOutCSV(CopyState cstate, char *string,
@@ -2027,23 +2057,34 @@ CopyTo(CopyState cstate)
 	cstate->fe_msgbuf = makeStringInfo();
 
 	/* Get info about the columns we need to process. */
-	cstate->out_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
+	cstate->out_attributes =
+		(CopyOutAttributeInfo *) palloc(num_phys_attrs * sizeof(CopyOutAttributeInfo));
 	foreach(cur, cstate->attnumlist)
 	{
 		int			attnum = lfirst_int(cur);
+		CopyOutAttributeInfo *attr = &cstate->out_attributes[attnum - 1];
+		Form_pg_attribute pgatt;
 		Oid			out_func_oid;
 		bool		isvarlena;
-		Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
+
+		pgatt = TupleDescAttr(tupDesc, attnum - 1);
+		attr->num = attnum;
+		attr->name = NameStr(pgatt->attname);
+		attr->typid = pgatt->atttypid;
 
 		if (cstate->binary)
-			getTypeBinaryOutputInfo(attr->atttypid,
+			getTypeBinaryOutputInfo(attr->typid,
 									&out_func_oid,
 									&isvarlena);
 		else
-			getTypeOutputInfo(attr->atttypid,
+			getTypeOutputInfo(attr->typid,
 							  &out_func_oid,
 							  &isvarlena);
-		fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
+
+		fmgr_info(out_func_oid, &attr->out_finfo);
+		InitFunctionCallInfoData(attr->out_fcinfo.fcinfo, &attr->out_finfo,
+								 1, InvalidOid,
+								 NULL, NULL);
 	}
 
 	/*
@@ -2156,7 +2197,7 @@ static void
 CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 {
 	bool		need_delim = false;
-	FmgrInfo   *out_functions = cstate->out_functions;
+	CopyOutAttributeInfo *out_attributes = cstate->out_attributes;
 	MemoryContext oldcontext;
 	ListCell   *cur;
 	char	   *string;
@@ -2176,6 +2217,8 @@ CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 	foreach(cur, cstate->attnumlist)
 	{
 		int			attnum = lfirst_int(cur);
+		CopyOutAttributeInfo *attr = &out_attributes[attnum - 1];
+		FunctionCallInfo fcinfo = &attr->out_fcinfo.fcinfo;
 		Datum		value = slot->tts_values[attnum - 1];
 		bool		isnull = slot->tts_isnull[attnum - 1];
 
@@ -2197,8 +2240,19 @@ CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 		{
 			if (!cstate->binary)
 			{
-				string = OutputFunctionCall(&out_functions[attnum - 1],
-											value);
+				Datum		result;
+
+				fcinfo->args[0].value = value;
+				fcinfo->args[0].isnull = false;
+
+				result = FunctionCallInvoke(fcinfo);
+
+				/* Check for null result, since caller is clearly not expecting one */
+				if (unlikely(fcinfo->isnull))
+					elog(ERROR, "send function return null");
+
+				string = DatumGetCString(result);
+
 				if (cstate->csv_mode)
 					CopyAttributeOutCSV(cstate, string,
 										cstate->force_quote_flags[attnum - 1],
@@ -2208,13 +2262,23 @@ CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 			}
 			else
 			{
+				int			outputlen;
+				Datum		result;
 				bytea	   *outputbytes;
 
-				outputbytes = SendFunctionCall(&out_functions[attnum - 1],
-											   value);
-				CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ);
-				CopySendData(cstate, VARDATA(outputbytes),
-							 VARSIZE(outputbytes) - VARHDRSZ);
+				fcinfo->args[0].value = value;
+				fcinfo->args[0].isnull = false;
+				result = FunctionCallInvoke(fcinfo);
+
+				/* Check for null result, since caller is clearly not expecting one */
+				if (unlikely(fcinfo->isnull))
+					elog(ERROR, "send function return null");
+
+				outputbytes = DatumGetByteaP(result);
+				outputlen = VARSIZE(outputbytes) - VARHDRSZ;
+
+				CopySendInt32(cstate, outputlen);
+				CopySendData(cstate, VARDATA(outputbytes), outputlen);
 			}
 		}
 	}
@@ -3344,8 +3408,7 @@ BeginCopyFrom(ParseState *pstate,
 	TupleDesc	tupDesc;
 	AttrNumber	num_phys_attrs,
 				num_defaults;
-	FmgrInfo   *in_functions;
-	Oid		   *typioparams;
+	CopyInAttributeInfo *in_attributes;
 	int			attnum;
 	Oid			in_func_oid;
 	int		   *defmap;
@@ -3386,30 +3449,39 @@ BeginCopyFrom(ParseState *pstate,
 	 * the input function), and info about defaults and constraints. (Which
 	 * input function we use depends on text/binary format choice.)
 	 */
-	in_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
-	typioparams = (Oid *) palloc(num_phys_attrs * sizeof(Oid));
+	in_attributes = (CopyInAttributeInfo * ) palloc0(num_phys_attrs * sizeof(CopyInAttributeInfo));
+
 	defmap = (int *) palloc(num_phys_attrs * sizeof(int));
 	defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *));
 
 	for (attnum = 1; attnum <= num_phys_attrs; attnum++)
 	{
-		Form_pg_attribute att = TupleDescAttr(tupDesc, attnum - 1);
+		CopyInAttributeInfo *attr = &in_attributes[attnum - 1];
+		Form_pg_attribute pgatt = TupleDescAttr(tupDesc, attnum - 1);
 
 		/* We don't need info for dropped attributes */
-		if (att->attisdropped)
+		if (pgatt->attisdropped)
 			continue;
 
+		attr->num = attnum;
+		attr->typmod = pgatt->atttypmod;
+		attr->name = NameStr(pgatt->attname);
+
 		/* Fetch the input function and typioparam info */
 		if (cstate->binary)
-			getTypeBinaryInputInfo(att->atttypid,
-								   &in_func_oid, &typioparams[attnum - 1]);
+			getTypeBinaryInputInfo(pgatt->atttypid,
+								   &in_func_oid, &attr->typioparam);
 		else
-			getTypeInputInfo(att->atttypid,
-							 &in_func_oid, &typioparams[attnum - 1]);
-		fmgr_info(in_func_oid, &in_functions[attnum - 1]);
+			getTypeInputInfo(pgatt->atttypid,
+							 &in_func_oid, &attr->typioparam);
+		attr->typmod = pgatt->atttypmod;
+
+		fmgr_info(in_func_oid, &attr->in_finfo);
+		InitFunctionCallInfoData(attr->in_fcinfo.fcinfo, &attr->in_finfo, 3,
+								 InvalidOid, NULL, NULL);
 
 		/* Get default info if needed */
-		if (!list_member_int(cstate->attnumlist, attnum) && !att->attgenerated)
+		if (!list_member_int(cstate->attnumlist, attnum) && !pgatt->attgenerated)
 		{
 			/* attribute is NOT to be copied from input */
 			/* use default value if one exists */
@@ -3446,8 +3518,7 @@ BeginCopyFrom(ParseState *pstate,
 	}
 
 	/* We keep those variables in cstate. */
-	cstate->in_functions = in_functions;
-	cstate->typioparams = typioparams;
+	cstate->in_attributes = in_attributes;
 	cstate->defmap = defmap;
 	cstate->defexprs = defexprs;
 	cstate->volatile_defexprs = volatile_defexprs;
@@ -3639,8 +3710,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 	AttrNumber	num_phys_attrs,
 				attr_count,
 				num_defaults = cstate->num_defaults;
-	FmgrInfo   *in_functions = cstate->in_functions;
-	Oid		   *typioparams = cstate->typioparams;
+	CopyInAttributeInfo *in_attributes = cstate->in_attributes;
 	int			i;
 	int		   *defmap = cstate->defmap;
 	ExprState **defexprs = cstate->defexprs;
@@ -3678,13 +3748,14 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 		{
 			int			attnum = lfirst_int(cur);
 			int			m = attnum - 1;
-			Form_pg_attribute att = TupleDescAttr(tupDesc, m);
+			CopyInAttributeInfo *attr = &in_attributes[m];
+			FunctionCallInfo fcinfo = &attr->in_fcinfo.fcinfo;
 
 			if (fieldno >= fldct)
 				ereport(ERROR,
 						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
 						 errmsg("missing data for column \"%s\"",
-								NameStr(att->attname))));
+								attr->name)));
 			string = field_strings[fieldno++];
 
 			if (cstate->convert_select_flags &&
@@ -3718,14 +3789,39 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 				}
 			}
 
-			cstate->cur_attname = NameStr(att->attname);
+			cstate->cur_attname = attr->name;
 			cstate->cur_attval = string;
-			values[m] = InputFunctionCall(&in_functions[m],
-										  string,
-										  typioparams[m],
-										  att->atttypmod);
-			if (string != NULL)
-				nulls[m] = false;
+
+			if (string == NULL && fcinfo->flinfo->fn_strict)
+				nulls[m] = true;
+			else
+			{
+				fcinfo->args[0].value = CStringGetDatum(string);
+				fcinfo->args[0].isnull = false;
+				fcinfo->args[1].value = ObjectIdGetDatum(attr->typioparam);
+				fcinfo->args[1].isnull = false;
+				fcinfo->args[2].value = Int32GetDatum(attr->typmod);
+				fcinfo->args[2].isnull = false;
+
+				values[m] = FunctionCallInvoke(fcinfo);
+
+				/* Should get null result if and only if str is NULL */
+				if (string == NULL)
+				{
+					if (unlikely(!fcinfo->isnull))
+						elog(ERROR, "input function %u returned non-NULL",
+							 fcinfo->flinfo->fn_oid);
+				}
+				else
+				{
+					if (unlikely(fcinfo->isnull))
+						elog(ERROR, "input function %u returned NULL",
+							 fcinfo->flinfo->fn_oid);
+				}
+
+				nulls[m] = string == NULL;
+			}
+
 			cstate->cur_attname = NULL;
 			cstate->cur_attval = NULL;
 		}
@@ -3787,9 +3883,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 			i++;
 			values[m] = CopyReadBinaryAttribute(cstate,
 												i,
-												&in_functions[m],
-												typioparams[m],
-												att->atttypmod,
+												&in_attributes[m],
 												&nulls[m]);
 			cstate->cur_attname = NULL;
 		}
@@ -4714,13 +4808,13 @@ endfield:
  * Read a binary attribute
  */
 static Datum
-CopyReadBinaryAttribute(CopyState cstate,
-						int column_no, FmgrInfo *flinfo,
-						Oid typioparam, int32 typmod,
+CopyReadBinaryAttribute(CopyState cstate, int column_no,
+						CopyInAttributeInfo *attr,
 						bool *isnull)
 {
 	int32		fld_size;
 	Datum		result;
+	FunctionCallInfo fcinfo = &attr->in_fcinfo.fcinfo;
 
 	if (!CopyGetInt32(cstate, &fld_size))
 		ereport(ERROR,
@@ -4729,7 +4823,7 @@ CopyReadBinaryAttribute(CopyState cstate,
 	if (fld_size == -1)
 	{
 		*isnull = true;
-		return ReceiveFunctionCall(flinfo, NULL, typioparam, typmod);
+		return ReceiveFunctionCall(fcinfo->flinfo, NULL, attr->typioparam, attr->typmod);
 	}
 	if (fld_size < 0)
 		ereport(ERROR,
@@ -4750,8 +4844,18 @@ CopyReadBinaryAttribute(CopyState cstate,
 	cstate->attribute_buf.data[fld_size] = '\0';
 
 	/* Call the column type's binary input converter */
-	result = ReceiveFunctionCall(flinfo, &cstate->attribute_buf,
-								 typioparam, typmod);
+	fcinfo->args[0].value = PointerGetDatum(&cstate->attribute_buf);
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = ObjectIdGetDatum(attr->typioparam);
+	fcinfo->args[1].isnull = false;
+	fcinfo->args[2].value = Int32GetDatum(attr->typmod);
+	fcinfo->args[2].isnull = false;
+
+	result = FunctionCallInvoke(fcinfo);
+
+	if (unlikely(fcinfo->isnull))
+		elog(ERROR, "receive function %u returned NULL",
+			 fcinfo->flinfo->fn_oid);
 
 	/* Trouble if it didn't eat the whole buffer */
 	if (cstate->attribute_buf.cursor != cstate->attribute_buf.len)
-- 
2.25.0.114.g5b0ca878e0

