diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 36a8e3e..f48fe4f 100644
*** a/contrib/dblink/dblink.c
--- b/contrib/dblink/dblink.c
*************** typedef struct remoteConn
*** 63,73 ****
  	bool		newXactForCursor;		/* Opened a transaction for a cursor */
  } remoteConn;
  
  /*
   * Internal declarations
   */
  static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async);
- static void materializeResult(FunctionCallInfo fcinfo, PGresult *res);
  static remoteConn *getConnectionByName(const char *name);
  static HTAB *createConnHash(void);
  static void createNewConnection(const char *name, remoteConn *rconn);
--- 63,85 ----
  	bool		newXactForCursor;		/* Opened a transaction for a cursor */
  } remoteConn;
  
+ typedef struct storeInfo
+ {
+ 	Tuplestorestate *tuplestore;
+ 	int nattrs;
+ 	AttInMetadata *attinmeta;
+ 	MemoryContext oldcontext;
+ 	char *attrvalbuf;
+ 	void **valbuf;
+ 	size_t *valbufsize;
+ 	bool error_occurred;
+ 	bool nummismatch;
+ } storeInfo;
+ 
  /*
   * Internal declarations
   */
  static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async);
  static remoteConn *getConnectionByName(const char *name);
  static HTAB *createConnHash(void);
  static void createNewConnection(const char *name, remoteConn *rconn);
*************** static char *escape_param_str(const char
*** 90,95 ****
--- 102,111 ----
  static void validate_pkattnums(Relation rel,
  				   int2vector *pkattnums_arg, int32 pknumatts_arg,
  				   int **pkattnums, int *pknumatts);
+ static void initStoreInfo(storeInfo *sinfo, FunctionCallInfo fcinfo);
+ static void finishStoreInfo(storeInfo *sinfo);
+ static void *addTuple(PGresult *res, AddTupFunc func, int id, size_t size);
+ 
  
  /* Global */
  static remoteConn *pconn = NULL;
*************** dblink_fetch(PG_FUNCTION_ARGS)
*** 503,508 ****
--- 519,525 ----
  	char	   *curname = NULL;
  	int			howmany = 0;
  	bool		fail = true;	/* default to backward compatible */
+ 	storeInfo   storeinfo;
  
  	DBLINK_INIT;
  
*************** dblink_fetch(PG_FUNCTION_ARGS)
*** 559,573 ****
--- 576,605 ----
  	appendStringInfo(&buf, "FETCH %d FROM %s", howmany, curname);
  
  	/*
+ 	 * Result is stored into storeinfo.tuplestore instead of
+ 	 * res->result retuned by PQexec below
+ 	 */
+ 	initStoreInfo(&storeinfo, fcinfo);
+ 	PQregisterTupleAdder(conn, addTuple, &storeinfo);
+ 
+ 	/*
  	 * Try to execute the query.  Note that since libpq uses malloc, the
  	 * PGresult will be long-lived even though we are still in a short-lived
  	 * memory context.
  	 */
  	res = PQexec(conn, buf.data);
+ 	finishStoreInfo(&storeinfo);
+ 
  	if (!res ||
  		(PQresultStatus(res) != PGRES_COMMAND_OK &&
  		 PQresultStatus(res) != PGRES_TUPLES_OK))
  	{
+ 		/* This is only for backward compatibility */
+ 		if (storeinfo.nummismatch)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 					 errmsg("remote query result rowtype does not match "
+ 							"the specified FROM clause rowtype")));
  		dblink_res_error(conname, res, "could not fetch from cursor", fail);
  		return (Datum) 0;
  	}
*************** dblink_fetch(PG_FUNCTION_ARGS)
*** 580,586 ****
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
- 	materializeResult(fcinfo, res);
  	return (Datum) 0;
  }
  
