alt-result-storage-v1.patch
text/x-patch
Filename: alt-result-storage-v1.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v1
| File | + | − |
|---|---|---|
| contrib/dblink/dblink.c | 0 | 0 |
| doc/src/sgml/libpq.sgml | 0 | 0 |
| src/interfaces/libpq/exports.txt | 0 | 0 |
| src/interfaces/libpq/fe-connect.c | 0 | 0 |
| src/interfaces/libpq/fe-exec.c | 0 | 0 |
| src/interfaces/libpq/fe-protocol2.c | 0 | 0 |
| src/interfaces/libpq/fe-protocol3.c | 0 | 0 |
| src/interfaces/libpq/libpq-fe.h | 0 | 0 |
| src/interfaces/libpq/libpq-int.h | 0 | 0 |
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 ----