From 62c3e61e50baacadcb038690353a3abc1f474538 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 6 Jun 2004 18:06:25 +0000
Subject: [PATCH] Add binary I/O support for composite types.

---
 src/backend/utils/adt/rowtypes.c | 319 ++++++++++++++++++++++++++++---
 1 file changed, 291 insertions(+), 28 deletions(-)

diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index 96cbac992c5..08189d3c143 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.2 2004/06/06 04:50:28 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.3 2004/06/06 18:06:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,8 +54,9 @@ record_in(PG_FUNCTION_ARGS)
 {
 	char	   *string = PG_GETARG_CSTRING(0);
 	Oid			tupType = PG_GETARG_OID(1);
-	HeapTuple	tuple;
+	int32		tupTypmod;
 	TupleDesc	tupdesc;
+	HeapTuple	tuple;
 	RecordIOData *my_extra;
 	int			ncolumns;
 	int			i;
@@ -74,7 +75,8 @@ record_in(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("input of anonymous composite types is not implemented")));
-	tupdesc = lookup_rowtype_tupdesc(tupType, -1);
+	tupTypmod = -1;				/* for all non-anonymous types */
+	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
 	ncolumns = tupdesc->natts;
 
 	/*
@@ -91,17 +93,17 @@ record_in(PG_FUNCTION_ARGS)
 							   + ncolumns * sizeof(ColumnIOData));
 		my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
 		my_extra->record_type = InvalidOid;
-		my_extra->record_typmod = -1;
+		my_extra->record_typmod = 0;
 	}
 
 	if (my_extra->record_type != tupType ||
-		my_extra->record_typmod != -1)
+		my_extra->record_typmod != tupTypmod)
 	{
 		MemSet(my_extra, 0,
 			   sizeof(RecordIOData) - sizeof(ColumnIOData)
 			   + ncolumns * sizeof(ColumnIOData));
 		my_extra->record_type = tupType;
-		my_extra->record_typmod = -1;
+		my_extra->record_typmod = tupTypmod;
 		my_extra->ncolumns = ncolumns;
 	}
 
@@ -109,7 +111,8 @@ record_in(PG_FUNCTION_ARGS)
 	nulls = (char *) palloc(ncolumns * sizeof(char));
 
 	/*
-	 * Scan the string.
+	 * Scan the string.  We use "buf" to accumulate the de-quoted data
+	 * for each column, which is then fed to the appropriate input converter.
 	 */
 	ptr = string;
 	/* Allow leading whitespace */
@@ -126,8 +129,9 @@ record_in(PG_FUNCTION_ARGS)
 	for (i = 0; i < ncolumns; i++)
 	{
 		ColumnIOData *column_info = &my_extra->columns[i];
+		Oid			column_type = tupdesc->attrs[i]->atttypid;
 
-		/* Check for null */
+		/* Check for null: completely empty input means null */
 		if (*ptr == ',' || *ptr == ')')
 		{
 			values[i] = (Datum) 0;
@@ -179,14 +183,14 @@ record_in(PG_FUNCTION_ARGS)
 			/*
 			 * Convert the column value
 			 */
-			if (column_info->column_type != tupdesc->attrs[i]->atttypid)
+			if (column_info->column_type != column_type)
 			{
-				getTypeInputInfo(tupdesc->attrs[i]->atttypid,
+				getTypeInputInfo(column_type,
 								 &column_info->typiofunc,
 								 &column_info->typioparam);
 				fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
 							  fcinfo->flinfo->fn_mcxt);
-				column_info->column_type = tupdesc->attrs[i]->atttypid;
+				column_info->column_type = column_type;
 			}
 
 			values[i] = FunctionCall3(&column_info->proc,
@@ -219,6 +223,7 @@ record_in(PG_FUNCTION_ARGS)
 		}
 	}
 
+	/* The check for ')' here is redundant except when ncolumns == 0 */
 	if (*ptr++ != ')')
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
@@ -274,6 +279,7 @@ record_out(PG_FUNCTION_ARGS)
 		tupTypmod = -1;
 	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
 	ncolumns = tupdesc->natts;
+
 	/* Build a temporary HeapTuple control structure */
 	tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
 	ItemPointerSetInvalid(&(tuple.t_self));
@@ -294,7 +300,7 @@ record_out(PG_FUNCTION_ARGS)
 							   + ncolumns * sizeof(ColumnIOData));
 		my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
 		my_extra->record_type = InvalidOid;