--- 612,617 ----
*************** dblink_record_internal(FunctionCallInfo
*** 640,645 ****
--- 671,677 ----
  	remoteConn *rconn = NULL;
  	bool		fail = true;	/* default to backward compatible */
  	bool		freeconn = false;
+ 	storeInfo   storeinfo;
  
  	/* check to see if caller supports us returning a tuplestore */
  	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
*************** dblink_record_internal(FunctionCallInfo
*** 715,878 ****
  	rsinfo->setResult = NULL;
  	rsinfo->setDesc = NULL;
  
  	/* synchronous query, or async result retrieval */
  	if (!is_async)
  		res = PQexec(conn, sql);
  	else
- 	{
  		res = PQgetResult(conn);
- 		/* NULL means we're all done with the async results */
- 		if (!res)
- 			return (Datum) 0;
- 	}
  
! 	/* if needed, close the connection to the database and cleanup */
! 	if (freeconn)
! 		PQfinish(conn);
  
! 	if (!res ||
! 		(PQresultStatus(res) != PGRES_COMMAND_OK &&
! 		 PQresultStatus(res) != PGRES_TUPLES_OK))
  	{
! 		dblink_res_error(conname, res, "could not execute query", fail);
! 		return (Datum) 0;
  	}
  
- 	materializeResult(fcinfo, res);
  	return (Datum) 0;
  }
  
- /*
-  * Materialize the PGresult to return them as the function result.
-  * The res will be released in this function.
-  */
  static void
! materializeResult(FunctionCallInfo fcinfo, PGresult *res)
  {
  	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
  
! 	Assert(rsinfo->returnMode == SFRM_Materialize);
  
! 	PG_TRY();
  	{
! 		TupleDesc	tupdesc;
! 		bool		is_sql_cmd = false;
! 		int			ntuples;
! 		int			nfields;
! 
! 		if (PQresultStatus(res) == PGRES_COMMAND_OK)
! 		{
! 			is_sql_cmd = true;
  
! 			/*
! 			 * need a tuple descriptor representing one TEXT column to return
! 			 * the command status string as our result tuple
! 			 */
! 			tupdesc = CreateTemplateTupleDesc(1, false);
! 			TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
! 							   TEXTOID, -1, 0);
! 			ntuples = 1;
! 			nfields = 1;
! 		}
! 		else
! 		{
! 			Assert(PQresultStatus(res) == PGRES_TUPLES_OK);
  
! 			is_sql_cmd = false;
  
! 			/* get a tuple descriptor for our result type */
! 			switch (get_call_result_type(fcinfo, NULL, &tupdesc))
! 			{
! 				case TYPEFUNC_COMPOSITE:
! 					/* success */
! 					break;
! 				case TYPEFUNC_RECORD:
! 					/* failed to determine actual type of RECORD */
! 					ereport(ERROR,
! 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 						errmsg("function returning record called in context "
! 							   "that cannot accept type record")));
! 					break;
! 				default:
! 					/* result type isn't composite */
! 					elog(ERROR, "return type must be a row type");
! 					break;
! 			}
  
! 			/* make sure we have a persistent copy of the tupdesc */
! 			tupdesc = CreateTupleDescCopy(tupdesc);
! 			ntuples = PQntuples(res);
! 			nfields = PQnfields(res);
  		}
  
! 		/*
! 		 * check result and tuple descriptor have the same number of columns
! 		 */
! 		if (nfields != tupdesc->natts)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_DATATYPE_MISMATCH),
! 					 errmsg("remote query result rowtype does not match "
! 							"the specified FROM clause rowtype")));
! 
! 		if (ntuples > 0)
! 		{
! 			AttInMetadata *attinmeta;
! 			Tuplestorestate *tupstore;
! 			MemoryContext oldcontext;
! 			int			row;
! 			char	  **values;
  
! 			attinmeta = TupleDescGetAttInMetadata(tupdesc);
  
! 			oldcontext = MemoryContextSwitchTo(
! 									rsinfo->econtext->ecxt_per_query_memory);
! 			tupstore = tuplestore_begin_heap(true, false, work_mem);
! 			rsinfo->setResult = tupstore;
! 			rsinfo->setDesc = tupdesc;
! 			MemoryContextSwitchTo(oldcontext);
  
! 			values = (char **) palloc(nfields * sizeof(char *));
  
! 			/* put all tuples into the tuplestore */
! 			for (row = 0; row < ntuples; row++)
  			{
! 				HeapTuple	tuple;
  
! 				if (!is_sql_cmd)
! 				{
! 					int			i;
  
! 					for (i = 0; i < nfields; i++)
! 					{
! 						if (PQgetisnull(res, row, i))
! 							values[i] = NULL;
! 						else
! 							values[i] = PQgetvalue(res, row, i);
! 					}
! 				}
! 				else
! 				{
! 					values[0] = PQcmdStatus(res);
! 				}
  
! 				/* build the tuple and put it into the tuplestore. */
! 				tuple = BuildTupleFromCStrings(attinmeta, values);
! 				tuplestore_puttuple(tupstore, tuple);
! 			}
  
! 			/* clean up and return the tuplestore */
! 			tuplestore_donestoring(tupstore);
! 		}
  
! 		PQclear(res);
  	}
  	PG_CATCH();
  	{
! 		/* be sure to release the libpq result */
! 		PQclear(res);
! 		PG_RE_THROW();
  	}
  	PG_END_TRY();
  }
  
  /*
--- 747,952 ----
  	rsinfo->setResult = NULL;
  	rsinfo->setDesc = NULL;
  
+ 
+ 	/*
+ 	 * Result is stored into storeinfo.tuplestore instead of
+ 	 * res->result retuned by PQexec/PQgetResult below
+ 	 */
+ 	initStoreInfo(&storeinfo, fcinfo);
+ 	PQregisterTupleAdder(conn, addTuple, &storeinfo);
+ 
  	/* synchronous query, or async result retrieval */
  	if (!is_async)
  		res = PQexec(conn, sql);
  	else
  		res = PQgetResult(conn);
  
! 	finishStoreInfo(&storeinfo);
  
! 	/* NULL res from async get means we're all done with the results */
! 	if (res || !is_async)
  	{
! 		if (freeconn)
! 			PQfinish(conn);
! 
! 		if (!res ||
! 			(PQresultStatus(res) != PGRES_COMMAND_OK &&
! 			 PQresultStatus(res) != PGRES_TUPLES_OK))
! 		{
! 			/* This is only for backward compatibility */
! 			if (storeinfo.nummismatch)
! 			{
! 				ereport(ERROR,
! 						(errcode(ERRCODE_DATATYPE_MISMATCH),
! 						 errmsg("remote query result rowtype does not match "
! 								"the specified FROM clause rowtype")));
! 			}
! 			dblink_res_error(conname, res, "could not execute query", fail);
! 			return (Datum) 0;
! 		}
  	}
  
  	return (Datum) 0;
  }
  
  static void
! initStoreInfo(storeInfo *sinfo, FunctionCallInfo fcinfo)
  {
  	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ 	TupleDesc	tupdesc;
+ 	int i;
+ 	
+ 	switch (get_call_result_type(fcinfo, NULL, &tupdesc))
+ 	{
+ 		case TYPEFUNC_COMPOSITE:
+ 			/* success */
+ 			break;
+ 		case TYPEFUNC_RECORD:
+ 			/* failed to determine actual type of RECORD */
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("function returning record called in context "
+ 							"that cannot accept type record")));
+ 			break;
+ 		default:
+ 			/* result type isn't composite */
+ 			elog(ERROR, "return type must be a row type");
+ 			break;
+ 	}
+ 	
+ 	sinfo->oldcontext = MemoryContextSwitchTo(
+ 		rsinfo->econtext->ecxt_per_query_memory);
  
! 	/* make sure we have a persistent copy of the tupdesc */
! 	tupdesc = CreateTupleDescCopy(tupdesc);
  
! 	sinfo->error_occurred = FALSE;
! 	sinfo->nummismatch = FALSE;
! 	sinfo->nattrs = tupdesc->natts;
! 	sinfo->tuplestore = tuplestore_begin_heap(true, false, work_mem);
! 	sinfo->attinmeta = TupleDescGetAttInMetadata(tupdesc);
! 	sinfo->valbuf = (void **)malloc(sinfo->nattrs * sizeof(void *));
! 	sinfo->valbufsize = (size_t *)malloc(sinfo->nattrs * sizeof(size_t));
! 	for (i = 0 ; i < sinfo->nattrs ; i++)
  	{
! 		sinfo->valbuf[i] = NULL;
! 		sinfo->valbufsize[i] = 0;
! 	}
  
! 	/* Preallocate memory of same size with PGresAttDesc array for values. */
! 	sinfo->attrvalbuf = (char *) malloc(sinfo->nattrs * sizeof(PGresAttValue));
  
