Commit 41b9c845 authored by Tom Lane's avatar Tom Lane

Replace libpq's "row processor" API with a "single row" mode.

After taking awhile to digest the row-processor feature that was added to
libpq in commit 92785dac, we've concluded
it is over-complicated and too hard to use.  Leave the core infrastructure
changes in place (that is, there's still a row processor function inside
libpq), but remove the exposed API pieces, and instead provide a "single
row" mode switch that causes PQgetResult to return one row at a time in
separate PGresult objects.

This approach incurs more overhead than proper use of a row processor
callback would, since construction of a PGresult per row adds extra cycles.
However, it is far easier to use and harder to break.  The single-row mode
still affords applications the primary benefit that the row processor API
was meant to provide, namely not having to accumulate large result sets in
memory before processing them.  Preliminary testing suggests that we can
probably buy back most of the extra cycles by micro-optimizing construction
of the extra results, but that task will be left for another day.

Marko Kreen
parent 7c0fecda
...@@ -70,6 +70,9 @@ typedef struct storeInfo ...@@ -70,6 +70,9 @@ typedef struct storeInfo
AttInMetadata *attinmeta; AttInMetadata *attinmeta;
MemoryContext tmpcontext; MemoryContext tmpcontext;
char **cstrs; char **cstrs;
/* temp storage for results to avoid leaks on exception */
PGresult *last_res;
PGresult *cur_res;
} storeInfo; } storeInfo;
/* /*
...@@ -83,8 +86,8 @@ static void materializeQueryResult(FunctionCallInfo fcinfo, ...@@ -83,8 +86,8 @@ static void materializeQueryResult(FunctionCallInfo fcinfo,
const char *conname, const char *conname,
const char *sql, const char *sql,
bool fail); bool fail);
static int storeHandler(PGresult *res, const PGdataValue *columns, static PGresult *storeQueryResult(storeInfo *sinfo, PGconn *conn, const char *sql);
const char **errmsgp, void *param); static void storeRow(storeInfo *sinfo, PGresult *res, bool first);
static remoteConn *getConnectionByName(const char *name); static remoteConn *getConnectionByName(const char *name);
static HTAB *createConnHash(void); static HTAB *createConnHash(void);
static void createNewConnection(const char *name, remoteConn *rconn); static void createNewConnection(const char *name, remoteConn *rconn);
...@@ -630,7 +633,7 @@ dblink_send_query(PG_FUNCTION_ARGS) ...@@ -630,7 +633,7 @@ dblink_send_query(PG_FUNCTION_ARGS)
/* async query send */ /* async query send */
retval = PQsendQuery(conn, sql); retval = PQsendQuery(conn, sql);
if (retval != 1) if (retval != 1)
elog(NOTICE, "%s", PQerrorMessage(conn)); elog(NOTICE, "could not send query: %s", PQerrorMessage(conn));
PG_RETURN_INT32(retval); PG_RETURN_INT32(retval);
} }
...@@ -927,8 +930,10 @@ materializeResult(FunctionCallInfo fcinfo, PGresult *res) ...@@ -927,8 +930,10 @@ materializeResult(FunctionCallInfo fcinfo, PGresult *res)
/* /*
* Execute the given SQL command and store its results into a tuplestore * Execute the given SQL command and store its results into a tuplestore
* to be returned as the result of the current function. * to be returned as the result of the current function.
*
* This is equivalent to PQexec followed by materializeResult, but we make * This is equivalent to PQexec followed by materializeResult, but we make
* use of libpq's "row processor" API to reduce per-row overhead. * use of libpq's single-row mode to avoid accumulating the whole result
* inside libpq before it gets transferred to the tuplestore.
*/ */
static void static void
materializeQueryResult(FunctionCallInfo fcinfo, materializeQueryResult(FunctionCallInfo fcinfo,
...@@ -944,19 +949,14 @@ materializeQueryResult(FunctionCallInfo fcinfo, ...@@ -944,19 +949,14 @@ materializeQueryResult(FunctionCallInfo fcinfo,
/* prepTuplestoreResult must have been called previously */ /* prepTuplestoreResult must have been called previously */
Assert(rsinfo->returnMode == SFRM_Materialize); Assert(rsinfo->returnMode == SFRM_Materialize);
PG_TRY();
{
/* initialize storeInfo to empty */ /* initialize storeInfo to empty */
memset(&sinfo, 0, sizeof(sinfo)); memset(&sinfo, 0, sizeof(sinfo));
sinfo.fcinfo = fcinfo; sinfo.fcinfo = fcinfo;
/* We'll collect tuples using storeHandler */ PG_TRY();
PQsetRowProcessor(conn, storeHandler, &sinfo); {
/* execute query, collecting any tuples into the tuplestore */
res = PQexec(conn, sql); res = storeQueryResult(&sinfo, conn, sql);
/* We don't keep the custom row processor installed permanently */
PQsetRowProcessor(conn, NULL, NULL);
if (!res || if (!res ||
(PQresultStatus(res) != PGRES_COMMAND_OK && (PQresultStatus(res) != PGRES_COMMAND_OK &&
...@@ -975,8 +975,8 @@ materializeQueryResult(FunctionCallInfo fcinfo, ...@@ -975,8 +975,8 @@ materializeQueryResult(FunctionCallInfo fcinfo,
else if (PQresultStatus(res) == PGRES_COMMAND_OK) else if (PQresultStatus(res) == PGRES_COMMAND_OK)
{ {
/* /*
* storeHandler didn't get called, so we need to convert the * storeRow didn't get called, so we need to convert the command
* command status string to a tuple manually * status string to a tuple manually
*/ */
TupleDesc tupdesc; TupleDesc tupdesc;
AttInMetadata *attinmeta; AttInMetadata *attinmeta;
...@@ -1008,25 +1008,30 @@ materializeQueryResult(FunctionCallInfo fcinfo, ...@@ -1008,25 +1008,30 @@ materializeQueryResult(FunctionCallInfo fcinfo,
tuplestore_puttuple(tupstore, tuple); tuplestore_puttuple(tupstore, tuple);
PQclear(res); PQclear(res);
res = NULL;
} }
else else
{ {
Assert(PQresultStatus(res) == PGRES_TUPLES_OK); Assert(PQresultStatus(res) == PGRES_TUPLES_OK);
/* storeHandler should have created a tuplestore */ /* storeRow should have created a tuplestore */
Assert(rsinfo->setResult != NULL); Assert(rsinfo->setResult != NULL);
PQclear(res); PQclear(res);
res = NULL;
} }
PQclear(sinfo.last_res);
sinfo.last_res = NULL;
PQclear(sinfo.cur_res);
sinfo.cur_res = NULL;
} }
PG_CATCH(); PG_CATCH();
{ {
/* be sure to unset the custom row processor */
PQsetRowProcessor(conn, NULL, NULL);
/* be sure to release any libpq result we collected */ /* be sure to release any libpq result we collected */
if (res)
PQclear(res); PQclear(res);
PQclear(sinfo.last_res);
PQclear(sinfo.cur_res);
/* and clear out any pending data in libpq */ /* and clear out any pending data in libpq */
while ((res = PQskipResult(conn)) != NULL) while ((res = PQgetResult(conn)) != NULL)
PQclear(res); PQclear(res);
PG_RE_THROW(); PG_RE_THROW();
} }
...@@ -1034,23 +1039,72 @@ materializeQueryResult(FunctionCallInfo fcinfo, ...@@ -1034,23 +1039,72 @@ materializeQueryResult(FunctionCallInfo fcinfo,
} }
/* /*
* Custom row processor for materializeQueryResult. * Execute query, and send any result rows to sinfo->tuplestore.
* Prototype of this function must match PQrowProcessor.
*/ */
static int static PGresult *
storeHandler(PGresult *res, const PGdataValue *columns, storeQueryResult(storeInfo *sinfo, PGconn *conn, const char *sql)
const char **errmsgp, void *param) {
bool first = true;
PGresult *res;
if (!PQsendQuery(conn, sql))
elog(ERROR, "could not send query: %s", PQerrorMessage(conn));
if (!PQsetSingleRowMode(conn)) /* shouldn't fail */
elog(ERROR, "failed to set single-row mode for dblink query");
for (;;)
{
CHECK_FOR_INTERRUPTS();
sinfo->cur_res = PQgetResult(conn);
if (!sinfo->cur_res)
break;
if (PQresultStatus(sinfo->cur_res) == PGRES_SINGLE_TUPLE)
{
/* got one row from possibly-bigger resultset */
storeRow(sinfo, sinfo->cur_res, first);
PQclear(sinfo->cur_res);
sinfo->cur_res = NULL;
first = false;
}
else
{
/* if empty resultset, fill tuplestore header */
if (first && PQresultStatus(sinfo->cur_res) == PGRES_TUPLES_OK)
storeRow(sinfo, sinfo->cur_res, first);
/* store completed result at last_res */
PQclear(sinfo->last_res);
sinfo->last_res = sinfo->cur_res;
sinfo->cur_res = NULL;
first = true;
}
}
/* return last_res */
res = sinfo->last_res;
sinfo->last_res = NULL;
return res;
}
/*
* Send single row to sinfo->tuplestore.
*
* If "first" is true, create the tuplestore using PGresult's metadata
* (in this case the PGresult might contain either zero or one row).
*/
static void
storeRow(storeInfo *sinfo, PGresult *res, bool first)
{ {
storeInfo *sinfo = (storeInfo *) param;
int nfields = PQnfields(res); int nfields = PQnfields(res);
char **cstrs = sinfo->cstrs;
HeapTuple tuple; HeapTuple tuple;
char *pbuf;
int pbuflen;
int i; int i;
MemoryContext oldcontext; MemoryContext oldcontext;
if (columns == NULL) if (first)
{ {
/* Prepare for new result set */ /* Prepare for new result set */
ReturnSetInfo *rsinfo = (ReturnSetInfo *) sinfo->fcinfo->resultinfo; ReturnSetInfo *rsinfo = (ReturnSetInfo *) sinfo->fcinfo->resultinfo;
...@@ -1098,13 +1152,16 @@ storeHandler(PGresult *res, const PGdataValue *columns, ...@@ -1098,13 +1152,16 @@ storeHandler(PGresult *res, const PGdataValue *columns,
sinfo->attinmeta = TupleDescGetAttInMetadata(tupdesc); sinfo->attinmeta = TupleDescGetAttInMetadata(tupdesc);
/* Create a new, empty tuplestore */ /* Create a new, empty tuplestore */
oldcontext = MemoryContextSwitchTo( oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
rsinfo->econtext->ecxt_per_query_memory);
sinfo->tuplestore = tuplestore_begin_heap(true, false, work_mem); sinfo->tuplestore = tuplestore_begin_heap(true, false, work_mem);
rsinfo->setResult = sinfo->tuplestore; rsinfo->setResult = sinfo->tuplestore;
rsinfo->setDesc = tupdesc; rsinfo->setDesc = tupdesc;
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
/* Done if empty resultset */
if (PQntuples(res) == 0)
return;
/* /*
* Set up sufficiently-wide string pointers array; this won't change * Set up sufficiently-wide string pointers array; this won't change
* in size so it's easy to preallocate. * in size so it's easy to preallocate.
...@@ -1121,11 +1178,10 @@ storeHandler(PGresult *res, const PGdataValue *columns, ...@@ -1121,11 +1178,10 @@ storeHandler(PGresult *res, const PGdataValue *columns,
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE); ALLOCSET_DEFAULT_MAXSIZE);
return 1;
} }
CHECK_FOR_INTERRUPTS(); /* Should have a single-row result if we get here */
Assert(PQntuples(res) == 1);
/* /*
* Do the following work in a temp context that we reset after each tuple. * Do the following work in a temp context that we reset after each tuple.
...@@ -1135,46 +1191,24 @@ storeHandler(PGresult *res, const PGdataValue *columns, ...@@ -1135,46 +1191,24 @@ storeHandler(PGresult *res, const PGdataValue *columns,
oldcontext = MemoryContextSwitchTo(sinfo->tmpcontext); oldcontext = MemoryContextSwitchTo(sinfo->tmpcontext);
/* /*
* The strings passed to us are not null-terminated, but the datatype * Fill cstrs with null-terminated strings of column values.
* input functions we're about to call require null termination. Copy the
* strings and add null termination. As a micro-optimization, allocate
* all the strings with one palloc.
*/ */
pbuflen = nfields; /* count the null terminators themselves */
for (i = 0; i < nfields; i++)
{
int len = columns[i].len;
if (len > 0)
pbuflen += len;
}
pbuf = (char *) palloc(pbuflen);
for (i = 0; i < nfields; i++) for (i = 0; i < nfields; i++)
{ {
int len = columns[i].len; if (PQgetisnull(res, 0, i))
sinfo->cstrs[i] = NULL;
if (len < 0)
cstrs[i] = NULL;
else else
{ sinfo->cstrs[i] = PQgetvalue(res, 0, i);
cstrs[i] = pbuf;
memcpy(pbuf, columns[i].value, len);
pbuf += len;
*pbuf++ = '\0';
}
} }
/* Convert row to a tuple, and add it to the tuplestore */ /* Convert row to a tuple, and add it to the tuplestore */
tuple = BuildTupleFromCStrings(sinfo->attinmeta, cstrs); tuple = BuildTupleFromCStrings(sinfo->attinmeta, sinfo->cstrs);
tuplestore_puttuple(sinfo->tuplestore, tuple); tuplestore_puttuple(sinfo->tuplestore, tuple);
/* Clean up */ /* Clean up */
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
MemoryContextReset(sinfo->tmpcontext); MemoryContextReset(sinfo->tmpcontext);
return 1;
} }
/* /*
......
This diff is collapsed.
...@@ -160,6 +160,4 @@ PQconnectStartParams 157 ...@@ -160,6 +160,4 @@ PQconnectStartParams 157
PQping 158 PQping 158
PQpingParams 159 PQpingParams 159
PQlibVersion 160 PQlibVersion 160
PQsetRowProcessor 161 PQsetSingleRowMode 161
PQgetRowProcessor 162
PQskipResult 163
...@@ -2709,8 +2709,7 @@ makeEmptyPGconn(void) ...@@ -2709,8 +2709,7 @@ makeEmptyPGconn(void)
/* Zero all pointers and booleans */ /* Zero all pointers and booleans */
MemSet(conn, 0, sizeof(PGconn)); MemSet(conn, 0, sizeof(PGconn));
/* install default row processor and notice hooks */ /* install default notice hooks */
PQsetRowProcessor(conn, NULL, NULL);
conn->noticeHooks.noticeRec = defaultNoticeReceiver; conn->noticeHooks.noticeRec = defaultNoticeReceiver;
conn->noticeHooks.noticeProc = defaultNoticeProcessor; conn->noticeHooks.noticeProc = defaultNoticeProcessor;
...@@ -5594,8 +5593,8 @@ static void ...@@ -5594,8 +5593,8 @@ static void
dot_pg_pass_warning(PGconn *conn) dot_pg_pass_warning(PGconn *conn)
{ {
/* If it was 'invalid authorization', add .pgpass mention */ /* If it was 'invalid authorization', add .pgpass mention */
if (conn->dot_pgpass_used && conn->password_needed && conn->result &&
/* only works with >= 9.0 servers */ /* only works with >= 9.0 servers */
if (conn->dot_pgpass_used && conn->password_needed && conn->result &&
strcmp(PQresultErrorField(conn->result, PG_DIAG_SQLSTATE), strcmp(PQresultErrorField(conn->result, PG_DIAG_SQLSTATE),
ERRCODE_INVALID_PASSWORD) == 0) ERRCODE_INVALID_PASSWORD) == 0)
{ {
......
...@@ -38,7 +38,8 @@ char *const pgresStatus[] = { ...@@ -38,7 +38,8 @@ char *const pgresStatus[] = {
"PGRES_BAD_RESPONSE", "PGRES_BAD_RESPONSE",
"PGRES_NONFATAL_ERROR", "PGRES_NONFATAL_ERROR",
"PGRES_FATAL_ERROR", "PGRES_FATAL_ERROR",
"PGRES_COPY_BOTH" "PGRES_COPY_BOTH",
"PGRES_SINGLE_TUPLE"
}; };
/* /*
...@@ -51,8 +52,6 @@ static bool static_std_strings = false; ...@@ -51,8 +52,6 @@ static bool static_std_strings = false;
static PGEvent *dupEvents(PGEvent *events, int count); static PGEvent *dupEvents(PGEvent *events, int count);
static bool pqAddTuple(PGresult *res, PGresAttValue *tup); static bool pqAddTuple(PGresult *res, PGresAttValue *tup);
static int pqStdRowProcessor(PGresult *res, const PGdataValue *columns,
const char **errmsgp, void *param);
static bool PQsendQueryStart(PGconn *conn); static bool PQsendQueryStart(PGconn *conn);
static int PQsendQueryGuts(PGconn *conn, static int PQsendQueryGuts(PGconn *conn,
const char *command, const char *command,
...@@ -64,8 +63,6 @@ static int PQsendQueryGuts(PGconn *conn, ...@@ -64,8 +63,6 @@ static int PQsendQueryGuts(PGconn *conn,
const int *paramFormats, const int *paramFormats,
int resultFormat); int resultFormat);
static void parseInput(PGconn *conn); static void parseInput(PGconn *conn);
static int dummyRowProcessor(PGresult *res, const PGdataValue *columns,
const char **errmsgp, void *param);
static bool PQexecStart(PGconn *conn); static bool PQexecStart(PGconn *conn);
static PGresult *PQexecFinish(PGconn *conn); static PGresult *PQexecFinish(PGconn *conn);
static int PQsendDescribe(PGconn *conn, char desc_type, static int PQsendDescribe(PGconn *conn, char desc_type,
...@@ -181,6 +178,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) ...@@ -181,6 +178,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
case PGRES_COPY_OUT: case PGRES_COPY_OUT:
case PGRES_COPY_IN: case PGRES_COPY_IN:
case PGRES_COPY_BOTH: case PGRES_COPY_BOTH:
case PGRES_SINGLE_TUPLE:
/* non-error cases */ /* non-error cases */
break; break;
default: default:
...@@ -698,6 +696,8 @@ PQclear(PGresult *res) ...@@ -698,6 +696,8 @@ PQclear(PGresult *res)
/* /*
* Handy subroutine to deallocate any partially constructed async result. * Handy subroutine to deallocate any partially constructed async result.
*
* Any "next" result gets cleared too.
*/ */
void void
pqClearAsyncResult(PGconn *conn) pqClearAsyncResult(PGconn *conn)
...@@ -705,6 +705,9 @@ pqClearAsyncResult(PGconn *conn) ...@@ -705,6 +705,9 @@ pqClearAsyncResult(PGconn *conn)
if (conn->result) if (conn->result)
PQclear(conn->result); PQclear(conn->result);
conn->result = NULL; conn->result = NULL;
if (conn->next_result)
PQclear(conn->next_result);
conn->next_result = NULL;
} }
/* /*
...@@ -758,7 +761,6 @@ pqPrepareAsyncResult(PGconn *conn) ...@@ -758,7 +761,6 @@ pqPrepareAsyncResult(PGconn *conn)
* conn->errorMessage. * conn->errorMessage.
*/ */
res = conn->result; res = conn->result;
conn->result = NULL; /* handing over ownership to caller */
if (!res) if (!res)
res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
else else
...@@ -771,6 +773,16 @@ pqPrepareAsyncResult(PGconn *conn) ...@@ -771,6 +773,16 @@ pqPrepareAsyncResult(PGconn *conn)
appendPQExpBufferStr(&conn->errorMessage, appendPQExpBufferStr(&conn->errorMessage,
PQresultErrorMessage(res)); PQresultErrorMessage(res));
} }
/*
* Replace conn->result with next_result, if any. In the normal case
* there isn't a next result and we're just dropping ownership of the
* current result. In single-row mode this restores the situation to what
* it was before we created the current single-row result.
*/
conn->result = conn->next_result;
conn->next_result = NULL;
return res; return res;
} }
...@@ -981,85 +993,55 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) ...@@ -981,85 +993,55 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
/* /*
* PQsetRowProcessor * pqRowProcessor
* Set function that copies row data out from the network buffer, * Add the received row to the current async result (conn->result).
* along with a passthrough parameter for it. * Returns 1 if OK, 0 if error occurred.
*/
void
PQsetRowProcessor(PGconn *conn, PQrowProcessor func, void *param)
{
if (!conn)
return;
if (func)
{
/* set custom row processor */
conn->rowProcessor = func;
conn->rowProcessorParam = param;
}
else
{
/* set default row processor */
conn->rowProcessor = pqStdRowProcessor;
conn->rowProcessorParam = conn;
}
}
/*
* PQgetRowProcessor
* Get current row processor of PGconn.
* If param is not NULL, also store the passthrough parameter at *param.
*/
PQrowProcessor
PQgetRowProcessor(const PGconn *conn, void **param)
{
if (!conn)
{
if (param)
*param = NULL;
return NULL;
}
if (param)
*param = conn->rowProcessorParam;
return conn->rowProcessor;
}
/*
* pqStdRowProcessor
* Add the received row to the PGresult structure
* Returns 1 if OK, -1 if error occurred.
* *
* Note: "param" should point to the PGconn, but we don't actually need that * On error, *errmsgp can be set to an error string to be returned.
* as of the current coding. * If it is left NULL, the error is presumed to be "out of memory".
*
* In single-row mode, we create a new result holding just the current row,
* stashing the previous result in conn->next_result so that it becomes
* active again after pqPrepareAsyncResult(). This allows the result metadata
* (column descriptions) to be carried forward to each result row.
*/ */
static int int
pqStdRowProcessor(PGresult *res, const PGdataValue *columns, pqRowProcessor(PGconn *conn, const char **errmsgp)
const char **errmsgp, void *param)
{ {
PGresult *res = conn->result;
int nfields = res->numAttributes; int nfields = res->numAttributes;
const PGdataValue *columns = conn->rowBuf;
PGresAttValue *tup; PGresAttValue *tup;
int i; int i;
if (columns == NULL) /*
* In single-row mode, make a new PGresult that will hold just this one
* row; the original conn->result is left unchanged so that it can be used
* again as the template for future rows.
*/
if (conn->singleRowMode)
{ {
/* New result set ... we have nothing to do in this function. */ /* Copy everything that should be in the result at this point */
return 1; res = PQcopyResult(res,
PG_COPYRES_ATTRS | PG_COPYRES_EVENTS |
PG_COPYRES_NOTICEHOOKS);
if (!res)
return 0;
} }
/* /*
* Basically we just allocate space in the PGresult for each field and * Basically we just allocate space in the PGresult for each field and
* copy the data over. * copy the data over.
* *
* Note: on malloc failure, we return -1 leaving *errmsgp still NULL, * Note: on malloc failure, we return 0 leaving *errmsgp still NULL, which
* which caller will take to mean "out of memory". This is preferable to * caller will take to mean "out of memory". This is preferable to trying
* trying to set up such a message here, because evidently there's not * to set up such a message here, because evidently there's not enough
* enough memory for gettext() to do anything. * memory for gettext() to do anything.
*/ */
tup = (PGresAttValue *) tup = (PGresAttValue *)
pqResultAlloc(res, nfields * sizeof(PGresAttValue), TRUE); pqResultAlloc(res, nfields * sizeof(PGresAttValue), TRUE);
if (tup == NULL) if (tup == NULL)
return -1; goto fail;
for (i = 0; i < nfields; i++) for (i = 0; i < nfields; i++)
{ {
...@@ -1078,7 +1060,7 @@ pqStdRowProcessor(PGresult *res, const PGdataValue *columns, ...@@ -1078,7 +1060,7 @@ pqStdRowProcessor(PGresult *res, const PGdataValue *columns,
val = (char *) pqResultAlloc(res, clen + 1, isbinary); val = (char *) pqResultAlloc(res, clen + 1, isbinary);
if (val == NULL) if (val == NULL)
return -1; goto fail;
/* copy and zero-terminate the data (even if it's binary) */ /* copy and zero-terminate the data (even if it's binary) */
memcpy(val, columns[i].value, clen); memcpy(val, columns[i].value, clen);
...@@ -1091,10 +1073,30 @@ pqStdRowProcessor(PGresult *res, const PGdataValue *columns, ...@@ -1091,10 +1073,30 @@ pqStdRowProcessor(PGresult *res, const PGdataValue *columns,
/* And add the tuple to the PGresult's tuple array */ /* And add the tuple to the PGresult's tuple array */
if (!pqAddTuple(res, tup)) if (!pqAddTuple(res, tup))
return -1; goto fail;
/*
* Success. In single-row mode, make the result available to the client
* immediately.
*/
if (conn->singleRowMode)
{
/* Change result status to special single-row value */
res->resultStatus = PGRES_SINGLE_TUPLE;
/* Stash old result for re-use later */
conn->next_result = conn->result;
conn->result = res;
/* And mark the result ready to return */
conn->asyncStatus = PGASYNC_READY;
}
/* Success */
return 1; return 1;
fail:
/* release locally allocated PGresult, if we made one */
if (res != conn->result)
PQclear(res);
return 0;
} }
...@@ -1343,6 +1345,10 @@ PQsendQueryStart(PGconn *conn) ...@@ -1343,6 +1345,10 @@ PQsendQueryStart(PGconn *conn)
/* initialize async result-accumulation state */ /* initialize async result-accumulation state */
conn->result = NULL; conn->result = NULL;
conn->next_result = NULL;
/* reset single-row processing mode */
conn->singleRowMode = false;
/* ready to send command message */ /* ready to send command message */
return true; return true;
...@@ -1547,6 +1553,31 @@ pqHandleSendFailure(PGconn *conn) ...@@ -1547,6 +1553,31 @@ pqHandleSendFailure(PGconn *conn)
parseInput(conn); parseInput(conn);
} }
/*
* Select row-by-row processing mode
*/
int
PQsetSingleRowMode(PGconn *conn)
{
/*
* Only allow setting the flag when we have launched a query and not yet
* received any results.
*/
if (!conn)
return 0;
if (conn->asyncStatus != PGASYNC_BUSY)
return 0;
if (conn->queryclass != PGQUERY_SIMPLE &&
conn->queryclass != PGQUERY_EXTENDED)
return 0;
if (conn->result)
return 0;
/* OK, set flag */
conn->singleRowMode = true;
return 1;
}
/* /*
* Consume any available input from the backend * Consume any available input from the backend
* 0 return: some kind of trouble * 0 return: some kind of trouble
...@@ -1587,9 +1618,6 @@ PQconsumeInput(PGconn *conn) ...@@ -1587,9 +1618,6 @@ PQconsumeInput(PGconn *conn)
* parseInput: if appropriate, parse input data from backend * parseInput: if appropriate, parse input data from backend
* until input is exhausted or a stopping state is reached. * until input is exhausted or a stopping state is reached.
* Note that this function will NOT attempt to read more data from the backend. * Note that this function will NOT attempt to read more data from the backend.
*
* Note: callers of parseInput must be prepared for a longjmp exit when we are
* in PGASYNC_BUSY state, since an external row processor might do that.
*/ */
static void static void
parseInput(PGconn *conn) parseInput(PGconn *conn)
...@@ -1737,49 +1765,6 @@ PQgetResult(PGconn *conn) ...@@ -1737,49 +1765,6 @@ PQgetResult(PGconn *conn)
return res; return res;
} }
/*
* PQskipResult
* Get the next PGresult produced by a query, but discard any data rows.
*
* This is mainly useful for cleaning up after a longjmp out of a row
* processor, when resuming processing of the current query result isn't
* wanted. Note that this is of little value in an async-style application,
* since any preceding calls to PQisBusy would have already called the regular
* row processor.
*/
PGresult *
PQskipResult(PGconn *conn)
{
PGresult *res;
PQrowProcessor savedRowProcessor;
if (!conn)
return NULL;
/* temporarily install dummy row processor */
savedRowProcessor = conn->rowProcessor;
conn->rowProcessor = dummyRowProcessor;
/* no need to save/change rowProcessorParam */
/* fetch the next result */
res = PQgetResult(conn);
/* restore previous row processor */
conn->rowProcessor = savedRowProcessor;
return res;
}
/*
* Do-nothing row processor for PQskipResult
*/
static int
dummyRowProcessor(PGresult *res, const PGdataValue *columns,
const char **errmsgp, void *param)
{
return 1;
}
/* /*
* PQexec * PQexec
...@@ -1886,7 +1871,7 @@ PQexecStart(PGconn *conn) ...@@ -1886,7 +1871,7 @@ PQexecStart(PGconn *conn)
* Silently discard any prior query result that application didn't eat. * Silently discard any prior query result that application didn't eat.
* This is probably poor design, but it's here for backward compatibility. * This is probably poor design, but it's here for backward compatibility.
*/ */
while ((result = PQskipResult(conn)) != NULL) while ((result = PQgetResult(conn)) != NULL)
{ {
ExecStatusType resultStatus = result->resultStatus; ExecStatusType resultStatus = result->resultStatus;
......
...@@ -682,8 +682,6 @@ lo_initialize(PGconn *conn) ...@@ -682,8 +682,6 @@ lo_initialize(PGconn *conn)
int n; int n;
const char *query; const char *query;
const char *fname; const char *fname;
PQrowProcessor savedRowProcessor;
void *savedRowProcessorParam;
Oid foid; Oid foid;
if (!conn) if (!conn)
...@@ -732,16 +730,7 @@ lo_initialize(PGconn *conn) ...@@ -732,16 +730,7 @@ lo_initialize(PGconn *conn)
"or proname = 'loread' " "or proname = 'loread' "
"or proname = 'lowrite'"; "or proname = 'lowrite'";
/* Ensure the standard row processor is used to collect the result */
savedRowProcessor = conn->rowProcessor;
savedRowProcessorParam = conn->rowProcessorParam;
PQsetRowProcessor(conn, NULL, NULL);
res = PQexec(conn, query); res = PQexec(conn, query);
conn->rowProcessor = savedRowProcessor;
conn->rowProcessorParam = savedRowProcessorParam;
if (res == NULL) if (res == NULL)
{ {
free(lobjfuncs); free(lobjfuncs);
......
...@@ -49,19 +49,11 @@ static int getNotify(PGconn *conn); ...@@ -49,19 +49,11 @@ static int getNotify(PGconn *conn);
PostgresPollingStatusType PostgresPollingStatusType
pqSetenvPoll(PGconn *conn) pqSetenvPoll(PGconn *conn)
{ {
PostgresPollingStatusType result;
PGresult *res; PGresult *res;
PQrowProcessor savedRowProcessor;
void *savedRowProcessorParam;
if (conn == NULL || conn->status == CONNECTION_BAD) if (conn == NULL || conn->status == CONNECTION_BAD)
return PGRES_POLLING_FAILED; return PGRES_POLLING_FAILED;
/* Ensure the standard row processor is used to collect any results */
savedRowProcessor = conn->rowProcessor;
savedRowProcessorParam = conn->rowProcessorParam;
PQsetRowProcessor(conn, NULL, NULL);
/* Check whether there are any data for us */ /* Check whether there are any data for us */
switch (conn->setenv_state) switch (conn->setenv_state)
{ {
...@@ -77,10 +69,7 @@ pqSetenvPoll(PGconn *conn) ...@@ -77,10 +69,7 @@ pqSetenvPoll(PGconn *conn)
if (n < 0) if (n < 0)
goto error_return; goto error_return;
if (n == 0) if (n == 0)
{ return PGRES_POLLING_READING;
result = PGRES_POLLING_READING;
goto normal_return;
}
break; break;
} }
...@@ -94,8 +83,7 @@ pqSetenvPoll(PGconn *conn) ...@@ -94,8 +83,7 @@ pqSetenvPoll(PGconn *conn)
/* Should we raise an error if called when not active? */ /* Should we raise an error if called when not active? */
case SETENV_STATE_IDLE: case SETENV_STATE_IDLE:
result = PGRES_POLLING_OK; return PGRES_POLLING_OK;
goto normal_return;
default: default:
printfPQExpBuffer(&conn->errorMessage, printfPQExpBuffer(&conn->errorMessage,
...@@ -192,10 +180,7 @@ pqSetenvPoll(PGconn *conn) ...@@ -192,10 +180,7 @@ pqSetenvPoll(PGconn *conn)
case SETENV_STATE_CLIENT_ENCODING_WAIT: case SETENV_STATE_CLIENT_ENCODING_WAIT:
{ {
if (PQisBusy(conn)) if (PQisBusy(conn))
{ return PGRES_POLLING_READING;
result = PGRES_POLLING_READING;
goto normal_return;
}
res = PQgetResult(conn); res = PQgetResult(conn);
...@@ -220,10 +205,7 @@ pqSetenvPoll(PGconn *conn) ...@@ -220,10 +205,7 @@ pqSetenvPoll(PGconn *conn)
case SETENV_STATE_OPTION_WAIT: case SETENV_STATE_OPTION_WAIT:
{ {
if (PQisBusy(conn)) if (PQisBusy(conn))
{ return PGRES_POLLING_READING;
result = PGRES_POLLING_READING;
goto normal_return;
}
res = PQgetResult(conn); res = PQgetResult(conn);
...@@ -262,17 +244,13 @@ pqSetenvPoll(PGconn *conn) ...@@ -262,17 +244,13 @@ pqSetenvPoll(PGconn *conn)
goto error_return; goto error_return;
conn->setenv_state = SETENV_STATE_QUERY1_WAIT; conn->setenv_state = SETENV_STATE_QUERY1_WAIT;
result = PGRES_POLLING_READING; return PGRES_POLLING_READING;
goto normal_return;
} }
case SETENV_STATE_QUERY1_WAIT: case SETENV_STATE_QUERY1_WAIT:
{ {
if (PQisBusy(conn)) if (PQisBusy(conn))
{ return PGRES_POLLING_READING;
result = PGRES_POLLING_READING;
goto normal_return;
}
res = PQgetResult(conn); res = PQgetResult(conn);
...@@ -349,17 +327,13 @@ pqSetenvPoll(PGconn *conn) ...@@ -349,17 +327,13 @@ pqSetenvPoll(PGconn *conn)
goto error_return; goto error_return;
conn->setenv_state = SETENV_STATE_QUERY2_WAIT; conn->setenv_state = SETENV_STATE_QUERY2_WAIT;
result = PGRES_POLLING_READING; return PGRES_POLLING_READING;
goto normal_return;
} }
case SETENV_STATE_QUERY2_WAIT: case SETENV_STATE_QUERY2_WAIT:
{ {
if (PQisBusy(conn)) if (PQisBusy(conn))
{ return PGRES_POLLING_READING;
result = PGRES_POLLING_READING;
goto normal_return;
}
res = PQgetResult(conn); res = PQgetResult(conn);
...@@ -406,8 +380,7 @@ pqSetenvPoll(PGconn *conn) ...@@ -406,8 +380,7 @@ pqSetenvPoll(PGconn *conn)
{ {
/* Query finished, so we're done */ /* Query finished, so we're done */
conn->setenv_state = SETENV_STATE_IDLE; conn->setenv_state = SETENV_STATE_IDLE;
result = PGRES_POLLING_OK; return PGRES_POLLING_OK;
goto normal_return;
} }
break; break;
} }
...@@ -425,12 +398,7 @@ pqSetenvPoll(PGconn *conn) ...@@ -425,12 +398,7 @@ pqSetenvPoll(PGconn *conn)
error_return: error_return:
conn->setenv_state = SETENV_STATE_IDLE; conn->setenv_state = SETENV_STATE_IDLE;
result = PGRES_POLLING_FAILED; return PGRES_POLLING_FAILED;
normal_return:
conn->rowProcessor = savedRowProcessor;
conn->rowProcessorParam = savedRowProcessorParam;
return result;
} }
...@@ -438,9 +406,6 @@ normal_return: ...@@ -438,9 +406,6 @@ normal_return:
* parseInput: if appropriate, parse input data from backend * parseInput: if appropriate, parse input data from backend
* until input is exhausted or a stopping state is reached. * until input is exhausted or a stopping state is reached.
* Note that this function will NOT attempt to read more data from the backend. * Note that this function will NOT attempt to read more data from the backend.
*
* Note: callers of parseInput must be prepared for a longjmp exit when we are
* in PGASYNC_BUSY state, since an external row processor might do that.
*/ */
void void
pqParseInput2(PGconn *conn) pqParseInput2(PGconn *conn)
...@@ -746,32 +711,17 @@ getRowDescriptions(PGconn *conn) ...@@ -746,32 +711,17 @@ getRowDescriptions(PGconn *conn)
/* Success! */ /* Success! */
conn->result = result; conn->result = result;
/* Advance inStart to show that the "T" message has been processed. */
conn->inStart = conn->inCursor;
/* /*
* Advance inStart to show that the "T" message has been processed. We * We could perform additional setup for the new result set here, but for
* must do this before calling the row processor, in case it longjmps. * now there's nothing else to do.
*/ */
conn->inStart = conn->inCursor;
/* Give the row processor a chance to initialize for new result set */ /* And we're done. */
errmsg = NULL;
switch ((*conn->rowProcessor) (result, NULL, &errmsg,
conn->rowProcessorParam))
{
case 1:
/* everything is good */
return 0; return 0;
case -1:
/* error, report the errmsg below */
break;
default:
/* unrecognized return code */
errmsg = libpq_gettext("unrecognized return value from row processor");
break;
}
goto set_error_result;
advance_and_error: advance_and_error:
/* /*
...@@ -781,8 +731,6 @@ advance_and_error: ...@@ -781,8 +731,6 @@ advance_and_error:
*/ */
conn->inStart = conn->inEnd; conn->inStart = conn->inEnd;
set_error_result:
/* /*
* Replace partially constructed result with an error result. First * Replace partially constructed result with an error result. First
* discard the old result to try to win back some memory. * discard the old result to try to win back some memory.
...@@ -790,7 +738,7 @@ set_error_result: ...@@ -790,7 +738,7 @@ set_error_result:
pqClearAsyncResult(conn); pqClearAsyncResult(conn);
/* /*
* If row processor didn't provide an error message, assume "out of * If preceding code didn't provide an error message, assume "out of
* memory" was meant. The advantage of having this special case is that * memory" was meant. The advantage of having this special case is that
* freeing the old result first greatly improves the odds that gettext() * freeing the old result first greatly improves the odds that gettext()
* will succeed in providing a translation. * will succeed in providing a translation.
...@@ -937,31 +885,15 @@ getAnotherTuple(PGconn *conn, bool binary) ...@@ -937,31 +885,15 @@ getAnotherTuple(PGconn *conn, bool binary)
free(bitmap); free(bitmap);
bitmap = NULL; bitmap = NULL;
/* /* Advance inStart to show that the "D" message has been processed. */
* Advance inStart to show that the "D" message has been processed. We
* must do this before calling the row processor, in case it longjmps.
*/
conn->inStart = conn->inCursor; conn->inStart = conn->inCursor;
/* Pass the completed row values to rowProcessor */ /* Process the collected row */
errmsg = NULL; errmsg = NULL;
switch ((*conn->rowProcessor) (result, rowbuf, &errmsg, if (pqRowProcessor(conn, &errmsg))
conn->rowProcessorParam)) return 0; /* normal, successful exit */
{
case 1:
/* everything is good */
return 0;
case -1: goto set_error_result; /* pqRowProcessor failed, report it */
/* error, report the errmsg below */
break;
default:
/* unrecognized return code */
errmsg = libpq_gettext("unrecognized return value from row processor");
break;
}
goto set_error_result;
advance_and_error: advance_and_error:
...@@ -981,7 +913,7 @@ set_error_result: ...@@ -981,7 +913,7 @@ set_error_result:
pqClearAsyncResult(conn); pqClearAsyncResult(conn);
/* /*
* If row processor didn't provide an error message, assume "out of * If preceding code didn't provide an error message, assume "out of
* memory" was meant. The advantage of having this special case is that * memory" was meant. The advantage of having this special case is that
* freeing the old result first greatly improves the odds that gettext() * freeing the old result first greatly improves the odds that gettext()
* will succeed in providing a translation. * will succeed in providing a translation.
......
...@@ -61,9 +61,6 @@ static int build_startup_packet(const PGconn *conn, char *packet, ...@@ -61,9 +61,6 @@ static int build_startup_packet(const PGconn *conn, char *packet,
* parseInput: if appropriate, parse input data from backend * parseInput: if appropriate, parse input data from backend
* until input is exhausted or a stopping state is reached. * until input is exhausted or a stopping state is reached.
* Note that this function will NOT attempt to read more data from the backend. * Note that this function will NOT attempt to read more data from the backend.
*
* Note: callers of parseInput must be prepared for a longjmp exit when we are
* in PGASYNC_BUSY state, since an external row processor might do that.
*/ */
void void
pqParseInput3(PGconn *conn) pqParseInput3(PGconn *conn)
...@@ -446,10 +443,6 @@ handleSyncLoss(PGconn *conn, char id, int msgLength) ...@@ -446,10 +443,6 @@ handleSyncLoss(PGconn *conn, char id, int msgLength)
* Returns: 0 if processed message successfully, EOF to suspend parsing * Returns: 0 if processed message successfully, EOF to suspend parsing
* (the latter case is not actually used currently). * (the latter case is not actually used currently).
* In either case, conn->inStart has been advanced past the message. * In either case, conn->inStart has been advanced past the message.
*
* Note: the row processor could also choose to longjmp out of libpq,
* in which case the library's state must allow for resumption at the
* next message.
*/ */
static int static int
getRowDescriptions(PGconn *conn, int msgLength) getRowDescriptions(PGconn *conn, int msgLength)
...@@ -564,10 +557,7 @@ getRowDescriptions(PGconn *conn, int msgLength) ...@@ -564,10 +557,7 @@ getRowDescriptions(PGconn *conn, int msgLength)
/* Success! */ /* Success! */
conn->result = result; conn->result = result;
/* /* Advance inStart to show that the "T" message has been processed. */
* Advance inStart to show that the "T" message has been processed. We
* must do this before calling the row processor, in case it longjmps.
*/
conn->inStart = conn->inCursor; conn->inStart = conn->inCursor;
/* /*
...@@ -580,25 +570,13 @@ getRowDescriptions(PGconn *conn, int msgLength) ...@@ -580,25 +570,13 @@ getRowDescriptions(PGconn *conn, int msgLength)
return 0; return 0;
} }
/* Give the row processor a chance to initialize for new result set */ /*
errmsg = NULL; * We could perform additional setup for the new result set here, but for
switch ((*conn->rowProcessor) (result, NULL, &errmsg, * now there's nothing else to do.
conn->rowProcessorParam)) */
{
case 1:
/* everything is good */
return 0;
case -1:
/* error, report the errmsg below */
break;
default: /* And we're done. */
/* unrecognized return code */ return 0;
errmsg = libpq_gettext("unrecognized return value from row processor");
break;
}
goto set_error_result;
advance_and_error: advance_and_error:
/* Discard unsaved result, if any */ /* Discard unsaved result, if any */
...@@ -608,8 +586,6 @@ advance_and_error: ...@@ -608,8 +586,6 @@ advance_and_error:
/* Discard the failed message by pretending we read it */ /* Discard the failed message by pretending we read it */
conn->inStart += 5 + msgLength; conn->inStart += 5 + msgLength;
set_error_result:
/* /*
* Replace partially constructed result with an error result. First * Replace partially constructed result with an error result. First
* discard the old result to try to win back some memory. * discard the old result to try to win back some memory.
...@@ -617,8 +593,10 @@ set_error_result: ...@@ -617,8 +593,10 @@ set_error_result:
pqClearAsyncResult(conn); pqClearAsyncResult(conn);
/* /*
* If row processor didn't provide an error message, assume "out of * If preceding code didn't provide an error message, assume "out of
* memory" was meant. * memory" was meant. The advantage of having this special case is that
* freeing the old result first greatly improves the odds that gettext()
* will succeed in providing a translation.
*/ */
if (!errmsg) if (!errmsg)
errmsg = libpq_gettext("out of memory for query result"); errmsg = libpq_gettext("out of memory for query result");
...@@ -695,10 +673,6 @@ failure: ...@@ -695,10 +673,6 @@ failure:
* Returns: 0 if processed message successfully, EOF to suspend parsing * Returns: 0 if processed message successfully, EOF to suspend parsing
* (the latter case is not actually used currently). * (the latter case is not actually used currently).
* In either case, conn->inStart has been advanced past the message. * In either case, conn->inStart has been advanced past the message.
*
* Note: the row processor could also choose to longjmp out of libpq,
* in which case the library's state must allow for resumption at the
* next message.
*/ */
static int static int
getAnotherTuple(PGconn *conn, int msgLength) getAnotherTuple(PGconn *conn, int msgLength)
...@@ -778,31 +752,15 @@ getAnotherTuple(PGconn *conn, int msgLength) ...@@ -778,31 +752,15 @@ getAnotherTuple(PGconn *conn, int msgLength)
goto advance_and_error; goto advance_and_error;
} }
/* /* Advance inStart to show that the "D" message has been processed. */
* Advance inStart to show that the "D" message has been processed. We
* must do this before calling the row processor, in case it longjmps.
*/
conn->inStart = conn->inCursor; conn->inStart = conn->inCursor;
/* Pass the completed row values to rowProcessor */ /* Process the collected row */
errmsg = NULL; errmsg = NULL;
switch ((*conn->rowProcessor) (result, rowbuf, &errmsg, if (pqRowProcessor(conn, &errmsg))
conn->rowProcessorParam)) return 0; /* normal, successful exit */
{
case 1:
/* everything is good */
return 0;
case -1:
/* error, report the errmsg below */
break;
default: goto set_error_result; /* pqRowProcessor failed, report it */
/* unrecognized return code */
errmsg = libpq_gettext("unrecognized return value from row processor");
break;
}
goto set_error_result;
advance_and_error: advance_and_error:
/* Discard the failed message by pretending we read it */ /* Discard the failed message by pretending we read it */
...@@ -817,7 +775,7 @@ set_error_result: ...@@ -817,7 +775,7 @@ set_error_result:
pqClearAsyncResult(conn); pqClearAsyncResult(conn);
/* /*
* If row processor didn't provide an error message, assume "out of * If preceding code didn't provide an error message, assume "out of
* memory" was meant. The advantage of having this special case is that * memory" was meant. The advantage of having this special case is that
* freeing the old result first greatly improves the odds that gettext() * freeing the old result first greatly improves the odds that gettext()
* will succeed in providing a translation. * will succeed in providing a translation.
......
...@@ -90,7 +90,8 @@ typedef enum ...@@ -90,7 +90,8 @@ typedef enum
* backend */ * backend */
PGRES_NONFATAL_ERROR, /* notice or warning message */ PGRES_NONFATAL_ERROR, /* notice or warning message */
PGRES_FATAL_ERROR, /* query failed */ PGRES_FATAL_ERROR, /* query failed */
PGRES_COPY_BOTH /* Copy In/Out data transfer in progress */ PGRES_COPY_BOTH, /* Copy In/Out data transfer in progress */
PGRES_SINGLE_TUPLE /* single tuple from larger resultset */
} ExecStatusType; } ExecStatusType;
typedef enum typedef enum
...@@ -129,17 +130,6 @@ typedef struct pg_conn PGconn; ...@@ -129,17 +130,6 @@ typedef struct pg_conn PGconn;
*/ */
typedef struct pg_result PGresult; typedef struct pg_result PGresult;
/* PGdataValue represents a data field value being passed to a row processor.
* It could be either text or binary data; text data is not zero-terminated.
* A SQL NULL is represented by len < 0; then value is still valid but there
* are no data bytes there.
*/
typedef struct pgDataValue
{
int len; /* data length in bytes, or <0 if NULL */
const char *value; /* data value, without zero-termination */
} PGdataValue;
/* PGcancel encapsulates the information needed to cancel a running /* PGcancel encapsulates the information needed to cancel a running
* query on an existing connection. * query on an existing connection.
* The contents of this struct are not supposed to be known to applications. * The contents of this struct are not supposed to be known to applications.
...@@ -161,10 +151,6 @@ typedef struct pgNotify ...@@ -161,10 +151,6 @@ typedef struct pgNotify
struct pgNotify *next; /* list link */ struct pgNotify *next; /* list link */
} PGnotify; } PGnotify;
/* Function type for row-processor callback */
typedef int (*PQrowProcessor) (PGresult *res, const PGdataValue *columns,
const char **errmsgp, void *param);
/* Function types for notice-handling callbacks */ /* Function types for notice-handling callbacks */
typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res); typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res);
typedef void (*PQnoticeProcessor) (void *arg, const char *message); typedef void (*PQnoticeProcessor) (void *arg, const char *message);
...@@ -403,17 +389,13 @@ extern int PQsendQueryPrepared(PGconn *conn, ...@@ -403,17 +389,13 @@ extern int PQsendQueryPrepared(PGconn *conn,
const int *paramLengths, const int *paramLengths,
const int *paramFormats, const int *paramFormats,
int resultFormat); int resultFormat);
extern int PQsetSingleRowMode(PGconn *conn);
extern PGresult *PQgetResult(PGconn *conn); extern PGresult *PQgetResult(PGconn *conn);
extern PGresult *PQskipResult(PGconn *conn);
/* Routines for managing an asynchronous query */ /* Routines for managing an asynchronous query */
extern int PQisBusy(PGconn *conn); extern int PQisBusy(PGconn *conn);
extern int PQconsumeInput(PGconn *conn); extern int PQconsumeInput(PGconn *conn);
/* Override default per-row processing */
extern void PQsetRowProcessor(PGconn *conn, PQrowProcessor func, void *param);
extern PQrowProcessor PQgetRowProcessor(const PGconn *conn, void **param);
/* LISTEN/NOTIFY support */ /* LISTEN/NOTIFY support */
extern PGnotify *PQnotifies(PGconn *conn); extern PGnotify *PQnotifies(PGconn *conn);
......
...@@ -277,6 +277,17 @@ typedef struct pgLobjfuncs ...@@ -277,6 +277,17 @@ typedef struct pgLobjfuncs
Oid fn_lo_write; /* OID of backend function LOwrite */ Oid fn_lo_write; /* OID of backend function LOwrite */
} PGlobjfuncs; } PGlobjfuncs;
/* PGdataValue represents a data field value being passed to a row processor.
* It could be either text or binary data; text data is not zero-terminated.
* A SQL NULL is represented by len < 0; then value is still valid but there
* are no data bytes there.
*/
typedef struct pgDataValue
{
int len; /* data length in bytes, or <0 if NULL */
const char *value; /* data value, without zero-termination */
} PGdataValue;
/* /*
* PGconn stores all the state data associated with a single connection * PGconn stores all the state data associated with a single connection
* to a backend. * to a backend.
...@@ -324,10 +335,6 @@ struct pg_conn ...@@ -324,10 +335,6 @@ struct pg_conn
/* Optional file to write trace info to */ /* Optional file to write trace info to */
FILE *Pfdebug; FILE *Pfdebug;
/* Callback procedure for per-row processing */
PQrowProcessor rowProcessor; /* function pointer */
void *rowProcessorParam; /* passthrough argument */
/* Callback procedures for notice message processing */ /* Callback procedures for notice message processing */
PGNoticeHooks noticeHooks; PGNoticeHooks noticeHooks;
...@@ -346,6 +353,7 @@ struct pg_conn ...@@ -346,6 +353,7 @@ struct pg_conn
bool options_valid; /* true if OK to attempt connection */ bool options_valid; /* true if OK to attempt connection */
bool nonblocking; /* whether this connection is using nonblock bool nonblocking; /* whether this connection is using nonblock
* sending semantics */ * sending semantics */
bool singleRowMode; /* return current query result row-by-row? */
char copy_is_binary; /* 1 = copy binary, 0 = copy text */ char copy_is_binary; /* 1 = copy binary, 0 = copy text */
int copy_already_done; /* # bytes already returned in COPY int copy_already_done; /* # bytes already returned in COPY
* OUT */ * OUT */
...@@ -406,6 +414,7 @@ struct pg_conn ...@@ -406,6 +414,7 @@ struct pg_conn
/* Status for asynchronous result construction */ /* Status for asynchronous result construction */
PGresult *result; /* result being constructed */ PGresult *result; /* result being constructed */
PGresult *next_result; /* next result (used in single-row mode) */
/* Assorted state for SSL, GSS, etc */ /* Assorted state for SSL, GSS, etc */
...@@ -517,6 +526,7 @@ extern void pqSaveMessageField(PGresult *res, char code, ...@@ -517,6 +526,7 @@ extern void pqSaveMessageField(PGresult *res, char code,
const char *value); const char *value);
extern void pqSaveParameterStatus(PGconn *conn, const char *name, extern void pqSaveParameterStatus(PGconn *conn, const char *name,
const char *value); const char *value);
extern int pqRowProcessor(PGconn *conn, const char **errmsgp);
extern void pqHandleSendFailure(PGconn *conn); extern void pqHandleSendFailure(PGconn *conn);
/* === in fe-protocol2.c === */ /* === in fe-protocol2.c === */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment