diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index fdf4ca8621..91273e4933 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.39 1999/01/24 22:50:58 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.40 1999/01/27 00:36:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,6 +27,10 @@
 #include <mb/pg_wchar.h>
 #endif
 
+static void printtup_setup(DestReceiver* self, TupleDesc typeinfo);
+static void printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver* self);
+static void printtup_cleanup(DestReceiver* self);
+
 /* ----------------------------------------------------------------
  *		printtup / debugtup support
  * ----------------------------------------------------------------
@@ -64,13 +68,89 @@ getTypeOutAndElem(Oid type, Oid* typOutput, Oid* typElem)
 	return 0;
 }
 
+/* ----------------
+ *		Private state for a printtup destination object
+ * ----------------
+ */
+typedef struct {				/* Per-attribute information */
+	Oid			typoutput;		/* Oid for the attribute's type output fn */
+	Oid			typelem;		/* typelem value to pass to the output fn */
+	/* more soon... */
+} PrinttupAttrInfo;
+
+typedef struct {
+	DestReceiver		pub;		/* publicly-known function pointers */
+	TupleDesc			attrinfo;	/* The attr info we are set up for */
+	int					nattrs;
+	PrinttupAttrInfo   *myinfo;		/* Cached info about each attr */
+} DR_printtup;
+
+/* ----------------
+ *		Initialize: create a DestReceiver for printtup
+ * ----------------
+ */
+DestReceiver*
+printtup_create_DR()
+{
+	DR_printtup* self = (DR_printtup*) palloc(sizeof(DR_printtup));
+
+	self->pub.receiveTuple = printtup;
+	self->pub.setup = printtup_setup;
+	self->pub.cleanup = printtup_cleanup;
+
+	self->attrinfo = NULL;
+	self->nattrs = 0;
+	self->myinfo = NULL;
+
+	return (DestReceiver*) self;
+}
+
+static void
+printtup_setup(DestReceiver* self, TupleDesc typeinfo)
+{
+	/* ----------------
+	 * We could set up the derived attr info at this time, but we postpone it
+	 * until the first call of printtup, for 3 reasons:
+	 * 1. We don't waste time (compared to the old way) if there are no
+	 *    tuples at all to output.
+	 * 2. Checking in printtup allows us to handle the case that the tuples
+	 *    change type midway through (although this probably can't happen in
+	 *    the current executor).
+	 * 3. Right now, ExecutorRun passes a NULL for typeinfo anyway :-(
+	 * ----------------
+	 */
+}
+
+static void
+printtup_prepare_info(DR_printtup* myState, TupleDesc typeinfo, int numAttrs)
+{
+	int i;
+
+	if (myState->myinfo)
+		pfree(myState->myinfo);	/* get rid of any old data */
+	myState->myinfo = NULL;
+	myState->attrinfo = typeinfo;
+	myState->nattrs = numAttrs;
+	if (numAttrs <= 0)
+		return;
+	myState->myinfo = (PrinttupAttrInfo*)
+		palloc(numAttrs * sizeof(PrinttupAttrInfo));
+	for (i = 0; i < numAttrs; i++)
+	{
+		PrinttupAttrInfo* thisState = myState->myinfo + i;
+		getTypeOutAndElem((Oid) typeinfo->attrs[i]->atttypid,
+						  &thisState->typoutput, &thisState->typelem);
+	}
+}
+
 /* ----------------
  *		printtup
  * ----------------
  */