! 	rsinfo->setResult = sinfo->tuplestore;
! 	rsinfo->setDesc = tupdesc;
! }
  
! static void
! finishStoreInfo(storeInfo *sinfo)
! {
! 	int i;
  
! 	for (i = 0 ; i < sinfo->nattrs ; i++)
! 	{
! 		if (sinfo->valbuf[i])
! 		{
! 			free(sinfo->valbuf[i]);
! 			sinfo->valbuf[i] = NULL;
  		}
+ 	}
+ 	if (sinfo->attrvalbuf)
+ 		free(sinfo->attrvalbuf);
+ 	sinfo->attrvalbuf = NULL;
+ 	MemoryContextSwitchTo(sinfo->oldcontext);
+ }
  
! static void *
! addTuple(PGresult *res, AddTupFunc  func, int id, size_t size)
! {
! 	storeInfo *sinfo = (storeInfo *)PQgetAddTupleParam(res);
! 	HeapTuple	tuple;
! 	int fields = PQnfields(res);
! 	int i;
! 	PGresAttValue *attval;
! 	char        **cstrs;
  
! 	if (sinfo->error_occurred)
! 		return NULL;
  
! 	switch (func)
! 	{
! 		case ADDTUP_ALLOC_TEXT:
! 		case ADDTUP_ALLOC_BINARY:
! 			if (id == -1)
! 				return sinfo->attrvalbuf;
  
! 			if (id < 0 || id >= sinfo->nattrs)
! 				return NULL;
  
! 			if (sinfo->valbufsize[id] < size)
  			{
! 				if (sinfo->valbuf[id] == NULL)
! 					sinfo->valbuf[id] = malloc(size);
! 				else
! 					sinfo->valbuf[id] = realloc(sinfo->valbuf[id], size);
! 				sinfo->valbufsize[id] = size;
! 			}
! 			return sinfo->valbuf[id];
  
! 		case ADDTUP_ADD_TUPLE:
! 			break;   /* Go through */
! 		default:
! 			/* Ignore */
! 			break;
! 	}
  
! 	if (sinfo->nattrs != fields)
! 	{
! 		sinfo->error_occurred = TRUE;
! 		sinfo->nummismatch = TRUE;
! 		finishStoreInfo(sinfo);
  
! 		PQsetAddTupleErrMes(res,
! 							strdup("function returning record called in "
! 								   "context that cannot accept type record"));
! 		return NULL;
! 	}
  
! 	/*
! 	 * Rewrite PGresAttDesc[] to char(*)[] in-place.
! 	 */
! 	Assert(sizeof(char*) <= sizeof(PGresAttValue));
! 	attval = (PGresAttValue *)sinfo->attrvalbuf;
! 	cstrs   = (char **)sinfo->attrvalbuf;
! 	for(i = 0 ; i < fields ; i++)
! 		cstrs[i] = PQgetAsCstring(attval++);
  
! 	PG_TRY();
! 	{
! 		tuple = BuildTupleFromCStrings(sinfo->attinmeta, cstrs);
! 		tuplestore_puttuple(sinfo->tuplestore, tuple);
  	}
  	PG_CATCH();
  	{
! 		/*
! 		 * Return the error message in the exception to the caller and
! 		 * cancel the exception.
! 		 */
! 		ErrorData *edata;
! 
! 		sinfo->error_occurred = TRUE;
! 		sinfo->nummismatch = TRUE;
! 
! 		finishStoreInfo(sinfo);
! 
! 		edata = CopyErrorData();
! 		FlushErrorState();
! 
! 		PQsetAddTupleErrMes(res, strdup(edata->message));
! 		return NULL;
  	}
  	PG_END_TRY();
+ 
+ 	return sinfo->attrvalbuf;
  }
  
  /*
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 72c9384..af90952 100644
*** a/doc/src/sgml/libpq.sgml
--- b/doc/src/sgml/libpq.sgml
*************** int PQisthreadsafe();
*** 7233,7238 ****
--- 7233,7557 ----
   </sect1>
  
  
+  <sect1 id="libpq-alterstorage">
+   <title>Alternative result storage</title>
+ 
+   <indexterm zone="libpq-alterstorage">
+    <primary>PGresult</primary>
+    <secondary>PGconn</secondary>
+   </indexterm>
+ 
+   <para>
+    As the standard usage, users can get the result of command
+    execution from <structname>PGresult</structname> aquired
+    with <function>PGgetResult</function>
+    from <structname>PGConn</structname>. While the memory areas for
+    the PGresult are allocated with malloc() internally within calls of
+    command execution functions such as <function>PQexec</function>
+    and <function>PQgetResult</function>. If you have difficulties to
+    handle the result records in the form of PGresult, you can instruct
+    PGconn to store them into your own storage instead of PGresult.
+   </para>
+ 
+   <variablelist>
+    <varlistentry id="libpq-registertupleadder">
+     <term>
+      <function>PQregisterTupleAdder</function>
+      <indexterm>
+       <primary>PQregisterTupleAdder</primary>
+      </indexterm>
+     </term>
+ 
+     <listitem>
+      <para>
+        Sets a function to allocate memory for each tuple and column
+        values, and add the completed tuple into your storage.
+ <synopsis>
+ void PQregisterTupleAdder(PGconn *conn,
+                           addTupleFunction func,
+                           void *param);
+ </synopsis>
+      </para>
+      
+      <para>
+        <variablelist>
+ 	 <varlistentry>
+ 	   <term><parameter>conn</parameter></term>
+ 	   <listitem>
+ 	     <para>
+ 	       The connection object to set the tuple adder
+ 	       function. PGresult created from this connection calles
+ 	       this function to store the result tuples instead of
+ 	       storing into its internal storage.
+ 	     </para>
+ 	   </listitem>
+ 	 </varlistentry>
+ 	 <varlistentry>
+ 	   <term><parameter>func</parameter></term>
+ 	   <listitem>
+ 	     <para>
+ 	       Tuple adder function to set. NULL means to use the
+ 	       default storage.
+ 	     </para>
+ 	   </listitem>
+ 	 </varlistentry>
+ 	 <varlistentry>
+ 	   <term><parameter>param</parameter></term>
+ 	   <listitem>
+ 	     <para>
+ 	       A pointer to contextual parameter passed
+ 	       to <parameter>func</parameter>.
+ 	     </para>
+ 	   </listitem>
+ 	 </varlistentry>
+        </variablelist>
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ 
+   <variablelist>
+    <varlistentry id="libpq-addtuplefunction">
+     <term>
+      <type>addTupleFunction</type>
+      <indexterm>
+       <primary>addTupleFunction</primary>
+      </indexterm>
+     </term>
+ 
+     <listitem>
+      <para>
+        The type for the callback function to serve memory blocks for
+        each tuple and its column values, and to add the constructed
+        tuple into your own storage.
+ <synopsis>
+ typedef enum 
+ {
+   ADDTUP_ALLOC_TEXT,
+   ADDTUP_ALLOC_BINARY,
+   ADDTUP_ADD_TUPLE
+ } AddTupFunc;
+ 
+ void *(*addTupleFunction)(PGresult *res,
+                           AddTupFunc func,
+                           int id,
+                           size_t size);
+ </synopsis>
+      </para>
+ 
+      <para>
+        Generally this function must return NULL for failure and should
+        set the error message
+        with <function>PGsetAddTupleErrMes</function> if the cause is
+        other than out of memory. This funcion must not throw any
+        exception. This function is called in the sequence following.
+ 
+        <itemizedlist spacing="compact">
+ 	 <listitem>
+ 	   <simpara>Call with <parameter>func</parameter>
+ 	   = <firstterm>ADDTUP_ALLOC_BINARY</firstterm>
+ 	   and <parameter>id</parameter> = -1 to request the memory
+ 	   for tuple used as an array
+ 	   of <type>PGresAttValue</type> </simpara>
+ 	 </listitem>
+ 	 <listitem>
+ 	   <simpara>Call with <parameter>func</parameter>
+ 	   = <firstterm>ADDTUP_ALLOC_TEXT</firstterm>
+ 	   or <firstterm>ADDTUP_ALLOC_TEXT</firstterm>
+ 	   and <parameter>id</parameter> is zero or positive number
+ 	   to request the memory for each column value in current
+ 	   tuple.</simpara>
+ 	 </listitem>
+ 	 <listitem>
+ 	   <simpara>Call with <parameter>func</parameter>
+ 	   = <firstterm>ADDTUP_ADD_TUPLE</firstterm> to request the
+ 	   constructed tuple to store.</simpara>
+ 	 </listitem>
+        </itemizedlist>
+      </para>
+      <para>
+        Calling <type>addTupleFunction</type>
+        with <parameter>func</parameter> =
+        <firstterm>ADDTUP_ALLOC_TEXT</firstterm> is telling to return a
+         memory block with at least <parameter>size</parameter> bytes
+         which may not be aligned to the word boundary.
+        <parameter>id</parameter> is a zero or positive number
+        distinguishes the usage of requested memory block, that is the
+        position of the column for which the memory block is used.
+      </para>
+      <para>
+        When <parameter>func</parameter>
+        = <firstterm>ADDTUP_ALLOC_BINARY</firstterm>, this function is
+        telled to return a memory block with at
+        least <parameter>size</parameter> bytes which is aligned to the
+        word boundary.
+        <parameter>id</parameter> is the identifier distinguishes the
+        usage of requested memory block. -1 means that it is used as an
+        array of <type>PGresAttValue</type> to store the tuple. Zero or
+        positive numbers have the same meanings as for
+        <firstterm>ADDTUP_ALLOC_BINARY</firstterm>.
+      </para>
+      <para>When <parameter>func</parameter>
+        = <firstterm>ADDTUP_ADD_TUPLE</firstterm>, this function is
+        telled to store the <type>PGresAttValue</type> structure
+        constructed by the caller into your storage. The pointer to the
+        tuple structure is not passed so you should memorize the
+        pointer to the memory block passed the caller on
+        <parameter>func</parameter>
+        = <parameter>ADDTUP_ALLOC_BINARY</parameter>
+        with <parameter>id</parameter> is -1. This function must return
+        any non-NULL values for success. You must properly put back the
+        memory blocks passed to the caller for this function if needed.
+      </para>
+      <variablelist>
+        <varlistentry>
+ 	 <term><parameter>res</parameter></term>
+ 	 <listitem>
+ 	   <para>
+ 	     A pointer to the <type>PGresult</type> object.
+ 	   </para>
+ 	 </listitem>
+        </varlistentry>
+        <varlistentry>
+ 	 <term><parameter>func</parameter></term>
+ 	 <listitem>
+ 	   <para>
+ 	     An <type>enum</type> value telling the function to perform.
+ 	   </para>
+ 	 </listitem>
+        </varlistentry>
+        <varlistentry>
+ 	 <term><parameter>param</parameter></term>
+ 	 <listitem>
+ 	   <para>
+ 	     A pointer to contextual parameter passed to func.
+ 	   </para>
+ 	 </listitem>
+        </varlistentry>
+      </variablelist>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ 
+   <variablelist>
+    <varlistentry id="libpq-pqgestasctring">
+     <term>
+      <function>PQgetAsCstring</function>
+      <indexterm>
+       <primary>PQgetAsCstring</primary>
+      </indexterm>
+     </term>
+     <listitem>
+       <para>
+ 	Get the value of the column pointed
+ 	by <parameter>attval</parameter> in the form of
+ 	zero-terminated C string. Returns NULL if the value is null.
+ <synopsis>
+ char *PQgetAsCstring(PGresAttValue *attval)
+ </synopsis>
+       </para>
+       <para>
+ 	<variablelist>
+ 	  <varlistentry>
+ 	    <term><parameter>attval</parameter></term>
+ 	    <listitem>
+ 	      <para>
+ 		A pointer to the <type>PGresAttValue</type> object
+ 		to retrieve the value.
+ 	      </para>
+ 	    </listitem>
+ 	  </varlistentry>
+ 	</variablelist>
+       </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ 
+   <variablelist>
+    <varlistentry id="libpq-pqgetaddtupleparam">
+     <term>
+      <function>PQgetAddTupleParam</function>
+      <indexterm>
+       <primary>PQgetAddTupleParam</primary>
+      </indexterm>
+     </term>
+     <listitem>
+       <para>
+ 	Get the pointer passed to <function>PQregisterTupleAdder</function>
+ 	as <parameter>param</parameter>.
+ <synopsis>
+ void *PQgetTupleParam(PGresult *res)
+ </synopsis>
+       </para>
+       <para>
+ 	<variablelist>
+ 	  <varlistentry>
+ 	    <term><parameter>res</parameter></term>
+ 	    <listitem>
+ 	      <para>
+ 		A pointer to the <type>PGresult</type> object.
+ 	      </para>
+ 	    </listitem>
+ 	  </varlistentry>
+ 	</variablelist>
+       </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ 
+   <variablelist>
+    <varlistentry id="libpq-pqsetaddtupleerrmes">
+     <term>
+      <function>PQsetAddTupleErrMes</function>
+      <indexterm>
+       <primary>PQsetAddTupleErrMes</primary>
+      </indexterm>
+     </term>
+     <listitem>
+       <para>
+ 	Set the message for the error occurred in <type>addTupleFunction</type>.
+ 	If this message is not set, the error is assumed to be out of
+ 	memory.
+ <synopsis>
+ void PQsetAddTupleErrMes(PGresult *res, char *mes)
+ </synopsis>
+       </para>
+       <para>
+ 	<variablelist>
+ 	  <varlistentry>
+ 	    <term><parameter>res</parameter></term>
+ 	    <listitem>
+ 	      <para>
+ 		A pointer to the <type>PGresult</type> object
+ 		in <type>addTupleFunction</type>.
+ 	      </para>
+ 	    </listitem>
+ 	  </varlistentry>
+ 	  <varlistentry>
+ 	    <term><parameter>mes</parameter></term>
+ 	    <listitem>
+ 	      <para>
+ 		A pointer to the memory block containing the error
+ 		message, which must be allocated by alloc(). The
+ 		memory block will be freed with free() in the caller
+ 		of
+ 		<type>addTupleFunction</type> only if it returns NULL.
+ 	      </para>
+ 	      <para>
+ 		If <parameter>res</parameter> already has a message
+ 		previously set, it is freed and the given message is
+ 		set. Set NULL to cancel the the costom message.
+ 	      </para>
+ 	    </listitem>
+ 	  </varlistentry>
+ 	</variablelist>
+       </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </sect1>
+ 
+ 
   <sect1 id="libpq-build">
    <title>Building <application>libpq</application> Programs</title>
  
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 1af8df6..a360d78 100644
*** a/src/interfaces/libpq/exports.txt
--- b/src/interfaces/libpq/exports.txt
*************** PQconnectStartParams      157
*** 160,162 ****
--- 160,166 ----
  PQping                    158
  PQpingParams              159
  PQlibVersion              160
+ PQregisterTupleAdder	  161
+ PQgetAsCstring		  162
+ PQgetAddTupleParam	  163
+ PQsetAddTupleErrMes	  164
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index d454538..15d6216 100644
*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
*************** makeEmptyPGconn(void)
*** 2692,2697 ****
--- 2692,2698 ----
  	conn->allow_ssl_try = true;
  	conn->wait_ssl_try = false;
  #endif
+ 	conn->addTupleFunc = NULL;
  
  	/*
  	 * We try to send at least 8K at a time, which is the usual size of pipe
*************** PQregisterThreadLock(pgthreadlock_t newh
*** 5076,5078 ****
--- 5077,5086 ----
  
  	return prev;
  }
+ 
+ void
+ PQregisterTupleAdder(PGconn *conn, addTupleFunction func, void *param)
+ {
+ 	conn->addTupleFunc = func;
+ 	conn->addTupleFuncParam = param;
+ }
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index b743566..3f774fd 100644
*** a/src/interfaces/libpq/fe-exec.c
--- b/src/interfaces/libpq/fe-exec.c
*************** char	   *const pgresStatus[] = {
*** 48,54 ****
  static int	static_client_encoding = PG_SQL_ASCII;
  static bool static_std_strings = false;
  
- 
  static PGEvent *dupEvents(PGEvent *events, int count);
  static bool PQsendQueryStart(PGconn *conn);
  static int PQsendQueryGuts(PGconn *conn,
--- 48,53 ----
*************** static PGresult *PQexecFinish(PGconn *co
*** 66,72 ****
  static int PQsendDescribe(PGconn *conn, char desc_type,
  			   const char *desc_target);
  static int	check_field_number(const PGresult *res, int field_num);
! 
  
  /* ----------------
   * Space management for PGresult.
--- 65,73 ----
  static int PQsendDescribe(PGconn *conn, char desc_type,
  			   const char *desc_target);
  static int	check_field_number(const PGresult *res, int field_num);
! static void *pqDefaultAddTupleFunc(PGresult *res, AddTupFunc func,
! 								   int id, size_t len);
! static void *pqAddTuple(PGresult *res, PGresAttValue *tup);
  
  /* ----------------
   * Space management for PGresult.
*************** PQmakeEmptyPGresult(PGconn *conn, ExecSt
*** 160,165 ****
--- 161,169 ----
  	result->curBlock = NULL;
  	result->curOffset = 0;
  	result->spaceLeft = 0;
+ 	result->addTupleFunc = pqDefaultAddTupleFunc;
+ 	result->addTupleFuncParam = NULL;
+ 	result->addTupleFuncErrMes = NULL;
  
  	if (conn)
  	{
*************** PQmakeEmptyPGresult(PGconn *conn, ExecSt
*** 194,199 ****
--- 198,209 ----
  			}
  			result->nEvents = conn->nEvents;
  		}
+ 
+ 		if (conn->addTupleFunc)
+ 		{
+ 			result->addTupleFunc = conn->addTupleFunc;
+ 			result->addTupleFuncParam = conn->addTupleFuncParam;
+ 		}
  	}
  	else
  	{
*************** PQresultAlloc(PGresult *res, size_t nByt
*** 487,492 ****
--- 497,529 ----
  	return pqResultAlloc(res, nBytes, TRUE);
  }
  
+ void *
+ pqDefaultAddTupleFunc(PGresult *res, AddTupFunc func, int id, size_t len)
+ {
+ 	void *p;
+ 
+ 	switch (func)
+ 	{
+ 		case ADDTUP_ALLOC_TEXT:
+ 			return pqResultAlloc(res, len, TRUE);
+ 
+ 		case ADDTUP_ALLOC_BINARY:
+ 			p = pqResultAlloc(res, len, FALSE);
+ 
+ 			if (id == -1)
+ 				res->addTupleFuncParam = p;
+ 
+ 			return p;
+ 
+ 		case ADDTUP_ADD_TUPLE:
+ 			return pqAddTuple(res, res->addTupleFuncParam);
+ 
+ 		default:
+ 			/* Ignore */
+ 			break;
+ 	}
+ 	return NULL;
+ }
  /*
   * pqResultAlloc -
   *		Allocate subsidiary storage for a PGresult.
*************** pqInternalNotice(const PGNoticeHooks *ho
*** 830,838 ****
  /*
   * pqAddTuple
   *	  add a row pointer to the PGresult structure, growing it if necessary
!  *	  Returns TRUE if OK, FALSE if not enough memory to add the row
   */
