diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index 1e62d8091a9d2bdf60af6745d5a01ee14ee5cf5a..a67a836eba1fc5e3ea14701284b831269fbc3357 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -70,6 +70,9 @@ typedef struct storeInfo AttInMetadata *attinmeta; MemoryContext tmpcontext; char **cstrs; + /* temp storage for results to avoid leaks on exception */ + PGresult *last_res; + PGresult *cur_res; } storeInfo; /* @@ -83,8 +86,8 @@ static void materializeQueryResult(FunctionCallInfo fcinfo, const char *conname, const char *sql, bool fail); -static int storeHandler(PGresult *res, const PGdataValue *columns, - const char **errmsgp, void *param); +static PGresult *storeQueryResult(storeInfo *sinfo, PGconn *conn, const char *sql); +static void storeRow(storeInfo *sinfo, PGresult *res, bool first); static remoteConn *getConnectionByName(const char *name); static HTAB *createConnHash(void); static void createNewConnection(const char *name, remoteConn *rconn); @@ -630,7 +633,7 @@ dblink_send_query(PG_FUNCTION_ARGS) /* async query send */ retval = PQsendQuery(conn, sql); if (retval != 1) - elog(NOTICE, "%s", PQerrorMessage(conn)); + elog(NOTICE, "could not send query: %s", PQerrorMessage(conn)); PG_RETURN_INT32(retval); } @@ -927,8 +930,10 @@ materializeResult(FunctionCallInfo fcinfo, PGresult *res) /* * Execute the given SQL command and store its results into a tuplestore * to be returned as the result of the current function. + * * 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 materializeQueryResult(FunctionCallInfo fcinfo, @@ -944,19 +949,14 @@ materializeQueryResult(FunctionCallInfo fcinfo, /* prepTuplestoreResult must have been called previously */ Assert(rsinfo->returnMode == SFRM_Materialize); + /* initialize storeInfo to empty */ + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.fcinfo = fcinfo; + PG_TRY(); { - /* initialize storeInfo to empty */ - memset(&sinfo, 0, sizeof(sinfo)); - sinfo.fcinfo = fcinfo; - - /* We'll collect tuples using storeHandler */ - PQsetRowProcessor(conn, storeHandler, &sinfo); - - res = PQexec(conn, sql); - - /* We don't keep the custom row processor installed permanently */ - PQsetRowProcessor(conn, NULL, NULL); + /* execute query, collecting any tuples into the tuplestore */ + res = storeQueryResult(&sinfo, conn, sql); if (!res || (PQresultStatus(res) != PGRES_COMMAND_OK && @@ -975,8 +975,8 @@ materializeQueryResult(FunctionCallInfo fcinfo, else if (PQresultStatus(res) == PGRES_COMMAND_OK) { /* - * storeHandler didn't get called, so we need to convert the - * command status string to a tuple manually + * storeRow didn't get called, so we need to convert the command + * status string to a tuple manually */ TupleDesc tupdesc; AttInMetadata *attinmeta; @@ -1008,25 +1008,30 @@ materializeQueryResult(FunctionCallInfo fcinfo, tuplestore_puttuple(tupstore, tuple); PQclear(res); + res = NULL; } else { Assert(PQresultStatus(res) == PGRES_TUPLES_OK); - /* storeHandler should have created a tuplestore */ + /* storeRow should have created a tuplestore */ Assert(rsinfo->setResult != NULL); PQclear(res); + res = NULL; } + PQclear(sinfo.last_res); + sinfo.last_res = NULL; + PQclear(sinfo.cur_res); + sinfo.cur_res = NULL; } PG_CATCH(); { - /* be sure to unset the custom row processor */ - PQsetRowProcessor(conn, NULL, NULL); /* 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 */ - while ((res = PQskipResult(conn)) != NULL) + while ((res = PQgetResult(conn)) != NULL) PQclear(res); PG_RE_THROW(); } @@ -1034,23 +1039,72 @@ materializeQueryResult(FunctionCallInfo fcinfo, } /* - * Custom row processor for materializeQueryResult. - * Prototype of this function must match PQrowProcessor. + * Execute query, and send any result rows to sinfo->tuplestore. */ -static int -storeHandler(PGresult *res, const PGdataValue *columns, - const char **errmsgp, void *param) +static PGresult * +storeQueryResult(storeInfo *sinfo, PGconn *conn, const char *sql) +{ + 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); - char **cstrs = sinfo->cstrs; HeapTuple tuple; - char *pbuf; - int pbuflen; int i; MemoryContext oldcontext; - if (columns == NULL) + if (first) { /* Prepare for new result set */ ReturnSetInfo *rsinfo = (ReturnSetInfo *) sinfo->fcinfo->resultinfo; @@ -1098,13 +1152,16 @@ storeHandler(PGresult *res, const PGdataValue *columns, sinfo->attinmeta = TupleDescGetAttInMetadata(tupdesc); /* Create a new, empty tuplestore */ - oldcontext = MemoryContextSwitchTo( - rsinfo->econtext->ecxt_per_query_memory); + oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); sinfo->tuplestore = tuplestore_begin_heap(true, false, work_mem); rsinfo->setResult = sinfo->tuplestore; rsinfo->setDesc = tupdesc; MemoryContextSwitchTo(oldcontext); + /* Done if empty resultset */ + if (PQntuples(res) == 0) + return; + /* * Set up sufficiently-wide string pointers array; this won't change * in size so it's easy to preallocate. @@ -1121,11 +1178,10 @@ storeHandler(PGresult *res, const PGdataValue *columns, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, 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. @@ -1135,46 +1191,24 @@ storeHandler(PGresult *res, const PGdataValue *columns, oldcontext = MemoryContextSwitchTo(sinfo->tmpcontext); /* - * The strings passed to us are not null-terminated, but the datatype - * 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. + * Fill cstrs with null-terminated strings of column values. */ - 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++) - { - int len = columns[i].len; - - if (len < 0) - cstrs[i] = NULL; + if (PQgetisnull(res, 0, i)) + sinfo->cstrs[i] = NULL; else - { - cstrs[i] = pbuf; - memcpy(pbuf, columns[i].value, len); - pbuf += len; - *pbuf++ = '\0'; - } + sinfo->cstrs[i] = PQgetvalue(res, 0, i); } /* 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); /* Clean up */ MemoryContextSwitchTo(oldcontext); MemoryContextReset(sinfo->tmpcontext); - - return 1; } /* diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 5c5dd68db30a717c59deff93a9a12d895d734677..255c5c1abb84127abfa8f7df9bc9a76a9c11fb19 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2418,14 +2418,28 @@ ExecStatusType PQresultStatus(const PGresult *res); <term><literal>PGRES_COPY_BOTH</literal></term> <listitem> <para> - Copy In/Out (to and from server) data transfer started. This is - currently used only for streaming replication. + Copy In/Out (to and from server) data transfer started. This + feature is currently used only for streaming replication, + so this status should not occur in ordinary applications. + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-pgres-single-tuple"> + <term><literal>PGRES_SINGLE_TUPLE</literal></term> + <listitem> + <para> + The <structname>PGresult</> contains a single result tuple + from the current command. This status occurs only when + single-row mode has been selected for the query + (see <xref linkend="libpq-single-row-mode">). </para> </listitem> </varlistentry> </variablelist> - If the result status is <literal>PGRES_TUPLES_OK</literal>, then + If the result status is <literal>PGRES_TUPLES_OK</literal> or + <literal>PGRES_SINGLE_TUPLE</literal>, then the functions described below can be used to retrieve the rows returned by the query. Note that a <command>SELECT</command> command that happens to retrieve zero rows still shows @@ -2726,7 +2740,8 @@ void PQclear(PGresult *res); These functions are used to extract information from a <structname>PGresult</structname> object that represents a successful query result (that is, one that has status - <literal>PGRES_TUPLES_OK</literal>). They can also be used to extract + <literal>PGRES_TUPLES_OK</literal> or <literal>PGRES_SINGLE_TUPLE</>). + They can also be used to extract information from a successful Describe operation: a Describe's result has all the same column information that actual execution of the query would provide, but it has zero rows. For objects with other status values, @@ -3738,7 +3753,7 @@ unsigned char *PQunescapeBytea(const unsigned char *from, size_t *to_length); <para> The <function>PQexec</function> function is adequate for submitting - commands in normal, synchronous applications. It has a couple of + commands in normal, synchronous applications. It has a few deficiencies, however, that can be of importance to some users: <itemizedlist> @@ -3769,6 +3784,15 @@ unsigned char *PQunescapeBytea(const unsigned char *from, size_t *to_length); <function>PQexec</function>. </para> </listitem> + + <listitem> + <para> + <function>PQexec</function> always collects the command's entire result, + buffering it in a single <structname>PGresult</structname>. While + this simplifies error-handling logic for the application, it can be + impractical for results containing many rows. + </para> + </listitem> </itemizedlist> </para> @@ -3984,8 +4008,11 @@ int PQsendDescribePortal(PGconn *conn, const char *portalName); Waits for the next result from a prior <function>PQsendQuery</function>, <function>PQsendQueryParams</function>, - <function>PQsendPrepare</function>, or - <function>PQsendQueryPrepared</function> call, and returns it. + <function>PQsendPrepare</function>, + <function>PQsendQueryPrepared</function>, + <function>PQsendDescribePrepared</function>, or + <function>PQsendDescribePortal</function> + call, and returns it. A null pointer is returned when the command is complete and there will be no more results. <synopsis> @@ -4012,7 +4039,7 @@ PGresult *PQgetResult(PGconn *conn); <para> Even when <function>PQresultStatus</function> indicates a fatal error, <function>PQgetResult</function> should be called until it - returns a null pointer to allow <application>libpq</> to + returns a null pointer, to allow <application>libpq</> to process the error information completely. </para> </note> @@ -4029,7 +4056,18 @@ PGresult *PQgetResult(PGconn *conn); can be obtained individually. (This allows a simple form of overlapped processing, by the way: the client can be handling the results of one command while the server is still working on later queries in the same - command string.) However, calling <function>PQgetResult</function> + command string.) + </para> + + <para> + Another frequently-desired feature that can be obtained with + <function>PQsendQuery</function> and <function>PQgetResult</function> + is retrieving large query results a row at a time. This is discussed + in <xref linkend="libpq-single-row-mode">. + </para> + + <para> + By itself, calling <function>PQgetResult</function> will still cause the client to block until the server completes the next <acronym>SQL</acronym> command. This can be avoided by proper use of two more functions: @@ -4238,6 +4276,98 @@ int PQflush(PGconn *conn); </sect1> + <sect1 id="libpq-single-row-mode"> + <title>Retrieving Query Results Row-By-Row</title> + + <indexterm zone="libpq-single-row-mode"> + <primary>libpq</primary> + <secondary>single-row mode</secondary> + </indexterm> + + <para> + Ordinarily, <application>libpq</> collects a SQL command's + entire result and returns it to the application as a single + <structname>PGresult</structname>. This can be unworkable for commands + that return a large number of rows. For such cases, applications can use + <function>PQsendQuery</function> and <function>PQgetResult</function> in + <firstterm>single-row mode</>. In this mode, the result row(s) are + returned to the application one at a time, as they are received from the + server. + </para> + + <para> + To enter single-row mode, call <function>PQsetSingleRowMode</function> + immediately after a successful call of <function>PQsendQuery</function> + (or a sibling function). This mode selection is effective only for the + currently executing query. Then call <function>PQgetResult</function> + repeatedly, until it returns null, as documented in <xref + linkend="libpq-async">. If the query returns any rows, they are returned + as individual <structname>PGresult</structname> objects, which look like + normal query results except for having status code + <literal>PGRES_SINGLE_TUPLE</literal> instead of + <literal>PGRES_TUPLES_OK</literal>. After the last row, or immediately if + the query returns zero rows, a zero-row object with status + <literal>PGRES_TUPLES_OK</literal> is returned; this is the signal that no + more rows will arrive. (But note that it is still necessary to continue + calling <function>PQgetResult</function> until it returns null.) All of + these <structname>PGresult</structname> objects will contain the same row + description data (column names, types, etc) that an ordinary + <structname>PGresult</structname> object for the query would have. + Each object should be freed with <function>PQclear</function> as usual. + </para> + + <para> + <variablelist> + <varlistentry id="libpq-pqsetsinglerowmode"> + <term> + <function>PQsetSingleRowMode</function> + <indexterm> + <primary>PQsetSingleRowMode</primary> + </indexterm> + </term> + + <listitem> + <para> + Select single-row mode for the currently-executing query. + +<synopsis> +int PQsetSingleRowMode(PGconn *conn); +</synopsis> + </para> + + <para> + This function can only be called immediately after + <function>PQsendQuery</function> or one of its sibling functions, + before any other operation on the connection such as + <function>PQconsumeInput</function> or + <function>PQgetResult</function>. If called at the correct time, + the function activates single-row mode for the current query and + returns 1. Otherwise the mode stays unchanged and the function + returns 0. In any case, the mode reverts to normal after + completion of the current query. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <caution> + <para> + While processing a query, the server may return some rows and then + encounter an error, causing the query to be aborted. Ordinarily, + <application>libpq</> discards any such rows and reports only the + error. But in single-row mode, those rows will have already been + returned to the application. Hence, the application will see some + <literal>PGRES_SINGLE_TUPLE</literal> <structname>PGresult</structname> + objects followed by a <literal>PGRES_FATAL_ERROR</literal> object. For + proper transactional behavior, the application must be designed to + discard or undo whatever has been done with the previously-processed + rows, if the query ultimately fails. + </para> + </caution> + + </sect1> + <sect1 id="libpq-cancel"> <title>Canceling Queries in Progress</title> @@ -5700,274 +5830,6 @@ defaultNoticeProcessor(void *arg, const char *message) </sect1> - <sect1 id="libpq-row-processor"> - <title>Custom Row Processing</title> - - <indexterm zone="libpq-row-processor"> - <primary>PQrowProcessor</primary> - </indexterm> - - <indexterm zone="libpq-row-processor"> - <primary>row processor</primary> - <secondary>in libpq</secondary> - </indexterm> - - <para> - Ordinarily, when receiving a query result from the server, - <application>libpq</> adds each row value to the current - <type>PGresult</type> until the entire result set is received; then - the <type>PGresult</type> is returned to the application as a unit. - This approach is simple to work with, but becomes inefficient for large - result sets. To improve performance, an application can register a - custom <firstterm>row processor</> function that processes each row - as the data is received from the network. The custom row processor could - process the data fully, or store it into some application-specific data - structure for later processing. - </para> - - <caution> - <para> - The row processor function sees the rows before it is known whether the - query will succeed overall, since the server might return some rows before - encountering an error. For proper transactional behavior, it must be - possible to discard or undo whatever the row processor has done, if the - query ultimately fails. - </para> - </caution> - - <para> - When using a custom row processor, row data is not accumulated into the - <type>PGresult</type>, so the <type>PGresult</type> ultimately delivered to - the application will contain no rows (<function>PQntuples</> = - <literal>0</>). However, it still has <function>PQresultStatus</> = - <literal>PGRES_TUPLES_OK</>, and it contains correct information about the - set of columns in the query result. On the other hand, if the query fails - partway through, the returned <type>PGresult</type> has - <function>PQresultStatus</> = <literal>PGRES_FATAL_ERROR</>. The - application must be prepared to undo any actions of the row processor - whenever it gets a <literal>PGRES_FATAL_ERROR</> result. - </para> - - <para> - A custom row processor is registered for a particular connection by - calling <function>PQsetRowProcessor</function>, described below. - This row processor will be used for all subsequent query results on that - connection until changed again. A row processor function must have a - signature matching - -<synopsis> -typedef int (*PQrowProcessor) (PGresult *res, const PGdataValue *columns, - const char **errmsgp, void *param); -</synopsis> - where <type>PGdataValue</> is described by -<synopsis> -typedef struct pgDataValue -{ - int len; /* data length in bytes, or <0 if NULL */ - const char *value; /* data value, without zero-termination */ -} PGdataValue; -</synopsis> - </para> - - <para> - The <parameter>res</> parameter is the <literal>PGRES_TUPLES_OK</> - <type>PGresult</type> that will eventually be delivered to the calling - application (if no error intervenes). It contains information about - the set of columns in the query result, but no row data. In particular the - row processor must fetch <literal>PQnfields(res)</> to know the number of - data columns. - </para> - - <para> - Immediately after <application>libpq</> has determined the result set's - column information, it will make a call to the row processor with - <parameter>columns</parameter> set to NULL, but the other parameters as - usual. The row processor can use this call to initialize for a new result - set; if it has nothing to do, it can just return <literal>1</>. In - subsequent calls, one per received row, <parameter>columns</parameter> - is non-NULL and points to an array of <type>PGdataValue</> structs, one per - data column. - </para> - - <para> - <parameter>errmsgp</parameter> is an output parameter used only for error - reporting. If the row processor needs to report an error, it can set - <literal>*</><parameter>errmsgp</parameter> to point to a suitable message - string (and then return <literal>-1</>). As a special case, returning - <literal>-1</> without changing <literal>*</><parameter>errmsgp</parameter> - from its initial value of NULL is taken to mean <quote>out of memory</>. - </para> - - <para> - The last parameter, <parameter>param</parameter>, is just a void pointer - passed through from <function>PQsetRowProcessor</function>. This can be - used for communication between the row processor function and the - surrounding application. - </para> - - <para> - In the <type>PGdataValue</> array passed to a row processor, data values - cannot be assumed to be zero-terminated, whether the data format is text - or binary. A SQL NULL value is indicated by a negative length field. - </para> - - <para> - The row processor <emphasis>must</> process the row data values - immediately, or else copy them into application-controlled storage. - The value pointers passed to the row processor point into - <application>libpq</>'s internal data input buffer, which will be - overwritten by the next packet fetch. - </para> - - <para> - The row processor function must return either <literal>1</> or - <literal>-1</>. - <literal>1</> is the normal, successful result value; <application>libpq</> - will continue with receiving row values from the server and passing them to - the row processor. <literal>-1</> indicates that the row processor has - encountered an error. In that case, - <application>libpq</> will discard all remaining rows in the result set - and then return a <literal>PGRES_FATAL_ERROR</> <type>PGresult</type> to - the application (containing the specified error message, or <quote>out of - memory for query result</> if <literal>*</><parameter>errmsgp</parameter> - was left as NULL). - </para> - - <para> - Another option for exiting a row processor is to throw an exception using - C's <function>longjmp()</> or C++'s <literal>throw</>. If this is done, - processing of the incoming data can be resumed later by calling - <function>PQgetResult</>; the row processor will be invoked as normal for - any remaining rows in the current result. - As with any usage of <function>PQgetResult</>, the application - should continue calling <function>PQgetResult</> until it gets a NULL - result before issuing any new query. - </para> - - <para> - In some cases, an exception may mean that the remainder of the - query result is not interesting. In such cases the application can discard - the remaining rows with <function>PQskipResult</>, described below. - Another possible recovery option is to close the connection altogether with - <function>PQfinish</>. - </para> - - <para> - <variablelist> - <varlistentry id="libpq-pqsetrowprocessor"> - <term> - <function>PQsetRowProcessor</function> - <indexterm> - <primary>PQsetRowProcessor</primary> - </indexterm> - </term> - - <listitem> - <para> - Sets a callback function to process each row. - -<synopsis> -void PQsetRowProcessor(PGconn *conn, PQrowProcessor func, void *param); -</synopsis> - </para> - - <para> - The specified row processor function <parameter>func</> is installed as - the active row processor for the given connection <parameter>conn</>. - Also, <parameter>param</> is installed as the passthrough pointer to - pass to it. Alternatively, if <parameter>func</> is NULL, the standard - row processor is reinstalled on the given connection (and - <parameter>param</> is ignored). - </para> - - <para> - Although the row processor can be changed at any time in the life of a - connection, it's generally unwise to do so while a query is active. - In particular, when using asynchronous mode, be aware that both - <function>PQisBusy</> and <function>PQgetResult</> can call the current - row processor. - </para> - </listitem> - </varlistentry> - - <varlistentry id="libpq-pqgetrowprocessor"> - <term> - <function>PQgetRowProcessor</function> - <indexterm> - <primary>PQgetRowProcessor</primary> - </indexterm> - </term> - - <listitem> - <para> - Fetches the current row processor for the specified connection. - -<synopsis> -PQrowProcessor PQgetRowProcessor(const PGconn *conn, void **param); -</synopsis> - </para> - - <para> - In addition to returning the row processor function pointer, the - current passthrough pointer will be returned at - <literal>*</><parameter>param</>, if <parameter>param</> is not NULL. - </para> - </listitem> - </varlistentry> - - <varlistentry id="libpq-pqskipresult"> - <term> - <function>PQskipResult</function> - <indexterm> - <primary>PQskipResult</primary> - </indexterm> - </term> - - <listitem> - <para> - Discard all the remaining rows in the incoming result set. - -<synopsis> -PGresult *PQskipResult(PGconn *conn); -</synopsis> - </para> - - <para> - This is a simple convenience function to discard incoming data after a - row processor has failed or it's determined that the rest of the result - set is not interesting. <function>PQskipResult</> is exactly - equivalent to <function>PQgetResult</> except that it transiently - installs a dummy row processor function that just discards data. - The returned <type>PGresult</> can be discarded without further ado - if it has status <literal>PGRES_TUPLES_OK</>; but other status values - should be handled normally. (In particular, - <literal>PGRES_FATAL_ERROR</> indicates a server-reported error that - will still need to be dealt with.) - As when using <function>PQgetResult</>, one should usually repeat the - call until NULL is returned to ensure the connection has reached an - idle state. Another possible usage is to call - <function>PQskipResult</> just once, and then resume using - <function>PQgetResult</> to process subsequent result sets normally. - </para> - - <para> - Because <function>PQskipResult</> will wait for server input, it is not - very useful in asynchronous applications. In particular you should not - code a loop of <function>PQisBusy</> and <function>PQskipResult</>, - because that will result in the installed row processor being called - within <function>PQisBusy</>. To get the proper behavior in an - asynchronous application, you'll need to install a dummy row processor - (or set a flag to make your normal row processor do nothing) and leave - it that way until you have discarded all incoming data via your normal - <function>PQisBusy</> and <function>PQgetResult</> loop. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> - - </sect1> - <sect1 id="libpq-events"> <title>Event System</title> diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 1251455f1f6d92c74e206b5a9b8dcdeb36ac9b98..9d95e262be3fbf26731c0926013db56dbc4e00ab 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 a32258a8cbab5e655484d0471c031864fa5084de..adaab7aaade60a980b7eaf5b6e9734444c72b930 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 badc0b32a8e8f3e33e42729a8f8899475a0de31b..53516db723492f11fc9f4bdc6d4d351693c4c22f 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; @@ -1547,6 +1553,31 @@ pqHandleSendFailure(PGconn *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 * 0 return: some kind of trouble @@ -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 13fd98c2f913d3818758e75bac96822306981b52..f3a6d0341c13ce644a7f1b2f15919385b85d6a6e 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 8dbd6b6982395850fc167eb20cb8d5590d33122f..1ba5885cd3b419a97f4fc5ea897eac209f364897 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 173af2e0a79ef3b443de515cda851e277deaed2d..d289f82285fea00d5de20542e43ea103493f9e58 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 67db6119bbaa35ae31fb58bb902a455b093ea23f..9d05dd20605a84ab4c4ec9d61ef8697fb2f3b77e 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 4bc89269fababe5e4d8ecbf6e80ca1a8625d4bd5..2bac59c3d879ecabce42ceab3b5133df03a0886a 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 === */