-void
-printtup(HeapTuple tuple, TupleDesc typeinfo)
+static void
+printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver* self)
 {
+	DR_printtup *myState = (DR_printtup*) self;
 	int			i,
 				j,
 				k,
@@ -78,12 +158,15 @@ printtup(HeapTuple tuple, TupleDesc typeinfo)
 	char	   *outputstr;
 	Datum		attr;
 	bool		isnull;
-	Oid			typoutput,
-				typelem;
 #ifdef MULTIBYTE
 	unsigned char *p;
 #endif
 
+	/* Set or update my derived attribute info, if needed */
+	if (myState->attrinfo != typeinfo ||
+		myState->nattrs != tuple->t_data->t_natts)
+		printtup_prepare_info(myState, typeinfo, tuple->t_data->t_natts);
+
 	/* ----------------
 	 *	tell the frontend to expect new tuple data (in ASCII style)
 	 * ----------------
@@ -120,10 +203,11 @@ printtup(HeapTuple tuple, TupleDesc typeinfo)
 		attr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
 		if (isnull)
 			continue;
-		if (getTypeOutAndElem((Oid) typeinfo->attrs[i]->atttypid,
-							  &typoutput, &typelem))
+		if (OidIsValid(myState->myinfo[i].typoutput))
 		{
-			outputstr = fmgr(typoutput, attr, typelem,
+			outputstr = fmgr(myState->myinfo[i].typoutput,
+							 attr,
+							 myState->myinfo[i].typelem,
 							 typeinfo->attrs[i]->atttypmod);
 #ifdef MULTIBYTE
 			p = pg_server_to_client(outputstr, strlen(outputstr));
@@ -147,6 +231,19 @@ printtup(HeapTuple tuple, TupleDesc typeinfo)
 	}
 }
 
+/* ----------------
+ *		printtup_cleanup
+ * ----------------
+ */
+static void
+printtup_cleanup(DestReceiver* self)
+{
+	DR_printtup* myState = (DR_printtup*) self;
+	if (myState->myinfo)
+		pfree(myState->myinfo);
+	pfree(myState);
+}
+
 /* ----------------
  *		printatt
  * ----------------
@@ -190,7 +287,7 @@ showatts(char *name, TupleDesc tupleDesc)
  * ----------------
  */
 void
-debugtup(HeapTuple tuple, TupleDesc typeinfo)
+debugtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver* self)
 {
 	int			i;
 	Datum		attr;
@@ -221,11 +318,12 @@ debugtup(HeapTuple tuple, TupleDesc typeinfo)
  *		We use a different data prefix, e.g. 'B' instead of 'D' to
  *		indicate a tuple in internal (binary) form.
  *
- *		This is same as printtup, except we don't use the typout func.
+ *		This is same as printtup, except we don't use the typout func,
+ *		and therefore have no need for persistent state.
  * ----------------
  */
 void
-printtup_internal(HeapTuple tuple, TupleDesc typeinfo)
+printtup_internal(HeapTuple tuple, TupleDesc typeinfo, DestReceiver* self)
 {
 	int			i,
 				j,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index e79816389c..79cffc49f8 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.63 1999/01/25 12:01:03 vadim Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.64 1999/01/27 00:36:20 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -66,9 +66,10 @@ static void EndPlan(Plan *plan, EState *estate);
 static TupleTableSlot *ExecutePlan(EState *estate, Plan *plan,
 			Query *parseTree, CmdType operation,
 			int numberTuples, ScanDirection direction,
-			void (*printfunc) ());
-static void ExecRetrieve(TupleTableSlot *slot, void (*printfunc) (),
-									 EState *estate);
+			DestReceiver *destfunc);
+static void ExecRetrieve(TupleTableSlot *slot,
+						 DestReceiver *destfunc,
+						 EState *estate);
 static void ExecAppend(TupleTableSlot *slot, ItemPointer tupleid,
 		   EState *estate);
 static void ExecDelete(TupleTableSlot *slot, ItemPointer tupleid,
@@ -171,7 +172,7 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
 	Plan	   *plan;
 	TupleTableSlot *result;
 	CommandDest dest;
-	void		(*destination) ();
+	DestReceiver   *destfunc;
 
 	/******************
 	 *	sanity checks
@@ -188,10 +189,19 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
 	parseTree = queryDesc->parsetree;
 	plan = queryDesc->plantree;
 	dest = queryDesc->dest;
-	destination = (void (*) ()) DestToFunction(dest);
+	destfunc = DestToFunction(dest);
 	estate->es_processed = 0;
 	estate->es_lastoid = InvalidOid;
 
+	/******************
+	 *	FIXME: the dest setup function ought to be handed the tuple desc
+	 *  for the tuples to be output, but I'm not quite sure how to get that
+	 *  info at this point.  For now, passing NULL is OK because no existing
+	 *  dest setup function actually uses the pointer.
+	 ******************
+	 */
+	(*destfunc->setup) (destfunc, (TupleDesc) NULL);
+
 	switch (feature)
 	{
 
@@ -202,7 +212,7 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
 								 operation,
 								 ALL_TUPLES,
 								 ForwardScanDirection,
-								 destination);
+								 destfunc);
 			break;
 		case EXEC_FOR:
 			result = ExecutePlan(estate,
@@ -211,7 +221,7 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
 								 operation,
 								 count,
 								 ForwardScanDirection,
-								 destination);
+								 destfunc);
 			break;
 
 			/******************
@@ -225,7 +235,7 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
 								 operation,
 								 count,
 								 BackwardScanDirection,
-								 destination);
+								 destfunc);
 			break;
 
 			/******************
@@ -240,7 +250,7 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
 								 operation,
 								 ONE_TUPLE,
 								 ForwardScanDirection,
-								 destination);
+								 destfunc);
 			break;
 		default:
 			result = NULL;
@@ -248,6 +258,8 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
 			break;
 	}
 
+	(*destfunc->cleanup) (destfunc);
+
 	return result;
 }
 
@@ -745,7 +757,7 @@ ExecutePlan(EState *estate,
 			CmdType operation,
 			int numberTuples,
 			ScanDirection direction,
-			void (*printfunc) ())
+			DestReceiver* destfunc)
 {
 	JunkFilter *junkfilter;
 
@@ -905,7 +917,7 @@ ExecutePlan(EState *estate,
 		{
 			case CMD_SELECT:
 				ExecRetrieve(slot,		/* slot containing tuple */