! int
  pqAddTuple(PGresult *res, PGresAttValue *tup)
  {
  	if (res->ntups >= res->tupArrSize)
--- 867,875 ----
  /*
   * pqAddTuple
   *	  add a row pointer to the PGresult structure, growing it if necessary
!  *	  Returns tup if OK, NULL if not enough memory to add the row.
   */
! static void *
  pqAddTuple(PGresult *res, PGresAttValue *tup)
  {
  	if (res->ntups >= res->tupArrSize)
*************** pqAddTuple(PGresult *res, PGresAttValue
*** 858,870 ****
  			newTuples = (PGresAttValue **)
  				realloc(res->tuples, newSize * sizeof(PGresAttValue *));
  		if (!newTuples)
! 			return FALSE;		/* malloc or realloc failed */
  		res->tupArrSize = newSize;
  		res->tuples = newTuples;
  	}
  	res->tuples[res->ntups] = tup;
  	res->ntups++;
! 	return TRUE;
  }
  
  /*
--- 895,907 ----
  			newTuples = (PGresAttValue **)
  				realloc(res->tuples, newSize * sizeof(PGresAttValue *));
  		if (!newTuples)
! 			return NULL;		/* malloc or realloc failed */
  		res->tupArrSize = newSize;
  		res->tuples = newTuples;
  	}
  	res->tuples[res->ntups] = tup;
  	res->ntups++;