-		my_extra->record_typmod = -1;
+		my_extra->record_typmod = 0;
 	}
 
 	if (my_extra->record_type != tupType ||
@@ -308,9 +314,10 @@ record_out(PG_FUNCTION_ARGS)
 		my_extra->ncolumns = ncolumns;
 	}
 
-	/* Break down the tuple into fields */
 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
 	nulls = (char *) palloc(ncolumns * sizeof(char));
+
+	/* Break down the tuple into fields */
 	heap_deformtuple(&tuple, tupdesc, values, nulls);
 
 	/* And build the result string */
@@ -321,6 +328,7 @@ record_out(PG_FUNCTION_ARGS)
 	for (i = 0; i < ncolumns; i++)
 	{
 		ColumnIOData *column_info = &my_extra->columns[i];
+		Oid			column_type = tupdesc->attrs[i]->atttypid;
 		char	*value;
 		char	*tmp;
 		bool	nq;
@@ -335,19 +343,19 @@ record_out(PG_FUNCTION_ARGS)
 		}
 
 		/*
-		 * Convert the column value
+		 * Convert the column value to text
 		 */
-		if (column_info->column_type != tupdesc->attrs[i]->atttypid)
+		if (column_info->column_type != column_type)
 		{
 			bool	typIsVarlena;
 
-			getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
+			getTypeOutputInfo(column_type,
 							  &column_info->typiofunc,
 							  &column_info->typioparam,
 							  &typIsVarlena);
 			fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
 						  fcinfo->flinfo->fn_mcxt);
-			column_info->column_type = tupdesc->attrs[i]->atttypid;
+			column_info->column_type = column_type;
 		}
 
 		value = DatumGetCString(FunctionCall3(&column_info->proc,
@@ -370,6 +378,7 @@ record_out(PG_FUNCTION_ARGS)
 			}
 		}
 
+		/* And emit the string */
 		if (nq)
 			appendStringInfoChar(&buf, '"');
 		for (tmp = value; *tmp; tmp++)
@@ -377,7 +386,7 @@ record_out(PG_FUNCTION_ARGS)
 			char		ch = *tmp;
 
 			if (ch == '"' || ch == '\\')
-				appendStringInfoChar(&buf, '\\');
+				appendStringInfoChar(&buf, ch);
 			appendStringInfoChar(&buf, ch);
 		}
 		if (nq)
@@ -398,12 +407,154 @@ record_out(PG_FUNCTION_ARGS)
 Datum
 record_recv(PG_FUNCTION_ARGS)
 {
-	/* Need to decide on external format before we can write this */
-	ereport(ERROR,
-			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			 errmsg("input of composite types not implemented yet")));
+	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
+	Oid			tupType = PG_GETARG_OID(1);
+	int32		tupTypmod;
+	TupleDesc	tupdesc;
+	HeapTuple	tuple;
+	RecordIOData *my_extra;
+	int			ncolumns;
+	int			i;
+	Datum	   *values;
+	char	   *nulls;
 
