aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/interfaces/libpq/exports.txt4
-rw-r--r--src/interfaces/libpq/fe-connect.c11
-rw-r--r--src/interfaces/libpq/fe-exec.c219
-rw-r--r--src/interfaces/libpq/fe-lobj.c11
-rw-r--r--src/interfaces/libpq/fe-protocol2.c116
-rw-r--r--src/interfaces/libpq/fe-protocol3.c76
-rw-r--r--src/interfaces/libpq/libpq-fe.h24
-rw-r--r--src/interfaces/libpq/libpq-int.h18
8 files changed, 166 insertions, 313 deletions
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 1251455f1f6..9d95e262be3 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -160,6 +160,4 @@ PQconnectStartParams 157
PQping 158
PQpingParams 159
PQlibVersion 160
-PQsetRowProcessor 161
-PQgetRowProcessor 162
-PQskipResult 163
+PQsetSingleRowMode 161
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a32258a8cba..adaab7aaade 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2709,8 +2709,7 @@ makeEmptyPGconn(void)
/* Zero all pointers and booleans */
MemSet(conn, 0, sizeof(PGconn));
- /* install default row processor and notice hooks */
- PQsetRowProcessor(conn, NULL, NULL);
+ /* install default notice hooks */
conn->noticeHooks.noticeRec = defaultNoticeReceiver;
conn->noticeHooks.noticeProc = defaultNoticeProcessor;
@@ -4658,7 +4657,7 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
if (p == host)
{
printfPQExpBuffer(errorMessage,
- libpq_gettext("IPv6 host address may not be empty in URI: \"%s\"\n"),
+ libpq_gettext("IPv6 host address may not be empty in URI: \"%s\"\n"),
uri);
goto cleanup;
}
@@ -4878,7 +4877,7 @@ conninfo_uri_parse_params(char *params,
printfPQExpBuffer(errorMessage,
libpq_gettext(
- "invalid URI query parameter: \"%s\"\n"),
+ "invalid URI query parameter: \"%s\"\n"),
keyword);
return false;
}
@@ -4943,7 +4942,7 @@ conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
if (!(get_hexdigit(*q++, &hi) && get_hexdigit(*q++, &lo)))
{
printfPQExpBuffer(errorMessage,
- libpq_gettext("invalid percent-encoded token: \"%s\"\n"),
+ libpq_gettext("invalid percent-encoded token: \"%s\"\n"),
str);
free(buf);
return NULL;
@@ -5594,8 +5593,8 @@ static void
dot_pg_pass_warning(PGconn *conn)
{
/* If it was 'invalid authorization', add .pgpass mention */
- if (conn->dot_pgpass_used && conn->password_needed && conn->result &&
/* only works with >= 9.0 servers */
+ if (conn->dot_pgpass_used && conn->password_needed && conn->result &&
strcmp(PQresultErrorField(conn->result, PG_DIAG_SQLSTATE),
ERRCODE_INVALID_PASSWORD) == 0)
{
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index badc0b32a8e..53516db7234 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -38,7 +38,8 @@ char *const pgresStatus[] = {
"PGRES_BAD_RESPONSE",
"PGRES_NONFATAL_ERROR",
"PGRES_FATAL_ERROR",
- "PGRES_COPY_BOTH"
+ "PGRES_COPY_BOTH",
+ "PGRES_SINGLE_TUPLE"
};
/*
@@ -51,8 +52,6 @@ static bool static_std_strings = false;
static PGEvent *dupEvents(PGEvent *events, int count);
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 int PQsendQueryGuts(PGconn *conn,
const char *command,
@@ -64,8 +63,6 @@ static int PQsendQueryGuts(PGconn *conn,
const int *paramFormats,
int resultFormat);
static void parseInput(PGconn *conn);
-static int dummyRowProcessor(PGresult *res, const PGdataValue *columns,
- const char **errmsgp, void *param);
static bool PQexecStart(PGconn *conn);
static PGresult *PQexecFinish(PGconn *conn);
static int PQsendDescribe(PGconn *conn, char desc_type,
@@ -181,6 +178,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
case PGRES_COPY_OUT:
case PGRES_COPY_IN:
case PGRES_COPY_BOTH:
+ case PGRES_SINGLE_TUPLE:
/* non-error cases */
break;
default:
@@ -698,6 +696,8 @@ PQclear(PGresult *res)
/*
* Handy subroutine to deallocate any partially constructed async result.
+ *
+ * Any "next" result gets cleared too.
*/
void
pqClearAsyncResult(PGconn *conn)
@@ -705,6 +705,9 @@ pqClearAsyncResult(PGconn *conn)
if (conn->result)
PQclear(conn->result);
conn->result = NULL;
+ if (conn->next_result)
+ PQclear(conn->next_result);
+ conn->next_result = NULL;
}
/*
@@ -758,7 +761,6 @@ pqPrepareAsyncResult(PGconn *conn)
* conn->errorMessage.
*/
res = conn->result;
- conn->result = NULL; /* handing over ownership to caller */
if (!res)
res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
else
@@ -771,6 +773,16 @@ pqPrepareAsyncResult(PGconn *conn)
appendPQExpBufferStr(&conn->errorMessage,
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;
}
@@ -981,85 +993,55 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
/*
- * PQsetRowProcessor
- * Set function that copies row data out from the network buffer,
- * along with a passthrough parameter for it.
- */
-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.
+ * pqRowProcessor
+ * Add the received row to the current async result (conn->result).
+ * Returns 1 if OK, 0 if error occurred.
+ *
+ * On error, *errmsgp can be set to an error string to be returned.
+ * If it is left NULL, the error is presumed to be "out of memory".
*
- * Note: "param" should point to the PGconn, but we don't actually need that
- * as of the current coding.
+ * 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
-pqStdRowProcessor(PGresult *res, const PGdataValue *columns,
- const char **errmsgp, void *param)
+int
+pqRowProcessor(PGconn *conn, const char **errmsgp)
{
+ PGresult *res = conn->result;
int nfields = res->numAttributes;
+ const PGdataValue *columns = conn->rowBuf;
PGresAttValue *tup;
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. */
- return 1;
+ /* Copy everything that should be in the result at this point */
+ 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
* copy the data over.
*
- * Note: on malloc failure, we return -1 leaving *errmsgp still NULL,
- * which caller will take to mean "out of memory". This is preferable to
- * trying to set up such a message here, because evidently there's not
- * enough memory for gettext() to do anything.
+ * Note: on malloc failure, we return 0 leaving *errmsgp still NULL, which
+ * caller will take to mean "out of memory". This is preferable to trying
+ * to set up such a message here, because evidently there's not enough
+ * memory for gettext() to do anything.
*/
tup = (PGresAttValue *)
pqResultAlloc(res, nfields * sizeof(PGresAttValue), TRUE);
if (tup == NULL)
- return -1;
+ goto fail;
for (i = 0; i < nfields; i++)
{
@@ -1078,7 +1060,7 @@ pqStdRowProcessor(PGresult *res, const PGdataValue *columns,
val = (char *) pqResultAlloc(res, clen + 1, isbinary);
if (val == NULL)
- return -1;
+ goto fail;
/* copy and zero-terminate the data (even if it's binary) */
memcpy(val, columns[i].value, clen);
@@ -1091,10 +1073,30 @@ pqStdRowProcessor(PGresult *res, const PGdataValue *columns,
/* And add the tuple to the PGresult's tuple array */
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;
+
+fail:
+ /* release locally allocated PGresult, if we made one */
+ if (res != conn->result)
+ PQclear(res);
+ return 0;
}
@@ -1343,6 +1345,10 @@ PQsendQueryStart(PGconn *conn)
/* initialize async result-accumulation state */
conn->result = NULL;
+ conn->next_result = NULL;
+
+ /* reset single-row processing mode */
+ conn->singleRowMode = false;
/* ready to send command message */
return true;
@@ -1548,6 +1554,31 @@ pqHandleSendFailure(PGconn *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
* 0 return: some kind of trouble
* 1 return: no problem
@@ -1587,9 +1618,6 @@ PQconsumeInput(PGconn *conn)
* parseInput: if appropriate, parse input data from backend
* 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: 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
parseInput(PGconn *conn)
@@ -1737,49 +1765,6 @@ PQgetResult(PGconn *conn)
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
@@ -1886,7 +1871,7 @@ PQexecStart(PGconn *conn)
* Silently discard any prior query result that application didn't eat.
* 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;
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 13fd98c2f91..f3a6d0341c1 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -682,8 +682,6 @@ lo_initialize(PGconn *conn)
int n;
const char *query;
const char *fname;
- PQrowProcessor savedRowProcessor;
- void *savedRowProcessorParam;
Oid foid;
if (!conn)
@@ -732,16 +730,7 @@ lo_initialize(PGconn *conn)
"or proname = 'loread' "
"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);
-
- conn->rowProcessor = savedRowProcessor;
- conn->rowProcessorParam = savedRowProcessorParam;
-
if (res == NULL)
{
free(lobjfuncs);
diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c
index 8dbd6b69823..1ba5885cd3b 100644
--- a/src/interfaces/libpq/fe-protocol2.c
+++ b/src/interfaces/libpq/fe-protocol2.c
@@ -49,19 +49,11 @@ static int getNotify(PGconn *conn);
PostgresPollingStatusType
pqSetenvPoll(PGconn *conn)
{
- PostgresPollingStatusType result;
PGresult *res;
- PQrowProcessor savedRowProcessor;
- void *savedRowProcessorParam;
if (conn == NULL || conn->status == CONNECTION_BAD)
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 */
switch (conn->setenv_state)
{
@@ -77,10 +69,7 @@ pqSetenvPoll(PGconn *conn)
if (n < 0)
goto error_return;
if (n == 0)
- {
- result = PGRES_POLLING_READING;
- goto normal_return;
- }
+ return PGRES_POLLING_READING;
break;
}
@@ -94,8 +83,7 @@ pqSetenvPoll(PGconn *conn)
/* Should we raise an error if called when not active? */
case SETENV_STATE_IDLE:
- result = PGRES_POLLING_OK;
- goto normal_return;
+ return PGRES_POLLING_OK;
default:
printfPQExpBuffer(&conn->errorMessage,
@@ -192,10 +180,7 @@ pqSetenvPoll(PGconn *conn)
case SETENV_STATE_CLIENT_ENCODING_WAIT:
{
if (PQisBusy(conn))
- {
- result = PGRES_POLLING_READING;
- goto normal_return;
- }
+ return PGRES_POLLING_READING;
res = PQgetResult(conn);
@@ -220,10 +205,7 @@ pqSetenvPoll(PGconn *conn)
case SETENV_STATE_OPTION_WAIT:
{
if (PQisBusy(conn))
- {
- result = PGRES_POLLING_READING;
- goto normal_return;
- }
+ return PGRES_POLLING_READING;
res = PQgetResult(conn);
@@ -262,17 +244,13 @@ pqSetenvPoll(PGconn *conn)
goto error_return;
conn->setenv_state = SETENV_STATE_QUERY1_WAIT;
- result = PGRES_POLLING_READING;
- goto normal_return;
+ return PGRES_POLLING_READING;
}
case SETENV_STATE_QUERY1_WAIT:
{
if (PQisBusy(conn))
- {
- result = PGRES_POLLING_READING;
- goto normal_return;
- }
+ return PGRES_POLLING_READING;
res = PQgetResult(conn);
@@ -349,17 +327,13 @@ pqSetenvPoll(PGconn *conn)
goto error_return;
conn->setenv_state = SETENV_STATE_QUERY2_WAIT;
- result = PGRES_POLLING_READING;
- goto normal_return;
+ return PGRES_POLLING_READING;
}
case SETENV_STATE_QUERY2_WAIT:
{
if (PQisBusy(conn))
- {
- result = PGRES_POLLING_READING;
- goto normal_return;
- }
+ return PGRES_POLLING_READING;
res = PQgetResult(conn);
@@ -406,8 +380,7 @@ pqSetenvPoll(PGconn *conn)
{
/* Query finished, so we're done */
conn->setenv_state = SETENV_STATE_IDLE;
- result = PGRES_POLLING_OK;
- goto normal_return;
+ return PGRES_POLLING_OK;
}
break;
}
@@ -425,12 +398,7 @@ pqSetenvPoll(PGconn *conn)
error_return:
conn->setenv_state = SETENV_STATE_IDLE;
- result = PGRES_POLLING_FAILED;
-
-normal_return:
- conn->rowProcessor = savedRowProcessor;
- conn->rowProcessorParam = savedRowProcessorParam;
- return result;
+ return PGRES_POLLING_FAILED;
}
@@ -438,9 +406,6 @@ normal_return:
* parseInput: if appropriate, parse input data from backend
* 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: 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
pqParseInput2(PGconn *conn)
@@ -746,31 +711,16 @@ getRowDescriptions(PGconn *conn)
/* Success! */
conn->result = result;
- /*
- * Advance inStart to show that the "T" message has been processed. We
- * must do this before calling the row processor, in case it longjmps.
- */
+ /* Advance inStart to show that the "T" message has been processed. */
conn->inStart = conn->inCursor;
- /* Give the row processor a chance to initialize for new result set */
- errmsg = NULL;
- switch ((*conn->rowProcessor) (result, NULL, &errmsg,
- conn->rowProcessorParam))
- {
- case 1:
- /* everything is good */
- return 0;
-
- case -1:
- /* error, report the errmsg below */
- break;
+ /*
+ * We could perform additional setup for the new result set here, but for
+ * now there's nothing else to do.
+ */
- default:
- /* unrecognized return code */
- errmsg = libpq_gettext("unrecognized return value from row processor");
- break;
- }
- goto set_error_result;
+ /* And we're done. */
+ return 0;
advance_and_error:
@@ -781,8 +731,6 @@ advance_and_error:
*/
conn->inStart = conn->inEnd;
-set_error_result:
-
/*
* Replace partially constructed result with an error result. First
* discard the old result to try to win back some memory.
@@ -790,7 +738,7 @@ set_error_result:
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
* freeing the old result first greatly improves the odds that gettext()
* will succeed in providing a translation.
@@ -937,31 +885,15 @@ getAnotherTuple(PGconn *conn, bool binary)
free(bitmap);
bitmap = NULL;
- /*
- * Advance inStart to show that the "D" message has been processed. We
- * must do this before calling the row processor, in case it longjmps.
- */
+ /* Advance inStart to show that the "D" message has been processed. */
conn->inStart = conn->inCursor;
- /* Pass the completed row values to rowProcessor */
+ /* Process the collected row */
errmsg = NULL;
- switch ((*conn->rowProcessor) (result, rowbuf, &errmsg,
- conn->rowProcessorParam))
- {
- case 1:
- /* everything is good */
- return 0;
+ if (pqRowProcessor(conn, &errmsg))
+ return 0; /* normal, successful exit */
- 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;
+ goto set_error_result; /* pqRowProcessor failed, report it */
advance_and_error:
@@ -981,7 +913,7 @@ set_error_result:
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
* freeing the old result first greatly improves the odds that gettext()
* will succeed in providing a translation.
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 173af2e0a79..d289f82285f 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -61,9 +61,6 @@ static int build_startup_packet(const PGconn *conn, char *packet,
* parseInput: if appropriate, parse input data from backend
* 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: 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
pqParseInput3(PGconn *conn)
@@ -446,10 +443,6 @@ handleSyncLoss(PGconn *conn, char id, int msgLength)
* Returns: 0 if processed message successfully, EOF to suspend parsing
* (the latter case is not actually used currently).
* 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
getRowDescriptions(PGconn *conn, int msgLength)
@@ -564,10 +557,7 @@ getRowDescriptions(PGconn *conn, int msgLength)
/* Success! */
conn->result = result;
- /*
- * Advance inStart to show that the "T" message has been processed. We
- * must do this before calling the row processor, in case it longjmps.
- */
+ /* Advance inStart to show that the "T" message has been processed. */
conn->inStart = conn->inCursor;
/*
@@ -580,25 +570,13 @@ getRowDescriptions(PGconn *conn, int msgLength)
return 0;
}
- /* Give the row processor a chance to initialize for new result set */
- errmsg = NULL;
- switch ((*conn->rowProcessor) (result, NULL, &errmsg,
- conn->rowProcessorParam))
- {
- case 1:
- /* everything is good */
- return 0;
-
- case -1:
- /* error, report the errmsg below */
- break;
+ /*
+ * We could perform additional setup for the new result set here, but for
+ * now there's nothing else to do.
+ */
- default:
- /* unrecognized return code */
- errmsg = libpq_gettext("unrecognized return value from row processor");
- break;
- }
- goto set_error_result;
+ /* And we're done. */
+ return 0;
advance_and_error:
/* Discard unsaved result, if any */
@@ -608,8 +586,6 @@ advance_and_error:
/* Discard the failed message by pretending we read it */
conn->inStart += 5 + msgLength;
-set_error_result:
-
/*
* Replace partially constructed result with an error result. First
* discard the old result to try to win back some memory.
@@ -617,8 +593,10 @@ set_error_result:
pqClearAsyncResult(conn);
/*
- * If row processor didn't provide an error message, assume "out of
- * memory" was meant.
+ * If preceding code didn't provide an error message, assume "out of
+ * 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)
errmsg = libpq_gettext("out of memory for query result");
@@ -695,10 +673,6 @@ failure:
* Returns: 0 if processed message successfully, EOF to suspend parsing
* (the latter case is not actually used currently).
* 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
getAnotherTuple(PGconn *conn, int msgLength)
@@ -778,31 +752,15 @@ getAnotherTuple(PGconn *conn, int msgLength)
goto advance_and_error;
}
- /*
- * Advance inStart to show that the "D" message has been processed. We
- * must do this before calling the row processor, in case it longjmps.
- */
+ /* Advance inStart to show that the "D" message has been processed. */
conn->inStart = conn->inCursor;
- /* Pass the completed row values to rowProcessor */
+ /* Process the collected row */
errmsg = NULL;
- switch ((*conn->rowProcessor) (result, rowbuf, &errmsg,
- conn->rowProcessorParam))
- {
- case 1:
- /* everything is good */
- return 0;
-
- case -1:
- /* error, report the errmsg below */
- break;
+ if (pqRowProcessor(conn, &errmsg))
+ return 0; /* normal, successful exit */
- default:
- /* unrecognized return code */
- errmsg = libpq_gettext("unrecognized return value from row processor");
- break;
- }
- goto set_error_result;
+ goto set_error_result; /* pqRowProcessor failed, report it */
advance_and_error:
/* Discard the failed message by pretending we read it */
@@ -817,7 +775,7 @@ set_error_result:
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
* freeing the old result first greatly improves the odds that gettext()
* will succeed in providing a translation.
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 67db6119bba..9d05dd20605 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -90,7 +90,8 @@ typedef enum
* backend */
PGRES_NONFATAL_ERROR, /* notice or warning message */
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;
typedef enum
@@ -129,17 +130,6 @@ typedef struct pg_conn PGconn;
*/
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
* query on an existing connection.
* The contents of this struct are not supposed to be known to applications.
@@ -161,10 +151,6 @@ typedef struct pgNotify
struct pgNotify *next; /* list link */
} 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 */
typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res);
typedef void (*PQnoticeProcessor) (void *arg, const char *message);
@@ -403,17 +389,13 @@ extern int PQsendQueryPrepared(PGconn *conn,
const int *paramLengths,
const int *paramFormats,
int resultFormat);
+extern int PQsetSingleRowMode(PGconn *conn);
extern PGresult *PQgetResult(PGconn *conn);
-extern PGresult *PQskipResult(PGconn *conn);
/* Routines for managing an asynchronous query */
extern int PQisBusy(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 */
extern PGnotify *PQnotifies(PGconn *conn);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4bc89269fab..2bac59c3d87 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -277,6 +277,17 @@ typedef struct pgLobjfuncs
Oid fn_lo_write; /* OID of backend function LOwrite */
} 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
* to a backend.
@@ -324,10 +335,6 @@ struct pg_conn
/* Optional file to write trace info to */
FILE *Pfdebug;
- /* Callback procedure for per-row processing */
- PQrowProcessor rowProcessor; /* function pointer */
- void *rowProcessorParam; /* passthrough argument */
-
/* Callback procedures for notice message processing */
PGNoticeHooks noticeHooks;
@@ -346,6 +353,7 @@ struct pg_conn
bool options_valid; /* true if OK to attempt connection */
bool nonblocking; /* whether this connection is using nonblock
* sending semantics */
+ bool singleRowMode; /* return current query result row-by-row? */
char copy_is_binary; /* 1 = copy binary, 0 = copy text */
int copy_already_done; /* # bytes already returned in COPY
* OUT */
@@ -406,6 +414,7 @@ struct pg_conn
/* Status for asynchronous result construction */
PGresult *result; /* result being constructed */
+ PGresult *next_result; /* next result (used in single-row mode) */
/* Assorted state for SSL, GSS, etc */
@@ -517,6 +526,7 @@ extern void pqSaveMessageField(PGresult *res, char code,
const char *value);
extern void pqSaveParameterStatus(PGconn *conn, const char *name,
const char *value);
+extern int pqRowProcessor(PGconn *conn, const char **errmsgp);
extern void pqHandleSendFailure(PGconn *conn);
/* === in fe-protocol2.c === */