! 	return tup;
  }
  
  /*
*************** PQgetisnull(const PGresult *res, int tup
*** 2822,2827 ****
--- 2859,2901 ----
  		return 0;
  }
  
+ /* PQgetAsCString
+  *	returns the field as C string.
+  */
+ char *
+ PQgetAsCstring(PGresAttValue *attval)
+ {
+ 	return attval->len == NULL_LEN ? NULL : attval->value;
+ }
+ 
+ /* PQgetAddTupleParam
+  *	Get the pointer to the contextual parameter from PGresult which is
+  *	registered to PGconn by PQregisterTupleAdder
+  */
+ void *
+ PQgetAddTupleParam(const PGresult *res)
+ {
+ 	if (!res)
+ 		return NULL;
+ 	return res->addTupleFuncParam;
+ }
+ 
+ /* PQsetAddTupleErrMes
+  *	Set the error message pass back to the caller of addTupleFunc
+  *  mes must be a malloc'ed memory block and it is released by the
+  *  caller of addTupleFunc if set.
+  *  You can replace the previous message by alternative mes, or clear
+  *  it with NULL.
+  */
+ void
+ PQsetAddTupleErrMes(PGresult *res, char *mes)
+ {
+ 	/* Free existing message */
+ 	if (res->addTupleFuncErrMes)
+ 		free(res->addTupleFuncErrMes);
+ 	res->addTupleFuncErrMes = mes;
+ }
+ 
  /* PQnparams:
   *	returns the number of input parameters of a prepared statement.
   */
diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c
index a7c3899..721c812 100644
*** a/src/interfaces/libpq/fe-protocol2.c
--- b/src/interfaces/libpq/fe-protocol2.c
*************** getAnotherTuple(PGconn *conn, bool binar
*** 733,741 ****
  	if (conn->curTuple == NULL)
  	{
  		conn->curTuple = (PGresAttValue *)
! 			pqResultAlloc(result, nfields * sizeof(PGresAttValue), TRUE);
  		if (conn->curTuple == NULL)
! 			goto outOfMemory;
  		MemSet(conn->curTuple, 0, nfields * sizeof(PGresAttValue));
  
  		/*
--- 733,742 ----
  	if (conn->curTuple == NULL)
  	{
  		conn->curTuple = (PGresAttValue *)
! 			result->addTupleFunc(result, ADDTUP_ALLOC_BINARY, -1,
! 								 nfields * sizeof(PGresAttValue));
  		if (conn->curTuple == NULL)
! 			goto addTupleError;
  		MemSet(conn->curTuple, 0, nfields * sizeof(PGresAttValue));
  
  		/*
*************** getAnotherTuple(PGconn *conn, bool binar
*** 757,763 ****
  	{
  		bitmap = (char *) malloc(nbytes);
  		if (!bitmap)
! 			goto outOfMemory;
  	}
  
  	if (pqGetnchar(bitmap, nbytes, conn))
--- 758,764 ----
  	{
  		bitmap = (char *) malloc(nbytes);
  		if (!bitmap)
! 			goto addTupleError;
  	}
  
  	if (pqGetnchar(bitmap, nbytes, conn))
*************** getAnotherTuple(PGconn *conn, bool binar
*** 787,795 ****
  				vlen = 0;
  			if (tup[i].value == NULL)
  			{
! 				tup[i].value = (char *) pqResultAlloc(result, vlen + 1, binary);
  				if (tup[i].value == NULL)
! 					goto outOfMemory;
  			}
  			tup[i].len = vlen;
  			/* read in the value */
--- 788,799 ----
  				vlen = 0;
  			if (tup[i].value == NULL)
  			{
! 				AddTupFunc func =
! 					(binary ? ADDTUP_ALLOC_BINARY : ADDTUP_ALLOC_TEXT);
! 				tup[i].value =
! 					(char *) result->addTupleFunc(result, func, i, vlen + 1);
  				if (tup[i].value == NULL)
! 					goto addTupleError;
  			}
  			tup[i].len = vlen;
  			/* read in the value */
*************** getAnotherTuple(PGconn *conn, bool binar
*** 812,819 ****
  	}
  
  	/* Success!  Store the completed tuple in the result */
! 	if (!pqAddTuple(result, tup))
! 		goto outOfMemory;
  	/* and reset for a new message */
  	conn->curTuple = NULL;
  
--- 816,824 ----
  	}
  
  	/* Success!  Store the completed tuple in the result */
! 	if (!result->addTupleFunc(result, ADDTUP_ADD_TUPLE, 0, 0))
! 		goto addTupleError;
! 
  	/* and reset for a new message */
  	conn->curTuple = NULL;
  
*************** getAnotherTuple(PGconn *conn, bool binar
*** 821,827 ****
  		free(bitmap);
  	return 0;
  
! outOfMemory:
  	/* Replace partially constructed result with an error result */
  
  	/*
--- 826,832 ----
  		free(bitmap);
  	return 0;
  
! addTupleError:
  	/* Replace partially constructed result with an error result */
  
  	/*
*************** outOfMemory:
*** 829,836 ****
  	 * there's not enough memory to concatenate messages...
  	 */
  	pqClearAsyncResult(conn);
! 	printfPQExpBuffer(&conn->errorMessage,
! 					  libpq_gettext("out of memory for query result\n"));
  
  	/*
  	 * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can
--- 834,854 ----
  	 * there's not enough memory to concatenate messages...
  	 */
  	pqClearAsyncResult(conn);
! 	resetPQExpBuffer(&conn->errorMessage);
! 
! 	/*
! 	 * If error message is passed from addTupleFunc, set it into
! 	 * PGconn, assume out of memory if not.
! 	 */
! 	appendPQExpBufferStr(&conn->errorMessage,
! 						 libpq_gettext(result->addTupleFuncErrMes ?
! 									   result->addTupleFuncErrMes :
! 									   "out of memory for query result\n"));
! 	if (result->addTupleFuncErrMes)
! 	{
! 		free(result->addTupleFuncErrMes);
! 		result->addTupleFuncErrMes = NULL;
! 	}
  
  	/*
  	 * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 892dcbc..1417d59 100644
*** a/src/interfaces/libpq/fe-protocol3.c
--- b/src/interfaces/libpq/fe-protocol3.c
*************** getAnotherTuple(PGconn *conn, int msgLen
*** 634,642 ****
  	if (conn->curTuple == NULL)
  	{
  		conn->curTuple = (PGresAttValue *)
! 			pqResultAlloc(result, nfields * sizeof(PGresAttValue), TRUE);
  		if (conn->curTuple == NULL)
! 			goto outOfMemory;
  		MemSet(conn->curTuple, 0, nfields * sizeof(PGresAttValue));
  	}
  	tup = conn->curTuple;
--- 634,643 ----
  	if (conn->curTuple == NULL)
  	{
  		conn->curTuple = (PGresAttValue *)
! 			result->addTupleFunc(result, ADDTUP_ALLOC_BINARY, -1,
! 								 nfields * sizeof(PGresAttValue));
  		if (conn->curTuple == NULL)
! 			goto addTupleError;
  		MemSet(conn->curTuple, 0, nfields * sizeof(PGresAttValue));
  	}
  	tup = conn->curTuple;
*************** getAnotherTuple(PGconn *conn, int msgLen
*** 673,683 ****
  			vlen = 0;
  		if (tup[i].value == NULL)
  		{
! 			bool		isbinary = (result->attDescs[i].format != 0);
! 
! 			tup[i].value = (char *) pqResultAlloc(result, vlen + 1, isbinary);
  			if (tup[i].value == NULL)
! 				goto outOfMemory;
  		}
  		tup[i].len = vlen;
  		/* read in the value */
--- 674,685 ----
  			vlen = 0;
  		if (tup[i].value == NULL)
  		{
! 			AddTupFunc func = (result->attDescs[i].format != 0 ?
! 							   ADDTUP_ALLOC_BINARY : ADDTUP_ALLOC_TEXT);
! 			tup[i].value =
! 				(char *) result->addTupleFunc(result, func, i, vlen + 1);
  			if (tup[i].value == NULL)
! 				goto addTupleError;
  		}
  		tup[i].len = vlen;
  		/* read in the value */
*************** getAnotherTuple(PGconn *conn, int msgLen
*** 689,710 ****
  	}
  
  	/* Success!  Store the completed tuple in the result */
! 	if (!pqAddTuple(result, tup))
! 		goto outOfMemory;
  	/* and reset for a new message */
  	conn->curTuple = NULL;
  
  	return 0;
  
! outOfMemory:
  
  	/*
  	 * Replace partially constructed result with an error result. First
  	 * discard the old result to try to win back some memory.
  	 */
  	pqClearAsyncResult(conn);
! 	printfPQExpBuffer(&conn->errorMessage,
! 					  libpq_gettext("out of memory for query result\n"));
  	pqSaveErrorResult(conn);
  
  	/* Discard the failed message by pretending we read it */
--- 691,726 ----
  	}
  
  	/* Success!  Store the completed tuple in the result */
! 	if (!result->addTupleFunc(result, ADDTUP_ADD_TUPLE, 0, 0))
! 		goto addTupleError;
! 
  	/* and reset for a new message */
  	conn->curTuple = NULL;
  
  	return 0;
  
! addTupleError:
  
  	/*
  	 * Replace partially constructed result with an error result. First
  	 * discard the old result to try to win back some memory.
  	 */
  	pqClearAsyncResult(conn);
! 	resetPQExpBuffer(&conn->errorMessage);
! 
! 	/*
! 	 * If error message is passed from addTupleFunc, set it into
! 	 * PGconn, assume out of memory if not.
! 	 */
! 	appendPQExpBufferStr(&conn->errorMessage,
! 						 libpq_gettext(result->addTupleFuncErrMes ?
! 									   result->addTupleFuncErrMes : 
! 									   "out of memory for query result\n"));
! 	if (result->addTupleFuncErrMes)
! 	{
! 		free(result->addTupleFuncErrMes);
! 		result->addTupleFuncErrMes = NULL;
! 	}
  	pqSaveErrorResult(conn);
  
  	/* Discard the failed message by pretending we read it */
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ef26ab9..bfa6556 100644
*** a/src/interfaces/libpq/libpq-fe.h
--- b/src/interfaces/libpq/libpq-fe.h
*************** typedef enum
*** 116,121 ****
--- 116,131 ----
  	PQPING_NO_ATTEMPT			/* connection not attempted (bad params) */
  } PGPing;
  