-	PG_RETURN_VOID();			/* keep compiler quiet */
+	/*
+	 * Use the passed type unless it's RECORD; we can't support input
+	 * of anonymous types, mainly because there's no good way to figure
+	 * out which anonymous type is wanted.  Note that for RECORD,
+	 * what we'll probably actually get is RECORD's typelem, ie, zero.
+	 */
+	if (tupType == InvalidOid || tupType == RECORDOID)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("input of anonymous composite types is not implemented")));
+	tupTypmod = -1;				/* for all non-anonymous types */
+	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+	ncolumns = tupdesc->natts;
+
+	/*
+	 * We arrange to look up the needed I/O info just once per series of
+	 * calls, assuming the record type doesn't change underneath us.
+	 */
+	my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+	if (my_extra == NULL ||
+		my_extra->ncolumns != ncolumns)
+	{
+		fcinfo->flinfo->fn_extra =
+			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+							   sizeof(RecordIOData) - sizeof(ColumnIOData)
+							   + ncolumns * sizeof(ColumnIOData));
+		my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+		my_extra->record_type = InvalidOid;
+		my_extra->record_typmod = 0;
+	}
+
+	if (my_extra->record_type != tupType ||
+		my_extra->record_typmod != tupTypmod)
+	{
+		MemSet(my_extra, 0,
+			   sizeof(RecordIOData) - sizeof(ColumnIOData)
+			   + ncolumns * sizeof(ColumnIOData));
+		my_extra->record_type = tupType;
+		my_extra->record_typmod = tupTypmod;
+		my_extra->ncolumns = ncolumns;
+	}
+
+	values = (Datum *) palloc(ncolumns * sizeof(Datum));
+	nulls = (char *) palloc(ncolumns * sizeof(char));
+
+	/* Verify number of columns */
+	i = pq_getmsgint(buf, 4);
+	if (i != ncolumns)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("wrong number of columns: %d, expected %d",
+						i, ncolumns)));
+
+	/* Process each column */
+	for (i = 0; i < ncolumns; i++)
+	{
+		ColumnIOData *column_info = &my_extra->columns[i];
+		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Oid			coltypoid;
+		int			itemlen;
+
+		/* Verify column datatype */
+		coltypoid = pq_getmsgint(buf, sizeof(Oid));
+		if (coltypoid != column_type)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("wrong data type: %u, expected %u",
+						coltypoid, column_type)));
+
+		/* Get and check the item length */
+		itemlen = pq_getmsgint(buf, 4);
+		if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+					 errmsg("insufficient data left in message")));
+
+		if (itemlen == -1)
+		{
+			/* -1 length means NULL */
+			values[i] = (Datum) 0;
+			nulls[i] = 'n';
+		}
+		else
+		{
+			/*
+			 * Rather than copying data around, we just set up a phony
+			 * StringInfo pointing to the correct portion of the input buffer.
+			 * We assume we can scribble on the input buffer so as to maintain
+			 * the convention that StringInfos have a trailing null.
+			 */
+			StringInfoData item_buf;
+			char		csave;
+
+			item_buf.data = &buf->data[buf->cursor];
+			item_buf.maxlen = itemlen + 1;
+			item_buf.len = itemlen;
+			item_buf.cursor = 0;
+
+			buf->cursor += itemlen;
+
+			csave = buf->data[buf->cursor];
+			buf->data[buf->cursor] = '\0';
+
+			/* Now call the column's receiveproc */
+			if (column_info->column_type != column_type)
+			{
+				getTypeBinaryInputInfo(column_type,
+									   &column_info->typiofunc,
+									   &column_info->typioparam);
+				fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+							  fcinfo->flinfo->fn_mcxt);
+				column_info->column_type = column_type;
+			}
+
+			values[i] = FunctionCall2(&column_info->proc,
+									  PointerGetDatum(&item_buf),
+									  ObjectIdGetDatum(column_info->typioparam));
+
+			nulls[i] = ' ';
+
+			/* Trouble if it didn't eat the whole buffer */
+			if (item_buf.cursor != itemlen)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+						 errmsg("improper binary format in record column %d",
+								i + 1)));
+
+			buf->data[buf->cursor] = csave;
+		}
+	}
+
+	tuple = heap_formtuple(tupdesc, values, nulls);
+
+	pfree(values);
+	pfree(nulls);
+
+	PG_RETURN_HEAPTUPLEHEADER(tuple->t_data);
 }
 
 /*
@@ -412,10 +563,122 @@ record_recv(PG_FUNCTION_ARGS)
 Datum
 record_send(PG_FUNCTION_ARGS)
 {
-	/* Need to decide on external format before we can write this */
-	ereport(ERROR,
-			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			 errmsg("output of composite types not implemented yet")));
+	HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
+	Oid			tupType = PG_GETARG_OID(1);
+	int32		tupTypmod;
+	TupleDesc	tupdesc;
+	HeapTupleData tuple;
+	RecordIOData *my_extra;
+	int			ncolumns;
+	int			i;
+	Datum	   *values;
+	char	   *nulls;
+	StringInfoData buf;
+
+	/*
+	 * Use the passed type unless it's RECORD; in that case, we'd better
+	 * get the type info out of the datum itself.  Note that for RECORD,
+	 * what we'll probably actually get is RECORD's typelem, ie, zero.
+	 */
+	if (tupType == InvalidOid || tupType == RECORDOID)
+	{
+		tupType = HeapTupleHeaderGetTypeId(rec);
+		tupTypmod = HeapTupleHeaderGetTypMod(rec);
+	}
+	else
+		tupTypmod = -1;
+	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+	ncolumns = tupdesc->natts;
+
+	/* Build a temporary HeapTuple control structure */
+	tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+	ItemPointerSetInvalid(&(tuple.t_self));
+	tuple.t_tableOid = InvalidOid;
+	tuple.t_data = rec;
+
+	/*
+	 * We arrange to look up the needed I/O info just once per series of
+	 * calls, assuming the record type doesn't change underneath us.
+	 */
+	my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+	if (my_extra == NULL ||
+		my_extra->ncolumns != ncolumns)
+	{
+		fcinfo->flinfo->fn_extra =
+			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+							   sizeof(RecordIOData) - sizeof(ColumnIOData)
+							   + ncolumns * sizeof(ColumnIOData));
+		my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+		my_extra->record_type = InvalidOid;
+		my_extra->record_typmod = 0;
+	}
+
+	if (my_extra->record_type != tupType ||
+		my_extra->record_typmod != tupTypmod)
+	{
+		MemSet(my_extra, 0,
+			   sizeof(RecordIOData) - sizeof(ColumnIOData)
+			   + ncolumns * sizeof(ColumnIOData));
+		my_extra->record_type = tupType;
+		my_extra->record_typmod = tupTypmod;
+		my_extra->ncolumns = ncolumns;
+	}
+
+	values = (Datum *) palloc(ncolumns * sizeof(Datum));
+	nulls = (char *) palloc(ncolumns * sizeof(char));
+
+	/* Break down the tuple into fields */
+	heap_deformtuple(&tuple, tupdesc, values, nulls);
+
+	/* And build the result string */
+	pq_begintypsend(&buf);
+
+	pq_sendint(&buf, ncolumns, 4);
+
+	for (i = 0; i < ncolumns; i++)
+	{
+		ColumnIOData *column_info = &my_extra->columns[i];
+		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		bytea	   *outputbytes;
+
+		pq_sendint(&buf, column_type, sizeof(Oid));
+
+		if (nulls[i] == 'n')
+		{
+			/* emit -1 data length to signify a NULL */
+			pq_sendint(&buf, -1, 4);
+			continue;
+		}
+
+		/*
+		 * Convert the column value to binary
+		 */
+		if (column_info->column_type != column_type)
+		{
+			bool	typIsVarlena;
+
+			getTypeBinaryOutputInfo(column_type,
+									&column_info->typiofunc,
+									&column_info->typioparam,
+									&typIsVarlena);
+			fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+						  fcinfo->flinfo->fn_mcxt);
+			column_info->column_type = column_type;
+		}
+
+		outputbytes = DatumGetByteaP(FunctionCall2(&column_info->proc,
+												   values[i],
+												   ObjectIdGetDatum(column_info->typioparam)));
+
+		/* We assume the result will not have been toasted */
+		pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
+		pq_sendbytes(&buf, VARDATA(outputbytes),
+					 VARSIZE(outputbytes) - VARHDRSZ);
+		pfree(outputbytes);
+	}
+
+	pfree(values);
+	pfree(nulls);
 
-	PG_RETURN_VOID();			/* keep compiler quiet */
+	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }
-- 
GitLab