ZeroTierOne/ext/libpqxx-7.7.3/include/pqxx/pipeline.hxx
Adam Ierymenko b1faebae4a Re-add...
2022-06-23 13:24:33 -04:00

238 lines
7.7 KiB
C++

/* Definition of the pqxx::pipeline class.
*
* Throughput-optimized mechanism for executing queries.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/pipeline instead.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#ifndef PQXX_H_PIPELINE
#define PQXX_H_PIPELINE
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <limits>
#include <map>
#include <string>
#include "pqxx/transaction_base.hxx"
namespace pqxx
{
// TODO: libpq 14 introduced a similar "pipeline mode." Can we use that?
/// Processes several queries in FIFO manner, optimized for high throughput.
/** Use a pipeline if you want to keep doing useful work while your queries are
* executing. Result retrieval is decoupled from execution request; queries
* "go in at the front" and results "come out the back."
*
* Actually, you can retrieve the results in any order if you want, but it may
* lead to surprising "time travel" effects if any of the queries fails. In
* particular, syntax errors in the queries can confuse things and show up too
* early in the stream of results.
*
* Generally, if any of the queries fails, it will throw an exception at the
* point where you request its result. But it may happen earlier, especially
* if you request results out of chronological order.
*
* @warning While a pipeline is active, you cannot execute queries, open
* streams, etc. on the same transaction. A transaction can have at most one
* object of a type derived from @ref pqxx::transaction_focus active on it at a
* time.
*/
class PQXX_LIBEXPORT pipeline : public transaction_focus
{
public:
/// Identifying numbers for queries.
using query_id = long;
pipeline(pipeline const &) = delete;
pipeline &operator=(pipeline const &) = delete;
/// Start a pipeline.
explicit pipeline(transaction_base &t) : transaction_focus{t, s_classname}
{
init();
}
/// Start a pipeline. Assign it a name, for more helpful error messages.
pipeline(transaction_base &t, std::string_view tname) :
transaction_focus{t, s_classname, tname}
{
init();
}
/// Close the pipeline.
~pipeline() noexcept;
/// Add query to the pipeline.
/** Queries accumulate in the pipeline, which sends them to the backend in a
* batch separated by semicolons. The queries you insert must not use this
* trick themselves, or the pipeline will get hopelessly confused!
*
* @return Identifier for this query, unique only within this pipeline.
*/
query_id insert(std::string_view) &;
/// Wait for all ongoing or pending operations to complete, and detach.
/** Detaches from the transaction when done.
*
* This does not produce the queries' results, so it may not report any
* errors which may have occurred in their execution. To be sure that your
* statements succeeded, call @ref retrieve until the pipeline is empty.
*/
void complete();
/// Forget all ongoing or pending operations and retrieved results.
/** Queries already sent to the backend may still be completed, depending
* on implementation and timing.
*
* Any error state (unless caused by an internal error) will also be cleared.
* This is mostly useful in a nontransaction, since a backend transaction is
* aborted automatically when an error occurs.
*
* Detaches from the transaction when done.
*/
void flush();
/// Cancel ongoing query, if any.
/** May cancel any or all of the queries that have been inserted at this
* point whose results have not yet been retrieved. If the pipeline lives in
* a backend transaction, that transaction may be left in a nonfunctional
* state in which it can only be aborted.
*
* Therefore, either use this function in a nontransaction, or abort the
* transaction after calling it.
*/
void cancel();
/// Is result for given query available?
[[nodiscard]] bool is_finished(query_id) const;
/// Retrieve result for given query.
/** If the query failed for whatever reason, this will throw an exception.
* The function will block if the query has not finished yet.
* @warning If results are retrieved out-of-order, i.e. in a different order
* than the one in which their queries were inserted, errors may "propagate"
* to subsequent queries.
*/
result retrieve(query_id qid)
{
return retrieve(m_queries.find(qid)).second;
}
/// Retrieve oldest unretrieved result (possibly wait for one).
/** @return The query's identifier and its result set. */
std::pair<query_id, result> retrieve();
[[nodiscard]] bool empty() const noexcept { return std::empty(m_queries); }
/// Set maximum number of queries to retain before issuing them to the
/// backend.
/** The pipeline will perform better if multiple queries are issued at once,
* but retaining queries until the results are needed (as opposed to issuing
* them to the backend immediately) may negate any performance benefits the
* pipeline can offer.
*
* Recommended practice is to set this value no higher than the number of
* queries you intend to insert at a time.
* @param retain_max A nonnegative "retention capacity;" passing zero will
* cause queries to be issued immediately
* @return Old retention capacity
*/
int retain(int retain_max = 2) &;
/// Resume retained query emission. Harmless when not needed.
void resume() &;
private:
struct PQXX_PRIVATE Query
{
explicit Query(std::string_view q) :
query{std::make_shared<std::string>(q)}
{}
std::shared_ptr<std::string> query;
result res;
};
using QueryMap = std::map<query_id, Query>;
void init();
void attach();
void detach();
/// Upper bound to query id's.
static constexpr query_id qid_limit() noexcept
{
// Parenthesise this to work around an eternal Visual C++ problem:
// Without the extra parentheses, unless NOMINMAX is defined, the
// preprocessor will mistake this "max" for its annoying built-in macro
// of the same name.
return (std::numeric_limits<query_id>::max)();
}
/// Create new query_id.
PQXX_PRIVATE query_id generate_id();
bool have_pending() const noexcept
{
return m_issuedrange.second != m_issuedrange.first;
}
PQXX_PRIVATE void issue();
/// The given query failed; never issue anything beyond that.
void set_error_at(query_id qid) noexcept
{
PQXX_UNLIKELY
if (qid < m_error)
m_error = qid;
}
/// Throw pqxx::internal_error.
[[noreturn]] PQXX_PRIVATE void internal_error(std::string const &err);
PQXX_PRIVATE bool obtain_result(bool expect_none = false);
PQXX_PRIVATE void obtain_dummy();
PQXX_PRIVATE void get_further_available_results();
PQXX_PRIVATE void check_end_results();
/// Receive any results that happen to be available; it's not urgent.
PQXX_PRIVATE void receive_if_available();
/// Receive results, up to stop if possible.
PQXX_PRIVATE void receive(pipeline::QueryMap::const_iterator stop);
std::pair<pipeline::query_id, result> retrieve(pipeline::QueryMap::iterator);
QueryMap m_queries;
std::pair<QueryMap::iterator, QueryMap::iterator> m_issuedrange;
int m_retain = 0;
int m_num_waiting = 0;
query_id m_q_id = 0;
/// Is there a "dummy query" pending?
bool m_dummy_pending = false;
/// Point at which an error occurred; no results beyond it will be available
query_id m_error = qid_limit();
/// Encoding.
/** We store this in the object to avoid the risk of exceptions at awkward
* moments.
*/
internal::encoding_group m_encoding;
static constexpr std::string_view s_classname{"pipeline"};
};
} // namespace pqxx
#endif