+ /* AddTupFunc is one of the parameters of addTupleFunc that decides
+  * the function of the addTupleFunction. See addTupleFunction for
+  * details */
+ typedef enum 
+ {
+ 	ADDTUP_ALLOC_TEXT,          /* Returns non-aligned memory for text value */
+ 	ADDTUP_ALLOC_BINARY,        /* Returns aligned memory for binary value */
+ 	ADDTUP_ADD_TUPLE            /* Adds tuple data into tuple storage */
+ } AddTupFunc;
+ 
  /* PGconn encapsulates a connection to the backend.
   * The contents of this struct are not supposed to be known to applications.
   */
*************** typedef struct pgresAttDesc
*** 225,230 ****
--- 235,246 ----
  	int			atttypmod;		/* type-specific modifier info */
  } PGresAttDesc;
  
+ typedef struct pgresAttValue
+ {
+ 	int			len;			/* length in bytes of the value */
+ 	char	   *value;			/* actual value, plus terminating zero byte */
+ } PGresAttValue;
+ 
  /* ----------------
   * Exported functions of libpq
   * ----------------
*************** extern PGPing PQping(const char *conninf
*** 416,421 ****
--- 432,483 ----
  extern PGPing PQpingParams(const char *const * keywords,
  			 const char *const * values, int expand_dbname);
  
+ /*
+  * Typedef for tuple storage function.
+  *
+  * This function pointer is used for tuple storage function in
+  * PGresult and PGconn.
+  *
+  * addTupleFunction is called for four types of function designated by
+  * the enum AddTupFunc.
+  *
+  * id is the identifier for allocated memory block. The caller sets -1
+  * for PGresAttValue array, and 0 to number of cols - 1 for each
+  * column.
+  *
+  * ADDTUP_ALLOC_TEXT requests the size bytes memory block for a text
+  * value which may not be alingned to the word boundary.
+  *
+  * ADDTUP_ALLOC_BINARY requests the size bytes memory block for a
+  * binary value which is aligned to the word boundary.
+  *
+  * ADDTUP_ADD_TUPLE requests to add tuple data into storage, and
+  * free the memory blocks allocated by this function if necessary.
+  * id and size are ignored.
+  *
+  * This function must return non-NULL value for success and must
+  * return NULL for failure and may set error message by
+  * PQsetAddTupleErrMes in malloc'ed memory. Assumed by caller as out
+  * of memory if the error message is NULL on failure. This function is
+  * assumed not to throw any exception.
+  */
+ 	typedef void *(*addTupleFunction)(PGresult *res, AddTupFunc func,
+ 									  int id, size_t size);
+ 
+ /*
+  * Register alternative tuple storage function to PGconn.
+  * 
+  * By registering this function, pg_result disables its own tuple
+  * storage and calls it to append rows one by one.
+  *
+  * func is tuple store function. See addTupleFunction.
+  * 
+  * addTupFuncParam is contextual storage that can be get with
+  * PQgetAddTupleParam in func.
+  */
+ extern void PQregisterTupleAdder(PGconn *conn, addTupleFunction func,
+ 								 void *addTupFuncParam);
+ 
  /* Force the write buffer to be written (or at least try) */
  extern int	PQflush(PGconn *conn);
  
*************** extern char *PQcmdTuples(PGresult *res);
*** 454,459 ****
--- 516,524 ----
  extern char *PQgetvalue(const PGresult *res, int tup_num, int field_num);
  extern int	PQgetlength(const PGresult *res, int tup_num, int field_num);
  extern int	PQgetisnull(const PGresult *res, int tup_num, int field_num);
+ extern char *PQgetAsCstring(PGresAttValue *attdesc);
+ extern void *PQgetAddTupleParam(const PGresult *res);
+ extern void	PQsetAddTupleErrMes(PGresult *res, char *mes);
  extern int	PQnparams(const PGresult *res);
  extern Oid	PQparamtype(const PGresult *res, int param_num);
  
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index d967d60..01e8c3e 100644
*** a/src/interfaces/libpq/libpq-int.h
--- b/src/interfaces/libpq/libpq-int.h
*************** typedef struct pgresParamDesc
*** 134,145 ****
  
  #define NULL_LEN		(-1)	/* pg_result len for NULL value */
  
- typedef struct pgresAttValue
- {
- 	int			len;			/* length in bytes of the value */
- 	char	   *value;			/* actual value, plus terminating zero byte */
- } PGresAttValue;
- 
  /* Typedef for message-field list entries */
  typedef struct pgMessageField
  {
--- 134,139 ----
*************** struct pg_result
*** 209,214 ****
--- 203,213 ----
  	PGresult_data *curBlock;	/* most recently allocated block */
  	int			curOffset;		/* start offset of free space in block */
  	int			spaceLeft;		/* number of free bytes remaining in block */
+ 
+ 	addTupleFunction addTupleFunc; /* Tuple storage function. See
+ 									* addTupleFunction for details. */
+ 	void *addTupleFuncParam;       /* Contextual parameter for addTupleFunc */
+ 	char *addTupleFuncErrMes;      /* Error message returned from addTupFunc */
  };
  
  /* PGAsyncStatusType defines the state of the query-execution state machine */
*************** struct pg_conn
*** 443,448 ****
--- 442,454 ----
  
  	/* Buffer for receiving various parts of messages */
  	PQExpBufferData workBuffer; /* expansible string */
+ 
+     /* Tuple store function. The two fields below is copied to newly
+ 	 * created PGresult if addTupleFunc is not NULL. Use default
+ 	 * function if addTupleFunc is NULL. */
+ 	addTupleFunction addTupleFunc; /* Tuple storage function. See
+ 									* addTupleFunction for details. */
+ 	void *addTupleFuncParam;       /* Contextual parameter for addTupFunc */
  };
  
  /* PGcancel stores all data necessary to cancel a connection. A copy of this
*************** extern void
*** 507,513 ****
  pqInternalNotice(const PGNoticeHooks *hooks, const char *fmt,...)
  /* This lets gcc check the format string for consistency. */
  __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
- extern int	pqAddTuple(PGresult *res, PGresAttValue *tup);
  extern void pqSaveMessageField(PGresult *res, char code,
  				   const char *value);
  extern void pqSaveParameterStatus(PGconn *conn, const char *name,
--- 513,518 ----
