mirror of
https://github.com/postgres/postgres.git
synced 2025-04-29 13:56:47 +03:00
Support retrieval of results in chunks with libpq.
This patch generalizes libpq's existing single-row mode to allow individual partial-result PGresults to contain up to N rows, rather than always one row. This reduces malloc overhead compared to plain single-row mode, and it is very useful for psql's FETCH_COUNT feature, since otherwise we'd have to add code (and cycles) to either merge single-row PGresults into a bigger one or teach psql's results-printing logic to accept arrays of PGresults. To avoid API breakage, PQsetSingleRowMode() remains the same, and we add a new function PQsetChunkedRowsMode() to invoke the more general case. Also, PGresults obtained the old way continue to carry the PGRES_SINGLE_TUPLE status code, while if PQsetChunkedRowsMode() is used then their status code is PGRES_TUPLES_CHUNK. The underlying logic is the same either way, though. Daniel Vérité, reviewed by Laurenz Albe and myself (and whacked around a bit by me, so any remaining bugs are my fault) Discussion: https://postgr.es/m/CAKZiRmxsVTkO928CM+-ADvsMyePmU3L9DQCa9NwqjvLPcEe5QA@mail.gmail.com
This commit is contained in:
parent
92641d8d65
commit
4643a2b265
@ -3588,6 +3588,20 @@ ExecStatusType PQresultStatus(const PGresult *res);
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="libpq-pgres-tuples-chunk">
|
||||||
|
<term><literal>PGRES_TUPLES_CHUNK</literal></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The <structname>PGresult</structname> contains several result tuples
|
||||||
|
from the current command. This status occurs only when
|
||||||
|
chunked mode has been selected for the query
|
||||||
|
(see <xref linkend="libpq-single-row-mode"/>).
|
||||||
|
The number of tuples will not exceed the limit passed to
|
||||||
|
<xref linkend="libpq-PQsetChunkedRowsMode"/>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry id="libpq-pgres-pipeline-sync">
|
<varlistentry id="libpq-pgres-pipeline-sync">
|
||||||
<term><literal>PGRES_PIPELINE_SYNC</literal></term>
|
<term><literal>PGRES_PIPELINE_SYNC</literal></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@ -3617,8 +3631,9 @@ ExecStatusType PQresultStatus(const PGresult *res);
|
|||||||
|
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|
||||||
If the result status is <literal>PGRES_TUPLES_OK</literal> or
|
If the result status is <literal>PGRES_TUPLES_OK</literal>,
|
||||||
<literal>PGRES_SINGLE_TUPLE</literal>, then
|
<literal>PGRES_SINGLE_TUPLE</literal>, or
|
||||||
|
<literal>PGRES_TUPLES_CHUNK</literal>, then
|
||||||
the functions described below can be used to retrieve the rows
|
the functions described below can be used to retrieve the rows
|
||||||
returned by the query. Note that a <command>SELECT</command>
|
returned by the query. Note that a <command>SELECT</command>
|
||||||
command that happens to retrieve zero rows still shows
|
command that happens to retrieve zero rows still shows
|
||||||
@ -4030,7 +4045,9 @@ void PQclear(PGresult *res);
|
|||||||
These functions are used to extract information from a
|
These functions are used to extract information from a
|
||||||
<structname>PGresult</structname> object that represents a successful
|
<structname>PGresult</structname> object that represents a successful
|
||||||
query result (that is, one that has status
|
query result (that is, one that has status
|
||||||
<literal>PGRES_TUPLES_OK</literal> or <literal>PGRES_SINGLE_TUPLE</literal>).
|
<literal>PGRES_TUPLES_OK</literal>,
|
||||||
|
<literal>PGRES_SINGLE_TUPLE</literal>, or
|
||||||
|
<literal>PGRES_TUPLES_CHUNK</literal>).
|
||||||
They can also be used to extract
|
They can also be used to extract
|
||||||
information from a successful Describe operation: a Describe's result
|
information from a successful Describe operation: a Describe's result
|
||||||
has all the same column information that actual execution of the query
|
has all the same column information that actual execution of the query
|
||||||
@ -5235,7 +5252,8 @@ PGresult *PQgetResult(PGconn *conn);
|
|||||||
<para>
|
<para>
|
||||||
Another frequently-desired feature that can be obtained with
|
Another frequently-desired feature that can be obtained with
|
||||||
<xref linkend="libpq-PQsendQuery"/> and <xref linkend="libpq-PQgetResult"/>
|
<xref linkend="libpq-PQsendQuery"/> and <xref linkend="libpq-PQgetResult"/>
|
||||||
is retrieving large query results a row at a time. This is discussed
|
is retrieving large query results a limited number of rows at a time.
|
||||||
|
This is discussed
|
||||||
in <xref linkend="libpq-single-row-mode"/>.
|
in <xref linkend="libpq-single-row-mode"/>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
@ -5599,15 +5617,6 @@ int PQflush(PGconn *conn);
|
|||||||
queries in the pipeline; see <xref linkend="libpq-pipeline-interleave"/>.
|
queries in the pipeline; see <xref linkend="libpq-pipeline-interleave"/>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
|
||||||
To enter single-row mode, call <function>PQsetSingleRowMode</function>
|
|
||||||
before retrieving results with <function>PQgetResult</function>.
|
|
||||||
This mode selection is effective only for the query currently
|
|
||||||
being processed. For more information on the use of
|
|
||||||
<function>PQsetSingleRowMode</function>,
|
|
||||||
refer to <xref linkend="libpq-single-row-mode"/>.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<function>PQgetResult</function> behaves the same as for normal
|
<function>PQgetResult</function> behaves the same as for normal
|
||||||
asynchronous processing except that it may contain the new
|
asynchronous processing except that it may contain the new
|
||||||
@ -5972,36 +5981,49 @@ UPDATE mytable SET x = x + 1 WHERE id = 42;
|
|||||||
</sect2>
|
</sect2>
|
||||||
</sect1>
|
</sect1>
|
||||||
|
|
||||||
|
<!-- keep this not-too-apropos sect1 ID for stability of doc URLs -->
|
||||||
<sect1 id="libpq-single-row-mode">
|
<sect1 id="libpq-single-row-mode">
|
||||||
<title>Retrieving Query Results Row-by-Row</title>
|
<title>Retrieving Query Results in Chunks</title>
|
||||||
|
|
||||||
<indexterm zone="libpq-single-row-mode">
|
<indexterm zone="libpq-single-row-mode">
|
||||||
<primary>libpq</primary>
|
<primary>libpq</primary>
|
||||||
<secondary>single-row mode</secondary>
|
<secondary>single-row mode</secondary>
|
||||||
</indexterm>
|
</indexterm>
|
||||||
|
|
||||||
|
<indexterm zone="libpq-single-row-mode">
|
||||||
|
<primary>libpq</primary>
|
||||||
|
<secondary>chunked mode</secondary>
|
||||||
|
</indexterm>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Ordinarily, <application>libpq</application> collects an SQL command's
|
Ordinarily, <application>libpq</application> collects an SQL command's
|
||||||
entire result and returns it to the application as a single
|
entire result and returns it to the application as a single
|
||||||
<structname>PGresult</structname>. This can be unworkable for commands
|
<structname>PGresult</structname>. This can be unworkable for commands
|
||||||
that return a large number of rows. For such cases, applications can use
|
that return a large number of rows. For such cases, applications can use
|
||||||
<xref linkend="libpq-PQsendQuery"/> and <xref linkend="libpq-PQgetResult"/> in
|
<xref linkend="libpq-PQsendQuery"/> and <xref linkend="libpq-PQgetResult"/> in
|
||||||
<firstterm>single-row mode</firstterm>. In this mode, the result row(s) are
|
<firstterm>single-row mode</firstterm> or <firstterm>chunked
|
||||||
returned to the application one at a time, as they are received from the
|
mode</firstterm>. In these modes, result row(s) are returned to the
|
||||||
server.
|
application as they are received from the server, one at a time for
|
||||||
|
single-row mode or in groups for chunked mode.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
To enter single-row mode, call <xref linkend="libpq-PQsetSingleRowMode"/>
|
To enter one of these modes, call <xref linkend="libpq-PQsetSingleRowMode"/>
|
||||||
|
or <xref linkend="libpq-PQsetChunkedRowsMode"/>
|
||||||
immediately after a successful call of <xref linkend="libpq-PQsendQuery"/>
|
immediately after a successful call of <xref linkend="libpq-PQsendQuery"/>
|
||||||
(or a sibling function). This mode selection is effective only for the
|
(or a sibling function). This mode selection is effective only for the
|
||||||
currently executing query. Then call <xref linkend="libpq-PQgetResult"/>
|
currently executing query. Then call <xref linkend="libpq-PQgetResult"/>
|
||||||
repeatedly, until it returns null, as documented in <xref
|
repeatedly, until it returns null, as documented in <xref
|
||||||
linkend="libpq-async"/>. If the query returns any rows, they are returned
|
linkend="libpq-async"/>. If the query returns any rows, they are returned
|
||||||
as individual <structname>PGresult</structname> objects, which look like
|
as one or more <structname>PGresult</structname> objects, which look like
|
||||||
normal query results except for having status code
|
normal query results except for having status code
|
||||||
<literal>PGRES_SINGLE_TUPLE</literal> instead of
|
<literal>PGRES_SINGLE_TUPLE</literal> for single-row mode or
|
||||||
<literal>PGRES_TUPLES_OK</literal>. After the last row, or immediately if
|
<literal>PGRES_TUPLES_CHUNK</literal> for chunked mode, instead of
|
||||||
|
<literal>PGRES_TUPLES_OK</literal>. There is exactly one result row in
|
||||||
|
each <literal>PGRES_SINGLE_TUPLE</literal> object, while
|
||||||
|
a <literal>PGRES_TUPLES_CHUNK</literal> object contains at least one
|
||||||
|
row but not more than the specified number of rows per chunk.
|
||||||
|
After the last row, or immediately if
|
||||||
the query returns zero rows, a zero-row object with status
|
the query returns zero rows, a zero-row object with status
|
||||||
<literal>PGRES_TUPLES_OK</literal> is returned; this is the signal that no
|
<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
|
more rows will arrive. (But note that it is still necessary to continue
|
||||||
@ -6013,9 +6035,9 @@ UPDATE mytable SET x = x + 1 WHERE id = 42;
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
When using pipeline mode, single-row mode needs to be activated for each
|
When using pipeline mode, single-row or chunked mode needs to be
|
||||||
query in the pipeline before retrieving results for that query
|
activated for each query in the pipeline before retrieving results for
|
||||||
with <function>PQgetResult</function>.
|
that query with <function>PQgetResult</function>.
|
||||||
See <xref linkend="libpq-pipeline-mode"/> for more information.
|
See <xref linkend="libpq-pipeline-mode"/> for more information.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
@ -6046,6 +6068,36 @@ int PQsetSingleRowMode(PGconn *conn);
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="libpq-PQsetChunkedRowsMode">
|
||||||
|
<term><function>PQsetChunkedRowsMode</function><indexterm><primary>PQsetChunkedRowsMode</primary></indexterm></term>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Select chunked mode for the currently-executing query.
|
||||||
|
|
||||||
|
<synopsis>
|
||||||
|
int PQsetChunkedRowsMode(PGconn *conn, int chunkSize);
|
||||||
|
</synopsis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This function is similar to
|
||||||
|
<xref linkend="libpq-PQsetSingleRowMode"/>, except that it
|
||||||
|
specifies retrieval of up to <replaceable>chunkSize</replaceable> rows
|
||||||
|
per <structname>PGresult</structname>, not necessarily just one row.
|
||||||
|
This function can only be called immediately after
|
||||||
|
<xref linkend="libpq-PQsendQuery"/> or one of its sibling functions,
|
||||||
|
before any other operation on the connection such as
|
||||||
|
<xref linkend="libpq-PQconsumeInput"/> or
|
||||||
|
<xref linkend="libpq-PQgetResult"/>. If called at the correct time,
|
||||||
|
the function activates chunked 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>
|
</variablelist>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
@ -6054,9 +6106,10 @@ int PQsetSingleRowMode(PGconn *conn);
|
|||||||
While processing a query, the server may return some rows and then
|
While processing a query, the server may return some rows and then
|
||||||
encounter an error, causing the query to be aborted. Ordinarily,
|
encounter an error, causing the query to be aborted. Ordinarily,
|
||||||
<application>libpq</application> discards any such rows and reports only the
|
<application>libpq</application> discards any such rows and reports only the
|
||||||
error. But in single-row mode, those rows will have already been
|
error. But in single-row or chunked mode, some rows may have already
|
||||||
returned to the application. Hence, the application will see some
|
been returned to the application. Hence, the application will see some
|
||||||
<literal>PGRES_SINGLE_TUPLE</literal> <structname>PGresult</structname>
|
<literal>PGRES_SINGLE_TUPLE</literal> or <literal>PGRES_TUPLES_CHUNK</literal>
|
||||||
|
<structname>PGresult</structname>
|
||||||
objects followed by a <literal>PGRES_FATAL_ERROR</literal> object. For
|
objects followed by a <literal>PGRES_FATAL_ERROR</literal> object. For
|
||||||
proper transactional behavior, the application must be designed to
|
proper transactional behavior, the application must be designed to
|
||||||
discard or undo whatever has been done with the previously-processed
|
discard or undo whatever has been done with the previously-processed
|
||||||
|
@ -1248,8 +1248,9 @@ libpqrcv_exec(WalReceiverConn *conn, const char *query,
|
|||||||
|
|
||||||
switch (PQresultStatus(pgres))
|
switch (PQresultStatus(pgres))
|
||||||
{
|
{
|
||||||
case PGRES_SINGLE_TUPLE:
|
|
||||||
case PGRES_TUPLES_OK:
|
case PGRES_TUPLES_OK:
|
||||||
|
case PGRES_SINGLE_TUPLE:
|
||||||
|
case PGRES_TUPLES_CHUNK:
|
||||||
walres->status = WALRCV_OK_TUPLES;
|
walres->status = WALRCV_OK_TUPLES;
|
||||||
libpqrcv_processTuples(pgres, walres, nRetTypes, retTypes);
|
libpqrcv_processTuples(pgres, walres, nRetTypes, retTypes);
|
||||||
break;
|
break;
|
||||||
|
@ -991,6 +991,7 @@ should_processing_continue(PGresult *res)
|
|||||||
case PGRES_SINGLE_TUPLE:
|
case PGRES_SINGLE_TUPLE:
|
||||||
case PGRES_PIPELINE_SYNC:
|
case PGRES_PIPELINE_SYNC:
|
||||||
case PGRES_PIPELINE_ABORTED:
|
case PGRES_PIPELINE_ABORTED:
|
||||||
|
case PGRES_TUPLES_CHUNK:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -203,3 +203,4 @@ PQcancelErrorMessage 200
|
|||||||
PQcancelReset 201
|
PQcancelReset 201
|
||||||
PQcancelFinish 202
|
PQcancelFinish 202
|
||||||
PQsocketPoll 203
|
PQsocketPoll 203
|
||||||
|
PQsetChunkedRowsMode 204
|
||||||
|
@ -41,7 +41,8 @@ char *const pgresStatus[] = {
|
|||||||
"PGRES_COPY_BOTH",
|
"PGRES_COPY_BOTH",
|
||||||
"PGRES_SINGLE_TUPLE",
|
"PGRES_SINGLE_TUPLE",
|
||||||
"PGRES_PIPELINE_SYNC",
|
"PGRES_PIPELINE_SYNC",
|
||||||
"PGRES_PIPELINE_ABORTED"
|
"PGRES_PIPELINE_ABORTED",
|
||||||
|
"PGRES_TUPLES_CHUNK"
|
||||||
};
|
};
|
||||||
|
|
||||||
/* We return this if we're unable to make a PGresult at all */
|
/* We return this if we're unable to make a PGresult at all */
|
||||||
@ -200,6 +201,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
|
|||||||
case PGRES_COPY_IN:
|
case PGRES_COPY_IN:
|
||||||
case PGRES_COPY_BOTH:
|
case PGRES_COPY_BOTH:
|
||||||
case PGRES_SINGLE_TUPLE:
|
case PGRES_SINGLE_TUPLE:
|
||||||
|
case PGRES_TUPLES_CHUNK:
|
||||||
/* non-error cases */
|
/* non-error cases */
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -771,7 +773,7 @@ PQclear(PGresult *res)
|
|||||||
/*
|
/*
|
||||||
* Handy subroutine to deallocate any partially constructed async result.
|
* Handy subroutine to deallocate any partially constructed async result.
|
||||||
*
|
*
|
||||||
* Any "next" result gets cleared too.
|
* Any "saved" result gets cleared too.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
pqClearAsyncResult(PGconn *conn)
|
pqClearAsyncResult(PGconn *conn)
|
||||||
@ -779,8 +781,8 @@ pqClearAsyncResult(PGconn *conn)
|
|||||||
PQclear(conn->result);
|
PQclear(conn->result);
|
||||||
conn->result = NULL;
|
conn->result = NULL;
|
||||||
conn->error_result = false;
|
conn->error_result = false;
|
||||||
PQclear(conn->next_result);
|
PQclear(conn->saved_result);
|
||||||
conn->next_result = NULL;
|
conn->saved_result = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -911,14 +913,14 @@ pqPrepareAsyncResult(PGconn *conn)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Replace conn->result with next_result, if any. In the normal case
|
* Replace conn->result with saved_result, if any. In the normal case
|
||||||
* there isn't a next result and we're just dropping ownership of the
|
* there isn't a saved result and we're just dropping ownership of the
|
||||||
* current result. In single-row mode this restores the situation to what
|
* current result. In partial-result mode this restores the situation to
|
||||||
* it was before we created the current single-row result.
|
* what it was before we created the current partial result.
|
||||||
*/
|
*/
|
||||||
conn->result = conn->next_result;
|
conn->result = conn->saved_result;
|
||||||
conn->error_result = false; /* next_result is never an error */
|
conn->error_result = false; /* saved_result is never an error */
|
||||||
conn->next_result = NULL;
|
conn->saved_result = NULL;
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -1199,11 +1201,6 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
|
|||||||
* On error, *errmsgp can be set to an error string to be returned.
|
* On error, *errmsgp can be set to an error string to be returned.
|
||||||
* (Such a string should already be translated via libpq_gettext().)
|
* (Such a string should already be translated via libpq_gettext().)
|
||||||
* If it is left NULL, the error is presumed to be "out of memory".
|
* If it is left NULL, the error is presumed to be "out of memory".
|
||||||
*
|
|
||||||
* In single-row mode, we create a new result holding just the current row,
|
|
||||||
* stashing the previous result in conn->next_result so that it becomes
|
|
||||||
* active again after pqPrepareAsyncResult(). This allows the result metadata
|
|
||||||
* (column descriptions) to be carried forward to each result row.
|
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
pqRowProcessor(PGconn *conn, const char **errmsgp)
|
pqRowProcessor(PGconn *conn, const char **errmsgp)
|
||||||
@ -1215,11 +1212,14 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In single-row mode, make a new PGresult that will hold just this one
|
* In partial-result mode, if we don't already have a partial PGresult
|
||||||
* row; the original conn->result is left unchanged so that it can be used
|
* then make one by cloning conn->result (which should hold the correct
|
||||||
* again as the template for future rows.
|
* result metadata by now). Then the original conn->result is moved over
|
||||||
|
* to saved_result so that we can re-use it as a reference for future
|
||||||
|
* partial results. The saved result will become active again after
|
||||||
|
* pqPrepareAsyncResult() returns the partial result to the application.
|
||||||
*/
|
*/
|
||||||
if (conn->singleRowMode)
|
if (conn->partialResMode && conn->saved_result == NULL)
|
||||||
{
|
{
|
||||||
/* Copy everything that should be in the result at this point */
|
/* Copy everything that should be in the result at this point */
|
||||||
res = PQcopyResult(res,
|
res = PQcopyResult(res,
|
||||||
@ -1227,6 +1227,11 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
|
|||||||
PG_COPYRES_NOTICEHOOKS);
|
PG_COPYRES_NOTICEHOOKS);
|
||||||
if (!res)
|
if (!res)
|
||||||
return 0;
|
return 0;
|
||||||
|
/* Change result status to appropriate special value */
|
||||||
|
res->resultStatus = (conn->singleRowMode ? PGRES_SINGLE_TUPLE : PGRES_TUPLES_CHUNK);
|
||||||
|
/* And stash it as the active result */
|
||||||
|
conn->saved_result = conn->result;
|
||||||
|
conn->result = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1241,7 +1246,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
|
|||||||
tup = (PGresAttValue *)
|
tup = (PGresAttValue *)
|
||||||
pqResultAlloc(res, nfields * sizeof(PGresAttValue), true);
|
pqResultAlloc(res, nfields * sizeof(PGresAttValue), true);
|
||||||
if (tup == NULL)
|
if (tup == NULL)
|
||||||
goto fail;
|
return 0;
|
||||||
|
|
||||||
for (i = 0; i < nfields; i++)
|
for (i = 0; i < nfields; i++)
|
||||||
{
|
{
|
||||||
@ -1260,7 +1265,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
|
|||||||
|
|
||||||
val = (char *) pqResultAlloc(res, clen + 1, isbinary);
|
val = (char *) pqResultAlloc(res, clen + 1, isbinary);
|
||||||
if (val == NULL)
|
if (val == NULL)
|
||||||
goto fail;
|
return 0;
|
||||||
|
|
||||||
/* copy and zero-terminate the data (even if it's binary) */
|
/* copy and zero-terminate the data (even if it's binary) */
|
||||||
memcpy(val, columns[i].value, clen);
|
memcpy(val, columns[i].value, clen);
|
||||||
@ -1273,30 +1278,16 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
|
|||||||
|
|
||||||
/* And add the tuple to the PGresult's tuple array */
|
/* And add the tuple to the PGresult's tuple array */
|
||||||
if (!pqAddTuple(res, tup, errmsgp))
|
if (!pqAddTuple(res, tup, errmsgp))
|
||||||
goto fail;
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Success. In single-row mode, make the result available to the client
|
* Success. In partial-result mode, if we have enough rows then make the
|
||||||
* immediately.
|
* result available to the client immediately.
|
||||||
*/
|
*/
|
||||||
if (conn->singleRowMode)
|
if (conn->partialResMode && res->ntups >= conn->maxChunkSize)
|
||||||
{
|
|
||||||
/* 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_MORE;
|
conn->asyncStatus = PGASYNC_READY_MORE;
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
fail:
|
|
||||||
/* release locally allocated PGresult, if we made one */
|
|
||||||
if (res != conn->result)
|
|
||||||
PQclear(res);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1745,8 +1736,10 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
|
|||||||
*/
|
*/
|
||||||
pqClearAsyncResult(conn);
|
pqClearAsyncResult(conn);
|
||||||
|
|
||||||
/* reset single-row processing mode */
|
/* reset partial-result mode */
|
||||||
|
conn->partialResMode = false;
|
||||||
conn->singleRowMode = false;
|
conn->singleRowMode = false;
|
||||||
|
conn->maxChunkSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ready to send command message */
|
/* ready to send command message */
|
||||||
@ -1925,30 +1918,61 @@ sendFailed:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Is it OK to change partial-result mode now?
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
canChangeResultMode(PGconn *conn)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Only allow changing the mode when we have launched a query and not yet
|
||||||
|
* received any results.
|
||||||
|
*/
|
||||||
|
if (!conn)
|
||||||
|
return false;
|
||||||
|
if (conn->asyncStatus != PGASYNC_BUSY)
|
||||||
|
return false;
|
||||||
|
if (!conn->cmd_queue_head ||
|
||||||
|
(conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE &&
|
||||||
|
conn->cmd_queue_head->queryclass != PGQUERY_EXTENDED))
|
||||||
|
return false;
|
||||||
|
if (pgHavePendingResult(conn))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Select row-by-row processing mode
|
* Select row-by-row processing mode
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
PQsetSingleRowMode(PGconn *conn)
|
PQsetSingleRowMode(PGconn *conn)
|
||||||
{
|
{
|
||||||
/*
|
if (canChangeResultMode(conn))
|
||||||
* Only allow setting the flag when we have launched a query and not yet
|
{
|
||||||
* received any results.
|
conn->partialResMode = true;
|
||||||
*/
|
conn->singleRowMode = true;
|
||||||
if (!conn)
|
conn->maxChunkSize = 1;
|
||||||
return 0;
|
return 1;
|
||||||
if (conn->asyncStatus != PGASYNC_BUSY)
|
}
|
||||||
return 0;
|
else
|
||||||
if (!conn->cmd_queue_head ||
|
|
||||||
(conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE &&
|
|
||||||
conn->cmd_queue_head->queryclass != PGQUERY_EXTENDED))
|
|
||||||
return 0;
|
|
||||||
if (pgHavePendingResult(conn))
|
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* OK, set flag */
|
/*
|
||||||
conn->singleRowMode = true;
|
* Select chunked results processing mode
|
||||||
return 1;
|
*/
|
||||||
|
int
|
||||||
|
PQsetChunkedRowsMode(PGconn *conn, int chunkSize)
|
||||||
|
{
|
||||||
|
if (chunkSize > 0 && canChangeResultMode(conn))
|
||||||
|
{
|
||||||
|
conn->partialResMode = true;
|
||||||
|
conn->singleRowMode = false;
|
||||||
|
conn->maxChunkSize = chunkSize;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2117,6 +2141,20 @@ PQgetResult(PGconn *conn)
|
|||||||
case PGASYNC_READY:
|
case PGASYNC_READY:
|
||||||
res = pqPrepareAsyncResult(conn);
|
res = pqPrepareAsyncResult(conn);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normally pqPrepareAsyncResult will have left conn->result
|
||||||
|
* empty. Otherwise, "res" must be a not-full PGRES_TUPLES_CHUNK
|
||||||
|
* result, which we want to return to the caller while staying in
|
||||||
|
* PGASYNC_READY state. Then the next call here will return the
|
||||||
|
* empty PGRES_TUPLES_OK result that was restored from
|
||||||
|
* saved_result, after which we can proceed.
|
||||||
|
*/
|
||||||
|
if (conn->result)
|
||||||
|
{
|
||||||
|
Assert(res->resultStatus == PGRES_TUPLES_CHUNK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Advance the queue as appropriate */
|
/* Advance the queue as appropriate */
|
||||||
pqCommandQueueAdvance(conn, false,
|
pqCommandQueueAdvance(conn, false,
|
||||||
res->resultStatus == PGRES_PIPELINE_SYNC);
|
res->resultStatus == PGRES_PIPELINE_SYNC);
|
||||||
@ -3173,10 +3211,12 @@ pqPipelineProcessQueue(PGconn *conn)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reset single-row processing mode. (Client has to set it up for each
|
* Reset partial-result mode. (Client has to set it up for each query, if
|
||||||
* query, if desired.)
|
* desired.)
|
||||||
*/
|
*/
|
||||||
|
conn->partialResMode = false;
|
||||||
conn->singleRowMode = false;
|
conn->singleRowMode = false;
|
||||||
|
conn->maxChunkSize = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there are no further commands to process in the queue, get us in
|
* If there are no further commands to process in the queue, get us in
|
||||||
|
@ -379,7 +379,8 @@ pqParseInput3(PGconn *conn)
|
|||||||
break;
|
break;
|
||||||
case PqMsg_DataRow:
|
case PqMsg_DataRow:
|
||||||
if (conn->result != NULL &&
|
if (conn->result != NULL &&
|
||||||
conn->result->resultStatus == PGRES_TUPLES_OK)
|
(conn->result->resultStatus == PGRES_TUPLES_OK ||
|
||||||
|
conn->result->resultStatus == PGRES_TUPLES_CHUNK))
|
||||||
{
|
{
|
||||||
/* Read another tuple of a normal query response */
|
/* Read another tuple of a normal query response */
|
||||||
if (getAnotherTuple(conn, msgLength))
|
if (getAnotherTuple(conn, msgLength))
|
||||||
|
@ -112,8 +112,9 @@ typedef enum
|
|||||||
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 */
|
PGRES_SINGLE_TUPLE, /* single tuple from larger resultset */
|
||||||
PGRES_PIPELINE_SYNC, /* pipeline synchronization point */
|
PGRES_PIPELINE_SYNC, /* pipeline synchronization point */
|
||||||
PGRES_PIPELINE_ABORTED /* Command didn't run because of an abort
|
PGRES_PIPELINE_ABORTED, /* Command didn't run because of an abort
|
||||||
* earlier in a pipeline */
|
* earlier in a pipeline */
|
||||||
|
PGRES_TUPLES_CHUNK /* chunk of tuples from larger resultset */
|
||||||
} ExecStatusType;
|
} ExecStatusType;
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
@ -489,6 +490,7 @@ extern int PQsendQueryPrepared(PGconn *conn,
|
|||||||
const int *paramFormats,
|
const int *paramFormats,
|
||||||
int resultFormat);
|
int resultFormat);
|
||||||
extern int PQsetSingleRowMode(PGconn *conn);
|
extern int PQsetSingleRowMode(PGconn *conn);
|
||||||
|
extern int PQsetChunkedRowsMode(PGconn *conn, int chunkSize);
|
||||||
extern PGresult *PQgetResult(PGconn *conn);
|
extern PGresult *PQgetResult(PGconn *conn);
|
||||||
|
|
||||||
/* Routines for managing an asynchronous query */
|
/* Routines for managing an asynchronous query */
|
||||||
|
@ -434,7 +434,10 @@ struct pg_conn
|
|||||||
bool nonblocking; /* whether this connection is using nonblock
|
bool nonblocking; /* whether this connection is using nonblock
|
||||||
* sending semantics */
|
* sending semantics */
|
||||||
PGpipelineStatus pipelineStatus; /* status of pipeline mode */
|
PGpipelineStatus pipelineStatus; /* status of pipeline mode */
|
||||||
|
bool partialResMode; /* true if single-row or chunked mode */
|
||||||
bool singleRowMode; /* return current query result row-by-row? */
|
bool singleRowMode; /* return current query result row-by-row? */
|
||||||
|
int maxChunkSize; /* return query result in chunks not exceeding
|
||||||
|
* this number of rows */
|
||||||
char copy_is_binary; /* 1 = copy binary, 0 = copy text */
|
char copy_is_binary; /* 1 = copy binary, 0 = copy text */
|
||||||
int copy_already_done; /* # bytes already returned in COPY OUT */
|
int copy_already_done; /* # bytes already returned in COPY OUT */
|
||||||
PGnotify *notifyHead; /* oldest unreported Notify msg */
|
PGnotify *notifyHead; /* oldest unreported Notify msg */
|
||||||
@ -535,12 +538,13 @@ struct pg_conn
|
|||||||
* and error_result is true, then we need to return a PGRES_FATAL_ERROR
|
* and error_result is true, then we need to return a PGRES_FATAL_ERROR
|
||||||
* result, but haven't yet constructed it; text for the error has been
|
* result, but haven't yet constructed it; text for the error has been
|
||||||
* appended to conn->errorMessage. (Delaying construction simplifies
|
* appended to conn->errorMessage. (Delaying construction simplifies
|
||||||
* dealing with out-of-memory cases.) If next_result isn't NULL, it is a
|
* dealing with out-of-memory cases.) If saved_result isn't NULL, it is a
|
||||||
* PGresult that will replace "result" after we return that one.
|
* PGresult that will replace "result" after we return that one; we use
|
||||||
|
* that in partial-result mode to remember the query's tuple metadata.
|
||||||
*/
|
*/
|
||||||
PGresult *result; /* result being constructed */
|
PGresult *result; /* result being constructed */
|
||||||
bool error_result; /* do we need to make an ERROR result? */
|
bool error_result; /* do we need to make an ERROR result? */
|
||||||
PGresult *next_result; /* next result (used in single-row mode) */
|
PGresult *saved_result; /* original, empty result in partialResMode */
|
||||||
|
|
||||||
/* Assorted state for SASL, SSL, GSS, etc */
|
/* Assorted state for SASL, SSL, GSS, etc */
|
||||||
const pg_fe_sasl_mech *sasl;
|
const pg_fe_sasl_mech *sasl;
|
||||||
|
@ -1719,6 +1719,46 @@ test_singlerowmode(PGconn *conn)
|
|||||||
if (PQgetResult(conn) != NULL)
|
if (PQgetResult(conn) != NULL)
|
||||||
pg_fatal("expected NULL result");
|
pg_fatal("expected NULL result");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try chunked mode as well; make sure that it correctly delivers a
|
||||||
|
* partial final chunk.
|
||||||
|
*/
|
||||||
|
if (PQsendQueryParams(conn, "SELECT generate_series(1, 5)",
|
||||||
|
0, NULL, NULL, NULL, NULL, 0) != 1)
|
||||||
|
pg_fatal("failed to send query: %s",
|
||||||
|
PQerrorMessage(conn));
|
||||||
|
if (PQsendFlushRequest(conn) != 1)
|
||||||
|
pg_fatal("failed to send flush request");
|
||||||
|
if (PQsetChunkedRowsMode(conn, 3) != 1)
|
||||||
|
pg_fatal("PQsetChunkedRowsMode() failed");
|
||||||
|
res = PQgetResult(conn);
|
||||||
|
if (res == NULL)
|
||||||
|
pg_fatal("unexpected NULL");
|
||||||
|
if (PQresultStatus(res) != PGRES_TUPLES_CHUNK)
|
||||||
|
pg_fatal("Expected PGRES_TUPLES_CHUNK, got %s: %s",
|
||||||
|
PQresStatus(PQresultStatus(res)),
|
||||||
|
PQerrorMessage(conn));
|
||||||
|
if (PQntuples(res) != 3)
|
||||||
|
pg_fatal("Expected 3 rows, got %d", PQntuples(res));
|
||||||
|
res = PQgetResult(conn);
|
||||||
|
if (res == NULL)
|
||||||
|
pg_fatal("unexpected NULL");
|
||||||
|
if (PQresultStatus(res) != PGRES_TUPLES_CHUNK)
|
||||||
|
pg_fatal("Expected PGRES_TUPLES_CHUNK, got %s",
|
||||||
|
PQresStatus(PQresultStatus(res)));
|
||||||
|
if (PQntuples(res) != 2)
|
||||||
|
pg_fatal("Expected 2 rows, got %d", PQntuples(res));
|
||||||
|
res = PQgetResult(conn);
|
||||||
|
if (res == NULL)
|
||||||
|
pg_fatal("unexpected NULL");
|
||||||
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
||||||
|
pg_fatal("Expected PGRES_TUPLES_OK, got %s",
|
||||||
|
PQresStatus(PQresultStatus(res)));
|
||||||
|
if (PQntuples(res) != 0)
|
||||||
|
pg_fatal("Expected 0 rows, got %d", PQntuples(res));
|
||||||
|
if (PQgetResult(conn) != NULL)
|
||||||
|
pg_fatal("expected NULL result");
|
||||||
|
|
||||||
if (PQexitPipelineMode(conn) != 1)
|
if (PQexitPipelineMode(conn) != 1)
|
||||||
pg_fatal("failed to end pipeline mode: %s", PQerrorMessage(conn));
|
pg_fatal("failed to end pipeline mode: %s", PQerrorMessage(conn));
|
||||||
|
|
||||||
|
@ -56,4 +56,18 @@ B 4 BindComplete
|
|||||||
B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
|
B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
|
||||||
B 11 DataRow 1 1 '1'
|
B 11 DataRow 1 1 '1'
|
||||||
B 13 CommandComplete "SELECT 1"
|
B 13 CommandComplete "SELECT 1"
|
||||||
|
F 36 Parse "" "SELECT generate_series(1, 5)" 0
|
||||||
|
F 14 Bind "" "" 0 0 1 0
|
||||||
|
F 6 Describe P ""
|
||||||
|
F 9 Execute "" 0
|
||||||
|
F 4 Flush
|
||||||
|
B 4 ParseComplete
|
||||||
|
B 4 BindComplete
|
||||||
|
B 40 RowDescription 1 "generate_series" NNNN 0 NNNN 4 -1 0
|
||||||
|
B 11 DataRow 1 1 '1'
|
||||||
|
B 11 DataRow 1 1 '2'
|
||||||
|
B 11 DataRow 1 1 '3'
|
||||||
|
B 11 DataRow 1 1 '4'
|
||||||
|
B 11 DataRow 1 1 '5'
|
||||||
|
B 13 CommandComplete "SELECT 5"
|
||||||
F 4 Terminate
|
F 4 Terminate
|
||||||
|
Loading…
x
Reference in New Issue
Block a user