-							 printfunc, /* print function */
+							 destfunc,	/* destination's tuple-receiver obj */
 							 estate);	/* */
 				result = slot;
 				break;
@@ -961,7 +973,7 @@ ExecutePlan(EState *estate,
  */
 static void
 ExecRetrieve(TupleTableSlot *slot,
-			 void (*printfunc) (),
+			 DestReceiver *destfunc,
 			 EState *estate)
 {
 	HeapTuple	tuple;
@@ -988,7 +1000,7 @@ ExecRetrieve(TupleTableSlot *slot,
 	 *	send the tuple to the front end (or the screen)
 	 ******************
 	 */
-	(*printfunc) (tuple, attrtype);
+	(*destfunc->receiveTuple) (tuple, attrtype, destfunc);
 	IncrRetrieved();
 	(estate->es_processed)++;
 }
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 5620cf7891..a7358425b4 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -3,7 +3,7 @@
  * spi.c--
  *				Server Programming Interface
  *
- * $Id: spi.c,v 1.30 1999/01/24 05:40:48 tgl Exp $
+ * $Id: spi.c,v 1.31 1999/01/27 00:36:21 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,8 +32,6 @@ uint32		SPI_processed = 0;
 SPITupleTable *SPI_tuptable;
 int			SPI_result;
 
-void		spi_printtup(HeapTuple tuple, TupleDesc tupdesc);
-
 typedef struct
 {
 	QueryTreeList *qtlist;
@@ -566,7 +564,7 @@ SPI_pfree(void *pointer)
  *
  */
 void
-spi_printtup(HeapTuple tuple, TupleDesc tupdesc)
+spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver* self)
 {
 	SPITupleTable *tuptable;
 	MemoryContext oldcxt;
diff --git a/src/backend/libpq/be-dumpdata.c b/src/backend/libpq/be-dumpdata.c
index 70d01e4dcf..4247b1c674 100644
--- a/src/backend/libpq/be-dumpdata.c
+++ b/src/backend/libpq/be-dumpdata.c
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- *  $Id: be-dumpdata.c,v 1.20 1999/01/24 05:40:49 tgl Exp $
+ *  $Id: be-dumpdata.c,v 1.21 1999/01/27 00:36:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -208,7 +208,7 @@ be_typeinit(PortalEntry *entry,
  * ----------------
  */
 void
-be_printtup(HeapTuple tuple, TupleDesc typeinfo)
+be_printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver* self)
 {
 	int			i;
 	Datum		attr;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index af3a3eb847..3acf895e7c 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.18 1998/09/01 04:29:10 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.19 1999/01/27 00:36:28 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -275,7 +275,7 @@ print_slot(TupleTableSlot *slot)
 		return;
 	}
 
-	debugtup(slot->val, slot->ttc_tupleDescriptor);
+	debugtup(slot->val, slot->ttc_tupleDescriptor, NULL);
 }
 
 static char *
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 0c1a7c530f..0d946287f3 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -1,19 +1,20 @@
 /*-------------------------------------------------------------------------
  *
  * dest.c--
- *	  support for various communication destinations - see lib/H/tcop/dest.h
+ *	  support for various communication destinations - see include/tcop/dest.h
  *
  * Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.23 1998/09/01 04:32:10 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.24 1999/01/27 00:36:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 /*
  *	 INTERFACE ROUTINES
  *		BeginCommand - prepare destination for tuples of the given type
+ *		DestToFunction - identify per-tuple processing routines
  *		EndCommand - tell destination that no more tuples will arrive
  *		NullCommand - tell dest that an empty query string was recognized
  *		ReadyForQuery - tell dest that we are ready for a new query
@@ -23,6 +24,13 @@
  *		tuples are returned by a query to keep the backend and the
  *		"destination" portals synchronized.
  *
+ *		There is a second level of initialization/cleanup performed by the
+ *		setup/cleanup routines identified by DestToFunction.  This could
+ *		probably be merged with the work done by BeginCommand/EndCommand,
+ *		but as of right now BeginCommand/EndCommand are used in a rather
+ *		unstructured way --- some places call Begin without End, some vice
+ *		versa --- so I think I'll just leave 'em alone for now.  tgl 1/99.
+ *
  */
 #include <stdio.h>				/* for sprintf() */
 #include <string.h>
@@ -47,182 +55,43 @@
 static char CommandInfo[32] = {0};
 
 /* ----------------
- *		output functions
+ *		dummy DestReceiver functions
  * ----------------
  */
 static void
-donothing(HeapTuple tuple, TupleDesc attrdesc)
+donothingReceive (HeapTuple tuple, TupleDesc typeinfo, DestReceiver* self)
 {
 }
 
-extern void spi_printtup(HeapTuple tuple, TupleDesc tupdesc);
-
-void		(*
-			 DestToFunction(CommandDest dest)) (HeapTuple, TupleDesc)
+static void
+donothingSetup (DestReceiver* self, TupleDesc typeinfo)
 {
-	switch (dest)
-	{
-			case RemoteInternal:
-			return printtup_internal;
-			break;
+}
 
-		case Remote:
-			return printtup;
-			break;
-
-		case Local:
-			return be_printtup;
-			break;
-
-		case Debug:
-			return debugtup;
-			break;
-
-		case SPI:
-			return spi_printtup;
-			break;
-
-		case None:
-		default:
-			return donothing;
-			break;
-	}
-
-	/*
-	 * never gets here, but DECstation lint appears to be stupid...
-	 */
-
-	return donothing;
+static void
+donothingCleanup (DestReceiver* self)
+{
 }
 
 /* ----------------
- *		EndCommand - tell destination that no more tuples will arrive
+ *		static DestReceiver structs for dest types needing no local state
  * ----------------
  */
-void
-EndCommand(char *commandTag, CommandDest dest)
-{
-	char		buf[64];
-
-	switch (dest)
-	{
-		case RemoteInternal:
-		case Remote:
-			/* ----------------
-			 *		tell the fe that the query is over
-			 * ----------------
-			 */
-			pq_putnchar("C", 1);
-			sprintf(buf, "%s%s", commandTag, CommandInfo);
-			CommandInfo[0] = 0;
-			pq_putstr(buf);
-			break;
-
-		case Local:
-		case Debug:
-		case None:
-		default:
-			break;
-	}
-}
-
-/*
- * These are necessary to sync communications between fe/be processes doing
- * COPY rel TO stdout
- *
- * or
- *
- * COPY rel FROM stdin
- *
- * NOTE: the message code letters are changed at protocol version 2.0
- * to eliminate possible confusion with data tuple messages.
- */
-void
-SendCopyBegin(void)
-{
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
-		pq_putnchar("H", 1);	/* new way */
-	else
-		pq_putnchar("B", 1);	/* old way */
-}
-
-void
-ReceiveCopyBegin(void)
-{
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
-		pq_putnchar("G", 1);	/* new way */
-	else
-		pq_putnchar("D", 1);	/* old way */
-	/* We *must* flush here to ensure FE knows it can send. */
-	pq_flush();
-}
-
-/* ----------------
- *		NullCommand - tell dest that an empty query string was recognized
- *
- *		In FE/BE protocol version 1.0, this hack is necessary to support
- *		libpq's crufty way of determining whether a multiple-command
- *		query string is done.  In protocol 2.0 it's probably not really
- *		necessary to distinguish empty queries anymore, but we still do it
- *		for backwards compatibility with 1.0.
- * ----------------
- */
-void
-NullCommand(CommandDest dest)
-{
-	switch (dest)
-	{
-			case RemoteInternal:
-			case Remote:
-			{
-				/* ----------------
-				 *		tell the fe that we saw an empty query string
-				 * ----------------
-				 */
-				pq_putstr("I");
-			}
-			break;
-
-		case Local:
-		case Debug:
-		case None:
-		default:
-			break;
-	}
-}
-
-/* ----------------
- *		ReadyForQuery - tell dest that we are ready for a new query
- *
- *		The ReadyForQuery message is sent in protocol versions 2.0 and up
- *		so that the FE can tell when we are done processing a query string.
- *
- *		Note that by flushing the stdio buffer here, we can avoid doing it
- *		most other places and thus reduce the number of separate packets sent.
- * ----------------
- */
-void
-ReadyForQuery(CommandDest dest)
-{
-	switch (dest)
-	{
-			case RemoteInternal:
-			case Remote:
-			{
-				if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
-					pq_putnchar("Z", 1);
-				/* Flush output at end of cycle in any case. */
-				pq_flush();
-			}
-			break;
-
-		case Local:
-		case Debug:
-		case None:
-		default:
-			break;
-	}
-}
+static DestReceiver donothingDR = {
+	donothingReceive, donothingSetup, donothingCleanup
+};
+static DestReceiver printtup_internalDR = {
+	printtup_internal, donothingSetup, donothingCleanup
+};
+static DestReceiver be_printtupDR = {
+	be_printtup, donothingSetup, donothingCleanup
+};
+static DestReceiver debugtupDR = {
+	debugtup, donothingSetup, donothingCleanup
+};
+static DestReceiver spi_printtupDR = {
+	spi_printtup, donothingSetup, donothingCleanup
+};
 
 /* ----------------
  *		BeginCommand - prepare destination for tuples of the given type
@@ -245,16 +114,16 @@ BeginCommand(char *pname,
 
 	switch (dest)
 	{
-		case RemoteInternal:
 		case Remote:
+		case RemoteInternal:
 			/* ----------------
-			 *		if this is a "retrieve portal" query, just return
+			 *		if this is a "retrieve portal" query, done
 			 *		because nothing needs to be sent to the fe.
 			 * ----------------
 			 */
-			CommandInfo[0] = 0;
+			CommandInfo[0] = '\0';
 			if (isIntoPortal)
-				return;
+				break;
 
 			/* ----------------
 			 *		if portal name not specified for remote query,
@@ -336,12 +205,179 @@ BeginCommand(char *pname,
 	}
 }
 
+/* ----------------
+ *		DestToFunction - return appropriate receiver function set for dest
+ * ----------------
+ */
+DestReceiver*
+DestToFunction(CommandDest dest)
+{
+	switch (dest)
+	{
+		case Remote:
+			/* printtup wants a dynamically allocated DestReceiver */
+			return printtup_create_DR();
+			break;
+
+		case RemoteInternal:
+			return & printtup_internalDR;
+			break;
+
+		case Local:
+			return & be_printtupDR;
+			break;
+
+		case Debug:
+			return & debugtupDR;
+			break;
+
+		case SPI:
+			return & spi_printtupDR;
+			break;
+
+		case None:
+		default:
+			return & donothingDR;
+			break;
+	}
+
+	/*
+	 * never gets here, but DECstation lint appears to be stupid...
+	 */
+
+	return & donothingDR;
+}
+
+/* ----------------
+ *		EndCommand - tell destination that no more tuples will arrive
+ * ----------------
+ */
+void
+EndCommand(char *commandTag, CommandDest dest)
+{
+	char		buf[64];
+
+	switch (dest)
+	{
+		case Remote:
+		case RemoteInternal:
+			/* ----------------
+			 *		tell the fe that the query is over
+			 * ----------------
+			 */
+			sprintf(buf, "C%s%s", commandTag, CommandInfo);
+			pq_putstr(buf);
+			CommandInfo[0] = '\0';
+			break;
+
+		case Local:
+		case Debug:
+		case None:
+		default:
+			break;
+	}
+}
+
+/*
+ * These are necessary to sync communications between fe/be processes doing
+ * COPY rel TO stdout
+ *
+ * or
+ *
+ * COPY rel FROM stdin
+ *
+ * NOTE: the message code letters are changed at protocol version 2.0
+ * to eliminate possible confusion with data tuple messages.
+ */
+void
+SendCopyBegin(void)
+{
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
+		pq_putnchar("H", 1);	/* new way */
+	else
+		pq_putnchar("B", 1);	/* old way */
+}
+
+void
+ReceiveCopyBegin(void)
+{
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
+		pq_putnchar("G", 1);	/* new way */
+	else
+		pq_putnchar("D", 1);	/* old way */
+	/* We *must* flush here to ensure FE knows it can send. */
+	pq_flush();
+}
+
+/* ----------------
+ *		NullCommand - tell dest that an empty query string was recognized
+ *
+ *		In FE/BE protocol version 1.0, this hack is necessary to support
+ *		libpq's crufty way of determining whether a multiple-command
+ *		query string is done.  In protocol 2.0 it's probably not really
+ *		necessary to distinguish empty queries anymore, but we still do it
+ *		for backwards compatibility with 1.0.
+ * ----------------
+ */
+void
+NullCommand(CommandDest dest)
+{
+	switch (dest)
+	{
+		case RemoteInternal:
+		case Remote:
+			/* ----------------
+			 *		tell the fe that we saw an empty query string
+			 * ----------------
+			 */
+			pq_putstr("I");
+			break;
+
+		case Local:
+		case Debug:
+		case None:
+		default:
+			break;
+	}
+}
+
+/* ----------------
+ *		ReadyForQuery - tell dest that we are ready for a new query
+ *
+ *		The ReadyForQuery message is sent in protocol versions 2.0 and up
+ *		so that the FE can tell when we are done processing a query string.
+ *
+ *		Note that by flushing the stdio buffer here, we can avoid doing it
+ *		most other places and thus reduce the number of separate packets sent.
+ * ----------------
+ */
+void
+ReadyForQuery(CommandDest dest)
+{
+	switch (dest)
+	{
+		case RemoteInternal:
+		case Remote:
+			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
+				pq_putnchar("Z", 1);
+			/* Flush output at end of cycle in any case. */
+			pq_flush();
+			break;
+
+		case Local:
+		case Debug:
+		case None:
+		default:
+			break;
+	}
+}
+
 void
 UpdateCommandInfo(int operation, Oid lastoid, uint32 tuples)
 {
 	switch (operation)
 	{
-			case CMD_INSERT:
+		case CMD_INSERT:
 			if (tuples > 1)
 				lastoid = InvalidOid;
 			sprintf(CommandInfo, " %u %u", lastoid, tuples);
@@ -351,7 +387,7 @@ UpdateCommandInfo(int operation, Oid lastoid, uint32 tuples)
 			sprintf(CommandInfo, " %u", tuples);
 			break;
 		default:
-			CommandInfo[0] = 0;
+			CommandInfo[0] = '\0';
+			break;
 	}
-	return;
 }
diff --git a/src/include/access/printtup.h b/src/include/access/printtup.h
index 0b4f7b0f04..c1b1c51ef8 100644
--- a/src/include/access/printtup.h
+++ b/src/include/access/printtup.h
@@ -6,20 +6,26 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: printtup.h,v 1.6 1999/01/24 05:40:46 tgl Exp $
+ * $Id: printtup.h,v 1.7 1999/01/27 00:36:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef PRINTTUP_H
 #define PRINTTUP_H
 
-#include <access/htup.h>
-#include <access/tupdesc.h>
+#include <tcop/dest.h>
+
+extern DestReceiver* printtup_create_DR(void);
+extern void showatts(char *name, TupleDesc attinfo);
+extern void debugtup(HeapTuple tuple, TupleDesc typeinfo,
+					 DestReceiver* self);
+extern void printtup_internal(HeapTuple tuple, TupleDesc typeinfo,
+							  DestReceiver* self);
+
+/* XXX this one is really in executor/spi.c */
+extern void spi_printtup(HeapTuple tuple, TupleDesc tupdesc,
+						 DestReceiver* self);
 
 extern int	getTypeOutAndElem(Oid type, Oid* typOutput, Oid* typElem);
-extern void printtup(HeapTuple tuple, TupleDesc typeinfo);
-extern void showatts(char *name, TupleDesc attinfo);
-extern void debugtup(HeapTuple tuple, TupleDesc typeinfo);
-extern void printtup_internal(HeapTuple tuple, TupleDesc typeinfo);
 
 #endif	 /* PRINTTUP_H */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index cd036ca67f..e654899909 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq.h,v 1.25 1999/01/24 02:47:15 tgl Exp $
+ * $Id: libpq.h,v 1.26 1999/01/27 00:36:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,8 +18,7 @@
 #include <netinet/in.h>
 
 #include "libpq/libpq-be.h"
-#include "access/htup.h"
-#include "access/tupdesc.h"
+#include "tcop/dest.h"
 
 
 /* ----------------
@@ -236,7 +235,8 @@ extern PortalEntry *be_currentportal(void);
 extern PortalEntry *be_newportal(void);
 extern void be_typeinit(PortalEntry *entry, TupleDesc attrs,
 			int natts);
-extern void be_printtup(HeapTuple tuple, TupleDesc typeinfo);
+extern void be_printtup(HeapTuple tuple, TupleDesc typeinfo,
+						DestReceiver* self);
 
 
 /* in be-pqexec.c */
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 9a0322e726..0b5a80908b 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * dest.h--
- *		Whenever the backend is submitted a query, the results
+ *		Whenever the backend executes a query, the results
  *		have to go someplace - either to the standard output,
  *		to a local portal buffer or to a remote portal buffer.
  *
@@ -23,21 +23,40 @@
  *		a query internally.  This is not used now but it may be
  *		useful for the parallel optimiser/executor.
  *
+ * dest.c defines three functions that implement destination management:
+ *
+ * BeginCommand: initialize the destination.
+ * DestToFunction: return a pointer to a struct of destination-specific
+ * receiver functions.
+ * EndCommand: clean up the destination when output is complete.
+ *
+ * The DestReceiver object returned by DestToFunction may be a statically
+ * allocated object (for destination types that require no local state)
+ * or can be a palloc'd object that has DestReceiver as its first field
+ * and contains additional fields (see printtup.c for an example).  These
+ * additional fields are then accessible to the DestReceiver functions
+ * by casting the DestReceiver* pointer passed to them.
+ * The palloc'd object is pfree'd by the DestReceiver's cleanup function.
+ *
+ * XXX FIXME: the initialization and cleanup code that currently appears
+ * in-line in BeginCommand and EndCommand probably should be moved out
+ * to routines associated with each destination receiver type.
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: dest.h,v 1.16 1998/09/01 04:38:39 momjian Exp $
+ * $Id: dest.h,v 1.17 1999/01/27 00:36:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef DEST_H
 #define DEST_H
 
+#include <access/htup.h>
 #include <access/tupdesc.h>
 
 /* ----------------
- *		CommandDest is used to allow the results of calling
- *		pg_eval() to go to the right place.
+ *		CommandDest is a simplistic means of identifying the desired
+ *		destination.  Someday this will probably need to be improved.
  * ----------------
  */
 typedef enum
@@ -51,25 +70,38 @@ typedef enum
 	SPI							/* results sent to SPI manager */
 } CommandDest;
 
+/* ----------------
+ *		DestReceiver is a base type for destination-specific local state.
+ *		In the simplest cases, there is no state info, just the function
+ *		pointers that the executor must call.
+ * ----------------
+ */
+typedef struct _DestReceiver DestReceiver;
 
-/* AttrInfo* replaced with TupleDesc, now that TupleDesc also has within it
-   the number of attributes
+struct _DestReceiver {
+	/* Called for each tuple to be output: */
+	void (*receiveTuple) (HeapTuple tuple, TupleDesc typeinfo,
+						  DestReceiver* self);
+	/* Initialization and teardown: */
+	void (*setup) (DestReceiver* self, TupleDesc typeinfo);
+	void (*cleanup) (DestReceiver* self);
+	/* Private fields might appear beyond this point... */
+};
 
-typedef struct AttrInfo {
-	int					numAttr;
-	Form_pg_attribute	*attrs;
-} AttrInfo;
-*/
+/* The primary destination management functions */
 
-extern void (*DestToFunction(CommandDest dest)) ();
+extern void BeginCommand(char *pname, int operation, TupleDesc attinfo,
+						 bool isIntoRel, bool isIntoPortal, char *tag,
+						 CommandDest dest);
+extern DestReceiver* DestToFunction(CommandDest dest);
 extern void EndCommand(char *commandTag, CommandDest dest);
+
+/* Additional functions that go with destination management, more or less. */
+
 extern void SendCopyBegin(void);
 extern void ReceiveCopyBegin(void);
 extern void NullCommand(CommandDest dest);
 extern void ReadyForQuery(CommandDest dest);
-extern void BeginCommand(char *pname, int operation, TupleDesc attinfo,
-			 bool isIntoRel, bool isIntoPortal, char *tag,
-			 CommandDest dest);
 extern void UpdateCommandInfo(int operation, Oid lastoid, uint32 tuples);
 
 #endif	 /* DEST_H */