diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/interfaces/libpq/exports.txt | 4 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-connect.c | 11 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-exec.c | 219 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-lobj.c | 11 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-protocol2.c | 116 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-protocol3.c | 76 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-fe.h | 24 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-int.h | 18 |
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 === */ |